class Prawn::Images::PNG
A convenience class that wraps the logic for extracting the parts of a PNG image that we need to embed them in a PDF.
Attributes
Extracted alpha-channel. @return [String, nil]
Bits per sample or per palette index. @return [Integer]
Color type. @return [Integer]
Compression method. @return [Integer]
Filter method. @return [Integer]
Image height in pixels. @return [Integer]
Image data. @return [String]
Interlace method. @return [Integer]
Palette data. @return [String]
Scaled height of the image in PDF points. @return [Number]
Scaled width of the image in PDF points. @return [Number]
Transparency data. @return [Hash{Symbol => String}]
Public Class Methods
Source
# File lib/prawn/images/png.rb, line 68 def self.can_render?(image_blob) image_blob[0, 8].unpack('C*') == [137, 80, 78, 71, 13, 10, 26, 10] end
Can this image handler process this image?
@param image_blob [String] @return [Boolean]
Source
# File lib/prawn/images/png.rb, line 75 def initialize(data) super() data = StringIO.new(data.dup) data.read(8) # Skip the default header @palette = +'' @img_data = +'' @transparency = {} loop do chunk_size = data.read(4).unpack1('N') section = data.read(4) case section when 'IHDR' # we can grab other interesting values from here (like width, # height, etc) values = data.read(chunk_size).unpack('NNCCCCC') @width = values[0] @height = values[1] @bits = values[2] @color_type = values[3] @compression_method = values[4] @filter_method = values[5] @interlace_method = values[6] when 'PLTE' @palette << data.read(chunk_size) when 'IDAT' @img_data << data.read(chunk_size) when 'tRNS' # This chunk can only occur once and it must occur after the # PLTE chunk and before the IDAT chunk @transparency = {} case @color_type when 3 @transparency[:palette] = data.read(chunk_size).unpack('C*') when 0 # Greyscale. Corresponding to entries in the PLTE chunk. # Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1 grayval = data.read(chunk_size).unpack1('n') @transparency[:grayscale] = grayval when 2 # True colour with proper alpha channel. @transparency[:rgb] = data.read(chunk_size).unpack('nnn') end when 'IEND' # we've got everything we need, exit the loop break else # unknown (or un-important) section, skip over it data.seek(data.pos + chunk_size) end data.read(4) # Skip the CRC end @img_data = Zlib::Inflate.inflate(@img_data) end
Process a new PNG image
@param data [String] A binary string of PNG data.
Public Instance Methods
Source
# File lib/prawn/images/png.rb, line 165 def alpha_channel? return true if color_type == 4 || color_type == 6 return @transparency.any? if color_type == 3 false end
Is there an alpha-channel in this image?
@return [Boolean]
Source
# File lib/prawn/images/png.rb, line 177 def build_pdf_object(document) if compression_method != 0 raise Errors::UnsupportedImageType, 'PNG uses an unsupported compression method' end if filter_method != 0 raise Errors::UnsupportedImageType, 'PNG uses an unsupported filter method' end if interlace_method != 0 raise Errors::UnsupportedImageType, 'PNG uses unsupported interlace method' end # some PNG types store the colour and alpha channel data together, # which the PDF spec doesn't like, so split it out. split_alpha_channel! case colors when 1 color = :DeviceGray when 3 color = :DeviceRGB else raise Errors::UnsupportedImageType, "PNG uses an unsupported number of colors (#{png.colors})" end # build the image dict obj = document.ref!( Type: :XObject, Subtype: :Image, Height: height, Width: width, BitsPerComponent: bits, ) # append the actual image data to the object as a stream obj << img_data obj.stream.filters << { FlateDecode: { Predictor: 15, Colors: colors, BitsPerComponent: bits, Columns: width, }, } # sort out the colours of the image if palette.empty? obj.data[:ColorSpace] = color else # embed the colour palette in the PDF as a object stream palette_obj = document.ref!({}) palette_obj << palette # build the color space array for the image obj.data[:ColorSpace] = [ :Indexed, :DeviceRGB, (palette.size / 3) - 1, palette_obj, ] end # ************************************* # add transparency data if necessary # ************************************* # For PNG color types 0, 2 and 3, the transparency data is stored in # a dedicated PNG chunk, and is exposed via the transparency attribute # of the PNG class. if transparency[:grayscale] # Use Color Key Masking (spec section 4.8.5) # - An array with N elements, where N is two times the number of color # components. val = transparency[:grayscale] obj.data[:Mask] = [val, val] elsif transparency[:rgb] # Use Color Key Masking (spec section 4.8.5) # - An array with N elements, where N is two times the number of color # components. rgb = transparency[:rgb] obj.data[:Mask] = rgb.map { |x| [x, x] }.flatten end # For PNG color types 4 and 6, the transparency data is stored as # a alpha channel mixed in with the main image data. The PNG class # separates it out for us and makes it available via the alpha_channel # attribute if alpha_channel? smask_obj = document.ref!( Type: :XObject, Subtype: :Image, Height: height, Width: width, BitsPerComponent: bits, ColorSpace: :DeviceGray, Decode: [0, 1], ) smask_obj.stream << alpha_channel smask_obj.stream.filters << { FlateDecode: { Predictor: 15, Colors: 1, BitsPerComponent: bits, Columns: width, }, } obj.data[:SMask] = smask_obj end obj end
Build a PDF object representing this image in ‘document`, and return a Reference to it.
@param document [Prawn::Document] @return [PDF::Core::Reference]
Source
# File lib/prawn/images/png.rb, line 138 def colors case color_type when 0, 3, 4 1 when 2, 6 3 end end
Number of color components to each pixel.
@return [Integer]
Source
# File lib/prawn/images/png.rb, line 299 def min_pdf_version if bits > 8 # 16-bit color only supported in 1.5+ (ISO 32000-1:2008 8.9.5.1) 1.5 elsif alpha_channel? # Need transparency for SMask 1.4 else 1.0 end end
Returns the minimum PDF version required to support this image.
@return [Float]
Source
# File lib/prawn/images/png.rb, line 152 def split_alpha_channel! if alpha_channel? if color_type == 3 generate_alpha_channel else split_image_data end end end
Split the alpha channel data from the raw image data in images where it’s required.
@private @return [void]