class HexaPDF::Type::Image

Represents an image XObject of a PDF document.

See: PDF1.7 s8.8

Constants

Info

The structure that is returned by the Image#info method.

Attributes

source_path[RW]

Returns the source path that was used when creating the image object.

This value is only set when the image object was created by using the image loading facility and not when the image is part of a loaded PDF file.

Public Instance Methods

height() click to toggle source

Returns the height of the image.

# File lib/hexapdf/type/image.rb, line 92
def height
  self[:Height]
end
info() click to toggle source

Returns an Info structure with information about the image.

Available accessors:

type

The type of the image. Either :jpeg, :jp2, :jbig2, :ccitt or :png.

width

The width of the image.

height

The height of the image.

color_space

The color space the image uses. Either :rgb, :cmyk, :gray or :other.

indexed

Whether the image uses an indexed color space or not.

components

The number of color components of the color space, or -1 if the number couldn't be determined.

bits_per_component

The number of bits per color component.

writable

Whether the image can be written by HexaPDF.

extension

The file extension that would be used when writing the file. Either jpg, jpx or png. Only meaningful when writable is true.

# File lib/hexapdf/type/image.rb, line 120
def info
  result = Info.new
  result.width = self[:Width]
  result.height = self[:Height]
  result.bits_per_component = self[:BitsPerComponent]
  result.indexed = false
  result.writable = true

  filter, rest = *self[:Filter]
  case filter
  when :DCTDecode
    result.type = :jpeg
    result.extension = 'jpg'
  when :JPXDecode
    result.type = :jp2
    result.extension = 'jpx'
  when :JBIG2Decode
    result.type = :jbig2
  when :CCITTFaxDecode
    result.type = :ccitt
  else
    result.type = :png
    result.extension = 'png'
  end

  if rest || ![:FlateDecode, :DCTDecode, :JPXDecode, nil].include?(filter)
    result.writable = false
  end

  color_space, = *self[:ColorSpace]
  if color_space == :Indexed
    result.indexed = true
    color_space, = *self[:ColorSpace][1]
  end
  case color_space
  when :DeviceRGB, :CalRGB
    result.color_space = :rgb
    result.components = 3
  when :DeviceGray, :CalGray
    result.color_space = :gray
    result.components = 1
  when :DeviceCMYK
    result.color_space = :cmyk
    result.components = 4
    result.writable = false if result.type == :png
  else
    result.color_space = :other
    result.components = -1
    result.writable = false if result.type == :png
  end

  result.writable = false if self[:SMask]

  result
end
width() click to toggle source

Returns the width of the image.

# File lib/hexapdf/type/image.rb, line 87
def width
  self[:Width]
end
write(basename) click to toggle source
write(io)

Saves this image XObject to the file with the given name and appends the correct extension (if the name already contains this extension, the name is used as is), or the given IO object.

Raises an error if the image format is not supported.

The output format and extension depends on the image type as returned by the info method:

:jpeg

Saved as a JPEG file with the extension '.jpg'

:jp2

Saved as a JPEG2000 file with the extension '.jpx'

:png

Saved as a PNG file with the extension '.png'

# File lib/hexapdf/type/image.rb, line 191
def write(name_or_io)
  info = self.info

  unless info.writable
    raise HexaPDF::Error, "PDF image format not supported for writing"
  end

  io = if name_or_io.kind_of?(String)
         File.open(name_or_io.sub(/\.#{info.extension}\z/, '') << "." << info.extension, "wb")
       else
         name_or_io
       end

  if info.type == :jpeg || info.type == :jp2
    source = stream_source
    while source.alive? && (chunk = source.resume)
      io << chunk
    end
  else
    write_png(io, info)
  end
ensure
  io.close if io && name_or_io.kind_of?(String)
end

Private Instance Methods

png_chunk(type, data = '') click to toggle source

Returns the binary representation of the PNG chunk for the given chunk type and data.

# File lib/hexapdf/type/image.rb, line 275
def png_chunk(type, data = '')
  [data.length].pack("N") << type << data << [Zlib.crc32(data, Zlib.crc32(type))].pack("N")
end
write_png(io, info) click to toggle source

Writes the image as PNG to the given IO stream.

# File lib/hexapdf/type/image.rb, line 219
def write_png(io, info)
  io << ImageLoader::PNG::MAGIC_FILE_MARKER

  color_type = if info.indexed
                 ImageLoader::PNG::INDEXED
               elsif info.color_space == :rgb
                 ImageLoader::PNG::TRUECOLOR
               else
                 ImageLoader::PNG::GREYSCALE
               end

  io << png_chunk('IHDR', [info.width, info.height, info.bits_per_component,
                           color_type, 0, 0, 0].pack('N2C5'))

  if key?(:Intent)
    # PNG s11.3.3.5
    intent = ImageLoader::PNG::RENDERING_INTENT_MAP.rassoc(self[:Intent]).first
    io << png_chunk('sRGB', intent.chr) <<
      png_chunk('gAMA', [45455].pack('N')) <<
      png_chunk('cHRM', [31270, 32900, 64000, 33000, 30000, 60000, 15000, 6000].pack('N8'))
  end

  if color_type == ImageLoader::PNG::INDEXED
    palette_data = self[:ColorSpace][3]
    palette_data = palette_data.stream unless palette_data.kind_of?(String)
    palette = ''.b
    if info.color_space == :rgb
      palette = palette_data[0, palette_data.length - palette_data.length % 3]
    else
      palette_data.each_byte {|byte| palette << byte << byte << byte }
    end
    io << png_chunk('PLTE', palette)
  end

  if self[:Mask].kind_of?(PDFArray) && self[:Mask].each_slice(2).all? {|a, b| a == b } &&
      (color_type == ImageLoader::PNG::TRUECOLOR || color_type == ImageLoader::PNG::GREYSCALE)
    io << png_chunk('tRNS', self[:Mask].each_slice(2).map {|a, _| a }.pack('n*'))
  end

  filter, = *self[:Filter]
  decode_parms, = *self[:DecodeParms]
  if filter == :FlateDecode && decode_parms && decode_parms[:Predictor].to_i >= 10
    data = stream_source
  else
    colors = (color_type == ImageLoader::PNG::INDEXED ? 1 : info.components)
    flate_decode = config.constantize('filter.map', :FlateDecode)
    data = flate_decode.encoder(stream_decoder, Predictor: 15,
                                Colors: colors, Columns: info.width,
                                BitsPerComponent: info.bits_per_component)
  end
  io << png_chunk('IDAT', Filter.string_from_source(data))

  io << png_chunk('IEND')
end