class FormatParser::PNGParser

Constants

COLOR_TYPES
PNG_HEADER_BYTES
PNG_MIME_TYPE
TRANSPARENCY_PER_COLOR_TYPE

Public Instance Methods

call(io) click to toggle source
# File lib/parsers/png_parser.rb, line 23
def call(io)
  io = FormatParser::IOConstraint.new(io)
  magic_bytes = safe_read(io, PNG_HEADER_BYTES.bytesize)
  return unless magic_bytes == PNG_HEADER_BYTES

  chunk_length, chunk_type = chunk_length_and_type(io)

  # For later: look at gAMA and iCCP chunks too. For now,
  # all we care about is the IHDR chunk, and it must have the
  # correct length as well.
  # IHDR _must_ come first, no exceptions. If it doesn't
  # we should not consider this a valid PNG.
  return unless chunk_type == 'IHDR' && chunk_length == 13

  chunk_data = safe_read(io, chunk_length)
  # Width:              4 bytes
  # Height:             4 bytes
  # Bit depth:          1 byte
  # Color type:         1 byte (0, 2, 3, 4, 6)
  # Compression method: 1 byte
  # Filter method:      1 byte
  # Interlace method:   1 byte
  w, h, _bit_depth, color_type, _compression_method,
    _filter_method, _interlace_method = chunk_data.unpack('N2C5')

  color_mode = COLOR_TYPES.fetch(color_type)
  has_transparency = TRANSPARENCY_PER_COLOR_TYPE[color_type]

  # Read the next chunk. If it turns out to be acTL (animation control)
  # we are dealing with an APNG.
  safe_skip(io, 4)

  chunk_length, chunk_type = chunk_length_and_type(io)
  if chunk_length == 8 && chunk_type == 'acTL'
    # https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk
    # Unlike GIF, we do have the frame count that we can recover
    has_animation = true
    num_frames, _loop_n_times = safe_read(io, 8).unpack('NN')
  end

  FormatParser::Image.new(
    format: :png,
    width_px: w,
    height_px: h,
    has_transparency: has_transparency,
    color_mode: color_mode,
    has_multiple_frames: has_animation,
    num_animation_or_video_frames: num_frames,
    content_type: PNG_MIME_TYPE,
  )
end
chunk_length_and_type(io) click to toggle source
# File lib/parsers/png_parser.rb, line 75
def chunk_length_and_type(io)
  safe_read(io, 8).unpack('Na4')
end
likely_match?(filename) click to toggle source
# File lib/parsers/png_parser.rb, line 19
def likely_match?(filename)
  filename =~ /\.png$/i
end