chunky_png 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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