chunky_png 0.9.2 → 0.10.0

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.
@@ -6,9 +6,9 @@ module ChunkyPNG
6
6
  # Overview of the encoding process:
7
7
  #
8
8
  # * The image is split up in scanlines (i.e. rows of pixels);
9
- # * Every pixel in this row is converted into bytes, based on the color mode;
10
- # * Filter every byte in the row according to the filter method.
11
- # * Concatenate all the filtered bytes of every line to a single stream
9
+ # * All pixels are encoded as a pixelstream, based on the color mode.
10
+ # * All the pixel bytes in the pixelstream are adjusted using a filtering
11
+ # method if one is specified.
12
12
  # * Compress the resulting string using deflate compression.
13
13
  # * Split compressed data over one or more PNG chunks.
14
14
  # * These chunks should be embedded in a datastream with at least a IHDR and
@@ -48,17 +48,17 @@ module ChunkyPNG
48
48
  to_datastream(constraints).to_blob
49
49
  end
50
50
 
51
- alias :to_string :to_blob
52
- alias :to_s :to_blob
51
+ alias_method :to_string, :to_blob
52
+ alias_method :to_s, :to_blob
53
53
 
54
54
  # Converts this Canvas to a datastream, so that it can be saved as a PNG image.
55
55
  # @param [Hash, Symbol] constraints The constraints to use when encoding the canvas.
56
56
  # This can either be a hash with different constraints, or a symbol which acts as a
57
57
  # preset for some constraints. If no constraints are given, ChunkyPNG will decide
58
58
  # for itself how to best create the PNG datastream.
59
- # Supported presets are :fast_rgba for quickly saving images with transparency,
60
- # :fast_rgb for quickly saving opaque images, and :best_compression to obtain the
61
- # smallest possible filesize.
59
+ # Supported presets are <tt>:fast_rgba</tt> for quickly saving images with transparency,
60
+ # <tt>:fast_rgb</tt> for quickly saving opaque images, and <tt>:best_compression</tt> to
61
+ # obtain the smallest possible filesize.
62
62
  # @option constraints [Fixnum] :color_mode The color mode to use. Use one of the
63
63
  # ChunkyPNG::COLOR_* constants.
64
64
  # @option constraints [true, false] :interlace Whether to use interlacing.
@@ -68,17 +68,16 @@ module ChunkyPNG
68
68
  # @see ChunkyPNG::Canvas::PNGEncoding#determine_png_encoding
69
69
  def to_datastream(constraints = {})
70
70
  encoding = determine_png_encoding(constraints)
71
-
71
+
72
72
  ds = Datastream.new
73
73
  ds.header_chunk = Chunk::Header.new(:width => width, :height => height,
74
74
  :color => encoding[:color_mode], :interlace => encoding[:interlace])
75
-
75
+
76
76
  if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
77
77
  ds.palette_chunk = encoding_palette.to_plte_chunk
78
78
  ds.transparency_chunk = encoding_palette.to_trns_chunk unless encoding_palette.opaque?
79
79
  end
80
-
81
- data = encode_png_pixelstream(encoding[:color_mode], encoding[:interlace], encoding[:compression])
80
+ data = encode_png_pixelstream(encoding[:color_mode], encoding[:interlace], encoding[:filtering])
82
81
  ds.data_chunks = Chunk::ImageData.split_in_chunks(data, encoding[:compression])
83
82
  ds.end_chunk = Chunk::End.new
84
83
  return ds
@@ -96,17 +95,21 @@ module ChunkyPNG
96
95
  # Hash or a preset symbol.
97
96
  # @return [Hash] A hash with encoding options for {ChunkyPNG::Canvas::PNGEncoding#to_datastream}
98
97
  def determine_png_encoding(constraints = {})
99
-
98
+
100
99
  if constraints == :fast_rgb
101
100
  encoding = { :color_mode => ChunkyPNG::COLOR_TRUECOLOR, :compression => Zlib::BEST_SPEED }
102
101
  elsif constraints == :fast_rgba
103
102
  encoding = { :color_mode => ChunkyPNG::COLOR_TRUECOLOR_ALPHA, :compression => Zlib::BEST_SPEED }
104
103
  elsif constraints == :best_compression
105
- encoding = { :compression => Zlib::BEST_COMPRESSION }
104
+ encoding = { :compression => Zlib::BEST_COMPRESSION, :filtering => ChunkyPNG::FILTER_PAETH }
105
+ elsif constraints == :good_compression
106
+ encoding = { :compression => Zlib::BEST_COMPRESSION, :filtering => ChunkyPNG::FILTER_NONE }
107
+ elsif constraints == :no_compression
108
+ encoding = { :compression => Zlib::NO_COMPRESSION }
106
109
  else
107
110
  encoding = constraints
108
111
  end
109
-
112
+
110
113
  # Do not create a pallete when the encoding is given and does not require a palette.
111
114
  if encoding[:color_mode]
112
115
  if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
@@ -116,47 +119,52 @@ module ChunkyPNG
116
119
  self.encoding_palette = self.palette
117
120
  encoding[:color_mode] ||= encoding_palette.best_colormode
118
121
  end
119
-
122
+
123
+ # Use Zlib's default for compression unless otherwise provided.
120
124
  encoding[:compression] ||= Zlib::DEFAULT_COMPRESSION
121
-
125
+
122
126
  encoding[:interlace] = case encoding[:interlace]
123
- when nil, false, ChunkyPNG::INTERLACING_NONE then ChunkyPNG::INTERLACING_NONE
124
- when true, ChunkyPNG::INTERLACING_ADAM7 then ChunkyPNG::INTERLACING_ADAM7
127
+ when nil, false, ChunkyPNG::INTERLACING_NONE; ChunkyPNG::INTERLACING_NONE
128
+ when true, ChunkyPNG::INTERLACING_ADAM7; ChunkyPNG::INTERLACING_ADAM7
125
129
  else encoding[:interlace]
126
130
  end
127
131
 
132
+ encoding[:filtering] ||= case encoding[:compression]
133
+ when Zlib::BEST_COMPRESSION; ChunkyPNG::FILTER_PAETH
134
+ when Zlib::NO_COMPRESSION..Zlib::BEST_SPEED; ChunkyPNG::FILTER_NONE
135
+ else ChunkyPNG::FILTER_UP
136
+ end
128
137
  return encoding
129
138
  end
130
-
139
+
131
140
  # Encodes the canvas according to the PNG format specification with a given color
132
141
  # mode, possibly with interlacing.
133
142
  # @param [Integer] color_mode The color mode to use for encoding.
134
143
  # @param [Integer] interlace The interlacing method to use.
135
- # @param [Integer] compression The Zlib compression level.
136
144
  # @return [String] The PNG encoded canvas as string.
137
- def encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, interlace = ChunkyPNG::INTERLACING_NONE, compression = ZLib::DEFAULT_COMPRESSION)
145
+ def encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, interlace = ChunkyPNG::INTERLACING_NONE, filtering = ChunkyPNG::FILTER_NONE)
138
146
 
139
147
  if color_mode == ChunkyPNG::COLOR_INDEXED && (encoding_palette.nil? || !encoding_palette.can_encode?)
140
148
  raise ChunkyPNG::ExpectationFailed, "This palette is not suitable for encoding!"
141
149
  end
142
150
 
143
151
  case interlace
144
- when ChunkyPNG::INTERLACING_NONE then encode_png_image_without_interlacing(color_mode, compression)
145
- when ChunkyPNG::INTERLACING_ADAM7 then encode_png_image_with_interlacing(color_mode, compression)
152
+ when ChunkyPNG::INTERLACING_NONE then encode_png_image_without_interlacing(color_mode, filtering)
153
+ when ChunkyPNG::INTERLACING_ADAM7 then encode_png_image_with_interlacing(color_mode, filtering)
146
154
  else raise ChunkyPNG::NotSupported, "Unknown interlacing method: #{interlace}!"
147
155
  end
148
156
  end
149
157
 
150
158
  # Encodes the canvas according to the PNG format specification with a given color mode.
151
159
  # @param [Integer] color_mode The color mode to use for encoding.
152
- # @param [Integer] compression The Zlib compression level.
160
+ # @param [Integer] filtering The filtering method to use.
153
161
  # @return [String] The PNG encoded canvas as string.
154
- def encode_png_image_without_interlacing(color_mode, compression = ZLib::DEFAULT_COMPRESSION)
162
+ def encode_png_image_without_interlacing(color_mode, filtering = ChunkyPNG::FILTER_NONE)
155
163
  stream = ""
156
- encode_png_image_pass_to_stream(stream, color_mode, compression)
164
+ encode_png_image_pass_to_stream(stream, color_mode, filtering)
157
165
  stream
158
166
  end
159
-
167
+
160
168
  # Encodes the canvas according to the PNG format specification with a given color
161
169
  # mode and Adam7 interlacing.
162
170
  #
@@ -164,141 +172,121 @@ module ChunkyPNG
164
172
  # one by one, concatenating the resulting strings.
165
173
  #
166
174
  # @param [Integer] color_mode The color mode to use for encoding.
167
- # @param [Integer] compression The Zlib compression level.
175
+ # @param [Integer] filtering The filtering method to use.
168
176
  # @return [String] The PNG encoded canvas as string.
169
- def encode_png_image_with_interlacing(color_mode, compression = ZLib::DEFAULT_COMPRESSION)
177
+ def encode_png_image_with_interlacing(color_mode, filtering = ChunkyPNG::FILTER_NONE)
170
178
  stream = ""
171
179
  0.upto(6) do |pass|
172
180
  subcanvas = self.class.adam7_extract_pass(pass, self)
173
181
  subcanvas.encoding_palette = encoding_palette
174
- subcanvas.encode_png_image_pass_to_stream(stream, color_mode, compression)
182
+ subcanvas.encode_png_image_pass_to_stream(stream, color_mode, filtering)
175
183
  end
176
184
  stream
177
185
  end
178
-
186
+
179
187
  # Encodes the canvas to a stream, in a given color mode.
180
- # @param [String, IO, :<<] stream The stream to write to.
188
+ # @param [String] stream The stream to write to.
181
189
  # @param [Integer] color_mode The color mode to use for encoding.
182
- # @param [Integer] compression The Zlib compression level.
183
- def encode_png_image_pass_to_stream(stream, color_mode, compression = ZLib::DEFAULT_COMPRESSION)
190
+ # @param [Integer] filtering The filtering method to use.
191
+ def encode_png_image_pass_to_stream(stream, color_mode, filtering = ChunkyPNG::FILTER_NONE)
184
192
 
185
- if compression < Zlib::BEST_COMPRESSION && color_mode == ChunkyPNG::COLOR_TRUECOLOR_ALPHA
186
- # Fast RGBA saving routine
187
- stream << pixels.pack("xN#{width}" * height)
188
-
189
- elsif compression < Zlib::BEST_COMPRESSION && color_mode == ChunkyPNG::COLOR_TRUECOLOR
190
- # Fast RGB saving routine
191
- line_packer = 'x' + ('NX' * width)
192
- stream << pixels.pack(line_packer * height)
193
-
194
- else
195
- # Normal saving routine
196
- pixel_size = Color.bytesize(color_mode)
197
- pixel_encoder = case color_mode
198
- when ChunkyPNG::COLOR_TRUECOLOR then lambda { |color| Color.to_truecolor_bytes(color) }
199
- when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then lambda { |color| Color.to_truecolor_alpha_bytes(color) }
200
- when ChunkyPNG::COLOR_INDEXED then lambda { |color| [encoding_palette.index(color)] }
201
- when ChunkyPNG::COLOR_GRAYSCALE then lambda { |color| Color.to_grayscale_bytes(color) }
202
- when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then lambda { |color| Color.to_grayscale_alpha_bytes(color) }
203
- else raise ChunkyPNG::NotSupported, "Cannot encode pixels for this mode: #{color_mode}!"
204
- end
205
-
206
- previous_bytes = Array.new(pixel_size * width, 0)
207
- each_scanline do |line|
208
- unencoded_bytes = line.map(&pixel_encoder).flatten
209
- stream << encode_png_scanline_paeth(unencoded_bytes, previous_bytes, pixel_size).pack('C*')
210
- previous_bytes = unencoded_bytes
211
- end
193
+ start_pos = stream.bytesize
194
+ pixel_size = Color.bytesize(color_mode)
195
+ line_width = pixel_size * width
196
+
197
+ # Encode the whole image without filtering
198
+ stream << case color_mode
199
+ when ChunkyPNG::COLOR_TRUECOLOR; pixels.pack(('x' + ('NX' * width)) * height)
200
+ when ChunkyPNG::COLOR_TRUECOLOR_ALPHA; pixels.pack("xN#{width}" * height)
201
+ when ChunkyPNG::COLOR_INDEXED; pixels.map { |p| encoding_palette.index(p) }.pack("xC#{width}" * height)
202
+ when ChunkyPNG::COLOR_GRAYSCALE; pixels.map { |p| p >> 8 }.pack("xC#{width}" * height)
203
+ when ChunkyPNG::COLOR_GRAYSCALE_ALPHA; pixels.pack("xn#{width}" * height)
204
+ else raise ChunkyPNG::NotSupported, "Cannot encode pixels for this mode: #{color_mode}!"
212
205
  end
213
- end
214
-
215
- # Passes to this canvas of pixel values line by line.
216
- # @yield [line] Yields the scanlines of this image one by one.
217
- # @yieldparam [Array<Integer>] line An line of fixnums representing pixels
218
- def each_scanline(&block)
219
- for line_no in 0...height do
220
- scanline = pixels[width * line_no, width]
221
- yield(scanline)
206
+
207
+ # Determine the filter method
208
+ filter_method = case filtering
209
+ when ChunkyPNG::FILTER_SUB; :encode_png_str_scanline_sub
210
+ when ChunkyPNG::FILTER_UP; :encode_png_str_scanline_up
211
+ when ChunkyPNG::FILTER_AVERAGE; :encode_png_str_scanline_average
212
+ when ChunkyPNG::FILTER_PAETH; :encode_png_str_scanline_paeth
213
+ else nil
222
214
  end
223
- end
224
-
225
- # Encodes the bytes of a scanline with a given filter.
226
- # @param [Integer] filter The filter method to use.
227
- # @param [Array<Integer>] bytes The scanline bytes to encode.
228
- # @param [Array<Integer>] previous_bytes The original bytes of the previous scanline.
229
- # @param [Integer] pixelsize The number of bytes per pixel.
230
- # @return [Array<Integer>] The filtered array of bytes.
231
- def encode_png_scanline(filter, bytes, previous_bytes = nil, pixelsize = 3)
232
- case filter
233
- when ChunkyPNG::FILTER_NONE then encode_png_scanline_none( bytes, previous_bytes, pixelsize)
234
- when ChunkyPNG::FILTER_SUB then encode_png_scanline_sub( bytes, previous_bytes, pixelsize)
235
- when ChunkyPNG::FILTER_UP then encode_png_scanline_up( bytes, previous_bytes, pixelsize)
236
- when ChunkyPNG::FILTER_AVERAGE then encode_png_scanline_average( bytes, previous_bytes, pixelsize)
237
- when ChunkyPNG::FILTER_PAETH then encode_png_scanline_paeth( bytes, previous_bytes, pixelsize)
238
- else raise ChunkyPNG::NotSupported, "Unknown filter type: #{filter}!"
215
+
216
+ # Now, apply filtering if any
217
+ if filter_method
218
+ (height - 1).downto(0) do |y|
219
+ pos = start_pos + y * (line_width + 1)
220
+ prev_pos = (y == 0) ? nil : pos - (line_width + 1)
221
+ send(filter_method, stream, pos, prev_pos, line_width, pixel_size)
222
+ end
239
223
  end
240
224
  end
241
225
 
242
- # Encodes the bytes of a scanline without filtering.
243
- # @param [Array<Integer>] bytes The scanline bytes to encode.
244
- # @param [Array<Integer>] previous_bytes The original bytes of the previous scanline.
245
- # @param [Integer] pixelsize The number of bytes per pixel.
246
- # @return [Array<Integer>] The filtered array of bytes.
247
- def encode_png_scanline_none(original_bytes, previous_bytes = nil, pixelsize = 3)
248
- [ChunkyPNG::FILTER_NONE] + original_bytes
226
+ # Encodes a scanline of a pixelstream without filtering. This is a no-op.
227
+ # @param [String] stream The pixelstream to work on. This string will be modified.
228
+ # @param [Integer] pos The starting position of the scanline.
229
+ # @param [Integer, nil] prev_pos The starting position of the previous scanline. <tt>nil</tt> if
230
+ # this is the first line.
231
+ # @param [Integer] line_width The number of bytes in this scanline, without counting the filtering
232
+ # method byte.
233
+ # @param [Integer] pixel_size The number of bytes used per pixel.
234
+ # @return [nil]
235
+ def encode_png_str_scanline_none(stream, pos, prev_pos, line_width, pixel_size)
236
+ # noop - this method shouldn't get called at all.
249
237
  end
250
238
 
251
- # Encodes the bytes of a scanline with SUB filtering.
252
- # @param (see ChunkyPNG::Canvas::PNGEncoding#encode_png_scanline_none)
253
- def encode_png_scanline_sub(original_bytes, previous_bytes = nil, pixelsize = 3)
254
- encoded_bytes = []
255
- for index in 0...original_bytes.length do
256
- a = (index >= pixelsize) ? original_bytes[index - pixelsize] : 0
257
- encoded_bytes[index] = (original_bytes[index] - a) % 256
239
+ # Encodes a scanline of a pixelstream using SUB filtering. This will modify the stream.
240
+ # @param (see #encode_png_str_scanline_none)
241
+ # @return [nil]
242
+ def encode_png_str_scanline_sub(stream, pos, prev_pos, line_width, pixel_size)
243
+ line_width.downto(1) do |i|
244
+ a = (i > pixel_size) ? stream.getbyte(pos + i - pixel_size) : 0
245
+ stream.setbyte(pos + i, (stream.getbyte(pos + i) - a) & 0xff)
258
246
  end
259
- [ChunkyPNG::FILTER_SUB] + encoded_bytes
247
+ stream.setbyte(pos, ChunkyPNG::FILTER_SUB)
260
248
  end
261
249
 
262
- # Encodes the bytes of a scanline with UP filtering.
263
- # @param (see ChunkyPNG::Canvas::PNGEncoding#encode_png_scanline_none)
264
- def encode_png_scanline_up(original_bytes, previous_bytes, pixelsize = 3)
265
- encoded_bytes = []
266
- for index in 0...original_bytes.length do
267
- b = previous_bytes[index]
268
- encoded_bytes[index] = (original_bytes[index] - b) % 256
250
+ # Encodes a scanline of a pixelstream using UP filtering. This will modify the stream.
251
+ # @param (see #encode_png_str_scanline_none)
252
+ # @return [nil]
253
+ def encode_png_str_scanline_up(stream, pos, prev_pos, line_width, pixel_size)
254
+ line_width.downto(1) do |i|
255
+ b = prev_pos ? stream.getbyte(prev_pos + i) : 0
256
+ stream.setbyte(pos + i, (stream.getbyte(pos + i) - b) & 0xff)
269
257
  end
270
- [ChunkyPNG::FILTER_UP] + encoded_bytes
258
+ stream.setbyte(pos, ChunkyPNG::FILTER_UP)
271
259
  end
272
-
273
- # Encodes the bytes of a scanline with AVERAGE filtering.
274
- # @param (see ChunkyPNG::Canvas::PNGEncoding#encode_png_scanline_none)
275
- def encode_png_scanline_average(original_bytes, previous_bytes, pixelsize = 3)
276
- encoded_bytes = []
277
- for index in 0...original_bytes.length do
278
- a = (index >= pixelsize) ? original_bytes[index - pixelsize] : 0
279
- b = previous_bytes[index]
280
- encoded_bytes[index] = (original_bytes[index] - ((a + b) >> 1)) % 256
260
+
261
+ # Encodes a scanline of a pixelstream using AVERAGE filtering. This will modify the stream.
262
+ # @param (see #encode_png_str_scanline_none)
263
+ # @return [nil]
264
+ def encode_png_str_scanline_average(stream, pos, prev_pos, line_width, pixel_size)
265
+ line_width.downto(1) do |i|
266
+ a = (i > pixel_size) ? stream.getbyte(pos + i - pixel_size) : 0
267
+ b = prev_pos ? stream.getbyte(prev_pos + i) : 0
268
+ stream.setbyte(pos + i, (stream.getbyte(pos + i) - ((a + b) >> 1)) & 0xff)
281
269
  end
282
- [ChunkyPNG::FILTER_AVERAGE] + encoded_bytes
270
+ stream.setbyte(pos, ChunkyPNG::FILTER_AVERAGE)
283
271
  end
284
-
285
- # Encodes the bytes of a scanline with PAETH filtering.
286
- # @param (see ChunkyPNG::Canvas::PNGEncoding#encode_png_scanline_none)
287
- def encode_png_scanline_paeth(original_bytes, previous_bytes, pixelsize = 3)
288
- encoded_bytes = []
289
- for i in 0...original_bytes.length do
290
- a = (i >= pixelsize) ? original_bytes[i - pixelsize] : 0
291
- b = previous_bytes[i]
292
- c = (i >= pixelsize) ? previous_bytes[i - pixelsize] : 0
272
+
273
+ # Encodes a scanline of a pixelstream using PAETH filtering. This will modify the stream.
274
+ # @param (see #encode_png_str_scanline_none)
275
+ # @return [nil]
276
+ def encode_png_str_scanline_paeth(stream, pos, prev_pos, line_width, pixel_size)
277
+ line_width.downto(1) do |i|
278
+ a = (i > pixel_size) ? stream.getbyte(pos + i - pixel_size) : 0
279
+ b = (prev_pos) ? stream.getbyte(prev_pos + i) : 0
280
+ c = (prev_pos && i > pixel_size) ? stream.getbyte(prev_pos + i - pixel_size) : 0
293
281
  p = a + b - c
294
282
  pa = (p - a).abs
295
283
  pb = (p - b).abs
296
284
  pc = (p - c).abs
297
285
  pr = (pa <= pb && pa <= pc) ? a : (pb <= pc ? b : c)
298
- encoded_bytes[i] = (original_bytes[i] - pr) % 256
286
+ stream.setbyte(pos + i, (stream.getbyte(pos + i) - pr) & 0xff)
299
287
  end
300
- [ChunkyPNG::FILTER_PAETH] + encoded_bytes
301
- end
288
+ stream.setbyte(pos, ChunkyPNG::FILTER_PAETH)
289
+ end
302
290
  end
303
291
  end
304
292
  end
@@ -17,7 +17,7 @@ module ChunkyPNG
17
17
  # @return [ChunkyPNG::Canvas] The newly constructed canvas instance.
18
18
  def from_rgb_stream(width, height, stream)
19
19
  string = stream.respond_to?(:read) ? stream.read(3 * width * height) : stream.to_s[0, 3 * width * height]
20
- string << "\255" # Add a fourth byte to the last RGB triple.
20
+ string << ChunkyPNG::EXTRA_BYTE # Add a fourth byte to the last RGB triple.
21
21
  unpacker = 'NX' * (width * height)
22
22
  pixels = string.unpack(unpacker).map { |color| color | 0x000000ff }
23
23
  self.new(width, height, pixels)
@@ -51,7 +51,7 @@ module ChunkyPNG
51
51
  # @param [#read, String] stream The stream to read the pixel data from.
52
52
  # @return [ChunkyPNG::Canvas] The newly constructed canvas instance.
53
53
  def from_bgr_stream(width, height, stream)
54
- string = "\255" # Add a first byte to the first BGR triple.
54
+ string = ChunkyPNG::EXTRA_BYTE # Add a first byte to the first BGR triple.
55
55
  string << stream.respond_to?(:read) ? stream.read(3 * width * height) : stream.to_s[0, 3 * width * height]
56
56
  pixels = string.unpack("@1" << ('XV' * (width * height))).map { |color| color | 0x000000ff }
57
57
  self.new(width, height, pixels)
@@ -6,53 +6,51 @@ describe ChunkyPNG::Canvas::PNGDecoding do
6
6
  describe '#decode_png_scanline' do
7
7
 
8
8
  it "should decode a line without filtering as is" do
9
- bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
10
- decode_png_scanline(ChunkyPNG::FILTER_NONE, bytes, nil).should == bytes
9
+ stream = [ChunkyPNG::FILTER_NONE, 255, 255, 255, 255, 255, 255, 255, 255, 255].pack('C*')
10
+ decode_png_str_scanline(stream, 0, nil, 9, 3)
11
+ stream.unpack('@1C*').should == [255, 255, 255, 255, 255, 255, 255, 255, 255]
11
12
  end
12
13
 
13
14
  it "should decode a line with sub filtering correctly" do
14
15
  # all white pixels
15
- bytes = [255, 255, 255, 0, 0, 0, 0, 0, 0]
16
- decoded_bytes = decode_png_scanline(ChunkyPNG::FILTER_SUB, bytes, nil)
17
- decoded_bytes.should == [255, 255, 255, 255, 255, 255, 255, 255, 255]
16
+ stream = [ChunkyPNG::FILTER_SUB, 255, 255, 255, 0, 0, 0, 0, 0, 0].pack('C*')
17
+ decode_png_str_scanline(stream, 0, nil, 9, 3)
18
+ stream.unpack('@1C*').should == [255, 255, 255, 255, 255, 255, 255, 255, 255]
18
19
 
19
20
  # all black pixels
20
- bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0]
21
- decoded_bytes = decode_png_scanline(ChunkyPNG::FILTER_SUB, bytes, nil)
22
- decoded_bytes.should == [0, 0, 0, 0, 0, 0, 0, 0, 0]
21
+ stream = [ChunkyPNG::FILTER_SUB, 0, 0, 0, 0, 0, 0, 0, 0, 0].pack('C*')
22
+ decode_png_str_scanline(stream, 0, nil, 9, 3)
23
+ stream.unpack('@1C*').should == [0, 0, 0, 0, 0, 0, 0, 0, 0]
23
24
 
24
25
  # various colors
25
- bytes = [255, 0, 45, 0, 255, 0, 112, 200, 178]
26
- decoded_bytes = decode_png_scanline(ChunkyPNG::FILTER_SUB, bytes, nil)
27
- decoded_bytes.should == [255, 0, 45, 255, 255, 45, 111, 199, 223]
26
+ stream = [ChunkyPNG::FILTER_SUB, 255, 0, 45, 0, 255, 0, 112, 200, 178].pack('C*')
27
+ decode_png_str_scanline(stream, 0, nil, 9, 3)
28
+ stream.unpack('@1C*').should == [255, 0, 45, 255, 255, 45, 111, 199, 223]
28
29
  end
29
30
 
30
31
  it "should decode a line with up filtering correctly" do
31
- # previous line is all black
32
- previous_bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0]
33
- bytes = [255, 255, 255, 127, 127, 127, 0, 0, 0]
34
- decoded_bytes = decode_png_scanline(ChunkyPNG::FILTER_UP, bytes, previous_bytes)
35
- decoded_bytes.should == [255, 255, 255, 127, 127, 127, 0, 0, 0]
36
-
37
32
  # previous line has various pixels
38
- previous_bytes = [255, 255, 255, 127, 127, 127, 0, 0, 0]
39
- bytes = [0, 127, 255, 0, 127, 255, 0, 127, 255]
40
- decoded_bytes = decode_png_scanline(ChunkyPNG::FILTER_UP, bytes, previous_bytes)
41
- decoded_bytes.should == [255, 126, 254, 127, 254, 126, 0, 127, 255]
33
+ previous = [ChunkyPNG::FILTER_UP, 255, 255, 255, 127, 127, 127, 0, 0, 0]
34
+ current = [ChunkyPNG::FILTER_UP, 0, 127, 255, 0, 127, 255, 0, 127, 255]
35
+ stream = (previous + current).pack('C*')
36
+ decode_png_str_scanline(stream, 10, 0, 9, 3)
37
+ stream.unpack('@11C9').should == [255, 126, 254, 127, 254, 126, 0, 127, 255]
42
38
  end
43
39
 
44
40
  it "should decode a line with average filtering correctly" do
45
- previous = [10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120]
46
- current = [ 0, 0, 10, 23, 15, 13, 23, 63, 38, 60, 253, 53]
47
- decoded = decode_png_scanline(ChunkyPNG::FILTER_AVERAGE, current, previous)
48
- decoded.should == [5, 10, 25, 45, 45, 55, 80, 125, 105, 150, 114, 165]
41
+ previous = [ChunkyPNG::FILTER_AVERAGE, 10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120]
42
+ current = [ChunkyPNG::FILTER_AVERAGE, 0, 0, 10, 23, 15, 13, 23, 63, 38, 60, 253, 53]
43
+ stream = (previous + current).pack('C*')
44
+ decode_png_str_scanline(stream, 13, 0, 12, 3)
45
+ stream.unpack('@14C12').should == [5, 10, 25, 45, 45, 55, 80, 125, 105, 150, 114, 165]
49
46
  end
50
47
 
51
48
  it "should decode a line with paeth filtering correctly" do
52
- previous = [10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120]
53
- current = [ 0, 0, 10, 20, 10, 0, 0, 40, 10, 20, 190, 0]
54
- decoded = decode_png_scanline(ChunkyPNG::FILTER_PAETH, current, previous)
55
- decoded.should == [10, 20, 40, 60, 60, 60, 70, 120, 90, 120, 54, 120]
49
+ previous = [ChunkyPNG::FILTER_PAETH, 10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120]
50
+ current = [ChunkyPNG::FILTER_PAETH, 0, 0, 10, 20, 10, 0, 0, 40, 10, 20, 190, 0]
51
+ stream = (previous + current).pack('C*')
52
+ decode_png_str_scanline(stream, 13, 0, 12, 3)
53
+ stream.unpack('@14C12').should == [10, 20, 40, 60, 60, 60, 70, 120, 90, 120, 54, 120]
56
54
  end
57
55
  end
58
56