oily_png 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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