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
40 def save(filename, constraints = {})
41   File.open(filename, "wb") { |io| write(io, constraints) }
42 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
47 def to_blob(constraints = {})
48   to_datastream(constraints).to_blob
49 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
72 def to_datastream(constraints = {})
73   encoding = determine_png_encoding(constraints)
74 
75   ds = Datastream.new
76   ds.header_chunk = Chunk::Header.new(
77     width: width,
78     height: height,
79     color: encoding[:color_mode],
80     depth: encoding[:bit_depth],
81     interlace: encoding[:interlace]
82   )
83 
84   if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
85     ds.palette_chunk      = encoding_palette.to_plte_chunk
86     ds.transparency_chunk = encoding_palette.to_trns_chunk unless encoding_palette.opaque?
87   end
88   data           = encode_png_pixelstream(encoding[:color_mode], encoding[:bit_depth], encoding[:interlace], encoding[:filtering])
89   ds.data_chunks = Chunk::ImageData.split_in_chunks(data, encoding[:compression])
90   ds.end_chunk   = Chunk::End.new
91   ds
92 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
32 def write(io, constraints = {})
33   to_datastream(constraints).write(io)
34 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
105 def determine_png_encoding(constraints = {})
106   encoding = case constraints
107     when :fast_rgb         then {color_mode: ChunkyPNG::COLOR_TRUECOLOR, compression: Zlib::BEST_SPEED}
108     when :fast_rgba        then {color_mode: ChunkyPNG::COLOR_TRUECOLOR_ALPHA, compression: Zlib::BEST_SPEED}
109     when :best_compression then {compression: Zlib::BEST_COMPRESSION, filtering: ChunkyPNG::FILTER_PAETH}
110     when :good_compression then {compression: Zlib::BEST_COMPRESSION, filtering: ChunkyPNG::FILTER_NONE}
111     when :no_compression   then {compression: Zlib::NO_COMPRESSION}
112     when :black_and_white  then {color_mode: ChunkyPNG::COLOR_GRAYSCALE, bit_depth: 1}
113     when Hash              then constraints
114     else raise ChunkyPNG::Exception, "Unknown encoding preset: #{constraints.inspect}"
115   end
116 
117   # Do not create a palette when the encoding is given and does not require a palette.
118   if encoding[:color_mode]
119     if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
120       self.encoding_palette = palette
121       encoding[:bit_depth] ||= encoding_palette.determine_bit_depth
122     else
123       encoding[:bit_depth] ||= 8
124     end
125   else
126     self.encoding_palette = palette
127     suggested_color_mode, suggested_bit_depth = encoding_palette.best_color_settings
128     encoding[:color_mode] ||= suggested_color_mode
129     encoding[:bit_depth]  ||= suggested_bit_depth
130   end
131 
132   # Use Zlib's default for compression unless otherwise provided.
133   encoding[:compression] ||= Zlib::DEFAULT_COMPRESSION
134 
135   encoding[:interlace] = case encoding[:interlace]
136     when nil, false then ChunkyPNG::INTERLACING_NONE
137     when true then ChunkyPNG::INTERLACING_ADAM7
138     else encoding[:interlace]
139   end
140 
141   encoding[:filtering] ||= case encoding[:compression]
142     when Zlib::BEST_COMPRESSION then ChunkyPNG::FILTER_PAETH
143     when Zlib::NO_COMPRESSION..Zlib::BEST_SPEED then ChunkyPNG::FILTER_NONE
144     else ChunkyPNG::FILTER_UP
145   end
146   encoding
147 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
204 def encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering)
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_NONE    then nil
213     when ChunkyPNG::FILTER_SUB     then :encode_png_str_scanline_sub
214     when ChunkyPNG::FILTER_UP      then :encode_png_str_scanline_up
215     when ChunkyPNG::FILTER_AVERAGE then :encode_png_str_scanline_average
216     when ChunkyPNG::FILTER_PAETH   then :encode_png_str_scanline_paeth
217     else raise ArgumentError, "Filtering method #{filtering} is not supported"
218   end
219 
220   0.upto(height - 1) do |y|
221     stream << send(encode_method, row(y))
222   end
223 
224   # Now, apply filtering if any
225   if filter_method
226     (height - 1).downto(0) do |y|
227       pos = start_pos + y * (line_width + 1)
228       prev_pos = y == 0 ? nil : pos - (line_width + 1)
229       send(filter_method, stream, pos, prev_pos, line_width, pixel_size)
230     end
231   end
232 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
189 def encode_png_image_with_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE)
190   stream = ChunkyPNG::Datastream.empty_bytearray
191   0.upto(6) do |pass|
192     subcanvas = self.class.adam7_extract_pass(pass, self)
193     subcanvas.encoding_palette = encoding_palette
194     subcanvas.encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering)
195   end
196   stream
197 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
173 def encode_png_image_without_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE)
174   stream = ChunkyPNG::Datastream.empty_bytearray
175   encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering)
176   stream
177 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
305 def encode_png_pixels_to_scanline_grayscale_1bit(pixels)
306   chars = []
307   pixels.each_slice(8) do |p1, p2, p3, p4, p5, p6, p7, p8|
308     chars << ((p1.nil? ? 0 : (p1 & 0x0000ffff) >> 15 << 7) |
309               (p2.nil? ? 0 : (p2 & 0x0000ffff) >> 15 << 6) |
310               (p3.nil? ? 0 : (p3 & 0x0000ffff) >> 15 << 5) |
311               (p4.nil? ? 0 : (p4 & 0x0000ffff) >> 15 << 4) |
312               (p5.nil? ? 0 : (p5 & 0x0000ffff) >> 15 << 3) |
313               (p6.nil? ? 0 : (p6 & 0x0000ffff) >> 15 << 2) |
314               (p7.nil? ? 0 : (p7 & 0x0000ffff) >> 15 << 1) |
315               (p8.nil? ? 0 : (p8 & 0x0000ffff) >> 15))
316   end
317   chars.pack("xC*")
318 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
323 def encode_png_pixels_to_scanline_grayscale_2bit(pixels)
324   chars = []
325   pixels.each_slice(4) do |p1, p2, p3, p4|
326     chars << ((p1.nil? ? 0 : (p1 & 0x0000ffff) >> 14 << 6) |
327               (p2.nil? ? 0 : (p2 & 0x0000ffff) >> 14 << 4) |
328               (p3.nil? ? 0 : (p3 & 0x0000ffff) >> 14 << 2) |
329               (p4.nil? ? 0 : (p4 & 0x0000ffff) >> 14))
330   end
331   chars.pack("xC*")
332 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
337 def encode_png_pixels_to_scanline_grayscale_4bit(pixels)
338   chars = []
339   pixels.each_slice(2) do |p1, p2|
340     chars << ((p1.nil? ? 0 : ((p1 & 0x0000ffff) >> 12) << 4) | (p2.nil? ? 0 : ((p2 & 0x0000ffff) >> 12)))
341   end
342   chars.pack("xC*")
343 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
348 def encode_png_pixels_to_scanline_grayscale_8bit(pixels)
349   pixels.map { |p| p >> 8 }.pack("xC#{width}")
350 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
355 def encode_png_pixels_to_scanline_grayscale_alpha_8bit(pixels)
356   pixels.pack("xn#{width}")
357 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
251 def encode_png_pixels_to_scanline_indexed_1bit(pixels)
252   chars = []
253   pixels.each_slice(8) do |p1, p2, p3, p4, p5, p6, p7, p8|
254     chars << (
255       (encoding_palette.index(p1) << 7) |
256       (encoding_palette.index(p2) << 6) |
257       (encoding_palette.index(p3) << 5) |
258       (encoding_palette.index(p4) << 4) |
259       (encoding_palette.index(p5) << 3) |
260       (encoding_palette.index(p6) << 2) |
261       (encoding_palette.index(p7) << 1) |
262       encoding_palette.index(p8)
263     )
264   end
265   chars.pack("xC*")
266 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
271 def encode_png_pixels_to_scanline_indexed_2bit(pixels)
272   chars = []
273   pixels.each_slice(4) do |p1, p2, p3, p4|
274     chars << (
275       (encoding_palette.index(p1) << 6) |
276       (encoding_palette.index(p2) << 4) |
277       (encoding_palette.index(p3) << 2) |
278       encoding_palette.index(p4)
279     )
280   end
281   chars.pack("xC*")
282 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
287 def encode_png_pixels_to_scanline_indexed_4bit(pixels)
288   chars = []
289   pixels.each_slice(2) do |p1, p2|
290     chars << ((encoding_palette.index(p1) << 4) | encoding_palette.index(p2))
291   end
292   chars.pack("xC*")
293 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
298 def encode_png_pixels_to_scanline_indexed_8bit(pixels)
299   pixels.map { |p| encoding_palette.index(p) }.pack("xC#{width}")
300 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
364 def encode_png_pixels_to_scanline_method(color_mode, depth)
365   encoder_method = case color_mode
366     when ChunkyPNG::COLOR_TRUECOLOR       then :"encode_png_pixels_to_scanline_truecolor_#{depth}bit"
367     when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then :"encode_png_pixels_to_scanline_truecolor_alpha_#{depth}bit"
368     when ChunkyPNG::COLOR_INDEXED         then :"encode_png_pixels_to_scanline_indexed_#{depth}bit"
369     when ChunkyPNG::COLOR_GRAYSCALE       then :"encode_png_pixels_to_scanline_grayscale_#{depth}bit"
370     when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then :"encode_png_pixels_to_scanline_grayscale_alpha_#{depth}bit"
371   end
372 
373   raise ChunkyPNG::NotSupported, "No encoder found for color mode #{color_mode} and #{depth}-bit depth!" unless respond_to?(encoder_method, true)
374   encoder_method
375 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
237 def encode_png_pixels_to_scanline_truecolor_8bit(pixels)
238   pixels.pack("x" + ("NX" * width))
239 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
244 def encode_png_pixels_to_scanline_truecolor_alpha_8bit(pixels)
245   pixels.pack("xN#{width}")
246 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
155 def encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, bit_depth = 8, interlace = ChunkyPNG::INTERLACING_NONE, filtering = ChunkyPNG::FILTER_NONE)
156   if color_mode == ChunkyPNG::COLOR_INDEXED
157     raise ChunkyPNG::ExpectationFailed, "This palette is not suitable for encoding!" if encoding_palette.nil? || !encoding_palette.can_encode?
158     raise ChunkyPNG::ExpectationFailed, "This palette has too many colors!" if encoding_palette.size > (1 << bit_depth)
159   end
160 
161   case interlace
162     when ChunkyPNG::INTERLACING_NONE  then encode_png_image_without_interlacing(color_mode, bit_depth, filtering)
163     when ChunkyPNG::INTERLACING_ADAM7 then encode_png_image_with_interlacing(color_mode, bit_depth, filtering)
164     else raise ChunkyPNG::NotSupported, "Unknown interlacing method: #{interlace}!"
165   end
166 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
415 def encode_png_str_scanline_average(stream, pos, prev_pos, line_width, pixel_size)
416   line_width.downto(1) do |i|
417     a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0
418     b = prev_pos ? stream.getbyte(prev_pos + i) : 0
419     stream.setbyte(pos + i, (stream.getbyte(pos + i) - ((a + b) >> 1)) & 0xff)
420   end
421   stream.setbyte(pos, ChunkyPNG::FILTER_AVERAGE)
422 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
386 def encode_png_str_scanline_none(stream, pos, prev_pos, line_width, pixel_size)
387   # noop - this method shouldn't get called at all.
388 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
427 def encode_png_str_scanline_paeth(stream, pos, prev_pos, line_width, pixel_size)
428   line_width.downto(1) do |i|
429     a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0
430     b = prev_pos ? stream.getbyte(prev_pos + i) : 0
431     c = prev_pos && i > pixel_size ? stream.getbyte(prev_pos + i - pixel_size) : 0
432     p = a + b - c
433     pa = (p - a).abs
434     pb = (p - b).abs
435     pc = (p - c).abs
436     pr = if pa <= pb && pa <= pc
437       a
438     else
439       pb <= pc ? b : c
440     end
441 
442     stream.setbyte(pos + i, (stream.getbyte(pos + i) - pr) & 0xff)
443   end
444   stream.setbyte(pos, ChunkyPNG::FILTER_PAETH)
445 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
393 def encode_png_str_scanline_sub(stream, pos, prev_pos, line_width, pixel_size)
394   line_width.downto(1) do |i|
395     a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0
396     stream.setbyte(pos + i, (stream.getbyte(pos + i) - a) & 0xff)
397   end
398   stream.setbyte(pos, ChunkyPNG::FILTER_SUB)
399 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
404 def encode_png_str_scanline_up(stream, pos, prev_pos, line_width, pixel_size)
405   line_width.downto(1) do |i|
406     b = prev_pos ? stream.getbyte(prev_pos + i) : 0
407     stream.setbyte(pos + i, (stream.getbyte(pos + i) - b) & 0xff)
408   end
409   stream.setbyte(pos, ChunkyPNG::FILTER_UP)
410 end