lib/chunky_png/chunk.rb in chunky_png-0.0.5 vs lib/chunky_png/chunk.rb in chunky_png-0.5.0
- old
+ new
@@ -1,101 +1,179 @@
module ChunkyPNG
- class Chunk
-
+
+ # A PNG datastream consists of multiple chunks. This module, and the classes
+ # contained within, help with handling these chunks. It supports both
+ # reading and writing chunks.
+ #
+ # All chunck types are instances of the {ChunkyPNG::Chunk::Base} class. For
+ # some chunk types a specialized class is available, e.g. the IHDR chunk is
+ # represented by the {ChunkyPNG::Chunk::Header} class. These specialized
+ # classes help accessing the content of the chunk. All other chunks are
+ # represented by the {ChunkyPNG::Chunk::Generic} class.
+ module Chunk
+
+ # Reads a chunk from an IO stream.
+ #
+ # @param [IO, #read] io The IO stream to read from.
+ # @return [ChunkyPNG::Chung::Base] The loaded chunk instance.
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)
-
+
+ verify_crc!(type, content, crc)
+
CHUNK_TYPES.fetch(type, Generic).read(type, content)
end
+ # Verifies the CRC of a chunk.
+ # @param [String] type The chunk's type.
+ # @param [String] content The chunk's content.
+ # @param [Fixnum] content The chunk's content.
+ # @raise [RuntimeError] An exception is raised if the found CRC value
+ # is not equal to the expected CRC value.
+ def self.verify_crc!(type, content, found_crc)
+ expected_crc = Zlib.crc32(content, Zlib.crc32(type))
+ raise "Chuck CRC mismatch!" if found_crc != expected_crc
+ 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)
io << [content.length].pack('N') << type << content
io << [Zlib.crc32(content, Zlib.crc32(type))].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
-
+
attr_accessor :width, :height, :depth, :color, :compression, :filtering, :interlace
-
+
def initialize(attrs = {})
super('IHDR', attrs)
@depth ||= 8
@color ||= ChunkyPNG::COLOR_TRUECOLOR
@compression ||= ChunkyPNG::COMPRESSION_DEFAULT
@filtering ||= ChunkyPNG::FILTERING_DEFAULT
@interlace ||= ChunkyPNG::INTERLACING_NONE
end
-
+
def self.read(type, content)
fields = content.unpack('NNC5')
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
-
+
class End < Base
def initialize
super('IEND')
end
-
+
def self.read(type, content)
raise 'The IEND chunk should be empty!' if content != ''
self.new
end
-
+
def content
''
end
end
class Palette < Generic
end
-
+
class Transparency < Generic
end
class ImageData < Generic
+
+ def self.combine_chunks(data_chunks)
+ Zlib::Inflate.inflate(data_chunks.map(&:content).join(''))
+ end
+
+ def self.split_in_chunks(data, chunk_size = 2147483647)
+ streamdata = Zlib::Deflate.deflate(data)
+ # TODO: Split long streamdata over multiple chunks
+ [ ChunkyPNG::Chunk::ImageData.new('IDAT', streamdata) ]
+ end
end
-
+
+ class Text < Base
+
+ attr_accessor :keyword, :value
+
+ def initialize(keyword, value)
+ super('tEXt')
+ @keyword, @value = keyword, value
+ end
+
+ def self.read(type, content)
+ keyword, value = content.unpack('Z*a*')
+ new(keyword, value)
+ end
+
+ def content
+ [keyword, value].pack('Z*a*')
+ end
+ end
+
+ class CompressedText < Base
+
+ attr_accessor :keyword, :value
+
+ def initialize(keyword, value)
+ super('tEXt')
+ @keyword, @value = keyword, value
+ end
+
+ def self.read(type, content)
+ keyword, compression, value = content.unpack('Z*Ca*')
+ raise "Compression method #{compression.inspect} not supported!" unless compression == ChunkyPNG::COMPRESSION_DEFAULT
+ new(keyword, Zlib::Inflate.inflate(value))
+ end
+
+ def content
+ [keyword, ChunkyPNG::COMPRESSION_DEFAULT, Zlib::Deflate.deflate(value)].pack('Z*Ca*')
+ end
+ end
+
+ class InternationalText < Generic
+ # TODO
+ 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, 'IEND' => End, 'IDAT' => ImageData, 'PLTE' => Palette, 'tRNS' => Transparency
+ 'IHDR' => Header, 'IEND' => End, 'IDAT' => ImageData, 'PLTE' => Palette, 'tRNS' => Transparency,
+ 'tEXt' => Text, 'zTXt' => CompressedText, 'iTXt' => InternationalText
}
-
end
end