| 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)
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