chunky_png 0.9.2 → 0.10.0

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