Module | ChunkyPNG::Canvas::PNGEncoding |
In: |
lib/chunky_png/canvas/png_encoding.rb
|
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
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] |
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, line 42 42: def save(filename, constraints = {}) 43: File.open(filename, 'wb') { |io| write(io, constraints) } 44: end
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, line 49 49: def to_blob(constraints = {}) 50: to_datastream(constraints).to_blob 51: end
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, line 74 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
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, line 34 34: def write(io, constraints = {}) 35: to_datastream(constraints).write(io) 36: end
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, line 102 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
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, line 203 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
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, line 188 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
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, line 172 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
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, line 300 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
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, line 318 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
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, line 332 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
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, line 343 343: def encode_png_pixels_to_scanline_grayscale_8bit(pixels) 344: pixels.map { |p| p >> 8 }.pack("xC#{width}") 345: end
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, line 350 350: def encode_png_pixels_to_scanline_grayscale_alpha_8bit(pixels) 351: pixels.pack("xn#{width}") 352: end
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, line 250 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
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, line 268 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
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, line 282 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
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, line 293 293: def encode_png_pixels_to_scanline_indexed_8bit(pixels) 294: pixels.map { |p| encoding_palette.index(p) }.pack("xC#{width}") 295: end
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, line 360 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""encode_png_pixels_to_scanline_truecolor_#{depth}bit" 363: when ChunkyPNG::COLOR_TRUECOLOR_ALPHA; "encode_png_pixels_to_scanline_truecolor_alpha_#{depth}bit""encode_png_pixels_to_scanline_truecolor_alpha_#{depth}bit" 364: when ChunkyPNG::COLOR_INDEXED; "encode_png_pixels_to_scanline_indexed_#{depth}bit""encode_png_pixels_to_scanline_indexed_#{depth}bit" 365: when ChunkyPNG::COLOR_GRAYSCALE; "encode_png_pixels_to_scanline_grayscale_#{depth}bit""encode_png_pixels_to_scanline_grayscale_#{depth}bit" 366: when ChunkyPNG::COLOR_GRAYSCALE_ALPHA; "encode_png_pixels_to_scanline_grayscale_alpha_#{depth}bit""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
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, line 236 236: def encode_png_pixels_to_scanline_truecolor_8bit(pixels) 237: pixels.pack('x' + ('NX' * width)) 238: end
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, line 243 243: def encode_png_pixels_to_scanline_truecolor_alpha_8bit(pixels) 244: pixels.pack("xN#{width}") 245: end
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, line 153 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
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, line 414 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
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, line 385 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
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, line 426 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
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, line 392 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
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, line 403 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