class Branding::PNG

This is a simple PNG decoder, inspired and adapted from ChunkyPNG

Constants

SIGNATURE

Attributes

color[R]
compression[R]
depth[R]
filtering[R]
height[R]
interlace[R]
width[R]

Public Class Methods

from_file(path) click to toggle source
# File lib/branding/png.rb, line 155
def self.from_file(path)
  new(File.open(path, 'rb'))
end
new(io) click to toggle source
# File lib/branding/png.rb, line 159
def initialize(io)
  signature = io.read(SIGNATURE.length)
  raise 'Signature mismatch' unless signature == SIGNATURE

  @data = ''

  until io.eof?
    type, content = read_chunk(io)

    case type
    when 'IHDR'
      fields = content.unpack('NNC5')
      @width, @height, @depth, @color, @compression, @filtering, @interlace = fields

    when 'IDAT'
      @data << content
    end
  end

  unless depth == 8
    raise NotImplementedError, 'only supporting 8bit color depth'
  end

  unless color == 2 || color == 6
    raise NotImplementedError, 'only supporting true color, with or without alpha'
  end

  unless filtering == 0
    raise NotImplementedError, 'does not supporting filtering'
  end

  unless compression == 0
    raise NotImplementedError, 'only supporting deflate compression'
  end
end

Public Instance Methods

color_channels() click to toggle source

the number of color channels. Not the PNG “color mode”

# File lib/branding/png.rb, line 205
def color_channels
  color == 2 ? 3 : 4
end
pixels() click to toggle source
# File lib/branding/png.rb, line 195
def pixels
  if block_given?
    imagedata.each_pixel
  else
    imagedata.enum_for(:each_pixel).to_a
  end
end

Private Instance Methods

imagedata() click to toggle source
# File lib/branding/png.rb, line 211
def imagedata
  Imagedata.new(data: @data, scanline_width: width, color_channels: color_channels)
end
read_bytes(io, length) click to toggle source
# File lib/branding/png.rb, line 225
def read_bytes(io, length)
  data = io.read(length)

  if data.nil? || data.bytesize != length
    raise "Could not read #{length} bytes from io"
  end

  data
end
read_chunk(io) click to toggle source
# File lib/branding/png.rb, line 215
def read_chunk(io)
  length, type = read_bytes(io, 8).unpack('Na4')

  content = read_bytes(io, length)
  crc     = read_bytes(io, 4).unpack('N').first
  verify_crc(type, content, crc)

  [type, content]
end
verify_crc(type, content, found_crc) click to toggle source
# File lib/branding/png.rb, line 235
def verify_crc(type, content, found_crc)
  expected_crc = Zlib.crc32(content, Zlib.crc32(type))
  raise 'Chuck CRC mismatch!' if found_crc != expected_crc
end