lib/chunky_png/datastream.rb in chunky_png-0.0.5 vs lib/chunky_png/datastream.rb in chunky_png-0.5.0

- old
+ new

@@ -1,71 +1,169 @@ module ChunkyPNG - + + # The Datastream class represents a PNG formatted datastream. It supports + # both reading from and writing to strings, stremas and files. + # + # A PNG datastream begins with the PNG signature, and than contains multiple + # chunks, starting with a header (IHDR) chunk and finishing with an end + # (IEND) chunk. + # + # @see ChunkyPNG::Chunk class Datastream - + + # The signature that each PNG file or stream should begin with. SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10].pack('C8') - + + # The header chunk of this datastream. + # @return [ChunkyPNG::Chunk::Header] attr_accessor :header_chunk + + # All other chunks in this PNG file. + # @return [Array<ChunkyPNG::Chunk::Generic>] attr_accessor :other_chunks + + # The chunk containing the image's palette. + # @return [ChunkyPNG::Chunk::Palette] attr_accessor :palette_chunk + + # The chunk containing the transparency information of the palette. + # @return [ChunkyPNG::Chunk::Transparency] attr_accessor :transparency_chunk + + # The chunks that together compose the images pixel data. + # @return [Array<ChunkyPNG::Chunk::ImageData>] attr_accessor :data_chunks + + # The empty chunk that signals the end of this datastream + # @return [ChunkyPNG::Chunk::Header] attr_accessor :end_chunk - def self.read(io) - verify_signature!(io) - ds = self.new - until io.eof? - chunk = ChunkyPNG::Chunk.read(io) - case chunk - when ChunkyPNG::Chunk::Header then ds.header_chunk = chunk - when ChunkyPNG::Chunk::Palette then ds.palette_chunk = chunk - when ChunkyPNG::Chunk::Transparency then ds.transparency_chunk = chunk - when ChunkyPNG::Chunk::ImageData then ds.data_chunks << chunk - when ChunkyPNG::Chunk::End then ds.end_chunk = chunk - else ds.other_chunks << chunk + # Initializes a new Datastream instance. + def initialize + @other_chunks = [] + @data_chunks = [] + end + + ############################################# + # LOADING DATASTREAMS + ############################################# + + class << self + + # Reads a PNG datastream from a string. + # @param [String] str The PNG encoded string to load from. + # @return [ChunkyPNG::Datastream] The loaded datastream instance. + def from_blob(str) + from_io(StringIO.new(str)) + end + + alias :from_string :from_blob + + # Reads a PNG datastream from a file. + # @param [String] filename The path of the file to load from. + # @return [ChunkyPNG::Datastream] The loaded datastream instance. + def from_file(filename) + ds = nil + File.open(filename, 'rb') { |f| ds = from_io(f) } + ds + end + + # Reads a PNG datastream from an input stream + # @param [IO] io The stream to read from. + # @return [ChunkyPNG::Datastream] The loaded datastream instance. + def from_io(io) + verify_signature!(io) + + ds = self.new + until io.eof? + chunk = ChunkyPNG::Chunk.read(io) + case chunk + when ChunkyPNG::Chunk::Header then ds.header_chunk = chunk + when ChunkyPNG::Chunk::Palette then ds.palette_chunk = chunk + when ChunkyPNG::Chunk::Transparency then ds.transparency_chunk = chunk + when ChunkyPNG::Chunk::ImageData then ds.data_chunks << chunk + when ChunkyPNG::Chunk::End then ds.end_chunk = chunk + else ds.other_chunks << chunk + end end + return ds end - return ds + + # Verifies that the current stream is a PNG datastream by checking its signature. + # + # This method reads the PNG signature from the stream, setting the current position + # of the stream directly after the signature, where the IHDR chunk should begin. + # + # @raise [RuntimeError] An exception is raised if the PNG signature is not found at + # the beginning of the stream. + def verify_signature!(io) + signature = io.read(ChunkyPNG::Datastream::SIGNATURE.length) + raise "PNG signature not found!" unless signature == ChunkyPNG::Datastream::SIGNATURE + end end - def self.verify_signature!(io) - signature = io.read(ChunkyPNG::Datastream::SIGNATURE.length) - raise "PNG signature not found!" unless signature == ChunkyPNG::Datastream::SIGNATURE + ############################################# + # CHUNKS + ############################################# + + # Enumerates the chunks in this datastream. + # + # This will iterate over the chunks using the order in which the chunks + # should appear in the PNG file. + # + # @yield [ChunkyPNG::Chunk::Base] The chunks in this datastrean, one by one. + # @see ChunkyPNG::Datastream#chunks + def each_chunk + yield(header_chunk) + other_chunks.each { |chunk| yield(chunk) } + yield(palette_chunk) if palette_chunk + yield(transparency_chunk) if transparency_chunk + data_chunks.each { |chunk| yield(chunk) } + yield(end_chunk) end - + + # Returns an enumerator instance for this datastream's chunks. + # @return [Enumerable::Enumerator] An enumerator for the :each_chunk method. + # @see ChunkyPNG::Datastream#each_chunk def chunks - cs = [header_chunk] - cs += other_chunks - cs << palette_chunk if palette_chunk - cs << transparency_chunk if transparency_chunk - cs += data_chunks - cs << end_chunk - return cs + enum_for(:each_chunk) end - def initialize - @other_chunks = [] - @data_chunks = [] + # Returns all the textual metadata key/value pairs as hash. + def metadata + metadata = {} + other_chunks.select do |chunk| + metadata[chunk.keyword] = chunk.value if chunk.respond_to?(:keyword) + end + metadata end - + + ############################################# + # WRITING DATASTREAMS + ############################################# + + # Writes the datastream to the given output stream. + # @param [IO] io The output stream to write to. def write(io) io << SIGNATURE - chunks.each { |c| c.write(io) } + each_chunk { |c| c.write(io) } end - - def idat_chunks(data) - streamdata = Zlib::Deflate.deflate(data) - # TODO: Split long streamdata over multiple chunks - return [ ChunkyPNG::Chunk::ImageData.new('IDAT', streamdata) ] + + # Saves this datastream as a PNG file. + # @param [String] filename The filename to use. + def save(filename) + File.open(filename, 'wb') { |f| write(f) } end - - def pixel_matrix=(pixel_matrix) - @pixel_matrix = pixel_matrix + + # Encodes this datastream into a string. + # @return [String] The encoded PNG datastream. + def to_blob + str = StringIO.new + write(str) + return str.string end - - def pixel_matrix - @pixel_matrix ||= ChunkyPNG::PixelMatrix.decode(self) - end + + alias :to_string :to_blob + alias :to_s :to_blob end end