module ChunkyPNG::Canvas::PNGEncoding

Methods for encoding a Canvas instance into a PNG datastream.

Overview of the encoding process:

For interlaced images, the initial image is first split into 7 subimages. These images get encoded exactly as above, and the result gets combined before the compression step.

@see ChunkyPNG::Canvas::PNGDecoding @see www.w3.org/TR/PNG/ The W3C PNG format specification

Attributes

encoding_palette[RW]

The palette used for encoding the image.This is only in used for images that get encoded using indexed colors. @return [ChunkyPNG::Palette]

Public Instance Methods

save(filename, constraints = {}) click to toggle source

Writes the canvas to a file, encoded as a PNG image. @param [String] filename The file to save the PNG image to. @param constraints (see ChunkyPNG::Canvas::PNGEncoding#to_datastream) @return [void]

   # File lib/chunky_png/canvas/png_encoding.rb
42 def save(filename, constraints = {})
43   File.open(filename, "wb") { |io| write(io, constraints) }
44 end
to_blob(constraints = {}) click to toggle source

Encoded the canvas to a PNG formatted string. @param constraints (see ChunkyPNG::Canvas::PNGEncoding#to_datastream) @return [String] The PNG encoded canvas as string.

   # File lib/chunky_png/canvas/png_encoding.rb
49 def to_blob(constraints = {})
50   to_datastream(constraints).to_blob
51 end
Also aliased as: to_string, to_s
to_datastream(constraints = {}) click to toggle source

Converts this Canvas to a datastream, so that it can be saved as a PNG image. @param [Hash, Symbol] constraints The constraints to use when encoding the canvas.

This can either be a hash with different constraints, or a symbol which acts as a
preset for some constraints. If no constraints are given, ChunkyPNG will decide
for itself how to best create the PNG datastream.
Supported presets are <tt>:fast_rgba</tt> for quickly saving images with transparency,
<tt>:fast_rgb</tt> for quickly saving opaque images, and <tt>:best_compression</tt> to
obtain the smallest possible filesize.

@option constraints [Fixnum] :color_mode The color mode to use. Use one of the

ChunkyPNG::COLOR_* constants.

@option constraints [true, false] :interlace Whether to use interlacing. @option constraints [Fixnum] :compression The compression level for Zlib. This can be a

value between 0 and 9, or a Zlib constant like Zlib::BEST_COMPRESSION.

@option constraints [Fixnum] :bit_depth The bit depth to use. This option is only used

for indexed images, in which case it overrides the determined minimal bit depth. For
all the other color modes, a bit depth of 8 is used.

@return [ChunkyPNG::Datastream] The PNG datastream containing the encoded canvas. @see ChunkyPNG::Canvas::PNGEncoding#determine_png_encoding

   # File lib/chunky_png/canvas/png_encoding.rb
74 def to_datastream(constraints = {})
75   encoding = determine_png_encoding(constraints)
76 
77   ds = Datastream.new
78   ds.header_chunk = Chunk::Header.new(
79     width: width,
80     height: height,
81     color: encoding[:color_mode],
82     depth: encoding[:bit_depth],
83     interlace: encoding[:interlace]
84   )
85 
86   if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
87     ds.palette_chunk      = encoding_palette.to_plte_chunk
88     ds.transparency_chunk = encoding_palette.to_trns_chunk unless encoding_palette.opaque?
89   end
90   data           = encode_png_pixelstream(encoding[:color_mode], encoding[:bit_depth], encoding[:interlace], encoding[:filtering])
91   ds.data_chunks = Chunk::ImageData.split_in_chunks(data, encoding[:compression])
92   ds.end_chunk   = Chunk::End.new
93   ds
94 end
to_s(constraints = {})
Alias for: to_blob
to_string(constraints = {})
Alias for: to_blob
write(io, constraints = {}) click to toggle source

Writes the canvas to an IO stream, encoded as a PNG image. @param [IO] io The output stream to write to. @param constraints (see ChunkyPNG::Canvas::PNGEncoding#to_datastream) @return [void]

   # File lib/chunky_png/canvas/png_encoding.rb
34 def write(io, constraints = {})
35   to_datastream(constraints).write(io)
36 end

Protected Instance Methods

determine_png_encoding(constraints = {}) click to toggle source

Determines the best possible PNG encoding variables for this image, by analyzing the colors used for the image.

You can provide constraints for the encoding variables by passing a hash with encoding variables to this method.

@param [Hash, Symbol] constraints The constraints for the encoding. This can be a

Hash or a preset symbol.

@return [Hash] A hash with encoding options for {ChunkyPNG::Canvas::PNGEncoding#to_datastream}

    # File lib/chunky_png/canvas/png_encoding.rb
107 def determine_png_encoding(constraints = {})
108   encoding = case constraints
109     when :fast_rgb         then {color_mode: ChunkyPNG::COLOR_TRUECOLOR, compression: Zlib::BEST_SPEED}
110     when :fast_rgba        then {color_mode: ChunkyPNG::COLOR_TRUECOLOR_ALPHA, compression: Zlib::BEST_SPEED}
111     when :best_compression then {compression: Zlib::BEST_COMPRESSION, filtering: ChunkyPNG::FILTER_PAETH}
112     when :good_compression then {compression: Zlib::BEST_COMPRESSION, filtering: ChunkyPNG::FILTER_NONE}
113     when :no_compression   then {compression: Zlib::NO_COMPRESSION}
114     when :black_and_white  then {color_mode: ChunkyPNG::COLOR_GRAYSCALE, bit_depth: 1}
115     when Hash              then constraints
116     else raise ChunkyPNG::Exception, "Unknown encoding preset: #{constraints.inspect}"
117   end
118 
119   # Do not create a palette when the encoding is given and does not require a palette.
120   if encoding[:color_mode]
121     if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
122       self.encoding_palette = palette
123       encoding[:bit_depth] ||= encoding_palette.determine_bit_depth
124     else
125       encoding[:bit_depth] ||= 8
126     end
127   else
128     self.encoding_palette = palette
129     suggested_color_mode, suggested_bit_depth = encoding_palette.best_color_settings
130     encoding[:color_mode] ||= suggested_color_mode
131     encoding[:bit_depth]  ||= suggested_bit_depth
132   end
133 
134   # Use Zlib's default for compression unless otherwise provided.
135   encoding[:compression] ||= Zlib::DEFAULT_COMPRESSION
136 
137   encoding[:interlace] = case encoding[:interlace]
138     when nil, false then ChunkyPNG::INTERLACING_NONE
139     when true then ChunkyPNG::INTERLACING_ADAM7
140     else encoding[:interlace]
141   end
142 
143   encoding[:filtering] ||= case encoding[:compression]
144     when Zlib::BEST_COMPRESSION then ChunkyPNG::FILTER_PAETH
145     when Zlib::NO_COMPRESSION..Zlib::BEST_SPEED then ChunkyPNG::FILTER_NONE
146     else ChunkyPNG::FILTER_UP
147   end
148   encoding
149 end
encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering) click to toggle source

Encodes the canvas to a stream, in a given color mode. @param [String] stream The stream to write to. @param [Integer] color_mode The color mode to use for encoding. @param [Integer] bit_depth The bit depth of the image. @param [Integer] filtering The filtering method to use.

    # File lib/chunky_png/canvas/png_encoding.rb
206 def encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering)
207   start_pos  = stream.bytesize
208   pixel_size = Color.pixel_bytesize(color_mode)
209   line_width = Color.scanline_bytesize(color_mode, bit_depth, width)
210 
211   # Determine the filter method
212   encode_method = encode_png_pixels_to_scanline_method(color_mode, bit_depth)
213   filter_method = case filtering
214     when ChunkyPNG::FILTER_NONE    then nil
215     when ChunkyPNG::FILTER_SUB     then :encode_png_str_scanline_sub
216     when ChunkyPNG::FILTER_UP      then :encode_png_str_scanline_up
217     when ChunkyPNG::FILTER_AVERAGE then :encode_png_str_scanline_average
218     when ChunkyPNG::FILTER_PAETH   then :encode_png_str_scanline_paeth
219     else raise ArgumentError, "Filtering method #{filtering} is not supported"
220   end
221 
222   0.upto(height - 1) do |y|
223     stream << send(encode_method, row(y))
224   end
225 
226   # Now, apply filtering if any
227   if filter_method
228     (height - 1).downto(0) do |y|
229       pos = start_pos + y * (line_width + 1)
230       prev_pos = y == 0 ? nil : pos - (line_width + 1)
231       send(filter_method, stream, pos, prev_pos, line_width, pixel_size)
232     end
233   end
234 end
encode_png_image_with_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE) click to toggle source

Encodes the canvas according to the PNG format specification with a given color mode and Adam7 interlacing.

This method will split the original canvas in 7 smaller canvases and encode them one by one, concatenating the resulting strings.

@param [Integer] color_mode The color mode to use for encoding. @param [Integer] bit_depth The bit depth of the image. @param [Integer] filtering The filtering method to use. @return [String] The PNG encoded canvas as string.

    # File lib/chunky_png/canvas/png_encoding.rb
191 def encode_png_image_with_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE)
192   stream = "".b
193   0.upto(6) do |pass|
194     subcanvas = self.class.adam7_extract_pass(pass, self)
195     subcanvas.encoding_palette = encoding_palette
196     subcanvas.encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering)
197   end
198   stream
199 end
encode_png_image_without_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE) click to toggle source

Encodes the canvas according to the PNG format specification with a given color mode. @param [Integer] color_mode The color mode to use for encoding. @param [Integer] bit_depth The bit depth of the image. @param [Integer] filtering The filtering method to use. @return [String] The PNG encoded canvas as string.

    # File lib/chunky_png/canvas/png_encoding.rb
175 def encode_png_image_without_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE)
176   stream = "".b
177   encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering)
178   stream
179 end
encode_png_pixels_to_scanline_grayscale_1bit(pixels) click to toggle source

Encodes a line of pixels using 1-bit grayscale mode. @param [Array<Integer>] pixels A row of pixels of the original image. @return [String] The encoded scanline as binary string

    # File lib/chunky_png/canvas/png_encoding.rb
307 def encode_png_pixels_to_scanline_grayscale_1bit(pixels)
308   chars = []
309   pixels.each_slice(8) do |p1, p2, p3, p4, p5, p6, p7, p8|
310     chars << ((p1.nil? ? 0 : (p1 & 0x0000ffff) >> 15 << 7) |
311               (p2.nil? ? 0 : (p2 & 0x0000ffff) >> 15 << 6) |
312               (p3.nil? ? 0 : (p3 & 0x0000ffff) >> 15 << 5) |
313               (p4.nil? ? 0 : (p4 & 0x0000ffff) >> 15 << 4) |
314               (p5.nil? ? 0 : (p5 & 0x0000ffff) >> 15 << 3) |
315               (p6.nil? ? 0 : (p6 & 0x0000ffff) >> 15 << 2) |
316               (p7.nil? ? 0 : (p7 & 0x0000ffff) >> 15 << 1) |
317               (p8.nil? ? 0 : (p8 & 0x0000ffff) >> 15))
318   end
319   chars.pack("xC*")
320 end
encode_png_pixels_to_scanline_grayscale_2bit(pixels) click to toggle source

Encodes a line of pixels using 2-bit grayscale mode. @param [Array<Integer>] pixels A row of pixels of the original image. @return [String] The encoded scanline as binary string

    # File lib/chunky_png/canvas/png_encoding.rb
325 def encode_png_pixels_to_scanline_grayscale_2bit(pixels)
326   chars = []
327   pixels.each_slice(4) do |p1, p2, p3, p4|
328     chars << ((p1.nil? ? 0 : (p1 & 0x0000ffff) >> 14 << 6) |
329               (p2.nil? ? 0 : (p2 & 0x0000ffff) >> 14 << 4) |
330               (p3.nil? ? 0 : (p3 & 0x0000ffff) >> 14 << 2) |
331               (p4.nil? ? 0 : (p4 & 0x0000ffff) >> 14))
332   end
333   chars.pack("xC*")
334 end
encode_png_pixels_to_scanline_grayscale_4bit(pixels) click to toggle source

Encodes a line of pixels using 2-bit grayscale mode. @param [Array<Integer>] pixels A row of pixels of the original image. @return [String] The encoded scanline as binary string

    # File lib/chunky_png/canvas/png_encoding.rb
339 def encode_png_pixels_to_scanline_grayscale_4bit(pixels)
340   chars = []
341   pixels.each_slice(2) do |p1, p2|
342     chars << ((p1.nil? ? 0 : ((p1 & 0x0000ffff) >> 12) << 4) | (p2.nil? ? 0 : ((p2 & 0x0000ffff) >> 12)))
343   end
344   chars.pack("xC*")
345 end
encode_png_pixels_to_scanline_grayscale_8bit(pixels) click to toggle source

Encodes a line of pixels using 8-bit grayscale mode. @param [Array<Integer>] pixels A row of pixels of the original image. @return [String] The encoded scanline as binary string

    # File lib/chunky_png/canvas/png_encoding.rb
350 def encode_png_pixels_to_scanline_grayscale_8bit(pixels)
351   pixels.map { |p| p >> 8 }.pack("xC#{width}")
352 end
encode_png_pixels_to_scanline_grayscale_alpha_8bit(pixels) click to toggle source

Encodes a line of pixels using 8-bit grayscale alpha mode. @param [Array<Integer>] pixels A row of pixels of the original image. @return [String] The encoded scanline as binary string

    # File lib/chunky_png/canvas/png_encoding.rb
357 def encode_png_pixels_to_scanline_grayscale_alpha_8bit(pixels)
358   pixels.pack("xn#{width}")
359 end
encode_png_pixels_to_scanline_indexed_1bit(pixels) click to toggle source

Encodes a line of pixels using 1-bit indexed mode. @param [Array<Integer>] pixels A row of pixels of the original image. @return [String] The encoded scanline as binary string

    # File lib/chunky_png/canvas/png_encoding.rb
253 def encode_png_pixels_to_scanline_indexed_1bit(pixels)
254   chars = []
255   pixels.each_slice(8) do |p1, p2, p3, p4, p5, p6, p7, p8|
256     chars << (
257       (encoding_palette.index(p1) << 7) |
258       (encoding_palette.index(p2) << 6) |
259       (encoding_palette.index(p3) << 5) |
260       (encoding_palette.index(p4) << 4) |
261       (encoding_palette.index(p5) << 3) |
262       (encoding_palette.index(p6) << 2) |
263       (encoding_palette.index(p7) << 1) |
264       encoding_palette.index(p8)
265     )
266   end
267   chars.pack("xC*")
268 end
encode_png_pixels_to_scanline_indexed_2bit(pixels) click to toggle source

Encodes a line of pixels using 2-bit indexed mode. @param [Array<Integer>] pixels A row of pixels of the original image. @return [String] The encoded scanline as binary string

    # File lib/chunky_png/canvas/png_encoding.rb
273 def encode_png_pixels_to_scanline_indexed_2bit(pixels)
274   chars = []
275   pixels.each_slice(4) do |p1, p2, p3, p4|
276     chars << (
277       (encoding_palette.index(p1) << 6) |
278       (encoding_palette.index(p2) << 4) |
279       (encoding_palette.index(p3) << 2) |
280       encoding_palette.index(p4)
281     )
282   end
283   chars.pack("xC*")
284 end
encode_png_pixels_to_scanline_indexed_4bit(pixels) click to toggle source

Encodes a line of pixels using 4-bit indexed mode. @param [Array<Integer>] pixels A row of pixels of the original image. @return [String] The encoded scanline as binary string

    # File lib/chunky_png/canvas/png_encoding.rb
289 def encode_png_pixels_to_scanline_indexed_4bit(pixels)
290   chars = []
291   pixels.each_slice(2) do |p1, p2|
292     chars << ((encoding_palette.index(p1) << 4) | encoding_palette.index(p2))
293   end
294   chars.pack("xC*")
295 end
encode_png_pixels_to_scanline_indexed_8bit(pixels) click to toggle source

Encodes a line of pixels using 8-bit indexed mode. @param [Array<Integer>] pixels A row of pixels of the original image. @return [String] The encoded scanline as binary string

    # File lib/chunky_png/canvas/png_encoding.rb
300 def encode_png_pixels_to_scanline_indexed_8bit(pixels)
301   pixels.map { |p| encoding_palette.index(p) }.pack("xC#{width}")
302 end
encode_png_pixels_to_scanline_method(color_mode, depth) click to toggle source

Returns the method name to use to decode scanlines into pixels. @param [Integer] color_mode The color mode of the image. @param [Integer] depth The bit depth of the image. @return [Symbol] The method name to use for decoding, to be called on the canvas class. @raise [ChunkyPNG::NotSupported] when the color_mode and/or bit depth is not supported.

    # File lib/chunky_png/canvas/png_encoding.rb
366 def encode_png_pixels_to_scanline_method(color_mode, depth)
367   encoder_method = case color_mode
368     when ChunkyPNG::COLOR_TRUECOLOR       then :"encode_png_pixels_to_scanline_truecolor_#{depth}bit"
369     when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then :"encode_png_pixels_to_scanline_truecolor_alpha_#{depth}bit"
370     when ChunkyPNG::COLOR_INDEXED         then :"encode_png_pixels_to_scanline_indexed_#{depth}bit"
371     when ChunkyPNG::COLOR_GRAYSCALE       then :"encode_png_pixels_to_scanline_grayscale_#{depth}bit"
372     when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then :"encode_png_pixels_to_scanline_grayscale_alpha_#{depth}bit"
373   end
374 
375   raise ChunkyPNG::NotSupported, "No encoder found for color mode #{color_mode} and #{depth}-bit depth!" unless respond_to?(encoder_method, true)
376   encoder_method
377 end
encode_png_pixels_to_scanline_truecolor_8bit(pixels) click to toggle source

Encodes a line of pixels using 8-bit truecolor mode. @param [Array<Integer>] pixels A row of pixels of the original image. @return [String] The encoded scanline as binary string

    # File lib/chunky_png/canvas/png_encoding.rb
239 def encode_png_pixels_to_scanline_truecolor_8bit(pixels)
240   pixels.pack("x" + ("NX" * width))
241 end
encode_png_pixels_to_scanline_truecolor_alpha_8bit(pixels) click to toggle source

Encodes a line of pixels using 8-bit truecolor alpha mode. @param [Array<Integer>] pixels A row of pixels of the original image. @return [String] The encoded scanline as binary string

    # File lib/chunky_png/canvas/png_encoding.rb
246 def encode_png_pixels_to_scanline_truecolor_alpha_8bit(pixels)
247   pixels.pack("xN#{width}")
248 end
encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, bit_depth = 8, interlace = ChunkyPNG::INTERLACING_NONE, filtering = ChunkyPNG::FILTER_NONE) click to toggle source

Encodes the canvas according to the PNG format specification with a given color mode, possibly with interlacing. @param [Integer] color_mode The color mode to use for encoding. @param [Integer] bit_depth The bit depth of the image. @param [Integer] interlace The interlacing method to use. @return [String] The PNG encoded canvas as string.

    # File lib/chunky_png/canvas/png_encoding.rb
157 def encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, bit_depth = 8, interlace = ChunkyPNG::INTERLACING_NONE, filtering = ChunkyPNG::FILTER_NONE)
158   if color_mode == ChunkyPNG::COLOR_INDEXED
159     raise ChunkyPNG::ExpectationFailed, "This palette is not suitable for encoding!" if encoding_palette.nil? || !encoding_palette.can_encode?
160     raise ChunkyPNG::ExpectationFailed, "This palette has too many colors!" if encoding_palette.size > (1 << bit_depth)
161   end
162 
163   case interlace
164     when ChunkyPNG::INTERLACING_NONE  then encode_png_image_without_interlacing(color_mode, bit_depth, filtering)
165     when ChunkyPNG::INTERLACING_ADAM7 then encode_png_image_with_interlacing(color_mode, bit_depth, filtering)
166     else raise ChunkyPNG::NotSupported, "Unknown interlacing method: #{interlace}!"
167   end
168 end
encode_png_str_scanline_average(stream, pos, prev_pos, line_width, pixel_size) click to toggle source

Encodes a scanline of a pixelstream using AVERAGE filtering. This will modify the stream. @param (see encode_png_str_scanline_none) @return [void]

    # File lib/chunky_png/canvas/png_encoding.rb
417 def encode_png_str_scanline_average(stream, pos, prev_pos, line_width, pixel_size)
418   line_width.downto(1) do |i|
419     a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0
420     b = prev_pos ? stream.getbyte(prev_pos + i) : 0
421     stream.setbyte(pos + i, (stream.getbyte(pos + i) - ((a + b) >> 1)) & 0xff)
422   end
423   stream.setbyte(pos, ChunkyPNG::FILTER_AVERAGE)
424 end
encode_png_str_scanline_none(stream, pos, prev_pos, line_width, pixel_size) click to toggle source

Encodes a scanline of a pixelstream without filtering. This is a no-op. @param [String] stream The pixelstream to work on. This string will be modified. @param [Integer] pos The starting position of the scanline. @param [Integer, nil] prev_pos The starting position of the previous scanline. nil if

this is the first line.

@param [Integer] line_width The number of bytes in this scanline, without counting the filtering

method byte.

@param [Integer] pixel_size The number of bytes used per pixel. @return [void]

    # File lib/chunky_png/canvas/png_encoding.rb
388 def encode_png_str_scanline_none(stream, pos, prev_pos, line_width, pixel_size)
389   # noop - this method shouldn't get called at all.
390 end
encode_png_str_scanline_paeth(stream, pos, prev_pos, line_width, pixel_size) click to toggle source

Encodes a scanline of a pixelstream using PAETH filtering. This will modify the stream. @param (see encode_png_str_scanline_none) @return [void]

    # File lib/chunky_png/canvas/png_encoding.rb
429 def encode_png_str_scanline_paeth(stream, pos, prev_pos, line_width, pixel_size)
430   line_width.downto(1) do |i|
431     a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0
432     b = prev_pos ? stream.getbyte(prev_pos + i) : 0
433     c = prev_pos && i > pixel_size ? stream.getbyte(prev_pos + i - pixel_size) : 0
434     p = a + b - c
435     pa = (p - a).abs
436     pb = (p - b).abs
437     pc = (p - c).abs
438     pr = if pa <= pb && pa <= pc
439       a
440     else
441       pb <= pc ? b : c
442     end
443 
444     stream.setbyte(pos + i, (stream.getbyte(pos + i) - pr) & 0xff)
445   end
446   stream.setbyte(pos, ChunkyPNG::FILTER_PAETH)
447 end
encode_png_str_scanline_sub(stream, pos, prev_pos, line_width, pixel_size) click to toggle source

Encodes a scanline of a pixelstream using SUB filtering. This will modify the stream. @param (see encode_png_str_scanline_none) @return [void]

    # File lib/chunky_png/canvas/png_encoding.rb
395 def encode_png_str_scanline_sub(stream, pos, prev_pos, line_width, pixel_size)
396   line_width.downto(1) do |i|
397     a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0
398     stream.setbyte(pos + i, (stream.getbyte(pos + i) - a) & 0xff)
399   end
400   stream.setbyte(pos, ChunkyPNG::FILTER_SUB)
401 end
encode_png_str_scanline_up(stream, pos, prev_pos, line_width, pixel_size) click to toggle source

Encodes a scanline of a pixelstream using UP filtering. This will modify the stream. @param (see encode_png_str_scanline_none) @return [void]

    # File lib/chunky_png/canvas/png_encoding.rb
406 def encode_png_str_scanline_up(stream, pos, prev_pos, line_width, pixel_size)
407   line_width.downto(1) do |i|
408     b = prev_pos ? stream.getbyte(prev_pos + i) : 0
409     stream.setbyte(pos + i, (stream.getbyte(pos + i) - b) & 0xff)
410   end
411   stream.setbyte(pos, ChunkyPNG::FILTER_UP)
412 end