oily_png 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,7 +1,8 @@
1
1
  Makefile
2
2
  /conftest.dSYM
3
- /*.bundle
4
- /*.o
3
+ *.bundle
4
+ *.o
5
5
  /.bundle/
6
6
  /pkg
7
7
  oily_png-*.gem
8
+ .DS_Store
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oily_png (0.0.2)
4
+ oily_png (0.0.3)
5
5
  chunky_png (~> 0.10.1)
6
6
 
7
7
  GEM
@@ -1,9 +1,26 @@
1
- === OilyPNG
1
+ = OilyPNG
2
2
 
3
- Native module to speed up ChunkyPNG.
3
+ OilyPNG is a Ruby C extension to speed up the pure Ruby ChunkyPNG library.
4
4
 
5
- * Install this gem
6
- * Use require "oily_png" instead of require"chunky_png"
7
- * Use the ChunkyPNG API as you normally would.
8
- * Presto!
5
+ Currently it has an alternative implementation of decoding PNGs, making that operation much
6
+ faster for PNG images that apply filtering. An alternative implementation for encoding is
7
+ planned.
8
+
9
+ *Warning*: this is my first C code in years. It may blow up your PC after leaking memory all
10
+ over the place, killing a kitten in the process. You have been warned.
11
+
12
+ == Usage
13
+
14
+ 1. First install the gem and make it available to your project.
15
+ 2. Use require "oily_png" instead of require "chunky_png"
16
+ 3. Presto! Now use ChunkyPNG as you normally would and get an instant speedup.
17
+
18
+ See http://github.com/wvanbergen/chunky_png/wiki for more information on how to use the
19
+ ChunkyPNG API.
20
+
21
+ == About
22
+
23
+ License: MIT (see LICENSE)
24
+
25
+ This C module is written by Willem van Bergen with help from Dirkjan Bussink.
9
26
 
@@ -1,11 +1,2 @@
1
- # Loads mkmf which is used to make makefiles for Ruby extensions
2
1
  require 'mkmf'
3
-
4
- # paths = ['/opt/local', '/usr/local']
5
- # lib_paths = paths.map { |p| p + '/lib' }
6
- # inc_paths = paths.map { |p| p + '/include' }
7
-
8
- # exit(1) unless find_library('png', 'png_get_io_ptr', *lib_paths) && find_header('png.h', *inc_paths)
9
-
10
- # Create a makefile
11
- create_makefile('oily_png/decoding')
2
+ create_makefile('oily_png/png_decoding')
@@ -1,65 +1,65 @@
1
1
  #include "ruby.h"
2
2
 
3
- // Variable for the OilyPNG module
4
- VALUE OilyPNG = Qnil;
3
+ // PNG color mode constants
4
+ #define OILY_PNG_COLOR_GRAYSCALE 0
5
+ #define OILY_PNG_COLOR_TRUECOLOR 2
6
+ #define OILY_PNG_COLOR_INDEXED 3
7
+ #define OILY_PNG_COLOR_GRAYSCALE_ALPHA 4
8
+ #define OILY_PNG_COLOR_TRUECOLOR_ALPHA 6
5
9
 
6
- void Init_decoding();
10
+ // PNG filter constants
11
+ #define OILY_PNG_FILTER_NONE 0
12
+ #define OILY_PNG_FILTER_SUB 1
13
+ #define OILY_PNG_FILTER_UP 2
14
+ #define OILY_PNG_FILTER_AVERAGE 3
15
+ #define OILY_PNG_FILTER_PAETH 4
7
16
 
17
+ // Type definitions
18
+ #define PIXEL unsigned int
19
+
20
+ void Init_png_decoding();
8
21
  VALUE oily_png_decode_png_image_pass(VALUE self, VALUE stream, VALUE width, VALUE height, VALUE color_mode, VALUE start_pos);
9
22
 
10
- // COLOR_GRAYSCALE = 0
11
- // COLOR_TRUECOLOR = 2
12
- // COLOR_INDEXED = 3
13
- // COLOR_GRAYSCALE_ALPHA = 4
14
- // COLOR_TRUECOLOR_ALPHA = 6
15
- //
16
- // FILTERING_DEFAULT = 0
17
- //
18
- // COMPRESSION_DEFAULT = 0
19
- //
20
- // INTERLACING_NONE = 0
21
- // INTERLACING_ADAM7 = 1
22
- //
23
- // FILTER_NONE = 0
24
- // FILTER_SUB = 1
25
- // FILTER_UP = 2
26
- // FILTER_AVERAGE = 3
27
- // FILTER_PAETH = 4
23
+ ////////////////////////////////////////////////////////////////////////////
28
24
 
29
- void Init_decoding() {
25
+ // Initialize the extension by creating the OilyPNG::PNGDecoding module
26
+ void Init_png_decoding() {
30
27
  VALUE OilyPNG = rb_define_module("OilyPNG");
31
- VALUE OilyPNGDecoding = rb_define_module_under(OilyPNG, "PNGDecoding");
32
- rb_define_method(OilyPNGDecoding, "decode_png_image_pass", oily_png_decode_png_image_pass, 5);
28
+ VALUE OilyPNGPNGDecoding = rb_define_module_under(OilyPNG, "PNGDecoding");
29
+ rb_define_method(OilyPNGPNGDecoding, "decode_png_image_pass", oily_png_decode_png_image_pass, 5);
33
30
  }
34
31
 
32
+ // Returns the number of bytes per pixel for a given color mode.
35
33
  int oily_png_pixel_size(color_mode) {
36
34
  switch (color_mode) {
37
- case 0: return 1;
38
- case 2: return 3;
39
- case 3: return 1;
40
- case 4: return 2;
41
- case 6: return 4;
35
+ case OILY_PNG_COLOR_GRAYSCALE: return 1;
36
+ case OILY_PNG_COLOR_TRUECOLOR: return 3;
37
+ case OILY_PNG_COLOR_INDEXED: return 1;
38
+ case OILY_PNG_COLOR_GRAYSCALE_ALPHA: return 2;
39
+ case OILY_PNG_COLOR_TRUECOLOR_ALPHA: return 4;
42
40
  default: return -1;
43
41
  }
44
42
  }
45
43
 
46
- unsigned int oily_png_decode_pixel(int color_mode, unsigned char* bytes, int byte_index, VALUE decoding_palette) {
44
+ // Decodes a pixel at the given position in the bytearray
45
+ PIXEL oily_png_decode_pixel(int color_mode, unsigned char* bytes, int byte_index, VALUE decoding_palette) {
47
46
  switch (color_mode) {
48
- case 0:
47
+ case OILY_PNG_COLOR_GRAYSCALE:
49
48
  return (bytes[byte_index] << 24) + (bytes[byte_index] << 16) + (bytes[byte_index] << 8) + 0xff;
50
- case 2:
49
+ case OILY_PNG_COLOR_TRUECOLOR:
51
50
  return (bytes[byte_index] << 24) + (bytes[byte_index + 1] << 16) + (bytes[byte_index + 2] << 8) + 0xff;
52
- case 3:
51
+ case OILY_PNG_COLOR_INDEXED:
53
52
  return NUM2UINT(rb_funcall(decoding_palette, rb_intern("[]"), 1, INT2FIX(bytes[byte_index])));
54
- case 4:
53
+ case OILY_PNG_COLOR_GRAYSCALE_ALPHA:
55
54
  return (bytes[byte_index] << 24) + (bytes[byte_index] << 16) + (bytes[byte_index] << 8) + bytes[byte_index + 1];
56
- case 6:
55
+ case OILY_PNG_COLOR_TRUECOLOR_ALPHA:
57
56
  return (bytes[byte_index] << 24) + (bytes[byte_index + 1] << 16) + (bytes[byte_index + 2] << 8) + bytes[byte_index + 3];
58
57
  default:
59
58
  exit(1);
60
59
  }
61
60
  }
62
61
 
62
+ // Decodes a SUB filtered scanline at the given position in the byte array
63
63
  void oily_png_filter_sub(unsigned char* bytes, int pos, int line_length, int pixel_size) {
64
64
  int i;
65
65
  for (i = 1 + pixel_size; i < line_length; i++) {
@@ -67,8 +67,10 @@ void oily_png_filter_sub(unsigned char* bytes, int pos, int line_length, int pix
67
67
  }
68
68
  }
69
69
 
70
+ // Decodes an UP filtered scanline at the given position in the byte array
70
71
  void oily_png_filter_up(unsigned char* bytes, int pos, int line_length, int pixel_size) {
71
72
  int i;
73
+ // The first line is not filtered because there is no privous line
72
74
  if (pos >= line_length) {
73
75
  for (i = 1; i < line_length; i++) {
74
76
  bytes[pos + i] += bytes[pos + i - line_length]; // mod 256 ???
@@ -76,33 +78,37 @@ void oily_png_filter_up(unsigned char* bytes, int pos, int line_length, int pixe
76
78
  }
77
79
  }
78
80
 
81
+ // Decodes an AVERAGE filtered scanline at the given position in the byte array
79
82
  void oily_png_filter_average(unsigned char* bytes, int pos, int line_length, int pixel_size) {
80
83
  int i;
81
84
  unsigned char a, b;
82
85
  for (i = 1; i < line_length; i++) {
83
86
  a = (i > pixel_size) ? bytes[pos + i - pixel_size] : 0;
84
87
  b = (pos >= line_length) ? bytes[pos + i - line_length] : 0;
85
- bytes[pos + i] += (a + b) >> 1;
88
+ bytes[pos + i] += (a + b) >> 1; // mod 256 ???
86
89
  }
87
90
  }
88
91
 
92
+ // Decodes a PAETH filtered scanline at the given position in the byte array
89
93
  void oily_png_filter_paeth(unsigned char* bytes, int pos, int line_length, int pixel_size) {
90
94
  unsigned char a, b, c, pr;
91
95
  int i, p, pa, pb, pc;
92
96
  for (i = 1; i < line_length; i++) {
93
97
  a = (i > pixel_size) ? bytes[pos + i - pixel_size] : 0;
94
98
  b = (pos >= line_length) ? bytes[pos + i - line_length] : 0;
95
- c = (pos >= line_length && i > pixel_size) ? bytes[pos + i - line_length - pixel_size] : 0;
99
+ c = (pos >= line_length && i > pixel_size) ? bytes[pos + i - line_length - pixel_size] : 0;
96
100
  p = a + b - c;
97
101
  pa = abs(p - a);
98
102
  pb = abs(p - b);
99
103
  pc = abs(p - c);
100
104
  pr = (pa <= pb) ? (pa <= pc ? a : c) : (pb <= pc ? b : c);
101
- bytes[pos + i] += pr;
105
+ bytes[pos + i] += pr; // mod 256 ???
102
106
  }
103
107
  }
104
108
 
105
-
109
+ // Decodes an image pass from the given byte stream at the given position.
110
+ // A normal PNG will only have one pass that consumes the entire stream, while an
111
+ // interlaced image requires 7 passes which are loaded from different starting positions.
106
112
  VALUE oily_png_decode_png_image_pass(VALUE self, VALUE stream, VALUE width, VALUE height, VALUE color_mode, VALUE start_pos) {
107
113
 
108
114
  int pixel_size = oily_png_pixel_size(FIX2INT(color_mode));
@@ -112,31 +118,37 @@ VALUE oily_png_decode_png_image_pass(VALUE self, VALUE stream, VALUE width, VALU
112
118
  VALUE pixels = rb_ary_new();
113
119
 
114
120
  VALUE decoding_palette = Qnil;
115
- if (FIX2INT(color_mode) == 3) {
121
+ if (FIX2INT(color_mode) == OILY_PNG_COLOR_INDEXED) {
116
122
  decoding_palette = rb_funcall(self, rb_intern("decoding_palette"), 0);
117
123
  }
118
124
 
125
+ // Copy the bytes for this pass from the stream to a separate location
126
+ // so we can work on this byte array directly.
119
127
  unsigned char* pixelstream = RSTRING_PTR(stream);
120
128
  unsigned char* bytes = malloc(pass_size);
121
129
  memcpy(bytes, pixelstream + FIX2INT(start_pos), pass_size);
122
130
 
123
131
  int y, x, line_start, prev_line_start, byte_index, pixel_index;
124
132
  unsigned char filter;
125
- unsigned int pixel;
133
+ PIXEL pixel;
126
134
 
127
135
  for (y = 0; y < FIX2INT(height); y++) {
128
136
  line_start = y * line_size;
129
- filter = bytes[line_start];
130
137
 
131
- switch (filter) {
132
- case 0: break;
133
- case 1: oily_png_filter_sub( bytes, line_start, line_size, pixel_size); break;
134
- case 2: oily_png_filter_up( bytes, line_start, line_size, pixel_size); break;
135
- case 3: oily_png_filter_average( bytes, line_start, line_size, pixel_size); break;
136
- case 4: oily_png_filter_paeth( bytes, line_start, line_size, pixel_size); break;
138
+ // Apply filering to the line
139
+ switch (bytes[line_start]) {
140
+ case OILY_PNG_FILTER_NONE: break;
141
+ case OILY_PNG_FILTER_SUB: oily_png_filter_sub( bytes, line_start, line_size, pixel_size); break;
142
+ case OILY_PNG_FILTER_UP: oily_png_filter_up( bytes, line_start, line_size, pixel_size); break;
143
+ case OILY_PNG_FILTER_AVERAGE: oily_png_filter_average( bytes, line_start, line_size, pixel_size); break;
144
+ case OILY_PNG_FILTER_PAETH: oily_png_filter_paeth( bytes, line_start, line_size, pixel_size); break;
137
145
  default: exit(1);
138
146
  }
139
147
 
148
+ // Set the filter byte to 0 because the bytearray is now unfiltered.
149
+ bytes[line_start] = OILY_PNG_FILTER_NONE;
150
+
151
+ // Now, iterate over all bytes in this line and combine them into pixels
140
152
  for (x = 0; x < FIX2INT(width); x++) {
141
153
  pixel_index = FIX2INT(width) * y + x;
142
154
  byte_index = line_start + 1 + (x * pixel_size);
@@ -145,7 +157,10 @@ VALUE oily_png_decode_png_image_pass(VALUE self, VALUE stream, VALUE width, VALU
145
157
  }
146
158
  }
147
159
 
160
+ // Get rid of the byte array.
148
161
  free(bytes);
162
+
163
+ // Now, return a new ChunkyPNG::Canvas instance with the decoded pixels.
149
164
  return rb_funcall(self, rb_intern("new"), 3, width, height, pixels);
150
165
  }
151
166
 
@@ -1,9 +1,9 @@
1
1
  require 'chunky_png'
2
- require 'oily_png/decoding'
2
+ require 'oily_png/png_decoding'
3
3
 
4
4
  module OilyPNG
5
5
 
6
- VERSION = "0.0.2"
6
+ VERSION = "0.0.3"
7
7
 
8
8
  def self.included(base)
9
9
  base::Canvas.extend OilyPNG::PNGDecoding
@@ -4,7 +4,7 @@ Gem::Specification.new do |s|
4
4
 
5
5
  # Do not change the version and date fields by hand. This will be done
6
6
  # automatically by the gem release script.
7
- s.version = "0.0.2"
7
+ s.version = "0.0.3"
8
8
  s.date = "2010-10-05"
9
9
 
10
10
  s.summary = "Native mixin to speed up ChunkyPNG"
@@ -30,6 +30,6 @@ Gem::Specification.new do |s|
30
30
 
31
31
  # Do not change the files and test_files fields by hand. This will be done
32
32
  # automatically by the gem release script.
33
- s.files = %w(.gitignore Gemfile Gemfile.lock LICENSE README.rdoc Rakefile ext/oily_png/decoding.c ext/oily_png/extconf.rb lib/oily_png.rb oily_png.gemspec tasks/github-gem.rake)
34
- s.test_files = %w()
33
+ s.files = %w(.gitignore Gemfile Gemfile.lock LICENSE README.rdoc Rakefile ext/oily_png/extconf.rb ext/oily_png/png_decoding.c lib/oily_png.rb oily_png.gemspec spec/decoding_spec.rb spec/resources/gray.png spec/resources/operations.png spec/spec_helper.rb tasks/github-gem.rake)
34
+ s.test_files = %w(spec/decoding_spec.rb)
35
35
  end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe OilyPNG::PNGDecoding do
4
+
5
+ it "should call Color.bytesize in the pure ruby version" do
6
+ ChunkyPNG::Color.should_receive(:bytesize).and_return(3)
7
+ ChunkyPNG::Canvas.from_file(resource_file('operations.png'))
8
+ end
9
+
10
+ it "should not call Color.bytesize in the native version" do
11
+ ChunkyPNG::Color.should_not_receive(:bytesize)
12
+ OilyCanvas.from_file(resource_file('operations.png'))
13
+ end
14
+
15
+ context 'decoding different filtering methods' do
16
+ before(:all) { @reference = ChunkyPNG::Canvas.from_file(resource_file('operations.png'))}
17
+
18
+ it "should decode NONE filtering exactly the same as ChunkyPNG" do
19
+ filtered_data = @reference.to_blob(:filtering => ChunkyPNG::FILTER_NONE)
20
+ ChunkyPNG::Canvas.from_blob(filtered_data).should == OilyCanvas.from_blob(filtered_data)
21
+ end
22
+
23
+ it "should decode SUB filtering exactly the same as ChunkyPNG" do
24
+ filtered_data = @reference.to_blob(:filtering => ChunkyPNG::FILTER_SUB)
25
+ ChunkyPNG::Canvas.from_blob(filtered_data).should == OilyCanvas.from_blob(filtered_data)
26
+ end
27
+
28
+ it "should decode UP filtering exactly the same as ChunkyPNG" do
29
+ filtered_data = @reference.to_blob(:filtering => ChunkyPNG::FILTER_UP)
30
+ ChunkyPNG::Canvas.from_blob(filtered_data).should == OilyCanvas.from_blob(filtered_data)
31
+ end
32
+
33
+ it "should decode AVERAGE filtering exactly the same as ChunkyPNG" do
34
+ filtered_data = @reference.to_blob(:filtering => ChunkyPNG::FILTER_AVERAGE)
35
+ ChunkyPNG::Canvas.from_blob(filtered_data).should == OilyCanvas.from_blob(filtered_data)
36
+ end
37
+
38
+ it "should decode PAETH filtering exactly the same as ChunkyPNG" do
39
+ filtered_data = @reference.to_blob(:filtering => ChunkyPNG::FILTER_PAETH)
40
+ ChunkyPNG::Canvas.from_blob(filtered_data).should == OilyCanvas.from_blob(filtered_data)
41
+ end
42
+ end
43
+
44
+ context 'decoding different color modes' do
45
+ before(:all) { @reference = ChunkyPNG::Canvas.from_file(resource_file('gray.png'))}
46
+
47
+ it "should decode RGBA images the same as ChunkyPNG" do
48
+ filtered_data = @reference.to_blob(:color_mode => ChunkyPNG::COLOR_TRUECOLOR_ALPHA)
49
+ ChunkyPNG::Canvas.from_blob(filtered_data).should == OilyCanvas.from_blob(filtered_data)
50
+ end
51
+
52
+ it "should decode RGB images exactly the same as ChunkyPNG" do
53
+ filtered_data = @reference.to_blob(:color_mode => ChunkyPNG::COLOR_TRUECOLOR)
54
+ ChunkyPNG::Canvas.from_blob(filtered_data).should == OilyCanvas.from_blob(filtered_data)
55
+ end
56
+
57
+ it "should decode indexed images exactly the same as ChunkyPNG" do
58
+ filtered_data = @reference.to_blob(:color_mode => ChunkyPNG::COLOR_INDEXED)
59
+ ChunkyPNG::Canvas.from_blob(filtered_data).should == OilyCanvas.from_blob(filtered_data)
60
+ end
61
+
62
+ it "should decode grayscale images exactly the same as ChunkyPNG" do
63
+ filtered_data = @reference.to_blob(:color_mode => ChunkyPNG::COLOR_GRAYSCALE)
64
+ ChunkyPNG::Canvas.from_blob(filtered_data).should == OilyCanvas.from_blob(filtered_data)
65
+ end
66
+
67
+ it "should decode grayscale images with transparency the same as ChunkyPNG" do
68
+ filtered_data = @reference.to_blob(:color_mode => ChunkyPNG::COLOR_GRAYSCALE_ALPHA)
69
+ ChunkyPNG::Canvas.from_blob(filtered_data).should == OilyCanvas.from_blob(filtered_data)
70
+ end
71
+ end
72
+ end
Binary file
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.setup
5
+
6
+ require 'spec'
7
+ require 'chunky_png'
8
+ require 'oily_png/png_decoding'
9
+
10
+ class OilyCanvas < ChunkyPNG::Canvas
11
+ extend OilyPNG::PNGDecoding
12
+ end
13
+
14
+ module ResourceHelper
15
+ def resource_files
16
+ Dir[File.join(File.dirname(__FILE__), 'resources', '*.png')]
17
+ end
18
+ end
19
+
20
+ module CanvasHelper
21
+
22
+ def resource_file(name)
23
+ File.join(File.dirname(__FILE__), 'resources', name)
24
+ end
25
+
26
+ def display(canvas)
27
+ filename = resource_file('_tmp.png')
28
+ canvas.to_datastream.save(filename)
29
+ `open #{filename}`
30
+ end
31
+
32
+ def reference_canvas(name)
33
+ ChunkyPNG::Canvas.from_file(resource_file("#{name}.png"))
34
+ end
35
+ end
36
+
37
+ Spec::Runner.configure do |config|
38
+ config.extend ResourceHelper
39
+ config.include CanvasHelper
40
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oily_png
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 25
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 2
10
- version: 0.0.2
9
+ - 3
10
+ version: 0.0.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Willem van Bergen
@@ -79,10 +79,14 @@ files:
79
79
  - LICENSE
80
80
  - README.rdoc
81
81
  - Rakefile
82
- - ext/oily_png/decoding.c
83
82
  - ext/oily_png/extconf.rb
83
+ - ext/oily_png/png_decoding.c
84
84
  - lib/oily_png.rb
85
85
  - oily_png.gemspec
86
+ - spec/decoding_spec.rb
87
+ - spec/resources/gray.png
88
+ - spec/resources/operations.png
89
+ - spec/spec_helper.rb
86
90
  - tasks/github-gem.rake
87
91
  has_rdoc: true
88
92
  homepage: http://wiki.github.com/wvanbergen/oily_png
@@ -126,5 +130,5 @@ rubygems_version: 1.3.7
126
130
  signing_key:
127
131
  specification_version: 3
128
132
  summary: Native mixin to speed up ChunkyPNG
129
- test_files: []
130
-
133
+ test_files:
134
+ - spec/decoding_spec.rb