Skip to content
Permalink
Browse files

Initial commit - work in progress.

  • Loading branch information
wvanbergen committed Jan 4, 2010
0 parents commit aa8a9378eedfc02aa1d0d1e05c313badc76594a7
@@ -0,0 +1 @@
.DS_Store
@@ -0,0 +1,9 @@
require 'zlib'

require 'chunky_png/datastream'
require 'chunky_png/chunk'
require 'chunky_png/reader'
require 'chunky_png/pixel_matrix'
require 'chunky_png/color'
require 'chunky_png/image'

@@ -0,0 +1,83 @@
module ChunkyPNG
class Chunk

def self.read(io)

length, type = io.read(8).unpack('Na4')
content = io.read(length)
crc = io.read(4).unpack('N').first

# verify_crc!(type, content, crc)

CHUNK_TYPES.fetch(type, Generic).read(type, content)
end

class Base
attr_accessor :type

def initialize(type, attributes = {})
self.type = type
attributes.each { |k, v| send("#{k}=", v) }
end

def write_with_crc(io, content)
message = type + content
io << [content.length].pack('N') << message << [Zlib.crc32(message)].pack('N')
end

def write(io)
write_with_crc(io, content || '')
end
end

class Generic < Base

attr_accessor :content

def initialize(type, content = '')
super(type, :content => content)
end

def self.read(type, content)
self.new(type, content)
end
end

class Header < Base

COLOR_GRAYSCALE = 0
COLOR_TRUECOLOR = 2
COLOR_INDEXED = 3
COLOR_GRAYSCALE_ALPHA = 4
COLOR_TRUECOLOR_ALPHA = 6

attr_accessor :width, :height, :depth, :color, :compression, :filtering, :interlace

def initialize(attrs = {})
super('IHDR', attrs)
@depth ||= 8
@color ||= COLOR_TRUECOLOR
@compression ||= 0
@filtering ||= 0
@interlace ||= 0
end

def self.read(type, content)
fields = content.unpack('NNC*5')
self.new(:width => fields[0], :height => fields[1], :depth => fields[2], :color => fields[3],
:compression => fields[4], :filtering => fields[5], :interlace => fields[6])
end

def content
[width, height, depth, color, compression, filtering, interlace].pack('NNC5')
end
end

# Maps chunk types to classes.
# If a chunk type is not given in this hash, a generic chunk type will be used.
CHUNK_TYPES = {
'IHDR' => Header,
}

end
end
@@ -0,0 +1,23 @@
module ChunkyPNG
class Color < Struct.new(:r, :g, :b, :a)

BLACK = self.new( 0, 0, 0, 255)
WHITE = self.new(255, 255, 255, 255)

def to_true_color
[r, g, b].pack('CCC')
end

def inspect
'#%02x%02x%02x' % [r, g, b]
end

def self.rgb(r, g, b)
self.new(r, g, b, 255)
end

def self.rgba(r, g, b, a)
self.new(r, g, b, a)
end
end
end
@@ -0,0 +1,31 @@
module ChunkyPNG

class Datastream

SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10].pack('C*8')

attr_accessor :chunks

def initialize
@chunks = []
end

def header
chunks.first
end

def write(io)
io << SIGNATURE
chunks.each { |c| c.write(io) }
end

def pixel_matrix
@pixel_matrix ||= begin
data = ""
chunks.each { |c| data << c.content if c.type == 'IDAT' }
streamdata = Zlib::Inflate.inflate(data)
matrix = PNG::PixelMatrix.load(header, streamdata)
end
end
end
end
@@ -0,0 +1,23 @@
module ChunkyPNG
class Image

attr_reader :width, :height, :pixelvector

def initialize(width, height)
@width, @height = width, height
black_pixel = PNG::Color.new(255, 0, 0)
@pixelvector = Array.new(width * height, black_pixel)
end


def write(io)
datastream = PNG::Datastream.new
datastream.chunks << PNG::Chunk::Header.new(:width => width, :height => height)

pixels = @pixelvector.map(&:to_true_color).join('')
datastream.chunks << PNG::Chunk::PixelData.new(pixels)
datastream.chunks << PNG::Chunk::Generic.new('IEND')
datastream.write(io)
end
end
end
@@ -0,0 +1,75 @@
module ChunkyPNG

class PixelMatrix

FILTER_NONE = 0
FILTER_SUB = 1
FILTER_UP = 2
FILTER_AVERAGE = 3
FILTER_PAETH = 4

attr_accessor :pixels, :width, :height

def self.load(header, content)
matrix = self.new(header.width, header.height)
matrix.decode_pixelstream(content, header)
return matrix
end

def initialize(width, height, background_color = PNG::Color::Black)
@width, @height = width, height
@pixels = Array.new(width * height)
end

def decode_pixelstream(stream, header = nil)
verify_length!(stream.length)

decoded_bytes = Array.new(header.width * 3, 0)
@pixels = []
height.times do |line_no|
position = line_no * (width * 3 + 1)
line_length = header.width * 3
bytes = stream.unpack("@#{position}CC#{line_length}")
filter = bytes.shift
decoded_bytes = decode_scanline(filter, bytes, decoded_bytes, header)
@pixels += decode_pixels(decoded_bytes, header)
end
end

def decode_pixels(bytes, header)
(0...width).map do |i|
PNG::Color.rgb(bytes[i*3+0], bytes[i*3+1], bytes[i*3+2])
end
end

def decode_scanline(filter, bytes, previous_bytes, header = nil)
case filter
when FILTER_NONE then decode_scanline_none( bytes, previous_bytes, header)
when FILTER_SUB then decode_scanline_sub( bytes, previous_bytes, header)
when FILTER_UP then decode_scanline_up( bytes, previous_bytes, header)
when FILTER_AVERAGE then raise "Average filter are not yet supported!"
when FILTER_PAETH then raise "Paeth filter are not yet supported!"
else raise "Unknown filter type"
end
end

def decode_scanline_none(bytes, previous_bytes, header = nil)
bytes
end

def decode_scanline_sub(bytes, previous_bytes, header = nil)
bytes.each_with_index { |b, i| bytes[i] = (b + bytes[i-3]) % 256 }
bytes
end

def decode_scanline_up(bytes, previous_bytes, header = nil)
bytes.each_with_index { |b, i| bytes[i] = (b + previous_bytes[i]) % 256 }
bytes
end

def verify_length!(bytes)
raise "Invalid stream length!" unless bytes == width * height * 3 + height
end

end
end
@@ -0,0 +1,38 @@
module ChunkyPNG

class Reader

attr_reader :io
attr_reader :datastream

def self.io
self.new(io).datastream
end

def self.file(file)
File.open(file, 'r') do |f|
self.new(f).datastream
end
end

def self.string(string)
self.new(StringIO.new(string)).datastream
end

protected

def initialize(io)
@io = io
verify_signature!

@datastream = PNG::Datastream.new
@datastream.chunks << PNG::Chunk.read(@io) until @io.eof?
return @datastream
end

def verify_signature!
signature = @io.read(PNG::Datastream::SIGNATURE.length)
raise "PNG signature not found!" unless signature == PNG::Datastream::SIGNATURE
end
end
end

0 comments on commit aa8a937

Please sign in to comment.
You can’t perform that action at this time.