chunky_png 0.5.1 → 0.5.2

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.
@@ -3,7 +3,7 @@ Gem::Specification.new do |s|
3
3
 
4
4
  # Do not change the version and date fields by hand. This will be done
5
5
  # automatically by the gem release script.
6
- s.version = "0.5.1"
6
+ s.version = "0.5.2"
7
7
  s.date = "2010-01-16"
8
8
 
9
9
  s.summary = "Pure ruby library for read/write, chunk-level access to PNG files"
@@ -22,7 +22,7 @@ module ChunkyPNG
22
22
 
23
23
  # The current version of ChunkyPNG. This value will be updated automatically
24
24
  # by them gem:release rake task.
25
- VERSION = "0.5.1"
25
+ VERSION = "0.5.2"
26
26
 
27
27
  ###################################################
28
28
  # PNG international standard defined constants
@@ -26,8 +26,8 @@ module ChunkyPNG
26
26
 
27
27
  def adam7_merge_pass(pass, canvas, subcanvas)
28
28
  m_o = adam7_multiplier_offset(pass)
29
- 0.upto(subcanvas.height - 1) do |y|
30
- 0.upto(subcanvas.width - 1) do |x|
29
+ for y in 0...subcanvas.height do
30
+ for x in 0...subcanvas.width do
31
31
  new_x = x * m_o[:x_multiplier] + m_o[:x_offset]
32
32
  new_y = y * m_o[:y_multiplier] + m_o[:y_offset]
33
33
  canvas[new_x, new_y] = subcanvas[x, y]
@@ -5,6 +5,8 @@ module ChunkyPNG
5
5
  # The datastream can be provided as filename, string or IO object.
6
6
  module PNGDecoding
7
7
 
8
+ attr_accessor :decoding_palette
9
+
8
10
  def from_blob(str)
9
11
  from_datastream(ChunkyPNG::Datastream.from_blob(str))
10
12
  end
@@ -26,37 +28,59 @@ module ChunkyPNG
26
28
  height = ds.header_chunk.height
27
29
  color_mode = ds.header_chunk.color
28
30
  interlace = ds.header_chunk.interlace
29
- palette = ChunkyPNG::Palette.from_chunks(ds.palette_chunk, ds.transparency_chunk)
30
- stream = ChunkyPNG::Chunk::ImageData.combine_chunks(ds.data_chunks)
31
- decode_png_pixelstream(stream, width, height, color_mode, palette, interlace)
31
+
32
+ self.decoding_palette = ChunkyPNG::Palette.from_chunks(ds.palette_chunk, ds.transparency_chunk)
33
+ pixelstream = ChunkyPNG::Chunk::ImageData.combine_chunks(ds.data_chunks)
34
+
35
+ decode_png_pixelstream(pixelstream, width, height, color_mode, interlace)
32
36
  end
33
37
 
34
- def decode_png_pixelstream(stream, width, height, color_mode = ChunkyPNG::COLOR_TRUECOLOR, palette = nil, interlace = ChunkyPNG::INTERLACING_NONE)
35
- raise "This palette is not suitable for decoding!" if palette && !palette.can_decode?
36
-
37
- pixel_size = Color.bytesize(color_mode)
38
- pixel_decoder = case color_mode
39
- when ChunkyPNG::COLOR_TRUECOLOR then lambda { |bytes| ChunkyPNG::Color.rgb(*bytes) }
40
- when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then lambda { |bytes| ChunkyPNG::Color.rgba(*bytes) }
41
- when ChunkyPNG::COLOR_INDEXED then lambda { |bytes| palette[bytes.first] }
42
- when ChunkyPNG::COLOR_GRAYSCALE then lambda { |bytes| ChunkyPNG::Color.grayscale(*bytes) }
43
- when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then lambda { |bytes| ChunkyPNG::Color.grayscale(*bytes) }
44
- else raise "No suitable pixel decoder found for color mode #{color_mode}!"
45
- end
38
+ def decode_png_pixelstream(stream, width, height, color_mode = ChunkyPNG::COLOR_TRUECOLOR, interlace = ChunkyPNG::INTERLACING_NONE)
39
+ raise "This palette is not suitable for decoding!" if decoding_palette && !decoding_palette.can_decode?
46
40
 
47
41
  return case interlace
48
- when ChunkyPNG::INTERLACING_NONE then decode_png_without_interlacing(stream, width, height, pixel_size, pixel_decoder)
49
- when ChunkyPNG::INTERLACING_ADAM7 then decode_png_with_adam7_interlacing(stream, width, height, pixel_size, pixel_decoder)
42
+ when ChunkyPNG::INTERLACING_NONE then decode_png_without_interlacing(stream, width, height, color_mode)
43
+ when ChunkyPNG::INTERLACING_ADAM7 then decode_png_with_adam7_interlacing(stream, width, height, color_mode)
50
44
  else raise "Don't know how the handle interlacing method #{interlace}!"
51
45
  end
52
46
  end
53
47
 
54
48
  protected
55
49
 
56
- def decode_png_image_pass(stream, width, height, pixel_size, pixel_decoder, start_pos = 0)
57
- pixels = []
50
+ def decode_png_without_interlacing(stream, width, height, color_mode)
51
+ decode_png_image_pass(stream, width, height, color_mode)
52
+ end
58
53
 
54
+ def decode_png_with_adam7_interlacing(stream, width, height, color_mode)
55
+ canvas = ChunkyPNG::Canvas.new(width, height)
56
+ pixel_size = Color.bytesize(color_mode)
57
+ start_pos = 0
58
+ for pass in 0...7 do
59
+ sm_width, sm_height = adam7_pass_size(pass, width, height)
60
+ sm = decode_png_image_pass(stream, sm_width, sm_height, color_mode, start_pos)
61
+ adam7_merge_pass(pass, canvas, sm)
62
+ start_pos += (sm_width * sm_height * pixel_size) + sm_height
63
+ end
64
+ canvas
65
+ end
66
+
67
+ def decode_png_image_pass(stream, width, height, color_mode, start_pos = 0)
68
+
69
+ pixel_size = Color.bytesize(color_mode)
70
+ pixel_decoder = case color_mode
71
+ when ChunkyPNG::COLOR_TRUECOLOR then lambda { |bytes| ChunkyPNG::Color.rgb(*bytes) }
72
+ when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then lambda { |bytes| ChunkyPNG::Color.rgba(*bytes) }
73
+ when ChunkyPNG::COLOR_INDEXED then lambda { |bytes| decoding_palette[bytes.first] }
74
+ when ChunkyPNG::COLOR_GRAYSCALE then lambda { |bytes| ChunkyPNG::Color.grayscale(*bytes) }
75
+ when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then lambda { |bytes| ChunkyPNG::Color.grayscale_alpha(*bytes) }
76
+ else raise "No suitable pixel decoder found for color mode #{color_mode}!"
77
+ end
78
+
79
+ pixels = []
59
80
  if width > 0
81
+
82
+ raise "Invalid stream length!" unless stream.length - start_pos >= width * height * pixel_size + height
83
+
60
84
  decoded_bytes = Array.new(width * pixel_size, 0)
61
85
  for line_no in 0...height do
62
86
 
@@ -75,22 +99,7 @@ module ChunkyPNG
75
99
  new(width, height, pixels)
76
100
  end
77
101
 
78
- def decode_png_without_interlacing(stream, width, height, pixel_size, pixel_decoder)
79
- raise "Invalid stream length!" unless stream.length == width * height * pixel_size + height
80
- decode_png_image_pass(stream, width, height, pixel_size, pixel_decoder)
81
- end
82
102
 
83
- def decode_png_with_adam7_interlacing(stream, width, height, pixel_size, pixel_decoder)
84
- start_pos = 0
85
- canvas = ChunkyPNG::Canvas.new(width, height)
86
- 0.upto(6) do |pass|
87
- sm_width, sm_height = adam7_pass_size(pass, width, height)
88
- sm = decode_png_image_pass(stream, sm_width, sm_height, pixel_size, pixel_decoder, start_pos)
89
- adam7_merge_pass(pass, canvas, sm)
90
- start_pos += (sm_width * sm_height * pixel_size) + sm_height
91
- end
92
- canvas
93
- end
94
103
 
95
104
  def decode_png_scanline(filter, bytes, previous_bytes, pixelsize = 3)
96
105
  case filter
@@ -121,7 +130,7 @@ module ChunkyPNG
121
130
  bytes.each_with_index do |byte, i|
122
131
  a = (i >= pixelsize) ? bytes[i - pixelsize] : 0
123
132
  b = previous_bytes[i]
124
- bytes[i] = (byte + (a + b / 2).floor) % 256
133
+ bytes[i] = (byte + ((a + b) >> 1)) % 256
125
134
  end
126
135
  bytes
127
136
  end
@@ -5,11 +5,8 @@ module ChunkyPNG
5
5
  #
6
6
  module PNGEncoding
7
7
 
8
- TRUECOLOR_ENCODER = lambda { |color| Color.to_truecolor_bytes(color) }
9
- TRUECOLOR_ALPHA_ENCODER = lambda { |color| Color.to_truecolor_alpha_bytes(color) }
10
- GRAYSCALE_ENCODER = lambda { |color| Color.to_grayscale_bytes(color) }
11
- GRAYSCALE_ALPHA_ENCODER = lambda { |color| Color.to_grayscale_alpha_bytes(color) }
12
-
8
+ attr_accessor :encoding_palette
9
+
13
10
  def write(io, constraints = {})
14
11
  to_datastream(constraints).write(io)
15
12
  end
@@ -46,11 +43,11 @@ module ChunkyPNG
46
43
  result[:header] = { :width => width, :height => height, :color => encoding[:color_mode], :interlace => encoding[:interlace] }
47
44
 
48
45
  if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
49
- result[:palette_chunk] = encoding[:palette].to_plte_chunk
50
- result[:transparency_chunk] = encoding[:palette].to_trns_chunk unless encoding[:palette].opaque?
46
+ result[:palette_chunk] = encoding_palette.to_plte_chunk
47
+ result[:transparency_chunk] = encoding_palette.to_trns_chunk unless encoding_palette.opaque?
51
48
  end
52
49
 
53
- result[:pixelstream] = encode_png_pixelstream(encoding[:color_mode], encoding[:palette], encoding[:interlace])
50
+ result[:pixelstream] = encode_png_pixelstream(encoding[:color_mode], encoding[:interlace])
54
51
  return result
55
52
  end
56
53
 
@@ -66,10 +63,12 @@ module ChunkyPNG
66
63
 
67
64
  # Do not create a pallete when the encoding is given and does not require a palette.
68
65
  if encoding[:color_mode]
69
- encoding[:palette] ||= palette if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
66
+ if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
67
+ self.encoding_palette = self.palette
68
+ end
70
69
  else
71
- encoding[:palette] ||= palette
72
- encoding[:color_mode] ||= encoding[:palette].best_colormode
70
+ self.encoding_palette = self.palette
71
+ encoding[:color_mode] ||= encoding_palette.best_colormode
73
72
  end
74
73
 
75
74
  encoding[:interlace] = case encoding[:interlace]
@@ -81,52 +80,57 @@ module ChunkyPNG
81
80
  return encoding
82
81
  end
83
82
 
84
- def encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, palette = nil, interlace = ChunkyPNG::INTERLACING_NONE)
83
+ def encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, interlace = ChunkyPNG::INTERLACING_NONE)
85
84
 
86
- if color_mode == ChunkyPNG::COLOR_INDEXED && !palette.can_encode?
85
+ if color_mode == ChunkyPNG::COLOR_INDEXED && (encoding_palette.nil? || !encoding_palette.can_encode?)
87
86
  raise "This palette is not suitable for encoding!"
88
87
  end
89
88
 
90
- pixel_size = Color.bytesize(color_mode)
91
- pixel_encoder = case color_mode
92
- when ChunkyPNG::COLOR_TRUECOLOR then TRUECOLOR_ENCODER
93
- when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then TRUECOLOR_ALPHA_ENCODER
94
- when ChunkyPNG::COLOR_GRAYSCALE then GRAYSCALE_ENCODER
95
- when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then GRAYSCALE_ALPHA_ENCODER
96
- when ChunkyPNG::COLOR_INDEXED then lambda { |color| [palette.index(color)] }
97
- else raise "Cannot encode pixels for this mode: #{color_mode}!"
98
- end
99
-
100
89
  case interlace
101
- when ChunkyPNG::INTERLACING_NONE then encode_png_image_without_interlacing(pixel_size, pixel_encoder)
102
- when ChunkyPNG::INTERLACING_ADAM7 then encode_png_image_with_interlacing(pixel_size, pixel_encoder)
90
+ when ChunkyPNG::INTERLACING_NONE then encode_png_image_without_interlacing(color_mode)
91
+ when ChunkyPNG::INTERLACING_ADAM7 then encode_png_image_with_interlacing(color_mode)
103
92
  else raise "Unknown interlacing method!"
104
93
  end
105
94
  end
106
95
 
107
- def encode_png_image_without_interlacing(pixel_size, pixel_encoder)
96
+ def encode_png_image_without_interlacing(color_mode)
108
97
  stream = ""
109
- encode_png_image_pass_to_stream(stream, pixel_size, pixel_encoder)
98
+ encode_png_image_pass_to_stream(stream, color_mode)
110
99
  stream
111
100
  end
112
101
 
113
- def encode_png_image_with_interlacing(pixel_size, pixel_encoder)
102
+ def encode_png_image_with_interlacing(color_mode)
114
103
  stream = ""
115
104
  0.upto(6) do |pass|
116
105
  subcanvas = self.class.adam7_extract_pass(pass, self)
117
- subcanvas.encode_png_image_pass_to_stream(stream, pixel_size, pixel_encoder)
106
+ subcanvas.encoding_palette = encoding_palette
107
+ subcanvas.encode_png_image_pass_to_stream(stream, color_mode)
118
108
  end
119
109
  stream
120
110
  end
121
111
 
122
- def encode_png_image_pass_to_stream(stream, pixel_size, pixel_encoder)
123
- case pixel_encoder
124
- when TRUECOLOR_ALPHA_ENCODER
112
+ def encode_png_image_pass_to_stream(stream, color_mode)
113
+
114
+ case color_mode
115
+ when ChunkyPNG::COLOR_TRUECOLOR_ALPHA
125
116
  stream << pixels.pack("xN#{width}" * height)
126
- when TRUECOLOR_ENCODER
117
+
118
+ when ChunkyPNG::COLOR_TRUECOLOR
127
119
  line_packer = 'x' + ('NX' * width)
128
120
  stream << pixels.pack(line_packer * height)
121
+
129
122
  else
123
+
124
+ pixel_size = Color.bytesize(color_mode)
125
+ pixel_encoder = case color_mode
126
+ when ChunkyPNG::COLOR_TRUECOLOR then lambda { |color| Color.to_truecolor_bytes(color) }
127
+ when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then lambda { |color| Color.to_truecolor_alpha_bytes(color) }
128
+ when ChunkyPNG::COLOR_INDEXED then lambda { |color| [encoding_palette.index(color)] }
129
+ when ChunkyPNG::COLOR_GRAYSCALE then lambda { |color| Color.to_grayscale_bytes(color) }
130
+ when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then lambda { |color| Color.to_grayscale_alpha_bytes(color) }
131
+ else raise "Cannot encode pixels for this mode: #{color_mode}!"
132
+ end
133
+
130
134
  previous_bytes = Array.new(pixel_size * width, 0)
131
135
  each_scanline do |line|
132
136
  unencoded_bytes = line.map(&pixel_encoder).flatten
@@ -183,7 +187,7 @@ module ChunkyPNG
183
187
  for index in 0...original_bytes.length do
184
188
  a = (index >= pixelsize) ? original_bytes[index - pixelsize] : 0
185
189
  b = previous_bytes[index]
186
- encoded_bytes[index] = (original_bytes[index] - (a + b / 2).floor) % 256
190
+ encoded_bytes[index] = (original_bytes[index] - ((a + b) >> 1)) % 256
187
191
  end
188
192
  [ChunkyPNG::FILTER_AVERAGE] + encoded_bytes
189
193
  end
@@ -31,20 +31,20 @@ module ChunkyPNG
31
31
 
32
32
  # Creates a new color using an r, g, b triple.
33
33
  # @return [Fixnum] The newly constructed color value.
34
- def rgb(r, g, b, a = MAX)
35
- rgba(r, g, b, a)
34
+ def rgb(r, g, b)
35
+ r << 24 | g << 16 | b << 8 | 0xff
36
36
  end
37
37
 
38
38
  # Creates a new color using a grayscale teint.
39
39
  # @return [ChunkyPNG::Color] The newly constructed color value.
40
- def grayscale(teint, a = MAX)
41
- rgba(teint, teint, teint, a)
40
+ def grayscale(teint)
41
+ teint << 24 | teint << 16 | teint << 8 | 0xff
42
42
  end
43
43
 
44
44
  # Creates a new color using a grayscale teint and alpha value.
45
45
  # @return [Fixnum] The newly constructed color value.
46
46
  def grayscale_alpha(teint, a)
47
- rgba(teint, teint, teint, a)
47
+ teint << 24 | teint << 16 | teint << 8 | a
48
48
  end
49
49
 
50
50
  ####################################################################
@@ -43,7 +43,7 @@ describe ChunkyPNG::Canvas::PNGDecoding do
43
43
 
44
44
  it "should decode a line with average filtering correctly" do
45
45
  previous = [10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120]
46
- current = [ 0, 0, 10, 20, 10, 0, 0, 40, 10, 20, 190, 0]
46
+ current = [ 0, 0, 10, 23, 15, 13, 23, 63, 38, 60, 253, 53]
47
47
  decoded = decode_png_scanline(ChunkyPNG::FILTER_AVERAGE, current, previous)
48
48
  decoded.should == [5, 10, 25, 45, 45, 55, 80, 125, 105, 150, 114, 165]
49
49
  end
@@ -57,7 +57,7 @@ describe ChunkyPNG::Canvas::PNGEncoding do
57
57
  previous = [10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120]
58
58
  current = [ 5, 10, 25, 45, 45, 55, 80, 125, 105, 150, 114, 165]
59
59
  encoded = encode_png_scanline(ChunkyPNG::FILTER_AVERAGE, current, previous)
60
- encoded.should == [3, 0, 0, 10, 20, 10, 0, 0, 40, 10, 20, 190, 0]
60
+ encoded.should == [3, 0, 0, 10, 23, 15, 13, 23, 63, 38, 60, 253, 53]
61
61
  end
62
62
 
63
63
  it "should encode a scanline with paeth filtering correctly" do
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe ChunkyPNG::Canvas do
4
4
 
5
5
  describe '.from_rgb_stream' do
6
- it "should load an image correctly from a datastrean" do
6
+ it "should load an image correctly from a datastream" do
7
7
  File.open(resource_file('pixelstream.rgb')) do |stream|
8
8
  matrix = ChunkyPNG::Canvas.from_rgb_stream(240, 180, stream)
9
9
  matrix.should == reference_canvas('pixelstream_reference')
@@ -12,7 +12,7 @@ describe ChunkyPNG::Canvas do
12
12
  end
13
13
 
14
14
  describe '.from_rgba_stream' do
15
- it "should load an image correctly from a datastrean" do
15
+ it "should load an image correctly from a datastream" do
16
16
  File.open(resource_file('pixelstream.rgba')) do |stream|
17
17
  matrix = ChunkyPNG::Canvas.from_rgba_stream(240, 180, stream)
18
18
  matrix.should == reference_canvas('pixelstream_reference')
@@ -21,16 +21,24 @@ describe ChunkyPNG::Canvas do
21
21
  end
22
22
 
23
23
  describe '#to_rgba_stream' do
24
- it "should load an image correctly from a datastrean" do
24
+ before (:each) do
25
+ File.open(resource_file('pixelstream.rgba'), 'rb') { |f| @reference_data = f.read }
26
+ end
27
+
28
+ it "should load an image correctly from a datastream" do
25
29
  canvas = reference_canvas('pixelstream_reference')
26
- canvas.to_rgba_stream.should == File.read(resource_file('pixelstream.rgba'))
30
+ canvas.to_rgba_stream.should == @reference_data
27
31
  end
28
32
  end
29
33
 
30
34
  describe '#to_rgb_stream' do
31
- it "should load an image correctly from a datastrean" do
35
+ before (:each) do
36
+ File.open(resource_file('pixelstream.rgb'), 'rb') { |f| @reference_data = f.read }
37
+ end
38
+
39
+ it "should load an image correctly from a datastream" do
32
40
  canvas = reference_canvas('pixelstream_reference')
33
- canvas.to_rgb_stream.should == File.read(resource_file('pixelstream.rgb'))
41
+ canvas.to_rgb_stream.should == @reference_data
34
42
  end
35
43
  end
36
44
 
@@ -13,9 +13,7 @@ describe ChunkyPNG::RMagick do
13
13
  it "should export an image to RMagick correctly" do
14
14
  canvas = reference_canvas('16x16_non_interlaced')
15
15
  image = ChunkyPNG::RMagick.export(canvas)
16
-
17
- filename = resource_file('_rmagick.png')
18
- image.write("png:#{filename}") { |info| info.depth = 8 }
19
- canvas.should == ChunkyPNG::Canvas.from_file(filename)
16
+ image.format = 'PNG32'
17
+ canvas.should == ChunkyPNG::Canvas.from_blob(image.to_blob)
20
18
  end
21
19
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chunky_png
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willem van Bergen