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(:width => width, :height => height,
79       :color => encoding[:color_mode], :depth => encoding[:bit_depth], :interlace => encoding[:interlace])
80 
81   if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
82     ds.palette_chunk      = encoding_palette.to_plte_chunk
83     ds.transparency_chunk = encoding_palette.to_trns_chunk unless encoding_palette.opaque?
84   end
85   data           = encode_png_pixelstream(encoding[:color_mode], encoding[:bit_depth], encoding[:interlace], encoding[:filtering])
86   ds.data_chunks = Chunk::ImageData.split_in_chunks(data, encoding[:compression])
87   ds.end_chunk   = Chunk::End.new
88   return ds
89 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
102 def determine_png_encoding(constraints = {})
103 
104   encoding = case constraints
105     when :fast_rgb;         { :color_mode => ChunkyPNG::COLOR_TRUECOLOR, :compression => Zlib::BEST_SPEED }
106     when :fast_rgba;        { :color_mode => ChunkyPNG::COLOR_TRUECOLOR_ALPHA, :compression => Zlib::BEST_SPEED }
107     when :best_compression; { :compression => Zlib::BEST_COMPRESSION, :filtering => ChunkyPNG::FILTER_PAETH }
108     when :good_compression; { :compression => Zlib::BEST_COMPRESSION, :filtering => ChunkyPNG::FILTER_NONE }
109     when :no_compression;   { :compression => Zlib::NO_COMPRESSION }
110     when :black_and_white;  { :color_mode => ChunkyPNG::COLOR_GRAYSCALE, :bit_depth => 1 } 
111     when Hash; constraints
112     else raise ChunkyPNG::Exception, "Unknown encoding preset: #{constraints.inspect}"
113   end
114 
115   # Do not create a palette when the encoding is given and does not require a palette.
116   if encoding[:color_mode]
117     if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
118       self.encoding_palette = self.palette
119       encoding[:bit_depth] ||= self.encoding_palette.determine_bit_depth
120     else
121       encoding[:bit_depth] ||= 8
122     end
123   else
124     self.encoding_palette = self.palette
125     suggested_color_mode, suggested_bit_depth = encoding_palette.best_color_settings
126     encoding[:color_mode] ||= suggested_color_mode
127     encoding[:bit_depth]  ||= suggested_bit_depth
128   end
129 
130   # Use Zlib's default for compression unless otherwise provided.
131   encoding[:compression] ||= Zlib::DEFAULT_COMPRESSION
132 
133   encoding[:interlace] = case encoding[:interlace]
134     when nil, false, ChunkyPNG::INTERLACING_NONE; ChunkyPNG::INTERLACING_NONE
135     when true, ChunkyPNG::INTERLACING_ADAM7;      ChunkyPNG::INTERLACING_ADAM7
136     else encoding[:interlace]
137   end
138 
139   encoding[:filtering] ||= case encoding[:compression]
140     when Zlib::BEST_COMPRESSION; ChunkyPNG::FILTER_PAETH
141     when Zlib::NO_COMPRESSION..Zlib::BEST_SPEED; ChunkyPNG::FILTER_NONE
142     else ChunkyPNG::FILTER_UP
143   end
144   return encoding
145 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
203 def encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering)
204 
205   start_pos  = stream.bytesize
206   pixel_size = Color.pixel_bytesize(color_mode)
207   line_width = Color.scanline_bytesize(color_mode, bit_depth, width)
208   
209   # Determine the filter method
210   encode_method = encode_png_pixels_to_scanline_method(color_mode, bit_depth)
211   filter_method = case filtering
212     when ChunkyPNG::FILTER_SUB;     :encode_png_str_scanline_sub
213     when ChunkyPNG::FILTER_UP;      :encode_png_str_scanline_up
214     when ChunkyPNG::FILTER_AVERAGE; :encode_png_str_scanline_average
215     when ChunkyPNG::FILTER_PAETH;   :encode_png_str_scanline_paeth
216     else nil
217   end
218   
219   0.upto(height - 1) do |y|
220     stream << send(encode_method, row(y))
221   end
222   
223   # Now, apply filtering if any
224   if filter_method
225     (height - 1).downto(0) do |y|
226       pos = start_pos + y * (line_width + 1)
227       prev_pos = (y == 0) ? nil : pos - (line_width + 1)
228       send(filter_method, stream, pos, prev_pos, line_width, pixel_size)
229     end
230   end
231 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
188 def encode_png_image_with_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE)
189   stream = ChunkyPNG::Datastream.empty_bytearray
190   0.upto(6) do |pass|
191     subcanvas = self.class.adam7_extract_pass(pass, self)
192     subcanvas.encoding_palette = encoding_palette
193     subcanvas.encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering)
194   end
195   stream
196 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
172 def encode_png_image_without_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE)
173   stream = ChunkyPNG::Datastream.empty_bytearray
174   encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering)
175   stream
176 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
300 def encode_png_pixels_to_scanline_grayscale_1bit(pixels)
301   chars = []
302   pixels.each_slice(8) do |p1, p2, p3, p4, p5, p6, p7, p8|
303     chars << ((p1.nil? ? 0 : (p1 & 0x0000ffff) >> 15 << 7) |
304               (p2.nil? ? 0 : (p2 & 0x0000ffff) >> 15 << 6) |
305               (p3.nil? ? 0 : (p3 & 0x0000ffff) >> 15 << 5) |
306               (p4.nil? ? 0 : (p4 & 0x0000ffff) >> 15 << 4) |
307               (p5.nil? ? 0 : (p5 & 0x0000ffff) >> 15 << 3) |
308               (p6.nil? ? 0 : (p6 & 0x0000ffff) >> 15 << 2) |
309               (p7.nil? ? 0 : (p7 & 0x0000ffff) >> 15 << 1) |
310               (p8.nil? ? 0 : (p8 & 0x0000ffff) >> 15))
311   end
312   chars.pack('xC*')
313 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
318 def encode_png_pixels_to_scanline_grayscale_2bit(pixels)
319   chars = []
320   pixels.each_slice(4) do |p1, p2, p3, p4|
321     chars << ((p1.nil? ? 0 : (p1 & 0x0000ffff) >> 14 << 6) |
322               (p2.nil? ? 0 : (p2 & 0x0000ffff) >> 14 << 4) |
323               (p3.nil? ? 0 : (p3 & 0x0000ffff) >> 14 << 2) |
324               (p4.nil? ? 0 : (p4 & 0x0000ffff) >> 14))
325   end
326   chars.pack('xC*')
327 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
332 def encode_png_pixels_to_scanline_grayscale_4bit(pixels)
333   chars = []
334   pixels.each_slice(2) do |p1, p2|
335     chars << ((p1.nil? ? 0 : ((p1 & 0x0000ffff) >> 12) << 4) | (p2.nil? ? 0 : ((p2 & 0x0000ffff) >> 12)))
336   end
337   chars.pack('xC*')
338 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
343 def encode_png_pixels_to_scanline_grayscale_8bit(pixels)
344   pixels.map { |p| p >> 8 }.pack("xC#{width}")
345 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
350 def encode_png_pixels_to_scanline_grayscale_alpha_8bit(pixels)
351   pixels.pack("xn#{width}")
352 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
250 def encode_png_pixels_to_scanline_indexed_1bit(pixels)
251   chars = []
252   pixels.each_slice(8) do |p1, p2, p3, p4, p5, p6, p7, p8|
253     chars << ((encoding_palette.index(p1) << 7) |
254               (encoding_palette.index(p2) << 6) |
255               (encoding_palette.index(p3) << 5) |
256               (encoding_palette.index(p4) << 4) |
257               (encoding_palette.index(p5) << 3) |
258               (encoding_palette.index(p6) << 2) |
259               (encoding_palette.index(p7) << 1) |
260               (encoding_palette.index(p8)))
261   end
262   chars.pack('xC*')
263 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
268 def encode_png_pixels_to_scanline_indexed_2bit(pixels)
269   chars = []
270   pixels.each_slice(4) do |p1, p2, p3, p4|
271     chars << ((encoding_palette.index(p1) << 6) |
272               (encoding_palette.index(p2) << 4) |
273               (encoding_palette.index(p3) << 2) |
274               (encoding_palette.index(p4)))
275   end
276   chars.pack('xC*')
277 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
282 def encode_png_pixels_to_scanline_indexed_4bit(pixels)
283   chars = []
284   pixels.each_slice(2) do |p1, p2|
285     chars << ((encoding_palette.index(p1) << 4) | (encoding_palette.index(p2)))
286   end
287   chars.pack('xC*')
288 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
293 def encode_png_pixels_to_scanline_indexed_8bit(pixels)
294   pixels.map { |p| encoding_palette.index(p) }.pack("xC#{width}")
295 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
360 def encode_png_pixels_to_scanline_method(color_mode, depth)
361   encoder_method = case color_mode
362     when ChunkyPNG::COLOR_TRUECOLOR;       :"encode_png_pixels_to_scanline_truecolor_#{depth}bit"
363     when ChunkyPNG::COLOR_TRUECOLOR_ALPHA; :"encode_png_pixels_to_scanline_truecolor_alpha_#{depth}bit"
364     when ChunkyPNG::COLOR_INDEXED;         :"encode_png_pixels_to_scanline_indexed_#{depth}bit"
365     when ChunkyPNG::COLOR_GRAYSCALE;       :"encode_png_pixels_to_scanline_grayscale_#{depth}bit"
366     when ChunkyPNG::COLOR_GRAYSCALE_ALPHA; :"encode_png_pixels_to_scanline_grayscale_alpha_#{depth}bit"
367     else nil
368   end
369   
370   raise ChunkyPNG::NotSupported, "No encoder found for color mode #{color_mode} and #{depth}-bit depth!" unless respond_to?(encoder_method, true)
371   encoder_method
372 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
236 def encode_png_pixels_to_scanline_truecolor_8bit(pixels)
237   pixels.pack('x' + ('NX' * width))
238 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
243 def encode_png_pixels_to_scanline_truecolor_alpha_8bit(pixels)
244   pixels.pack("xN#{width}")
245 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
153 def encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, bit_depth = 8, interlace = ChunkyPNG::INTERLACING_NONE, filtering = ChunkyPNG::FILTER_NONE)
154 
155   if color_mode == ChunkyPNG::COLOR_INDEXED 
156     raise ChunkyPNG::ExpectationFailed, "This palette is not suitable for encoding!" if encoding_palette.nil? || !encoding_palette.can_encode?
157     raise ChunkyPNG::ExpectationFailed, "This palette has too many colors!" if encoding_palette.size > (1 << bit_depth)
158   end
159 
160   case interlace
161     when ChunkyPNG::INTERLACING_NONE;  encode_png_image_without_interlacing(color_mode, bit_depth, filtering)
162     when ChunkyPNG::INTERLACING_ADAM7; encode_png_image_with_interlacing(color_mode, bit_depth, filtering)
163     else raise ChunkyPNG::NotSupported, "Unknown interlacing method: #{interlace}!"
164   end
165 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
414 def encode_png_str_scanline_average(stream, pos, prev_pos, line_width, pixel_size)
415   line_width.downto(1) do |i|
416     a = (i > pixel_size) ? stream.getbyte(pos + i - pixel_size) : 0
417     b = prev_pos ? stream.getbyte(prev_pos + i) : 0
418     stream.setbyte(pos + i, (stream.getbyte(pos + i) - ((a + b) >> 1)) & 0xff)
419   end
420   stream.setbyte(pos, ChunkyPNG::FILTER_AVERAGE)
421 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
385 def encode_png_str_scanline_none(stream, pos, prev_pos, line_width, pixel_size)
386   # noop - this method shouldn't get called at all.
387 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
426 def encode_png_str_scanline_paeth(stream, pos, prev_pos, line_width, pixel_size)
427   line_width.downto(1) do |i|
428     a = (i > pixel_size) ? stream.getbyte(pos + i - pixel_size) : 0
429     b = (prev_pos) ? stream.getbyte(prev_pos + i) : 0
430     c = (prev_pos && i > pixel_size) ? stream.getbyte(prev_pos + i - pixel_size) : 0
431     p = a + b - c
432     pa = (p - a).abs
433     pb = (p - b).abs
434     pc = (p - c).abs
435     pr = (pa <= pb && pa <= pc) ? a : (pb <= pc ? b : c)
436     stream.setbyte(pos + i, (stream.getbyte(pos + i) - pr) & 0xff)
437   end
438   stream.setbyte(pos, ChunkyPNG::FILTER_PAETH)
439 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
392 def encode_png_str_scanline_sub(stream, pos, prev_pos, line_width, pixel_size)
393   line_width.downto(1) do |i|
394     a = (i > pixel_size) ? stream.getbyte(pos + i - pixel_size) : 0
395     stream.setbyte(pos + i, (stream.getbyte(pos + i) - a) & 0xff)
396   end
397   stream.setbyte(pos, ChunkyPNG::FILTER_SUB)
398 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
403 def encode_png_str_scanline_up(stream, pos, prev_pos, line_width, pixel_size)
404   line_width.downto(1) do |i|
405     b = prev_pos ? stream.getbyte(prev_pos + i) : 0
406     stream.setbyte(pos + i, (stream.getbyte(pos + i) - b) & 0xff)
407   end
408   stream.setbyte(pos, ChunkyPNG::FILTER_UP)
409 end