chunky_png-1.3.15/0000755000175000017500000000000013766004353013227 5ustar danieldanielchunky_png-1.3.15/Rakefile0000644000175000017500000000043013766004353014671 0ustar danieldaniel# frozen-string-literal: true require "bundler/gem_tasks" require "rspec/core/rake_task" Dir["tasks/*.rake"].each { |file| load(file) } RSpec::Core::RakeTask.new(:spec) do |task| task.pattern = "./spec/**/*_spec.rb" task.rspec_opts = ["--color"] end task default: [:spec] chunky_png-1.3.15/chunky_png.gemspec0000644000175000017500000000443513766004353016747 0ustar danieldaniel# frozen-string-literal: true lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "chunky_png/version" Gem::Specification.new do |s| s.name = "chunky_png" # Do not change the version and date fields by hand. This will be done # automatically by the gem release script. s.version = ChunkyPNG::VERSION s.summary = "Pure ruby library for read/write, chunk-level access to PNG files" s.description = <<-EOT This pure Ruby library can read and write PNG images without depending on an external image library, like RMagick. It tries to be memory efficient and reasonably fast. It supports reading and writing all PNG variants that are defined in the specification, with one limitation: only 8-bit color depth is supported. It supports all transparency, interlacing and filtering options the PNG specifications allows. It can also read and write textual metadata from PNG files. Low-level read/write access to PNG chunks is also possible. This library supports simple drawing on the image canvas and simple operations like alpha composition and cropping. Finally, it can import from and export to RMagick for interoperability. Also, have a look at OilyPNG at https://github.com/wvanbergen/oily_png. OilyPNG is a drop in mixin module that implements some of the ChunkyPNG algorithms in C, which provides a massive speed boost to encoding and decoding. EOT s.authors = ["Willem van Bergen"] s.email = ["willem@railsdoctors.com"] s.homepage = "https://github.com/wvanbergen/chunky_png/wiki" s.license = "MIT" s.metadata = { "source_code_uri" => "https://github.com/wvanbergen/chunky_png", "wiki_uri" => "https://github.com/wvanbergen/chunky_png/wiki", } s.add_development_dependency("rake") s.add_development_dependency("standard") s.add_development_dependency("yard", "~> 0.9") s.add_development_dependency("rspec", "~> 3") s.rdoc_options << "--title" << s.name << "--main" << "README.rdoc" << "--line-numbers" << "--inline-source" s.extra_rdoc_files = ["README.md", "BENCHMARKING.rdoc", "CONTRIBUTING.rdoc", "CHANGELOG.rdoc"] s.files = `git ls-files`.split($/) s.test_files = s.files.grep(%r{^(test|spec|features)/}) s.required_ruby_version = ">= 2.0.0" end chunky_png-1.3.15/CHANGELOG.rdoc0000644000175000017500000002345713766004353015402 0ustar danieldaniel= Changelog The file documents the changes to this library over the different versions. - ChunkyPNG uses semantic versioning. This means that the public API will not change except for major versions. - Please add an entry to the "Unreleased changes" section in your pull requests, so I can move them into a numbered version section on release. === Unreleased changes - Implemented ChunkyPNG::Dimension#hash to fix some specs after a behavior change in RSpec. === 1.3.11 - 2018-11-21 - Updated project metadata as published on Rubygems.org === 1.3.10 - 2018-01-23 - Fixed a regression in Datastream#metadata, which was not able to deal with iTXt chunks. === 1.3.9 - 2018-01-23 - Add support for reading and writing an international textual data (iTXt chunks). === 1.3.8 - 2016-08-31 - Add support for reading and writing an image's physical dimension (pHYs chunks). === 1.3.7 - 2016-08-31 - Performance improvement for Color.euclidean_distance_rgba. - Bugfix in decoding transparent pixels when decoding multiple images in a row. === 1.3.6 - 2016-06-19 - Allow reading images from streams that have trailing data after the IEND chunk. - Add compatibility for Ruby 2.3's frozen string literals. - Documentation updates and small cleanups. === 1.3.5 - 2015-10-28 - Performance improvements for Canvas#crop! and ImageData.combine_chunks - Update chunky_png/rmagick to work with the latest versions of RMagick. - Bugfix in Color#from_hsl and Color#from_hsv when hue value is 360. - Fix encoding issue in Datastream#to_blob === 1.3.4 - 2015-02-16 - Assert compatibility with Ruby 2.2 - Improved documentation using RDoc, so it is included on https://www.rubydoc.info/gems/chunky_png - Update chunkypng.com website; migrate some stuff from the wiki. === 1.3.3 - 2014-10-24 - Improve performance of Canvas#crop and Canvas#crop! by doing less memory allocations. - Update to RSPEC 3 - Add CONTRIBUTING.rdoc file. === 1.3.2 - 2014-10-18 - Add HSV/HSL color conversions: Color.from_hsl, Color.to_hsl - Allow empty IDAT chunks to better conform to the PNG standard. - Small bugfix in image resampling. - Documentation and code readability improvements. === 1.3.1 - 2014-04-28 - Improve performance of Palette.from_canvas. - Add Color.euclidean_distance_rgba to compare colors. - Bugix in Canvas.from_bgr_stream. - Documentation and code readibility improvements. - README updates, include mention of screencast. === 1.3.0 - 2014-02-10 - Add support for parsing color that use three-hex notation. (e.g. #aaa instead of #aaaaaa) - Add Canvas#border! and Canvas#border to draw a border around a canvas. - Add Canvas#trim! and Canvas#trim to trim a border from a canvas. === 1.2.9 - 2013-10-17 - Set license in chunky_png.gemspec for better discoverability. - Improve error messages for Canvas#crop. - Use better gem release management tasks from bundler. === 1.2.8 - 2013-03-30 - Ruby 2.0 compatibility. - Fixed some encoding issues on JRuby. - Update Travis CI configuration to test on more Ruby versions. === 1.2.7 - 2013-01-07 - Small PNG decoding performnace improvements by using bitwise math. === 1.2.6 - 2012-08-07 - Add decompression bomb security warning to README. - Fix RMagick loading issue on case sensitive filesystems. - Some compatibility fixes for the upcoming Ruby 2.0. - Allow more data-url notations for ChunkyPNG::Canvas.from_data_url. === 1.2.5 - 2011-09-23 - Edge case bugfix in Color.decompose_alpha_component that could get triggered in the change_theme_color! method. === 1.2.4 - 2011-09-14 - Added data URL importing Canvas.from_data_url. === 1.2.3 - 2011-09-14 - Added data URL exporting Canvas#to_data_url to easily use PNGs inline in CSS or HTML. === 1.2.2 - 2011-09-14 - Workaround for performance bug in REE. === 1.2.1 - 2011-08-10 - Added bicubic resampling of images. - Update resampling code to use integer math instead of floating points. === 1.2.0 - 2011-05-08 - Properly read PNG files with a tRNS chunk in color mode 0 (grayscale) or 2 (true color). === 1.1.2 - 2011-05-06 - Added Color.to_grayscale and Canvas#grayscale! to convert colors and canvases to grayscale. - Memory footprint improvement of Canvas#resample! === 1.1.1 - 2011-04-22 - Added Canvas#to_alpha_channel_bytes and Canvas#to_grayscale_stream to export raw pixel data. - Spec suite cleanup === 1.1.0 - 2011-03-19 - Add bezier curve drawing: Canvas#bezier_curve. - RDoc fixes & improvements. === 1.0.1 - 2011-03-08 - Performance improvements. === 1.0.0 - 2011-03-06 There are some API changes for this release. If you are using Canvas#compose or Canvas#replace, these methods will no longer operate in place, but will return a new canvas instance instead. The in place versions have been renamed to compose! and replace! to be more consistent with the rest of the API. - Added image resampling using the nearest neighbor algorithm: Canvas#resample. - Added circle and polygon drawing methods: Canvas#circle and Canvas#polygon. - Added in place version of Canvas#crop, Canvas#rotate_180, Canvas#flip_horizontally and Canvas#flip_vertically. Just add a bang to the method name (e.g. Canvas#crop!) and it will change the current canvas instead of returning a new one. These implementations are also more memory and CPU efficient. - Added geometry helper classes: ChunkyPNG::Point, ChunkyPNG::Dimension and ChunkyPNG::Vector. - Added a list of HTML named colors. Get them by calling ChunkyPNG::Color(:teal) or ChunkyPNG::Color('red @ 0.8') - Added encoding support for 1-, 2-, and 4-bit grayscale images. - Cleaned up auto-detection of color mode settings. It will now choose 1 bit grayscale mode if an image only contains black and white. (The other low bitrate grayscale modes are never chosen automatically.) - RDoc improvements. See https://rdoc.info/gems/chunky_png. - ChunkyPNG is now also tested on Ruby 1.8.6. === 0.12.0 - 2010-12-12 - Added support for encoding indexed images with a low bitrate. It will automatically use less bits per pixel if possible. - Improved testing setup. ChunkyPNG is now tested on Ruby 1.8.7, 1.9.2, JRuby and Rubinius. === 0.11.0 - 2010-11-16 - Decoding of 1, 2 and 4 bit indexed color images. - Decoding of 1, 2 and 4 bit grayscale images. - Decoding 16 bit images. The extra bits will be discarded, so the image will be loaded as 8 bit. - Used the official PNG suite to build a more complete test suite. === 0.10.5 - 2010-10-21 - Bugfix: allow 256 instead of 255 colors for indexed images. === 0.10.4 - 2010-10-17 - Improved handling of binary encoding for strings in Ruby 1.9. === 0.10.3 - 2010-10-07 - Small fix to make grayscale use the B byte consistently. === 0.10.2 - 2010-10-04 - Another small fix for OilyPNG compatibility === 0.10.1 - 2010-10-03 - Small fix for OilyPNG compatibility === 0.10.0 - 2010-10-03 - Refactored decoding and encoding to work on binary strings instead of arrays of integers. This gives a nice speedup and uses less memory. Thanks to Yehuda Katz for the idea. === 0.9.2 - 2010-09-16 - Fixed an issue with interlaced images. === 0.9.1 - 2010-09-15 - Fixed image metadata issue when duplicating images. === 0.9.0 - 2010-08-18 - Added flip_horizontally, flip_vertically, rotate_left, rotate_right and rotate_180 to ChunkyPNG::Canvas. - Now raises ChunkyPNG::OutOfBounds exceptions when referencing coordinates outside the image bounds. - Added Gemfile for development dependency management. === 0.8.0 - 2010-06-30 - Added ChunkyPNG::Image#rect to draw simple rectangles. - Fixed composing a transparent color on a fully transparent background. === 0.7.3 - 2010-04-28 - Based on the suggestion of [Dirkjan Bussink](https://github.com/dbussink), introduced custom exception classes: - ChunkyPNG::SignatureMismatch is raised when the PNG signature could not be found. Usually this means the the file is not a PNG image. - ChunkyPNG::CRCMismatch is raised when the a CRC check for a chunk in the PNG file fails. - ChunkyPNG::NotSupported is raised when the PNG image uses a feature that ChunkyPNG does not support. - ChunkyPNG::ExpectationFailed is raised when a required expectation failed. === 0.7.2 - 2010-04-28 [YANKED] === 0.7.1 - 2010-03-23 - Some fixes for 32-bit systems. === 0.7.0 - 2010-03-15 - Added :best_compression saving routine to allow creating the smallest images possible. - Added option to control Zlib compression level while saving. === 0.6.0 - 2010-02-25 - Added methods to easily create different color variants of an image with a color theme. See [[Images with a color theme]] for more information. === 0.5.8 - 2010-02-24 - Ruby 1.8.6 compatibility fixes - Improved API documentation. === 0.5.5 - 2010-02-15 - Added alpha decomposition to extract a color mask from a themed image. - Improved API documentation. === 0.5.4 - 2010-01-17 - Added point and line anti-aliased drawing functions. === 0.5.3 - 2010-01-16 - Removed last occurrences of floating math to speed up the library. - Added importing of ABGR and BGR streams. - Added exporting an image as ABGR stream. === 0.5.2 - 2010-01-15 - Ruby 1.9 compatibility fixes. - Improved speed of PNG decoding. - Bugfix in *average* scanline decoding filter. === 0.5.1 - 2010-01-15 - Added :fast_rgba and :fast_rgb saving routines, which yield a 1500% speedup when saving an image. === 0.5.0 - 2010-01-15 - Complete rewrite of the earlier versions, now including awesomeness and unicorns. chunky_png-1.3.15/BENCHMARKING.rdoc0000644000175000017500000000330513766004353015731 0ustar danieldaniel= ChunkyPNG benchmark suite I would like the performance of this library as good as possible, and I will gladly accept changes to this library that improves performance. The library comes with a basic benchmark suite is intended to test the speed of PNG decoding and encoding against different ruby interpreters. Execute them using rake. You can set the number of runs by passing the N environment variable. bundle exec rake benchmark:encoding bundle exec rake benchmark:decoding bundle exec rake benchmark N=10 # Run all of them with 10 iterations You can use rvm to run the benchmarks against different interpreters. Of course, make sure that the chunky_png is installed for all your interpreters. rvm 1.8.7,1.9.3,rbx bundle exec rake benchmark N=10 == Results What is a speed improvement on one interpreter doesn't necessarily mean the performance will be better on other interpreters as well. Please make sure to benchamrk different RUby interpreters. When it comes to different Ruby interpreters, the priority is the performance on recent MRI versions. Some very old benchmark result (using N=50) on my 2007 iMac can be found at https://gist.github.com/wvanbergen/495323. == Why is this relevant? ChunkyPNG is a pure Ruby library to handle PNG files. Decoding a PNG requires a lot of integer math and bitwise operations, and moderate use of the unpack method to read binary data. Encoding is a good test for +Array#pack+, and depending on the encoding options, also requires a lot of calculations. Therefore, the library is a good benchmark candidate for these methods and algorithms. It has been used to improve the Array#pack and String#unpack methods in Rubinius. chunky_png-1.3.15/lib/0000755000175000017500000000000013766004353013775 5ustar danieldanielchunky_png-1.3.15/lib/chunky_png/0000755000175000017500000000000013766004353016142 5ustar danieldanielchunky_png-1.3.15/lib/chunky_png/dimension.rb0000644000175000017500000001143513766004353020460 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG # Creates a {ChunkyPNG::Dimension} instance using arguments that can be interpreted # as width and height. # # @overload Dimension(width, height) # @param [Integer] width The width-component of the dimension. # @param [Integer] height The height-component of the dimension. # @return [ChunkyPNG::Dimension] The instantiated dimension. # # @overload Dimension(string) # @param [String] string A string from which a width and height value can be parsed, e.g. # '10x20' or '[10, 20]'. # @return [ChunkyPNG::Dimension] The instantiated dimension. # # @overload Dimension(ary) # @param [Array] ary An array with the desired width as first element and the # desired height as second element, e.g. [10, 20]. # @return [ChunkyPNG::Dimension] The instantiated dimension. # # @overload Dimension(hash) # @param [Hash] hash An hash with a 'height' or :height key for the # desired height and with a 'width' or :width key for the desired # width. # @return [ChunkyPNG::Dimension] The instantiated dimension. # # @return [ChunkyPNG::Dimension] The dimension created by this factory method. # @raise [ArgumentError] If the argument(s) given where not understood as a dimension. # @see ChunkyPNG::Dimension def self.Dimension(*args) case args.length when 2 then ChunkyPNG::Dimension.new(*args) when 1 then build_dimension_from_object(args.first) else raise ArgumentError, "Don't know how to construct a dimension from #{args.inspect}" end end def self.build_dimension_from_object(source) case source when ChunkyPNG::Dimension source when ChunkyPNG::Point ChunkyPNG::Dimension.new(source.x, source.y) when Array ChunkyPNG::Dimension.new(source[0], source[1]) when Hash width = source[:width] || source["width"] height = source[:height] || source["height"] ChunkyPNG::Dimension.new(width, height) when ChunkyPNG::Dimension::DIMENSION_REGEXP ChunkyPNG::Dimension.new($1, $2) else if source.respond_to?(:width) && source.respond_to?(:height) ChunkyPNG::Dimension.new(source.width, source.height) else raise ArgumentError, "Don't know how to construct a dimension from #{source.inspect}!" end end end private_class_method :build_dimension_from_object # Class that represents the dimension of something, e.g. a {ChunkyPNG::Canvas}. # # This class contains some methods to simplify performing dimension related checks. class Dimension # @return [Regexp] The regexp to parse dimensions from a string. # @private DIMENSION_REGEXP = /^[\(\[\{]?(\d+)\s*[x,]?\s*(\d+)[\)\]\}]?$/ # @return [Integer] The width-component of this dimension. attr_accessor :width # @return [Integer] The height-component of this dimension. attr_accessor :height # Initializes a new dimension instance. # @param [Integer] width The width-component of the new dimension. # @param [Integer] height The height-component of the new dimension. def initialize(width, height) @width, @height = width.to_i, height.to_i end # Returns the area of this dimension. # @return [Integer] The area in number of pixels. def area width * height end # Checks whether a point is within bounds of this dimension. # @param [ChunkyPNG::Point, ...] point_like A point-like to bounds-check. # @return [true, false] True iff the x and y coordinate fall in this dimension. # @see ChunkyPNG.Point def include?(*point_like) point = ChunkyPNG::Point(*point_like) point.x >= 0 && point.x < width && point.y >= 0 && point.y < height end # Checks whether 2 dimensions are identical. # @param [ChunkyPNG::Dimension] other The dimension to compare with. # @return [true, false] true iff width and height match. def eql?(other) return false unless other.respond_to?(:width) && other.respond_to?(:height) other.width == width && other.height == height end alias == eql? # Calculates a hash for the dimension object, based on width and height # @return [Integer] A hashed value of the dimensions def hash [width, height].hash end # Compares the size of 2 dimensions. # @param [ChunkyPNG::Dimension] other The dimension to compare with. # @return [-1, 0, 1] -1 if the other dimension has a larger area, 1 of this # dimension is larger, 0 if both are identical in size. def <=>(other) other.area <=> area end # Casts this dimension into an array. # @return [Array] [width, height] for this dimension. def to_a [width, height] end alias to_ary to_a end end chunky_png-1.3.15/lib/chunky_png/datastream.rb0000644000175000017500000001524213766004353020620 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG # The Datastream class represents a PNG formatted datastream. It supports # both reading from and writing to strings, streams and files. # # A PNG datastream begins with the PNG signature, and then 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").force_encoding(::Encoding::BINARY).freeze # The header chunk of this datastream. # @return [ChunkyPNG::Chunk::Header] attr_accessor :header_chunk # All other chunks in this PNG file. # @return [Array] 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 chunk containing the physical dimensions of the PNG's pixels. # @return [ChunkyPNG::Chunk::Physical] attr_accessor :physical_chunk # The chunks that together compose the images pixel data. # @return [Array] attr_accessor :data_chunks # The empty chunk that signals the end of this datastream # @return [ChunkyPNG::Chunk::Header] attr_accessor :end_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, "rb")) 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) io.set_encoding(::Encoding::BINARY) verify_signature!(io) ds = new while ds.end_chunk.nil? 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::Physical then ds.physical_chunk = chunk when ChunkyPNG::Chunk::End then ds.end_chunk = chunk else ds.other_chunks << chunk end end ds end # 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. # # @param [IO] io The stream to read the PNG signature from. # @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) unless signature == ChunkyPNG::Datastream::SIGNATURE raise ChunkyPNG::SignatureMismatch, "PNG signature not found, found #{signature.inspect} instead of #{ChunkyPNG::Datastream::SIGNATURE.inspect}!" end end end ################################################################################## # 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 [chunk] Yields the chunks in this datastream, one by one in the correct order. # @yieldparam [ChunkyPNG::Chunk::Base] chunk A chunk in this datastream. # @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 yield(physical_chunk) if physical_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 enum_for(:each_chunk) end # Returns all the textual metadata key/value pairs as hash. # @return [Hash] A hash containing metadata fields and their values. def metadata metadata = {} other_chunks.each do |chunk| metadata[chunk.keyword] = chunk.value if chunk.respond_to?(:keyword) && chunk.respond_to?(:value) end metadata end # Returns the uncompressed image data, combined from all the IDAT chunks # @return [String] The uncompressed image data for this datastream def imagedata ChunkyPNG::Chunk::ImageData.combine_chunks(data_chunks) 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 each_chunk { |c| c.write(io) } end # 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 # Encodes this datastream into a string. # @return [String] The encoded PNG datastream. def to_blob str = StringIO.new str.set_encoding("ASCII-8BIT") write(str) str.string end alias to_string to_blob alias to_s to_blob end end chunky_png-1.3.15/lib/chunky_png/color.rb0000644000175000017500000011332113766004353017606 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG # Factory method to return a color value, based on the arguments given. # # @overload Color(r, g, b, a) # @param (see ChunkyPNG::Color.rgba) # @return [Integer] The rgba color value. # # @overload Color(r, g, b) # @param (see ChunkyPNG::Color.rgb) # @return [Integer] The rgb color value. # # @overload Color(hex_value, opacity = nil) # @param (see ChunkyPNG::Color.from_hex) # @return [Integer] The hex color value, with the opacity applied if one # was given. # # @overload Color(color_name, opacity = nil) # @param (see ChunkyPNG::Color.html_color) # @return [Integer] The hex color value, with the opacity applied if one # was given. # # @overload Color(color_value, opacity = nil) # @param [Integer, :to_i] The color value. # @return [Integer] The color value, with the opacity applied if one was # given. # # @return [Integer] The determined color value as RGBA integer. # @raise [ArgumentError] if the arguments weren't understood as a color. # @see ChunkyPNG::Color # @see ChunkyPNG::Color.parse def self.Color(*args) # rubocop:disable Naming/MethodName # API backwards compatibility case args.length when 1 then ChunkyPNG::Color.parse(args.first) when 2 then (ChunkyPNG::Color.parse(args.first) & 0xffffff00) | args[1].to_i when 3 then ChunkyPNG::Color.rgb(*args) when 4 then ChunkyPNG::Color.rgba(*args) else raise ArgumentError, "Don't know how to create a color from #{args.inspect}!" end end # rubocop:enable Naming/MethodName # The Color module defines methods for handling colors. Within the ChunkyPNG # library, the concepts of pixels and colors are both used, and they are # both represented by a Integer. # # Pixels/colors are represented in RGBA components. Each of the four # components is stored with a depth of 8 bits (maximum value = 255 = # {ChunkyPNG::Color::MAX}). Together, these components are stored in a 4-byte # Integer. # # A color will always be represented using these 4 components in memory. # When the image is encoded, a more suitable representation can be used # (e.g. rgb, grayscale, palette-based), for which several conversion methods # are provided in this module. module Color extend self # @return [Integer] The maximum value of each color component. MAX = 0xff # @private # @return [Regexp] The regexp to parse 3-digit hex color values. HEX3_COLOR_REGEXP = /\A(?:#|0x)?([0-9a-f]{3})\z/i # @private # @return [Regexp] The regexp to parse 6- and 8-digit hex color values. HEX6_COLOR_REGEXP = /\A(?:#|0x)?([0-9a-f]{6})([0-9a-f]{2})?\z/i # @private # @return [Regexp] The regexp to parse named color values. HTML_COLOR_REGEXP = /^([a-z][a-z_ ]+[a-z])(?:\ ?\@\ ?(1\.0|0\.\d+))?$/i #################################################################### # CONSTRUCTING COLOR VALUES #################################################################### # Parses a color value given a numeric or string argument. # # It supports color numbers, colors in hex notation and named HTML colors. # # @param [Integer, String] source The color value. # @return [Integer] The color value, with the opacity applied if one was # given. def parse(source) return source if source.is_a?(Integer) case source.to_s when /^\d+$/ then source.to_s.to_i when HEX3_COLOR_REGEXP, HEX6_COLOR_REGEXP then from_hex(source.to_s) when HTML_COLOR_REGEXP then html_color(source.to_s) else raise ArgumentError, "Don't know how to create a color from #{source.inspect}!" end end # Creates a new color using an r, g, b triple and an alpha value. # @param [Integer] r The r-component (0-255) # @param [Integer] g The g-component (0-255) # @param [Integer] b The b-component (0-255) # @param [Integer] a The opacity (0-255) # @return [Integer] The newly constructed color value. def rgba(r, g, b, a) r << 24 | g << 16 | b << 8 | a end # Creates a new color using an r, g, b triple. # @param [Integer] r The r-component (0-255) # @param [Integer] g The g-component (0-255) # @param [Integer] b The b-component (0-255) # @return [Integer] The newly constructed color value. def rgb(r, g, b) r << 24 | g << 16 | b << 8 | 0xff end # Creates a new color using a grayscale teint. # @param [Integer] teint The grayscale teint (0-255), will be used as r, g, # and b value. # @return [Integer] The newly constructed color value. def grayscale(teint) teint << 24 | teint << 16 | teint << 8 | 0xff end # Creates a new color using a grayscale teint and alpha value. # @param [Integer] teint The grayscale teint (0-255), will be used as r, g, # and b value. # @param [Integer] a The opacity (0-255) # @return [Integer] The newly constructed color value. def grayscale_alpha(teint, a) teint << 24 | teint << 16 | teint << 8 | a end #################################################################### # COLOR IMPORTING #################################################################### # Creates a color by unpacking an rgb triple from a string. # # @param [String] stream The string to load the color from. It should be # at least 3 + pos bytes long. # @param [Integer] pos The position in the string to load the triple from. # @return [Integer] The newly constructed color value. def from_rgb_stream(stream, pos = 0) rgb(*stream.unpack("@#{pos}C3")) end # Creates a color by unpacking an rgba triple from a string # # @param [String] stream The string to load the color from. It should be # at least 4 + pos bytes long. # @param [Integer] pos The position in the string to load the triple from. # @return [Integer] The newly constructed color value. def from_rgba_stream(stream, pos = 0) rgba(*stream.unpack("@#{pos}C4")) end # Creates a color by converting it from a string in hex notation. # # It supports colors with (#rrggbbaa) or without (#rrggbb) alpha channel # as well as the 3-digit short format (#rgb) for those without. # Color strings may include the prefix "0x" or "#". # # @param [String] hex_value The color in hex notation. # @param [Integer] opacity The opacity value for the color. Overrides any # opacity value given in the hex value if given. # @return [Integer] The color value. # @raise [ArgumentError] if the value given is not a hex color notation. def from_hex(hex_value, opacity = nil) base_color = case hex_value when HEX3_COLOR_REGEXP $1.gsub(/([0-9a-f])/i, '\1\1').hex << 8 when HEX6_COLOR_REGEXP $1.hex << 8 else raise ArgumentError, "Not a valid hex color notation: #{hex_value.inspect}!" end opacity ||= $2 ? $2.hex : 0xff base_color | opacity end # Creates a new color from an HSV triple. # # Create a new color using an HSV (sometimes also called HSB) triple. The # words `value` and `brightness` are used interchangeably and synonymously # in descriptions of this colorspace. This implementation follows the modern # convention of 0 degrees hue indicating red. # # @param [Fixnum] hue The hue component (0-360) # @param [Fixnum] saturation The saturation component (0-1) # @param [Fixnum] value The value (brightness) component (0-1) # @param [Fixnum] alpha Defaults to opaque (255). # @return [Integer] The newly constructed color value. # @raise [ArgumentError] if the hsv triple is invalid. # @see https://en.wikipedia.org/wiki/HSL_and_HSV def from_hsv(hue, saturation, value, alpha = 255) raise ArgumentError, "Hue must be between 0 and 360" unless (0..360).cover?(hue) raise ArgumentError, "Saturation must be between 0 and 1" unless (0..1).cover?(saturation) raise ArgumentError, "Value/brightness must be between 0 and 1" unless (0..1).cover?(value) chroma = value * saturation rgb = cylindrical_to_cubic(hue, saturation, value, chroma) rgb.map! { |component| ((component + value - chroma) * 255).to_i } rgb << alpha rgba(*rgb) end alias from_hsb from_hsv # Creates a new color from an HSL triple. # # This implementation follows the modern convention of 0 degrees hue # indicating red. # # @param [Fixnum] hue The hue component (0-360) # @param [Fixnum] saturation The saturation component (0-1) # @param [Fixnum] lightness The lightness component (0-1) # @param [Fixnum] alpha Defaults to opaque (255). # @return [Integer] The newly constructed color value. # @raise [ArgumentError] if the hsl triple is invalid. # @see https://en.wikipedia.org/wiki/HSL_and_HSV def from_hsl(hue, saturation, lightness, alpha = 255) raise ArgumentError, "Hue #{hue} was not between 0 and 360" unless (0..360).cover?(hue) raise ArgumentError, "Saturation #{saturation} was not between 0 and 1" unless (0..1).cover?(saturation) raise ArgumentError, "Lightness #{lightness} was not between 0 and 1" unless (0..1).cover?(lightness) chroma = (1 - (2 * lightness - 1).abs) * saturation rgb = cylindrical_to_cubic(hue, saturation, lightness, chroma) rgb.map! { |component| ((component + lightness - 0.5 * chroma) * 255).to_i } rgb << alpha rgba(*rgb) end # Convert one HSL or HSV triple and associated chroma to a scaled rgb triple # # This method encapsulates the shared mathematical operations needed to # convert coordinates from a cylindrical colorspace such as HSL or HSV into # coordinates of the RGB colorspace. # # Even though chroma values are derived from the other three coordinates, # the formula for calculating chroma differs for each colorspace. Since it # is calculated differently for each colorspace, it must be passed in as # a parameter. # # @param [Fixnum] hue The hue-component (0-360) # @param [Fixnum] saturation The saturation-component (0-1) # @param [Fixnum] y_component The y_component can represent either lightness # or brightness/value (0-1) depending on which scheme (HSV/HSL) is being used. # @param [Fixnum] chroma The associated chroma value. # @return [Array] A scaled r,g,b triple. Scheme-dependent # adjustments are still needed to reach the true r,g,b values. # @see https://en.wikipedia.org/wiki/HSL_and_HSV # @private def cylindrical_to_cubic(hue, saturation, y_component, chroma) hue_prime = hue.fdiv(60) x = chroma * (1 - (hue_prime % 2 - 1).abs) case hue_prime when (0...1) then [chroma, x, 0] when (1...2) then [x, chroma, 0] when (2...3) then [0, chroma, x] when (3...4) then [0, x, chroma] when (4...5) then [x, 0, chroma] when (5..6) then [chroma, 0, x] end end private :cylindrical_to_cubic #################################################################### # PROPERTIES #################################################################### # Returns the red-component from the color value. # # @param [Integer] value The color value. # @return [Integer] A value between 0 and MAX. def r(value) (value & 0xff000000) >> 24 end # Returns the green-component from the color value. # # @param [Integer] value The color value. # @return [Integer] A value between 0 and MAX. def g(value) (value & 0x00ff0000) >> 16 end # Returns the blue-component from the color value. # # @param [Integer] value The color value. # @return [Integer] A value between 0 and MAX. def b(value) (value & 0x0000ff00) >> 8 end # Returns the alpha channel value for the color value. # # @param [Integer] value The color value. # @return [Integer] A value between 0 and MAX. def a(value) value & 0x000000ff end # Returns true if this color is fully opaque. # # @param [Integer] value The color to test. # @return [true, false] True if the alpha channel equals MAX. def opaque?(value) a(value) == 0x000000ff end # Returns the opaque value of this color by removing the alpha channel. # @param [Integer] value The color to transform. # @return [Integer] The opaque color def opaque!(value) value | 0x000000ff end # Returns true if this color is fully transparent. # # @param [Integer] value The color to test. # @return [true, false] True if the r, g and b component are equal. def grayscale?(value) r(value) == b(value) && b(value) == g(value) end # Returns true if this color is fully transparent. # # @param [Integer] value The color to test. # @return [true, false] True if the alpha channel equals 0. def fully_transparent?(value) a(value) == 0x00000000 end #################################################################### # ALPHA COMPOSITION #################################################################### # Multiplies two fractions using integer math, where the fractions are # stored using an integer between 0 and 255. This method is used as a # helper method for compositing colors using integer math. # # This is a quicker implementation of ((a * b) / 255.0).round. # # @param [Integer] a The first fraction. # @param [Integer] b The second fraction. # @return [Integer] The result of the multiplication. def int8_mult(a, b) t = a * b + 0x80 ((t >> 8) + t) >> 8 end # Composes two colors with an alpha channel using integer math. # # This version is faster than the version based on floating point math, so # this compositing function is used by default. # # @param [Integer] fg The foreground color. # @param [Integer] bg The background color. # @return [Integer] The composited color. # @see ChunkyPNG::Color#compose_precise def compose_quick(fg, bg) return fg if opaque?(fg) || fully_transparent?(bg) return bg if fully_transparent?(fg) a_com = int8_mult(0xff - a(fg), a(bg)) new_r = int8_mult(a(fg), r(fg)) + int8_mult(a_com, r(bg)) new_g = int8_mult(a(fg), g(fg)) + int8_mult(a_com, g(bg)) new_b = int8_mult(a(fg), b(fg)) + int8_mult(a_com, b(bg)) new_a = a(fg) + a_com rgba(new_r, new_g, new_b, new_a) end # Composes two colors with an alpha channel using floating point math. # # This method uses more precise floating point math, but this precision is # lost when the result is converted back to an integer. Because it is # slower than the version based on integer math, that version is preferred. # # @param [Integer] fg The foreground color. # @param [Integer] bg The background color. # @return [Integer] The composited color. # @see ChunkyPNG::Color#compose_quick def compose_precise(fg, bg) return fg if opaque?(fg) || fully_transparent?(bg) return bg if fully_transparent?(fg) fg_a = a(fg).to_f / MAX bg_a = a(bg).to_f / MAX a_com = (1.0 - fg_a) * bg_a new_r = (fg_a * r(fg) + a_com * r(bg)).round new_g = (fg_a * g(fg) + a_com * g(bg)).round new_b = (fg_a * b(fg) + a_com * b(bg)).round new_a = ((fg_a + a_com) * MAX).round rgba(new_r, new_g, new_b, new_a) end alias compose compose_quick # Blends the foreground and background color by taking the average of # the components. # # @param [Integer] fg The foreground color. # @param [Integer] bg The foreground color. # @return [Integer] The blended color. def blend(fg, bg) (fg + bg) >> 1 end # Interpolates the foreground and background colors by the given alpha # value. This also blends the alpha channels themselves. # # A blending factor of 255 will give entirely the foreground, # while a blending factor of 0 will give the background. # # @param [Integer] fg The foreground color. # @param [Integer] bg The background color. # @param [Integer] alpha The blending factor (fixed 8bit) # @return [Integer] The interpolated color. def interpolate_quick(fg, bg, alpha) return fg if alpha >= 255 return bg if alpha <= 0 alpha_com = 255 - alpha new_r = int8_mult(alpha, r(fg)) + int8_mult(alpha_com, r(bg)) new_g = int8_mult(alpha, g(fg)) + int8_mult(alpha_com, g(bg)) new_b = int8_mult(alpha, b(fg)) + int8_mult(alpha_com, b(bg)) new_a = int8_mult(alpha, a(fg)) + int8_mult(alpha_com, a(bg)) rgba(new_r, new_g, new_b, new_a) end # Calculates the grayscale teint of an RGB color. # # @param [Integer] color The color to convert. # @return [Integer] The grayscale teint of the input color, 0-255. def grayscale_teint(color) (r(color) * 0.3 + g(color) * 0.59 + b(color) * 0.11).round end # Converts a color to a fiting grayscale value. It will conserve the alpha # channel. # # This method will return a full color value, with the R, G, and B value # set to the grayscale teint calcuated from the input color's R, G and B # values. # # @param [Integer] color The color to convert. # @return [Integer] The input color, converted to the best fitting # grayscale. # @see #grayscale_teint def to_grayscale(color) grayscale_alpha(grayscale_teint(color), a(color)) end # Lowers the intensity of a color, by lowering its alpha by a given factor. # @param [Integer] color The color to adjust. # @param [Integer] factor Fade factor as an integer between 0 and 255. # @return [Integer] The faded color. def fade(color, factor) new_alpha = int8_mult(a(color), factor) (color & 0xffffff00) | new_alpha end # Decomposes a color, given a color, a mask color and a background color. # The returned color will be a variant of the mask color, with the alpha # channel set to the best fitting value. This basically is the reverse # operation if alpha composition. # # If the color cannot be decomposed, this method will return the fully # transparent variant of the mask color. # # @param [Integer] color The color that was the result of compositing. # @param [Integer] mask The opaque variant of the color that was being # composed # @param [Integer] bg The background color on which the color was composed. # @param [Integer] tolerance The decomposition tolerance level, a value # between 0 and 255. # @return [Integer] The decomposed color, a variant of the masked color # with the alpha channel set to an appropriate value. def decompose_color(color, mask, bg, tolerance = 1) if alpha_decomposable?(color, mask, bg, tolerance) mask & 0xffffff00 | decompose_alpha(color, mask, bg) else mask & 0xffffff00 end end # Checks whether an alpha channel value can successfully be composed # given the resulting color, the mask color and a background color, # all of which should be opaque. # # @param [Integer] color The color that was the result of compositing. # @param [Integer] mask The opaque variant of the color that was being # composed # @param [Integer] bg The background color on which the color was composed. # @param [Integer] tolerance The decomposition tolerance level, a value # between 0 and 255. # @return [Boolean] True if the alpha component can be decomposed # successfully. # @see #decompose_alpha def alpha_decomposable?(color, mask, bg, tolerance = 1) components = decompose_alpha_components(color, mask, bg) sum = components.inject(0) { |a, b| a + b } max = components.max * 3 components.max <= 255 && components.min >= 0 && (sum + tolerance * 3) >= max end # Decomposes the alpha channel value given the resulting color, the mask # color and a background color, all of which should be opaque. # # Make sure to call {#alpha_decomposable?} first to see if the alpha # channel value can successfully decomposed with a given tolerance, # otherwise the return value of this method is undefined. # # @param [Integer] color The color that was the result of compositing. # @param [Integer] mask The opaque variant of the color that was being # composed # @param [Integer] bg The background color on which the color was composed. # @return [Integer] The best fitting alpha channel, a value between 0 and # 255. # @see #alpha_decomposable? def decompose_alpha(color, mask, bg) components = decompose_alpha_components(color, mask, bg) (components.inject(0) { |a, b| a + b } / 3.0).round end # Decomposes an alpha channel for either the r, g or b color channel. # @param [:r, :g, :b] channel The channel to decompose the alpha channel # from. # @param [Integer] color The color that was the result of compositing. # @param [Integer] mask The opaque variant of the color that was being # composed # @param [Integer] bg The background color on which the color was composed. # @return [Integer] The decomposed alpha value for the channel. def decompose_alpha_component(channel, color, mask, bg) cc, mc, bc = send(channel, color), send(channel, mask), send(channel, bg) return 0x00 if bc == cc return 0xff if bc == mc return 0xff if cc == mc (((bc - cc).to_f / (bc - mc).to_f) * MAX).round end # Decomposes the alpha channels for the r, g and b color channel. # @param [Integer] color The color that was the result of compositing. # @param [Integer] mask The opaque variant of the color that was being # composed # @param [Integer] bg The background color on which the color was composed. # @return [Array] The decomposed alpha values for the r, g and b # channels. def decompose_alpha_components(color, mask, bg) [ decompose_alpha_component(:r, color, mask, bg), decompose_alpha_component(:g, color, mask, bg), decompose_alpha_component(:b, color, mask, bg), ] end #################################################################### # CONVERSIONS #################################################################### # Returns a string representing this color using hex notation (i.e. # #rrggbbaa). # # @param [Integer] color The color to convert. # @param [Boolean] include_alpha # @return [String] The color in hex notation, starting with a pound sign. def to_hex(color, include_alpha = true) include_alpha ? ("#%08x" % color) : ("#%06x" % [color >> 8]) end # Returns an array with the separate HSV components of a color. # # Because ChunkyPNG internally handles colors as Integers for performance # reasons, some rounding occurs when importing or exporting HSV colors # whose coordinates are float-based. Because of this rounding, #to_hsv and # #from_hsv may not be perfect inverses. # # This implementation follows the modern convention of 0 degrees hue # indicating red. # # @param [Integer] color The ChunkyPNG color to convert. # @param [Boolean] include_alpha Flag indicates whether a fourth element # representing alpha channel should be included in the returned array. # @return [Array[0]] The hue of the color (0-360) # @return [Array[1]] The saturation of the color (0-1) # @return [Array[2]] The value of the color (0-1) # @return [Array[3]] Optional fourth element for alpha, included if # include_alpha=true (0-255) # @see https://en.wikipedia.org/wiki/HSL_and_HSV def to_hsv(color, include_alpha = false) hue, chroma, max, _ = hue_and_chroma(color) value = max saturation = chroma.zero? ? 0.0 : chroma.fdiv(value) include_alpha ? [hue, saturation, value, a(color)] : [hue, saturation, value] end alias to_hsb to_hsv # Returns an array with the separate HSL components of a color. # # Because ChunkyPNG internally handles colors as Integers for performance # reasons, some rounding occurs when importing or exporting HSL colors # whose coordinates are float-based. Because of this rounding, #to_hsl and # #from_hsl may not be perfect inverses. # # This implementation follows the modern convention of 0 degrees hue indicating red. # # @param [Integer] color The ChunkyPNG color to convert. # @param [Boolean] include_alpha Flag indicates whether a fourth element # representing alpha channel should be included in the returned array. # @return [Array[0]] The hue of the color (0-360) # @return [Array[1]] The saturation of the color (0-1) # @return [Array[2]] The lightness of the color (0-1) # @return [Array[3]] Optional fourth element for alpha, included if # include_alpha=true (0-255) # @see https://en.wikipedia.org/wiki/HSL_and_HSV def to_hsl(color, include_alpha = false) hue, chroma, max, min = hue_and_chroma(color) lightness = 0.5 * (max + min) saturation = chroma.zero? ? 0.0 : chroma.fdiv(1 - (2 * lightness - 1).abs) include_alpha ? [hue, saturation, lightness, a(color)] : [hue, saturation, lightness] end # This method encapsulates the logic needed to extract hue and chroma from # a ChunkPNG color. This logic is shared by the cylindrical HSV/HSB and HSL # color space models. # # @param [Integer] color A ChunkyPNG color. # @return [Fixnum] hue The hue of the color (0-360) # @return [Fixnum] chroma The chroma of the color (0-1) # @return [Fixnum] max The magnitude of the largest scaled rgb component (0-1) # @return [Fixnum] min The magnitude of the smallest scaled rgb component (0-1) # @private def hue_and_chroma(color) scaled_rgb = to_truecolor_bytes(color) scaled_rgb.map! { |component| component.fdiv(255) } min, max = scaled_rgb.minmax chroma = max - min r, g, b = scaled_rgb hue_prime = chroma.zero? ? 0 : case max when r then (g - b).fdiv(chroma) when g then (b - r).fdiv(chroma) + 2 when b then (r - g).fdiv(chroma) + 4 else 0 end hue = 60 * hue_prime [hue.round, chroma, max, min] end private :hue_and_chroma # Returns an array with the separate RGBA values for this color. # # @param [Integer] color The color to convert. # @return [Array] An array with 4 Integer elements. def to_truecolor_alpha_bytes(color) [r(color), g(color), b(color), a(color)] end # Returns an array with the separate RGB values for this color. The alpha # channel will be discarded. # # @param [Integer] color The color to convert. # @return [Array] An array with 3 Integer elements. def to_truecolor_bytes(color) [r(color), g(color), b(color)] end # Returns an array with the grayscale teint value for this color. # # This method expects the r, g, and b value to be equal, and the alpha # channel will be discarded. # # @param [Integer] color The grayscale color to convert. # @return [Array] An array with 1 Integer element. def to_grayscale_bytes(color) [b(color)] # assumption r == g == b end # Returns an array with the grayscale teint and alpha channel values for # this color. # # This method expects the color to be grayscale, i.e. r, g, and b value to # be equal and uses only the B channel. If you need to convert a color to # grayscale first, see {#to_grayscale}. # # @param [Integer] color The grayscale color to convert. # @return [Array] An array with 2 Integer elements. # @see #to_grayscale def to_grayscale_alpha_bytes(color) [b(color), a(color)] # assumption r == g == b end #################################################################### # COMPARISON #################################################################### # Compute the Euclidean distance between 2 colors in RGBA # # This method simply takes the Euclidean distance between the RGBA channels # of 2 colors, which gives us a measure of how different the two colors # are. # # Although it would be more perceptually accurate to calculate a proper # Delta E in Lab colorspace, this method should serve many use-cases while # avoiding the overhead of converting RGBA to Lab. # # @param pixel_after [Integer] # @param pixel_before [Integer] # @return [Float] def euclidean_distance_rgba(pixel_after, pixel_before) return 0.0 if pixel_after == pixel_before Math.sqrt( (r(pixel_after) - r(pixel_before))**2 + (g(pixel_after) - g(pixel_before))**2 + (b(pixel_after) - b(pixel_before))**2 + (a(pixel_after) - a(pixel_before))**2 ) end # Could be simplified as MAX * 2, but this format mirrors the math in # {#euclidean_distance_rgba} # @return [Float] The maximum Euclidean distance of two RGBA colors. MAX_EUCLIDEAN_DISTANCE_RGBA = Math.sqrt(MAX**2 * 4) #################################################################### # COLOR CONSTANTS #################################################################### # @return [Hash] All the predefined color names in HTML. PREDEFINED_COLORS = { aliceblue: 0xf0f8ff00, antiquewhite: 0xfaebd700, aqua: 0x00ffff00, aquamarine: 0x7fffd400, azure: 0xf0ffff00, beige: 0xf5f5dc00, bisque: 0xffe4c400, black: 0x00000000, blanchedalmond: 0xffebcd00, blue: 0x0000ff00, blueviolet: 0x8a2be200, brown: 0xa52a2a00, burlywood: 0xdeb88700, cadetblue: 0x5f9ea000, chartreuse: 0x7fff0000, chocolate: 0xd2691e00, coral: 0xff7f5000, cornflowerblue: 0x6495ed00, cornsilk: 0xfff8dc00, crimson: 0xdc143c00, cyan: 0x00ffff00, darkblue: 0x00008b00, darkcyan: 0x008b8b00, darkgoldenrod: 0xb8860b00, darkgray: 0xa9a9a900, darkgrey: 0xa9a9a900, darkgreen: 0x00640000, darkkhaki: 0xbdb76b00, darkmagenta: 0x8b008b00, darkolivegreen: 0x556b2f00, darkorange: 0xff8c0000, darkorchid: 0x9932cc00, darkred: 0x8b000000, darksalmon: 0xe9967a00, darkseagreen: 0x8fbc8f00, darkslateblue: 0x483d8b00, darkslategray: 0x2f4f4f00, darkslategrey: 0x2f4f4f00, darkturquoise: 0x00ced100, darkviolet: 0x9400d300, deeppink: 0xff149300, deepskyblue: 0x00bfff00, dimgray: 0x69696900, dimgrey: 0x69696900, dodgerblue: 0x1e90ff00, firebrick: 0xb2222200, floralwhite: 0xfffaf000, forestgreen: 0x228b2200, fuchsia: 0xff00ff00, gainsboro: 0xdcdcdc00, ghostwhite: 0xf8f8ff00, gold: 0xffd70000, goldenrod: 0xdaa52000, gray: 0x80808000, grey: 0x80808000, green: 0x00800000, greenyellow: 0xadff2f00, honeydew: 0xf0fff000, hotpink: 0xff69b400, indianred: 0xcd5c5c00, indigo: 0x4b008200, ivory: 0xfffff000, khaki: 0xf0e68c00, lavender: 0xe6e6fa00, lavenderblush: 0xfff0f500, lawngreen: 0x7cfc0000, lemonchiffon: 0xfffacd00, lightblue: 0xadd8e600, lightcoral: 0xf0808000, lightcyan: 0xe0ffff00, lightgoldenrodyellow: 0xfafad200, lightgray: 0xd3d3d300, lightgrey: 0xd3d3d300, lightgreen: 0x90ee9000, lightpink: 0xffb6c100, lightsalmon: 0xffa07a00, lightseagreen: 0x20b2aa00, lightskyblue: 0x87cefa00, lightslategray: 0x77889900, lightslategrey: 0x77889900, lightsteelblue: 0xb0c4de00, lightyellow: 0xffffe000, lime: 0x00ff0000, limegreen: 0x32cd3200, linen: 0xfaf0e600, magenta: 0xff00ff00, maroon: 0x80000000, mediumaquamarine: 0x66cdaa00, mediumblue: 0x0000cd00, mediumorchid: 0xba55d300, mediumpurple: 0x9370d800, mediumseagreen: 0x3cb37100, mediumslateblue: 0x7b68ee00, mediumspringgreen: 0x00fa9a00, mediumturquoise: 0x48d1cc00, mediumvioletred: 0xc7158500, midnightblue: 0x19197000, mintcream: 0xf5fffa00, mistyrose: 0xffe4e100, moccasin: 0xffe4b500, navajowhite: 0xffdead00, navy: 0x00008000, oldlace: 0xfdf5e600, olive: 0x80800000, olivedrab: 0x6b8e2300, orange: 0xffa50000, orangered: 0xff450000, orchid: 0xda70d600, palegoldenrod: 0xeee8aa00, palegreen: 0x98fb9800, paleturquoise: 0xafeeee00, palevioletred: 0xd8709300, papayawhip: 0xffefd500, peachpuff: 0xffdab900, peru: 0xcd853f00, pink: 0xffc0cb00, plum: 0xdda0dd00, powderblue: 0xb0e0e600, purple: 0x80008000, red: 0xff000000, rosybrown: 0xbc8f8f00, royalblue: 0x4169e100, saddlebrown: 0x8b451300, salmon: 0xfa807200, sandybrown: 0xf4a46000, seagreen: 0x2e8b5700, seashell: 0xfff5ee00, sienna: 0xa0522d00, silver: 0xc0c0c000, skyblue: 0x87ceeb00, slateblue: 0x6a5acd00, slategray: 0x70809000, slategrey: 0x70809000, snow: 0xfffafa00, springgreen: 0x00ff7f00, steelblue: 0x4682b400, tan: 0xd2b48c00, teal: 0x00808000, thistle: 0xd8bfd800, tomato: 0xff634700, turquoise: 0x40e0d000, violet: 0xee82ee00, wheat: 0xf5deb300, white: 0xffffff00, whitesmoke: 0xf5f5f500, yellow: 0xffff0000, yellowgreen: 0x9acd3200, } # Gets a color value based on a HTML color name. # # The color name is flexible. E.g. 'yellowgreen', 'Yellow # green', 'YellowGreen', 'YELLOW_GREEN' and # :yellow_green will all return the same color value. # # You can include a opacity level in the color name (e.g. 'red @ # 0.5') or give an explicit opacity value as second argument. If no # opacity value is given, the color will be fully opaque. # # @param [Symbol, String] color_name The color name. It may include an # opacity specifier like @ 0.8 to set the color's opacity. # @param [Integer] opacity The opacity value for the color between 0 and # 255. Overrides any opacity value given in the color name. # @return [Integer] The color value. # @raise [ChunkyPNG::Exception] If the color name was not recognized. def html_color(color_name, opacity = nil) if color_name.to_s =~ HTML_COLOR_REGEXP opacity ||= $2 ? ($2.to_f * 255.0).round : 0xff base_color_name = $1.gsub(/[^a-z]+/i, "").downcase.to_sym return PREDEFINED_COLORS[base_color_name] | opacity if PREDEFINED_COLORS.key?(base_color_name) end raise ArgumentError, "Unknown color name #{color_name}!" end # @return [Integer] Black pixel/color BLACK = rgb(0, 0, 0) # @return [Integer] White pixel/color WHITE = rgb(255, 255, 255) # @return [Integer] Fully transparent pixel/color TRANSPARENT = rgba(0, 0, 0, 0) #################################################################### # STATIC UTILITY METHODS #################################################################### # Returns the number of sample values per pixel. # @param [Integer] color_mode The color mode being used. # @return [Integer] The number of sample values per pixel. def samples_per_pixel(color_mode) case color_mode when ChunkyPNG::COLOR_INDEXED then 1 when ChunkyPNG::COLOR_TRUECOLOR then 3 when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then 4 when ChunkyPNG::COLOR_GRAYSCALE then 1 when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then 2 else raise ChunkyPNG::NotSupported, "Don't know the number of samples for this colormode: #{color_mode}!" end end # Returns the size in bytes of a pixel when it is stored using a given # color mode. # # @param [Integer] color_mode The color mode in which the pixels are # stored. # @return [Integer] The number of bytes used per pixel in a datastream. def pixel_bytesize(color_mode, depth = 8) return 1 if depth < 8 (pixel_bitsize(color_mode, depth) + 7) >> 3 end # Returns the size in bits of a pixel when it is stored using a given color # mode. # # @param [Integer] color_mode The color mode in which the pixels are # stored. # @param [Integer] depth The color depth of the pixels. # @return [Integer] The number of bytes used per pixel in a datastream. def pixel_bitsize(color_mode, depth = 8) samples_per_pixel(color_mode) * depth end # Returns the number of bytes used per scanline. # @param [Integer] color_mode The color mode in which the pixels are # stored. # @param [Integer] depth The color depth of the pixels. # @param [Integer] width The number of pixels per scanline. # @return [Integer] The number of bytes used per scanline in a datastream. def scanline_bytesize(color_mode, depth, width) ((pixel_bitsize(color_mode, depth) * width) + 7) >> 3 end # Returns the number of bytes used for an image pass # @param [Integer] color_mode The color mode in which the pixels are # stored. # @param [Integer] depth The color depth of the pixels. # @param [Integer] width The width of the image pass. # @param [Integer] height The height of the image pass. # @return [Integer] The number of bytes used per scanline in a datastream. def pass_bytesize(color_mode, depth, width, height) return 0 if width == 0 || height == 0 (scanline_bytesize(color_mode, depth, width) + 1) * height end end end chunky_png-1.3.15/lib/chunky_png/palette.rb0000644000175000017500000001750713766004353020137 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG # A palette describes the set of colors that is being used for an image. # # A PNG image can contain an explicit palette which defines the colors of # that image, but can also use an implicit palette, e.g. all truecolor colors # or all grayscale colors. # # This palette supports decoding colors from a palette if an explicit palette # is provided in a PNG datastream, and it supports encoding colors to an # explicit palette (stores as PLTE & tRNS chunks in a PNG file). # # @see ChunkyPNG::Color class Palette < Set # Builds a new palette given a set (Enumerable instance) of colors. # # @param enum [Enumerable] The set of colors to include in this # palette.This Enumerable can contain duplicates. # @param decoding_map [Array] An array of colors in the exact order at # which they appeared in the palette chunk, so that this array can be # used for decoding. def initialize(enum, decoding_map = nil) super(enum.sort.freeze) @decoding_map = decoding_map if decoding_map @encoding_map = {} freeze end # Builds a palette instance from a PLTE chunk and optionally a tRNS chunk # from a PNG datastream. # # This method will cerate a palette that is suitable for decoding an image. # # @param palette_chunk [ChunkyPNG::Chunk::Palette] The palette chunk to # load from # @param transparency_chunk [ChunkyPNG::Chunk::Transparency, nil] The # optional transparency chunk. # @return [ChunkyPNG::Palette] The loaded palette instance. # @see ChunkyPNG::Palette#can_decode? def self.from_chunks(palette_chunk, transparency_chunk = nil) return nil if palette_chunk.nil? decoding_map = [] index = 0 palatte_bytes = palette_chunk.content.unpack("C*") alpha_channel = transparency_chunk ? transparency_chunk.content.unpack("C*") : [] index = 0 palatte_bytes.each_slice(3) do |bytes| bytes << alpha_channel.fetch(index, ChunkyPNG::Color::MAX) decoding_map << ChunkyPNG::Color.rgba(*bytes) index += 1 end new(decoding_map, decoding_map) end # Builds a palette instance from a given canvas. # @param canvas [ChunkyPNG::Canvas] The canvas to create a palette for. # @return [ChunkyPNG::Palette] The palette instance. def self.from_canvas(canvas) # Although we don't need to call .uniq.sort before initializing, because # Palette subclasses SortedSet, we get significantly better performance # by doing so. new(canvas.pixels.uniq.sort) end # Builds a palette instance from a given set of pixels. # @param pixels [Enumerable] An enumeration of pixels to create a # palette for # @return [ChunkyPNG::Palette] The palette instance. def self.from_pixels(pixels) new(pixels) end # Checks whether the size of this palette is suitable for indexed storage. # @return [true, false] True if the number of colors in this palette is at # most 256. def indexable? size <= 256 end # Check whether this palette only contains opaque colors. # @return [true, false] True if all colors in this palette are opaque. # @see ChunkyPNG::Color#opaque? def opaque? all? { |color| Color.opaque?(color) } end # Check whether this palette only contains grayscale colors. # @return [true, false] True if all colors in this palette are grayscale # teints. # @see ChunkyPNG::Color#grayscale?? def grayscale? all? { |color| Color.grayscale?(color) } end # Check whether this palette only contains bacl and white. # @return [true, false] True if all colors in this palette are grayscale # teints. # @see ChunkyPNG::Color#grayscale?? def black_and_white? entries == [ChunkyPNG::Color::BLACK, ChunkyPNG::Color::WHITE] end # Returns a palette with all the opaque variants of the colors in this # palette. # @return [ChunkyPNG::Palette] A new Palette instance with only opaque # colors. # @see ChunkyPNG::Color#opaque! def opaque_palette self.class.new(map { |c| ChunkyPNG::Color.opaque!(c) }) end # Checks whether this palette is suitable for decoding an image from a # datastream. # # This requires that the positions of the colors in the original palette # chunk is known, which is stored as an array in the +@decoding_map+ # instance variable. # # @return [true, false] True if a decoding map was built when this palette # was loaded. def can_decode? !@decoding_map.nil? end # Checks whether this palette is suitable for encoding an image from to # datastream. # # This requires that the position of the color in the future palette chunk # is known, which is stored as a hash in the +@encoding_map+ instance # variable. # # @return [true, false] True if a encoding map was built when this palette # was loaded. def can_encode? !@encoding_map.empty? end # Returns a color, given the position in the original palette chunk. # @param index [Integer] The 0-based position of the color in the palette. # @return [ChunkyPNG::Color] The color that is stored in the palette under # the given index # @see ChunkyPNG::Palette#can_decode? def [](index) @decoding_map[index] end # Returns the position of a color in the palette # @param color [ChunkyPNG::Color] The color for which to look up the index. # @return [Integer] The 0-based position of the color in the palette. # @see ChunkyPNG::Palette#can_encode? def index(color) color.nil? ? 0 : @encoding_map[color] end # Creates a tRNS chunk that corresponds with this palette to store the # alpha channel of all colors. # # Note that this chunk can be left out of every color in the palette is # opaque, and the image is encoded using indexed colors. # # @return [ChunkyPNG::Chunk::Transparency] The tRNS chunk. def to_trns_chunk ChunkyPNG::Chunk::Transparency.new("tRNS", map { |c| ChunkyPNG::Color.a(c) }.pack("C*")) end # Creates a PLTE chunk that corresponds with this palette to store the r, # g, and b channels of all colors. # # @note A PLTE chunk should only be included if the image is encoded using # index colors. After this chunk has been built, the palette becomes # suitable for encoding an image. # # @return [ChunkyPNG::Chunk::Palette] The PLTE chunk. # @see ChunkyPNG::Palette#can_encode? def to_plte_chunk @encoding_map.clear colors = [] each_with_index do |color, index| @encoding_map[color] = index colors += ChunkyPNG::Color.to_truecolor_bytes(color) end ChunkyPNG::Chunk::Palette.new("PLTE", colors.pack("C*")) end # Determines the most suitable colormode for this palette. # @return [Integer] The colormode which would create the smallest possible # file for images that use this exact palette. def best_color_settings if black_and_white? [ChunkyPNG::COLOR_GRAYSCALE, 1] elsif grayscale? if opaque? [ChunkyPNG::COLOR_GRAYSCALE, 8] else [ChunkyPNG::COLOR_GRAYSCALE_ALPHA, 8] end elsif indexable? [ChunkyPNG::COLOR_INDEXED, determine_bit_depth] elsif opaque? [ChunkyPNG::COLOR_TRUECOLOR, 8] else [ChunkyPNG::COLOR_TRUECOLOR_ALPHA, 8] end end # Determines the minimal bit depth required for an indexed image # @return [Integer] Number of bits per pixel, i.e. 1, 2, 4 or 8, or nil if # this image cannot be saved as an indexed image. def determine_bit_depth case size when 1..2 then 1 when 3..4 then 2 when 5..16 then 4 when 17..256 then 8 end end end end chunky_png-1.3.15/lib/chunky_png/canvas/0000755000175000017500000000000013766004353017415 5ustar danieldanielchunky_png-1.3.15/lib/chunky_png/canvas/data_url_exporting.rb0000644000175000017500000000071613766004353023640 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG class Canvas # Methods to export a canvas to a PNG data URL. module DataUrlExporting # Exports the canvas as a data url (e.g. data:image/png;base64,) that can # easily be used inline in CSS or HTML. # @return [String] The canvas formatted as a data URL string. def to_data_url ["data:image/png;base64,", to_blob].pack("A*m").delete("\n") end end end end chunky_png-1.3.15/lib/chunky_png/canvas/data_url_importing.rb0000644000175000017500000000146213766004353023630 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG class Canvas # Methods to import a canvas from a PNG data URL. module DataUrlImporting # Imports a canvas from a PNG data URL. # @param [String] string The data URL string to load from. # @return [Canvas] The imported canvas. # @raise ChunkyPNG::SignatureMismatch if the provides string is not a properly # formatted PNG data URL (i.e. it should start with "data:image/png;base64,") def from_data_url(string) if string =~ %r[^data:image/png;base64,((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)$] from_blob($1.unpack("m").first) else raise SignatureMismatch, "The string was not a properly formatted data URL for a PNG image." end end end end end chunky_png-1.3.15/lib/chunky_png/canvas/resampling.rb0000644000175000017500000001152013766004353022102 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG class Canvas # The ChunkyPNG::Canvas::Resampling module defines methods to perform image resampling to # a {ChunkyPNG::Canvas}. # # Currently, only the nearest neighbor algorithm is implemented. Bilinear and cubic # algorithms may be added later on. # # @see ChunkyPNG::Canvas module Resampling # Integer Interpolation between two values # # Used for generating indicies for interpolation (eg, nearest # neighbour). # # @param [Integer] width The width of the source # @param [Integer] new_width The width of the destination # @return [Array] An Array of Integer indicies def steps(width, new_width) indicies, residues = steps_residues(width, new_width) for i in 1..new_width indicies[i - 1] = (indicies[i - 1] + (residues[i - 1] + 127) / 255) end indicies end # Fractional Interpolation between two values # # Used for generating values for interpolation (eg, bilinear). # Produces both the indices and the interpolation factors (residues). # # @param [Integer] width The width of the source # @param [Integer] new_width The width of the destination # @return [Array, Array] Two arrays of indicies and residues def steps_residues(width, new_width) indicies = Array.new(new_width, nil) residues = Array.new(new_width, nil) # This works by accumulating the fractional error and # overflowing when necessary. # We use mixed number arithmetic with a denominator of # 2 * new_width base_step = width / new_width err_step = (width % new_width) << 1 denominator = new_width << 1 # Initial pixel index = (width - new_width) / denominator err = (width - new_width) % denominator for i in 1..new_width indicies[i - 1] = index residues[i - 1] = (255.0 * err.to_f / denominator.to_f).round index += base_step err += err_step if err >= denominator index += 1 err -= denominator end end [indicies, residues] end # Resamples the canvas using nearest neighbor interpolation. # @param [Integer] new_width The width of the resampled canvas. # @param [Integer] new_height The height of the resampled canvas. # @return [ChunkyPNG::Canvas] A new canvas instance with the resampled pixels. def resample_nearest_neighbor!(new_width, new_height) steps_x = steps(width, new_width) steps_y = steps(height, new_height) pixels = Array.new(new_width * new_height) i = 0 for y in steps_y for x in steps_x pixels[i] = get_pixel(x, y) i += 1 end end replace_canvas!(new_width.to_i, new_height.to_i, pixels) end def resample_nearest_neighbor(new_width, new_height) dup.resample_nearest_neighbor!(new_width, new_height) end # Resamples the canvas with bilinear interpolation. # @param [Integer] new_width The width of the resampled canvas. # @param [Integer] new_height The height of the resampled canvas. # @return [ChunkyPNG::Canvas] A new canvas instance with the resampled pixels. def resample_bilinear!(new_width, new_height) index_x, interp_x = steps_residues(width, new_width) index_y, interp_y = steps_residues(height, new_height) pixels = Array.new(new_width * new_height) i = 0 for y in 1..new_height # Clamp the indicies to the edges of the image y1 = [index_y[y - 1], 0].max y2 = [index_y[y - 1] + 1, height - 1].min y_residue = interp_y[y - 1] for x in 1..new_width # Clamp the indicies to the edges of the image x1 = [index_x[x - 1], 0].max x2 = [index_x[x - 1] + 1, width - 1].min x_residue = interp_x[x - 1] pixel_11 = get_pixel(x1, y1) pixel_21 = get_pixel(x2, y1) pixel_12 = get_pixel(x1, y2) pixel_22 = get_pixel(x2, y2) # Interpolate by Row pixel_top = ChunkyPNG::Color.interpolate_quick(pixel_21, pixel_11, x_residue) pixel_bot = ChunkyPNG::Color.interpolate_quick(pixel_22, pixel_12, x_residue) # Interpolate by Column pixels[i] = ChunkyPNG::Color.interpolate_quick(pixel_bot, pixel_top, y_residue) i += 1 end end replace_canvas!(new_width.to_i, new_height.to_i, pixels) end def resample_bilinear(new_width, new_height) dup.resample_bilinear!(new_width, new_height) end alias resample resample_nearest_neighbor alias resize resample end end end chunky_png-1.3.15/lib/chunky_png/canvas/adam7_interlacing.rb0000644000175000017500000000657713766004353023331 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG class Canvas # Methods for decoding and encoding Adam7 interlacing. # # Adam7 interlacing extracts 7 pass images out of a single image, that can be encoded to a # stream separately so the image can be built up progressively. The module is included into # ChunkyPNG canvas and is used to extract the pass images from the original image, or to # reconstruct an original image from separate pass images. module Adam7Interlacing # Returns an array with the x-shift, x-offset, y-shift and y-offset for the requested pass. # @param [Integer] pass The pass number, should be in 0..6. def adam7_multiplier_offset(pass) [ 3 - (pass >> 1), pass & 1 == 0 ? 0 : 8 >> ((pass + 1) >> 1), pass == 0 ? 3 : 3 - ((pass - 1) >> 1), pass == 0 || pass & 1 == 1 ? 0 : 8 >> (pass >> 1), ] end # Returns the pixel dimensions of the requested pass. # @param [Integer] pass The pass number, should be in 0..6. # @param [Integer] original_width The width of the original image. # @param [Integer] original_height The height of the original image. def adam7_pass_size(pass, original_width, original_height) x_shift, x_offset, y_shift, y_offset = adam7_multiplier_offset(pass) [ (original_width - x_offset + (1 << x_shift) - 1) >> x_shift, (original_height - y_offset + (1 << y_shift) - 1) >> y_shift, ] end # Returns an array of the dimension of all the pass images. # @param [Integer] original_width The width of the original image. # @param [Integer] original_height The height of the original image. # @return [Array>] Returns an array with 7 pairs of dimensions. # @see #adam7_pass_size def adam7_pass_sizes(original_width, original_height) (0...7).map { |pass| adam7_pass_size(pass, original_width, original_height) } end # Merges a pass image into a total image that is being constructed. # @param [Integer] pass The pass number, should be in 0..6. # @param [ChunkyPNG::Canvas] canvas The image that is being constructed. # @param [ChunkyPNG::Canvas] subcanvas The pass image that should be merged def adam7_merge_pass(pass, canvas, subcanvas) x_shift, x_offset, y_shift, y_offset = adam7_multiplier_offset(pass) for y in 0...subcanvas.height do for x in 0...subcanvas.width do new_x = (x << x_shift) | x_offset new_y = (y << y_shift) | y_offset canvas[new_x, new_y] = subcanvas[x, y] end end end # Extracts a pass from a complete image # @param [Integer] pass The pass number, should be in 0..6. # @param [ChunkyPNG::Canvas] canvas The image that is being deconstructed. # @return [ChunkyPNG::Canvas] The extracted pass image. def adam7_extract_pass(pass, canvas) x_shift, x_offset, y_shift, y_offset = adam7_multiplier_offset(pass) sm_pixels = [] y_offset.step(canvas.height - 1, 1 << y_shift) do |y| x_offset.step(canvas.width - 1, 1 << x_shift) do |x| sm_pixels << canvas[x, y] end end new_canvas_args = adam7_pass_size(pass, canvas.width, canvas.height) + [sm_pixels] ChunkyPNG::Canvas.new(*new_canvas_args) end end end end chunky_png-1.3.15/lib/chunky_png/canvas/masking.rb0000644000175000017500000001134313766004353021375 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG class Canvas # The ChunkyPNG::Canvas::Masking module defines methods to perform masking # and theming operations on a {ChunkyPNG::Canvas}. The module is included into the Canvas class so all # these methods are available on every canvas. # # @see ChunkyPNG::Canvas module Masking # Creates a new image, based on the current image but with a new theme color. # # This method will replace one color in an image with another image. This is done by # first extracting the pixels with a color close to the original theme color as a mask # image, changing the color of this mask image and then apply it on the original image. # # Mask extraction works best when the theme colored pixels are clearly distinguishable # from a background color (preferably white). You can set a tolerance level to influence # the extraction process. # # @param [Integer] old_theme_color The original theme color in this image. # @param [Integer] new_theme_color The color to replace the old theme color with. # @param [Integer] bg_color The background color on which the theme colored pixels are placed. # @param [Integer] tolerance The tolerance level to use when extracting the mask image. Five is # the default; increase this if the masked image does not extract all the required pixels, # decrease it if too many pixels get extracted. # @return [ChunkyPNG::Canvas] Returns itself, but with the theme colored pixels changed. # @see #change_theme_color! # @see #change_mask_color! def change_theme_color!(old_theme_color, new_theme_color, bg_color = ChunkyPNG::Color::WHITE, tolerance = 5) base, mask = extract_mask(old_theme_color, bg_color, tolerance) mask.change_mask_color!(new_theme_color) replace!(base.compose!(mask)) end # Creates a base image and a mask image from an original image that has a particular theme color. # This can be used to easily change a theme color in an image. # # It will extract all the pixels that look like the theme color (with a tolerance level) and put # these in a mask image. All the other pixels will be stored in a base image. Both images will be # of the exact same size as the original image. The original image will be left untouched. # # The color of the mask image can be changed with {#change_mask_color!}. This new mask image can # then be composed upon the base image to create an image with a new theme color. A call to # {#change_theme_color!} will perform this in one go. # # @param [Integer] mask_color The current theme color. # @param [Integer] bg_color The background color on which the theme colored pixels are applied. # @param [Integer] tolerance The tolerance level to use when extracting the mask image. Five is # the default; increase this if the masked image does not extract all the required pixels, # decrease it if too many pixels get extracted. # @return [Array] An array with the base canvas and the mask # canvas as elements. # @see #change_theme_color! # @see #change_mask_color! def extract_mask(mask_color, bg_color = ChunkyPNG::Color::WHITE, tolerance = 5) base_pixels = [] mask_pixels = [] pixels.each do |pixel| if ChunkyPNG::Color.alpha_decomposable?(pixel, mask_color, bg_color, tolerance) mask_pixels << ChunkyPNG::Color.decompose_color(pixel, mask_color, bg_color, tolerance) base_pixels << bg_color else mask_pixels << (mask_color & 0xffffff00) base_pixels << pixel end end [self.class.new(width, height, base_pixels), self.class.new(width, height, mask_pixels)] end # Changes the color of a mask image. # # This method works on a canvas extracted out of another image using the {#extract_mask} method. # It can then be applied on the extracted base image. See {#change_theme_color!} to perform # these operations in one go. # # @param [Integer] new_color The color to replace the original mask color with. # @raise [ChunkyPNG::ExpectationFailed] when this canvas is not a mask image, i.e. its palette # has more than once color, disregarding transparency. # @see #change_theme_color! # @see #extract_mask def change_mask_color!(new_color) raise ChunkyPNG::ExpectationFailed, "This is not a mask image!" if palette.opaque_palette.size != 1 pixels.map! { |pixel| (new_color & 0xffffff00) | ChunkyPNG::Color.a(pixel) } self end end end end chunky_png-1.3.15/lib/chunky_png/canvas/stream_importing.rb0000644000175000017500000000771113766004353023333 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG class Canvas # Methods to quickly load a canvas from a stream, encoded in RGB, RGBA, BGR or ABGR format. module StreamImporting # Creates a canvas by reading pixels from an RGB formatted stream with a # provided with and height. # # Every pixel should be represented by 3 bytes in the stream, in the correct # RGB order. This format closely resembles the internal representation of a # canvas object, so this kind of stream can be read extremely quickly. # # @param [Integer] width The width of the new canvas. # @param [Integer] height The height of the new canvas. # @param [#read, String] stream The stream to read the pixel data from. # @return [ChunkyPNG::Canvas] The newly constructed canvas instance. def from_rgb_stream(width, height, stream) string = stream.respond_to?(:read) ? stream.read(3 * width * height) : stream.to_s[0, 3 * width * height] string << ChunkyPNG::EXTRA_BYTE # Add a fourth byte to the last RGB triple. unpacker = "NX" * (width * height) pixels = string.unpack(unpacker).map { |color| color | 0x000000ff } new(width, height, pixels) end # Creates a canvas by reading pixels from an RGBA formatted stream with a # provided with and height. # # Every pixel should be represented by 4 bytes in the stream, in the correct # RGBA order. This format is exactly like the internal representation of a # canvas object, so this kind of stream can be read extremely quickly. # # @param [Integer] width The width of the new canvas. # @param [Integer] height The height of the new canvas. # @param [#read, String] stream The stream to read the pixel data from. # @return [ChunkyPNG::Canvas] The newly constructed canvas instance. def from_rgba_stream(width, height, stream) string = stream.respond_to?(:read) ? stream.read(4 * width * height) : stream.to_s[0, 4 * width * height] new(width, height, string.unpack("N*")) end # Creates a canvas by reading pixels from an BGR formatted stream with a # provided with and height. # # Every pixel should be represented by 3 bytes in the stream, in the correct # BGR order. This format closely resembles the internal representation of a # canvas object, so this kind of stream can be read extremely quickly. # # @param [Integer] width The width of the new canvas. # @param [Integer] height The height of the new canvas. # @param [#read, String] stream The stream to read the pixel data from. # @return [ChunkyPNG::Canvas] The newly constructed canvas instance. def from_bgr_stream(width, height, stream) string = ChunkyPNG::EXTRA_BYTE.dup # Add a first byte to the first BGR triple. string << (stream.respond_to?(:read) ? stream.read(3 * width * height) : stream.to_s[0, 3 * width * height]) pixels = string.unpack("@1#{"XV" * (width * height)}").map { |color| color | 0x000000ff } new(width, height, pixels) end # Creates a canvas by reading pixels from an ARGB formatted stream with a # provided with and height. # # Every pixel should be represented by 4 bytes in the stream, in the correct # ARGB order. This format is almost like the internal representation of a # canvas object, so this kind of stream can be read extremely quickly. # # @param [Integer] width The width of the new canvas. # @param [Integer] height The height of the new canvas. # @param [#read, String] stream The stream to read the pixel data from. # @return [ChunkyPNG::Canvas] The newly constructed canvas instance. def from_abgr_stream(width, height, stream) string = stream.respond_to?(:read) ? stream.read(4 * width * height) : stream.to_s[0, 4 * width * height] new(width, height, string.unpack("V*")) end end end end chunky_png-1.3.15/lib/chunky_png/canvas/operations.rb0000644000175000017500000003661713766004353022142 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG class Canvas # The ChunkyPNG::Canvas::Operations module defines methods to perform # operations on a {ChunkyPNG::Canvas}. The module is included into the # Canvas class so all these methods are available on every canvas. # # Note that some of these operations modify the canvas, while some # operations return a new canvas and leave the original intact. # # @see ChunkyPNG::Canvas module Operations # Converts the canvas to grayscale. # # This method will modify the canvas. The obtain a new canvas and leave # the current instance intact, use {#grayscale} instead. # # @return [ChunkyPNG::Canvas] Returns itself, converted to grayscale. # @see #grayscale # @see ChunkyPNG::Color#to_grayscale def grayscale! pixels.map! { |pixel| ChunkyPNG::Color.to_grayscale(pixel) } self end # Converts the canvas to grayscale, returning a new canvas. # # This method will not modify the canvas. To modift the current canvas, # use {#grayscale!} instead. # # @return [ChunkyPNG::Canvas] A copy of the canvas, converted to # grayscale. # @see #grayscale! # @see ChunkyPNG::Color#to_grayscale def grayscale dup.grayscale! end # Composes another image onto this image using alpha blending. This will # modify the current canvas. # # If you simply want to replace pixels or when the other image does not # have transparency, it is faster to use {#replace!}. # # @param [ChunkyPNG::Canvas] other The foreground canvas to compose on # the current canvas, using alpha compositing. # @param [Integer] offset_x The x-offset to apply the new foreground on. # @param [Integer] offset_y The y-offset to apply the new foreground on. # @return [ChunkyPNG::Canvas] Returns itself, but with the other canvas # composed onto it. # @raise [ChunkyPNG::OutOfBounds] when the other canvas doesn't fit on # this one, given the offset and size of the other canvas. # @see #replace! # @see #compose def compose!(other, offset_x = 0, offset_y = 0) check_size_constraints!(other, offset_x, offset_y) for y in 0...other.height do for x in 0...other.width do set_pixel( x + offset_x, y + offset_y, ChunkyPNG::Color.compose( other.get_pixel(x, y), get_pixel(x + offset_x, y + offset_y) ) ) end end self end # Composes another image onto this image using alpha blending. This will # return a new canvas and leave the original intact. # # If you simply want to replace pixels or when the other image does not # have transparency, it is faster to use {#replace}. # # @param (see #compose!) # @return [ChunkyPNG::Canvas] Returns the new canvas, composed of the # other 2. # @raise [ChunkyPNG::OutOfBounds] when the other canvas doesn't fit on # this one, given the offset and size of the other canvas. # # @note API changed since 1.0 - This method now no longer is in place, # but returns a new canvas and leaves the original intact. Use # {#compose!} if you want to compose on the canvas in place. # @see #replace def compose(other, offset_x = 0, offset_y = 0) dup.compose!(other, offset_x, offset_y) end # Replaces pixels on this image by pixels from another pixels, on a given # offset. This method will modify the current canvas. # # This will completely replace the pixels of the background image. If you # want to blend them with semi-transparent pixels from the foreground # image, see {#compose!}. # # @param [ChunkyPNG::Canvas] other The foreground canvas to get the # pixels from. # @param [Integer] offset_x The x-offset to apply the new foreground on. # @param [Integer] offset_y The y-offset to apply the new foreground on. # @return [ChunkyPNG::Canvas] Returns itself, but with the other canvas # placed onto it. # @raise [ChunkyPNG::OutOfBounds] when the other canvas doesn't fit on # this one, given the offset and size of the other canvas. # @see #compose! # @see #replace def replace!(other, offset_x = 0, offset_y = 0) check_size_constraints!(other, offset_x, offset_y) for y in 0...other.height do for d in 0...other.width pixels[(y + offset_y) * width + offset_x + d] = other.pixels[y * other.width + d] end end self end # Replaces pixels on this image by pixels from another pixels, on a given # offset. This method will modify the current canvas. # # This will completely replace the pixels of the background image. If you # want to blend them with semi-transparent pixels from the foreground # image, see {#compose!}. # # @param (see #replace!) # @return [ChunkyPNG::Canvas] Returns a new, combined canvas. # @raise [ChunkyPNG::OutOfBounds] when the other canvas doesn't fit on # this one, given the offset and size of the other canvas. # # @note API changed since 1.0 - This method now no longer is in place, # but returns a new canvas and leaves the original intact. Use # {#replace!} if you want to replace pixels on the canvas in place. # @see #compose def replace(other, offset_x = 0, offset_y = 0) dup.replace!(other, offset_x, offset_y) end # Crops an image, given the coordinates and size of the image that needs # to be cut out. This will leave the original image intact and return a # new, cropped image with pixels copied from the original image. # # @param [Integer] x The x-coordinate of the top left corner of the image # to be cropped. # @param [Integer] y The y-coordinate of the top left corner of the image # to be cropped. # @param [Integer] crop_width The width of the image to be cropped. # @param [Integer] crop_height The height of the image to be cropped. # @return [ChunkyPNG::Canvas] Returns the newly created cropped image. # @raise [ChunkyPNG::OutOfBounds] when the crop dimensions plus the given # coordinates are bigger then the original image. def crop(x, y, crop_width, crop_height) dup.crop!(x, y, crop_width, crop_height) end # Crops an image, given the coordinates and size of the image that needs # to be cut out. # # This will change the size and content of the current canvas. Use # {#crop} if you want to have a new canvas returned instead, leaving the # current canvas intact. # # @param [Integer] x The x-coordinate of the top left corner of the image # to be cropped. # @param [Integer] y The y-coordinate of the top left corner of the image # to be cropped. # @param [Integer] crop_width The width of the image to be cropped. # @param [Integer] crop_height The height of the image to be cropped. # @return [ChunkyPNG::Canvas] Returns itself, but cropped. # @raise [ChunkyPNG::OutOfBounds] when the crop dimensions plus the given # coordinates are bigger then the original image. def crop!(x, y, crop_width, crop_height) if crop_width + x > width raise ChunkyPNG::OutOfBounds, "Original image width is too small!" end if crop_height + y > height raise ChunkyPNG::OutOfBounds, "Original image height is too small!" end if crop_width == width && x == 0 # We only need to crop off the top and/or bottom, so we can take a # shortcut. replace_canvas!(crop_width, crop_height, pixels.slice(y * width, width * crop_height)) else new_pixels = [] for cy in 0...crop_height do new_pixels.concat pixels.slice((cy + y) * width + x, crop_width) end replace_canvas!(crop_width, crop_height, new_pixels) end end # Flips the image horizontally, leaving the original intact. # # This will flip the image on its horizontal axis, e.g. pixels on the top # will now be pixels on the bottom. Chaining this method twice will # return the original canvas. This method will leave the original object # intact and return a new canvas. # # @return [ChunkyPNG::Canvas] The flipped image # @see #flip_horizontally! def flip_horizontally dup.flip_horizontally! end # Flips the image horizontally in place. # # This will flip the image on its horizontal axis, e.g. pixels on the top # will now be pixels on the bottom. Chaining this method twice will # return the original canvas. This method will leave the original object # intact and return a new canvas. # # @return [ChunkyPNG::Canvas] Itself, but flipped # @see #flip_horizontally def flip_horizontally! for y in 0..((height - 1) >> 1) do other_y = height - (y + 1) other_row = row(other_y) replace_row!(other_y, row(y)) replace_row!(y, other_row) end self end alias flip! flip_horizontally! alias flip flip_horizontally # Flips the image vertically, leaving the original intact. # # This will flip the image on its vertical axis, e.g. pixels on the left # will now be pixels on the right. Chaining this method twice will return # the original canvas. This method will leave the original object intact # and return a new canvas. # # @return [ChunkyPNG::Canvas] The flipped image # @see #flip_vertically! def flip_vertically dup.flip_vertically! end # Flips the image vertically in place. # # This will flip the image on its vertical axis, e.g. pixels on the left # will now be pixels on the right. Chaining this method twice will return # the original canvas. This method will leave the original object intact # and return a new canvas. # # @return [ChunkyPNG::Canvas] Itself, but flipped # @see #flip_vertically def flip_vertically! for y in 0...height do replace_row!(y, row(y).reverse) end self end alias mirror! flip_vertically! alias mirror flip_vertically # Returns a new canvas instance that is rotated 90 degrees clockwise. # # This method will return a new canvas and leaves the original intact. # # @return [ChunkyPNG::Canvas] A clockwise-rotated copy. # @see #rotate_right! for the in place version. def rotate_right dup.rotate_right! end # Rotates the image 90 degrees clockwise in place. # # This method will change the current canvas. # # @return [ChunkyPNG::Canvas] Itself, but rotated clockwise. # @see #rotate_right for a version that leaves the current canvas intact def rotate_right! new_pixels = [] 0.upto(width - 1) { |i| new_pixels += column(i).reverse } replace_canvas!(height, width, new_pixels) end alias rotate_clockwise rotate_right alias rotate_clockwise! rotate_right! # Returns an image that is rotated 90 degrees counter-clockwise. # # This method will leave the original object intact and return a new # canvas. # # @return [ChunkyPNG::Canvas] A rotated copy of itself. # @see #rotate_left! for the in-place version. def rotate_left dup.rotate_left! end # Rotates the image 90 degrees counter-clockwise in place. # # This method will change the original canvas. See {#rotate_left} for a # version that leaves the canvas intact and returns a new rotated canvas # instead. # # @return [ChunkyPNG::Canvas] Itself, but rotated. def rotate_left! new_pixels = [] (width - 1).downto(0) { |i| new_pixels += column(i) } replace_canvas!(height, width, new_pixels) end alias rotate_counter_clockwise rotate_left alias rotate_counter_clockwise! rotate_left! # Rotates the image 180 degrees. # # This method will leave the original object intact and return a new # canvas. # # @return [ChunkyPNG::Canvas] The rotated image. # @see #rotate_180! def rotate_180 dup.rotate_180! end # Rotates the image 180 degrees in place. # # @return [ChunkyPNG::Canvas] Itself, but rotated 180 degrees. # @see #rotate_180 def rotate_180! pixels.reverse! self end # Trims the border around the image, presumed to be the color of the # first pixel. # # @param [Integer] border The color to attempt to trim. # @return [ChunkyPNG::Canvas] The trimmed image. # @see #trim! def trim(border = pixels.first) dup.trim! end # Trims the border around the image in place. # # @param [Integer] border The color to attempt to trim. # @return [ChunkyPNG::Canvas] Returns itself, but with the border # trimmed. # @see #trim def trim!(border = pixels.first) x1 = [*0...width].index { |c| column(c).uniq != [border] } x2 = [*0...width].rindex { |c| column(c).uniq != [border] } y1 = [*0...height].index { |r| row(r).uniq != [border] } y2 = [*0...height].rindex { |r| row(r).uniq != [border] } crop! x1, y1, x2 - x1 + 1, y2 - y1 + 1 end # Draws a border around the image. # # @param [Integer] size The size of the border. # @param [Integer] color The color of the border. # @return [ChunkyPNG::Canvas] Returns a bordered version of the image. # @see #border! def border(size, color = ChunkyPNG::Color::BLACK) dup.border!(size, color) end # Draws a border around the image in place. # # @param [Integer] size The size of the border. # @param [Integer] color The color of the border. # @return [ChunkyPNG::Canvas] Returns itself with the border added. # @see #border def border!(size, color = ChunkyPNG::Color::BLACK) new_width = width + size * 2 new_height = height + size * 2 bg = Canvas.new(new_width, new_height, color).replace(self, size, size) replace_canvas!(new_width, new_height, bg.pixels) end protected # Checks whether another image has the correct dimension to be used for # an operation on the current image, given an offset coordinate to work # with. # @param [ChunkyPNG::Canvas] other The other canvas # @param [Integer] offset_x The x offset on which the other image will be # applied. # @param [Integer] offset_y The y offset on which the other image will be # applied. # @raise [ChunkyPNG::OutOfBounds] when the other image doesn't fit. def check_size_constraints!(other, offset_x, offset_y) if width < other.width + offset_x raise ChunkyPNG::OutOfBounds, "Background image width is too small!" end if height < other.height + offset_y raise ChunkyPNG::OutOfBounds, "Background image height is too small!" end end end end end chunky_png-1.3.15/lib/chunky_png/canvas/png_encoding.rb0000644000175000017500000005226513766004353022406 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG class Canvas # Methods for encoding a Canvas instance into a PNG datastream. # # Overview of the encoding process: # # * The image is split up in scanlines (i.e. rows of pixels); # * All pixels are encoded as a pixelstream, based on the color mode. # * All the pixel bytes in the pixelstream are adjusted using a filtering # method if one is specified. # * Compress the resulting string using deflate compression. # * Split compressed data over one or more PNG chunks. # * These chunks should be embedded in a datastream with at least a IHDR and # IEND chunk and possibly a PLTE chunk. # # For interlaced images, the initial image is first split into 7 subimages. # These images get encoded exactly as above, and the result gets combined # before the compression step. # # @see ChunkyPNG::Canvas::PNGDecoding # @see https://www.w3.org/TR/PNG/ The W3C PNG format specification module PNGEncoding # The palette used for encoding the image.This is only in used for images # that get encoded using indexed colors. # @return [ChunkyPNG::Palette] attr_accessor :encoding_palette # Writes the canvas to an IO stream, encoded as a PNG image. # @param [IO] io The output stream to write to. # @param constraints (see ChunkyPNG::Canvas::PNGEncoding#to_datastream) # @return [void] def write(io, constraints = {}) to_datastream(constraints).write(io) end # Writes the canvas to a file, encoded as a PNG image. # @param [String] filename The file to save the PNG image to. # @param constraints (see ChunkyPNG::Canvas::PNGEncoding#to_datastream) # @return [void] def save(filename, constraints = {}) File.open(filename, "wb") { |io| write(io, constraints) } end # Encoded the canvas to a PNG formatted string. # @param constraints (see ChunkyPNG::Canvas::PNGEncoding#to_datastream) # @return [String] The PNG encoded canvas as string. def to_blob(constraints = {}) to_datastream(constraints).to_blob end alias to_string to_blob alias to_s to_blob # Converts this Canvas to a datastream, so that it can be saved as a PNG image. # @param [Hash, Symbol] constraints The constraints to use when encoding the canvas. # This can either be a hash with different constraints, or a symbol which acts as a # preset for some constraints. If no constraints are given, ChunkyPNG will decide # for itself how to best create the PNG datastream. # Supported presets are :fast_rgba for quickly saving images with transparency, # :fast_rgb for quickly saving opaque images, and :best_compression to # obtain the smallest possible filesize. # @option constraints [Fixnum] :color_mode The color mode to use. Use one of the # ChunkyPNG::COLOR_* constants. # @option constraints [true, false] :interlace Whether to use interlacing. # @option constraints [Fixnum] :compression The compression level for Zlib. This can be a # value between 0 and 9, or a Zlib constant like Zlib::BEST_COMPRESSION. # @option constraints [Fixnum] :bit_depth The bit depth to use. This option is only used # for indexed images, in which case it overrides the determined minimal bit depth. For # all the other color modes, a bit depth of 8 is used. # @return [ChunkyPNG::Datastream] The PNG datastream containing the encoded canvas. # @see ChunkyPNG::Canvas::PNGEncoding#determine_png_encoding def to_datastream(constraints = {}) encoding = determine_png_encoding(constraints) ds = Datastream.new ds.header_chunk = Chunk::Header.new( width: width, height: height, color: encoding[:color_mode], depth: encoding[:bit_depth], interlace: encoding[:interlace] ) if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED ds.palette_chunk = encoding_palette.to_plte_chunk ds.transparency_chunk = encoding_palette.to_trns_chunk unless encoding_palette.opaque? end data = encode_png_pixelstream(encoding[:color_mode], encoding[:bit_depth], encoding[:interlace], encoding[:filtering]) ds.data_chunks = Chunk::ImageData.split_in_chunks(data, encoding[:compression]) ds.end_chunk = Chunk::End.new ds end protected # Determines the best possible PNG encoding variables for this image, by analyzing # the colors used for the image. # # You can provide constraints for the encoding variables by passing a hash with # encoding variables to this method. # # @param [Hash, Symbol] constraints The constraints for the encoding. This can be a # Hash or a preset symbol. # @return [Hash] A hash with encoding options for {ChunkyPNG::Canvas::PNGEncoding#to_datastream} def determine_png_encoding(constraints = {}) encoding = case constraints when :fast_rgb then {color_mode: ChunkyPNG::COLOR_TRUECOLOR, compression: Zlib::BEST_SPEED} when :fast_rgba then {color_mode: ChunkyPNG::COLOR_TRUECOLOR_ALPHA, compression: Zlib::BEST_SPEED} when :best_compression then {compression: Zlib::BEST_COMPRESSION, filtering: ChunkyPNG::FILTER_PAETH} when :good_compression then {compression: Zlib::BEST_COMPRESSION, filtering: ChunkyPNG::FILTER_NONE} when :no_compression then {compression: Zlib::NO_COMPRESSION} when :black_and_white then {color_mode: ChunkyPNG::COLOR_GRAYSCALE, bit_depth: 1} when Hash then constraints else raise ChunkyPNG::Exception, "Unknown encoding preset: #{constraints.inspect}" end # Do not create a palette when the encoding is given and does not require a palette. if encoding[:color_mode] if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED self.encoding_palette = palette encoding[:bit_depth] ||= encoding_palette.determine_bit_depth else encoding[:bit_depth] ||= 8 end else self.encoding_palette = palette suggested_color_mode, suggested_bit_depth = encoding_palette.best_color_settings encoding[:color_mode] ||= suggested_color_mode encoding[:bit_depth] ||= suggested_bit_depth end # Use Zlib's default for compression unless otherwise provided. encoding[:compression] ||= Zlib::DEFAULT_COMPRESSION encoding[:interlace] = case encoding[:interlace] when nil, false then ChunkyPNG::INTERLACING_NONE when true then ChunkyPNG::INTERLACING_ADAM7 else encoding[:interlace] end encoding[:filtering] ||= case encoding[:compression] when Zlib::BEST_COMPRESSION then ChunkyPNG::FILTER_PAETH when Zlib::NO_COMPRESSION..Zlib::BEST_SPEED then ChunkyPNG::FILTER_NONE else ChunkyPNG::FILTER_UP end encoding end # Encodes the canvas according to the PNG format specification with a given color # mode, possibly with interlacing. # @param [Integer] color_mode The color mode to use for encoding. # @param [Integer] bit_depth The bit depth of the image. # @param [Integer] interlace The interlacing method to use. # @return [String] The PNG encoded canvas as string. def encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, bit_depth = 8, interlace = ChunkyPNG::INTERLACING_NONE, filtering = ChunkyPNG::FILTER_NONE) if color_mode == ChunkyPNG::COLOR_INDEXED raise ChunkyPNG::ExpectationFailed, "This palette is not suitable for encoding!" if encoding_palette.nil? || !encoding_palette.can_encode? raise ChunkyPNG::ExpectationFailed, "This palette has too many colors!" if encoding_palette.size > (1 << bit_depth) end case interlace when ChunkyPNG::INTERLACING_NONE then encode_png_image_without_interlacing(color_mode, bit_depth, filtering) when ChunkyPNG::INTERLACING_ADAM7 then encode_png_image_with_interlacing(color_mode, bit_depth, filtering) else raise ChunkyPNG::NotSupported, "Unknown interlacing method: #{interlace}!" end end # Encodes the canvas according to the PNG format specification with a given color mode. # @param [Integer] color_mode The color mode to use for encoding. # @param [Integer] bit_depth The bit depth of the image. # @param [Integer] filtering The filtering method to use. # @return [String] The PNG encoded canvas as string. def encode_png_image_without_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE) stream = "".b encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering) stream end # Encodes the canvas according to the PNG format specification with a given color # mode and Adam7 interlacing. # # This method will split the original canvas in 7 smaller canvases and encode them # one by one, concatenating the resulting strings. # # @param [Integer] color_mode The color mode to use for encoding. # @param [Integer] bit_depth The bit depth of the image. # @param [Integer] filtering The filtering method to use. # @return [String] The PNG encoded canvas as string. def encode_png_image_with_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE) stream = "".b 0.upto(6) do |pass| subcanvas = self.class.adam7_extract_pass(pass, self) subcanvas.encoding_palette = encoding_palette subcanvas.encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering) end stream end # Encodes the canvas to a stream, in a given color mode. # @param [String] stream The stream to write to. # @param [Integer] color_mode The color mode to use for encoding. # @param [Integer] bit_depth The bit depth of the image. # @param [Integer] filtering The filtering method to use. def encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering) start_pos = stream.bytesize pixel_size = Color.pixel_bytesize(color_mode) line_width = Color.scanline_bytesize(color_mode, bit_depth, width) # Determine the filter method encode_method = encode_png_pixels_to_scanline_method(color_mode, bit_depth) filter_method = case filtering when ChunkyPNG::FILTER_NONE then nil when ChunkyPNG::FILTER_SUB then :encode_png_str_scanline_sub when ChunkyPNG::FILTER_UP then :encode_png_str_scanline_up when ChunkyPNG::FILTER_AVERAGE then :encode_png_str_scanline_average when ChunkyPNG::FILTER_PAETH then :encode_png_str_scanline_paeth else raise ArgumentError, "Filtering method #{filtering} is not supported" end 0.upto(height - 1) do |y| stream << send(encode_method, row(y)) end # Now, apply filtering if any if filter_method (height - 1).downto(0) do |y| pos = start_pos + y * (line_width + 1) prev_pos = y == 0 ? nil : pos - (line_width + 1) send(filter_method, stream, pos, prev_pos, line_width, pixel_size) end end end # Encodes a line of pixels using 8-bit truecolor mode. # @param [Array] pixels A row of pixels of the original image. # @return [String] The encoded scanline as binary string def encode_png_pixels_to_scanline_truecolor_8bit(pixels) pixels.pack("x" + ("NX" * width)) end # Encodes a line of pixels using 8-bit truecolor alpha mode. # @param [Array] pixels A row of pixels of the original image. # @return [String] The encoded scanline as binary string def encode_png_pixels_to_scanline_truecolor_alpha_8bit(pixels) pixels.pack("xN#{width}") end # Encodes a line of pixels using 1-bit indexed mode. # @param [Array] pixels A row of pixels of the original image. # @return [String] The encoded scanline as binary string def encode_png_pixels_to_scanline_indexed_1bit(pixels) chars = [] pixels.each_slice(8) do |p1, p2, p3, p4, p5, p6, p7, p8| chars << ( (encoding_palette.index(p1) << 7) | (encoding_palette.index(p2) << 6) | (encoding_palette.index(p3) << 5) | (encoding_palette.index(p4) << 4) | (encoding_palette.index(p5) << 3) | (encoding_palette.index(p6) << 2) | (encoding_palette.index(p7) << 1) | encoding_palette.index(p8) ) end chars.pack("xC*") end # Encodes a line of pixels using 2-bit indexed mode. # @param [Array] pixels A row of pixels of the original image. # @return [String] The encoded scanline as binary string def encode_png_pixels_to_scanline_indexed_2bit(pixels) chars = [] pixels.each_slice(4) do |p1, p2, p3, p4| chars << ( (encoding_palette.index(p1) << 6) | (encoding_palette.index(p2) << 4) | (encoding_palette.index(p3) << 2) | encoding_palette.index(p4) ) end chars.pack("xC*") end # Encodes a line of pixels using 4-bit indexed mode. # @param [Array] pixels A row of pixels of the original image. # @return [String] The encoded scanline as binary string def encode_png_pixels_to_scanline_indexed_4bit(pixels) chars = [] pixels.each_slice(2) do |p1, p2| chars << ((encoding_palette.index(p1) << 4) | encoding_palette.index(p2)) end chars.pack("xC*") end # Encodes a line of pixels using 8-bit indexed mode. # @param [Array] pixels A row of pixels of the original image. # @return [String] The encoded scanline as binary string def encode_png_pixels_to_scanline_indexed_8bit(pixels) pixels.map { |p| encoding_palette.index(p) }.pack("xC#{width}") end # Encodes a line of pixels using 1-bit grayscale mode. # @param [Array] pixels A row of pixels of the original image. # @return [String] The encoded scanline as binary string def encode_png_pixels_to_scanline_grayscale_1bit(pixels) chars = [] pixels.each_slice(8) do |p1, p2, p3, p4, p5, p6, p7, p8| chars << ((p1.nil? ? 0 : (p1 & 0x0000ffff) >> 15 << 7) | (p2.nil? ? 0 : (p2 & 0x0000ffff) >> 15 << 6) | (p3.nil? ? 0 : (p3 & 0x0000ffff) >> 15 << 5) | (p4.nil? ? 0 : (p4 & 0x0000ffff) >> 15 << 4) | (p5.nil? ? 0 : (p5 & 0x0000ffff) >> 15 << 3) | (p6.nil? ? 0 : (p6 & 0x0000ffff) >> 15 << 2) | (p7.nil? ? 0 : (p7 & 0x0000ffff) >> 15 << 1) | (p8.nil? ? 0 : (p8 & 0x0000ffff) >> 15)) end chars.pack("xC*") end # Encodes a line of pixels using 2-bit grayscale mode. # @param [Array] pixels A row of pixels of the original image. # @return [String] The encoded scanline as binary string def encode_png_pixels_to_scanline_grayscale_2bit(pixels) chars = [] pixels.each_slice(4) do |p1, p2, p3, p4| chars << ((p1.nil? ? 0 : (p1 & 0x0000ffff) >> 14 << 6) | (p2.nil? ? 0 : (p2 & 0x0000ffff) >> 14 << 4) | (p3.nil? ? 0 : (p3 & 0x0000ffff) >> 14 << 2) | (p4.nil? ? 0 : (p4 & 0x0000ffff) >> 14)) end chars.pack("xC*") end # Encodes a line of pixels using 2-bit grayscale mode. # @param [Array] pixels A row of pixels of the original image. # @return [String] The encoded scanline as binary string def encode_png_pixels_to_scanline_grayscale_4bit(pixels) chars = [] pixels.each_slice(2) do |p1, p2| chars << ((p1.nil? ? 0 : ((p1 & 0x0000ffff) >> 12) << 4) | (p2.nil? ? 0 : ((p2 & 0x0000ffff) >> 12))) end chars.pack("xC*") end # Encodes a line of pixels using 8-bit grayscale mode. # @param [Array] pixels A row of pixels of the original image. # @return [String] The encoded scanline as binary string def encode_png_pixels_to_scanline_grayscale_8bit(pixels) pixels.map { |p| p >> 8 }.pack("xC#{width}") end # Encodes a line of pixels using 8-bit grayscale alpha mode. # @param [Array] pixels A row of pixels of the original image. # @return [String] The encoded scanline as binary string def encode_png_pixels_to_scanline_grayscale_alpha_8bit(pixels) pixels.pack("xn#{width}") end # Returns the method name to use to decode scanlines into pixels. # @param [Integer] color_mode The color mode of the image. # @param [Integer] depth The bit depth of the image. # @return [Symbol] The method name to use for decoding, to be called on the canvas class. # @raise [ChunkyPNG::NotSupported] when the color_mode and/or bit depth is not supported. def encode_png_pixels_to_scanline_method(color_mode, depth) encoder_method = case color_mode when ChunkyPNG::COLOR_TRUECOLOR then :"encode_png_pixels_to_scanline_truecolor_#{depth}bit" when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then :"encode_png_pixels_to_scanline_truecolor_alpha_#{depth}bit" when ChunkyPNG::COLOR_INDEXED then :"encode_png_pixels_to_scanline_indexed_#{depth}bit" when ChunkyPNG::COLOR_GRAYSCALE then :"encode_png_pixels_to_scanline_grayscale_#{depth}bit" when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then :"encode_png_pixels_to_scanline_grayscale_alpha_#{depth}bit" end raise ChunkyPNG::NotSupported, "No encoder found for color mode #{color_mode} and #{depth}-bit depth!" unless respond_to?(encoder_method, true) encoder_method end # Encodes a scanline of a pixelstream without filtering. This is a no-op. # @param [String] stream The pixelstream to work on. This string will be modified. # @param [Integer] pos The starting position of the scanline. # @param [Integer, nil] prev_pos The starting position of the previous scanline. nil if # this is the first line. # @param [Integer] line_width The number of bytes in this scanline, without counting the filtering # method byte. # @param [Integer] pixel_size The number of bytes used per pixel. # @return [void] def encode_png_str_scanline_none(stream, pos, prev_pos, line_width, pixel_size) # noop - this method shouldn't get called at all. end # Encodes a scanline of a pixelstream using SUB filtering. This will modify the stream. # @param (see #encode_png_str_scanline_none) # @return [void] def encode_png_str_scanline_sub(stream, pos, prev_pos, line_width, pixel_size) line_width.downto(1) do |i| a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0 stream.setbyte(pos + i, (stream.getbyte(pos + i) - a) & 0xff) end stream.setbyte(pos, ChunkyPNG::FILTER_SUB) end # Encodes a scanline of a pixelstream using UP filtering. This will modify the stream. # @param (see #encode_png_str_scanline_none) # @return [void] def encode_png_str_scanline_up(stream, pos, prev_pos, line_width, pixel_size) line_width.downto(1) do |i| b = prev_pos ? stream.getbyte(prev_pos + i) : 0 stream.setbyte(pos + i, (stream.getbyte(pos + i) - b) & 0xff) end stream.setbyte(pos, ChunkyPNG::FILTER_UP) end # Encodes a scanline of a pixelstream using AVERAGE filtering. This will modify the stream. # @param (see #encode_png_str_scanline_none) # @return [void] def encode_png_str_scanline_average(stream, pos, prev_pos, line_width, pixel_size) line_width.downto(1) do |i| a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0 b = prev_pos ? stream.getbyte(prev_pos + i) : 0 stream.setbyte(pos + i, (stream.getbyte(pos + i) - ((a + b) >> 1)) & 0xff) end stream.setbyte(pos, ChunkyPNG::FILTER_AVERAGE) end # Encodes a scanline of a pixelstream using PAETH filtering. This will modify the stream. # @param (see #encode_png_str_scanline_none) # @return [void] def encode_png_str_scanline_paeth(stream, pos, prev_pos, line_width, pixel_size) line_width.downto(1) do |i| a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0 b = prev_pos ? stream.getbyte(prev_pos + i) : 0 c = prev_pos && i > pixel_size ? stream.getbyte(prev_pos + i - pixel_size) : 0 p = a + b - c pa = (p - a).abs pb = (p - b).abs pc = (p - c).abs pr = if pa <= pb && pa <= pc a else pb <= pc ? b : c end stream.setbyte(pos + i, (stream.getbyte(pos + i) - pr) & 0xff) end stream.setbyte(pos, ChunkyPNG::FILTER_PAETH) end end end end chunky_png-1.3.15/lib/chunky_png/canvas/stream_exporting.rb0000644000175000017500000000410713766004353023336 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG class Canvas # Methods to save load a canvas from to stream, encoded in RGB, RGBA, BGR or ABGR format. module StreamExporting # Creates an RGB-formatted pixelstream with the pixel data from this canvas. # # Note that this format is fast but bloated, because no compression is used # and the internal representation is left intact. To reconstruct the # canvas, the width and height should be known. # # @return [String] The RGBA-formatted pixel data. def to_rgba_stream pixels.pack("N*") end # Creates an RGB-formatted pixelstream with the pixel data from this canvas. # # Note that this format is fast but bloated, because no compression is used # and the internal representation is almost left intact. To reconstruct # the canvas, the width and height should be known. # # @return [String] The RGB-formatted pixel data. def to_rgb_stream pixels.pack("NX" * pixels.length) end # Creates a stream of the alpha channel of this canvas. # # @return [String] The 0-255 alpha values of all pixels packed as string def to_alpha_channel_stream pixels.pack("C*") end # Creates a grayscale stream of this canvas. # # This method assume sthat this image is fully grayscale, i.e. R = G = B for # every pixel. The alpha channel will not be included in the stream. # # @return [String] The 0-255 grayscale values of all pixels packed as string. def to_grayscale_stream pixels.pack("nX" * pixels.length) end # Creates an ABGR-formatted pixelstream with the pixel data from this canvas. # # Note that this format is fast but bloated, because no compression is used # and the internal representation is left intact. To reconstruct the # canvas, the width and height should be known. # # @return [String] The RGBA-formatted pixel data. def to_abgr_stream pixels.pack("V*") end end end end chunky_png-1.3.15/lib/chunky_png/canvas/drawing.rb0000644000175000017500000002700513766004353021401 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG class Canvas # Module that adds some primitive drawing methods to {ChunkyPNG::Canvas}. # # All of these methods change the current canvas instance and do not create # a new one, even though the method names do not end with a bang. # # @note Drawing operations will not fail when something is drawn outside of # the bounds of the canvas; these pixels will simply be ignored. # @see ChunkyPNG::Canvas module Drawing # Composes a pixel on the canvas by alpha blending a color with its # background color. # # @param [Integer] x The x-coordinate of the pixel to blend. # @param [Integer] y The y-coordinate of the pixel to blend. # @param [Integer] color The foreground color to blend with # @return [Integer] The composed color. def compose_pixel(x, y, color) return unless include_xy?(x, y) compose_pixel_unsafe(x, y, ChunkyPNG::Color.parse(color)) end # Composes a pixel on the canvas by alpha blending a color with its # background color, without bounds checking. # # @param (see #compose_pixel) # @return [Integer] The composed color. def compose_pixel_unsafe(x, y, color) set_pixel(x, y, ChunkyPNG::Color.compose(color, get_pixel(x, y))) end # Draws a Bezier curve # @param [Array, Point] points A collection of control points # @param [Integer] stroke_color # @return [Chunky:PNG::Canvas] Itself, with the curve drawn def bezier_curve(points, stroke_color = ChunkyPNG::Color::BLACK) points = ChunkyPNG::Vector(*points) case points.length when 0, 1 then return self when 2 then return line(points[0].x, points[0].y, points[1].x, points[1].y, stroke_color) end curve_points = [] t = 0 n = points.length - 1 while t <= 100 bicof = 0 cur_p = ChunkyPNG::Point.new(0, 0) # Generate a float of t. t_f = t / 100.00 cur_p.x += ((1 - t_f)**n) * points[0].x cur_p.y += ((1 - t_f)**n) * points[0].y for i in 1...points.length - 1 bicof = binomial_coefficient(n, i) cur_p.x += (bicof * (1 - t_f)**(n - i)) * (t_f**i) * points[i].x cur_p.y += (bicof * (1 - t_f)**(n - i)) * (t_f**i) * points[i].y i += 1 end cur_p.x += (t_f**n) * points[n].x cur_p.y += (t_f**n) * points[n].y curve_points << cur_p t += 1 end curve_points.each_cons(2) do |p1, p2| line_xiaolin_wu(p1.x.round, p1.y.round, p2.x.round, p2.y.round, stroke_color) end self end # Draws an anti-aliased line using Xiaolin Wu's algorithm. # # @param [Integer] x0 The x-coordinate of the first control point. # @param [Integer] y0 The y-coordinate of the first control point. # @param [Integer] x1 The x-coordinate of the second control point. # @param [Integer] y1 The y-coordinate of the second control point. # @param [Integer] stroke_color The color to use for this line. # @param [true, false] inclusive Whether to draw the last pixel. Set to # false when drawing multiple lines in a path. # @return [ChunkyPNG::Canvas] Itself, with the line drawn. def line_xiaolin_wu(x0, y0, x1, y1, stroke_color, inclusive = true) stroke_color = ChunkyPNG::Color.parse(stroke_color) dx = x1 - x0 sx = dx < 0 ? -1 : 1 dx *= sx dy = y1 - y0 sy = dy < 0 ? -1 : 1 dy *= sy if dy == 0 # vertical line x0.step(inclusive ? x1 : x1 - sx, sx) do |x| compose_pixel(x, y0, stroke_color) end elsif dx == 0 # horizontal line y0.step(inclusive ? y1 : y1 - sy, sy) do |y| compose_pixel(x0, y, stroke_color) end elsif dx == dy # diagonal x0.step(inclusive ? x1 : x1 - sx, sx) do |x| compose_pixel(x, y0, stroke_color) y0 += sy end elsif dy > dx # vertical displacement compose_pixel(x0, y0, stroke_color) e_acc = 0 e = ((dx << 16) / dy.to_f).round (dy - 1).downto(0) do |i| e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xffff x0 += sx if e_acc <= e_acc_temp w = 0xff - (e_acc >> 8) compose_pixel(x0, y0, ChunkyPNG::Color.fade(stroke_color, w)) if inclusive || i > 0 compose_pixel(x0 + sx, y0 + sy, ChunkyPNG::Color.fade(stroke_color, 0xff - w)) end y0 += sy end compose_pixel(x1, y1, stroke_color) if inclusive else # horizontal displacement compose_pixel(x0, y0, stroke_color) e_acc = 0 e = ((dy << 16) / dx.to_f).round (dx - 1).downto(0) do |i| e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xffff y0 += sy if e_acc <= e_acc_temp w = 0xff - (e_acc >> 8) compose_pixel(x0, y0, ChunkyPNG::Color.fade(stroke_color, w)) if inclusive || i > 0 compose_pixel(x0 + sx, y0 + sy, ChunkyPNG::Color.fade(stroke_color, 0xff - w)) end x0 += sx end compose_pixel(x1, y1, stroke_color) if inclusive end self end alias line line_xiaolin_wu # Draws a polygon on the canvas using the stroke_color, filled using the # fill_color if any. # # @param [Array, String] path The control point vector. Accepts everything # {ChunkyPNG.Vector} accepts. # @param [Integer] stroke_color The stroke color to use for this polygon. # @param [Integer] fill_color The fill color to use for this polygon. # @return [ChunkyPNG::Canvas] Itself, with the polygon drawn. def polygon(path, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT) vector = ChunkyPNG::Vector(*path) if path.length < 3 raise ArgumentError, "A polygon requires at least 3 points" end stroke_color = ChunkyPNG::Color.parse(stroke_color) fill_color = ChunkyPNG::Color.parse(fill_color) # Fill unless fill_color == ChunkyPNG::Color::TRANSPARENT vector.y_range.each do |y| intersections = [] vector.edges.each do |p1, p2| if (p1.y < y && p2.y >= y) || (p2.y < y && p1.y >= y) intersections << (p1.x + (y - p1.y).to_f / (p2.y - p1.y) * (p2.x - p1.x)).round end end intersections.sort! 0.step(intersections.length - 1, 2) do |i| intersections[i].upto(intersections[i + 1]) do |x| compose_pixel(x, y, fill_color) end end end end # Stroke vector.each_edge do |(from_x, from_y), (to_x, to_y)| line(from_x, from_y, to_x, to_y, stroke_color, false) end self end # Draws a rectangle on the canvas, using two control points. # # @param [Integer] x0 The x-coordinate of the first control point. # @param [Integer] y0 The y-coordinate of the first control point. # @param [Integer] x1 The x-coordinate of the second control point. # @param [Integer] y1 The y-coordinate of the second control point. # @param [Integer] stroke_color The line color to use for this rectangle. # @param [Integer] fill_color The fill color to use for this rectangle. # @return [ChunkyPNG::Canvas] Itself, with the rectangle drawn. def rect(x0, y0, x1, y1, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT) stroke_color = ChunkyPNG::Color.parse(stroke_color) fill_color = ChunkyPNG::Color.parse(fill_color) # Fill unless fill_color == ChunkyPNG::Color::TRANSPARENT [x0, x1].min.upto([x0, x1].max) do |x| [y0, y1].min.upto([y0, y1].max) do |y| compose_pixel(x, y, fill_color) end end end # Stroke line(x0, y0, x0, y1, stroke_color, false) line(x0, y1, x1, y1, stroke_color, false) line(x1, y1, x1, y0, stroke_color, false) line(x1, y0, x0, y0, stroke_color, false) self end # Draws a circle on the canvas. # # @param [Integer] x0 The x-coordinate of the center of the circle. # @param [Integer] y0 The y-coordinate of the center of the circle. # @param [Integer] radius The radius of the circle from the center point. # @param [Integer] stroke_color The color to use for the line. # @param [Integer] fill_color The color to use that fills the circle. # @return [ChunkyPNG::Canvas] Itself, with the circle drawn. def circle(x0, y0, radius, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT) stroke_color = ChunkyPNG::Color.parse(stroke_color) fill_color = ChunkyPNG::Color.parse(fill_color) f = 1 - radius dd_f_x = 1 dd_f_y = -2 * radius x = 0 y = radius compose_pixel(x0, y0 + radius, stroke_color) compose_pixel(x0, y0 - radius, stroke_color) compose_pixel(x0 + radius, y0, stroke_color) compose_pixel(x0 - radius, y0, stroke_color) lines = [radius - 1] unless fill_color == ChunkyPNG::Color::TRANSPARENT while x < y if f >= 0 y -= 1 dd_f_y += 2 f += dd_f_y end x += 1 dd_f_x += 2 f += dd_f_x unless fill_color == ChunkyPNG::Color::TRANSPARENT lines[y] = lines[y] ? [lines[y], x - 1].min : x - 1 lines[x] = lines[x] ? [lines[x], y - 1].min : y - 1 end compose_pixel(x0 + x, y0 + y, stroke_color) compose_pixel(x0 - x, y0 + y, stroke_color) compose_pixel(x0 + x, y0 - y, stroke_color) compose_pixel(x0 - x, y0 - y, stroke_color) unless x == y compose_pixel(x0 + y, y0 + x, stroke_color) compose_pixel(x0 - y, y0 + x, stroke_color) compose_pixel(x0 + y, y0 - x, stroke_color) compose_pixel(x0 - y, y0 - x, stroke_color) end end unless fill_color == ChunkyPNG::Color::TRANSPARENT lines.each_with_index do |length, y_offset| if length > 0 line(x0 - length, y0 - y_offset, x0 + length, y0 - y_offset, fill_color) end if length > 0 && y_offset > 0 line(x0 - length, y0 + y_offset, x0 + length, y0 + y_offset, fill_color) end end end self end private # Calculates the binomial coefficient for n over k. # # @param [Integer] n first parameter in coeffient (the number on top when # looking at the mathematic formula) # @param [Integer] k k-element, second parameter in coeffient (the number # on the bottom when looking at the mathematic formula) # @return [Integer] The binomial coeffcient of (n,k) def binomial_coefficient(n, k) return 1 if n == k || k == 0 return n if k == 1 return -1 if n < k # calculate factorials fact_n = (2..n).inject(1) { |carry, i| carry * i } fact_k = (2..k).inject(1) { |carry, i| carry * i } fact_n_sub_k = (2..(n - k)).inject(1) { |carry, i| carry * i } fact_n / (fact_k * fact_n_sub_k) end end end end chunky_png-1.3.15/lib/chunky_png/canvas/png_decoding.rb0000644000175000017500000006223213766004353022367 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG class Canvas # The PNGDecoding contains methods for decoding PNG datastreams to create a # Canvas object. The datastream can be provided as filename, string or IO # stream. # # Overview of the decoding process: # # * The optional PLTE and tRNS chunk are decoded for the color palette of # the original image. # * The contents of the IDAT chunks is combined, and uncompressed using # Inflate decompression to the image pixelstream. # * Based on the color mode, width and height of the original image, which # is read from the PNG header (IHDR chunk), the amount of bytes # per line is determined. # * For every line of pixels in the encoded image, the original byte values # are restored by unapplying the milter method for that line. # * The read bytes are unfiltered given by the filter function specified by # the first byte of the line. # * The unfiltered pixelstream are is into colored pixels, using the color mode. # * All lines combined to form the original image. # # For interlaced images, the original image was split into 7 subimages. # These images get decoded just like the process above (from step 3), and get # combined to form the original images. # # @see ChunkyPNG::Canvas::PNGEncoding # @see https://www.w3.org/TR/PNG/ The W3C PNG format specification module PNGDecoding # Decodes a Canvas from a PNG encoded string. # @param [String] str The string to read from. # @return [ChunkyPNG::Canvas] The canvas decoded from the PNG encoded string. def from_blob(str) from_datastream(ChunkyPNG::Datastream.from_blob(str)) end alias from_string from_blob # Decodes a Canvas from a PNG encoded file. # @param [String] filename The file to read from. # @return [ChunkyPNG::Canvas] The canvas decoded from the PNG file. def from_file(filename) from_datastream(ChunkyPNG::Datastream.from_file(filename)) end # Decodes a Canvas from a PNG encoded stream. # @param [IO, #read] io The stream to read from. # @return [ChunkyPNG::Canvas] The canvas decoded from the PNG stream. def from_io(io) from_datastream(ChunkyPNG::Datastream.from_io(io)) end alias from_stream from_io # Decodes the Canvas from a PNG datastream instance. # @param [ChunkyPNG::Datastream] ds The datastream to decode. # @return [ChunkyPNG::Canvas] The canvas decoded from the PNG datastream. def from_datastream(ds) width = ds.header_chunk.width height = ds.header_chunk.height color_mode = ds.header_chunk.color interlace = ds.header_chunk.interlace depth = ds.header_chunk.depth if width == 0 || height == 0 raise ExpectationFailed, "Invalid image size, width: #{width}, height: #{height}" end decoding_palette, transparent_color = nil, nil case color_mode when ChunkyPNG::COLOR_INDEXED decoding_palette = ChunkyPNG::Palette.from_chunks(ds.palette_chunk, ds.transparency_chunk) when ChunkyPNG::COLOR_TRUECOLOR transparent_color = ds.transparency_chunk.truecolor_entry(depth) if ds.transparency_chunk when ChunkyPNG::COLOR_GRAYSCALE transparent_color = ds.transparency_chunk.grayscale_entry(depth) if ds.transparency_chunk end decode_png_pixelstream(ds.imagedata, width, height, color_mode, depth, interlace, decoding_palette, transparent_color) end # Decodes a canvas from a PNG encoded pixelstream, using a given width, height, # color mode and interlacing mode. # @param [String] stream The pixelstream to read from. # @param [Integer] width The width of the image. # @param [Integer] height The height of the image. # @param [Integer] color_mode The color mode of the encoded pixelstream. # @param [Integer] depth The bit depth of the pixel samples. # @param [Integer] interlace The interlace method of the encoded pixelstream. # @param [ChunkyPNG::Palette] decoding_palette The palette to use to decode colors. # @param [Integer] transparent_color The color that should be considered fully transparent. # @return [ChunkyPNG::Canvas] The decoded Canvas instance. def decode_png_pixelstream(stream, width, height, color_mode, depth, interlace, decoding_palette, transparent_color) raise ChunkyPNG::ExpectationFailed, "This palette is not suitable for decoding!" if decoding_palette && !decoding_palette.can_decode? image = case interlace when ChunkyPNG::INTERLACING_NONE then decode_png_without_interlacing(stream, width, height, color_mode, depth, decoding_palette) when ChunkyPNG::INTERLACING_ADAM7 then decode_png_with_adam7_interlacing(stream, width, height, color_mode, depth, decoding_palette) else raise ChunkyPNG::NotSupported, "Don't know how the handle interlacing method #{interlace}!" end image.pixels.map! { |c| c == transparent_color ? ChunkyPNG::Color::TRANSPARENT : c } if transparent_color image end protected # Decodes a canvas from a non-interlaced PNG encoded pixelstream, using a # given width, height and color mode. # @param stream (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param width (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param height (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param color_mode (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param depth (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param [ChunkyPNG::Palette] decoding_palette The palette to use to decode colors. # @return (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) def decode_png_without_interlacing(stream, width, height, color_mode, depth, decoding_palette) decode_png_image_pass(stream, width, height, color_mode, depth, 0, decoding_palette) end # Decodes a canvas from a Adam 7 interlaced PNG encoded pixelstream, using a # given width, height and color mode. # @param stream (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param width (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param height (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param color_mode (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param depth (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param [ChunkyPNG::Palette] decoding_palette The palette to use to decode colors. # @return (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) def decode_png_with_adam7_interlacing(stream, width, height, color_mode, depth, decoding_palette) canvas = new(width, height) start_pos = 0 for pass in 0...7 sm_width, sm_height = adam7_pass_size(pass, width, height) sm = decode_png_image_pass(stream, sm_width, sm_height, color_mode, depth, start_pos, decoding_palette) adam7_merge_pass(pass, canvas, sm) start_pos += ChunkyPNG::Color.pass_bytesize(color_mode, depth, sm_width, sm_height) end canvas end # Extract 4 consecutive bits from a byte. # @param [Integer] byte The byte (0..255) value to extract a 4 bit value from. # @param [Integer] index The index within the byte. This should be either 0 or 2; # the value will be modded by 2 to enforce this. # @return [Integer] The extracted 4bit value (0..15) def decode_png_extract_4bit_value(byte, index) index & 0x01 == 0 ? ((byte & 0xf0) >> 4) : (byte & 0x0f) end # Extract 2 consecutive bits from a byte. # @param [Integer] byte The byte (0..255) value to extract a 2 bit value from. # @param [Integer] index The index within the byte. This should be either 0, 1, 2, or 3; # the value will be modded by 4 to enforce this. # @return [Integer] The extracted 2 bit value (0..3) def decode_png_extract_2bit_value(byte, index) bitshift = 6 - ((index & 0x03) << 1) (byte & (0x03 << bitshift)) >> bitshift end # Extract a bit from a byte on a given index. # @param [Integer] byte The byte (0..255) value to extract a bit from. # @param [Integer] index The index within the byte. This should be 0..7; # the value will be modded by 8 to enforce this. # @return [Integer] Either 1 or 0. def decode_png_extract_1bit_value(byte, index) bitshift = 7 - (index & 0x07) (byte & (0x01 << bitshift)) >> bitshift end # Resamples a 16 bit value to an 8 bit value. This will discard some color information. # @param [Integer] value The 16 bit value to resample. # @return [Integer] The 8 bit resampled value def decode_png_resample_16bit_value(value) value >> 8 end # No-op - available for completeness sake only # @param [Integer] value The 8 bit value to resample. # @return [Integer] The 8 bit resampled value def decode_png_resample_8bit_value(value) value end # Resamples a 4 bit value to an 8 bit value. # @param [Integer] value The 4 bit value to resample. # @return [Integer] The 8 bit resampled value. def decode_png_resample_4bit_value(value) value << 4 | value end # Resamples a 2 bit value to an 8 bit value. # @param [Integer] value The 2 bit value to resample. # @return [Integer] The 8 bit resampled value. def decode_png_resample_2bit_value(value) value << 6 | value << 4 | value << 2 | value end # Resamples a 1 bit value to an 8 bit value. # @param [Integer] value The 1 bit value to resample. # @return [Integer] The 8 bit resampled value def decode_png_resample_1bit_value(value) value == 0x01 ? 0xff : 0x00 end # Decodes a scanline of a 1-bit, indexed image into a row of pixels. # @param [String] stream The stream to decode from. # @param [Integer] pos The position in the stream on which the scanline starts (including the filter byte). # @param [Integer] width The width in pixels of the scanline. # @param [ChunkyPNG::Palette] decoding_palette The palette to use to decode colors. # @return [Array] An array of decoded pixels. def decode_png_pixels_from_scanline_indexed_1bit(stream, pos, width, decoding_palette) (0...width).map do |index| palette_pos = decode_png_extract_1bit_value(stream.getbyte(pos + 1 + (index >> 3)), index) decoding_palette[palette_pos] end end # Decodes a scanline of a 2-bit, indexed image into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_indexed_2bit(stream, pos, width, decoding_palette) (0...width).map do |index| palette_pos = decode_png_extract_2bit_value(stream.getbyte(pos + 1 + (index >> 2)), index) decoding_palette[palette_pos] end end # Decodes a scanline of a 4-bit, indexed image into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_indexed_4bit(stream, pos, width, decoding_palette) (0...width).map do |index| palette_pos = decode_png_extract_4bit_value(stream.getbyte(pos + 1 + (index >> 1)), index) decoding_palette[palette_pos] end end # Decodes a scanline of a 8-bit, indexed image into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_indexed_8bit(stream, pos, width, decoding_palette) (1..width).map { |i| decoding_palette[stream.getbyte(pos + i)] } end # Decodes a scanline of an 8-bit, true color image with transparency into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_truecolor_alpha_8bit(stream, pos, width, _decoding_palette) stream.unpack("@#{pos + 1}N#{width}") end # Decodes a scanline of a 16-bit, true color image with transparency into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_truecolor_alpha_16bit(stream, pos, width, _decoding_palette) pixels = [] stream.unpack("@#{pos + 1}n#{width * 4}").each_slice(4) do |r, g, b, a| pixels << ChunkyPNG::Color.rgba( decode_png_resample_16bit_value(r), decode_png_resample_16bit_value(g), decode_png_resample_16bit_value(b), decode_png_resample_16bit_value(a), ) end pixels end # Decodes a scanline of an 8-bit, true color image into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_truecolor_8bit(stream, pos, width, _decoding_palette) stream.unpack("@#{pos + 1}#{"NX" * width}").map { |c| c | 0x000000ff } end # Decodes a scanline of a 16-bit, true color image into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_truecolor_16bit(stream, pos, width, _decoding_palette) pixels = [] stream.unpack("@#{pos + 1}n#{width * 3}").each_slice(3) do |r, g, b| pixels << ChunkyPNG::Color.rgb(decode_png_resample_16bit_value(r), decode_png_resample_16bit_value(g), decode_png_resample_16bit_value(b)) end pixels end # Decodes a scanline of an 8-bit, grayscale image with transparency into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_grayscale_alpha_8bit(stream, pos, width, _decoding_palette) (0...width).map { |i| ChunkyPNG::Color.grayscale_alpha(stream.getbyte(pos + (i * 2) + 1), stream.getbyte(pos + (i * 2) + 2)) } end # Decodes a scanline of a 16-bit, grayscale image with transparency into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_grayscale_alpha_16bit(stream, pos, width, _decoding_palette) pixels = [] stream.unpack("@#{pos + 1}n#{width * 2}").each_slice(2) do |g, a| pixels << ChunkyPNG::Color.grayscale_alpha(decode_png_resample_16bit_value(g), decode_png_resample_16bit_value(a)) end pixels end # Decodes a scanline of a 1-bit, grayscale image into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_grayscale_1bit(stream, pos, width, _decoding_palette) (0...width).map do |index| value = decode_png_extract_1bit_value(stream.getbyte(pos + 1 + (index >> 3)), index) value == 1 ? ChunkyPNG::Color::WHITE : ChunkyPNG::Color::BLACK end end # Decodes a scanline of a 2-bit, grayscale image into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_grayscale_2bit(stream, pos, width, _decoding_palette) (0...width).map do |index| value = decode_png_extract_2bit_value(stream.getbyte(pos + 1 + (index >> 2)), index) ChunkyPNG::Color.grayscale(decode_png_resample_2bit_value(value)) end end # Decodes a scanline of a 4-bit, grayscale image into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_grayscale_4bit(stream, pos, width, _decoding_palette) (0...width).map do |index| value = decode_png_extract_4bit_value(stream.getbyte(pos + 1 + (index >> 1)), index) ChunkyPNG::Color.grayscale(decode_png_resample_4bit_value(value)) end end # Decodes a scanline of an 8-bit, grayscale image into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_grayscale_8bit(stream, pos, width, _decoding_palette) (1..width).map { |i| ChunkyPNG::Color.grayscale(stream.getbyte(pos + i)) } end # Decodes a scanline of a 16-bit, grayscale image into a row of pixels. # @params (see #decode_png_pixels_from_scanline_indexed_1bit) # @return (see #decode_png_pixels_from_scanline_indexed_1bit) def decode_png_pixels_from_scanline_grayscale_16bit(stream, pos, width, _decoding_palette) values = stream.unpack("@#{pos + 1}n#{width}") values.map { |value| ChunkyPNG::Color.grayscale(decode_png_resample_16bit_value(value)) } end # Returns the method name to use to decode scanlines into pixels. # @param [Integer] color_mode The color mode of the image. # @param [Integer] depth The bit depth of the image. # @return [Symbol] The method name to use for decoding, to be called on the canvas class. # @raise [ChunkyPNG::NotSupported] when the color_mode and/or bit depth is not supported. def decode_png_pixels_from_scanline_method(color_mode, depth) decoder_method = case color_mode when ChunkyPNG::COLOR_TRUECOLOR then :"decode_png_pixels_from_scanline_truecolor_#{depth}bit" when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then :"decode_png_pixels_from_scanline_truecolor_alpha_#{depth}bit" when ChunkyPNG::COLOR_INDEXED then :"decode_png_pixels_from_scanline_indexed_#{depth}bit" when ChunkyPNG::COLOR_GRAYSCALE then :"decode_png_pixels_from_scanline_grayscale_#{depth}bit" when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then :"decode_png_pixels_from_scanline_grayscale_alpha_#{depth}bit" end raise ChunkyPNG::NotSupported, "No decoder found for color mode #{color_mode} and #{depth}-bit depth!" unless respond_to?(decoder_method, true) decoder_method end # Decodes a single PNG image pass width a given width, height and color # mode, to a Canvas, starting at the given position in the stream. # # A non-interlaced image only consists of one pass, while an Adam7 # image consists of 7 passes that must be combined after decoding. # # @param stream (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param width (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param height (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param color_mode (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) # @param [Integer] start_pos The position in the pixel stream to start reading. # @param [ChunkyPNG::Palette] decoding_palette The palette to use to decode colors. # @return (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream) def decode_png_image_pass(stream, width, height, color_mode, depth, start_pos, decoding_palette) pixels = [] if width > 0 && height > 0 stream << ChunkyPNG::EXTRA_BYTE if color_mode == ChunkyPNG::COLOR_TRUECOLOR pixel_decoder = decode_png_pixels_from_scanline_method(color_mode, depth) line_length = ChunkyPNG::Color.scanline_bytesize(color_mode, depth, width) pixel_size = ChunkyPNG::Color.pixel_bytesize(color_mode, depth) raise ChunkyPNG::ExpectationFailed, "Invalid stream length!" unless stream.bytesize - start_pos >= ChunkyPNG::Color.pass_bytesize(color_mode, depth, width, height) pos, prev_pos = start_pos, nil for _ in 0...height do decode_png_str_scanline(stream, pos, prev_pos, line_length, pixel_size) pixels.concat(send(pixel_decoder, stream, pos, width, decoding_palette)) prev_pos = pos pos += line_length + 1 end end new(width, height, pixels) end # Decodes a scanline if it was encoded using filtering. # # It will extract the filtering method from the first byte of the scanline, and uses the # method to change the subsequent bytes to unfiltered values. This will modify the pixelstream. # # The bytes of the scanline can then be used to construct pixels, based on the color mode.. # # @param [String] stream The pixelstream to undo the filtering in. # @param [Integer] pos The starting position of the scanline to decode. # @param [Integer, nil] prev_pos The starting position of the previously decoded scanline, or nil # if this is the first scanline of the image. # @param [Integer] line_length The number of bytes in the scanline, discounting the filter method byte. # @param [Integer] pixel_size The number of bytes used per pixel, based on the color mode. # @return [void] def decode_png_str_scanline(stream, pos, prev_pos, line_length, pixel_size) case stream.getbyte(pos) when ChunkyPNG::FILTER_NONE then # rubocop:disable Lint/EmptyWhen # no-op when ChunkyPNG::FILTER_SUB then decode_png_str_scanline_sub(stream, pos, prev_pos, line_length, pixel_size) when ChunkyPNG::FILTER_UP then decode_png_str_scanline_up(stream, pos, prev_pos, line_length, pixel_size) when ChunkyPNG::FILTER_AVERAGE then decode_png_str_scanline_average(stream, pos, prev_pos, line_length, pixel_size) when ChunkyPNG::FILTER_PAETH then decode_png_str_scanline_paeth(stream, pos, prev_pos, line_length, pixel_size) else raise ChunkyPNG::NotSupported, "Unknown filter type: #{stream.getbyte(pos)}!" end end # Decodes a scanline that wasn't encoded using filtering. This is a no-op. # @params (see #decode_png_str_scanline) # @return [void] def decode_png_str_scanline_sub_none(stream, pos, prev_pos, line_length, pixel_size) # noop - this method shouldn't get called. end # Decodes a scanline in a pixelstream that was encoded using SUB filtering. # This will change the pixelstream to have unfiltered values. # @params (see #decode_png_str_scanline) # @return [void] def decode_png_str_scanline_sub(stream, pos, prev_pos, line_length, pixel_size) for i in 1..line_length do stream.setbyte(pos + i, (stream.getbyte(pos + i) + (i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0)) & 0xff) end end # Decodes a scanline in a pixelstream that was encoded using UP filtering. # This will change the pixelstream to have unfiltered values. # @params (see #decode_png_str_scanline) # @return [void] def decode_png_str_scanline_up(stream, pos, prev_pos, line_length, pixel_size) for i in 1..line_length do up = prev_pos ? stream.getbyte(prev_pos + i) : 0 stream.setbyte(pos + i, (stream.getbyte(pos + i) + up) & 0xff) end end # Decodes a scanline in a pixelstream that was encoded using AVERAGE filtering. # This will change the pixelstream to have unfiltered values. # @params (see #decode_png_str_scanline) # @return [void] def decode_png_str_scanline_average(stream, pos, prev_pos, line_length, pixel_size) for i in 1..line_length do a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0 b = prev_pos ? stream.getbyte(prev_pos + i) : 0 stream.setbyte(pos + i, (stream.getbyte(pos + i) + ((a + b) >> 1)) & 0xff) end end # Decodes a scanline in a pixelstream that was encoded using PAETH filtering. # This will change the pixelstream to have unfiltered values. # @params (see #decode_png_str_scanline) # @return [void] def decode_png_str_scanline_paeth(stream, pos, prev_pos, line_length, pixel_size) for i in 1..line_length do cur_pos = pos + i a = i > pixel_size ? stream.getbyte(cur_pos - pixel_size) : 0 b = prev_pos ? stream.getbyte(prev_pos + i) : 0 c = prev_pos && i > pixel_size ? stream.getbyte(prev_pos + i - pixel_size) : 0 p = a + b - c pa = (p - a).abs pb = (p - b).abs pc = (p - c).abs pr = if pa <= pb pa <= pc ? a : c else pb <= pc ? b : c end stream.setbyte(cur_pos, (stream.getbyte(cur_pos) + pr) & 0xff) end end end end end chunky_png-1.3.15/lib/chunky_png/image.rb0000644000175000017500000000540113766004353017551 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG # ChunkyPNG::Image is an extension of the {ChunkyPNG::Canvas} class, that # also includes support for metadata. # # @see ChunkyPNG::Canvas class Image < Canvas # The minimum size of bytes the value of a metadata field should be before compression # is enabled for the chunk. METADATA_COMPRESSION_TRESHOLD = 300 # @return [Hash] The hash of metadata fields for this PNG image. attr_accessor :metadata # Initializes a new ChunkyPNG::Image instance. # @param [Integer] width The width of the new image. # @param [Integer] height The height of the new image. # @param [Integer] bg_color The background color of the new image. # @param [Hash] metadata A hash of metadata fields and values for this image. # @see ChunkyPNG::Canvas#initialize def initialize(width, height, bg_color = ChunkyPNG::Color::TRANSPARENT, metadata = {}) super(width, height, bg_color) @metadata = metadata end # Initializes a copy of another ChunkyPNG::Image instance. # # @param [ChunkyPNG::Image] other The other image to copy. def initialize_copy(other) super(other) @metadata = other.metadata end # Returns the metadata for this image as PNG chunks. # # Chunks will either be of the {ChunkyPNG::Chunk::Text} type for small # values (in bytes), or of the {ChunkyPNG::Chunk::CompressedText} type # for values that are larger in size. # # @return [Array] An array of metadata chunks. # @see ChunkyPNG::Image::METADATA_COMPRESSION_TRESHOLD def metadata_chunks metadata.map do |key, value| if value.length >= METADATA_COMPRESSION_TRESHOLD ChunkyPNG::Chunk::CompressedText.new(key, value) else ChunkyPNG::Chunk::Text.new(key, value) end end end # Encodes the image to a PNG datastream for saving to disk or writing to an IO stream. # # Besides encoding the canvas, it will also encode the metadata fields to text chunks. # # @param [Hash] constraints The constraints to use when encoding the canvas. # @return [ChunkyPNG::Datastream] The datastream that contains this image. # @see ChunkyPNG::Canvas::PNGEncoding#to_datastream # @see #metadata_chunks def to_datastream(constraints = {}) ds = super(constraints) ds.other_chunks += metadata_chunks ds end # Reads a ChunkyPNG::Image instance from a data stream. # # Besides decoding the canvas, this will also read the metadata fields # from the datastream. # # @param [ChunkyPNG::Datastream] ds The datastream to read from. def self.from_datastream(ds) image = super(ds) image.metadata = ds.metadata image end end end chunky_png-1.3.15/lib/chunky_png/vector.rb0000644000175000017500000001535313766004353020000 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG # Factory method for {ChunkyPNG::Vector} instances. # # @overload Vector(x0, y0, x1, y1, x2, y2, ...) # Creates a vector by parsing two subsequent values in the argument list # as x- and y-coordinate of a point. # @return [ChunkyPNG::Vector] The instantiated vector. # @overload Vector(string) # Creates a vector by parsing coordinates from the input string. # @return [ChunkyPNG::Vector] The instantiated vector. # @overload Vector(pointlike, pointlike, pointlike, ...) # Creates a vector by converting every argument to a point using {ChunkyPNG.Point}. # @return [ChunkyPNG::Vector] The instantiated vector. # # @return [ChunkyPNG::Vector] The vector created by this factory method. # @raise [ArgumentError] If the given arguments could not be understood as a vector. # @see ChunkyPNG::Vector def self.Vector(*args) return args.first if args.length == 1 && args.first.is_a?(ChunkyPNG::Vector) if args.length == 1 && args.first.respond_to?(:scan) ChunkyPNG::Vector.new(ChunkyPNG::Vector.multiple_from_string(args.first)) # e.g. ['1,1 2,2 3,3'] else ChunkyPNG::Vector.new(ChunkyPNG::Vector.multiple_from_array(args)) # e.g. [[1,1], [2,2], [3,3]] or [1,1,2,2,3,3] end end # Class that represents a vector of points, i.e. a list of {ChunkyPNG::Point} instances. # # Vectors can be created quite flexibly. See the {ChunkyPNG.Vector} factory methods for # more information on how to construct vectors. class Vector include Enumerable # @return [Array] The array that holds all the points in this vector. attr_reader :points # Initializes a vector based on a list of Point instances. # # You usually do not want to use this method directly, but call {ChunkyPNG.Vector} instead. # # @param [Array] points # @see ChunkyPNG.Vector def initialize(points = []) @points = points end # Iterates over all the edges in this vector. # # An edge is a combination of two subsequent points in the vector. Together, they will form # a path from the first point to the last point # # @param [true, false] close Whether to close the path, i.e. return an edge that connects the last # point in the vector back to the first point. # @return [void] # @raise [ChunkyPNG::ExpectationFailed] if the vector contains less than two points. # @see #edges def each_edge(close = true) raise ChunkyPNG::ExpectationFailed, "Not enough points in this path to draw an edge!" if length < 2 points.each_cons(2) { |a, b| yield(a, b) } yield(points.last, points.first) if close end # Returns the point with the given indexof this vector. # @param [Integer] index The 0-based index of the point in this vector. # @return [ChunkyPNG::Point] The point instance. def [](index) points[index] end # Returns an enumerator that will iterate over all the edges in this vector. # @param (see #each_edge) # @return [Enumerator] The enumerator that iterates over the edges. # @raise [ChunkyPNG::ExpectationFailed] if the vector contains less than two points. # @see #each_edge def edges(close = true) to_enum(:each_edge, close) end # Returns the number of points in this vector. # @return [Integer] The length of the points array. def length points.length end # Iterates over all the points in this vector # @yield [ChunkyPNG::Point] The points in the correct order. # @return [void] def each(&block) points.each(&block) end # Comparison between two vectors for quality. # @param [ChunkyPNG::Vector] other The vector to compare with. # @return [true, false] true if the list of points are identical def eql?(other) other.points == points end alias == eql? # Returns the range in x-coordinates for all the points in this vector. # @return [Range] The (inclusive) range of x-coordinates. def x_range Range.new(*points.map { |p| p.x }.minmax) end # Returns the range in y-coordinates for all the points in this vector. # @return [Range] The (inclusive) range of y-coordinates. def y_range Range.new(*points.map { |p| p.y }.minmax) end # Finds the lowest x-coordinate in this vector. # @return [Integer] The lowest x-coordinate of all the points in the vector. def min_x x_range.first end # Finds the highest x-coordinate in this vector. # @return [Integer] The highest x-coordinate of all the points in the vector. def max_x x_range.last end # Finds the lowest y-coordinate in this vector. # @return [Integer] The lowest y-coordinate of all the points in the vector. def min_y y_range.first end # Finds the highest y-coordinate in this vector. # @return [Integer] The highest y-coordinate of all the points in the vector. def max_y y_range.last end # Returns the offset from (0,0) of the minimal bounding box of all the # points in this vector # @return [ChunkyPNG::Point] A point that describes the top left corner if a # minimal bounding box would be drawn around all the points in the vector. def offset ChunkyPNG::Point.new(min_x, min_y) end # Returns the width of the minimal bounding box of all the points in this vector. # @return [Integer] The x-distance between the points that are farthest from each other. def width 1 + (max_x - min_x) end # Returns the height of the minimal bounding box of all the points in this vector. # @return [Integer] The y-distance between the points that are farthest from each other. def height 1 + (max_y - min_y) end # Returns the dimension of the minimal bounding rectangle of the points in this vector. # @return [ChunkyPNG::Dimension] The dimension instance with the width and height def dimension ChunkyPNG::Dimension.new(width, height) end # @return [Array] The list of points interpreted from the input array. def self.multiple_from_array(source) return [] if source.empty? if source.first.is_a?(Numeric) || source.first =~ /^\d+$/ raise ArgumentError, "The points array is expected to have an even number of items!" if source.length % 2 != 0 points = [] source.each_slice(2) { |x, y| points << ChunkyPNG::Point.new(x, y) } return points else source.map { |p| ChunkyPNG::Point(p) } end end # @return [Array] The list of points parsed from the string. def self.multiple_from_string(source_str) multiple_from_array(source_str.scan(/[\(\[\{]?(\d+)\s*[,x]?\s*(\d+)[\)\]\}]?/)) end end end chunky_png-1.3.15/lib/chunky_png/chunk.rb0000644000175000017500000004437513766004353017614 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG # 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 chunk 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. # # @see ChunkyPNG::Datastream module Chunk # Reads a chunk from an IO stream. # # @param io [IO, #read] The IO stream to read from. # @return [ChunkyPNG::Chung::Base] The loaded chunk instance. def self.read(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) CHUNK_TYPES.fetch(type, Generic).read(type, content) end # Reads an exact number of bytes from an IO stream. # @param io [IO, #read] The IO stream to read from. # @param length [Integer] The IO exact number of bytes to read. # @return [String] A binary string of exactly length bytes. # @raise [ChunkyPNG::ExpectationFailed] If not exactly length # bytes could be read from the IO stream. def self.read_bytes(io, length) data = io.read(length) raise ExpectationFailed, "Couldn't read #{length} bytes from IO stream." if data.nil? || data.bytesize != length data end # Verifies the CRC of a chunk. # @param type [String] The chunk's type. # @param content [String] The chunk's content. # @param found_crc [Integer] The chunk's found CRC value. # @raise [ChunkyPNG::CRCMismatch] 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 ChunkyPNG::CRCMismatch, "Chuck CRC mismatch!" if found_crc != expected_crc end # The base chunk class is the superclass for every chunk type. It contains # methods to write the chunk to an output stream. # # A subclass should implement the +content+ method, which gets called when # the chunk gets written to a PNG datastream # # @abstract class Base # The four-character type indicator for the chunk. This field is used to # find the correct class for a chunk when it is loaded from a PNG stream. # @return [String] attr_accessor :type # Initializes the chunk instance. # @param type [String] The four character chunk type indicator. # @param attributes [Hash] A hash of attributes to set on this chunk. def initialize(type, attributes = {}) self.type = type attributes.each { |k, v| send("#{k}=", v) } end # Writes the chunk to the IO stream, using the provided content. # The checksum will be calculated and appended to the stream. # @param io [IO] The IO stream to write to. # @param content [String] The content for this chunk. def write_with_crc(io, content) io << [content.length].pack("N") << type << content io << [Zlib.crc32(content, Zlib.crc32(type))].pack("N") end # Writes the chunk to the IO stream. # # It will call the +content+ method to get the content for this chunk, # and will calculate and append the checksum automatically. # @param io [IO] The IO stream to write to. def write(io) write_with_crc(io, content || "") end end # The Generic chunk type will read the content from the chunk as it, # and will write it back as it was read. class Generic < Base # The attribute to store the content from the chunk, which gets # written by the +write+ method. attr_accessor :content def initialize(type, content = "") super(type, content: content) end # Creates an instance, given the chunk's type and content. # @param type [String] The four character chunk type indicator. # @param content [String] The content read from the chunk. # @return [ChunkyPNG::Chunk::Generic] The new chunk instance. def self.read(type, content) new(type, content) end end # The header (IHDR) chunk is the first chunk of every PNG image, and # contains information about the image: i.e. its width, height, color # depth, color mode, compression method, filtering method and interlace # method. # # ChunkyPNG supports all values for these variables that are defined in the # PNG spec, except for color depth: Only 8-bit depth images are supported. # Note that it is still possible to access the chunk for such an image, but # ChunkyPNG will raise an exception if you try to access the pixel data. # # @see https://www.w3.org/TR/PNG/#11IHDR 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 # Reads the 13 bytes of content from the header chunk to set the image # attributes. # @param type [String] The four character chunk type indicator (= "IHDR"). # @param content [String] The 13 bytes of content read from the chunk. # @return [ChunkyPNG::Chunk::End] The new Header chunk instance with the # variables set to the values according to the content. def self.read(type, content) fields = content.unpack("NNC5") new( width: fields[0], height: fields[1], depth: fields[2], color: fields[3], compression: fields[4], filtering: fields[5], interlace: fields[6] ) end # Returns the content for this chunk when it gets written to a file, by # packing the image information variables into the correct format. # @return [String] The 13-byte content for the header chunk. def content [ width, height, depth, color, compression, filtering, interlace, ].pack("NNC5") end end # The End (IEND) chunk indicates the last chunk of a PNG stream. It does # not contain any data. # # @see https://www.w3.org/TR/PNG/#11IEND class End < Base def initialize super("IEND") end # Reads the END chunk. It will check if the content is empty. # @param type [String] The four character chunk type indicator (= # "IEND"). # @param content [String] The content read from the chunk. Should be # empty. # @return [ChunkyPNG::Chunk::End] The new End chunk instance. # @raise [ChunkyPNG::ExpectationFailed] Raises an exception if the content was not empty. def self.read(type, content) raise ExpectationFailed, "The IEND chunk should be empty!" if content.bytesize > 0 new end # Returns an empty string, because this chunk should always be empty. # @return [""] An empty string. def content "".b end end # The Palette (PLTE) chunk contains the image's palette, i.e. the # 8-bit RGB colors this image is using. # # @see https://www.w3.org/TR/PNG/#11PLTE # @see ChunkyPNG::Chunk::Transparency # @see ChunkyPNG::Palette class Palette < Generic end # A transparency (tRNS) chunk defines the transparency for an image. # # * For indexed images, it contains the alpha channel for the colors # defined in the Palette (PLTE) chunk. # * For grayscale images, it contains the grayscale teint that should be # considered fully transparent. # * For truecolor images, it contains the color that should be considered # fully transparent. # # Images having a color mode that already includes an alpha channel, this # chunk should not be included. # # @see https://www.w3.org/TR/PNG/#11tRNS # @see ChunkyPNG::Chunk::Palette # @see ChunkyPNG::Palette class Transparency < Generic # Returns the alpha channel for the palette of an indexed image. # # This method should only be used for images having color mode # ChunkyPNG::COLOR_INDEXED (3). # # @return [Array] Returns an array of alpha channel values # [0-255]. def palette_alpha_channel content.unpack("C*") end # Returns the truecolor entry to be replaced by transparent pixels, # # This method should only be used for images having color mode # ChunkyPNG::COLOR_TRUECOLOR (2). # # @return [Integer] The color to replace with fully transparent pixels. def truecolor_entry(bit_depth) decode_method_name = :"decode_png_resample_#{bit_depth}bit_value" values = content.unpack("nnn").map { |c| ChunkyPNG::Canvas.send(decode_method_name, c) } ChunkyPNG::Color.rgb(*values) end # Returns the grayscale entry to be replaced by transparent pixels. # # This method should only be used for images having color mode # ChunkyPNG::COLOR_GRAYSCALE (0). # # @return [Integer] The (grayscale) color to replace with fully # transparent pixels. def grayscale_entry(bit_depth) value = ChunkyPNG::Canvas.send(:"decode_png_resample_#{bit_depth}bit_value", content.unpack("n")[0]) ChunkyPNG::Color.grayscale(value) end end # An image data (IDAT) chunk holds (part of) the compressed image pixel data. # # The data of an image can be split over multiple chunks, which will have to be combined # and inflated in order to decode an image. See {{.combine_chunks}} to combine chunks # to decode, and {{.split_in_chunks}} for encoding a pixeldata stream into IDAT chunks. # # @see https://www.w3.org/TR/PNG/#11IDAT class ImageData < Generic # Combines the list of IDAT chunks and inflates their contents to produce the # pixeldata stream for the image. # # @return [String] The combined, inflated pixeldata as binary string def self.combine_chunks(data_chunks) zstream = Zlib::Inflate.new data_chunks.each { |c| zstream << c.content } inflated = zstream.finish zstream.close inflated end # Splits and compresses a pixeldata stream into a list of IDAT chunks. # # @param data [String] The binary string of pixeldata # @param level [Integer] The compression level to use. # @param chunk_size [Integer] The maximum size of a chunk. # @return Array The list of IDAT chunks. def self.split_in_chunks(data, level = Zlib::DEFAULT_COMPRESSION, chunk_size = 2147483647) streamdata = Zlib::Deflate.deflate(data, level) # TODO: Split long streamdata over multiple chunks [ChunkyPNG::Chunk::ImageData.new("IDAT", streamdata)] end end # The Text (tEXt) chunk contains keyword/value metadata about the PNG # stream. In this chunk, the value is stored uncompressed. # # The tEXt chunk only supports Latin-1 encoded textual data. If you need # UTF-8 support, check out the InternationalText chunk type. # # @see https://www.w3.org/TR/PNG/#11tEXt # @see ChunkyPNG::Chunk::CompressedText # @see ChunkyPNG::Chunk::InternationalText 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 # Creates the content to write to the stream, by concatenating the # keyword with the value, joined by a null character. # # @return The content that should be written to the datastream. def content [keyword, value].pack("Z*a*") end end # The CompressedText (zTXt) chunk contains keyword/value metadata about the # PNG stream. In this chunk, the value is compressed using Deflate # compression. # # @see https://www.w3.org/TR/PNG/#11zTXt # @see ChunkyPNG::Chunk::CompressedText # @see ChunkyPNG::Chunk::InternationalText class CompressedText < Base attr_accessor :keyword, :value def initialize(keyword, value) super("zTXt") @keyword, @value = keyword, value end def self.read(type, content) keyword, compression, value = content.unpack("Z*Ca*") raise ChunkyPNG::NotSupported, "Compression method #{compression.inspect} not supported!" unless compression == ChunkyPNG::COMPRESSION_DEFAULT new(keyword, Zlib::Inflate.inflate(value)) end # Creates the content to write to the stream, by concatenating the # keyword with the deflated value, joined by a null character. # # @return The content that should be written to the datastream. def content [ keyword, ChunkyPNG::COMPRESSION_DEFAULT, Zlib::Deflate.deflate(value), ].pack("Z*Ca*") end end # The Physical (pHYs) chunk specifies the intended pixel size or aspect # ratio for display of the image. # # @see https://www.w3.org/TR/PNG/#11pHYs class Physical < Base attr_accessor :ppux, :ppuy, :unit def initialize(ppux, ppuy, unit = :unknown) raise ArgumentError, "unit must be either :meters or :unknown" unless [:meters, :unknown].member?(unit) super("pHYs") @ppux, @ppuy, @unit = ppux, ppuy, unit end def dpix raise ChunkyPNG::UnitsUnknown, "the PNG specifies its physical aspect ratio, but does not specify the units of its pixels' physical dimensions" unless unit == :meters ppux * INCHES_PER_METER end def dpiy raise ChunkyPNG::UnitsUnknown, "the PNG specifies its physical aspect ratio, but does not specify the units of its pixels' physical dimensions" unless unit == :meters ppuy * INCHES_PER_METER end def self.read(type, content) ppux, ppuy, unit = content.unpack("NNC") unit = unit == 1 ? :meters : :unknown new(ppux, ppuy, unit) end # Assembles the content to write to the stream for this chunk. # @return [String] The binary content that should be written to the datastream. def content [ppux, ppuy, unit == :meters ? 1 : 0].pack("NNC") end INCHES_PER_METER = 0.0254 end # The InternationalText (iTXt) chunk contains keyword/value metadata about the PNG # stream, translated to a given locale. # # The metadata in this chunk can be encoded using UTF-8 characters. # Moreover, it is possible to define the language of the metadata, and give # a translation of the keyword name. Finally, it supports bot compressed # and uncompressed values. # # @see https://www.w3.org/TR/PNG/#11iTXt # @see ChunkyPNG::Chunk::Text # @see ChunkyPNG::Chunk::CompressedText class InternationalText < Base attr_accessor :keyword, :text, :language_tag, :translated_keyword, :compressed, :compression def initialize(keyword, text, language_tag = "", translated_keyword = "", compressed = ChunkyPNG::UNCOMPRESSED_CONTENT, compression = ChunkyPNG::COMPRESSION_DEFAULT) super("iTXt") @keyword = keyword @text = text @language_tag = language_tag @translated_keyword = translated_keyword @compressed = compressed @compression = compression end # Reads the iTXt chunk. # @param type [String] The four character chunk type indicator (= "iTXt"). # @param content [String] The content read from the chunk. # @return [ChunkyPNG::Chunk::InternationalText] The new End chunk instance. # @raise [ChunkyPNG::InvalidUTF8] If the chunk contains data that is not UTF8-encoded text. # @raise [ChunkyPNG::NotSupported] If the chunk refers to an unsupported compression method. # Currently uncompressed data and deflate are supported. def self.read(type, content) keyword, compressed, compression, language_tag, translated_keyword, text = content.unpack("Z*CCZ*Z*a*") raise ChunkyPNG::NotSupported, "Compression flag #{compressed.inspect} not supported!" unless compressed == ChunkyPNG::UNCOMPRESSED_CONTENT || compressed == ChunkyPNG::COMPRESSED_CONTENT raise ChunkyPNG::NotSupported, "Compression method #{compression.inspect} not supported!" unless compression == ChunkyPNG::COMPRESSION_DEFAULT text = Zlib::Inflate.inflate(text) if compressed == ChunkyPNG::COMPRESSED_CONTENT text.force_encoding("utf-8") raise ChunkyPNG::InvalidUTF8, "Invalid unicode encountered in iTXt chunk" unless text.valid_encoding? translated_keyword.force_encoding("utf-8") raise ChunkyPNG::InvalidUTF8, "Invalid unicode encountered in iTXt chunk" unless translated_keyword.valid_encoding? new(keyword, text, language_tag, translated_keyword, compressed, compression) end # Assembles the content to write to the stream for this chunk. # @return [String] The binary content that should be written to the datastream. def content text_field = text.encode("utf-8") text_field = compressed == ChunkyPNG::COMPRESSED_CONTENT ? Zlib::Deflate.deflate(text_field) : text_field [keyword, compressed, compression, language_tag, translated_keyword.encode("utf-8"), text_field].pack("Z*CCZ*Z*a*") end end # Maps chunk types to classes, based on the four byte chunk type indicator # at the beginning of a chunk. # # If a chunk type is not specified in this hash, the Generic chunk type # will be used. # # @see ChunkyPNG::Chunk.read CHUNK_TYPES = { "IHDR" => Header, "IEND" => End, "IDAT" => ImageData, "PLTE" => Palette, "tRNS" => Transparency, "tEXt" => Text, "zTXt" => CompressedText, "iTXt" => InternationalText, "pHYs" => Physical, } end end chunky_png-1.3.15/lib/chunky_png/canvas.rb0000644000175000017500000003250013766004353017742 0ustar danieldaniel# frozen-string-literal: true require "chunky_png/canvas/png_encoding" require "chunky_png/canvas/png_decoding" require "chunky_png/canvas/adam7_interlacing" require "chunky_png/canvas/stream_exporting" require "chunky_png/canvas/stream_importing" require "chunky_png/canvas/data_url_exporting" require "chunky_png/canvas/data_url_importing" require "chunky_png/canvas/operations" require "chunky_png/canvas/drawing" require "chunky_png/canvas/resampling" require "chunky_png/canvas/masking" module ChunkyPNG # The ChunkyPNG::Canvas class represents a raster image as a matrix of # pixels. # # This class supports loading a Canvas from a PNG datastream, and creating a # {ChunkyPNG::Datastream PNG datastream} based on this matrix. ChunkyPNG # only supports 8-bit color depth, otherwise all of the PNG format's # variations are supported for both reading and writing. # # This class offers per-pixel access to the matrix by using x,y coordinates. # It uses a palette (see {ChunkyPNG::Palette}) to keep track of the # different colors used in this matrix. # # The pixels in the canvas are stored as 4-byte fixnum, representing 32-bit # RGBA colors (8 bit per channel). The module {ChunkyPNG::Color} is provided # to work more easily with these number as color values. # # The module {ChunkyPNG::Canvas::Operations} is imported for operations on # the whole canvas, like cropping and alpha compositing. Simple drawing # functions are imported from the {ChunkyPNG::Canvas::Drawing} module. class Canvas include PNGEncoding extend PNGDecoding extend Adam7Interlacing include StreamExporting extend StreamImporting include DataUrlExporting extend DataUrlImporting include Operations include Drawing include Resampling include Masking # @return [Integer] The number of columns in this canvas attr_reader :width # @return [Integer] The number of rows in this canvas attr_reader :height # @return [Array] The list of pixels in this canvas. # This array always should have +width * height+ elements. attr_reader :pixels ################################################################# # CONSTRUCTORS ################################################################# # Initializes a new Canvas instance. # # @overload initialize(width, height, background_color) # @param [Integer] width The width in pixels of this canvas # @param [Integer] height The height in pixels of this canvas # @param [Integer, ...] background_color The initial background color of # this canvas. This can be a color value or any value that # {ChunkyPNG::Color#parse} can handle. # # @overload initialize(width, height, initial) # @param [Integer] width The width in pixels of this canvas # @param [Integer] height The height in pixels of this canvas # @param [Array] initial The initial pizel values. Must be an # array with width * height elements. def initialize(width, height, initial = ChunkyPNG::Color::TRANSPARENT) @width, @height = width, height if initial.is_a?(Array) pixel_count = width * height unless initial.length == pixel_count raise ArgumentError, "The initial array should have #{width}x#{height} = #{pixel_count} elements!" end @pixels = initial else @pixels = Array.new(width * height, ChunkyPNG::Color.parse(initial)) end end # Initializes a new Canvas instances when being cloned. # @param [ChunkyPNG::Canvas] other The canvas to duplicate # @return [void] # @private def initialize_copy(other) @width, @height = other.width, other.height @pixels = other.pixels.dup end # Creates a new canvas instance by duplicating another instance. # @param [ChunkyPNG::Canvas] canvas The canvas to duplicate # @return [ChunkyPNG::Canvas] The newly constructed canvas instance. def self.from_canvas(canvas) new(canvas.width, canvas.height, canvas.pixels.dup) end ################################################################# # PROPERTIES ################################################################# # Returns the dimension (width x height) for this canvas. # @return [ChunkyPNG::Dimension] A dimension instance with the width and # height set for this canvas. def dimension ChunkyPNG::Dimension.new(width, height) end # Returns the area of this canvas in number of pixels. # @return [Integer] The number of pixels in this canvas def area pixels.length end # Replaces a single pixel in this canvas. # @param [Integer] x The x-coordinate of the pixel (column) # @param [Integer] y The y-coordinate of the pixel (row) # @param [Integer] color The new color for the provided coordinates. # @return [Integer] The new color value for this pixel, i.e. # color. # @raise [ChunkyPNG::OutOfBounds] when the coordinates are outside of the # image's dimensions. # @see #set_pixel def []=(x, y, color) assert_xy!(x, y) @pixels[y * width + x] = ChunkyPNG::Color.parse(color) end # Replaces a single pixel in this canvas, without bounds checking. # # This method return value and effects are undefined for coordinates # out of bounds of the canvas. # # @param [Integer] x The x-coordinate of the pixel (column) # @param [Integer] y The y-coordinate of the pixel (row) # @param [Integer] color The new color for the provided coordinates. # @return [Integer] The new color value for this pixel, i.e. # color. def set_pixel(x, y, color) @pixels[y * width + x] = color end # Replaces a single pixel in this canvas, with bounds checking. It will do # noting if the provided coordinates are out of bounds. # # @param [Integer] x The x-coordinate of the pixel (column) # @param [Integer] y The y-coordinate of the pixel (row) # @param [Integer] color The new color value for the provided coordinates. # @return [Integer] The new color value for this pixel, i.e. # color, or nil if the coordinates are out of bounds. def set_pixel_if_within_bounds(x, y, color) return unless include_xy?(x, y) @pixels[y * width + x] = color end # Returns a single pixel's color value from this canvas. # @param [Integer] x The x-coordinate of the pixel (column) # @param [Integer] y The y-coordinate of the pixel (row) # @return [Integer] The current color value at the provided coordinates. # @raise [ChunkyPNG::OutOfBounds] when the coordinates are outside of the # image's dimensions. # @see #get_pixel def [](x, y) assert_xy!(x, y) @pixels[y * width + x] end # Returns a single pixel from this canvas, without checking bounds. The # return value for this method is undefined if the coordinates are out of # bounds. # # @param (see #[]) # @return [Integer] The current pixel at the provided coordinates. def get_pixel(x, y) @pixels[y * width + x] end # Returns an extracted row as vector of pixels # @param [Integer] y The 0-based row index # @return [Array] The vector of pixels in the requested row def row(y) assert_y!(y) pixels.slice(y * width, width) end # Returns an extracted column as vector of pixels. # @param [Integer] x The 0-based column index. # @return [Array] The vector of pixels in the requested column. def column(x) assert_x!(x) (0...height).inject([]) { |pixels, y| pixels << get_pixel(x, y) } end # Replaces a row of pixels on this canvas. # @param [Integer] y The 0-based row index. # @param [Array] vector The vector of pixels to replace the row # with. # @return [void] def replace_row!(y, vector) assert_y!(y) && assert_width!(vector.length) pixels[y * width, width] = vector end # Replaces a column of pixels on this canvas. # @param [Integer] x The 0-based column index. # @param [Array] vector The vector of pixels to replace the column # with. # @return [void] def replace_column!(x, vector) assert_x!(x) && assert_height!(vector.length) for y in 0...height do set_pixel(x, y, vector[y]) end end # Checks whether the given coordinates are in the range of the canvas # @param [ChunkyPNG::Point, Array, Hash, String] point_like The point to # check. # @return [true, false] True if the x and y coordinates of the point are # within the limits of this canvas. # @see ChunkyPNG.Point def include_point?(*point_like) dimension.include?(ChunkyPNG::Point(*point_like)) end alias include? include_point? # Checks whether the given x- and y-coordinate are in the range of the # canvas # # @param [Integer] x The x-coordinate of the pixel (column) # @param [Integer] y The y-coordinate of the pixel (row) # @return [true, false] True if the x- and y-coordinate is in the range of # this canvas. def include_xy?(x, y) y >= 0 && y < height && x >= 0 && x < width end # Checks whether the given y-coordinate is in the range of the canvas # @param [Integer] y The y-coordinate of the pixel (row) # @return [true, false] True if the y-coordinate is in the range of this # canvas. def include_y?(y) y >= 0 && y < height end # Checks whether the given x-coordinate is in the range of the canvas # @param [Integer] x The y-coordinate of the pixel (column) # @return [true, false] True if the x-coordinate is in the range of this # canvas. def include_x?(x) x >= 0 && x < width end # Returns the palette used for this canvas. # @return [ChunkyPNG::Palette] A palette which contains all the colors that # are being used for this image. def palette ChunkyPNG::Palette.from_canvas(self) end # Equality check to compare this canvas with other matrices. # @param other The object to compare this Matrix to. # @return [true, false] True if the size and pixel values of the other # canvas are exactly the same as this canvas's size and pixel values. def eql?(other) other.is_a?(self.class) && other.pixels == pixels && other.width == width && other.height == height end alias == eql? ################################################################# # EXPORTING ################################################################# # Creates an ChunkyPNG::Image object from this canvas. # @return [ChunkyPNG::Image] This canvas wrapped in an Image instance. def to_image ChunkyPNG::Image.from_canvas(self) end # Alternative implementation of the inspect method. # @return [String] A nicely formatted string representation of this canvas. # @private def inspect inspected = +"<#{self.class.name} #{width}x#{height} [" for y in 0...height inspected << "\n\t[" << row(y).map { |p| ChunkyPNG::Color.to_hex(p) }.join(" ") << "]" end inspected << "\n]>" end protected # Replaces the image, given a new width, new height, and a new pixel array. def replace_canvas!(new_width, new_height, new_pixels) unless new_pixels.length == new_width * new_height raise ArgumentError, "The provided pixel array should have #{new_width * new_height} items" end @width, @height, @pixels = new_width, new_height, new_pixels self end # Throws an exception if the x-coordinate is out of bounds. def assert_x!(x) unless include_x?(x) raise ChunkyPNG::OutOfBounds, "Column index #{x} out of bounds!" end true end # Throws an exception if the y-coordinate is out of bounds. def assert_y!(y) unless include_y?(y) raise ChunkyPNG::OutOfBounds, "Row index #{y} out of bounds!" end true end # Throws an exception if the x- or y-coordinate is out of bounds. def assert_xy!(x, y) unless include_xy?(x, y) raise ChunkyPNG::OutOfBounds, "Coordinates (#{x},#{y}) out of bounds!" end true end # Throws an exception if the vector_length does not match this canvas' # height. def assert_height!(vector_length) if height != vector_length raise ChunkyPNG::ExpectationFailed, "The length of the vector (#{vector_length}) does not match the canvas height (#{height})!" end true end # Throws an exception if the vector_length does not match this canvas' # width. def assert_width!(vector_length) if width != vector_length raise ChunkyPNG::ExpectationFailed, "The length of the vector (#{vector_length}) does not match the canvas width (#{width})!" end true end # Throws an exception if the matrix width and height does not match this canvas' dimensions. def assert_size!(matrix_width, matrix_height) if width != matrix_width raise ChunkyPNG::ExpectationFailed, "The width of the matrix does not match the canvas width!" end if height != matrix_height raise ChunkyPNG::ExpectationFailed, "The height of the matrix does not match the canvas height!" end true end end end chunky_png-1.3.15/lib/chunky_png/point.rb0000644000175000017500000001075713766004353017632 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG # Factory method to create {ChunkyPNG::Point} instances. # # This method tries to be as flexible as possible with regards to the given input: besides # explicit coordinates, this method also accepts arrays, hashes, strings, {ChunkyPNG::Dimension} # instances and anything that responds to :x and :y. # # @overload Point(x, y) # @param [Integer, :to_i] x The x-coordinate # @param [Integer, :to_i] y The y-coordinate # @return [ChunkyPNG::Point] The instantiated point. # # @overload Point(array) # @param [Array] array A two element array which represent the x- and y-coordinate. # @return [ChunkyPNG::Point] The instantiated point. # # @overload Point(hash) # @param [Hash] array A hash with the :x or 'x' and :y or # 'y' keys set, which will be used as coordinates. # @return [ChunkyPNG::Point] The instantiated point. # # @overload Point(string) # @param [String] string A string that contains the coordinates, e.g. '0, 4', # '(0 4)', [0,4}', etc. # @return [ChunkyPNG::Point] The instantiated point. # # @return [ChunkyPNG::Point] # @raise [ArgumentError] if the arguments weren't understood. # @see ChunkyPNG::Point def self.Point(*args) case args.length when 2 then ChunkyPNG::Point.new(*args) when 1 then build_point_from_object(args.first) else raise ArgumentError, "Don't know how to construct a point from #{args.inspect}!" end end def self.build_point_from_object(source) case source when ChunkyPNG::Point source when ChunkyPNG::Dimension ChunkyPNG::Point.new(source.width, source.height) when Array ChunkyPNG::Point.new(source[0], source[1]) when Hash x = source[:x] || source["x"] y = source[:y] || source["y"] ChunkyPNG::Point.new(x, y) when ChunkyPNG::Point::POINT_REGEXP ChunkyPNG::Point.new($1.to_i, $2.to_i) else if source.respond_to?(:x) && source.respond_to?(:y) ChunkyPNG::Point.new(source.x, source.y) else raise ArgumentError, "Don't know how to construct a point from #{source.inspect}!" end end end private_class_method :build_point_from_object # Simple class that represents a point on a canvas using an x and y coordinate. # # This class implements some basic methods to handle comparison, the splat operator and # bounds checking that make it easier to work with coordinates. # # @see ChunkyPNG.Point class Point # @return [Regexp] The regexp to parse points from a string. # @private POINT_REGEXP = /^[\(\[\{]?(\d+)\s*[,]?\s*(\d+)[\)\]\}]?$/ # @return [Integer] The x-coordinate of the point. attr_accessor :x # @return [Integer] The y-coordinate of the point. attr_accessor :y # Initializes a new point instance. # @param [Integer, :to_i] x The x-coordinate. # @param [Integer, :to_i] y The y-coordinate. def initialize(x, y) @x, @y = x.to_i, y.to_i end # Checks whether 2 points are identical. # @return [true, false] true iff the x and y coordinates match def eql?(other) other.x == x && other.y == y end alias == eql? # Compares 2 points. # # It will first compare the y coordinate, and it only takes the x-coordinate into # account if the y-coordinates of the points are identical. This way, an array of # points will be sorted into the order in which they would occur in the pixels # array returned by {ChunkyPNG::Canvas#pixels}. # # @param [ChunkyPNG::Point] other The point to compare this point with. # @return [-1, 0, 1] -1 If this point comes before the other one, 1 # if after, and 0 if the points are identical. def <=>(other) (y <=> other.y) == 0 ? x <=> other.x : y <=> other.y end # Converts the point instance to an array. # @return [Array] A 2-element array, i.e. [x, y]. def to_a [x, y] end alias to_ary to_a # Checks whether the point falls into a dimension # @param [ChunkyPNG::Dimension, ...] dimension_like The dimension of which the bounds # should be taken for the check. # @return [true, false] true iff the x and y coordinate fall width the width # and height of the dimension. def within_bounds?(*dimension_like) ChunkyPNG::Dimension(*dimension_like).include?(self) end end end chunky_png-1.3.15/lib/chunky_png/rmagick.rb0000644000175000017500000000260413766004353020106 0ustar danieldaniel# frozen-string-literal: true require "rmagick" module ChunkyPNG # Methods for importing and exporting RMagick image objects. # # By default, this module is disabled because of the dependency on RMagick. # You need to include this module yourself if you want to use it. # # @example # # require 'rmagick' # require 'chunky_png/rmagick' # # canvas = ChunkyPNG::Canvas.from_file('filename.png') # image = ChunkyPNG::RMagick.export(canvas) # # # do something with the image using RMagick # # updated_canvas = ChunkyPNG::RMagick.import(image) # module RMagick extend self # Imports an RMagick image as Canvas object. # @param [Magick::Image] image The image to import # @return [ChunkyPNG::Canvas] The canvas, constructed from the RMagick image. def import(image) pixels = image.export_pixels_to_str(0, 0, image.columns, image.rows, "RGBA") ChunkyPNG::Canvas.from_rgba_stream(image.columns, image.rows, pixels) end # Exports a Canvas as RMagick image instance. # @param [ChunkyPNG::Canvas] canvas The canvas to export. # @return [Magick::Image] The RMagick image constructed from the Canvas instance. def export(canvas) image = Magick::Image.new(canvas.width, canvas.height) image.import_pixels(0, 0, canvas.width, canvas.height, "RGBA", canvas.pixels.pack("N*")) image end end end chunky_png-1.3.15/lib/chunky_png/version.rb0000644000175000017500000000026213766004353020154 0ustar danieldaniel# frozen-string-literal: true module ChunkyPNG # The current version of ChunkyPNG. # Set it and commit the change this before running rake release. VERSION = "1.3.15" end chunky_png-1.3.15/lib/chunky_png.rb0000644000175000017500000001172513766004353016475 0ustar danieldaniel# frozen-string-literal: true # Basic requirements from standard library require "set" require "zlib" require "stringio" # ChunkyPNG - the pure ruby library to access PNG files. # # The ChunkyPNG module defines some constants that are used in the # PNG specification, specifies some exception classes, and serves as # a namespace for all the other modules and classes in this library. # # {ChunkyPNG::Image}:: class to represent PNG images, including metadata. # {ChunkyPNG::Canvas}:: class to represent the image's canvas. # {ChunkyPNG::Color}:: module to work with color values. # {ChunkyPNG::Palette}:: represents the palette of colors used on a {ChunkyPNG::Canvas}. # {ChunkyPNG::Datastream}:: represents the internal structure of a PNG {ChunkyPNG::Image}. # {ChunkyPNG::Color}:: represents one chunk of data within a {ChunkyPNG::Datastream}. # {ChunkyPNG::Point}:: geometry helper class representing a 2-dimensional point. # {ChunkyPNG::Dimension}:: geometry helper class representing a dimension (i.e. width x height). # {ChunkyPNG::Vector}:: geometry helper class representing a series of points. # # @author Willem van Bergen module ChunkyPNG ################################################### # PNG international standard defined constants ################################################### # Indicates that the PNG image uses grayscale colors, i.e. only a # single teint channel. # @private COLOR_GRAYSCALE = 0 # Indicates that the PNG image uses true color, composed of a red # green and blue channel. # @private COLOR_TRUECOLOR = 2 # Indicates that the PNG image uses indexed colors, where the values # point to colors defined on a palette. # @private COLOR_INDEXED = 3 # Indicates that the PNG image uses grayscale colors with opacity, i.e. # a teint channel with an alpha channel. # @private COLOR_GRAYSCALE_ALPHA = 4 # Indicates that the PNG image uses true color with opacity, composed of # a red, green and blue channel, and an alpha value. # @private COLOR_TRUECOLOR_ALPHA = 6 # Indicates that the PNG specification's default compression # method is used (Zlib/Deflate) # @private COMPRESSION_DEFAULT = 0 # Indicates that the PNG chunk content is not compressed # flag used in iTXt chunk # @private UNCOMPRESSED_CONTENT = 0 # Indicates that the PNG chunk content is compressed # flag used in iTXt chunk # @private COMPRESSED_CONTENT = 1 # Indicates that the image does not use interlacing. # @private INTERLACING_NONE = 0 # Indicates that the image uses Adam7 interlacing. # @private INTERLACING_ADAM7 = 1 ### Filter method constants # Indicates that the PNG specification's default filtering are # being used in the image. # @private FILTERING_DEFAULT = 0 # Indicates that no filtering is used for the scanline. # @private FILTER_NONE = 0 # Indicates that SUB filtering is used for the scanline. # @private FILTER_SUB = 1 # Indicates that UP filtering is used for the scanline. # @private FILTER_UP = 2 # Indicates that AVERAGE filtering is used for the scanline. # @private FILTER_AVERAGE = 3 # Indicates that PAETH filtering is used for the scanline. # @private FILTER_PAETH = 4 ################################################### # ChunkyPNG exception classes ################################################### # Default exception class for ChunkyPNG class Exception < ::StandardError end # Exception that is raised for an unsupported PNG image. class NotSupported < ChunkyPNG::Exception end # Exception that is raised if the PNG signature is not encountered at the # beginning of the file. class SignatureMismatch < ChunkyPNG::Exception end # Exception that is raised if the CRC check for a block fails class CRCMismatch < ChunkyPNG::Exception end # Exception that is raised if an tTXt chunk does not contain valid UTF-8 data. class InvalidUTF8 < ChunkyPNG::Exception end # Exception that is raised if an expectation fails. class ExpectationFailed < ChunkyPNG::Exception end # Exception that when provided coordinates are out of bounds for the canvas class OutOfBounds < ChunkyPNG::ExpectationFailed end # Exception that is raised when requesting the DPI of a PNG that doesn't # specify the units of its physical pixel dimensions. class UnitsUnknown < ChunkyPNG::Exception end # Null-byte, with the encoding set correctly to ASCII-8BIT (binary) in Ruby 1.9. # @return [String] A binary string, consisting of one NULL-byte. # @private EXTRA_BYTE = "\0".b end require "chunky_png/version" # PNG file structure require "chunky_png/datastream" require "chunky_png/chunk" # Colors require "chunky_png/palette" require "chunky_png/color" # Geometry require "chunky_png/point" require "chunky_png/vector" require "chunky_png/dimension" # Canvas / Image classes require "chunky_png/canvas" require "chunky_png/image" chunky_png-1.3.15/README.md0000644000175000017500000000743713766004353014521 0ustar danieldaniel# ChunkyPNG This library can read and write PNG files. It is written in pure Ruby for maximum portability. Let me rephrase: it does NOT require RMagick or any other memory leaking image library. - [Source code](https://github.com/wvanbergen/chunky_png/tree/master) - [RDoc](https://rdoc.info/gems/chunky_png) - [Wiki](https://github.com/wvanbergen/chunky_png/wiki) - [Issue tracker](https://github.com/wvanbergen/chunky_png/issues) ## Features - Decodes any image that the PNG standard allows. This includes all standard color modes, all bit depths, all transparency, and interlacing and filtering options. - Encodes images supports all color modes (true color, grayscale, and indexed) and transparency for all these color modes. The best color mode will be chosen automatically, based on the amount of used colors. - R/W access to the image's pixels. - R/W access to all image metadata that is stored in chunks. - Memory efficient (uses a Fixnum, i.e. 4 or 8 bytes of memory per pixel, depending on the hardware) - Reasonably fast for Ruby standards, by only using integer math and a highly optimized saving routine. - Works on every currently supported Ruby version (2.5+) - Interoperability with RMagick if you really have to. Also, have a look at [OilyPNG](https://github.com/wvanbergen/oily_png) which is a mixin module that implements some of the ChunkyPNG algorithms in C, which provides a massive speed boost to encoding and decoding. ## Usage ```ruby require 'chunky_png' # Creating an image from scratch, save as an interlaced PNG png = ChunkyPNG::Image.new(16, 16, ChunkyPNG::Color::TRANSPARENT) png[1,1] = ChunkyPNG::Color.rgba(10, 20, 30, 128) png[2,1] = ChunkyPNG::Color('black @ 0.5') png.save('filename.png', :interlace => true) # Compose images using alpha blending. avatar = ChunkyPNG::Image.from_file('avatar.png') badge = ChunkyPNG::Image.from_file('no_ie_badge.png') avatar.compose!(badge, 10, 10) avatar.save('composited.png', :fast_rgba) # Force the fast saving routine. # Accessing metadata image = ChunkyPNG::Image.from_file('with_metadata.png') puts image.metadata['Title'] image.metadata['Author'] = 'Willem van Bergen' image.save('with_metadata.png') # Overwrite file # Low level access to PNG chunks png_stream = ChunkyPNG::Datastream.from_file('filename.png') png_stream.each_chunk { |chunk| p chunk.type } ``` Also check out the screencast on the ChunkyPNG homepage by John Davison, which illustrates basic usage of the library on the [ChunkyPNG website](https://chunkypng.com/). For more information, see the [project wiki](https://github.com/wvanbergen/chunky_png/wiki) or the [RDOC documentation](https://www.rubydoc.info/gems/chunky_png). ## Security warning ChunkyPNG is vulnerable to decompression bombs, which means that ChunkyPNG is vulnerable to DOS attacks by running out of memory when loading a specifically crafted PNG file. Because of the pure-Ruby nature of the library it is very hard to fix this problem in the library itself. In order to safely deal with untrusted images, you should make sure to do the image processing using ChunkyPNG in a separate process, e.g. by using fork or a background processing library. ## About The library is written by Willem van Bergen for Floorplanner.com, and released under the MIT license (see LICENSE). Please contact me for questions or remarks. I generally consider this library to be feature complete. I will gladly accept patches to fix bugs and improve performance, but I will generally be hesitant to accept new features or API endpoints. Before contributing, please read [CONTRIBUTING.rdoc](CONTRIBUTING.rdoc) that explains this in more detail. Please check out CHANGELOG.rdoc to see what changed in all versions. P.S.: The name of this library is intentionally similar to Chunky Bacon and Chunky GIF. Use Google if you want to know _why_. :-) chunky_png-1.3.15/.standard.yml0000644000175000017500000000115713766004353015634 0ustar danieldaniel# ChunkyPNG uses and enforces standard.rb as code style (see https://github.com/testdouble/standard). # For backwards compatilibity and idiosyncratic preferences of the main author, # there are some minor differences listed in here. ruby_version: 2.2 ignore: - lib/chunky_png/**/*.rb: # We allow `for` loops in the codebase, especially in hot paths, # because they perform better than `each` blocks. - "Style/For" - spec/chunky_png/**/*.rb: # In RSpec, having to follow this rule will cause expectations to # be less readable, specifically blocks for the `change` matcher. - "Lint/AmbiguousBlockAssociation" chunky_png-1.3.15/tasks/0000755000175000017500000000000013766004353014354 5ustar danieldanielchunky_png-1.3.15/tasks/benchmarks.rake0000644000175000017500000000124113766004353017333 0ustar danieldanielall_benchamrk_tasks = [] namespace(:benchmark) do Dir[File.join(File.dirname(__FILE__), "..", "benchmarks", "*_benchmark.rb")]. each do |benchmark_file| task_name = File.basename(benchmark_file, "_benchmark.rb").to_sym desc "Run the #{task_name} benchmark." task(task_name, :n) do |task, args| ENV["N"] = args[:n] if args[:n] load(File.expand_path(benchmark_file)) end all_benchamrk_tasks << "benchmark:#{task_name}" end end unless all_benchamrk_tasks.empty? desc "Run the whole benchmark suite" task(:benchmark, :n) do |task, args| all_benchamrk_tasks.each do |t| task(t).invoke(args[:n]) puts end end end chunky_png-1.3.15/Gemfile0000644000175000017500000000042213766004353014520 0ustar danieldaniel# frozen-string-literal: true source "https://rubygems.org" gemspec platforms :jruby do gem "jruby-openssl" end group :jekyll do gem "jekyll", "~> 3.3" gem "kramdown-parser-gfm" end group :jekyll_plugins do gem "jekyll-commonmark" gem "jekyll-theme-cayman" endchunky_png-1.3.15/spec/0000755000175000017500000000000013766004353014161 5ustar danieldanielchunky_png-1.3.15/spec/chunky_png/0000755000175000017500000000000013766004353016326 5ustar danieldanielchunky_png-1.3.15/spec/chunky_png/datastream_spec.rb0000644000175000017500000001434313766004353022017 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Datastream do describe ".from_io" do it "should raise an error when loading a file with a bad signature" do filename = resource_file("damaged_signature.png") expect { ChunkyPNG::Datastream.from_file(filename) }.to raise_error(ChunkyPNG::SignatureMismatch) end it "should raise an error if the CRC of a chunk is incorrect" do filename = resource_file("damaged_chunk.png") expect { ChunkyPNG::Datastream.from_file(filename) }.to raise_error(ChunkyPNG::CRCMismatch) end it "should raise an error for a stream that is too short" do stream = StringIO.new expect { ChunkyPNG::Datastream.from_io(stream) }.to raise_error(ChunkyPNG::SignatureMismatch) end it "should read a stream with trailing data without failing" do filename = resource_file("trailing_bytes_after_iend_chunk.png") image = ChunkyPNG::Datastream.from_file(filename) expect(image).to be_instance_of(ChunkyPNG::Datastream) end end describe "#metadata" do it "should load uncompressed tXTt chunks correctly" do filename = resource_file("text_chunk.png") ds = ChunkyPNG::Datastream.from_file(filename) expect(ds.metadata["Title"]).to eql "My amazing icon!" expect(ds.metadata["Author"]).to eql "Willem van Bergen" end it "should load compressed zTXt chunks correctly" do filename = resource_file("ztxt_chunk.png") ds = ChunkyPNG::Datastream.from_file(filename) expect(ds.metadata["Title"]).to eql "PngSuite" expect(ds.metadata["Copyright"]).to eql "Copyright Willem van Schaik, Singapore 1995-96" end it "ignores iTXt chunks" do filename = resource_file("itxt_chunk.png") ds = ChunkyPNG::Datastream.from_file(filename) expect(ds.metadata).to be_empty end end describe "#physical_chunk" do it "should read and write pHYs chunks correctly" do filename = resource_file("clock.png") ds = ChunkyPNG::Datastream.from_file(filename) expect(ds.physical_chunk.unit).to eql :meters expect(ds.physical_chunk.dpix.round).to eql 72 expect(ds.physical_chunk.dpiy.round).to eql 72 ds = ChunkyPNG::Datastream.from_blob(ds.to_blob) expect(ds.physical_chunk).not_to be_nil end it "should raise ChunkyPNG::UnitsUnknown if we request dpi but the units are unknown" do physical_chunk = ChunkyPNG::Chunk::Physical.new(2835, 2835, :unknown) expect { physical_chunk.dpix }.to raise_error(ChunkyPNG::UnitsUnknown) expect { physical_chunk.dpiy }.to raise_error(ChunkyPNG::UnitsUnknown) end end describe "#iTXt_chunk" do it "should read iTXt chunks correctly" do filename = resource_file("itxt_chunk.png") ds = ChunkyPNG::Datastream.from_file(filename) int_text_chunks = ds.chunks.select { |chunk| chunk.is_a?(ChunkyPNG::Chunk::InternationalText) } expect(int_text_chunks.length).to eq(2) coach_uk = int_text_chunks.find { |chunk| chunk.language_tag == "en-gb" } coach_us = int_text_chunks.find { |chunk| chunk.language_tag == "en-us" } expect(coach_uk).to_not be_nil expect(coach_us).to_not be_nil expect(coach_uk.keyword).to eq("coach") expect(coach_uk.text).to eq("bus with of higher standard of comfort, usually chartered or used for longer journeys") expect(coach_uk.translated_keyword).to eq("bus") expect(coach_uk.compressed).to eq(ChunkyPNG::UNCOMPRESSED_CONTENT) expect(coach_uk.compression).to eq(ChunkyPNG::COMPRESSION_DEFAULT) expect(coach_us.keyword).to eq("coach") expect(coach_us.text).to eq("US extracurricular sports teacher at a school (UK: PE teacher) lowest class on a passenger aircraft (UK: economy)") expect(coach_us.translated_keyword).to eq("trainer") expect(coach_us.compressed).to eq(ChunkyPNG::COMPRESSED_CONTENT) expect(coach_us.compression).to eq(ChunkyPNG::COMPRESSION_DEFAULT) end it "should write iTXt chunks correctly" do expected_hex = %w[0000 001d 6954 5874 436f 6d6d 656e 7400 0000 0000 4372 6561 7465 6420 7769 7468 2047 494d 5064 2e65 07].join("") stream = StringIO.new itext = ChunkyPNG::Chunk::InternationalText.new("Comment", "Created with GIMP") itext.write(stream) generated_hex = stream.string.unpack("H*").join("") expect(generated_hex).to eq(expected_hex) end it "should handle incorrect UTF-8 encoding in iTXt chunks" do incorrect_text_encoding = [0, 0, 0, 14, 105, 84, 88, 116, 67, 111, 109, 109, 101, 110, 116, 0, 0, 0, 0, 0, 195, 40, 17, 87, 97, 213].pack("C*") incorrect_translated_keyword_encoding = [0, 0, 0, 19, 105, 84, 88, 116, 67, 111, 109, 109, 101, 110, 116, 0, 0, 0, 0, 226, 130, 40, 0, 116, 101, 115, 116, 228, 53, 113, 182].pack("C*") expect { ChunkyPNG::Chunk.read(StringIO.new(incorrect_text_encoding)) }.to raise_error(ChunkyPNG::InvalidUTF8) expect { ChunkyPNG::Chunk.read(StringIO.new(incorrect_translated_keyword_encoding)) }.to raise_error(ChunkyPNG::InvalidUTF8) end it "should handle UTF-8 in iTXt compressed chunks correctly" do parsed = serialized_chunk(ChunkyPNG::Chunk::InternationalText.new("Comment", "✨", "", "💩", ChunkyPNG::COMPRESSED_CONTENT)) expect(parsed.text).to eq("✨") expect(parsed.text.encoding).to eq(Encoding::UTF_8) expect(parsed.translated_keyword).to eq("💩") expect(parsed.translated_keyword.encoding).to eq(Encoding::UTF_8) end it "should handle UTF-8 in iTXt chunks correctly" do parsed = serialized_chunk(ChunkyPNG::Chunk::InternationalText.new("Comment", "✨", "", "💩")) expect(parsed.text).to eq("✨") expect(parsed.text.encoding).to eq(Encoding::UTF_8) expect(parsed.translated_keyword).to eq("💩") expect(parsed.translated_keyword.encoding).to eq(Encoding::UTF_8) end it "should transform non UTF-8 iTXt fields to UTF-8 on write" do parsed = serialized_chunk(ChunkyPNG::Chunk::InternationalText.new("Comment", "®".encode("Windows-1252"), "", "ƒ".encode("Windows-1252"))) expect(parsed.text).to eq("®") expect(parsed.text.encoding).to eq(Encoding::UTF_8) expect(parsed.translated_keyword).to eq("ƒ") expect(parsed.translated_keyword.encoding).to eq(Encoding::UTF_8) end end end chunky_png-1.3.15/spec/chunky_png/color_spec.rb0000644000175000017500000003524713766004353021016 0ustar danieldanielrequire "spec_helper" describe "ChunyPNG.Color" do it "should interpret 4 arguments as RGBA values" do expect(ChunkyPNG::Color(1, 2, 3, 4)).to eql ChunkyPNG::Color.rgba(1, 2, 3, 4) end it "should interpret 3 arguments as RGBA values" do expect(ChunkyPNG::Color(1, 2, 3)).to eql ChunkyPNG::Color.rgb(1, 2, 3) end it "should interpret 2 arguments as a color to parse and an opacity value" do expect(ChunkyPNG::Color("0x0a649664", 0xaa)).to eql 0x0a6496aa expect(ChunkyPNG::Color("spring green @ 0.6666", 0xff)).to eql 0x00ff7fff end it "should interpret 1 argument as a color to parse" do expect(ChunkyPNG::Color).to receive(:parse).with("0x0a649664") ChunkyPNG::Color("0x0a649664") end end describe ChunkyPNG::Color do include ChunkyPNG::Color before(:each) do @white = 0xffffffff @black = 0x000000ff @opaque = 0x0a6496ff @non_opaque = 0x0a649664 @fully_transparent = 0x0a649600 @red = 0xff0000ff @green = 0x00ff00ff @blue = 0x0000ffff end describe "#parse" do it "should interpret a hex string correctly" do expect(parse("0x0a649664")).to eql ChunkyPNG::Color.from_hex("#0a649664") end it "should interpret a color name correctly" do expect(parse(:spring_green)).to eql 0x00ff7fff expect(parse("spring green")).to eql 0x00ff7fff expect(parse("spring green @ 0.6666")).to eql 0x00ff7faa end it "should return numbers as is" do expect(parse("12345")).to eql 12345 expect(parse(12345)).to eql 12345 end end describe "#pixel_bytesize" do it "should return the normal amount of bytes with a bit depth of 8" do expect(pixel_bytesize(ChunkyPNG::COLOR_TRUECOLOR, 8)).to eql 3 end it "should return a multiple of the normal amount of bytes with a bit depth greater than 8" do expect(pixel_bytesize(ChunkyPNG::COLOR_TRUECOLOR, 16)).to eql 6 expect(pixel_bytesize(ChunkyPNG::COLOR_TRUECOLOR_ALPHA, 16)).to eql 8 expect(pixel_bytesize(ChunkyPNG::COLOR_GRAYSCALE_ALPHA, 16)).to eql 4 end it "should return 1 with a bit depth lower than 0" do expect(pixel_bytesize(ChunkyPNG::COLOR_TRUECOLOR, 4)).to eql 1 expect(pixel_bytesize(ChunkyPNG::COLOR_INDEXED, 2)).to eql 1 expect(pixel_bytesize(ChunkyPNG::COLOR_GRAYSCALE_ALPHA, 1)).to eql 1 end end describe "#pass_bytesize" do it "should calculate a pass size correctly" do expect(pass_bytesize(ChunkyPNG::COLOR_TRUECOLOR, 8, 10, 10)).to eql 310 end it "should return 0 if one of the dimensions is zero" do expect(pass_bytesize(ChunkyPNG::COLOR_TRUECOLOR, 8, 0, 10)).to eql 0 expect(pass_bytesize(ChunkyPNG::COLOR_TRUECOLOR, 8, 10, 0)).to eql 0 end end describe "#rgba" do it "should represent pixels as the correct number" do # rubocop:disable Layout/ExtraSpacing, Layout/SpaceInsideParens expect(rgba(255, 255, 255, 255)).to eql @white expect(rgba( 0, 0, 0, 255)).to eql @black expect(rgba( 10, 100, 150, 255)).to eql @opaque expect(rgba( 10, 100, 150, 100)).to eql @non_opaque expect(rgba( 10, 100, 150, 0)).to eql @fully_transparent # rubocop:enable Layout/ExtraSpacing, Layout/SpaceInsideParens end end describe "#from_hex" do it "should load colors correctly from hex notation" do expect(from_hex("0a649664")).to eql @non_opaque expect(from_hex("#0a649664")).to eql @non_opaque expect(from_hex("0x0a649664")).to eql @non_opaque expect(from_hex("0a6496")).to eql @opaque expect(from_hex("#0a6496")).to eql @opaque expect(from_hex("0x0a6496")).to eql @opaque expect(from_hex("abc")).to eql 0xaabbccff expect(from_hex("#abc")).to eql 0xaabbccff expect(from_hex("0xabc")).to eql 0xaabbccff end it "should allow setting opacity explicitly" do expect(from_hex("0x0a6496", 0x64)).to eql @non_opaque expect(from_hex("#0a6496", 0x64)).to eql @non_opaque expect(from_hex("0xabc", 0xdd)).to eql 0xaabbccdd expect(from_hex("#abc", 0xdd)).to eql 0xaabbccdd end end describe "#from_hsv" do it "should load colors correctly from an HSV triple" do # At 0 brightness, should be @black independent of hue or sat expect(from_hsv(0, 0, 0)).to eql @black expect(from_hsv(100, 1, 0)).to eql @black expect(from_hsv(100, 0.5, 0)).to eql @black # At brightness 1 and sat 0, should be @white regardless of hue expect(from_hsv(0, 0, 1)).to eql @white expect(from_hsv(100, 0, 1)).to eql @white # Converting the "pure" colors should work expect(from_hsv(0, 1, 1)).to eql @red expect(from_hsv(120, 1, 1)).to eql @green expect(from_hsv(240, 1, 1)).to eql @blue # And, finally, one random color expect(from_hsv(120, 0.5, 0.80)).to eql 0x66cc66ff # Hue 0 and hue 360 should be equivalent expect(from_hsv(0, 0.5, 0.5)).to eql from_hsv(360, 0.5, 0.5) expect(from_hsv(0, 0.5, 0.5)).to eql from_hsv(360.0, 0.5, 0.5) end it "should optionally accept a fourth param for alpha" do expect(from_hsv(0, 1, 1, 255)).to eql @red expect(from_hsv(120, 1, 1, 255)).to eql @green expect(from_hsv(240, 1, 1, 255)).to eql @blue expect(from_hsv(0, 1, 1, 0)).to eql 0xff000000 # transparent red expect(from_hsv(120, 1, 1, 0)).to eql 0x00ff0000 # transparent green expect(from_hsv(240, 1, 1, 0)).to eql 0x0000ff00 # transparent blue end end describe "#from_hsl" do it "should load colors correctly from an HSL triple" do # At 0 lightness, should always be black expect(from_hsl(0, 0, 0)).to eql @black expect(from_hsl(100, 0, 0)).to eql @black expect(from_hsl(54, 0.5, 0)).to eql @black # At 1 lightness, should always be white expect(from_hsl(0, 0, 1)).to eql @white expect(from_hsl(0, 0.5, 1)).to eql @white expect(from_hsl(110, 0, 1)).to eql @white # 'Pure' colors should work expect(from_hsl(0, 1, 0.5)).to eql @red expect(from_hsl(120, 1, 0.5)).to eql @green expect(from_hsl(240, 1, 0.5)).to eql @blue # Random colors expect(from_hsl(87.27, 0.5, 0.5686)).to eql 0x95c759ff expect(from_hsl(271.83, 0.5399, 0.4176)).to eql 0x6d30a3ff expect(from_hsl(63.6, 0.5984, 0.4882)).to eql 0xbec631ff # Hue 0 and hue 360 should be equivalent expect(from_hsl(0, 0.5, 0.5)).to eql from_hsl(360, 0.5, 0.5) expect(from_hsl(0, 0.5, 0.5)).to eql from_hsl(360.0, 0.5, 0.5) end it "should optionally accept a fourth param for alpha" do expect(from_hsl(0, 1, 0.5, 255)).to eql @red expect(from_hsl(120, 1, 0.5, 255)).to eql @green expect(from_hsl(240, 1, 0.5, 255)).to eql @blue expect(from_hsl(0, 1, 0.5, 0)).to eql 0xff000000 # transparent red expect(from_hsl(120, 1, 0.5, 0)).to eql 0x00ff0000 # transparent green expect(from_hsl(240, 1, 0.5, 0)).to eql 0x0000ff00 # transparent blue end end describe "#html_color" do it "should find the correct color value" do expect(html_color(:springgreen)).to eql 0x00ff7fff expect(html_color(:spring_green)).to eql 0x00ff7fff expect(html_color("springgreen")).to eql 0x00ff7fff expect(html_color("spring green")).to eql 0x00ff7fff expect(html_color("SpringGreen")).to eql 0x00ff7fff expect(html_color("SPRING_GREEN")).to eql 0x00ff7fff end it "should set the opacity level explicitly" do expect(html_color(:springgreen, 0xff)).to eql 0x00ff7fff expect(html_color(:springgreen, 0xaa)).to eql 0x00ff7faa expect(html_color(:springgreen, 0x00)).to eql 0x00ff7f00 end it "should set opacity levels from the color name" do expect(html_color("Spring green @ 1.0")).to eql 0x00ff7fff expect(html_color("Spring green @ 0.666")).to eql 0x00ff7faa expect(html_color("Spring green @ 0.0")).to eql 0x00ff7f00 end it "should raise for an unkown color name" do expect { html_color(:nonsense) }.to raise_error(ArgumentError) end end describe "#opaque?" do it "should correctly check for opaqueness" do expect(opaque?(@white)).to eql true expect(opaque?(@black)).to eql true expect(opaque?(@opaque)).to eql true expect(opaque?(@non_opaque)).to eql false expect(opaque?(@fully_transparent)).to eql false end end describe "extraction of separate color channels" do it "should extract components from a color correctly" do expect(r(@opaque)).to eql 10 expect(g(@opaque)).to eql 100 expect(b(@opaque)).to eql 150 expect(a(@opaque)).to eql 255 end end describe "#grayscale_teint" do it "should calculate the correct grayscale teint" do expect(grayscale_teint(@opaque)).to eql 79 expect(grayscale_teint(@non_opaque)).to eql 79 end end describe "#to_grayscale" do it "should use the grayscale teint for r, g and b" do gs = to_grayscale(@non_opaque) expect(r(gs)).to eql grayscale_teint(@non_opaque) expect(g(gs)).to eql grayscale_teint(@non_opaque) expect(b(gs)).to eql grayscale_teint(@non_opaque) end it "should preserve the alpha channel" do expect(a(to_grayscale(@non_opaque))).to eql a(@non_opaque) expect(a(to_grayscale(@opaque))).to eql ChunkyPNG::Color::MAX end end describe "#to_hex" do it "should represent colors correcly using hex notation" do expect(to_hex(@white)).to eql "#ffffffff" expect(to_hex(@black)).to eql "#000000ff" expect(to_hex(@opaque)).to eql "#0a6496ff" expect(to_hex(@non_opaque)).to eql "#0a649664" expect(to_hex(@fully_transparent)).to eql "#0a649600" end it "should represent colors correcly using hex notation without alpha channel" do expect(to_hex(@white, false)).to eql "#ffffff" expect(to_hex(@black, false)).to eql "#000000" expect(to_hex(@opaque, false)).to eql "#0a6496" expect(to_hex(@non_opaque, false)).to eql "#0a6496" expect(to_hex(@fully_transparent, false)).to eql "#0a6496" end end describe "#to_hsv" do it "should return a [hue, saturation, value] array" do expect(to_hsv(@white)).to eql [0, 0.0, 1.0] expect(to_hsv(@black)).to eql [0, 0.0, 0.0] expect(to_hsv(@red)).to eql [0, 1.0, 1.0] expect(to_hsv(@blue)).to eql [240, 1.0, 1.0] expect(to_hsv(@green)).to eql [120, 1.0, 1.0] expect(to_hsv(0x805440ff)[0]).to be_within(1).of(19) expect(to_hsv(0x805440ff)[1]).to be_within(0.01).of(0.5) expect(to_hsv(0x805440ff)[2]).to be_within(0.01).of(0.5) end it "should optionally include the alpha channel" do expect(to_hsv(@white, true)).to eql [0, 0.0, 1.0, 255] expect(to_hsv(@red, true)).to eql [0, 1.0, 1.0, 255] expect(to_hsv(@blue, true)).to eql [240, 1.0, 1.0, 255] expect(to_hsv(@green, true)).to eql [120, 1.0, 1.0, 255] expect(to_hsv(@opaque, true)[3]).to eql 255 expect(to_hsv(@fully_transparent, true)[3]).to eql 0 end end describe "#to_hsl" do it "should return a [hue, saturation, lightness] array" do expect(to_hsl(@white)).to eql [0, 0.0, 1.0] expect(to_hsl(@black)).to eql [0, 0.0, 0.0] expect(to_hsl(@red)).to eql [0, 1.0, 0.5] expect(to_hsl(@blue)).to eql [240, 1.0, 0.5] expect(to_hsl(@green)).to eql [120, 1.0, 0.5] end it "should optionally include the alpha channel in the returned array" do expect(to_hsl(@white, true)).to eql [0, 0.0, 1.0, 255] expect(to_hsl(@black, true)).to eql [0, 0.0, 0.0, 255] expect(to_hsl(@red, true)).to eql [0, 1.0, 0.5, 255] expect(to_hsl(@blue, true)).to eql [240, 1.0, 0.5, 255] expect(to_hsl(@green, true)).to eql [120, 1.0, 0.5, 255] expect(to_hsl(@opaque, true)[3]).to eql 255 expect(to_hsl(@fully_transparent, true)[3]).to eql 0 end end describe "conversion to other formats" do it "should convert the individual color values back correctly" do expect(to_truecolor_bytes(@opaque)).to eql [10, 100, 150] expect(to_truecolor_alpha_bytes(@non_opaque)).to eql [10, 100, 150, 100] end end describe "#compose" do it "should use the foregorund color as is when the background color is fully transparent" do expect(compose(@non_opaque, @fully_transparent)).to eql @non_opaque end it "should use the foregorund color as is when an opaque color is given as foreground color" do expect(compose(@opaque, @white)).to eql @opaque end it "should use the background color as is when a fully transparent pixel is given as foreground color" do expect(compose(@fully_transparent, @white)).to eql @white end it "should compose pixels correctly with both algorithms" do expect(compose_quick(@non_opaque, @white)).to eql 0x9fc2d6ff expect(compose_precise(@non_opaque, @white)).to eql 0x9fc2d6ff end end describe "#decompose_alpha" do it "should decompose the alpha channel correctly" do expect(decompose_alpha(0x9fc2d6ff, @opaque, @white)).to eql 0x00000064 end it "should return fully transparent if the background channel matches the resulting color" do expect(decompose_alpha(0xabcdefff, 0xff000000, 0xabcdefff)).to eql 0x00 end it "should return fully opaque if the background channel matches the mask color" do expect(decompose_alpha(0xff000000, 0xabcdefff, 0xabcdefff)).to eql 0xff end it "should return fully opaque if the resulting color matches the mask color" do expect(decompose_alpha(0xabcdefff, 0xabcdefff, 0xffffffff)).to eql 255 end end describe "#blend" do it "should blend colors correctly" do expect(blend(@opaque, @black)).to eql 0x05324bff end it "should not matter what color is used as foreground, and what as background" do expect(blend(@opaque, @black)).to eql blend(@black, @opaque) end end describe "#euclidean_distance_rgba" do subject { euclidean_distance_rgba(color_a, color_b) } context "with white and black" do let(:color_a) { @white } let(:color_b) { @black } it { should == Math.sqrt(195_075) } # sqrt(255^2 * 3) end context "with black and white" do let(:color_a) { @black } let(:color_b) { @white } it { should == Math.sqrt(195_075) } # sqrt(255^2 * 3) end context "with the same colors" do let(:color_a) { @white } let(:color_b) { @white } it { should == 0 } end end end chunky_png-1.3.15/spec/chunky_png/point_spec.rb0000644000175000017500000000534313766004353021023 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Point do subject { ChunkyPNG::Point.new(1, 2) } it { should respond_to(:x) } it { should respond_to(:y) } describe "#within_bounds?" do it { should be_within_bounds(2, 3) } it { should_not be_within_bounds("1x3") } it { should_not be_within_bounds(2, 2) } it { should_not be_within_bounds("[1 2]") } end describe "#<=>" do it "should return 0 if the coordinates are identical" do expect((subject <=> ChunkyPNG::Point.new(1, 2))).to eql(0) end it "should return -1 if the y coordinate is smaller than the other one" do expect((subject <=> ChunkyPNG::Point.new(1, 3))).to eql(-1) expect((subject <=> ChunkyPNG::Point.new(0, 3))).to eql(-1) # x doesn't matter expect((subject <=> ChunkyPNG::Point.new(2, 3))).to eql(-1) # x doesn't matter end it "should return 1 if the y coordinate is larger than the other one" do expect((subject <=> ChunkyPNG::Point.new(1, 0))).to eql(1) expect((subject <=> ChunkyPNG::Point.new(0, 0))).to eql(1) # x doesn't matter expect((subject <=> ChunkyPNG::Point.new(2, 0))).to eql(1) # x doesn't matter end it "should return -1 if the x coordinate is smaller and y is the same" do expect((subject <=> ChunkyPNG::Point.new(2, 2))).to eql(-1) end it "should return 1 if the x coordinate is larger and y is the same" do expect((subject <=> ChunkyPNG::Point.new(0, 2))).to eql(1) end end end describe "ChunkyPNG.Point" do subject { ChunkyPNG::Point.new(1, 2) } it "should create a point from a 2-item array" do expect(ChunkyPNG::Point([1, 2])).to eql subject expect(ChunkyPNG::Point(["1", "2"])).to eql subject end it "should create a point from a hash with x and y keys" do expect(ChunkyPNG::Point(x: 1, y: 2)).to eql subject expect(ChunkyPNG::Point("x" => "1", "y" => "2")).to eql subject end it "should create a point from a ChunkyPNG::Dimension object" do dimension = ChunkyPNG::Dimension.new(1, 2) ChunkyPNG::Point(dimension) == subject end it "should create a point from a point-like string" do [ ChunkyPNG::Point("1,2"), ChunkyPNG::Point("1 2"), ChunkyPNG::Point("(1 , 2)"), ChunkyPNG::Point("{1,\t2}"), ChunkyPNG::Point("[1 2}"), ].all? { |point| point == subject } end it "should create a point from an object that responds to x and y" do mock_object = Struct.new(:x, :y).new(1, 2) expect(ChunkyPNG::Point(mock_object)).to eql subject end it "should raise an exception if the input is not understood" do expect { ChunkyPNG::Point(Object.new) }.to raise_error(ArgumentError) expect { ChunkyPNG::Point(1, 2, 3) }.to raise_error(ArgumentError) end end chunky_png-1.3.15/spec/chunky_png/canvas_spec.rb0000644000175000017500000002140413766004353021141 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Canvas do subject { ChunkyPNG::Canvas.new(1, 1, ChunkyPNG::Color::WHITE) } it { should respond_to(:width) } it { should respond_to(:height) } it { should respond_to(:pixels) } describe "#initialize" do it "should accept a single color value as background color" do canvas = ChunkyPNG::Canvas.new(2, 2, "red @ 0.8") expect(canvas[1, 0]).to eql ChunkyPNG::Color.parse("red @ 0.8") end it "should raise an error if the color value is not understood" do expect { ChunkyPNG::Canvas.new(2, 2, :nonsense) }.to raise_error(ArgumentError) end it "should accept an array as initial pixel values" do canvas = ChunkyPNG::Canvas.new(2, 2, [1, 2, 3, 4]) expect(canvas[0, 0]).to eql 1 expect(canvas[1, 0]).to eql 2 expect(canvas[0, 1]).to eql 3 expect(canvas[1, 1]).to eql 4 end it "should raise an ArgumentError if the initial array does not have the correct number of elements" do expect { ChunkyPNG::Canvas.new(2, 2, [1, 2, 3]) }.to raise_error(ArgumentError) expect { ChunkyPNG::Canvas.new(2, 2, [1, 2, 3, 4, 5]) }.to raise_error(ArgumentError) end it "should use a transparent background by default" do canvas = ChunkyPNG::Canvas.new(1, 1) expect(canvas[0, 0]).to eql ChunkyPNG::Color::TRANSPARENT end end describe "#dimension" do it "should return the dimensions as a Dimension instance" do expect(subject.dimension).to eql ChunkyPNG::Dimension("1x1") end end describe "#area" do it "should return the dimensions as two-item array" do expect(subject.area).to eql ChunkyPNG::Dimension("1x1").area end end describe "#include?" do it "should return true if the coordinates are within bounds, false otherwise" do # rubocop:disable Layout/SpaceInsideParens expect(subject.include_xy?( 0, 0)).to eql true expect(subject.include_xy?(-1, 0)).to eql false expect(subject.include_xy?( 1, 0)).to eql false expect(subject.include_xy?( 0, -1)).to eql false expect(subject.include_xy?( 0, 1)).to eql false expect(subject.include_xy?(-1, -1)).to eql false expect(subject.include_xy?(-1, 1)).to eql false expect(subject.include_xy?( 1, -1)).to eql false expect(subject.include_xy?( 1, 1)).to eql false # rubocop:enable Layout/SpaceInsideParens end it "should accept strings, arrays, hashes and points as well" do expect(subject).to include("0, 0") expect(subject).to_not include("0, 1") expect(subject).to include([0, 0]) expect(subject).to_not include([0, 1]) expect(subject).to include(y: 0, x: 0) expect(subject).to_not include(y: 1, x: 0) expect(subject).to include(ChunkyPNG::Point.new(0, 0)) expect(subject).to_not include(ChunkyPNG::Point.new(0, 1)) end end describe "#include_x?" do it "should return true if the x-coordinate is within bounds, false otherwise" do expect(subject.include_x?(0)).to eql true expect(subject.include_x?(-1)).to eql false expect(subject.include_x?(1)).to eql false end end describe "#include_y?" do it "should return true if the y-coordinate is within bounds, false otherwise" do expect(subject.include_y?(0)).to eql true expect(subject.include_y?(-1)).to eql false expect(subject.include_y?(1)).to eql false end end describe "#assert_xy!" do it "should not raise an exception if the coordinates are within bounds" do expect(subject).to receive(:include_xy?).with(0, 0).and_return(true) expect { subject.send(:assert_xy!, 0, 0) }.to_not raise_error end it "should raise an exception if the coordinates are out of bounds bounds" do expect(subject).to receive(:include_xy?).with(0, -1).and_return(false) expect { subject.send(:assert_xy!, 0, -1) }.to raise_error(ChunkyPNG::OutOfBounds) end end describe "#assert_x!" do it "should not raise an exception if the x-coordinate is within bounds" do expect(subject).to receive(:include_x?).with(0).and_return(true) expect { subject.send(:assert_x!, 0) }.to_not raise_error end it "should raise an exception if the x-coordinate is out of bounds bounds" do expect(subject).to receive(:include_y?).with(-1).and_return(false) expect { subject.send(:assert_y!, -1) }.to raise_error(ChunkyPNG::OutOfBounds) end end describe "#[]" do it "should return the pixel value if the coordinates are within bounds" do expect(subject[0, 0]).to eql ChunkyPNG::Color::WHITE end it "should assert the coordinates to be within bounds" do expect(subject).to receive(:assert_xy!).with(0, 0) subject[0, 0] end end describe "#get_pixel" do it "should return the pixel value if the coordinates are within bounds" do expect(subject.get_pixel(0, 0)).to eql ChunkyPNG::Color::WHITE end it "should not assert nor check the coordinates" do expect(subject).to_not receive(:assert_xy!) expect(subject).to_not receive(:include_xy?) subject.get_pixel(0, 0) end end describe "#[]=" do it "should change the pixel's color value" do expect { subject[0, 0] = ChunkyPNG::Color::BLACK }.to change { subject[0, 0] } .from(ChunkyPNG::Color::WHITE) .to(ChunkyPNG::Color::BLACK) end it "should assert the bounds of the image" do expect(subject).to receive(:assert_xy!).with(0, 0) subject[0, 0] = ChunkyPNG::Color::BLACK end end describe "set_pixel" do it "should change the pixel's color value" do expect { subject.set_pixel(0, 0, ChunkyPNG::Color::BLACK) }.to change { subject[0, 0] } .from(ChunkyPNG::Color::WHITE) .to(ChunkyPNG::Color::BLACK) end it "should not assert or check the bounds of the image" do expect(subject).to_not receive(:assert_xy!) expect(subject).to_not receive(:include_xy?) subject.set_pixel(0, 0, ChunkyPNG::Color::BLACK) end end describe "#set_pixel_if_within_bounds" do it "should change the pixel's color value" do expect { subject.set_pixel_if_within_bounds(0, 0, ChunkyPNG::Color::BLACK) }.to change { subject[0, 0] } .from(ChunkyPNG::Color::WHITE) .to(ChunkyPNG::Color::BLACK) end it "should not assert, but only check the coordinates" do expect(subject).to_not receive(:assert_xy!) expect(subject).to receive(:include_xy?).with(0, 0) subject.set_pixel_if_within_bounds(0, 0, ChunkyPNG::Color::BLACK) end it "should do nothing if the coordinates are out of bounds" do expect(subject.set_pixel_if_within_bounds(-1, 1, ChunkyPNG::Color::BLACK)).to be_nil expect(subject[0, 0]).to eql ChunkyPNG::Color::WHITE end end describe "#row" do before { @canvas = reference_canvas("operations") } it "should give an out of bounds exception when y-coordinate is out of bounds" do expect { @canvas.row(-1) }.to raise_error(ChunkyPNG::OutOfBounds) expect { @canvas.row(16) }.to raise_error(ChunkyPNG::OutOfBounds) end it "should return the correct pixels" do data = @canvas.row(0) expect(data.length).to eql @canvas.width expect(data).to eql [65535, 268500991, 536936447, 805371903, 1073807359, 1342242815, 1610678271, 1879113727, 2147549183, 2415984639, 2684420095, 2952855551, 3221291007, 3489726463, 3758161919, 4026597375] end end describe "#column" do before { @canvas = reference_canvas("operations") } it "should give an out of bounds exception when x-coordinate is out of bounds" do expect { @canvas.column(-1) }.to raise_error(ChunkyPNG::OutOfBounds) expect { @canvas.column(16) }.to raise_error(ChunkyPNG::OutOfBounds) end it "should return the correct pixels" do data = @canvas.column(0) expect(data.length).to eql @canvas.height expect(data).to eql [65535, 1114111, 2162687, 3211263, 4259839, 5308415, 6356991, 7405567, 8454143, 9502719, 10551295, 11599871, 12648447, 13697023, 14745599, 15794175] end end describe "#replace_canvas" do it "should change the dimension of the canvas" do expect { subject.send(:replace_canvas!, 2, 2, [1, 2, 3, 4]) }.to change { subject.dimension } .from(ChunkyPNG::Dimension("1x1")) .to(ChunkyPNG::Dimension("2x2")) end it "should change the pixel array" do expect { subject.send(:replace_canvas!, 2, 2, [1, 2, 3, 4]) }.to change { subject.pixels } .from([ChunkyPNG::Color("white")]) .to([1, 2, 3, 4]) end it "should return itself" do expect(subject.send(:replace_canvas!, 2, 2, [1, 2, 3, 4])).to equal(subject) end end describe "#inspect" do it "should give a string description of the canvas" do expect(subject.inspect).to eql "" end end end chunky_png-1.3.15/spec/chunky_png/canvas/0000755000175000017500000000000013766004353017601 5ustar danieldanielchunky_png-1.3.15/spec/chunky_png/canvas/stream_exporting_spec.rb0000644000175000017500000000546013766004353024537 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Canvas do describe "#to_rgba_stream" do it "should export a sample canvas to an RGBA stream correctly" do canvas = ChunkyPNG::Canvas.new(2, 2, [ ChunkyPNG::Color.rgba(1, 2, 3, 4), ChunkyPNG::Color.rgba(5, 6, 7, 8), ChunkyPNG::Color.rgba(4, 3, 2, 1), ChunkyPNG::Color.rgba(8, 7, 6, 5), ]) expect(canvas.to_rgba_stream).to eql [1, 2, 3, 4, 5, 6, 7, 8, 4, 3, 2, 1, 8, 7, 6, 5].pack("C16") end it "should export an image to an RGBA datastream correctly" do expect(reference_canvas("pixelstream_reference").to_rgba_stream).to eql resource_data("pixelstream.rgba") end end describe "#to_rgb_stream" do it "should export a sample canvas to an RGBA stream correctly" do canvas = ChunkyPNG::Canvas.new(2, 2, [ ChunkyPNG::Color.rgba(1, 2, 3, 4), ChunkyPNG::Color.rgba(5, 6, 7, 8), ChunkyPNG::Color.rgba(4, 3, 2, 1), ChunkyPNG::Color.rgba(8, 7, 6, 5), ]) expect(canvas.to_rgb_stream).to eql [1, 2, 3, 5, 6, 7, 4, 3, 2, 8, 7, 6].pack("C12") end it "should export an image to an RGB datastream correctly" do expect(reference_canvas("pixelstream_reference").to_rgb_stream).to eql resource_data("pixelstream.rgb") end end describe "#to_grayscale_stream" do it "should export a grayscale image to a grayscale datastream correctly" do canvas = ChunkyPNG::Canvas.new(2, 2, [ ChunkyPNG::Color.grayscale(1), ChunkyPNG::Color.grayscale(2), ChunkyPNG::Color.grayscale(3), ChunkyPNG::Color.grayscale(4), ]) expect(canvas.to_grayscale_stream).to eql [1, 2, 3, 4].pack("C4") end it "should export a color image to a grayscale datastream, using B values" do canvas = ChunkyPNG::Canvas.new(2, 2, [ ChunkyPNG::Color.rgba(1, 2, 3, 4), ChunkyPNG::Color.rgba(5, 6, 7, 8), ChunkyPNG::Color.rgba(4, 3, 2, 1), ChunkyPNG::Color.rgba(8, 7, 6, 5), ]) expect(canvas.to_grayscale_stream).to eql [3, 7, 2, 6].pack("C4") end end describe "#to_alpha_channel_stream" do it "should export an opaque image to an alpha channel datastream correctly" do grayscale_array = Array.new(reference_canvas("pixelstream_reference").pixels.length, 255) expect(reference_canvas("pixelstream_reference").to_alpha_channel_stream).to eql grayscale_array.pack("C*") end it "should export a transparent image to an alpha channel datastream correctly" do canvas = ChunkyPNG::Canvas.new(2, 2, [ ChunkyPNG::Color.rgba(1, 2, 3, 4), ChunkyPNG::Color.rgba(5, 6, 7, 8), ChunkyPNG::Color.rgba(4, 3, 2, 1), ChunkyPNG::Color.rgba(8, 7, 6, 5), ]) expect(canvas.to_alpha_channel_stream).to eql [4, 8, 1, 5].pack("C4") end end end chunky_png-1.3.15/spec/chunky_png/canvas/data_url_importing_spec.rb0000644000175000017500000000076313766004353025031 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Canvas do describe ".from_data_url" do it "should import an image from a data URL" do data_url = reference_canvas("operations").to_data_url expect(ChunkyPNG::Canvas.from_data_url(data_url)).to eql reference_canvas("operations") end it "should raise an exception if the string is not a proper data URL" do expect { ChunkyPNG::Canvas.from_data_url("whatever") }.to raise_error(ChunkyPNG::SignatureMismatch) end end end chunky_png-1.3.15/spec/chunky_png/canvas/stream_importing_spec.rb0000644000175000017500000000200113766004353024514 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Canvas do describe ".from_rgb_stream" do it "should load an image correctly from a datastream" do File.open(resource_file("pixelstream.rgb")) do |stream| matrix = ChunkyPNG::Canvas.from_rgb_stream(240, 180, stream) expect(matrix).to eql reference_canvas("pixelstream_reference") end end end describe ".from_bgr_stream" do it "should load an image correctly from a datastream" do File.open(resource_file("pixelstream.bgr")) do |stream| matrix = ChunkyPNG::Canvas.from_bgr_stream(240, 180, stream) expect(matrix).to eql reference_canvas("pixelstream_reference") end end end describe ".from_rgba_stream" do it "should load an image correctly from a datastream" do File.open(resource_file("pixelstream.rgba")) do |stream| matrix = ChunkyPNG::Canvas.from_rgba_stream(240, 180, stream) expect(matrix).to eql reference_canvas("pixelstream_reference") end end end end chunky_png-1.3.15/spec/chunky_png/canvas/resampling_spec.rb0000644000175000017500000001053313766004353023303 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Canvas::Resampling do subject { reference_canvas("clock") } describe "#resample_nearest_neighbor" do it "should downscale from 2x2 to 1x1 correctly" do canvas = ChunkyPNG::Canvas.new(2, 2, [1, 2, 3, 4]) expect(canvas.resample_nearest_neighbor(1, 1)).to eql ChunkyPNG::Canvas.new(1, 1, [4]) end it "should upscale from 2x2 to 4x4 correctly" do canvas = ChunkyPNG::Canvas.new(2, 2, [1, 2, 3, 4]) expect(canvas.resample_nearest_neighbor(4, 4)).to eql ChunkyPNG::Canvas.new(4, 4, [1, 1, 2, 2, 1, 1, 2, 2, 3, 3, 4, 4, 3, 3, 4, 4]) end it "should upscale both axis of the image" do expect(subject.resample_nearest_neighbor(45, 45)).to eql reference_canvas("clock_nn_xup_yup") end it "should downscale both axis of the image" do expect(subject.resample_nearest_neighbor(12, 12)).to eql reference_canvas("clock_nn_xdown_ydown") end it "should downscale the x-axis and upscale the y-axis of the image" do expect(subject.resample_nearest_neighbor(20, 50)).to eql reference_canvas("clock_nn_xdown_yup") end it "should not return itself" do subject.resample_nearest_neighbor(1, 1).should_not equal(subject) end it "should not change the original image's dimensions" do expect { subject.resample_nearest_neighbor(1, 1) }.to_not change { subject.dimension } end end describe "#resample_nearest_neighbor!" do it "should upscale both axis of the image" do subject.resample_nearest_neighbor!(45, 45) expect(subject).to eql reference_canvas("clock_nn_xup_yup") end it "should downscale both axis of the image" do subject.resample_nearest_neighbor!(12, 12) expect(subject).to eql reference_canvas("clock_nn_xdown_ydown") end it "should downscale the x-axis and upscale the y-axis of the image" do subject.resample_nearest_neighbor!(20, 50) expect(subject).to eql reference_canvas("clock_nn_xdown_yup") end it "should return itself" do expect(subject.resample_nearest_neighbor!(1, 1)).to equal(subject) end it "should change the original image's dimensions" do expect { subject.resample_nearest_neighbor!(1, 1) }.to change { subject.dimension }.to(ChunkyPNG::Dimension("1x1")) end end describe "#resample_bilinear" do it "should downscale from 2x2 to 1x1 correctly" do canvas = ChunkyPNG::Canvas.new(2, 2, [1, 2, 3, 4]) expect(canvas.resample_bilinear(1, 1)).to eql ChunkyPNG::Canvas.new(1, 1, [2]) end it "should upscale from 2x2 to 4x4 correctly" do canvas = ChunkyPNG::Canvas.new(2, 2, [1, 2, 3, 4]) expect(canvas.resample_bilinear(4, 4)).to eql ChunkyPNG::Canvas.new(4, 4, [1, 2, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4, 3, 3, 4, 4]) end it "should upscale both axis of the image" do expect(subject.resample_bilinear(45, 45)).to eql reference_canvas("clock_bl_xup_yup") end it "should downscale both axis of the image" do expect(subject.resample_bilinear(12, 12)).to eql reference_canvas("clock_bl_xdown_ydown") end it "should downscale the x-axis and upscale the y-axis of the image" do expect(subject.resample_bilinear(20, 50)).to eql reference_canvas("clock_bl_xdown_yup") end it "should not return itself" do subject.resample_bilinear(1, 1).should_not equal(subject) end it "should not change the original image's dimensions" do expect { subject.resample_bilinear(1, 1) }.to_not change { subject.dimension } end end describe "#resample_bilinear!" do it "should upscale both axis of the image" do subject.resample_bilinear!(45, 45) expect(subject).to eql reference_canvas("clock_bl_xup_yup") end it "should downscale both axis of the image" do subject.resample_bilinear!(12, 12) expect(subject).to eql reference_canvas("clock_bl_xdown_ydown") end it "should downscale the x-axis and upscale the y-axis of the image" do subject.resample_bilinear!(20, 50) expect(subject).to eql reference_canvas("clock_bl_xdown_yup") end it "should return itself" do expect(subject.resample_bilinear!(1, 1)).to equal(subject) end it "should change the original image's dimensions" do expect { subject.resample_bilinear!(1, 1) }.to change { subject.dimension }.to(ChunkyPNG::Dimension("1x1")) end end end chunky_png-1.3.15/spec/chunky_png/canvas/adam7_interlacing_spec.rb0000644000175000017500000000710113766004353024507 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Canvas::Adam7Interlacing do include ChunkyPNG::Canvas::Adam7Interlacing describe "#adam7_pass_sizes" do it "should get the pass sizes for a 8x8 image correctly" do expect(adam7_pass_sizes(8, 8)).to eql [ [1, 1], [1, 1], [2, 1], [2, 2], [4, 2], [4, 4], [8, 4], ] end it "should get the pass sizes for a 12x12 image correctly" do expect(adam7_pass_sizes(12, 12)).to eql [ [2, 2], [1, 2], [3, 1], [3, 3], [6, 3], [6, 6], [12, 6], ] end it "should get the pass sizes for a 33x47 image correctly" do expect(adam7_pass_sizes(33, 47)).to eql [ [5, 6], [4, 6], [9, 6], [8, 12], [17, 12], [16, 24], [33, 23], ] end it "should get the pass sizes for a 1x1 image correctly" do expect(adam7_pass_sizes(1, 1)).to eql [ [1, 1], [0, 1], [1, 0], [0, 1], [1, 0], [0, 1], [1, 0], ] end it "should get the pass sizes for a 0x0 image correctly" do expect(adam7_pass_sizes(0, 0)).to eql [ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], ] end it "should always maintain the same amount of pixels in total" do [[8, 8], [12, 12], [33, 47], [1, 1], [0, 0]].each do |(width, height)| pass_sizes = adam7_pass_sizes(width, height) expect(pass_sizes.inject(0) { |sum, (w, h)| sum + (w * h) }).to eql width * height end end end describe "#adam7_multiplier_offset" do it "should get the multiplier and offset values for pass 1 correctly" do expect(adam7_multiplier_offset(0)).to eql [3, 0, 3, 0] end it "should get the multiplier and offset values for pass 2 correctly" do expect(adam7_multiplier_offset(1)).to eql [3, 4, 3, 0] end it "should get the multiplier and offset values for pass 3 correctly" do expect(adam7_multiplier_offset(2)).to eql [2, 0, 3, 4] end it "should get the multiplier and offset values for pass 4 correctly" do expect(adam7_multiplier_offset(3)).to eql [2, 2, 2, 0] end it "should get the multiplier and offset values for pass 5 correctly" do expect(adam7_multiplier_offset(4)).to eql [1, 0, 2, 2] end it "should get the multiplier and offset values for pass 6 correctly" do expect(adam7_multiplier_offset(5)).to eql [1, 1, 1, 0] end it "should get the multiplier and offset values for pass 7 correctly" do expect(adam7_multiplier_offset(6)).to eql [0, 0, 1, 1] end end describe "#adam7_merge_pass" do it "should merge the submatrices correctly" do submatrices = [ ChunkyPNG::Canvas.new(1, 1, 168430335), # r = 10 ChunkyPNG::Canvas.new(1, 1, 336860415), # r = 20 ChunkyPNG::Canvas.new(2, 1, 505290495), # r = 30 ChunkyPNG::Canvas.new(2, 2, 677668095), # r = 40 ChunkyPNG::Canvas.new(4, 2, 838912255), # r = 50 ChunkyPNG::Canvas.new(4, 4, 1023344895), # r = 60 ChunkyPNG::Canvas.new(8, 4, 1175063295), # r = 70 ] canvas = ChunkyPNG::Image.new(8, 8) submatrices.each_with_index { |m, pass| adam7_merge_pass(pass, canvas, m) } expect(canvas).to eql reference_image("adam7") end end describe "#adam7_extract_pass" do before(:each) { @canvas = reference_canvas("adam7") } 1.upto(7) do |pass| it "should extract pass #{pass} correctly" do sm = adam7_extract_pass(pass - 1, @canvas) expect(sm.pixels.length).to eql sm.width * sm.height expect(sm.pixels.uniq.length).to eql 1 expect(ChunkyPNG::Color.r(sm[0, 0])).to eql pass * 10 end end end end chunky_png-1.3.15/spec/chunky_png/canvas/operations_spec.rb0000644000175000017500000003124013766004353023323 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Canvas::Operations do subject { reference_canvas("operations") } describe "#grayscale" do it "should not return itself" do subject.grayscale.should_not equal(subject) end it "should convert the image correctly" do expect(subject.grayscale).to eql reference_canvas("operations_grayscale") end it "should not adjust the current image" do expect { subject.grayscale }.to_not change { subject.pixels } end end describe "#grayscale!" do it "should return itself" do expect(subject.grayscale!).to equal(subject) end it "should convert the image correctly" do subject.grayscale! expect(subject).to eql reference_canvas("operations_grayscale") end end describe "#crop" do it "should crop the right pixels from the original canvas" do expect(subject.crop(10, 5, 4, 8)).to eql reference_canvas("cropped") end it "should not return itself" do subject.crop(10, 5, 4, 8).should_not equal(subject) end it "should not adjust the current image" do expect { subject.crop(10, 5, 4, 8) }.to_not change { subject.pixels } end it "should raise an exception when the cropped image falls outside the oiginal image" do expect { subject.crop(16, 16, 2, 2) }.to raise_error(ChunkyPNG::OutOfBounds) end end describe "#crop!" do context "when cropping both width and height" do let(:crop_opts) { [10, 5, 4, 8] } it "should crop the right pixels from the original canvas" do subject.crop!(*crop_opts) expect(subject).to eql reference_canvas("cropped") end it "should have a new width and height" do expect { subject.crop!(*crop_opts) }.to change { subject.dimension } .from(ChunkyPNG::Dimension("16x16")) .to(ChunkyPNG::Dimension("4x8")) end it "should return itself" do expect(subject.crop!(*crop_opts)).to equal(subject) end end context "when cropping just the height" do let(:crop_opts) { [0, 5, 16, 8] } it "should crop the right pixels from the original canvas" do subject.crop!(*crop_opts) expect(subject).to eql reference_canvas("cropped_height") end it "should have a new width and height" do expect { subject.crop!(*crop_opts) }.to change { subject.dimension } .from(ChunkyPNG::Dimension("16x16")) .to(ChunkyPNG::Dimension("16x8")) end it "should return itself" do expect(subject.crop!(*crop_opts)).to equal(subject) end end context "when the cropped image falls outside the original image" do it "should raise an exception" do expect { subject.crop!(16, 16, 2, 2) }.to raise_error(ChunkyPNG::OutOfBounds) end end end describe "#compose" do it "should compose pixels correctly" do subcanvas = ChunkyPNG::Canvas.new(4, 8, ChunkyPNG::Color.rgba(0, 0, 0, 75)) expect(subject.compose(subcanvas, 8, 4)).to eql reference_canvas("composited") end it "should leave the original intact" do subject.compose(ChunkyPNG::Canvas.new(1, 1)) expect(subject).to eql reference_canvas("operations") end it "should not return itself" do subject.compose(ChunkyPNG::Canvas.new(1, 1)).should_not equal(subject) end it "should raise an exception when the pixels to compose fall outside the image" do expect { subject.compose(ChunkyPNG::Canvas.new(1, 1), 16, 16) }.to raise_error(ChunkyPNG::OutOfBounds) end end describe "#compose!" do it "should compose pixels correctly" do subcanvas = ChunkyPNG::Canvas.new(4, 8, ChunkyPNG::Color.rgba(0, 0, 0, 75)) subject.compose!(subcanvas, 8, 4) expect(subject).to eql reference_canvas("composited") end it "should return itself" do expect(subject.compose!(ChunkyPNG::Canvas.new(1, 1))).to equal(subject) end it "should compose a base image and mask correctly" do base = reference_canvas("clock_base") mask = reference_canvas("clock_mask_updated") base.compose!(mask) expect(base).to eql reference_canvas("clock_updated") end it "should raise an exception when the pixels to compose fall outside the image" do expect { subject.compose!(ChunkyPNG::Canvas.new(1, 1), 16, 16) }.to raise_error(ChunkyPNG::OutOfBounds) end end describe "#replace" do it "should replace the correct pixels" do subcanvas = ChunkyPNG::Canvas.new(3, 2, ChunkyPNG::Color.rgb(200, 255, 0)) expect(subject.replace(subcanvas, 5, 4)).to eql reference_canvas("replaced") end it "should not return itself" do subject.replace(ChunkyPNG::Canvas.new(1, 1)).should_not equal(subject) end it "should leave the original intact" do subject.replace(ChunkyPNG::Canvas.new(1, 1)) expect(subject).to eql reference_canvas("operations") end it "should raise an exception when the pixels to replace fall outside the image" do expect { subject.replace(ChunkyPNG::Canvas.new(1, 1), 16, 16) }.to raise_error(ChunkyPNG::OutOfBounds) end end describe "#replace!" do it "should replace the correct pixels" do subcanvas = ChunkyPNG::Canvas.new(3, 2, ChunkyPNG::Color.rgb(200, 255, 0)) subject.replace!(subcanvas, 5, 4) expect(subject).to eql reference_canvas("replaced") end it "should return itself" do expect(subject.replace!(ChunkyPNG::Canvas.new(1, 1))).to equal(subject) end it "should raise an exception when the pixels to replace fall outside the image" do expect { subject.replace!(ChunkyPNG::Canvas.new(1, 1), 16, 16) }.to raise_error(ChunkyPNG::OutOfBounds) end end end describe ChunkyPNG::Canvas::Operations do subject { ChunkyPNG::Canvas.new(2, 3, [1, 2, 3, 4, 5, 6]) } describe "#flip_horizontally!" do it "should flip the pixels horizontally in place" do subject.flip_horizontally! expect(subject).to eql ChunkyPNG::Canvas.new(2, 3, [5, 6, 3, 4, 1, 2]) end it "should return itself" do expect(subject.flip_horizontally!).to equal(subject) end end describe "#flip_horizontally" do it "should flip the pixels horizontally" do expect(subject.flip_horizontally).to eql ChunkyPNG::Canvas.new(2, 3, [5, 6, 3, 4, 1, 2]) end it "should not return itself" do subject.flip_horizontally.should_not equal(subject) end it "should return a copy of itself when applied twice" do expect(subject.flip_horizontally.flip_horizontally).to eql subject end end describe "#flip_vertically!" do it "should flip the pixels vertically" do subject.flip_vertically! expect(subject).to eql ChunkyPNG::Canvas.new(2, 3, [2, 1, 4, 3, 6, 5]) end it "should return itself" do expect(subject.flip_horizontally!).to equal(subject) end end describe "#flip_vertically" do it "should flip the pixels vertically" do expect(subject.flip_vertically).to eql ChunkyPNG::Canvas.new(2, 3, [2, 1, 4, 3, 6, 5]) end it "should not return itself" do subject.flip_horizontally.should_not equal(subject) end it "should return a copy of itself when applied twice" do expect(subject.flip_vertically.flip_vertically).to eql subject end end describe "#rotate_left" do it "should rotate the pixels 90 degrees counter-clockwise" do expect(subject.rotate_left).to eql ChunkyPNG::Canvas.new(3, 2, [2, 4, 6, 1, 3, 5]) end it "should not return itself" do subject.rotate_left.should_not equal(subject) end it "should not change the image dimensions" do expect { subject.rotate_left }.to_not change { subject.dimension } end it "it should rotate 180 degrees when applied twice" do expect(subject.rotate_left.rotate_left).to eql subject.rotate_180 end it "it should rotate right when applied three times" do expect(subject.rotate_left.rotate_left.rotate_left).to eql subject.rotate_right end it "should return itself when applied four times" do expect(subject.rotate_left.rotate_left.rotate_left.rotate_left).to eql subject end end describe "#rotate_left!" do it "should rotate the pixels 90 degrees clockwise" do subject.rotate_left! expect(subject).to eql ChunkyPNG::Canvas.new(3, 2, [2, 4, 6, 1, 3, 5]) end it "should return itself" do expect(subject.rotate_left!).to equal(subject) end it "should change the image dimensions" do expect { subject.rotate_left! }.to change { subject.dimension } .from(ChunkyPNG::Dimension("2x3")).to(ChunkyPNG::Dimension("3x2")) end end describe "#rotate_right" do it "should rotate the pixels 90 degrees clockwise" do expect(subject.rotate_right).to eql ChunkyPNG::Canvas.new(3, 2, [5, 3, 1, 6, 4, 2]) end it "should not return itself" do subject.rotate_right.should_not equal(subject) end it "should not change the image dimensions" do expect { subject.rotate_right }.to_not change { subject.dimension } end it "it should rotate 180 degrees when applied twice" do expect(subject.rotate_right.rotate_right).to eql subject.rotate_180 end it "it should rotate left when applied three times" do expect(subject.rotate_right.rotate_right.rotate_right).to eql subject.rotate_left end it "should return itself when applied four times" do expect(subject.rotate_right.rotate_right.rotate_right.rotate_right).to eql subject end end describe "#rotate_right!" do it "should rotate the pixels 90 degrees clockwise" do subject.rotate_right! expect(subject).to eql ChunkyPNG::Canvas.new(3, 2, [5, 3, 1, 6, 4, 2]) end it "should return itself" do expect(subject.rotate_right!).to equal(subject) end it "should change the image dimensions" do expect { subject.rotate_right! }.to change { subject.dimension } .from(ChunkyPNG::Dimension("2x3")) .to(ChunkyPNG::Dimension("3x2")) end end describe "#rotate_180" do it "should rotate the pixels 180 degrees" do expect(subject.rotate_180).to eql ChunkyPNG::Canvas.new(2, 3, [6, 5, 4, 3, 2, 1]) end it "should return not itself" do subject.rotate_180.should_not equal(subject) end it "should return a copy of itself when applied twice" do expect(subject.rotate_180.rotate_180).to eql subject end end describe "#rotate_180!" do it "should rotate the pixels 180 degrees" do subject.rotate_180! expect(subject).to eql ChunkyPNG::Canvas.new(2, 3, [6, 5, 4, 3, 2, 1]) end it "should return itself" do expect(subject.rotate_180!).to equal(subject) end end end describe ChunkyPNG::Canvas::Operations do subject { ChunkyPNG::Canvas.new(4, 4).rect(1, 1, 2, 2, 255, 255) } describe "#trim" do it "should trim the border" do expect(subject.trim).to eql ChunkyPNG::Canvas.new(2, 2, 255) end it "should not return itself" do subject.trim.should_not equal(subject) end it "should be able to fail to trim a specified color" do expect { subject.trim(ChunkyPNG::Color::BLACK) }.to_not change { subject.pixels } end it "should be the same after trimming an added border" do expect(subject.border(2).trim).to eql subject end end describe "#trim!" do it "should trim the border" do subject.trim! expect(subject).to eql ChunkyPNG::Canvas.new(2, 2, 255) end it "should return itself" do expect(subject.trim!).to equal(subject) end it "should change the image dimensions" do expect { subject.trim! }.to change { subject.dimension } .from(ChunkyPNG::Dimension("4x4")) .to(ChunkyPNG::Dimension("2x2")) end end end describe ChunkyPNG::Canvas::Operations do subject { ChunkyPNG::Canvas.new(4, 4) } describe "#border" do it "should add the border" do expect(subject.border(2)).to eql reference_canvas("operations_border") end it "should not return itself" do subject.border(1).should_not equal(subject) end it "should retain transparency" do expect(ChunkyPNG::Canvas.new(1, 1).border(1).pixels).to include(0) end end describe "#border!" do it "should add the border" do subject.border!(2) expect(subject).to eql reference_canvas("operations_border") end it "should return itself" do expect(subject.border!(1)).to equal(subject) end it "should retain transparency" do subject.border!(1) expect(subject.pixels).to include(0) end it "should change the image dimensions" do expect { subject.border!(1) }.to change { subject.dimension } .from(ChunkyPNG::Dimension("4x4")) .to(ChunkyPNG::Dimension("6x6")) end end end chunky_png-1.3.15/spec/chunky_png/canvas/masking_spec.rb0000644000175000017500000000321513766004353022572 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Canvas::Masking do subject { reference_canvas("clock") } before(:all) do @theme_color = ChunkyPNG::Color("#e10f7a") @new_color = ChunkyPNG::Color("#ff0000") @background_color = ChunkyPNG::Color("white") end describe "#change_theme_color!" do it "should change the theme color correctly" do subject.change_theme_color!(@theme_color, @new_color) expect(subject).to eql reference_canvas("clock_updated") end end describe "#extract_mask" do it "should create the correct base and mask image" do base, mask = subject.extract_mask(@theme_color, @background_color) expect(base).to eql reference_canvas("clock_base") expect(mask).to eql reference_canvas("clock_mask") end it "should create a mask image with only one opaque color" do _, mask = subject.extract_mask(@theme_color, @background_color) expect(mask.palette.opaque_palette.size).to eql 1 end end describe "#change_mask_color!" do before { @mask = reference_canvas("clock_mask") } it "should replace the mask color correctly" do @mask.change_mask_color!(@new_color) expect(@mask).to eql reference_canvas("clock_mask_updated") end it "should still only have one opaque color" do @mask.change_mask_color!(@new_color) expect(@mask.palette.opaque_palette.size).to eql 1 end it "should raise an exception when the mask image has more than once color" do not_a_mask = reference_canvas("operations") expect { not_a_mask.change_mask_color!(@new_color) }.to raise_error(ChunkyPNG::ExpectationFailed) end end end chunky_png-1.3.15/spec/chunky_png/canvas/png_decoding_spec.rb0000644000175000017500000001066613766004353023571 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Canvas::PNGDecoding do include ChunkyPNG::Canvas::PNGDecoding describe "#decode_png_scanline" do it "should decode a line without filtering as is" do stream = [ChunkyPNG::FILTER_NONE, 255, 255, 255, 255, 255, 255, 255, 255, 255].pack("C*") decode_png_str_scanline(stream, 0, nil, 9, 3) expect(stream.unpack("@1C*")).to eql [255, 255, 255, 255, 255, 255, 255, 255, 255] end it "should decode a line with sub filtering correctly" do # all white pixels stream = [ChunkyPNG::FILTER_SUB, 255, 255, 255, 0, 0, 0, 0, 0, 0].pack("C*") decode_png_str_scanline(stream, 0, nil, 9, 3) expect(stream.unpack("@1C*")).to eql [255, 255, 255, 255, 255, 255, 255, 255, 255] # all black pixels stream = [ChunkyPNG::FILTER_SUB, 0, 0, 0, 0, 0, 0, 0, 0, 0].pack("C*") decode_png_str_scanline(stream, 0, nil, 9, 3) expect(stream.unpack("@1C*")).to eql [0, 0, 0, 0, 0, 0, 0, 0, 0] # various colors stream = [ChunkyPNG::FILTER_SUB, 255, 0, 45, 0, 255, 0, 112, 200, 178].pack("C*") decode_png_str_scanline(stream, 0, nil, 9, 3) expect(stream.unpack("@1C*")).to eql [255, 0, 45, 255, 255, 45, 111, 199, 223] end it "should decode a line with up filtering correctly" do # previous line has various pixels previous = [ChunkyPNG::FILTER_UP, 255, 255, 255, 127, 127, 127, 0, 0, 0] current = [ChunkyPNG::FILTER_UP, 0, 127, 255, 0, 127, 255, 0, 127, 255] stream = (previous + current).pack("C*") decode_png_str_scanline(stream, 10, 0, 9, 3) expect(stream.unpack("@11C9")).to eql [255, 126, 254, 127, 254, 126, 0, 127, 255] end it "should decode a line with average filtering correctly" do previous = [ChunkyPNG::FILTER_AVERAGE, 10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120] # rubocop:disable Layout/ExtraSpacing current = [ChunkyPNG::FILTER_AVERAGE, 0, 0, 10, 23, 15, 13, 23, 63, 38, 60, 253, 53] # rubocop:disable Layout/ExtraSpacing stream = (previous + current).pack("C*") decode_png_str_scanline(stream, 13, 0, 12, 3) expect(stream.unpack("@14C12")).to eql [5, 10, 25, 45, 45, 55, 80, 125, 105, 150, 114, 165] end it "should decode a line with paeth filtering correctly" do previous = [ChunkyPNG::FILTER_PAETH, 10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120] # rubocop:disable Layout/ExtraSpacing current = [ChunkyPNG::FILTER_PAETH, 0, 0, 10, 20, 10, 0, 0, 40, 10, 20, 190, 0] # rubocop:disable Layout/ExtraSpacing stream = (previous + current).pack("C*") decode_png_str_scanline(stream, 13, 0, 12, 3) expect(stream.unpack("@14C12")).to eql [10, 20, 40, 60, 60, 60, 70, 120, 90, 120, 54, 120] end end describe "#decode_png_extract_4bit_value" do it "should extract the high bits successfully" do expect(decode_png_extract_4bit_value("10010110".to_i(2), 0)).to eql "1001".to_i(2) end it "should extract the low bits successfully" do expect(decode_png_extract_4bit_value("10010110".to_i(2), 17)).to eql "0110".to_i(2) end end describe "#decode_png_extract_2bit_value" do it "should extract the first 2 bits successfully" do expect(decode_png_extract_2bit_value("10010110".to_i(2), 0)).to eql "10".to_i(2) end it "should extract the second 2 bits successfully" do expect(decode_png_extract_2bit_value("10010110".to_i(2), 5)).to eql "01".to_i(2) end it "should extract the third 2 bits successfully" do expect(decode_png_extract_2bit_value("10010110".to_i(2), 2)).to eql "01".to_i(2) end it "should extract the low two bits successfully" do expect(decode_png_extract_2bit_value("10010110".to_i(2), 7)).to eql "10".to_i(2) end end describe "#decode_png_extract_1bit_value" do it "should extract all separate bits correctly" do expect(decode_png_extract_1bit_value("10010110".to_i(2), 0)).to eql 1 expect(decode_png_extract_1bit_value("10010110".to_i(2), 1)).to eql 0 expect(decode_png_extract_1bit_value("10010110".to_i(2), 2)).to eql 0 expect(decode_png_extract_1bit_value("10010110".to_i(2), 3)).to eql 1 expect(decode_png_extract_1bit_value("10010110".to_i(2), 4)).to eql 0 expect(decode_png_extract_1bit_value("10010110".to_i(2), 5)).to eql 1 expect(decode_png_extract_1bit_value("10010110".to_i(2), 6)).to eql 1 expect(decode_png_extract_1bit_value("10010110".to_i(2), 7)).to eql 0 end end end chunky_png-1.3.15/spec/chunky_png/canvas/drawing_spec.rb0000644000175000017500000001636713766004353022610 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Canvas::Drawing do describe "#compose_pixel" do subject { ChunkyPNG::Canvas.new(1, 1, ChunkyPNG::Color.rgb(200, 150, 100)) } it "should compose colors correctly" do subject.compose_pixel(0, 0, ChunkyPNG::Color(100, 150, 200, 128)) expect(subject[0, 0]).to eql ChunkyPNG::Color(150, 150, 150) end it "should return the composed color" do expect(subject.compose_pixel(0, 0, ChunkyPNG::Color.rgba(100, 150, 200, 128))).to eql ChunkyPNG::Color.rgb(150, 150, 150) end it "should do nothing when the coordinates are out of bounds" do expect(subject.compose_pixel(1, -1, :black)).to be_nil expect { subject.compose_pixel(1, -1, :black) }.to_not change { subject[0, 0] } end end describe "#line" do it "should draw lines correctly with anti-aliasing" do canvas = ChunkyPNG::Canvas.new(31, 31, ChunkyPNG::Color::WHITE) # rubocop:disable Layout/SpaceInsideParens # for improved readability canvas.line( 0, 0, 30, 30, ChunkyPNG::Color::BLACK) canvas.line( 0, 30, 30, 0, ChunkyPNG::Color::BLACK) canvas.line(15, 30, 15, 0, ChunkyPNG::Color.rgba(200, 0, 0, 128)) canvas.line( 0, 15, 30, 15, ChunkyPNG::Color.rgba(200, 0, 0, 128)) canvas.line(30, 30, 0, 15, ChunkyPNG::Color.rgba( 0, 200, 0, 128), false) canvas.line( 0, 15, 30, 0, ChunkyPNG::Color.rgba( 0, 200, 0, 128)) canvas.line( 0, 30, 15, 0, ChunkyPNG::Color.rgba( 0, 0, 200, 128), false) canvas.line(15, 0, 30, 30, ChunkyPNG::Color.rgba( 0, 0, 200, 128)) # rubocop:enable Layout/SpaceInsideParens expect(canvas).to eql reference_canvas("lines") end it "should draw partial lines if the coordinates are partially out of bounds" do canvas = ChunkyPNG::Canvas.new(1, 2, ChunkyPNG::Color::WHITE) canvas.line(-5, -5, 0, 0, "#000000") expect(canvas.pixels).to eql [ChunkyPNG::Color::BLACK, ChunkyPNG::Color::WHITE] end it "should return itself to allow chaining" do canvas = ChunkyPNG::Canvas.new(16, 16, ChunkyPNG::Color::WHITE) expect(canvas.line(1, 1, 10, 10, :black)).to equal(canvas) end it "should draw a single pixel when the start and end point are the same" do canvas = ChunkyPNG::Canvas.new(5, 5, ChunkyPNG::Color::WHITE) canvas.line(2, 2, 2, 2, ChunkyPNG::Color::BLACK) non_white_pixels = canvas.pixels.count { |pixel| pixel != ChunkyPNG::Color::WHITE } expect(non_white_pixels).to eql 1 end end describe "#rect" do subject { ChunkyPNG::Canvas.new(16, 16, "#ffffff") } it "should draw a rectangle with the correct colors" do subject.rect(1, 1, 10, 10, ChunkyPNG::Color.rgba(0, 255, 0, 80), ChunkyPNG::Color.rgba(255, 0, 0, 100)) subject.rect(5, 5, 14, 14, ChunkyPNG::Color.rgba(0, 0, 255, 160), ChunkyPNG::Color.rgba(255, 255, 0, 100)) expect(subject).to eql reference_canvas("rect") end it "should return itself to allow chaining" do expect(subject.rect(1, 1, 10, 10)).to equal(subject) end it "should draw partial rectangles if the coordinates are partially out of bounds" do subject.rect(0, 0, 20, 20, :black, :white) expect(subject[0, 0]).to eql ChunkyPNG::Color::BLACK end it "should draw the rectangle fill only if the coordinates are fully out of bounds" do subject.rect(-1, -1, 20, 20, :black, :white) expect(subject[0, 0]).to eql ChunkyPNG::Color::WHITE end end describe "#circle" do subject { ChunkyPNG::Canvas.new(32, 32, ChunkyPNG::Color.rgba(0, 0, 255, 128)) } it "should draw circles" do subject.circle(11, 11, 10, ChunkyPNG::Color("red @ 0.5"), ChunkyPNG::Color("white @ 0.2")) subject.circle(21, 21, 10, ChunkyPNG::Color("green @ 0.5")) expect(subject).to eql reference_canvas("circles") end it "should draw partial circles when going of the canvas bounds" do subject.circle(0, 0, 10, ChunkyPNG::Color(:red)) subject.circle(31, 16, 10, ChunkyPNG::Color(:black), ChunkyPNG::Color(:white, 0xaa)) expect(subject).to eql reference_canvas("partial_circles") end it "should return itself to allow chaining" do expect(subject.circle(10, 10, 5, :red)).to equal(subject) end end describe "#polygon" do subject { ChunkyPNG::Canvas.new(22, 22) } it "should draw an filled triangle when using 3 control points" do subject.polygon("(2,2) (20,5) (5,20)", ChunkyPNG::Color(:black, 0xaa), ChunkyPNG::Color(:red, 0x44)) expect(subject).to eql reference_canvas("polygon_triangle_filled") end it "should draw a unfilled polygon with 6 control points" do subject.polygon("(2,2) (12, 1) (20,5) (18,18) (5,20) (1,12)", ChunkyPNG::Color(:black)) expect(subject).to eql reference_canvas("polygon_unfilled") end it "should draw a vertically crossed filled polygon with 4 control points" do subject.polygon("(2,2) (21,2) (2,21) (21,21)", ChunkyPNG::Color(:black), ChunkyPNG::Color(:red)) expect(subject).to eql reference_canvas("polygon_filled_vertical") end it "should draw a vertically crossed filled polygon with 4 control points" do subject.polygon("(2,2) (2,21) (21,2) (21,21)", ChunkyPNG::Color(:black), ChunkyPNG::Color(:red)) expect(subject).to eql reference_canvas("polygon_filled_horizontal") end it "should return itself to allow chaining" do expect(subject.polygon("(2,2) (20,5) (5,20)")).to equal(subject) end end describe "#bezier_curve" do subject { ChunkyPNG::Canvas.new(24, 24, ChunkyPNG::Color::WHITE) } it "should draw a bezier curve starting at the first point" do subject.bezier_curve("3,20 10,10, 20,20") expect(subject[3, 20]).to eql ChunkyPNG::Color::BLACK end it "should draw a bezier curve ending at the last point" do subject.bezier_curve("3,20 10,10, 20,20") expect(subject[20, 20]).to eql ChunkyPNG::Color::BLACK end it "should draw a bezier curve with a color of green" do subject.bezier_curve("3,20 10,10, 20,20", :green) expect(subject[3, 20]).to eql ChunkyPNG::Color(:green) end it "should draw a three point bezier curve" do expect(subject.bezier_curve("1,23 12,10 23,23")).to eql reference_canvas("bezier_three_point") end it "should draw a three point bezier curve flipped" do expect(subject.bezier_curve("1,1 12,15 23,1")).to eql reference_canvas("bezier_three_point_flipped") end it "should draw a four point bezier curve" do expect(subject.bezier_curve("1,23 1,5 22,5 22,23")).to eql reference_canvas("bezier_four_point") end it "should draw a four point bezier curve flipped" do expect(subject.bezier_curve("1,1 1,19 22,19 22,1")).to eql reference_canvas("bezier_four_point_flipped") end it "should draw a four point bezier curve with a shape of an s" do expect(subject.bezier_curve("1,23 1,5 22,23 22,5")).to eql reference_canvas("bezier_four_point_s") end it "should draw a five point bezier curve" do expect(subject.bezier_curve("10,23 1,10 12,5 23,10 14,23")).to eql reference_canvas("bezier_five_point") end it "should draw a six point bezier curve" do expect(subject.bezier_curve("1,23 4,15 8,20 2,2 23,15 23,1")).to eql reference_canvas("bezier_six_point") end end end chunky_png-1.3.15/spec/chunky_png/canvas/data_url_exporting_spec.rb0000644000175000017500000000114213766004353025030 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Canvas do describe "#to_data_url" do it "should export a sample canvas to an RGBA stream correctly" do canvas = ChunkyPNG::Canvas.new(2, 2, [ ChunkyPNG::Color.rgba(1, 2, 3, 4), ChunkyPNG::Color.rgba(5, 6, 7, 8), ChunkyPNG::Color.rgba(4, 3, 2, 1), ChunkyPNG::Color.rgba(8, 7, 6, 5), ]) expect(canvas.to_data_url).to eql "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEUBAgMEAwIFBgcIBwazgAAdAAAABHRSTlMEAQgFhYDlfQAAAAxJREFUeJxjUmAKAAAAwAB1GNhIEwAAAABJRU5ErkJggg==" end end end chunky_png-1.3.15/spec/chunky_png/canvas/png_encoding_spec.rb0000644000175000017500000003032513766004353023575 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Canvas::PNGEncoding do include ChunkyPNG::Canvas::PNGEncoding context "determining encoding options" do [:indexed, :grayscale, :grayscale_alpha, :truecolor, :truecolor_alpha].each do |color_mode_name| it "should encode an image with color mode #{color_mode_name} correctly" do canvas = ChunkyPNG::Canvas.new(10, 10, ChunkyPNG::Color.rgb(100, 100, 100)) color_mode = ChunkyPNG.const_get("COLOR_#{color_mode_name.to_s.upcase}") blob = canvas.to_blob(color_mode: color_mode) ds = ChunkyPNG::Datastream.from_blob(blob) expect(ds.header_chunk.color).to eql color_mode expect(ChunkyPNG::Canvas.from_datastream(ds)).to eql ChunkyPNG::Canvas.new(10, 10, ChunkyPNG::Color.rgb(100, 100, 100)) end end it "should encode an image with 2 colors using 1-bit indexed color mode" do @canvas = ChunkyPNG::Canvas.from_file(png_suite_file("basic", "basn3p01.png")) ds = ChunkyPNG::Datastream.from_blob(@canvas.to_blob) expect(ds.header_chunk.color).to eql ChunkyPNG::COLOR_INDEXED expect(ds.header_chunk.depth).to eql 1 expect(@canvas).to eql ChunkyPNG::Canvas.from_datastream(ds) end it "should encode an image with 4 colors using 2-bit indexed color mode" do @canvas = ChunkyPNG::Canvas.from_file(png_suite_file("basic", "basn3p02.png")) ds = ChunkyPNG::Datastream.from_blob(@canvas.to_blob) expect(ds.header_chunk.color).to eql ChunkyPNG::COLOR_INDEXED expect(ds.header_chunk.depth).to eql 2 expect(@canvas).to eql ChunkyPNG::Canvas.from_datastream(ds) end it "should encode an image with 16 colors using 4-bit indexed color mode" do @canvas = ChunkyPNG::Canvas.from_file(png_suite_file("basic", "basn3p04.png")) ds = ChunkyPNG::Datastream.from_blob(@canvas.to_blob) expect(ds.header_chunk.color).to eql ChunkyPNG::COLOR_INDEXED expect(ds.header_chunk.depth).to eql 4 expect(@canvas).to eql ChunkyPNG::Canvas.from_datastream(ds) end it "should encode an image with 256 colors using 8-bit indexed color mode" do @canvas = ChunkyPNG::Canvas.from_file(png_suite_file("basic", "basn3p08.png")) ds = ChunkyPNG::Datastream.from_blob(@canvas.to_blob) expect(ds.header_chunk.color).to eql ChunkyPNG::COLOR_INDEXED expect(ds.header_chunk.depth).to eql 8 expect(@canvas).to eql ChunkyPNG::Canvas.from_datastream(ds) end it "should use a higher bit depth than necessary if requested" do @canvas = ChunkyPNG::Canvas.from_file(png_suite_file("basic", "basn3p01.png")) ds = ChunkyPNG::Datastream.from_blob(@canvas.to_blob(bit_depth: 4)) expect(ds.header_chunk.color).to eql ChunkyPNG::COLOR_INDEXED expect(ds.header_chunk.depth).to eql 4 expect(@canvas).to eql ChunkyPNG::Canvas.from_datastream(ds) end it "should encode an image with interlacing correctly" do input_canvas = ChunkyPNG::Canvas.from_file(resource_file("operations.png")) blob = input_canvas.to_blob(interlace: true) ds = ChunkyPNG::Datastream.from_blob(blob) expect(ds.header_chunk.interlace).to eql ChunkyPNG::INTERLACING_ADAM7 expect(ChunkyPNG::Canvas.from_datastream(ds)).to eql input_canvas end it "should save an image using the normal routine correctly" do canvas = reference_canvas("operations") expect(Zlib::Deflate).to receive(:deflate).with(anything, Zlib::DEFAULT_COMPRESSION).and_return("") canvas.to_blob end it "should save an image using the :fast_rgba routine correctly" do canvas = reference_canvas("operations") expect(canvas).to_not receive(:encode_png_str_scanline_none) expect(canvas).to_not receive(:encode_png_str_scanline_sub) expect(canvas).to_not receive(:encode_png_str_scanline_up) expect(canvas).to_not receive(:encode_png_str_scanline_average) expect(canvas).to_not receive(:encode_png_str_scanline_paeth) expect(Zlib::Deflate).to receive(:deflate).with(anything, Zlib::BEST_SPEED).and_return("") canvas.to_blob(:fast_rgba) end it "should save an image using the :good_compression routine correctly" do canvas = reference_canvas("operations") expect(canvas).to_not receive(:encode_png_str_scanline_none) expect(canvas).to_not receive(:encode_png_str_scanline_sub) expect(canvas).to_not receive(:encode_png_str_scanline_up) expect(canvas).to_not receive(:encode_png_str_scanline_average) expect(canvas).to_not receive(:encode_png_str_scanline_paeth) expect(Zlib::Deflate).to receive(:deflate).with(anything, Zlib::BEST_COMPRESSION).and_return("") canvas.to_blob(:good_compression) end it "should save an image using the :best_compression routine correctly" do canvas = reference_canvas("operations") expect(canvas).to receive(:encode_png_str_scanline_paeth).exactly(canvas.height).times expect(Zlib::Deflate).to receive(:deflate).with(anything, Zlib::BEST_COMPRESSION).and_return("") canvas.to_blob(:best_compression) end it "should save an image with black and white only if requested" do ds = ChunkyPNG::Datastream.from_blob(reference_canvas("lines").to_blob(:black_and_white)) expect(ds.header_chunk.color).to eql ChunkyPNG::COLOR_GRAYSCALE expect(ds.header_chunk.depth).to eql 1 end end describe "different color modes and bit depths" do before do @canvas = ChunkyPNG::Canvas.new(2, 2) # rubocop:disable Layout/ExtraSpacing, Layout/SpaceInsideParens @canvas[0, 0] = ChunkyPNG::Color.rgba( 1, 2, 3, 4) @canvas[1, 0] = ChunkyPNG::Color.rgba(252, 253, 254, 255) @canvas[0, 1] = ChunkyPNG::Color.rgba(255, 254, 253, 252) @canvas[1, 1] = ChunkyPNG::Color.rgba( 4, 3, 2, 1) # rubocop:enable Layout/ExtraSpacing, Layout/SpaceInsideParens @canvas.encoding_palette = @canvas.palette @canvas.encoding_palette.to_plte_chunk end it "should encode using 8-bit RGBA mode correctly" do stream = @canvas.encode_png_pixelstream(ChunkyPNG::COLOR_TRUECOLOR_ALPHA, 8, ChunkyPNG::INTERLACING_NONE, ChunkyPNG::FILTER_NONE) expect(stream).to eql "\0\x01\x02\x03\x04\xFC\xFD\xFE\xFF\0\xFF\xFE\xFD\xFC\x04\x03\x02\x01".force_encoding(Encoding::BINARY) end it "should encode using 8 bit RGB mode correctly" do stream = @canvas.encode_png_pixelstream(ChunkyPNG::COLOR_TRUECOLOR, 8, ChunkyPNG::INTERLACING_NONE, ChunkyPNG::FILTER_NONE) expect(stream).to eql "\0\x01\x02\x03\xFC\xFD\xFE\0\xFF\xFE\xFD\x04\x03\x02".force_encoding(Encoding::BINARY) end it "should encode using 1-bit grayscale mode correctly" do stream = @canvas.encode_png_pixelstream(ChunkyPNG::COLOR_GRAYSCALE, 1, ChunkyPNG::INTERLACING_NONE, ChunkyPNG::FILTER_NONE) expect(stream).to eql "\0\x40\0\x80".force_encoding(Encoding::BINARY) # Using the B byte of the pixel == 3, assuming R == G == B for grayscale images end it "should encode using 2-bit grayscale mode correctly" do stream = @canvas.encode_png_pixelstream(ChunkyPNG::COLOR_GRAYSCALE, 2, ChunkyPNG::INTERLACING_NONE, ChunkyPNG::FILTER_NONE) expect(stream).to eql "\0\x30\0\xC0".force_encoding(Encoding::BINARY) # Using the B byte of the pixel == 3, assuming R == G == B for grayscale images end it "should encode using 4-bit grayscale mode correctly" do stream = @canvas.encode_png_pixelstream(ChunkyPNG::COLOR_GRAYSCALE, 4, ChunkyPNG::INTERLACING_NONE, ChunkyPNG::FILTER_NONE) expect(stream).to eql "\0\x0F\0\xF0".force_encoding(Encoding::BINARY) # Using the B byte of the pixel == 3, assuming R == G == B for grayscale images end it "should encode using 8-bit grayscale mode correctly" do stream = @canvas.encode_png_pixelstream(ChunkyPNG::COLOR_GRAYSCALE, 8, ChunkyPNG::INTERLACING_NONE, ChunkyPNG::FILTER_NONE) expect(stream).to eql "\0\x03\xFE\0\xFD\x02".force_encoding(Encoding::BINARY) # Using the B byte of the pixel == 3, assuming R == G == B for grayscale images end it "should not encode using 1-bit indexed mode because the image has too many colors" do expect { @canvas.encode_png_pixelstream(ChunkyPNG::COLOR_INDEXED, 1, ChunkyPNG::INTERLACING_NONE, ChunkyPNG::FILTER_NONE) }.to raise_error(ChunkyPNG::ExpectationFailed) end it "should encode using 2-bit indexed mode correctly" do stream = @canvas.encode_png_pixelstream(ChunkyPNG::COLOR_INDEXED, 2, ChunkyPNG::INTERLACING_NONE, ChunkyPNG::FILTER_NONE) expect(stream).to eql "\0\x20\0\xD0".force_encoding(Encoding::BINARY) end it "should encode using 4-bit indexed mode correctly" do stream = @canvas.encode_png_pixelstream(ChunkyPNG::COLOR_INDEXED, 4, ChunkyPNG::INTERLACING_NONE, ChunkyPNG::FILTER_NONE) expect(stream).to eql "\0\x02\0\x31".force_encoding(Encoding::BINARY) end it "should encode using 8-bit indexed mode correctly" do stream = @canvas.encode_png_pixelstream(ChunkyPNG::COLOR_INDEXED, 8, ChunkyPNG::INTERLACING_NONE, ChunkyPNG::FILTER_NONE) expect(stream).to eql "\0\x00\x02\0\x03\x01".force_encoding(Encoding::BINARY) end end describe "different filter methods" do it "should encode a scanline without filtering correctly" do stream = [ChunkyPNG::FILTER_NONE, 0, 0, 0, 1, 1, 1, 2, 2, 2].pack("C*") encode_png_str_scanline_none(stream, 0, nil, 9, 3) expect(stream.unpack("C*")).to eql [ChunkyPNG::FILTER_NONE, 0, 0, 0, 1, 1, 1, 2, 2, 2] end it "should encode a scanline with sub filtering correctly" do stream = [ ChunkyPNG::FILTER_NONE, 255, 255, 255, 255, 255, 255, 255, 255, 255, ChunkyPNG::FILTER_NONE, 255, 255, 255, 255, 255, 255, 255, 255, 255, ].pack("C*") # Check line with previous line encode_png_str_scanline_sub(stream, 10, 0, 9, 3) expect(stream.unpack("@10C10")).to eql [ChunkyPNG::FILTER_SUB, 255, 255, 255, 0, 0, 0, 0, 0, 0] # Check line without previous line encode_png_str_scanline_sub(stream, 0, nil, 9, 3) expect(stream.unpack("@0C10")).to eql [ChunkyPNG::FILTER_SUB, 255, 255, 255, 0, 0, 0, 0, 0, 0] end it "should encode a scanline with up filtering correctly" do stream = [ ChunkyPNG::FILTER_NONE, 255, 255, 255, 255, 255, 255, 255, 255, 255, ChunkyPNG::FILTER_NONE, 255, 255, 255, 255, 255, 255, 255, 255, 255, ].pack("C*") # Check line with previous line encode_png_str_scanline_up(stream, 10, 0, 9, 3) expect(stream.unpack("@10C10")).to eql [ChunkyPNG::FILTER_UP, 0, 0, 0, 0, 0, 0, 0, 0, 0] # Check line without previous line encode_png_str_scanline_up(stream, 0, nil, 9, 3) expect(stream.unpack("@0C10")).to eql [ChunkyPNG::FILTER_UP, 255, 255, 255, 255, 255, 255, 255, 255, 255] end it "should encode a scanline with average filtering correctly" do stream = [ ChunkyPNG::FILTER_NONE, 10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120, # rubocop:disable Layout/ExtraSpacing ChunkyPNG::FILTER_NONE, 5, 10, 25, 45, 45, 55, 80, 125, 105, 150, 114, 165, # rubocop:disable Layout/ExtraSpacing ].pack("C*") # Check line with previous line encode_png_str_scanline_average(stream, 13, 0, 12, 3) expect(stream.unpack("@13C13")).to eql [ChunkyPNG::FILTER_AVERAGE, 0, 0, 10, 23, 15, 13, 23, 63, 38, 60, 253, 53] # Check line without previous line encode_png_str_scanline_average(stream, 0, nil, 12, 3) expect(stream.unpack("@0C13")).to eql [ChunkyPNG::FILTER_AVERAGE, 10, 20, 30, 35, 40, 45, 50, 55, 50, 65, 70, 80] end it "should encode a scanline with paeth filtering correctly" do stream = [ ChunkyPNG::FILTER_NONE, 10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120, # rubocop:disable Layout/ExtraSpacing ChunkyPNG::FILTER_NONE, 10, 20, 40, 60, 60, 60, 70, 120, 90, 120, 54, 120, # rubocop:disable Layout/ExtraSpacing ].pack("C*") # Check line with previous line encode_png_str_scanline_paeth(stream, 13, 0, 12, 3) expect(stream.unpack("@13C13")).to eql [ChunkyPNG::FILTER_PAETH, 0, 0, 10, 20, 10, 0, 0, 40, 10, 20, 190, 0] # Check line without previous line encode_png_str_scanline_paeth(stream, 0, nil, 12, 3) expect(stream.unpack("@0C13")).to eql [ChunkyPNG::FILTER_PAETH, 10, 20, 30, 30, 30, 30, 30, 30, 20, 30, 30, 40] end end end chunky_png-1.3.15/spec/chunky_png/vector_spec.rb0000644000175000017500000000653613766004353021201 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Vector do subject { ChunkyPNG::Vector.new([ChunkyPNG::Point.new(2, 5), ChunkyPNG::Point.new(1, 3), ChunkyPNG::Point.new(4, 6)]) } it { should respond_to(:points) } describe "#length" do it "shopuld have 3 items" do expect(subject.length).to eql(3) end end describe "#x_range" do it "should get the right range of x values" do expect(subject.x_range).to eql(1..4) end it "should find the minimum x-coordinate" do expect(subject.min_x).to eql(1) end it "should find the maximum x-coordinate" do expect(subject.max_x).to eql(4) end it "should calculate the width correctly" do expect(subject.width).to eql(4) end end describe "#y_range" do it "should get the right range of y values" do expect(subject.y_range).to eql(3..6) end it "should find the minimum x-coordinate" do expect(subject.min_y).to eql(3) end it "should find the maximum x-coordinate" do expect(subject.max_y).to eql(6) end it "should calculate the height correctly" do expect(subject.height).to eql(4) end end describe "#offset" do it "should return a ChunkyPNG::Point" do expect(subject.offset).to be_kind_of(ChunkyPNG::Point) end it "should use the mininum x and y coordinates as values for the point" do expect(subject.offset.x).to eql subject.min_x expect(subject.offset.y).to eql subject.min_y end end describe "#dimension" do it "should return a ChunkyPNG::Dimension" do expect(subject.dimension).to be_kind_of(ChunkyPNG::Dimension) end it "should use the width and height of the vector for the dimension" do expect(subject.dimension.width).to eql subject.width expect(subject.dimension.height).to eql subject.height end end describe "#edges" do it "should get three edges when closing the path" do expect(subject.edges(true).to_a).to eql [ [ChunkyPNG::Point.new(2, 5), ChunkyPNG::Point.new(1, 3)], [ChunkyPNG::Point.new(1, 3), ChunkyPNG::Point.new(4, 6)], [ChunkyPNG::Point.new(4, 6), ChunkyPNG::Point.new(2, 5)], ] end it "should get two edges when not closing the path" do expect(subject.edges(false).to_a).to eql [ [ChunkyPNG::Point.new(2, 5), ChunkyPNG::Point.new(1, 3)], [ChunkyPNG::Point.new(1, 3), ChunkyPNG::Point.new(4, 6)], ] end end end describe "ChunkyPNG.Vector" do let(:example) { ChunkyPNG::Vector.new([ChunkyPNG::Point.new(2, 4), ChunkyPNG::Point.new(1, 2), ChunkyPNG::Point.new(3, 6)]) } it "should return an empty vector when given an empty array" do expect(ChunkyPNG::Vector()).to eql ChunkyPNG::Vector.new([]) expect(ChunkyPNG::Vector(*[])).to eql ChunkyPNG::Vector.new([]) # rubocop:disable Lint/UnneededSplatExpansion end it "should raise an error when an odd number of numerics is given" do expect { ChunkyPNG::Vector(1, 2, 3) }.to raise_error(ArgumentError) end it "should create a vector from a string" do expect(ChunkyPNG::Vector("(2,4) (1,2) (3,6)")).to eql example end it "should create a vector from a flat array" do expect(ChunkyPNG::Vector(2, 4, 1, 2, 3, 6)).to eql example end it "should create a vector from a nested array" do expect(ChunkyPNG::Vector("(2,4)", [1, 2], x: 3, y: 6)).to eql example end end chunky_png-1.3.15/spec/chunky_png/image_spec.rb0000644000175000017500000000172413766004353020753 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Image do describe "#metadata" do it "should load metadata from an existing file" do image = ChunkyPNG::Image.from_file(resource_file("text_chunk.png")) expect(image.metadata["Title"]).to eql "My amazing icon!" expect(image.metadata["Author"]).to eql "Willem van Bergen" end it "should write metadata to the file correctly" do filename = resource_file("_metadata.png") image = ChunkyPNG::Image.new(10, 10) image.metadata["Title"] = "My amazing icon!" image.metadata["Author"] = "Willem van Bergen" image.save(filename) metadata = ChunkyPNG::Datastream.from_file(filename).metadata expect(metadata["Title"]).to eql "My amazing icon!" expect(metadata["Author"]).to eql "Willem van Bergen" end it "should load empty images correctly" do expect { ChunkyPNG::Image.from_file(resource_file("empty.png")) }.to_not raise_error end end end chunky_png-1.3.15/spec/chunky_png/rmagick_spec.rb0000644000175000017500000000121513766004353021301 0ustar danieldanielrequire "spec_helper" begin require "chunky_png/rmagick" describe ChunkyPNG::RMagick do it "should import an image from RMagick correctly" do image = Magick::Image.read(resource_file("composited.png")).first canvas = ChunkyPNG::RMagick.import(image) expect(canvas).to eql reference_canvas("composited") end it "should export an image to RMagick correctly" do canvas = reference_canvas("composited") image = ChunkyPNG::RMagick.export(canvas) image.format = "PNG32" expect(canvas).to eql ChunkyPNG::Canvas.from_blob(image.to_blob) end end rescue LoadError # skipping RMagick tests end chunky_png-1.3.15/spec/chunky_png/dimension_spec.rb0000644000175000017500000000277513766004353021665 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG::Dimension do subject { ChunkyPNG::Dimension.new(2, 3) } it { should respond_to(:width) } it { should respond_to(:height) } describe "#area" do it "should calculate the area correctly" do expect(subject.area).to eql 6 end end end describe "ChunkyPNG.Dimension" do subject { ChunkyPNG::Dimension.new(1, 2) } it "should create a dimension from a 2-item array" do expect(ChunkyPNG::Dimension([1, 2])).to eql subject expect(ChunkyPNG::Dimension(["1", "2"])).to eql subject end it "should create a dimension from a hash with width and height keys" do expect(ChunkyPNG::Dimension(width: 1, height: 2)).to eql subject expect(ChunkyPNG::Dimension("width" => "1", "height" => "2")).to eql subject end it "should create a dimension from a point-like string" do [ ChunkyPNG::Dimension("1,2"), ChunkyPNG::Dimension("1 2"), ChunkyPNG::Dimension("(1 , 2)"), ChunkyPNG::Dimension("{1x2}"), ChunkyPNG::Dimension("[1\t2}"), ].all? { |point| point == subject } end it "should create a dimension from an object that responds to width and height" do mock_object = Struct.new(:width, :height).new(1, 2) expect(ChunkyPNG::Dimension(mock_object)).to eql subject end it "should raise an exception if the input is not understood" do expect { ChunkyPNG::Dimension(Object.new) }.to raise_error(ArgumentError) expect { ChunkyPNG::Dimension(1, 2, 3) }.to raise_error(ArgumentError) end end chunky_png-1.3.15/spec/chunky_png_spec.rb0000644000175000017500000000023513766004353017665 0ustar danieldanielrequire "spec_helper" describe ChunkyPNG do it "should have a VERSION constant" do expect(ChunkyPNG.const_defined?("VERSION")).to be_truthy end end chunky_png-1.3.15/spec/spec_helper.rb0000644000175000017500000000245313766004353017003 0ustar danieldanielrequire "rubygems" require "bundler/setup" require "chunky_png" module PNGSuite def png_suite_file(kind, file) File.join(png_suite_dir(kind), file) end def png_suite_dir(kind) File.expand_path("./png_suite/#{kind}", File.dirname(__FILE__)) end def png_suite_files(kind, pattern = "*.png") Dir[File.join(png_suite_dir(kind), pattern)] end end module ResourceFileHelper def resource_file(name) File.expand_path("./resources/#{name}", File.dirname(__FILE__)) end def resource_data(name) data = nil File.open(resource_file(name), "rb") { |f| data = f.read } data end def reference_canvas(name) ChunkyPNG::Canvas.from_file(resource_file("#{name}.png")) end def reference_image(name) ChunkyPNG::Image.from_file(resource_file("#{name}.png")) end def display(png) filename = resource_file("_tmp.png") png.save(filename) `open #{filename}` end end module ChunkOperationsHelper def serialized_chunk(chunk) chunk.write(stream = StringIO.new) stream.rewind ChunkyPNG::Chunk.read(stream) end end RSpec.configure do |config| config.extend PNGSuite config.include PNGSuite config.include ResourceFileHelper config.include ChunkOperationsHelper config.expect_with :rspec do |c| c.syntax = [:should, :expect] end end chunky_png-1.3.15/spec/png_suite_spec.rb0000644000175000017500000001245613766004353017525 0ustar danieldanielrequire "spec_helper" describe "PNG testuite" do context "Decoding broken images" do png_suite_files(:broken).each do |file| it "should report #{File.basename(file)} as broken" do expect { ChunkyPNG::Image.from_file(file) }.to raise_error(ChunkyPNG::Exception) end end end context "Decoding supported images" do png_suite_files(:basic, "*.png").each do |file| reference = file.sub(/\.png$/, ".rgba") color_mode = file.match(/[in](\d)[apgc](\d\d)\.png$/)[1].to_i bit_depth = file.match(/[in](\d)[apgc](\d\d)\.png$/)[2].to_i it "should decode #{File.basename(file)} (color mode: #{color_mode}, bit depth: #{bit_depth}) exactly the same as the reference image" do decoded = ChunkyPNG::Canvas.from_file(file) expect(decoded.to_rgba_stream).to eql(File.read(reference, mode: "rb")) end end end context "Decoding text chunks" do it "should not find metadata in a file without text chunks" do image = ChunkyPNG::Image.from_file(png_suite_file(:metadata, "cm0n0g04.png")) expect(image.metadata).to be_empty end # it "should find metadata in a file with uncompressed text chunks" do # image = ChunkyPNG::Image.from_file(png_suite_file(:metadata, 'cm7n0g04.png')) # image.metadata.should_not be_empty # end # # it "should find metadata in a file with compressed text chunks" do # image = ChunkyPNG::Image.from_file(png_suite_file(:metadata, 'cm9n0g04.png')) # image.metadata.should_not be_empty # end end context "Decoding filter methods" do png_suite_files(:filtering, "*_reference.png").each do |reference_file| file = reference_file.sub(/_reference\.png$/, ".png") filter_method = file.match(/f(\d\d)[a-z0-9]+\.png/)[1].to_i it "should decode #{File.basename(file)} (filter method: #{filter_method}) exactly the same as the reference image" do decoded = ChunkyPNG::Canvas.from_file(file) reference = ChunkyPNG::Canvas.from_file(reference_file) expect(decoded).to eql reference end end end context "Decoding different chunk splits" do it "should decode grayscale images successfully regardless of the data chunk ordering and splitting" do reference = ChunkyPNG::Datastream.from_file(png_suite_file(:chunk_ordering, "oi1n0g16.png")).imagedata expect(ChunkyPNG::Datastream.from_file(png_suite_file(:chunk_ordering, "oi2n0g16.png")).imagedata).to eql reference expect(ChunkyPNG::Datastream.from_file(png_suite_file(:chunk_ordering, "oi4n0g16.png")).imagedata).to eql reference expect(ChunkyPNG::Datastream.from_file(png_suite_file(:chunk_ordering, "oi9n0g16.png")).imagedata).to eql reference end it "should decode color images successfully regardless of the data chunk ordering and splitting" do reference = ChunkyPNG::Datastream.from_file(png_suite_file(:chunk_ordering, "oi1n2c16.png")).imagedata expect(ChunkyPNG::Datastream.from_file(png_suite_file(:chunk_ordering, "oi2n2c16.png")).imagedata).to eql reference expect(ChunkyPNG::Datastream.from_file(png_suite_file(:chunk_ordering, "oi4n2c16.png")).imagedata).to eql reference expect(ChunkyPNG::Datastream.from_file(png_suite_file(:chunk_ordering, "oi9n2c16.png")).imagedata).to eql reference end end context "Decoding different compression levels" do it "should decode the image successfully regardless of the compression level" do reference = ChunkyPNG::Datastream.from_file(png_suite_file(:compression_levels, "z00n2c08.png")).imagedata expect(ChunkyPNG::Datastream.from_file(png_suite_file(:compression_levels, "z03n2c08.png")).imagedata).to eql reference expect(ChunkyPNG::Datastream.from_file(png_suite_file(:compression_levels, "z06n2c08.png")).imagedata).to eql reference expect(ChunkyPNG::Datastream.from_file(png_suite_file(:compression_levels, "z09n2c08.png")).imagedata).to eql reference end end context "Decoding transparency" do png_suite_files(:transparency, "tp0*.png").each do |file| it "should not have transparency in #{File.basename(file)}" do expect(ChunkyPNG::Color.a(ChunkyPNG::Image.from_file(file)[0, 0])).to eql 255 end end png_suite_files(:transparency, "tp1*.png").each do |file| it "should have transparency in #{File.basename(file)}" do expect(ChunkyPNG::Color.a(ChunkyPNG::Image.from_file(file)[0, 0])).to eql 0 end end png_suite_files(:transparency, "tb*.png").each do |file| it "should have transparency in #{File.basename(file)}" do expect(ChunkyPNG::Color.a(ChunkyPNG::Image.from_file(file)[0, 0])).to eql 0 end end end context "Decoding different sizes" do png_suite_files(:sizes, "*n*.png").each do |file| dimension = file.match(/s(\d\d)n\dp\d\d/)[1].to_i it "should create a canvas with a #{dimension}x#{dimension} size" do canvas = ChunkyPNG::Image.from_file(file) expect(canvas.width).to eql dimension expect(canvas.height).to eql dimension end it "should decode the #{dimension}x#{dimension} interlaced image exactly the same the non-interlaced version" do interlaced_file = file.sub(/n3p(\d\d)\.png$/, 'i3p\\1.png') expect(ChunkyPNG::Image.from_file(interlaced_file)).to eql ChunkyPNG::Image.from_file(file) end end end end chunky_png-1.3.15/spec/resources/0000755000175000017500000000000013766004353016173 5ustar danieldanielchunky_png-1.3.15/spec/resources/bezier_six_point.png0000644000175000017500000000020413766004353022251 0ustar danieldanielPNG  IHDR UKIDATx-ʱ 0CQbF`F8(tt(%RXw^̹"y(\[C=r1^?)虔;j9dQ8UMbi7eߏiS&+A>Q*W'gXK-,͇VZmMWȴeQۤ H#Uڶ=9#@}ޏ@ yd.¨|A*BDd9< ˨ 4n@9$cRYQ>*ȋ\DIK?<r0 8(HF4R8> @AFl|(1*Cbt4Aj5 zľ&_[seL60<(6#02@qRJ ] 1R&'qfA}%3pb"5CP L Dx @|K^_M$,Zt2%u)L;DSjiy5`,*N]kj: ~u% ::Aj !{)Õ$+cɠ^8봠x ymڡ* ^_:ͨ:TcgSsshNg>?|A*s !^C!$>n/cODx2IENDB`chunky_png-1.3.15/spec/resources/clock_bl_xup_yup.png0000644000175000017500000000403613766004353022245 0ustar danieldanielPNG  IHDR-- PLTEzz|}~{||}}~~,-74477899::;<>79;>AADHADFGOPZYYZZ__]^^ccdfg}~VE.KIDATxkLYOgtn@u+>+ .GE] >hHL$D cBW$ ]6 Ȇڵ#P;>LWWݓ;sen9]iקHIq,3w.B~N·"ah7|fʚA(Y13+sc6Y N:ʘxZf{;Ñ*g=/ :ͼYWҖ36'Wue7J4ԯ~V4,ө|i;cYN[ǽ N״ v<{ywF,xe6@z-g;Ozi1>km;*xufmH6VhbZ^]EyAoɽ9l87atmы*J8g@i0xLbjrT P 1=+]ycBχGԼ ~M3~xY~J>P>2yXNr>3@Xh}F?m& &)>J<,迍]bA >ʆm,HH(I= %ҘsIdaL[h.zB _olq c[!)rNpH^];園 g$h5iK_ Anq4.!虧svnv807hĽqt8q0;X7{ط&~{1e"[#7bQppV!{`ff߱H-N#@1W8 }%THLL2?spA$rB)e48ueܝ#,CxxM)hO>Hf)&jDH{z9^іS!)$ۤ=yBXRד54̍AHxk T1q*H06p{Di4 yF(vl6*]V>Z9{sP 4Q(/w,.IqTQbSMQW*DjfhVQAO6fK"2Yx, 6Y=*Q{ 1ͺdnRoG?Kh]5V~>_+Kt$Dm%En8X;[-Lt:a!3 0Kn)Fj%Ch-Ks:Z@`8X?h'Gp#Dk(Gn/Lq7Ru4V:];`*Ip/Ox>^Gg9Z4T|>])Ho,Kq)Ek6Ru/Jm!#%ccc7>H.GiڒZZZ%%%''' -Fb#<\ 1(CfA^IhIiEe5T|5Sw.'5.'5.'5.'5.'5.'5.'5.'5/(6/(6/(6/(6/(6/(63-:/Ij7Rv5Rw6Tz.Mu$Dm000)+.,Lt8W~7Tz@\3CW#8T(Dg2Ns6U{8Y2T~9[0R|9[AcLmLnIi-Ls(; /Jm(Ei-Jq1QyKkVxKmAc:[:[,Lt,Io6Ru9St0Lq7U{+Js(Ju?aEg>_<\,Ls%Ck-LsB_?Z}=Wx"$&ccc;BK7Rt ,,,;;;???DDDHHH!!!555+Ca/Ih!2 )Ei)Gn0Qy2T+Nz'Gq*Iq0)70)70)70)70)7/(6/(6/(6/(6/(6/(6.'5.'5.'54-;1Km;X|>\=]Bd1R{-/29\<^Bc(;S 1Nr3QxCcHjImFj=a8]6[0U1U8Z+Lu 3 /Ko1Ov8Y1S~6Z9]6[.S*N}:]?a8W:W|">`#Af*Ks<_=aFkHlGi>`>_1Qz"'%@c'Ac!#$ddd5e Bl8]1W(Kx)Is/(6/(6/(6/(6/(6/(6/(6/(6/(6/(6/(6/(6/(6 EEE.14$Ix&Iw#Dp'Fo 9^&Fn#Fq.S/UFx+R/V/V*Q-T%Jx$Fr!6;`%Dl+Lw)M|,R(O>e5\+R1W&Kx$Fq!?g&Bg$Ck*Mx*O~)P'O*Q,P+N{'It+:CPgd3V$>`!#&nnnTY^(Eh666***>>>BBBGGGKKKOOOSSSWWWOOO666*A^2Ji$4 .JnBaHkHmJqGmCf0)80)80)80)8/(6/(70)80)80)70)70)81*81*95/=6Ty:[2T@dHnDj247>eDh?b1Q{+>4Rw@a@d7^EmS|>g9bPyWPyEkAe-B :X}4T|7Z]MoBgFmPvJn 5U}BdFj;_LrMt369Dk:`EiBc,Hm:Y2T~ImOv:bT}JsNwT}Z[>eGk 1F :X~CdCgJpW]JsLuU~OxTz>cDf:ZIkLqW~EnKtLtHm;^]r| б4Pt666@@@ZZZ@@@%%% RRR1Ie1Hg&6 ;Wz=\DgPuU|JqImJlBb>]0Lq4Ps:Uw:Uw);(:6Qs5Pr;Ux>Y{5Pr$41GdAaGi=`DiSxT{U{369PwU{@d>`Dc"1AaLoHmJqV\U~ZYU~VU|Rv 1G"GgFhVzW}XW\ZPyWU|QvJmKmGjKqW~U~VBiGkJlS__ б3Ns666 BBB{{{2P!9Y. -Ik%Dj,Lv6Y7[8\,Ny'Fo':#3Ot3Pu.Kr2Pv5Sy5Sy&Dj&Dj0Nt)Gn.Ls 9Y 26X8[AfCi8]>dIp136;b`0Qy!6O*3U6ZFkHoDmMvGpMv=f0Y9b6]Dj/Nv9Z#;Z 7T%;V%:T#8Q%:S"6P&;T3L*D1J/H!5O4M-F/I1K"7R(?[-Gh:\;^Gl?f1Y:cZDDD???zzz$>]5Np'7 !4M 4O'>[%7N&6K,3=Z~>]@`1R{2S}5V3T~5VEfLmIj+Mx)>`GlMsPwLr9`?f147ElNuEkKnCd-Lt 0H(Bc4Op?[}Ec6V0Q}CfInKrWPy>gBk?h?h?hMvW~[Fj3WDf@`DcB`9W|2Pv.Lr,Jo-Kq)Gl7U{?]Hf:X~,Jo=[:X~EdGgDeAd9]:`Cj:cMvWcW6_NvJqQvPtIlCe0Q{4U~8X4V=_KoW{Rw0WFnLuQySyJnAaJX]䠹 б,Jo  9[9]#5 +, , -NYy -Il7W~4WBf>bEiCgHlLpHl=a+O~!?f0;a?f>f@gJqAg136(O>ec>e>fBkOxHqJsNwR{OxRzHo,R-R$Hv8[5W6X8X<\8X8X>_@a?`Ij@`%Fp$Eo$Dn1R|7Y:\?c;`?eFmFmLuPy\W3\-V0X8_BiAg?e6[>b=`?cCgHmPuPv9`+S/W3\Aj>e?b2S|ET^ʫyб,Jp+Io5Tz5Tz2Ns)De(@_!9V4Q4Q1L%:U2M 6P4N3M4O&;W'fb=`=`7[5Y7[2U,P|0T9]1U1U5Z8]:_?eCiJq=d=fBk>g6_7`Bk6_8`:bBjAh?fDkKqe@g=d5]6_;c3\?h;c^2Km7Rs8Su:Ux3Or3Os0Mr,KsCcCc?`?a6W4T},Ks9W}?]A^>[:X}9W}0Ns-KpDb;X~:X};Y0Nt/Lo%?a'(67DDCC89--"" 1Mq4V7^IrYZVR{U~ClFoVT}S|7Sw +Gk9`@gMt258NtKqFm;b;a7Z@c#4I$4<]<]4XAgS{QyJrIqBkGp=fNwVU~YKtGp>f=cMtOuJpCh?d?d5ZFkNsNsPuFkDi;`5ZOtLqHmIn@ef_QzPyQzEnCl:cMuV~YWR{T}BkDm\PyOxMvEk5Y.Ox7HPg~б=\:Y?`Fi@b<\Ba@][;X}4Qw]@`?_?_:]?aHlDgJlKn;]CdAbCd>^@aDeAb<]2R|BbBb?`CdJj;[6Tx*+9:EE??45)) ?[?aElGpJsQzT}V[WDmYMvPyOxKl (*Ba5Rw136KqOvU{KrFmCiEi@a FgJmQuOvJrT}U~ZR{LuPyWQzOxXV~RzT{LsEjOtInNsLp@eIoKqMrDiJpMrHlFj>cDiMsInOtPv?dIoGlJnFjInOuMtIpbGkLpLpBeHh237834+,!":VyDfZYMvWn_R{WNwClEnNwOxS|S{>Y|(8 GGG136EkNuMtMtU|[FjFj"/@(dX}Gk<_EgFhFhFjRvHkFiUyRvQtGj=_Jm@d?bHlLpLp?cPtJl@cNq[~TxJoJpRy@hGpR{WXOx]XLuZt]PyS|U~@iEnPyR{QxJnMoS__бLmPqTxW|^_V|X~EjY~?d9^AfMrRwQv_{l{k][Fl[CiAhEjl}ea[[~~jW~Ho_GmTzElKrU{Ry|e '(*+&' !;W{QsaYboj_T}jdcR{LuWPybdYGfXXXkkkhhhfffhhh... 9XSyLs``ZW|Z~Jf FiW{GlOvIqVVbdc_fg`_HnTw1H !"$$%)#3GKnOp-?U*'!&  !)=DgNq/B)'#&))'9O?a\ElXJsPyYXdbT}`qj]PylYaNwNwVPwX}UxHYcʫvб;[1S~3X-T'O7_Px9b#K~)Q3\1Z=e>fFnFnλضҪ¦кֳΩֻ֬ĨҖ l 8Tx4V4[9b&O3\DmIr7`'P1Z1Z6_f*QG]yPfTjH^x@UoEZt #BiOvHogMvBk꘤  l 5Rv6YJrLtBeRyPwKsAjLuW>gFoDmAjNw?hKsIqMtSzT{Jq;`DiIngAjLuNw뗢z 7SxDf:`Ah';+O|,R:b1Z6_.WAj?hMvU~Qz?hKs.V:b.V*Q2X.S3XKrPwQxDmMu0W0V+O}*Mz 'Dh%Iv=b9`DlOxS|IrFo=f1Z8a2Y)O2V)Jt$;  1G*Gl0Nr'Di5Q +In/Lq8U{4Qw 6S)Ho$Fp-Q;a;cClQzJsR{=f?h6_?h/X5^;d1Z4]5^=fAjPyNvMt9_?d*Nz4V&Fo!?e)Ej```б*Kt0R~)N})Q)R(Q%N'P/X3\4]8a5^6_,U5^얢">b,Ny4Y:\(D"Er,S2Z3\6_4]>g%N0Y3\6_1Y'O$L~$L%M(O.T-S&L}.U1X6_/X"I|"Iy#Gv"Fs'@./R~+O|0U,T-U6_:c.W,U*R&N"Hy"Ft&Gq%Ci*䎎 &6U8X6W $!<]%?`8Z9Y䎎 #$?b)Gn)Kv5Z8_:a1Y1Z7`i=e!?e000б0Pz5WBg>f8aLuYPyPyMvU~PyFoDmLu=f떢$;W.Nx@d  A^HlOvRzWJs>gDmFo9bJsOx@i4]\VQyQxIp?fEk8`FoMvgT}Nw۾ٿ똤 |u|,!4J5Tz!3  B`CgRyS|[BkS|PyIr8aNwLuWWW[HoFhFmJqGo9bEnIrKsMsKpPs'8$6LKn?c;aMuJr<?3$5 2I +?_SwGnXOxHqEnJsPyS|XT}^S|Lu:^MtCiKsS|PyPyU}YMrEi4Sz IlCgGlElS{OxOxU~V|Sx\x\x\x\x\x\x[vYtYtYtYt[v[vYtYtXsYtYtYtYtZu[v[v[w[v[v[v[w\x\x\x\x\x=]IlInElIqJsHqPy\PyVS|T}[\JsZPyHqFoIrIrS|XQzRxGj>_ б+Ku*Kw/S+Q,S#Hx(M{3W-R0T(M|(N~-S4\5^6_Ͼ- ',-,* (+"7.H4N "#%""$ $>eAp-T0X8a;d9b-V1Z7`1Z+T-V8`#>a9`9`7^.W3\0Y.W(P+Q3W1T ,H)Ly3W6\6]3\7`.V-T"Ft@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So:a&Hr+O}1W9a4]2[3\5^5^)R4]:c5^.W%N0Y2[7`:c7`0Y1Z0Y4\'M~)Ly.OyPPPб3Rz-Nx2T7ZDh;^3U4Mm"2HAa6Y8\5ZRxOwCkƸ=Wv4Pt7U|-KqDc?^g:cQzOxDmQz[QxW~Ho?gb5X:_8_AiBkPxInAc@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So'Be;b:\8]g>gHqAjLuGpEnAgFi?`OOOб0Mr0Ns9X&:R  2S}2U7\>e߷񨵿HpMte0J ""$456BBC@@A455'() "<[2U?e@hPyPyHqAj7`Cl?h=d;b3Z;bBjHqPyLuFo9bBj=d9^5Y 4M 11T5Y=b?fKtMuBg5WXrYt[v[vZuXrXrXrXrXrXrXrXrYtYtYtYtYtYtYtYtYt[v[v\x\x\x\x\x\x\x\x\x+Fh*Io,Mx6ZFmDmLuKtOx@iClDm:^GnJrS|WR{QzYR{Em>e@gBhDlKtU~R{NwPyV~OvBh7[4U 4U:]=bJpRzHpEjAc@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So5Pr9W~8ZFkAhKtPyR{T}U~S|WOxDmDmOxOxEnKtClIrNwQzXR{LrMpAb+++ ___б'8 """### CcDfKpٰS|U~U~R{VVZEnX<^"1/01;<=9:;./0""$ FgDjEnKtMvXS|KtYRyKrPwPwHpIrKtOxU~S|HqV}QwImKn#4%7MHkEiFkIpQxLqCdYtYtYtYtYtYtYtYtYtYtYtZu[v[v[v[v[v[v[v[vYtYtYtXsYtYtZu[v[v\x\x\x[w6Qt6TzCeImYPyFoR{T}U~S|U~WYFo]JsPyT}ZMvFoMvQzU~MsDgIjWWW@@@)))а+@[ ###### )?[@aGjڲÿ_hVepLu]ClHpMrCe ./0./0&'($:VCd^T}^h]]n\QxAgCiNuYZ^`^_d^JqChJn;Z FiIl\V|ZUzPr@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So0Km?^BcEiMuhPy^hVfpMv\DmKtT}U~HqR{kT}^fXYaEfvvv^^^а  ###############$FeLmڲ[^_`\GpdMvVDl;_Ab#0 ""$  5N;YLoU{^dZZefNwNuV}GnU{][_[YieT}IpW}6[Ei$1GcSwUzV|W|MqUvZu[vYtYtYsXrXrXrXrYs[v[v[vZuYtYtZuYtXrYsXsXrXrXrYsZuZuYtYtYtYtYtZu8Su2Pv7XKoZ^^[^^`]GpbNwXHqBkR{]Zad^YZUx=^@@@а!!!###"""###############(Bb6WٲCl@i=fgAjBk8a.V4[3YCiDjFn:cHq9bFoAj?h,U/W3Y.S1T$<\#Ad3W>d>d:]2S~@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So#>a)Gm7X8];bBk=fBk?h]ڰ`S|=f>gT}Yc]FoDmBkHoHmGh'9P$+Ee2Qw4VCiIqS|U~]W9b9bQzT}QxPw>eHoT|GpZb;d3\R{S|WOwFl?c9] ;Y~>bQvW}7[%GrXsYtYtYtYtXrXrXrXrXrXrXrXrXrXrXrXrXrXrXrYsYtYt[v[v[v[v[v[v[v[v[v[v*Eh1Pv7XEiNvU~V`S|=f>gS|Ya\FoDmBkKtOxWX_S|9a7]DgFgpppа"2########################-Giݷ9b3\8a1Z`,Jp4U>c:bJs:c:c2[8a0Y;dAjBk7`2[:cAjHqEnIr5^@i-V5].T-P}5V000б'?]### !!!################## -ܷø=fMvNw:cCl>g3\9b@iJsVKtKtGnFl7Y!3K!8V)Gn3V?fU~BkQzBkLuGpLuEnBk?h5]9`5\>eMt>fKtHqGpClEn?h5^>g8`c<`6X[vYtYtZu[v[v[v[v[v[v[v[vZu[v[v[v[v\w\x\x[v[v[v\x\x[v[v[v[v[v\x\x^z*Eh:XCeFjElLuLu=fMvNw:cBk=f4]9b?hIrVJsMvGpLuClHqGp7^6Y,Mv@@@б)Bd 0 ###### ###### }̶ѴѺҴѵвαȪ¢˽c9b:cLuWPyOxOxQzAj?hJsCl?hMuHn-Oy*&:S9V{EhKqBj?hIr@igCl^QzOxPyWIp=d@fDj>deFl2V5W@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So@So7Sv0Ov7YAfBjGpNwT}9bfMuKr6]=d<`Eh;[б2Mo(?[###  ,@hpy ^clĬĨõ̹zGpQz\c[NwS|S|[MvQzEnPy=fFmCh7V~ !B`CdJoRzWJsVAjQzAjgLuHqPy\c[OxS|S|[LuPyBkLs:aBhCi>cMpJjEcPPPС5Ru,Gi( """""" 2@@~ %FJPȱʪħôʵϻxZPyU~WKtNw_U~T}NwGpU~Gp:cHqT|NsGjEf=`?dYVPyQz@iQzEnBkJsVX\YKtBjBhIpDlGpGpPyU~]_T}HqW^OwU}AiMuMu@hDkMtMsRvA`FgAeGmU}Js;dGpZQzU~VJsMv_U~S|NwEnRzCj6\AfLoHkDe>]3PuqqqY]a2X5Y.N"""/2x[BWZZVV00776κպյҹӸҹӸҶҸԼؾg)R'PG{#L#L%NBvDx"KDx I}%N"K'P#LH|%L~"HxEu!Gw)P@sAu%NDxEy$M$M$M#L%N#L&O#L!I}:\#J{"Hy!I|!J~%NFz!J~!J~*S#L.WDx?sH|Dx'PH{Gz#L%N?sEv>m Co9b3X*J%?#=%?":$> !:"<(D0Q-S1Z>j?nDu"J}G{'P$M'P'PG{#L$M%NAuDx#LDxG{"J|Fv"Gv )%Kg])%+2/PС5Ry3Pv3Qw, !!!4X\$;>13a=%HL>NN>>66DDffzU~GpAjT}LuNwEn9b>gOxBkPyQzKtGpS|EnFoOwHqNwIr=f;dKtLuQzPyGpHqPyGpKtKtHq:X}KrIpJrFoIrLuOxHqNwMvT}EnClHq@iLuKtKtHqHqHpRy?eEi?`=[)Df&Z-C^ 4N$:U 6Q )?[-Fe1Lp2Qy3U9]Gm9`JsR{MvJsU~GpAjT}LuNwFo8a>gOxBkMuJo@c-=8pxxd]]]б/Nu3Qx0Ov1Kn  p7fk)+TJ<MMJJ55--;;,,W.04{KtJsKtLuR{PyIrBk,UAjBk>gBkHqKtKtDmHqLuJsXKtBk-V>gEnEnClFoKtHqDmJsHqGpQx6SxCiDj>fFoDmNwIrLu@iMvOxLuFod>f:cBkIrLuKtIrLuLuQzQzJsAj,UAjAjg1Z:cEn@iQzS|NwDmeGpPyMvLu6^ClCl?h?hGp1Y4\9aFo@iFoMvU~R{>e=d5Z3V+Jp-C.Hh5No4Nn'@_!5%>_%?a(Ce'@_/G /F 5N+In.Nw<_1X9`4[9bClBkOxQzS|ClClCl;d=fIr]DdJiEeBa?\%5 $6%:Uf7gl!57hZ=0ci 22qDD8M_sg}y~yzi`XT}ZT}GpIrU~GpVVR{W`Y_ZU~]WHqHqNw>gXQzX][[\YY_XFm@gMtSySyU|]T|V}XZXJr>eGn`BhRyLuU~[QzU~cZ`XT}[U~GpIrT}EnVS|MtLoKl.AFixxNkcб8W~7V}8X5V5U}5Op 1#ZK i>sy +?2QtAhIrWMvR{Ir=fLuNwT}S|PyR{OxPyFoMvXOxS|T}?hMvNwVVOxS|S|R{JsPyWR{R{T}BkKtJsR{T}LuT}LuLuEnNwVT}R{PyDmIr?hMuRzHpSzJpAgHmJpFkCkEl>eEkOvGmQwOu@fEkFlOvMtLsJqGnGoBkGpQzKtPwNtHj  $9Z=] 1F);Fh>`!2G +>FjHnLsClLuXOxR{V@iMvNwU~VNwS|R{QzJsPyWPyLsGk4T~2HLpxx\v аA_GfJj  8E D 0 =bf(JO<> !" (BF:<58/2(+0E7UzLrT{Qx]SzdYZW^V~\YKsHpQyWPxcRzcW\Z^Y\XMuFoPyT|QzcOxhS|_Zd]_`S|KtIrQzU~bOxhMv^^aXZ]FlJoTyIoV|KqYV|YV{OtW|BgQv9^JpTzU{QxT{\]^^hNwZLsNs 2H "1DNpMpVxFhKlGg=]<\FhIlGjRuIlVxKlIiFfLlHjPsPtGmEmPyWR{dT}dX^\`[^ZOxHqR{T}R{bKq[Aa-=8pxxdЯEbFe7OmP= 6`BH%% &&!  #+C_8W}=b=a;_SwQuQuMrFkV{X}Y~NtCiHnLrPvDj7]QvU{W}QvKqU{QvbQwCiEkOuT{Jp5]PxZ_WYS|T}j_JsHqWLuU~8aQz[aT}\V}U|f^Ms@ePu "EjTyV{NtX}SxOsaOmLp7[Mq4XHl5ZDjYX~Z[T|PxjbQy;bPv>b3W6X:[NoKmRtMpFiImZ~OsGj:\DfBe@c=`9]QvQtMqJnBdOqPsTwLpCiIpPwWJs;dW^`XS|[VkYIrKtU~ZOx8aRzU{TwEf.AFayxxIe`ϯ7Ty0Ns,@ 0<%n|Ai -U:zW"pOX3(\?=_K+`P/77~11sWI!!d& "$>\&Ek+Mv1S}=_:[;\;]AbAc=_1T.Q|+Ny:\5X-P|1T4W=`>a:]Bd@cBe;^0S-P|,O{9\-P|+N{2V6[?dEk>eIqCkOxBk5^5^5^>gGp.W9b;dClHqAjIr>fNvDkbgnafl`fl_dj]ag[`f.02 3S{Bf=`Cg;`Gl?c:^1T *@5W,O{'Jv3V7[Di@eHn?eIpJrAi7_:c5];b6]0V4X4XBe=_Ad<`EjGk=b4Y6Z.S:^8\0U2W7]EjAgBhBhHmFkAe5Y2W/UAh:a3[:c>gHqJsEnOxKtOxEn7`4]4]Dm8a1Z8a=fEnHqg3\6_S|U~h]Aj@id9^7\;`PuMr@eGm7]2Z;aMu\XEl;b3Z;b@gW~QyElOv:a3Z9`Mt[]RzCk4]@iBkZU~HqNw?h5^:cS|ZdZFo=f>g;dZRxAe<\!08ixxF`Zϭ53.!L+wT'Tgi"YYWqg9b8a8aBk@i=e)Lx 6R  GGG,/2)Lz3V5X6Y+Oz(Dh2:\"40! 1Nu*Eg 4I)Lx3V5Y;_2W6\4[,T7`Ajf:b,UCl5^8a6_4]Cl9a6^4\>gEn=fCl;d0YGp8a=f5^=f:cgAjFoGpNwIrDf+ "4I)>Z-03=`?bFi?aJl:Ux ?kH  )=VGkMsQwHoGnBjAj:cEn@iEnFoGpMuQyS{S|T}T}MvBkAj?hCl>gGpEnHqPyR{R{S|S|OxOxEn=fBk?h9bDmMvHqNwQzQzXT}MvMvGp:cAj *$8SEiMvOvOsHk-=8ixx^| ά!2 D4UzfT'mywB-d{ERn0NNpJ%RRkBJk=xc ; 3M???/Ov.R~:^3Nq!1(,FgZMvLuOxNwR{:c,Fg  9YBjGnCi/133V;^6X/"# (M45cB   &J1  6Z5ZIoCi8_5^PxS{FmGnOvFm>f?g@h=e1XLsFm?g;cBjT|LsHoGnLsBjGnAi;c2YIpMtJq9a9aYNuIpBjNuHoElBjb6ZImPtW{JoKpDiBgDiEi:_HlLq@d9]4YHlOsVzNrLqHlChImHm7\EjTzEkbElT}XU~Hl%6AbZS|Gk,BiVAjFnIpNu/14JmBeBd3T~+A]9kF2X; WcFuO  Df8[>bBfBg<[IjHkGj:\=^LnFgHj9[Hj<]7X>`>`BdGiCeHjAb<]Km<]Fg:\JkBc:\>`9[EfDfDfGiDf=^Jk<]Ef<^Fg:V{9[Bf@eRyRzT}U~MvZNwS|NwKtS|1Ns />`IlHjBb=Z4Qu$')ά~* -V9 iC\2vg3(WJ}B:l-bxBt1vQ/~]X{uKD !2???,Jo0Pz@c>e;c:c:cAjfDl@g@g-03-P~,Nz-Mu)?'M49&=wP  #7%DrM *+Mw/R~7Z:^,O|=`:^>d7^3[Aj;d8a5^;d/X4]>f;c4Qv%77\3W0R}1Px.Lr&'*nnn RUY0Jk,C` D"}Ryŵ4dt:La‚xj N.!'..AZ 6R???8Uz>^GjDjR{7`@igJs@i*Sa;]?_4RxAaDg>cFmFoDmDm7`9b1Z@i@i?hLuR{Jp(>d<_5V)+.ccc5%BP-:QaKpEyhhC'6" ,E-Oz.Q|.R}1R|/Ov)Fi,Kr4V1V4\5^+T%N.WG{2[4]7`3\;d8a;d0I7U-P}-Nw(*,bbb9@I1Ln4Os$  RF_tĹ? //Hg+Ef0Kl.Hi-Gh'8'...???fff///5Tz?a;]>bFmGnIpMtQxJqEl EEE-/2Fh=^=^ /1Ms &7'9\?1I%*Ab;\?a4S|3Pv+Ef3Pu;[?bNtOvMtJqAgMtLsNuDjKrAgAgEl9X /BdBb)+-ddd;BJB]=Y|C_,@Y .@-?V*70S9E @ $fS0# 6LiYz;Vx?[|B\~:Uv+A[??????6MkCaIjGiUyPu\RwGlQvIn@e5Z-?=X|V{NsUyGkTwB^+.0DfMmBa @`HgCa;SqB_-A\ 9Uz,Io**EeOpIjJhA]8Rr@\GfQrKoDiEjCh7\UzUzSxPuTyMrY~Rw[ >^Dc'),fff>FP^/Eb$>^A`>]@]=[9Nl#18U{,Ip?^+Jr?\FfFf@`;Y~:Tv4RvGg;]PtGfGkMrOsKp7[LqMrZMrFjLqDh)3IgEd)+.kkk,,.LQVKOUNQVMQVKOTHLPGJO :=@>BE+.1(*-BDIDGLFINHLPIMRJORKOSKOTINSHKOEIMCFKBFIAEIAEIAEIAEIBDIBDIAEIADHAEHBFJAEIADIADHACHADHBDIBEIBFJBEIBFJBFJCFJBEIBFJCFJBFJBFJBFIDGLFJNIMRKOT\ag\`fY^c[_eY^diiiiiigggfffffffffVZ_TX^UY_TX]QUZMRV GKPMPUOTXUY_[_dRV\oooÿѺի֬Ĭ»ṹñ¼chunky_png-1.3.15/spec/resources/adam7.png0000644000175000017500000000026113766004353017671 0ustar danieldanielPNG  IHDR~/tEXtSoftwareAdobe ImageReadyqe<PLTEF <2(dd ܣ/IDATxb` Vb`a ff  & h@1@;SdIENDB`chunky_png-1.3.15/spec/resources/composited.png0000644000175000017500000000025413766004353021050 0ustar danieldanielPNG  IHDRh6sIDATxcb`/_ 0o`?`310Pí[z3g7"J Ij.Z..&.޴iir2 IENDB`chunky_png-1.3.15/spec/resources/text_chunk.png0000644000175000017500000000022213766004353021051 0ustar danieldanielPNG  IHDR ';6tEXtAuthorWillem van Bergenv tEXtTitleMy amazing icon!Y!=IDATxcπ "q )IENDB`chunky_png-1.3.15/spec/resources/pixelstream_reference.png0000644000175000017500000005676213766004353023274 0ustar danieldanielPNG  IHDR%6]IDATx}]U6sBzB(Hoʧ(">XP.^R M&̝{fnD|s3޵pe)4,eFRtY]i%e@eZIeVRtY]i%e@eZIeVRtY]i%e@eZIeVRtY]i%e@eZIeVRtY]i%e@eZIeVRtY]i%e@eZIeVRtY]i%e@eZIeVRtY]i%e@eZIeVRtY]i%e@eZIeVRtY]i%e@eZIeVRtY]i%e@eZIeVRtY]i%e@eZIeVRtY]i%e@eZIeVRtY]i%e@eZIeVRtY]i%Ųrjrj #L&NRdYTdBSh eD""HSHSȧyNY%GQ`j89>Y@t3ǑtdX G~a=))e=,IUGzGZ :փfN |Si ֭[Tټy)qF eˤ 6E'M*ǗREr[:EKc1B3iIW%)WL5rt_4 |>oYS(kno\n7qַo;Τq8kp=n[O|mDO4=$}r[:EKc?G=g>4JBGbj5zTMW/~]w݁4t =e L_ =Ck I'ͮUX-6l364u|#DWؚZ[Zښ[?kzrYC]mS}-:(L6}$4- uu5 K}MumNl357hZlmnkij!iƅU( d[-b3HQ5W4aiFj<'NiD '0/%mEO}EkW3ZM,3fХEͳ?Zx[>kA$[ӨX7KEyQEsy% {M7Q[ ܝS]ߦba,u C}CC#C#5u盯oX&faM5w9{%w s $a)>9 1έ⯰ϫ^>[s3UKu*+-ٳ$i?FR,^,?0K\,Ԧ DyY女TZdW7 8saﲘ ~1=y}@SfSR-Ndᄉk^=ȖH$_~9g>SRq86҉ۑZ ki[ zdĩuSb( 9bR=YsS ZC~py/2!r9lq;^=F28scžyf) b٨%I/Q%BZ)r=a~ R( 48Ze3J96KŽq 9B*SLrF4"~oqؤE7拞 >7:Ɔ kb'b6eHȟϤ'װQ;P(ţB&K%bB"(B&8A{;s\~h4J/$UҧSd\a5zݻwCtT[˸tX"Xbd ւlhT*8Hr arß#T(V&bи%\67zl|wBH;.2l6KA&̩38f:m2l2p>\&=sFC}mMu^s2VSQaUP2^0kFJ)G"ofج5OZIfH i׹}Z}lwL?J@aFL&J*?JSCT}c׭[šlSPfO[&A˃Kbѝ]J]F;܏+?R'籋2dЁEWH$Rj)oP H@QϪP8b  Uf)Pw1 'UHQ)K@|)4pe? >9(> P(tW_}_Fs܃>aÆMMM% ' LiwМJ8-ϗV߼pK-m-iޱ~.Y˗ؙ_gq͜_4ߠcɁZ"*ЦV5GrVӜ RUaY[ =p1"OlӀ:J$DѠR ).$B! (ajtZ5X`3 d ?D9L/i$ K7&moo?y澾>cիш{[oĚv5n=pdRU(‘Ȅ5pO ށ>|6Pl"V̦VWZ$b\: xٹt^g$#a'>A.'<*ubqPbU"B)ӀD"`b(ёX,2Byb5Kk/;ꊥ-_%,liÅg.Sq,ͷ;|,9;ڕMΪSOHvWc ]G/}3dmk}abNg__)K(ٺ}~ܨ';L"HO D3IY" *aʕ~;ΪUČP D;Px\&6u8{2Jޘ#uD;[?7:NO5H\jk"rY:ܠQLJ]'Mf ZRI\rD[گb ڦJ!7hMKJ_^tw__>+^ZVtZhL`gl4ڌ-3Z4Wokhj ̪ԛM9u%3iSgm.jXxF&{Sb!Bm%bpE:쀐Ab24jJU)< D.k2Vc5jqlTJF͂Ie֬^=|lW]:)5Yh"+ b@ R-KUU2f˖-w5\s#s hѢseDVVV@$B6`wҚ-u8:={YŠ$aYRO#L&I:Ԝ ٠UH\dLa-S?=IxLVs؟\((p(J&>o*42 j2K'8(,u9ZFFj\6]l1:\Ȧ`@]T$ش Q[ H#2Hʦng!L#xUrH ӡhzO&@  ǣl:JDujR*VHzR|6u; 5 C>7G"av.{pg[gGO,-hRJ5߿l;._g7aUϯvK/[qª/Wnʥ W/kzyUKZZ{Ͳk^E}+bbqKꈝ]\ {ʋW[yL lE.Ϋ\Xg~j3~9kj52@,M ;ѪRO)Ĭ\Lă"nԚ+LrxG^-MIz̤RBAe5ZU?K҉X2I%"O.uZ>f:ȓ9NCaƂ?e&ר\ \v^,}`C38XS뎄|դש08<TJ"ˬċdR  ar8MsdRmZդBKRI6YLF P)Eh ҉Eh;X FV/S%R@XQQgl28L"F3GBNc1 EmM5χDb`_:0ラOƎP.GJeBJZQe3 EV)96F+WCjpǒxz\ kzSI| ȥTj5q٨B('\# HtT,:=/F68h˓B>f (-:v)hW4iuAW,nitьӾXvmhBBQ2 9R؁ DegP|*px&*0|(! l'#0E$q4'4`ԈZ;j3|,#xb EP+"2n1IuZ-w2Hhك(P2(]:E'TjE]uhP :)Sw: 2 ;5V#aBqQWJA_ R,x56 j.FK R?JE`Z(ā)˜ŒZZ<@ m&.+}L +%B(XAl4d>ʧӅ lhv2eYPJx? da#ǓJ(0DV蚈<0YsHb:yP)pФPǫf@u}pxp\y]4 F#@ןLT<|8ǓiJX^PXY:pg:OY\F PPi\>B0~tp8hHx;Z-jPYx84E8(kh=2Plr9Mlhԥg6+^h%"|ZtJM_7Rll1 оv[" *kvLm| eJ!qe͌8d_՜%jsJdz5xvl>OM2!nFbQyPN-.D"0urh,_|_y啗^zvWo߸? LHe ~MxCtVYšnYL_T#^mib^,Ik;~||+wbA}3_>uxe ;z3=n}lu/ѣ=N! GGGFz;5e\#CǂEd,tYKg\W3{etyk wa.j/.:f f'wq9+- mUg{h5}Ql;zKˮT\=2Ibl$B~c\> P#Ç;:&&q/jQ,E*1,"0 '6vCA@t ܿo鰻ޢh3ϗ&.Fm۶["䵏zlI$j/0:^*Uګf̬WiBk}w8%KBqŲSBH5E<YpIԳ8,erHpH"Qk4*UjـA-^C.p Pn¥.>c5U?hh+W<%;FAP\>J[&ʲ̸QTW[e*I l*ud4 Mhw6BNZljYEZȻ$l#2iHBcsw=omI^y啳&d-=oP"3,lS_xdCSԳ8Mzy^ۋ&wBc (dк̡89bt:b5J< E܈8ɑPOg ^挙JUiȼC=M;>qk2Vh;ލnx 8lҮG& Y@:+**QCC_d _©)|VI " dd&߿݃#Oo:99=  & %p8bnߧo,MnB.)nLYYsQ)H2p}WVe2Տvosq:@M}P8+|egR 7P`ri" KpJij^Oƕy|v;ލnx h bĐf(U:v#/V'L]%ma4;4p(oFSf4L.v/Xh֠}=RÓpIxrdł| 6D |Th,ƀ0|f V)g"n[%#T,$5 I/FY,$B\DŽ}4FD: @3PsQhX^TEqd  )kz#S(i˚?f֠&4-8thжjZ+tm,f BG:P(/xhAh:#_P\F3Ԁ29rgyw߽nݺ'銇Nfr;xa˻ E'E4/ l.̬)CWHO<C x2ZE*%& b"EǴ{F8J:$#Q#?V^յiӦ3?FN&@Q7;{]`ZTqeK@.rkSlɹg$@OV"njrQ NNd<k$"@n];Z+3G Q,,+vݽ.h70PKt$XR`W,QP* dW;B5SmfcT0 *㉤PDRsbLtpc5-U..Yud4kd hs f46KyPFy"( 2%Pavm޼yw~7> >J@[Y[;p᳣"!AQ6Hgs$0 d욌:9$M"կxJ:/ dCfgɸ!$RL~Bh@xjlQ?x6Gt0??h$Ըt(XͤsdXxd;.Og0Her̳3^(O8v+]S(]\RxKP xRRgRb}^"Iv>LIEk>gXJ:Y(eS<ނ\Q2E"G)&ߏ_:C(<,Ed*uT%;n;)%9-FR9 R(&sE΁Qt֊Y3#A5܂{cfd`ÇQHĢRW̮kk1I'd'LMYyv {bbb||l ~{{0F^t4q}H3|дfd:uR:()C%tq,ԋqM鲩 ,GF%644a^GGsAff'2~rFGFήN/OJз~?[+9-ˤ<.E5Ly;ݗgȚ2+_,Ld/NޱYv'l% [edΜ9Zf¤9ͩ fԇY4W.k6k4RN%S%zn f~ ; xWeeEFl>EVjnnFRǍMJlYwxFFkMf3x|,FV+ڻ6# ҩd*_=o0;}PϬuߧ]W}I,"P((]$)z&]Ipp=M['sfͧ˩tזC.2xA<TIov9P@J((.'Ke2&l$ڣĚR&t, ه n]b];4rLQ!2Ȍ{ fg"Ջ v;s8477 (Ř 2zzN!Fi5r$3 -V@7S>(9ϩ5$N))_NDhriR%}G\jh5">A0p"O^FNO,ii(BqPcIp0#Gtb@ =.2b А\z=ȃz&'C?b! |dl4ЏYzfC IF0kޥ@ebIi~4gWmvIR#]cHQP QLti ff}] )yT>AoQڧq]ޞQ.O&qRYH$Y`LQt0Hmp;(4;H`A Qyp9B+zB 8U=kT*n)$A!rU u9tbIu{pꇟ|n0r"\!WU(R-G-Lwt(pDA6(,d=yAMZtzY\|9@SXOj%SjR8S!c!u?T)躉SkǙ4~O98EO|ɨdyCŧk`p wIer%bXkT*BQ(:*2TR "ƉTBWљ0Zv53#χc:k֬:Fpn3Z͝[__~<3fЉ8HWNSS.N-&ps3eoT췴444OCzzq7{9% <.Me8dHQ!z=J"ܡyE} fjS$ܲBѯ̟Ýdegt>-䰁Y)0aB Iy1AC'3E63rtM~: +PBNc1 L_9S0Jtf[Y*.+Ψ2~҅bI%IGܬ"83-‘="}r̠W=BLd.P ȤȌB&hd *GC/]7X_Jϑ@l6xMO#w]T(RL"$bQ,IhX><]3GGY O-=t6{2+#4v O)0 .헥!Ju6[NoeM 6`E^.SHerTH$ʟ\/`j^F ήFC˜4d;>fd6QUACk76p˅WϲYΫϱy }p>> 7wj*kM=A\67vgl0hT磌+Y %#(ɄB0p>BbZdrt!:>tUqaC0΁h6_!Z0\rpמ3/xh5O`2',$#HuwGMo@ϞXGbPd`?vҎNl;|;^~φ?׿奐{F92fr YLЧ J~T2-t6.Cc$Ǭ ZE2 ѱH4L]nwY( _0@xD'SiktoI4R ?;"C"|N.t'6w?}t}1 Y?:&۴2luaEkLW)$(&>`%@fH9vbسn[ ҇ku :0v<9-ȝX$FC=&O'P гw9mǪgۜC;Ě#sD2OdyT lp`16a r2S^epGɄX$ 1ȹ 5#'LK%"J8dUa362*H0J|4 (w3L.7NMqBѾ#1Mh##;rjұg^^xrjy(̽W/hiv@ě(;Ƃ,tg]wOUVp 'X}G]r .x饗DR^wiP[d@hT*Ph|?Y,i#ovt,)dV=WTUjM3jc<ظ6']n%o~k]?znn\p+(8B3PVIRXͦJj^PșI?-f'Xl6[Ml4,F&龠S***l .[E\74"_"WqFJF}G>Cf&yB?s=Udx{j n4\(+[ C0g$Xgמ- TiH\7Z P۷o߆ Nk)y晷~-+|gݬ}n6ZԠ4?aT0]kKOmU_~U>mrd6O- :Q˪\?w#{diYYJ\]P`dM tv8PdsBHR(pTdr0HC(D*]`5:=_($J2œ@pY<: F\)We h<]np(;'̴B00gos:o^}xp|l4u?~4kt&4u-}헫\Pi! E^PgZV., ^NByUzT dU ҁ@;X7ꫯ1V[\*щ~^uj9]n,tj )m9Ug\wb2J@ŝ"*r?莘VԮ^1br8eՎh/`n IB0,Kd (Os,'N"pBdx*M $ l"7+D6%EN2[$|cI7JƐH|\\xγH+,l2ǧ_`a1Pzx΅s+7w>Eﯟ} }>-5I sitGgXQhoasYl|îׅ0'` \&5bנ*d곖-Yjh.틐^j:Murz~ixPx j wNK˚n>_SL{#Gis*,F̳ t4U"|Cd3[?xvYiGgiEK> ]:18dQ3zS@$t1)@"pbY s||'=0ٖ899-7gʈppOe\A!zMmQ:OW_ZSq9,w+f@OO4|E^F<3Aā+_^׷G~kZh^`\HQ'>t|l rxM_ԠsP*z=Caeo}޷͆ N'oL\=|?u߾}~m~?v{Ol]yt+ouzo;لS_{u??mĽ˻=lx2.8-xO CC4F )z=d͜~~o$fؓ0;ju![ns;::ΜY}6qLܱkߋES?z-lc*vW{O෬[3˲C{άғ <3k N&AR &#<"_T n\3w\ge$BbsD=& C[B){h483 M+,j+**$gMo4L156QFbE|BÆhUK)}ߖ.WAཀྵlBJ! D" dBZku<6K.qE!%tЧ*YH+yTL8fO۱խ[ګ5bi,k^?ww!3qE}\&arMÇB'AvwwISk.7Y`>,F>lLޤavbq}z4`0HNB'WObWjN+kfig#PWb6hQ~LgW\q o$6 t}Jw*[ H,^dQt4]E d'X\lEp 0V*DR*2l1 3"pBĸj5HIILƜƜmLsbLOEFEP`@J fwߗy BèyΜ9o޼wc.|xd©n7:mi,|_7I5L jZ}SkIHB~A]^祌i|U,$5/k -C(tv8!  oֶmۂ8@O!;$%3.LcB6PQaa3=L)nV5p(mVG_aKR8ߟ*U :ycW?5R+ J 4ؾ}{}}}CC$(%Kر6ZuֵkRVVfbH|%ỸN2vJ{{Kk+*4A:z ˫=xxL__hk]0C ~||}Q~}Cz&;$h ^4[Hw ,޾^c_nkCPB^}C5/ژ. $xM!+O E+CGcj&#+~7z#BܖR]L{eqP&L==QU M >| ߏ$ %R|hWC(Tar#zTI,6Pfe@ff.q^g2MG_qK7[40(p݃17:걄w)*uͯT|o4=h&NDÖɤ ♑w1Qݽnjڱtssseee>}zJ2cJ,W2nI04Lɛ,WW-ܲi|l \ M[vs2œ.gp[j/%4z| /F\dLٽC6Tp #d"09N#%]ᎿC{4wJ@׭}џ=k]䪇VXxjs&+F}}`"%]>c"z 䒠`Yz" yt]Pj{BD-ni]M2yO,k `d5Mdnz D60 TjkxFI0>n371${"?TШiy+[sFliL-RoŔ!1*zytt)pB-D.-v|.4` 2)q%=,)qןO bfrGYs6%͎3/;̚`v9w=~agɳm=Ρ=g~C'Νh]USzT_PRRVKKK[ݣL?g M)oCi-HWI8iCn[ْ|콼Ϫ%u/ R%$LEwk7ȉk{ןwp `3)CMKjz4n=e|Pjjv~qɹ+ݻK; ڏčkٛz]גvKJ}Kh#h i&QViSNUWWWTTAɐk`=ز`58eܽ pImp\5'O EEEP^p}\gϦ2lyKߤ" 455edd؋SW3{XyBjac8 +!pEEW𗛋gsἐ݀(j54 Չr.Wۗ$F~@5n. kM89p Ųkr|G2>vI8g;!ՄL;Zv YQ*rwӺp`99Dv1/dJB'!Lxg 3*JG 06 oRH/Uܨi lIۉy18 {>6nv5\<1qp3gqe{x2@ؾ%k&FlM.ys 7+?&1<>z4ܴK|k:?Vn PAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84SPAS84S :IENDB`chunky_png-1.3.15/spec/resources/polygon_filled_vertical.png0000644000175000017500000000027513766004353023604 0ustar danieldanielPNG  IHDRڄ PLTE=-tRNSDP!`IDATxE 0 ?/Sd FHÔ4R ./Kgl[;'.?V+]i殍8=0L5PJ扴 W),#^(֙#IENDB`chunky_png-1.3.15/spec/resources/operations_border.png0000644000175000017500000000013013766004353022413 0ustar danieldanielPNG  IHDRnvIDATxcb D * ^ z`IENDB`chunky_png-1.3.15/spec/resources/polygon_unfilled.png0000644000175000017500000000045113766004353022252 0ustar danieldanielPNG  IHDRnIDATxK@EO&l3g%*vCv@{ ^SA%ztl~9 Ti3`’7/+KO(@!SV9,S v0혩 #?6?:wfͰJ?Q9Vo ^[g)*c6{ɭ+w2MF,݊:]f $FjXlg4In8Bָ#POЍ^Ϲ5A-XIENDB`chunky_png-1.3.15/spec/resources/bezier_four_point_flipped.png0000644000175000017500000000016613766004353024133 0ustar danieldanielPNG  IHDR U=IDATxc?L (XILL< /.013,`bbt`b Z' .6lIENDB`chunky_png-1.3.15/spec/resources/empty.png0000644000175000017500000000012113766004353020031 0ustar danieldanielPNG  IHDRwS IDATc?YIDAT5IENDB`chunky_png-1.3.15/spec/resources/bezier_five_point.png0000644000175000017500000000014613766004353022404 0ustar danieldanielPNG  IHDR U-IDATxc?a78`74012@qX- cIENDB`chunky_png-1.3.15/spec/resources/pixelstream_best_compression.png0000644000175000017500000005135013766004353024700 0ustar danieldanielPNG  IHDR%6RIDATx\Wu.|znozs7lـ8$H!@KxxB ;Ƙf\7VWWOog{_.%۸ ?b;s}ZZ{u$@"R$Ё:@@:@@H@ H  H  $t $Ё$Ё:@@H@@H@ H  $t  $t $Ё:Ё:@@H@ H@ H  $t  $t $Ё:Ё:@@H@ H@ H  $t $t $Ё:@@Ar2yO+`ϑ֟)`8TA+0yE"I7 ֧Jm۶MB޴=u6rj]3╍Bk8澞Z%,+^٩0Fh4)mI\<ϗ7`ͮv*gmx珒x݉ZD{ds @jyՙL +Ӕr0G?@N&ׯ_}:-@5@N!BU?̡-J'g*KBq <3 -^p.4p߁ԞX4!\˭ȑ :th hs"Ѹ(KӲEY;8{a]K'e6t]@NQ,+BqǤxwT+;5-䤦i6nJEY_lL0xD"ГD,)r<GkՊmٚfheh4nih 4fj%H=ONޟ#гY}Dyn bk _BPڠ{[hU~۷o섵Za/3cBk7{:'O[=T5jKjzG=e и|(ްU#)QMDCR2\.';+3Ur޵ds|tv hQ(L3S=Yj "&2Ӵe#?SyP8"2Jx ho@k!{d2-bllȑ# ™tz^}t6ӑE`h"np QmY<7м~yCxHyW>ū@4-JӲsEv]]xD9*-9{(lUQYm;&[__X!E krOg;46:-ofNf&HIVe<˅|4mV\ñU oh:W:k':Sq? [.V%8cǡa cXK+W*LOO\/|ҥܜB~QaA݇BР Dv.rRXq< <1yhD%ޏ"F.IUj֢^oꆢ>ԀUVuuDRb.LģTCy/Fp6W_T?N%e]#c) $NaJIxQ)W_ypU?孟뮺yJg׬Yze2E 艉 0i(cЉ(DGǫ^*0 .@UUY9|>48h2O%$ڟ_]10b^tovȋUQƦ]kv/N4j,twuJԒ3,! r[Nj45ٞ zz1h4 i/l+9I%yT*i t8aa$dIuxʳᰩ'ȑX|whMhx<2ގ187.W^~9;?Wr1>W*b)2|ΖI|vsa5Qv;_b!MÎm~aq`и/u(˶e1͖?tˑ` OȨ<_tUJgnlyѨ@c8ax`]F(5,LlK[[>Mc ٴ{HI>nVH!XD۶D'gPh4qr`" $NbPLWL Wb Aj{DZ'H}Zj `jpl}4Sq-#M\DS.Sw}gwj4m t2bT^y e~SC7]d<ڞI&@Ié`hBu(Z4Ecm3bnlf9 t؞h, ՑB=A 5 }f[k}  j*?[9+ .:=Q^nL0TP4\s>zh+VԢ*q$%44-ڀ7L߇Ba(P8ڀwl4uJ Bᰇ]eH`fe'(bVbJP: &HeEjjZc7ftwuu0NL{EX>uL&Pxzy,3+shP? %xM.|P,tvmǰEqωBD^]i. >ɴk5I2XHXW$j  Na2mKʥ2LJ #1Q4JgzZ6[“!'VEh*N`AbVt% ek/ϯݡ h\㒬LNg1Ɨ^GwSSS=]W58ӸLN\.440WІzӞUMoR'ʊyv2$6mςI$HB^$i-k|Gt u0`1oz\Z F07`WGI  yhhC 4i%9䫍g]N>|*P% 6ަi* ,W.Y<91!V*cX5y^(fz 8.蝜|/ѳ@$Tg&s:UQM{&&v+eE2綝ǡzheagXMI.@Y=8bo0d2u998Q'@~ӟ[-@!2$⛄  ~pe+{R,s*B{x \NۿBМ7j ?yl)4²?˶p@o: ˁ[>Y4pD)BՓZs4a 4@s6 7؊tuu+ڎ+T7o޶xY?Y)$.g-fds,s!t Lvr80Ԭ9LeoځG5ԋ %QػiV  *m4S%at<=w27eP{Oe|}?_,w-Zf͚?bSpLsX-Pl?ܿ?1$WkzxH6!~'g& `wuO}S۷oVȌ aFr4DH5 ˗4$m(YQ W'Lbft: .NW{;$k˒g5 +sFmmxP2ܬ*GccD$@= Q`Dm>IfdD֚qa}U[7]AQ; 9]?xp``%dCс+U,bTjEccGWkb[lC͖Fc8Ə!M}luηuwo?[-2ݲЕ|Mkԫd#;1X ԙň@U*}X56!;_?4KY!]^G2*6F oZG|Yo'\q0@쒊LdiZ Zscz=k7qzu5A)0oScsm +FڣGdLrV3ꗯUY3d<k@[^` $&FٸO 4*`Z$ڰ'f`6O*+z1'"%ez( 2L=_&ً=U@a:A.AX8PΎAԫUIxTx6?m;8oh$ $HMܸmkj55IISɎ~F6 &p0-|'o:I9Jb2jqҞD2*< y˧a 4n+]ӀhEJ%c%xz¡CY#DwͤHG4I^hL^w0*Uy8: bTg{֑Λѧ0&[WTIDHuM4="b4y1_F Rk=::7u9ySg\4P(Jy F*P7\Wyp-= \ G+M5GR4nͧs%gY5#jPo&{"a7z%1ohrf$U*Qx N9+$~0ۅ @0j%% j&>4e|͹ A,ہGj (ӋN¦[yfsw'Cz2-m 91j(+ `l/34=t+Vؼyw;]'M2-r6`Xv|4u :dKt"D݇j"2 t } 4ԕIɡ`lγ#(FXf}1vV+p$L$aH,A`qʪ‘I,}ugҘX$q|4iYt8zgjj {d7> %+ġ@ ҂_Ecw"=7^F ˧2 -l&f?>A|$ާK}\ãzasj6*$i&Yq\.W(5 ZͿ[VZj__ox: Kda-|n&2/n>[NX,y=^@N9rCAvѲ\ 0l… 00dqRZ$]G~q*;qyW*e!5Ж}.@8N.U~NoƵI.޷o~``h40WZr衩rnRiUUjڲys;E[(TEQ=h?=R{gO؎`VM 1΀ E>D+]wu۶mЇ>N_@㮖L@z4M;HP]Gg9Iv9Y!Yr"eTK5M^jߏjgXxL3檳?Vl&s’doX =@v"3\3$*Q]RIֈe#G`RKAZǖi,xbbW8`ʕh@ amxQdZAk+hMM\Y,3#Uo@*_Bq'.I-a-ܸ_ܩj͆Y7|k׮}OKŐSҿ;*1Mr\V& cs#v$CK ==RI$XQpuVcE ͋zeI3 1C^#e!eq*(-fA9RXxg&*eHU&ՊiൻS =9Ih:Z,[Z)?A19iهLyk$HE_QH}X)sZec+T2K%=FwOJWjƩ=ORy˴[֛?S=P<[k03_ o@$khHw,O3 cx$d)vpB!]hh Y"D|%{Y`HX$Lұ!‰*N#E>pŽT$+`1Eq0s[;@'ʕ &-|}Ȳ=9s3riCRIVE5nxmYMZ T4ZnǡW@CbFÐyW$g3`:>>qDj{ldIqqB&9}ߟJxtM3/I$2Fc{Ԁ#;$Z4.^ғò"5>pv5^ףJI&Qmej*Y#! xܧB#c[Y>gMbh'5zW[.mY!M-NƠa|p#Ul%IL[R2'sOfQI,K^dn4 J8D>al˭YR$;Hqk->=7l(LxVH(X^%2ʚ2Yj6 frl(h"Ѱ\U j۝ѕ]v]w$ufdԗdc'9F3qtƞ$3>.Ю#Z<뺺^?/o$uc3^ ;ԧURw-x>'ph5zlI$@6BG<.϶'O`@'c[F˕Bh>ٺn $ .ͧ t2q9024&CLV 2+b(!؇mDYzgf[JAUbl\zK(fxզ=֬YJ? Yy1r"ց!x$ zg ҹ(nyFq-;≧¾M,/σV&hM!Z(tƷme;bƑ\jC,gzJ$[eum^V춢{ZJX$=Q̱Y{7L4+x~y47hP~FJb7#aQ>[6tu݌~ס.}%A %s8{`I(Ĥg3x| +Xʆ f#"ŒY>;a0PDZeqna0&+c[ݲdZ8fv-f8]&\\!x!q̮jjtN_fev~WG{g̵$J t,74ہ*IyJSQdILz"U* ^ZCkF~] kmX93A W4CZ=繗{ոLtC ޽V#Oyl˗vᆿcY՞IQyj4Op#ӢDGG$hV %  A8p+G9pIl92δ2;buV7l 5QovHa2IN5Ue! SUhQu)eH;A-[[_>Y=ɯ7[<$ (s="C$ª"򤂄,QZpe֤Q9zqa#twP\se3Z1?#07̂t I:{[d=9:do,qË/޹kO j7<|]_̻jGG׬<\AUI"ؤ7LE&Ar5i}A%G\B[`s`78LbX2; 8 ] LݢZ'+r.0XLObuZNsM|#ugfrpq`^4vDzZkFHqN{֭[w4ȯO)T/|Lhޓ =_5*o,J5^`lŷ#Hc$h鲢͆RDkg~sbp4YIEZ-QWkn+F3)GxETB~}w>aSřkGʯwNδ/ԧˆ iRwXgXY3`PrE`kjDuׇ6MqH^CdAWUhD}WK\o!,=|e֪uMƌne7Aɘ(sXa1DIНs*޳L% ,`%bu[y$zEk@M")cIj%{{]9W( =pT:X1hxd#hV'ڬH  rd_&۟b [gs9ӑ98rEymSd7 $ghiWxdQxIWGR5YU!]~*:ĶIJFн$0HFgI2WTIͮ3&s,飸;]IM?D㼨w" sOwMOe7\?6^89 :IGWv]{Y8iCJ4%8^G[oxy5cؾD#9lFu0Lkzt=J`[&3 sn#Z<!g=əwNK,}ʫ{n ElBs9=ȡ*Քm EN|\*NR'ib&QL$azd+nT1hy-^6_Vm+5Ӵ k P)PWj糰#PؠJ(r#Rpj?rLA|nTm45M+X*tWHrРf2Sꘁh hD"vFKXB.tʴ]i8Lnf8ibʉ"a8j`ahJrW|i4He6fGW4T`o1gQW~q " ^>Gm7&-qEH =wap$c[mxͅk\Yv%D>"uE6/›Γ%n>=Fj}&JXXB4uFr ,` doԓ'zN7Zr݄֭Yw?jhA~;/XqCtW4P&@ՅkMMNH YhZI灁TKE%AhksY,76CHӨYX4V)ؒ58JFR'_: jt n';JCCC,B ld*5el9L&؉>g|utx@N­~;ֆuQ(7ѽ Lqu;ѱnrm_ߺ.fTZ x8LHJYzINO31}q_'#D`{M ዺS熐yI‡,QE yE@2%B~EU WXqheb`\,Y)I jiP?~pLJ>!8´¥nѭBaTNtX&R䵆LלN Q70D˦K,gǎ¾q##1EIσR 1Urt&3saVZpUd:$V#+eSYN3ۼR9`m 5r9Y M狠+14I[.N~-׿yk?e+.?-}W5nxs w =<3.y|b#̴1+>C;GF#'kӲ' o=N]Ae1Bq9S\ʉqcaT|2>>NO4 4(+ʨ6-jONLtW%tn>0'w*;MC+ -NMQDT.ÎDsF=R٩ <}Wהj9tӨT%éf,<|J"DZv<.`V0w2 Yq| cY_ziOkÓ &\"m>c|1%R|FR`>*YG OdҎE'н{K]{|[=/u߮Iu^^ظq?ym7׭D,zz˹tyWa MMMm;R_^>NAnsg uUӥ{~ŗ̋G3kw ~}Q2e7:dDRMhZ0q& U5zڴLb;AA%,7:oeΎ>L g@%5M1:[9B6KҠa@m}=!] gѕKv%Y mrGv~[?yo8q3+p&Mz4|<w4yKսVz7վnk"BHP`sELsڂ-wr _Ogg7u_Sެwyor!\(4x݃ zgyX֮7{@ HI] MO,DS]K“/_8ҳx׿?ryx.ˑpe]B!TUPgBYe'{̀p8 *-i"0dӱh4%M 0]EOP:n IAgrJ1u[;(ӱ׬]/$Z׸>?@Galϵ:zמA=8w(_?Y8/eYRxORcCr gc\z 3vd1r b 0yNQT}z& P(wuvr z>wo_t.`X};>]황)AY.aˤ2UX81&_Ny߶%Փ9s|?[׷|{%f|h;HM wS%ӗ4fD 8}<d(k$ >NEZJECPWKeV% D=.MArQX R7u]h4p|4W˕k R2}7i ~M,׹fɼGw }7\, mXRoھ`e9k}~XL(3陚wX-7*xPؘjS nĜMmٽ}]0F %hݪSQSg>ZaE$8G}m{;Ǯ\6֗4-V om|0מ]nX/_l]?=N#MLN2)IN\Ӵ=$/.(EV΋'bIEDGUk2!_m<8lq2** gSȶd&t10E ʅLj#ΫϺБ_mE+KX&FdBlovͲ)vfuv{ KWbā&c<#-*'d9[npS?zf#nJ_0oti˞޶+7?3ww.^"JK.9E~lا>S|{=g8xEK _MɈy˞4-7rrRgYhIwѼwˈu}_}7-3roҕK,JAZoQχ)SEQj-44۩6k}?V5Їر p {;/\Ԙ C\֫/[?ꃿ+8zȲN~vǎ[n]hzˮw'0 /l7^yiTkլkD*\`5s#{~z~.v̭z7 "yal*M[{@36p X0p'y^n(Ţszw@ku̴XX#ʈC0˘ޙL摙O\C;/Y5μBK?p o|MkX?;͒NopEIR_xvާ7E},bp SJpVLիW3'D4;"ZH(gēiQ4:k[<^װl0c립z U}_z`ךLb^rq:.6JglץJ+DYd5JmD?oIo4=}l놦")w>q M>hPT2z9<=Y'&3wrg}kPFi?kºHB x|}V8H۷G[8پ}]{9" @GQI7Lo]yΙW-";\D"WkUW]bMz^,~X^w~:w&k/>3z$^RI+!h4LꞨHvNK RH%U0?qC\nLeşSv^@35v) $u,m\[la7ԣN/$6?0l׬[*pI'm}+Vҫt~OJ;S?l= آ:eˇBh E]WnS0٥B3sx衭tptُl~n'\͛75 ggTTǸ n:$c'O\nhҰ$Aڿw@KdU 3ٙJ>|GꆑB=jfPJJ[<2/9Vd9l#Oڤ[t؇C%<>MdIE-ޖ[r,Kv.(nC:l Ko<9D˱'#7LtѭtYƍiw] fgB;{ Ko(i|Qu.ҩNzIRvLW0=OX?ny?~q˶?M'jiɧo{octwvHiWQRZ߈b>U* Jo{|!e}44,>ٱklZܳ;䎟կwx =9pp_;g 2yإ| _V'&1o>OmNy췭Vi紿F 'ߖ8Rٳgυ^غ'?jQm!. tR$.ʺΤ8+Zưʢ{}NF>|P(,Z-S;&E5!NV8@[m ;`3*'.˲ǭpʩ;<$ǝأ9h)#\z\֥ ;hᏽaz >5-i V:[f1HVSnn-R[8;ZKHl=m4"NkϹp"'~ξjjoQǢs NkbFg:F^}tjkhZ;o0;|ZXY׳ 27 rO5=^,6~Z-@~Nžge|̵c/tϽAa=fu[[{=N^h ؙ闊,Jxf:@@H@ H@ H  $t  $t $Ё:Ё:@@H@ H@ H  $t $t $Ё:@@:@@H@ H  H  $t $Ё$Ё:@@:@'j)Q'IENDB`chunky_png-1.3.15/spec/resources/clock_nn_xdown_yup.png0000644000175000017500000000117613766004353022610 0ustar danieldanielPNG  IHDR2k ~PLTEz|"#Ccsx ?IDATx} OP_. b/[VrZjٲL(|u褳y<}﹀^К1Ld,28e2[=mn8"!ǰ.UkޚgГFM#L;H1JuDC6<;m?]Wߢ)+)ontZb+BlVPJTI< L [x-ȘoҊh0fƻ0c҆\ Gˢ])9Y+TLB 賙Jk-WS( 碟;25KOgJKRcFza.iEXuaf1\còt pUD4y86!l]>S$TwNΛ '! Mz _/~Æ"*\spv'n%ꥉ*09IENDB`chunky_png-1.3.15/spec/resources/trailing_bytes_after_iend_chunk.png0000644000175000017500000000027513766004353025274 0ustar danieldanielPNG  IHDR IDAT8OA ƠYB H=ht"#(hL 4U(@=]9th 4պO`u8t҃C\L'FOXz;7G 5`Y9=4ApnIENDB`chunky_png-1.3.15/spec/resources/replaced.png0000644000175000017500000000154713766004353020467 0ustar danieldanielPNG  IHDR(-SPLTE 0@P`p 0@P`p  0 @ P ` p 000 000@0P0`0p00000000@@@ @0@@@P@`@p@@@@@@@@PPP P0P`PpPPPPPPPP``` `0```p````````ppp p0p`pppppppppp 0@P`p 0@P`p 0@P`p 0@P`p 0@P`p 0@P`pЀАРа 0@P`p 0@P`p1IDATxcbP0p˩ꚵjۯѨ ]3j( X3IENDB`chunky_png-1.3.15/spec/resources/itxt_chunk.png0000644000175000017500000000050213766004353021056 0ustar danieldanielPNG  IHDR ';6IDATxcπ "q )giTXtcoachen-gbbusbus with of higher standard of comfort, usually chartered or used for longer journeyswiTXtcoachen-ustrainerx5; @ J {֍$*b703iaĻ{&oj!@QAFC<ܡ)L]Zt(5%QIENDB`chunky_png-1.3.15/spec/resources/partial_circles.png0000644000175000017500000000027313766004353022043 0ustar danieldanielPNG  IHDR g PLTEݵ_tRNS]<ZIDATxc &(ʐIѫUL l _@ U %@0L.""L0cLJ`1X`x%߹TIENDB`chunky_png-1.3.15/spec/resources/rect.png0000644000175000017500000000024613766004353017640 0ustar danieldanielPNG  IHDRRPLTEMO_H__گj@^ FIDATxc߫`0PgϩY @"߻b0d- a*IENDB`chunky_png-1.3.15/spec/resources/bezier_three_point.png0000644000175000017500000000014613766004353022562 0ustar danieldanielPNG  IHDR U-IDATxc?eo ^100H0m``dR`` V?̉IENDB`chunky_png-1.3.15/spec/resources/clock_mask_updated.png0000644000175000017500000000077413766004353022525 0ustar danieldanielPNG  IHDR(( H_HPLTE|CtRNS029?ABJRc bKIDATxko06 kYYZIi~9?NF( #72q`ronhRRыa1Q'505u9[Z2G;]j G&@PArk4AGYKO&oրk5ʛ|W ֏|b4 <;ޭH+0 %Z}}Jcc'=33#277ח퍫s/qm~D~{ݰDÛ"x~)|RjQND(mkH)0Q\KKtuuy]nz7.yQ>`|~rKUHi|x3I I_kGGG_.+~ ~W֘˝;dK_{EJ/=kdttԶzݞmLf|;Y~۔`tmEɈ_C7\X4ת#tkY{_{ֹt | Ys &i7MVSG.8#Ujτ4r(% NZ@uʄa&] NdoUw. (7jJtg-҅u._P.Є6I !1z @5`s'?`7S56`SAT[ T s#aB&ۦ4l׶) NV(..6ft%b6~< E-m7تSOv:r|9f,3zK]T ΔJ $sLYSi [v"]8jPB/ 6ܘNo8| _aD!hzV\0}Р@Gj DGbVdof$鬜:y @ "R9UhHd_7F$\TRC@+\pkz&YLƯ 5&qJ$ F.تtEV8t' N7gVMF g%d!X䄀 ǡь|GT. PiC؅I5y+u9Ym:p D5g% jVPubƑd|cNF7G6@:s6TH7\~>m6~6T`BCG.Ux'h5"7Y3Oß]2?lf^* w">C̉:oTfc4"n 0!|@؂ĽH#F\DjG3GYuDlD1"QKP/#OHH(*PB4%A0 %TT8P6!m%IW85 E.i?jb{@ʛ*s)%UK?R%)bKndId ܊2 Z˭jMQxf`֊:D VWzjt6@gGTX_X 3-]WU >-I.0x ݈u6rh8Mŗ_Ps4F'.A_KO@?UEN/^D󔝭"좄8 dk!ܬ 7?Ώ&|=gN"K;IENDB`chunky_png-1.3.15/spec/resources/clock_mask.png0000644000175000017500000000077413766004353021017 0ustar danieldanielPNG  IHDR(( H_HPLTEzzzzzzzzzzzzzzzzzzzzzzzzxtRNS029?ABJRc bKIDATxko06 kYYZIi~9?NF( #72q`ronhRRыa1Q'505u9[Z2G;]j G&@PArk4AGYKO&oրk5ʛ|W ֏|b4 <6 M:ڄ7<~ëX΃)%xwp/ʇ4Hg}$xӥ4H0]AIG!,' d; ,xHX~AKv=U<32p.){}N6vnnɖ?ëxx5 \:8%yjmv4 _V7$/5\֕jBX}ظe}::;Ba4'ݦs# L c}Z~ f!73ҙY(;oqT]£?CIENDB`chunky_png-1.3.15/spec/resources/circles.png0000644000175000017500000000043713766004353020331 0ustar danieldanielPNG  IHDR TgPLTE@@O.0@33@tRNS7oIDATx1 E]!z S Kr!\XX' Й9,^7眥`p"֣Ʀm~ (Ĥ::9uYo  Uͭ~Mml<`ޱ kD|$r|Cs&8AJ26B\ _3*IENDB`chunky_png-1.3.15/spec/resources/pixelstream.rgb0000644000175000017500000037510013766004353021232 0ustar danieldanielȦoookkkggg'%# #" $" (&$*'%*(&+(&,)'*(')'% &$#(%#*'%+(&+(&,)' //////........./// +(&+)')'&)&%)&$'$"&#!$" %"!#!#! # $" %"!&$"(%#)'%*(&,)(-*(-*(-*(-+)-+).+),*(*)'*(&$" (&$(&$*(&+)&-*(-+),*(-+)-*(,*(+(&)&%'&$'$#(&$*(&+(&,)'-+)-+).+)-*(,)',)',*(+(')'%'%# dddJA:nM3ΣVVV T6Z:!0jG,qM0[=|U6d<f?!`;8)08)08)08*18*18*18*18*18*18*18*18*18*18*1=/5mK1wT8uQ4xR4`AqJ+///,)'pH)|U8Z~V5_>tK+mD$nE%X8[;tL-a:3! nK0jF)hC%sK-Z:`@X8h?pG'kD#nG(qL/uR7V4]:`;pI*xO/^>gGZ9|T4]>oH)qK,kE)uR6mJ/%#!cccH>7iG.ڒZZZ%%%''' bF-\<#1 fC(^AhIiIeE|T5wS55'.5'.5'.5'.5'.5'.5'.5'.6(/6(/6(/6(/6(/6(/:-3jI/vR7wR5zT6uM.mD$000.+)tL,~W8zT7\@WC3T8#gD(sN2{U6Y8~T2[9|R0[9cAmLnLiIsL-;( mJ/iE(qJ-yQ1kKxVmKcA[:[:tL,oI,uR6tS9qL0{U7sJ+uJ(a?gE_>\]=dB{R12/-\9^"fA#sK*__>zQ1"'c@%cA'$#!dddE<5a="lll 444<<lB ]8W1xK(sI)6(/6(/6(/6(/6(/6(/6(/6(/6(/6(/6(/6(/6(/ EEE41.xI$wI&pD#oF' ^9nF&qF#S.U/xFR+V/V/Q*T-xJ%rF$6!`;lD%wL+|M)R,O(e>\5R+W1xK&qF$g?!gB&kC$xM*~O*P)O'Q*P,{N+tI'C:+dgPV3`>$&#!nnn^YThE(666***>>>BBBGGGKKKOOOSSSWWWOOO666^A*iJ24$ nJ.aBkHmHqJmGfC8)08)08)08)06(/7(/8)08)07)07)08)08*19*1=/5yT6[:T2d@nHjD742e>hDb?{Q1>+wR4a@d@^7mE|Sg>b9yPWyPkEeAB- }X:|T4Z7boMgBmFvPnJ }U5dBjF_;rLtM963kD`:iEcBmH,Y:~T2mIvOb:}TsJwN}TZ[e>kGF1  ~X:dCgCpJW]sJuL~UxOzTc>fDZ:kIqL~WnEtKtLmH^;|r]޽Ȭ бtP4666@@@ZZZ@@@%%% RRReI1gH16& zW;\=gDuP|UqJmIlJbB]>qL0sP4wU:wU:;):(sQ6rP5xU;{Y>rP54$dG1aAiG`=iDxS{T{U963wP{Ud@`>cD1"aAoLmHqJV\~UZY~UV|UvRG1 "gGhFzV}WXW\ZyPW|UvQmJmKjGqK~W~UViBkGlJ__SӶȭ бsN3666 BBB{{{P2Y9!. kI-jD%vL,Y6[7\8yN,oF':'#tO3uP3rK.vP2yS5yS5jD&jD&tN0nG)sL.Y9 2 X6[8fAiC]8d>pI631b;cyQ0O6!*U3Z6kFoHmDvMpGvMf=Y0b9]6jDvN/Z9Z;#T7 V;%T:%Q8#S:%P6"T;&L3D*J1H/O5!M4F-I/K1R7"[?(hG-\:^;lGf?Y1c:eDDD???zzz]>$pN57' M4!O4 [>'N7%K6&,3~Z=]>`@{R1}S2V5~T3V5fEmLjIxM+)`>lGsMwPrL`9f?741lEuNkEnKdCtL- H0cB(pO4}[?cEV6}Q0fCnIrKWyPg>kBh?h?h?vM~W[jFW3fD`@cD`B|W9vP2rL.oJ,qK-lG){U7]?fH~X:oJ,[=~X:dEgGeDdA]9`:jCc:vMWcW_6vNqJvQtPlIeC{Q0~U4X8V4_=oK{WwRW0nFuLyQySnJaA]XJ޽Ÿ бoJ,  [9]95# +,, - NYy lI-~W7W4fBb>iEgClHpLlHa=~O+f?!0a;f?f>g@qJgA631O(e>be>f>kBxOqHsJwN{RxOzRoHR,R-vH$[8W5X6X8\a@`?jI`@pF%oE$nD$|R1Y7\:c?`;e?mFmFuLyP\W\3V-X0_8iBgAe?[6b>`=c?gCmHuPvP`9S+W/\3jAe>b?|S2^TEҫҫyбpJ,oI+zT5zT5sN2eD)_@(V9!Q4Q4L1U:%M2P6 N4M3O4W;&Y<'Y='X;$X;#Z<$[;#\: fB%sM0oH*dB&W:#W:#V9"U:$Q8#S9%L3L2N4 K2H.I/M4 H.I/J1M4 K36$* ,,76:954,,##kH,}S1gAnFnFhAce`=`=[7Y5[7U2|P,T0]9U1U1Z5]8_:e?iCqJd=f=kBg>_6`7kB_6`8b:jBhAf?kDqKcg@d=]5_6c;\3h?c;c$mK2sR7uS8xU:rO3sO3rM0sK,cCcC`?a?W6}T4sK,}W9]?^A[>}X:}W9sN0pK-bD~X;}X:Y;tN0oL/a?%('76DDCC98--"" qM1V4^7rIYZV{R~UlCoFV}T|SwS7 kG+`9g@tM852tNqKmFb;a;Z7c@I4#4$]<]c=tMuOpJhCd?d?Z5kFsNsNuPkFiD`;Z5tOqLmHnIe@c<\6pJxQ{TyRuNvNiAf>_zQyPzQnElCc:uM~VYW{R}TkBmD\yPxOvMkEY5xO.PH7~gб\=Y:`?iFb@\}X;wQ4Y<\=^?}W8]>`@_?_?]:a?lHgDlJnK];dCbAdC^>a@eDbA]<|R2bBbB`?dCjJ[;xT6+*:9EE??54)) [?a?lEpGsJzQ}TV[WmDYvMyPxOlK (aB*wR5631qKvO{UrKmFiCiEa@ gFmJuQvOrJ}T~UZ{RuLyPWzQxOX~VzR{TsLjEtOnIsNpLe@oIqKrMiDpJrMlHjFc>iDsMnItOvPd?oIlGnJjFnIuOtMpId<{SzQxO~U^wNvMtKuLyP|SWWzQf=bxOxOyQXgCZ:`YEǤҫҫ б\kGpLpLeBhH328743,+"!yV:fDZYvMW˗n_{RWwNlCnEwNxO|S{S|Y>8( GGG631kEuNtMtM|U[jFjF@/"U<(pM]9xRxPpH|S{RxOVavM{RÏfa}UyQd>}XkG_nFnF¶ȻĶ·µǺɽǻĹ÷ l xT8V4[4b9O&\3mDrI`7P'Z1Z1_6eQ*y]GfPjTx^HoU@tZE# iBvOoHevMkB̭  l vR5Y6rJtLeByRwPsKjAuLWg>oFmDjAwNh?sKqItMzS{TqJ`;iDnIbjAuLwNΫz xS7fD`:hA;'|O+R,b:Z1_6W.jAh?vM~UzQh?sKV.b:V.Q*X2S.X3rKwPxQmDuMW0V0}O+zM* hD'vI%b=`9lDxO|SrIoFf=Z1a8Y2O)V2tJ);$  G1lG*rN0iD'Q5 nI+qL/{U8wQ4 S6oH)pF$Q-a;c;lCzQsJ{Rf=h?_6h?X/^5d;Z1]4^5f=jAyPvNtM_9d?zN*V4oF&e?!jE)```бtK*~R0}N)Q)R)Q(N%P'X/\3]4a8^5_6U,^5Ϋb>"yN,Y4\:D(rE"S,Z2\3_6]4g>N%Y0\3_6Y1O'~L$L$M%O(T.S-}L&U.X1_6X/|I"yI"vG#sF"@'.~R/|O+U0T,U-_6c:W.U,R*N&yH"tF"qG&iC%*䎎& U6X8W6$ ]e=e?!000бzP0W5gBf>a8uLYyPyPvM~UyPoFmDuLf=̫W;$xN.d@  ^AlHvOzRWsJg>mDoFb9sJxOi@]4\VyQxQpIf?kE`8oFvMd<\5uPqMdC gFhE_:g@pHe}TwNȾɿά |u|,J4!zT53!  `BgCyR|S[kB|SyPrIa8wNuLWWW[oHhFmFqJoGb9nErIsKsMpKsP8'L6$nKc?a;uMrJe<?35$I2  +_?wSnGXxOqHnEsJyP|SX}T^|SuL^:tMiCsK|SyPyP}UYrMiEzS4 lIgClGlE{SxOxO~U|VxSx\x\x\x\x\x\v[tYtYtYtYv[v[tYtYsXtYtYtYtYuZv[v[w[v[v[v[w[x\x\x\x\x\]=lInIlEqIsJqHyP\yPV|S}T[\sJZyPqHoFrIrI|SXzQxRjG_> бuK+wK*S/Q+S,xH#{M(W3R-T0|M(~N(S-\4^5_6ûȾк-' ,-,*( +7"H.N4 %#"$""$ e>pAT-X0a8d;b9V-Z1`7Z1T+V-`8a>#`9`9^7W.\3Y0W.P(Q+W3T1 H,yL)W3\6]6\3`7V.T-tF"oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@a:rH&}O+W1a9]4[2\3^5^5R)]4c:^5W.N%Y0[2`7c:`7Y0Z1Y0\4~M'yL)yO.PPPбzR3xN-T2Z7hD^;U3mM4H2"aAY6\8Z5xRwOkCƺvW=tP4|U7qK-cD^?Z<~W8kD%~V8[=cE- --,10/+*)"!   jC%fBsLzQzQlCg>c:zQxOmDzQ[xQ~WoHg?e"X5_:_8iAkBxPnIcAoS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@eB'b;\:]8cg>qHjAuLpGnEgAiF`?OOOбrM0sN0X9R:&  }S2U2\7e>º׿pHtMcJ0 $""654CBBA@@554)(' [<"U2e?h@yPyPqHjA`7lCh?d=b;Z3b;jBqHyPuLoFb9jBd=^9Y5M4 1 T1Y5b=f?tKuMgBW5rXtYv[v[uZrXrXrXrXrXrXrXrXtYtYtYtYtYtYtYtYtYv[v[x\x\x\x\x\x\x\x\x\hF+oI*xM,Z6mFmDuLtKxOi@lCmDe+^:nGrJ|SW{RzQY{RmEe>g@hBlDtK~U{RwNyP~VvOhB[7U4 U4]:b=pJzRpHjEcAoS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@rP5~W9Z8kFhAtKyP{R}T~U|SWxOmDmDxOxOnEtKlCrIwNzQX{RrLpMbA+++ ___б8' """### cCfDpKǹø|S~U~U{RVVZnEX^<1"10/=<;;:90/.$"" gFjDnEtKvMX|StKYyRrKwPwPpHrItKxO~U|SqH}VwQmInK4#M7%kHiEkFpIxQqLdCtYtYtYtYtYtYtYtYtYtYtYuZv[v[v[v[v[v[v[v[tYtYtYsXtYtYuZv[v[x\x\x\w[tQ6zT6eCmIYyPoF{R}T~U|S~UWYoF]sJyP}TZvMoFvMzQ~UsMgDjIWWW@@@)))а[@+ ###### [?)a@jGȻ÷ȿ_őhVŽe͙puL]lCpHrMeC 0/.0/.('&V:$dC^}T^őh]]˗n\xQgAiCuNYZ^`^_d^qJhCnJZ; iFlI\|VZzUrPoS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@mK0^?cBiEuMőhyP^őhVÏf͙pvM\mDtK}T~UqH{RȔk}T^ÏfXYafEvvv^^^а  ###############$eFmLȺø[^_`\pGdvMVlD_;bA0# $""  N5 Y;oL{U^dZZŽeÏfwNuN}VnG{U][_[YƒiŽe}TpI}W[6iE$cG1wSzU|V|WqMvUuZv[tYtYsYrXrXrXrXsYv[v[v[uZtYtYuZtYrXsYsXrXrXrXsYuZuZtYtYtYtYtYuZuS8vP2X7oKZ^^[^^`]pGbwNXqHkB{R]Zad^YZxU^=@@@а!!!###"""###############bB(W6ɾȻølCi@f=ejAkBa8V.[4Y3iCjDnFc:qHb9oFjAh?U,W/Y3S.T1\<$#dAW3d>d>]:~S2oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@a>#mG)X7]8b;kBf=kBh?eƻɹƺ`|Sf=g>}TYc]oFmDkBoHmHhGP9'$eE+wQ2V4iCqI|S~U]Wb9b9zQ}TxQwPe>oH|TpGZbd;\3{R|SWwOlFc?]9 ~Y;b>vQ}W[7rG%sXtYtYtYtYrXrXrXrXrXrXrXrXrXrXrXrXrXrXrXsYtYtYv[v[v[v[v[v[v[v[v[v[hE*vP1X7iEvN~UV`|Sf=g>|SYa\oFmDkBtKxOWX_|Sa9]7gDgFpppа2"########################iG-øο˽b9\3a8Z1e"pJ,U4c>b:sJc:c:[2a8Y0d;jAkB`7[2c:jAqHnErI^5i@V-]5T.}P-V5000б]?'### !!!################## -μ˿õ¸f=vMwNc:lCg>\3b9i@sJVtKtKnGlFY7K3!V8!nG)V3f?~UkBzQkBuLpGuLnEkBh?]5`9\5e>tMf>tKqHpGlCnEh?^5g>`8c<\6lF^: 9&kEgAc>`lC^zQxOyPWpId=f@jDd>blFV2W5oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@vS7vO0Y7fAjBpGwN}Tb9euMrK]6d=`uLqHyP\c[xO|S|S[uLyPkBsLa:hBiCc>pMjJcEPPPСuR5iG,( """""" 2~@@% PJFȹʺĵöʼϾŻɼxZyP~UWtKwN_~U}TwNpG~UpGc:qH|TsNjGfE`=d?YVyPzQi@zQnEkBsJVX\YtKjBhBpIlDpGpGyP~U]_}TqHW^wO}UiAuMuMh@kDtMsMvR`AgFeAmG}UsJd;pGZzQ~UVsJvM_~U|SwNnEzRjC\6fAoLkHeD]>uP3qqqa]YX2Y5N."""2/x[BWZZVV00776ξźĺùøùø¶øȼʾɼgR)P'{GL#L#N%vBxDK"xD}I N%K"P'L#|H~L%xH"uEwG!P)s@uAN%xDyEM$M$M$L#N%L#O&L#}I!\:{J#yH"|I!~J!N%zF~J!~J!S*L#W.xDs?|HxDP'{HzGL#N%s?vEm>oC b9X3J*?%=#?%:">$:! <"D(Q0S-Z1j>n?uD}J"{GP'M$P'P'{GL#M$N%uAxDL#xD{G|J"vFvG" %)]gK%)2+P/СyR5vP3wQ3, !!!\X4>;$31a=LH%>NN>>66DDffĺɻz~UpGjA}TuLwNnEb9g>xOkByPzQtKpG|SnEoFwOqHwNrIf=d;tKuLzQyPpGqHyPpGtKtKqH}X:rKpIrJoFrIuLxOqHwNvM}TnElCqHi@uLtKtKqHqHpHyRe?iE`?[=fD)X<&Z>(^C- N4U:$Q6  [?)eF-pL1yQ2U3]9mG`9sJ{RvMsJ~UpGjA}TuLwNoFa8g>xOkBuMoJc@8=-pxxd]]]бuN/xQ3vO0nK1  pkf7+)TJ<MMJJ55--;;W,,40.ƻȻ{tKsJtKuL{RyPrIkBU,jAkBg>kBqHtKtKmDqHuLsJXtKkBV-g>nEnElCoFtKqHmDsJqHpGxQxS6iCjDf>oFmDwNrIuLi@vMxOuLoFef>c:kBrIuLtKrIuLuLzQzQsJjAU,jAjAc<`;^;G@-yaxx[aFбvO0{S5Z;`@[Z1c:nEi@zQ|SwNmDepGyPvMuL^6lClCh?h?pGY1\4a9oFi@oFvM~U{Re>d=Z5V3pJ+C-hH.oN5nN4_@'5!_>%a?%eC(_@'G/ F/N5 nI+wN._dDiJeEaB\?5% 6$U:%flg775!hZ=ic0 q22DD8s_M}gy~yzi`X}TZ}TpGrI~UpGVV{RW`Y_Z~U]WqHqHwNg>XzQX][[\YY_XmFg@tMySyS|U]|T}VXZXrJe>nGdhByRuL~U[zQ~UcZ`X}T[~UpGrI}TnEV|StMoLlKFA.ixxckNб~W8}V7X8V5}U5pO5 1 KZ#  iys> ?+tQ2hArIWvM{RrIf=uLwN}T|SyP{RxOyPoFvMXxO|S}Th?vMwNVVxO|S|S{RsJyPW{R{R}TkBtKsJ{R}TuL}TuLuLnEwNV}T{RyPmDrIh?uMzRpHzSpJgAmHpJkFkClEe>kEvOmGwQuOf@kElFvOtMsLqJnGoGkBpGzQtKwPtNjH $ Z9]=F1 ;)hF`>G2! >+jFnHsLlCuLXxO{RVi@vMwN~UVwN|S{RzQsJyPWyPsLkG~T4LH2pxxv\ а_AfGjJ 8 E D0 fb=OJ(><"!  FB(<:852/+(E0zU7rL{TxQ]zSdYZW^~V\YsKpHyQWxPczRcW\Z^Y\XuMoFyP|TzQcxOőh|S_Zd]_`|StKrIzQ~UbxOőhvM^^aXZ]lFoJyToI|VqKY|VY{VtO|WgBvQ^9pJzT{UxQ{T\]^^đhwNZsLsNH2 D1"pNpMxVhFlKgG]=\W3X6[:oNmKtRpMiFmI~ZsOjG\:fDeBc@`=]9vQtQqMnJdBqOsPwTpLiCpIwPWsJd;W^`X|S[VȔkYrItK~UZxOa8zR{UwTfEFA.yaxx`eIϯyT7sN0@,0 %<|niAU- Wz:Op"3X?\(K_=P`+/~77s11WId!!&" \>$kE&vM+}S1_=[:\;];bAcA_=T1|Q.yN+\:X5|P-T1W4`=a>]:dBc@eB^;S0|P-{O,\9|P-{N+V2[6d?kEe>qIkCxOkB^5^5^5g>pGW.b9d;lCqHjArIf>vNkDngblfalf`jd_ga]f`[20. {S3fB`=gC`;lGc?^:T1 @*W5{O,vJ'V3[7iDe@nHe?pIrJiA_7c:]5b;]6V0X4X4eB_=dA`qHsJnExOtKxOnE`7]4]4mDa8Z1a8f=nEqHc\3_6|S~Uőh]jAi@e<^5]ZuLnGc^9\7`;uPrMe@mG]7Z2a;uM\XlEb;Z3b;g@~WyQlEvOa:Z3`9tM[]zRkC]4i@kBZ~UqHwNh?^5c:|SZdZoFf=g>d;ZxReA\<80!ixxZ`Fϭ35!.+Lw'TgTiY"WYb9a8a8kBi@e=xL)R6  GGG2/,zL)V3X5Y6zO+hD(2\:4"!0 uN1gE* I4xL)V3Y5_;W2\6[4T,`7jAeb:U,lC^5a8_6]4lCa9^6\4g>nEf=lCd;Y0pGa8f=^5f=c:e<^5]4ejAoFpGwNrIfD+ I4"Z>)30-`=b?iFa?lJxU: Hk?  V=)kGsMwQoHnGjBjAc:nEi@nEoFpGuMyQ{S|S}T}TvMkBjAh?lCg>pGnEqHyP{R{R|S|SxOxOnEf=kBh?b9mDvMqHwNzQzQX}TvMvMpGc:jA *S8$iEvMvOsOkH8=-ixx|^ ά2! 4D fzU'TymBwd-E{nRN0NJpRR%BkkJcx=;  M3???vO/~R.^:qN31!(gF,ZvMuLxOwN{Rc:gF, Y9 jBnGiC31/V3^;X6/"# 4M(Bc5   1J&  Z6Z5oIiC_8^5xP{SmFnGvOmFf>g?h@e=X1sLmFg?c;jB|TsLoHnGsLjBnGiAc;Y2pItMqJa9a9YuNpIjBuNoHlEjBc<`8nFyPkCf=kB_uLrIiElL2&2"qK-V1^; }CI5 ]@)653ά~zw "@En3}2iFSyAA|f.tLs)wYNk') ]A*???~V6nLiDb;i@W2\B-0#wP}T{RyPyPdB(e?YnF_8a:30.fB`=V4.!;W3Hk@ *- 2K+%'?'jdDg=S8#iFiE^:iEb=jEuPZtOnJd@iEeAgBaZ6mItP{WoJpKiDgBiDiE_:lHqLd@]9Y4lHsOzVrNqLlHhCmImH\7jEzTkEclE}TX~UlH6%bAZ|SkG,iBVjAnFpIuN41/mJeBdB~T3]A+Fk9;X2 cWOuF  fD[8b>fBgB[`>dBiGeCjHbA][9fEfDfDiGfD^=kJ]lIjHbBZ=uQ4)'$ά~* 9V-Ci 2\v3gW(JB}l:b-BxtQv1]~/u{XDK 2!???oJ,zP0c@e>c;c:c:jAb<)`:\3:&sH'\3i@f>lDg@g@30-~P-zN,uM-?)4M'&9Pw=  %7#MrD *wM+~R/Z7^:|O,`=^:d>^7[3jAd;a8^5d;X/]4f>c;vQ47%\7W3}R0xP1rL.*'&nnn YURkJ0`C, "D}yRŐd4:tLaњxj.N .'!ZA.R6 ???zU8^>jGjD{R`7i@esJi@S*e];_?xR4aAgDc>mFoFmDmD`7b9Z1i@i@h?uL{RpJ(d>_<V5.+)cccE<5b?#X9!Ug.%> B-PaQ:pKhyESg<%NpföXrM'%%' ]<"OOO!!!???YYYiD'vL*Y4]6c:b9_6^5`7R)L#, [2\3b9`8[4]7^80-,{N*T2|Q0J/    Ch>'"6 E,zO-|Q.}R.|R1vO/iF)rK,V4V1\4^5T+N%W.{G[2]4`7\3d;a8d;I0U7}P-wN-,*(bbbI@9nL1sO4$ R t_FĴ?/ gH/fE+lK0iH.hG-8''...???fff///zT5a?];b>mFnGpItMxQqJlE EEE2/-hF^=^= /sM1 7&'?\9I1%*bA\;a?|S4vP3fE+uP3[;b?tNvOtMqJgAtMsLuNjDrKgAgAlEX9/ dBbB-+)dddJB;]B|Y=_CY@,@. V?-*079S E@ Sf$#0 iL6tUxV;|[?~\BvU:[A+??????kM6aCjIiGyUuP\wRlGvQnIe@Z5?-|X={VsNyUkGwT^B0.+fDmMaB `@gHaCqS;_B\A- zU9oI,**eEpOjIhJ]ArR8\@fGrQoKiDjEhC\7zUzUxSuPyTrM~YwR[ ^>cD,)'fffPF>}Y<~Y=~Y<}X:|X;vS7M5# (? #6$$R;)kL2vU:wT8uQ6zW:wS7uR6xT9L5#/"  1$         2#cB(aAtRkIgDpKiD{WxStPsOrMd@S<)<'pKtP{WkHjGmJ4441.,yO.^>bE/$^>`A]>]@[=lN91#{U8pI,^?rJ+\?fFfF`@~Y;vT:vR4gG];tPfGkGrMsOpK[7qLrMZrMjFqLhD)gI3dE.+)kkk.,,VQLUOKVQNVQMTOKPLHOJG @=:EB>1.+-*(IDBLGDNIFPLHRMIROJSOKTOKSNIOKHMIEKFCIFBIEAIEAIEAIEAIDBIDBIEAHDAHEAJFBIEAIDAHDAHCAHDAIDBIEBJFBIEBJFBJFBJFCIEBJFBJFCJFBJFBIFBLGDNJFRMITOKga\f`\c^Ye_[d^Yiiiiiigggfffffffff_ZV^XT_YU]XTZUQVRM PKGUPMXTO_YUd_[\VRoooÿѺի֬Ĭ»ṹñ¼chunky_png-1.3.15/spec/resources/operations_grayscale.png0000644000175000017500000000017213766004353023116 0ustar danieldanielPNG  IHDR:AIDATxcQT7sr OaB"@,$ T!&4@hz8LŪEHkM%a. SIENDB`chunky_png-1.3.15/spec/resources/pixelstream.rgba0000644000175000017500000052140013766004353021367 0ustar danieldanieloookkkggg'%# #" $" (&$*'%*(&+(&,)'*(')'% &$#(%#*'%+(&+(&,)' //////........./// +(&+)')'&)&%)&$'$"&#!$" %"!#!#! # $" %"!&$"(%#)'%*(&,)(-*(-*(-*(-+)-+).+),*(*)'*(&$" (&$(&$*(&+)&-*(-+),*(-+)-*(,*(+(&)&%'&$'$#(&$*(&+(&,)'-+)-+).+)-*(,)',)',*(+(')'%'%# dddJA:nM3VVV T6Z:!0jG,qM0[=|U6d<f?!`;8)08)08)08*18*18*18*18*18*18*18*18*18*18*1=/5mK1wT8uQ4xR4`AqJ+///,)'pH)|U8Z~V5_>tK+mD$nE%X8[;tL-a:3! nK0jF)hC%sK-Z:`@X8h?pG'kD#nG(qL/uR7V4]:`;pI*xO/^>gGZ9|T4]>oH)qK,kE)uR6mJ/%#!cccH>7iG.ZZZ%%%''' bF-\<#1 fC(^AhIiIeE|T5wS55'.5'.5'.5'.5'.5'.5'.5'.6(/6(/6(/6(/6(/6(/:-3jI/vR7wR5zT6uM.mD$000.+)tL,~W8zT7\@WC3T8#gD(sN2{U6Y8~T2[9|R0[9cAmLnLiIsL-;( mJ/iE(qJ-yQ1kKxVmKcA[:[:tL,oI,uR6tS9qL0{U7sJ+uJ(a?gE_>\]=dB{R12/-\9^"fA#sK*__>zQ1"'c@%cA'$#!dddE<5a="lll 444<<lB ]8W1xK(sI)6(/6(/6(/6(/6(/6(/6(/6(/6(/6(/6(/6(/6(/ EEE41.xI$wI&pD#oF' ^9nF&qF#S.U/xFR+V/V/Q*T-xJ%rF$6!`;lD%wL+|M)R,O(e>\5R+W1xK&qF$g?!gB&kC$xM*~O*P)O'Q*P,{N+tI'C:+dgPV3`>$&#!nnn^YThE(666***>>>BBBGGGKKKOOOSSSWWWOOO666^A*iJ24$ nJ.aBkHmHqJmGfC8)08)08)08)06(/7(/8)08)07)07)08)08*19*1=/5yT6[:T2d@nHjD742e>hDb?{Q1>+wR4a@d@^7mE|Sg>b9yPWyPkEeAB- }X:|T4Z7boMgBmFvPnJ }U5dBjF_;rLtM963kD`:iEcBmH,Y:~T2mIvOb:}TsJwN}TZ[e>kGF1  ~X:dCgCpJW]sJuL~UxOzTc>fDZ:kIqL~WnEtKtLmH^;|r]޽Ȭ tP4666@@@ZZZ@@@%%% RRReI1gH16& zW;\=gDuP|UqJmIlJbB]>qL0sP4wU:wU:;):(sQ6rP5xU;{Y>rP54$dG1aAiG`=iDxS{T{U963wP{Ud@`>cD1"aAoLmHqJV\~UZY~UV|UvRG1 "gGhFzV}WXW\ZyPW|UvQmJmKjGqK~W~UViBkGlJ__SӶȭ sN3666 BBB{{{P2Y9!. kI-jD%vL,Y6[7\8yN,oF':'#tO3uP3rK.vP2yS5yS5jD&jD&tN0nG)sL.Y9 2 X6[8fAiC]8d>pI631b;cyQ0O6!*U3Z6kFoHmDvMpGvMf=Y0b9]6jDvN/Z9Z;#T7 V;%T:%Q8#S:%P6"T;&L3D*J1H/O5!M4F-I/K1R7"[?(hG-\:^;lGf?Y1c:eDDD???zzz]>$pN57' M4!O4 [>'N7%K6&,3~Z=]>`@{R1}S2V5~T3V5fEmLjIxM+)`>lGsMwPrL`9f?741lEuNkEnKdCtL- H0cB(pO4}[?cEV6}Q0fCnIrKWyPg>kBh?h?h?vM~W[jFW3fD`@cD`B|W9vP2rL.oJ,qK-lG){U7]?fH~X:oJ,[=~X:dEgGeDdA]9`:jCc:vMWcW_6vNqJvQtPlIeC{Q0~U4X8V4_=oK{WwRW0nFuLyQySnJaA]XJ޽ oJ,  [9]95# +,, - NYy lI-~W7W4fBb>iEgClHpLlHa=~O+f?!0a;f?f>g@qJgA631O(e>be>f>kBxOqHsJwN{RxOzRoHR,R-vH$[8W5X6X8\a@`?jI`@pF%oE$nD$|R1Y7\:c?`;e?mFmFuLyP\W\3V-X0_8iBgAe?[6b>`=c?gCmHuPvP`9S+W/\3jAe>b?|S2^TEҫҫypJ,oI+zT5zT5sN2eD)_@(V9!Q4Q4L1U:%M2P6 N4M3O4W;&Y<'Y='X;$X;#Z<$[;#\: fB%sM0oH*dB&W:#W:#V9"U:$Q8#S9%L3L2N4 K2H.I/M4 H.I/J1M4 K36$* ,,76:954,,##kH,}S1gAnFnFhAce`=`=[7Y5[7U2|P,T0]9U1U1Z5]8_:e?iCqJd=f=kBg>_6`7kB_6`8b:jBhAf?kDqKcg@d=]5_6c;\3h?c;c$mK2sR7uS8xU:rO3sO3rM0sK,cCcC`?a?W6}T4sK,}W9]?^A[>}X:}W9sN0pK-bD~X;}X:Y;tN0oL/a?%('76DDCC98--"" qM1V4^7rIYZV{R~UlCoFV}T|SwS7 kG+`9g@tM852tNqKmFb;a;Z7c@I4#4$]<]c=tMuOpJhCd?d?Z5kFsNsNuPkFiD`;Z5tOqLmHnIe@c<\6pJxQ{TyRuNvNiAf>_zQyPzQnElCc:uM~VYW{R}TkBmD\yPxOvMkEY5xO.PH7~g\=Y:`?iFb@\}X;wQ4Y<\=^?}W8]>`@_?_?]:a?lHgDlJnK];dCbAdC^>a@eDbA]<|R2bBbB`?dCjJ[;xT6+*:9EE??54)) [?a?lEpGsJzQ}TV[WmDYvMyPxOlK (aB*wR5631qKvO{UrKmFiCiEa@ gFmJuQvOrJ}T~UZ{RuLyPWzQxOX~VzR{TsLjEtOnIsNpLe@oIqKrMiDpJrMlHjFc>iDsMnItOvPd?oIlGnJjFnIuOtMpId<{SzQxO~U^wNvMtKuLyP|SWWzQf=bxOxOyQXgCZ:`YEǤҫҫ \kGpLpLeBhH328743,+"!yV:fDZYvMW˗n_{RWwNlCnEwNxO|S{S|Y>8( GGG631kEuNtMtM|U[jFjF@/"U<(pM]9xRxPpH|S{RxOVavM{RÏfa}UyQd>}XkG_nFnF¶ȻĶ·µǺɽǻĹ÷ l xT8V4[4b9O&\3mDrI`7P'Z1Z1_6eQ*y]GfPjTx^HoU@tZE# iBvOoHevMkB  l vR5Y6rJtLeByRwPsKjAuLWg>oFmDjAwNh?sKqItMzS{TqJ`;iDnIbjAuLwNz xS7fD`:hA;'|O+R,b:Z1_6W.jAh?vM~UzQh?sKV.b:V.Q*X2S.X3rKwPxQmDuMW0V0}O+zM* hD'vI%b=`9lDxO|SrIoFf=Z1a8Y2O)V2tJ);$  G1lG*rN0iD'Q5 nI+qL/{U8wQ4 S6oH)pF$Q-a;c;lCzQsJ{Rf=h?_6h?X/^5d;Z1]4^5f=jAyPvNtM_9d?zN*V4oF&e?!jE)```tK*~R0}N)Q)R)Q(N%P'X/\3]4a8^5_6U,^5b>"yN,Y4\:D(rE"S,Z2\3_6]4g>N%Y0\3_6Y1O'~L$L$M%O(T.S-}L&U.X1_6X/|I"yI"vG#sF"@'.~R/|O+U0T,U-_6c:W.U,R*N&yH"tF"qG&iC%*& U6X8W6$ ]e=e?!000zP0W5gBf>a8uLYyPyPvM~UyPoFmDuLf=W;$xN.d@  ^AlHvOzRWsJg>mDoFb9sJxOi@]4\VyQxQpIf?kE`8oFvMd<\5uPqMdC gFhE_:g@pHe}TwNȾɿ |u|,J4!zT53!  `BgCyR|S[kB|SyPrIa8wNuLWWW[oHhFmFqJoGb9nErIsKsMpKsP8'L6$nKc?a;uMrJe<?35$I2  +_?wSnGXxOqHnEsJyP|SX}T^|SuL^:tMiCsK|SyPyP}UYrMiEzS4 lIgClGlE{SxOxO~U|VxSx\x\x\x\x\x\v[tYtYtYtYv[v[tYtYsXtYtYtYtYuZv[v[w[v[v[v[w[x\x\x\x\x\]=lInIlEqIsJqHyP\yPV|S}T[\sJZyPqHoFrIrI|SXzQxRjG_> uK+wK*S/Q+S,xH#{M(W3R-T0|M(~N(S-\4^5_6ûȾ-' ,-,*( +7"H.N4 %#"$""$ e>pAT-X0a8d;b9V-Z1`7Z1T+V-`8a>#`9`9^7W.\3Y0W.P(Q+W3T1 H,yL)W3\6]6\3`7V.T-tF"oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@a:rH&}O+W1a9]4[2\3^5^5R)]4c:^5W.N%Y0[2`7c:`7Y0Z1Y0\4~M'yL)yO.PPPzR3xN-T2Z7hD^;U3mM4H2"aAY6\8Z5xRwOkCƺvW=tP4|U7qK-cD^?Z<~W8kD%~V8[=cE- --,10/+*)"!   jC%fBsLzQzQlCg>c:zQxOmDzQ[xQ~WoHg?e"X5_:_8iAkBxPnIcAoS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@eB'b;\:]8cg>qHjAuLpGnEgAiF`?OOOrM0sN0X9R:&  }S2U2\7e>ºpHtMcJ0 $""654CBBA@@554)(' [<"U2e?h@yPyPqHjA`7lCh?d=b;Z3b;jBqHyPuLoFb9jBd=^9Y5M4 1 T1Y5b=f?tKuMgBW5rXtYv[v[uZrXrXrXrXrXrXrXrXtYtYtYtYtYtYtYtYtYv[v[x\x\x\x\x\x\x\x\x\hF+oI*xM,Z6mFmDuLtKxOi@lCmDe+^:nGrJ|SW{RzQY{RmEe>g@hBlDtK~U{RwNyP~VvOhB[7U4 U4]:b=pJzRpHjEcAoS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@rP5~W9Z8kFhAtKyP{R}T~U|SWxOmDmDxOxOnEtKlCrIwNzQX{RrLpMbA+++ ___8' """### cCfDpKǹø|S~U~U{RVVZnEX^<1"10/=<;;:90/.$"" gFjDnEtKvMX|StKYyRrKwPwPpHrItKxO~U|SqH}VwQmInK4#M7%kHiEkFpIxQqLdCtYtYtYtYtYtYtYtYtYtYtYuZv[v[v[v[v[v[v[v[tYtYtYsXtYtYuZv[v[x\x\x\w[tQ6zT6eCmIYyPoF{R}T~U|S~UWYoF]sJyP}TZvMoFvMzQ~UsMgDjIWWW@@@)))[@+ ###### [?)a@jGȻ÷ȿ_őhVŽe͙puL]lCpHrMeC 0/.0/.('&V:$dC^}T^őh]]˗n\xQgAiCuNYZ^`^_d^qJhCnJZ; iFlI\|VZzUrPoS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@mK0^?cBiEuMőhyP^őhVÏf͙pvM\mDtK}T~UqH{RȔk}T^ÏfXYafEvvv^^^  ###############$eFmLȺø[^_`\pGdvMVlD_;bA0# $""  N5 Y;oL{U^dZZŽeÏfwNuN}VnG{U][_[YƒiŽe}TpI}W[6iE$cG1wSzU|V|WqMvUuZv[tYtYsYrXrXrXrXsYv[v[v[uZtYtYuZtYrXsYsXrXrXrXsYuZuZtYtYtYtYtYuZuS8vP2X7oKZ^^[^^`]pGbwNXqHkB{R]Zad^YZxU^=@@@!!!###"""###############bB(W6ɾȻølCi@f=ejAkBa8V.[4Y3iCjDnFc:qHb9oFjAh?U,W/Y3S.T1\<$#dAW3d>d>]:~S2oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@a>#mG)X7]8b;kBf=kBh?eƻɹƺ`|Sf=g>}TYc]oFmDkBoHmHhGP9'$eE+wQ2V4iCqI|S~U]Wb9b9zQ}TxQwPe>oH|TpGZbd;\3{R|SWwOlFc?]9 ~Y;b>vQ}W[7rG%sXtYtYtYtYrXrXrXrXrXrXrXrXrXrXrXrXrXrXrXsYtYtYv[v[v[v[v[v[v[v[v[v[hE*vP1X7iEvN~UV`|Sf=g>|SYa\oFmDkBtKxOWX_|Sa9]7gDgFppp2"########################iG-øο˽b9\3a8Z1e"pJ,U4c>b:sJc:c:[2a8Y0d;jAkB`7[2c:jAqHnErI^5i@V-]5T.}P-V5000]?'### !!!################## -μ˿õ¸f=vMwNc:lCg>\3b9i@sJVtKtKnGlFY7K3!V8!nG)V3f?~UkBzQkBuLpGuLnEkBh?]5`9\5e>tMf>tKqHpGlCnEh?^5g>`8c<\6lF^: 9&kEgAc>`lC^zQxOyPWpId=f@jDd>blFV2W5oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@oS@vS7vO0Y7fAjBpGwN}Tb9euMrK]6d=`uLqHyP\c[xO|S|S[uLyPkBsLa:hBiCc>pMjJcEPPPuR5iG,( """""" 2~@@% PJFȹʺĵöʼϾŻɼxZyP~UWtKwN_~U}TwNpG~UpGc:qH|TsNjGfE`=d?YVyPzQi@zQnEkBsJVX\YtKjBhBpIlDpGpGyP~U]_}TqHW^wO}UiAuMuMh@kDtMsMvR`AgFeAmG}UsJd;pGZzQ~UVsJvM_~U|SwNnEzRjC\6fAoLkHeD]>uP3qqqa]YX2Y5N."""2/x[BWZZVV00776ξźĺùøùø¶øȼʾɼgR)P'{GL#L#N%vBxDK"xD}I N%K"P'L#|H~L%xH"uEwG!P)s@uAN%xDyEM$M$M$L#N%L#O&L#}I!\:{J#yH"|I!~J!N%zF~J!~J!S*L#W.xDs?|HxDP'{HzGL#N%s?vEm>oC b9X3J*?%=#?%:">$:! <"D(Q0S-Z1j>n?uD}J"{GP'M$P'P'{GL#M$N%uAxDL#xD{G|J"vFvG" %)]gK%)2+P/yR5vP3wQ3, !!!\X4>;$31a=LH%>NN>>66DDffĺɻz~UpGjA}TuLwNnEb9g>xOkByPzQtKpG|SnEoFwOqHwNrIf=d;tKuLzQyPpGqHyPpGtKtKqH}X:rKpIrJoFrIuLxOqHwNvM}TnElCqHi@uLtKtKqHqHpHyRe?iE`?[=fD)X<&Z>(^C- N4U:$Q6  [?)eF-pL1yQ2U3]9mG`9sJ{RvMsJ~UpGjA}TuLwNoFa8g>xOkBuMoJc@8=-pxxd]]]uN/xQ3vO0nK1  pkf7+)TJ<MMJJ55--;;W,,40.ƻȻ{tKsJtKuL{RyPrIkBU,jAkBg>kBqHtKtKmDqHuLsJXtKkBV-g>nEnElCoFtKqHmDsJqHpGxQxS6iCjDf>oFmDwNrIuLi@vMxOuLoFef>c:kBrIuLtKrIuLuLzQzQsJjAU,jAjAc<`;^;G@-yaxx[aFvO0{S5Z;`@[Z1c:nEi@zQ|SwNmDepGyPvMuL^6lClCh?h?pGY1\4a9oFi@oFvM~U{Re>d=Z5V3pJ+C-hH.oN5nN4_@'5!_>%a?%eC(_@'G/ F/N5 nI+wN._dDiJeEaB\?5% 6$U:%flg775!hZ=ic0 q22DD8s_M}gy~yzi`X}TZ}TpGrI~UpGVV{RW`Y_Z~U]WqHqHwNg>XzQX][[\YY_XmFg@tMySyS|U]|T}VXZXrJe>nGdhByRuL~U[zQ~UcZ`X}T[~UpGrI}TnEV|StMoLlKFA.ixxckN~W8}V7X8V5}U5pO5 1 KZ#  iys> ?+tQ2hArIWvM{RrIf=uLwN}T|SyP{RxOyPoFvMXxO|S}Th?vMwNVVxO|S|S{RsJyPW{R{R}TkBtKsJ{R}TuL}TuLuLnEwNV}T{RyPmDrIh?uMzRpHzSpJgAmHpJkFkClEe>kEvOmGwQuOf@kElFvOtMsLqJnGoGkBpGzQtKwPtNjH $ Z9]=F1 ;)hF`>G2! >+jFnHsLlCuLXxO{RVi@vMwN~UVwN|S{RzQsJyPWyPsLkG~T4LH2pxxv\ _AfGjJ 8 E D0 fb=OJ(><"!  FB(<:852/+(E0zU7rL{TxQ]zSdYZW^~V\YsKpHyQWxPczRcW\Z^Y\XuMoFyP|TzQcxOőh|S_Zd]_`|StKrIzQ~UbxOőhvM^^aXZ]lFoJyToI|VqKY|VY{VtO|WgBvQ^9pJzT{UxQ{T\]^^đhwNZsLsNH2 D1"pNpMxVhFlKgG]=\W3X6[:oNmKtRpMiFmI~ZsOjG\:fDeBc@`=]9vQtQqMnJdBqOsPwTpLiCpIwPWsJd;W^`X|S[VȔkYrItK~UZxOa8zR{UwTfEFA.yaxx`eIyT7sN0@,0 %<|niAU- Wz:Op"3X?\(K_=P`+/~77s11WId!!&" \>$kE&vM+}S1_=[:\;];bAcA_=T1|Q.yN+\:X5|P-T1W4`=a>]:dBc@eB^;S0|P-{O,\9|P-{N+V2[6d?kEe>qIkCxOkB^5^5^5g>pGW.b9d;lCqHjArIf>vNkDngblfalf`jd_ga]f`[20. {S3fB`=gC`;lGc?^:T1 @*W5{O,vJ'V3[7iDe@nHe?pIrJiA_7c:]5b;]6V0X4X4eB_=dA`qHsJnExOtKxOnE`7]4]4mDa8Z1a8f=nEqHc\3_6|S~Uőh]jAi@e<^5]ZuLnGc^9\7`;uPrMe@mG]7Z2a;uM\XlEb;Z3b;g@~WyQlEvOa:Z3`9tM[]zRkC]4i@kBZ~UqHwNh?^5c:|SZdZoFf=g>d;ZxReA\<80!ixxZ`F35!.+Lw'TgTiY"WYb9a8a8kBi@e=xL)R6  GGG2/,zL)V3X5Y6zO+hD(2\:4"!0 uN1gE* I4xL)V3Y5_;W2\6[4T,`7jAeb:U,lC^5a8_6]4lCa9^6\4g>nEf=lCd;Y0pGa8f=^5f=c:e<^5]4ejAoFpGwNrIfD+ I4"Z>)30-`=b?iFa?lJxU: Hk?  V=)kGsMwQoHnGjBjAc:nEi@nEoFpGuMyQ{S|S}T}TvMkBjAh?lCg>pGnEqHyP{R{R|S|SxOxOnEf=kBh?b9mDvMqHwNzQzQX}TvMvMpGc:jA *S8$iEvMvOsOkH8=-ixx|^ 2! 4D fzU'TymBwd-E{nRN0NJpRR%BkkJcx=;  M3???vO/~R.^:qN31!(gF,ZvMuLxOwN{Rc:gF, Y9 jBnGiC31/V3^;X6/"# 4M(Bc5   1J&  Z6Z5oIiC_8^5xP{SmFnGvOmFf>g?h@e=X1sLmFg?c;jB|TsLoHnGsLjBnGiAc;Y2pItMqJa9a9YuNpIjBuNoHlEjBc<`8nFyPkCf=kB_uLrIiElL2&2"qK-V1^; }CI5 ]@)653~zw "@En3}2iFSyAA|f.tLs)wYNk') ]A*???~V6nLiDb;i@W2\B-0#wP}T{RyPyPdB(e?YnF_8a:30.fB`=V4.!;W3Hk@ *- 2K+%'?'jdDg=S8#iFiE^:iEb=jEuPZtOnJd@iEeAgBaZ6mItP{WoJpKiDgBiDiE_:lHqLd@]9Y4lHsOzVrNqLlHhCmImH\7jEzTkEclE}TX~UlH6%bAZ|SkG,iBVjAnFpIuN41/mJeBdB~T3]A+Fk9;X2 cWOuF  fD[8b>fBgB[`>dBiGeCjHbA][9fEfDfDiGfD^=kJ]lIjHbBZ=uQ4)'$~* 9V-Ci 2\v3gW(JB}l:b-BxtQv1]~/u{XDK 2!???oJ,zP0c@e>c;c:c:jAb<)`:\3:&sH'\3i@f>lDg@g@30-~P-zN,uM-?)4M'&9Pw=  %7#MrD *wM+~R/Z7^:|O,`=^:d>^7[3jAd;a8^5d;X/]4f>c;vQ47%\7W3}R0xP1rL.*'&nnn YURkJ0`C, "D}yRŐd4:tLaњxj.N .'!ZA.R6 ???zU8^>jGjD{R`7i@esJi@S*e];_?xR4aAgDc>mFoFmDmD`7b9Z1i@i@h?uL{RpJ(d>_<V5.+)cccE<5b?#X9!Ug.%> B-PaQ:pKhyESg<%NpföXrM'%%' ]<"OOO!!!???YYYiD'vL*Y4]6c:b9_6^5`7R)L#, [2\3b9`8[4]7^80-,{N*T2|Q0J/    Ch>'"6 E,zO-|Q.}R.|R1vO/iF)rK,V4V1\4^5T+N%W.{G[2]4`7\3d;a8d;I0U7}P-wN-,*(bbbI@9nL1sO4$ R t_FĴ?/ gH/fE+lK0iH.hG-8''...???fff///zT5a?];b>mFnGpItMxQqJlE EEE2/-hF^=^= /sM1 7&'?\9I1%*bA\;a?|S4vP3fE+uP3[;b?tNvOtMqJgAtMsLuNjDrKgAgAlEX9/ dBbB-+)dddJB;]B|Y=_CY@,@. V?-*079S E@ Sf$#0 iL6tUxV;|[?~\BvU:[A+??????kM6aCjIiGyUuP\wRlGvQnIe@Z5?-|X={VsNyUkGwT^B0.+fDmMaB `@gHaCqS;_B\A- zU9oI,**eEpOjIhJ]ArR8\@fGrQoKiDjEhC\7zUzUxSuPyTrM~YwR[ ^>cD,)'fffPF>}Y<~Y=~Y<}X:|X;vS7M5# (? #6$$R;)kL2vU:wT8uQ6zW:wS7uR6xT9L5#/"  1$         2#cB(aAtRkIgDpKiD{WxStPsOrMd@S<)<'pKtP{WkHjGmJ4441.,yO.^>bE/$^>`A]>]@[=lN91#{U8pI,^?rJ+\?fFfF`@~Y;vT:vR4gG];tPfGkGrMsOpK[7qLrMZrMjFqLhD)gI3dE.+)kkk.,,VQLUOKVQNVQMTOKPLHOJG @=:EB>1.+-*(IDBLGDNIFPLHRMIROJSOKTOKSNIOKHMIEKFCIFBIEAIEAIEAIEAIDBIDBIEAHDAHEAJFBIEAIDAHDAHCAHDAIDBIEBJFBIEBJFBJFBJFCIEBJFBJFCJFBJFBIFBLGDNJFRMITOKga\f`\c^Ye_[d^Yiiiiiigggfffffffff_ZV^XT_YU]XTZUQVRM PKGUPMXTO_YUd_[\VRooochunky_png-1.3.15/spec/resources/pixelstream_fast_rgba.png0000644000175000017500000006377313766004353023266 0ustar danieldanielPNG  IHDRgIDATxtն86** MB4ы*DƸwn['q8K}e `'&=w޹S9};)k)pGq>?| b /O))8<>|c@.\>sS?| b /O))8<>|c@.\>sS?| b /O))8<>|c@.\>sS?| b /O))8<>|c@.\>sS?| b /O))8<>|c@.\>sS?| b /O))8<>|c@.\>sS?| b /O))8<>|c@.\>sS?| b /O7ߦ10}|e?۟'4uc84c1XO|1Ph׿򗿴'r~f_}k{i}_~{7#f6bf~y?я߷zx㍛˗/믿ګjk/ym9yU;M}{c^s7޸`73G}d?O?sǠ`V .b矷Ç޽{mΝyf۴ii6l׻n:[vYV^mV+Wڊ+rix6ne n޳qoڵsݬy?}}wGsonwuw-o{zcgLqWջ6[lmf{챃:_qc$M;a;[58`?¦03?3[ȟwv*!!&z|#*t#FTZ .=^PRUP*ʗsBvm+}WSl!*Z5vXծU 0 ׾ *h5kTZ= ,hw^z5;V} i5CjXXjVzUGjUz*VJHξ:nx-[Z=_=oMw>u4}+LK6?ޏR1^3З+~9}mjPbkWx.FȖ6]-`6sb[٘Vm|Z6}}%Pa%F"p [=MlS2:ַm~F6[DIژp]k[B5 z}EƠʸC]Vb2dHC6Q"/7Q/Ɔ޽{[rr·xw}.]BC5놅@~sb)ַqup"ymxZs[3VKˏ] g7k kշ뵄7M}A9KЦu@ )S0 رÉs;Nm_2{8znshѪMIٚ[fh[GKfin[ǵۻg"'ֺx_ٖ/fT6fvcفe\;K$O y'@=ύqjF|1ΦhBֶ$'rMV6)&=ߢ-ln$DRQ^p#ݟ/xg7ܮf ~b8'\> *hXbE[%&&ZRRjRSSjժu$9ZֱAtՒW]_X޹QǷ, Yiul$'|m` Ĉ|8^T-lv(7a?v=zԎ9bs]?–md7CZXZZJT5hpON1jHK4:նkc[GؚAMlex[ާ-eKzK:h_bӷ;AQk-Vk0k=VVKp kN׏zƁMuƶZ϶mLmjF2y&j$rW.aCڐaV#p`:q j<13"3cZ|Db 4ƍ[-99ڴiciii֥KٳuѽSNIpYeā7jÚU4mIO v`m]n֛/-::={Μ9cϟwDճ65lF6QI(y jpoTƧԴI)m@{}H E]ز][ع-amCmrJ kX-ٛne[pmPIbNp[إX˶ kjhNk/\hѢk P2;ֶu0שDI߶-B]j&Zۺ-r+0S{~H x:L %" mfǧڎѭr~ة<;Am2dER=CAll't#ڦNv&vd[7'op[;WGuY]\_Lmg'fuv6vxz'EF;V7JClD;0 ڶ}bm߸n,[~kFuڕl6|'<>ieȰ<c\@0"qSN֭[7KOOw pflсt-ڰH 00-geD 3. ^տʺFbmvhjk?=/5cށ % qusLh'ةȔvlF>YŶ1-%Jo긊% ZzhK8Nk⸸=v, i30}3.1l˘6gR{?lL w6gr?>}Ƶ?n% <Ǐn`y/]6S@ǻ5k Xl1V{`[L'B,HF?؅q̰6Q6"5,չ^D!3mbG:9ݯpacڊ-ldyF+ZB 6sC)VZNo#- m%6HDjfw㳻<ˍ|dF'WK[9EU Xjp}wL (Ngu`?=4\lQg82ŎaigRC_N }ĉlֶwR;SxZHhm j8?a% {. .]ݷZ!xJZ `O&Ѹx 0|pԳgv"s"(n&H^*$)7j*,&FKG.lExV aez6Uniblrz6S͔6UMצE:cؔ*]-ЖeҲcoMl31Qapm6s1ul#kW(b8cuOHNlۇ7H'NжIVz7 މխ[\jB,]԰uKicBU6֯`cZ!Qw;W$ղ1%Jqc[ۖIom`ۆ5#l_[mk%qVKmLI/]bFs4U|l F.^:7vn5R k@ʶqlu T*lY:"x6baEƺ ,ͳfrmhO<񄋱$GÂddtkqa~7GwkU Xaj*!Ǩ#wWćynh-mOkmF:^2pL`pTzketm;Ʒl+!FV!-jĎ ln$[>$ٲz4:߮mvvJ"+\n=cZ8=)S'+l!p#7aM4|厕͂~Ijx_:"ʡɎ&pm.w#Ν[JWMp\UC70f%-O$kr-G lz $MزS qv̮$'I!zJ^+^(C{wYJ7m?>*[F,׍_s]V7o^?EH2H^^͔z_RvO¶DQA 汱ԙ=lf[22I?C=mWMf:.$RRAUguD37HhCr^/dixкi3'v2Lse)FXzC2xz]>J{ť:U4\3Lr }bJMHVDhr[gAImӰ$-^5ƄH="leH[? މ[D:wkAk k&]؊uv C3 s4ld/\>왚#Q#Bi5NhSG(ĺam@ZC"o]kUK]IbXK!^H8қYjխwG۾ Ulx 9%5! ۠gBuz4kwBX>]iZ:t5J[mB'}!=m~^N+|H 0cdD+?f =iG6Ms1_ٽmPZ2'05q0ofx&q-W>JGTZSZV9޶i(vq <,QzK @[Hmlh۾2"mH:6E նqoBuwNIdwBV*![mfSbo;ĸEߧsYo6Q$%Y > s>g[ lD_/[eНw:L%K`[`sFԽc5P'}X}fF8Ri]q p*Źsŋ"c)9εNZqr@zH3k ՠFY[OW[Ar| e%Mn++/`M+!M젬㇦u=o6 MrIa'4ivPz spԎ2WSi&iU߆|4M}bGAd9`^6å$qo;7?=`mINyHK4dtgRl9:p>sp-vkè[5L.%p͕wVMZPlpuxc.HD"2Ed [$nx,+%:p-pb`{εRsDO-\C㸍ۺ fy3uj;*>n'C*1GJ,附n f 6 (.%Hgel;+U$ժPLWmV5i  b8>\@Xe{򬜛;k*Hۃ: 20xb+ s{9` BN 0:q}qτ6ڻr&}@ڿ_n{P`ƯIn]vKѳ͙$˘yhcO/nL2^y9[98 uV/Z$b`z=΍lNGQhݓ;9pLgt׏L[&tigvUW MvNJ]j[e@( m5w#6ɪ{8ÿ_xV;8G7)C*Y?ʭZغR-V̹hgH|#[5} gHp3n- P ;`a1H_8K^ `8  tn؄OY@$\Ƶ\\xь.voF'M0AnRaSk.Ogg?1RXɆƽʹM.[4Q74(~&]3:d=ǽ=z4wz̤ѐ/;f2z}T]dWfwn`?+\NZ96sR';$ -ogcs[`l~"3]COc>,!YO-Ԡ0ێ-koK){^Nc;Ǧ8b<[<..dg{5mʹw/GIYT1|%P6W#gQ3^\6..(}5IdҒ 6,YexqP3OOL4#S5uԨZUˬFbV|q U TZT|Mhݓ"vRVU kV(!;Ix9[pw\I^;.<ױQfN-hCGWOrwiXw,% ?=dt Ihv^>uU~&QIeʨW-QL킎a2)F2UEm ٽl8XD1NnY`YvlfP,/^XTPގ* W)uPݥ6VjiZ 8tM=߂5ݳcE'Sֳ0`ef}ˁl_鶿+[זc||}KvV{l\Gp[Qyey[XGN8v <',$pw<,Ψs AD pXR-P5k$SRYhI<{" fw&x+,(d=IbpKǙh<_UJ +_TFR*RDV-Yqu*KJъƗg[ oxBdf?1Ȗ7 ;Z%{AlDI EIk#kme$WRΛ7h#"afzD032KX5A΋xx'^r \sP_i @# `/|zq-n eIݯhw7mi;K`-C[:E m'8QQZsŇ+{s dBWH H[qc\֐8ljWzr.e꾳:Ԗ[$*̦ =1FWDB`BC7gG[VGev5FI+Y(Uwkc[׶ɲ=:džw {}-6^nEg+N#%.c~0DpkXV+gB' P/4e,1(xu71nc3X3"@L%IN5LN>z)'Ni,]oݩJR4 KJO u" B":0/j*DmZjulOds)z ks2E X_J_=1+``:>D?D>/,R /m ځ@Jwޯk@-҃ɣ%iϤ.'@_yd"рqo:#bM9Rj.w|wYߤ0nDr|rwq\2ƨr!"J"C]5=1hupi $85{FNU9( fhw/Cɓ70*-Mb]C/_Xba' ݋2GExN䐈̀Ơ|YvoMD]o_}sO/׉lNj߰Y#Kg;dï[T|\n;9M͝pP/ :td`2Ж s2z>cA=cI_7**70/"7-%2t(P2&!.;+7\p"e0@o] e\#}gמ`/>rΞpҞ{~{i{){؛>,`?a??}+_ H:SKUKVkTZ b~&ɓU\,@BJJOW9- 2$`7r/^ tLI5vS5vǹC7K gA#Ĝ.dn5혚nۯ4/x)r9nZvݏ~,Q!>c^Ў.vL883q<49z/9 R+plip8Cz080#PK1Q`n*R2j"w& >rj㔶蹿߼d?z37S;?|YWctwGx6G{1]Ҿ: [\.:M݅UEj*Z*TBnyoK]y+9\ ܄}Oc>>K#iQ)|stXt>DI/TZe!%!uKGh?a CO\."}τ~ J`Sk:RAƆ3j cyFc 15w]f*2X)BAͲ-8GYHy%*䢎Rv\/*F̂psNXXXKF, Tpȟy_9^BX'!$瀋+`U[Hcǯ=#Q7nW€x@|xQW@~ ?a#n*@.:VkH&}B9&(فFAshkܿD<+Y+wA,ȠE#aR9kXRJ"@`ޫɐ5b -V c1_+ȺCIvdzHswLmRbڬ$u c^+@ gf2d!J{.KhMrVכBq70$(W{vR_>EOt,5zd@g+ƹOv hq5鳜u?s~{q{ɋ+/ew$b'tmplN*uFJFV B@dnD$Ҽ=\!^1;4f.Np OWc!9,`"GrIiuv83 cH$tIΥ6bR[/0ȁIB:r\*V:8F|#S(5U;ԕ&A;ɳd,ЊH&} r,<@*KS\K_wQ~ >g sxp+@}ʧVaoXP=Y MIggիជwmw^|~!XO5B0|9zAWQ5jBZƪf1U8j+dEػvJ["15Rl2 }<804;\W[t yɟ'whN&i~MZ[WNAu)4EQQ13%ڣ~fǵ؞8P5!I1uq 9}kBpxJrֺjR|?BI,y;TaKADs** p xwq aif}ټN=`O]8eox=PWѰh2N)-m GU+c Dm8Y~-}ɫyۥ"1X~"sF >X7m+P85|:qS5n{`Q?ǁ~}]?ʝOu⮔l~Ds!*mKQ|R*zk@Y[3zz[sڝ˱q|9޹l9~RJ$8Ek {O|=1H5DLF^BkC9R or]{52v~( d;w'`fO/=x*)f,Ρ]K*yDžId@\dտƁ׉u"[7H ;bbgH 3n H42k5ve9-L&9 h#+XȉQERX :1gIS\~u2W^_R^ jKQ7%!bW}1RŽOrVI/ΔAp`| Yi wІwÖ'K{;.WA' [8)lʼnON J8`DhJ]=;h]0nHIW:gϖUr=g}}:Iu_Ёl[gg\Io ʈP@Ax(u,GDB#׏lm~w*A0x(H (ʂ&Y\ ࢈$)Up8 F\JFI9ϥ) `Pq6uOq`u;#7<`D(BEl='8,Rg#pf '0‚ ց`Kĉo o;fFyse "> oudžev WޖH,>?zI{7}g>˳OۧyX `23s3pIAp(ҭ)+:HOUբs%\dts_ HD*9<4@8-"I sih8qFdF26.<j =o4HS,"zAg噙<{BBVwJxZVގL$s[l޿ks ̅ pֵK앇O O'?c*'ҋ?|1{~p=ux\^dTƒ EBG1 pDpH' v1U]=)r zÓeDc@c! g+(CȦ9J]IV*wV}u2"j:@5#㼞>|3nycv|gm}?{^8p c0H&>'h`yO/ީGc|f G]5pt›n>|{4["B{4J*CBMҹ%%%ykW $)}=q=ȳvnC}sSڇ Dп8+/f~~c# SZJZ@!t&p_oz€80ӃDa9=XĈ?΄Oib ѶK߭v^K6we+j:LT3l2Isbr\.- hY $ W:ߚX[Җ[r%K: -.%0">Íez ;um\)ڞS{=lJ,^IkN!qE{vwe{>0TϹ&E\MB} F x*n]浯i[V,]5ۦK[7s j6-9Uw\U4+cӚusm~4lvsTLm7s?d8oVJ%iukJRi//Yz,3_NI*cqMۄUlp+P;zU$&Ջ߻9.ZakZ%T-dGd:U9u(jl|Ry֪m[ͽh 'A ̳B9:wzJJ_p5Fשhꗲ' [Q.oYZH2]cu+w. P1djDg^Z^ DU_β'ʅM}j5Oim,-u}WڛOP>6d*tL.UD_}$EkgjQk^znUqᶈaLޅ8*\Oh+u#W& 1\牃|w=J)Z &\>q 'pb9λ>h|'d 5d氢pU.qARExj\z7e[Fw[n?8ֽrkVi 8WzD KlVyT *HuĘwXgzϵ i=!>nƬ8$Vmhe6oPZ~r茡ߤ{k: [aŠo&ژqV'涶|~c(汃UX[$&ZO-: =pt'go] TzWAYr+PVD]-mE}nl7 4 Z$4|g0c Xb1q Xp99|G<ѵa i\ί@uOJ͢=U=9yTtU,u/}7@dՇu NY1i~X5яulIU΢:Guci]MjZJ VfMw=#ke&+yMt+hTܨM 4&,h}&D&E,ln&)uɠ&*;pgk녊]6f6:UR B֬vYHpgo s36{j_`=F5hqRľo tk|(>ھ'[>w^pjMT[:@ap(&F&  Z^@鳞95r pD[ \%}8΁{odJY0Ȃ+c,NpH1PՑ /h@ν'U5Q+pre䵑ZXk4Ok'Ì}$u2j14-7e-w\kK;\9~%bJ *KMK/tAЛ}^ /~qYFC[Xz&p^_V'ޥBZ'vh+U8к7ÒU}Cy},6)v,e?pp;"ⴾcGý-{Q5$RIB& %>ˊ]T12@#,{x{|{tWwqpM@-d`xb)CA0pyUpS- @U}h f eD%``Z=3%VNjms328a_7tohUGKFs/!g`{LYG7SZ9ji$K NoZD6Ѳ[cYM} Rbs*_\ +*JMi˳B&(z$4x14{ wpil;Ki^*NK8g+;3WnZ9K`nXkeY-BԜQ6s/h"E@7=A/ R-s'$\o,fGZ2H׹{/aB) E-t V$4a\Ԫ*b# XҁJ,ض5m\YHR~KQrC+ݪ~ S+`NU~ѽJĦ%畕#N{8PO{pIUZYgy rcF[q9q.EWVN㾁t>rbv=}".{2q?C9yF}8Hjp_<=:S>]%JsYE-bgT`8*s{Y)[ WYݛR|o{>[EU%Uȗz +Yn͈nɕ} EREMҪGi28_[pkB\F2D Mh|H,pa/iǛD>*Z2+U4nx`c^޿F, `{/u-TjฎqVI'-|Unk1ݛ=Ϟ9ޟeDVfZ,Wؾ'`m-(B{-%WSuϸ hš>5wHq{@mru)v2Kcqvkf/F-kN0?~~²ZG:C8QX q?$2=nNn@ Z^iKd>ynR+EY#YY?ZĢ =D')kia 1TSJԓڮdZqOVjXQaqa͖/|EZ~t]9k$Ae%D%BHHWROlK/Gtf5c|F8#w` 7lST`e"sk\ozJYL/ֲvZK:V~Nr?QfPjʂ]MMkؘ䚊]Fi`a\/Q0$>SRtJc49kB[=-W*V%fVWձ;SFk_RjtLHeG5t^g W]XJbBRPeð7F`#X, ]8\}U)=Pޣh;qԼBmV[>_ոۆ1,ƺU6Qx$^bIݣx--Xy+EoRW+2<%Re[E*B-岤id,UݖFerҠFDZRVFi=>W_Ȩ 7 ?PE,FQcߙRZ%սJ<[)rxu6KqAJ*ػ?|jiY1mV68SeZ[}j XɆ7dwrY|w[b[5VBZFV&-XBXkUh=E}ũGqm-`bj-lX{2Iiڄ۴RT BoJ$Sl] ǧ? d0ڮFX#p85*up.]"z5)tRp֪Uj˗;Ѣ\;Uzh+e f;D^+0WT=IalG`kSSCfb+^-aX\)gD]$Z(IB4r*c2 ̞?!5RX PB3&Eh==c 'Z?5[l3aR䞌.nXwY*`ҥKnrxB)k1j}Ν;j\Vڸ&=K#[ղ萢.jAV5Ka,S$iP݇(*URQd& B qK-Ѣg*pAWsZaR#2||ĵ90noecl<7)4H+V-&?` qS֛=Zs4Ss B_SeJLiSztntn$ kVI|*k;~ؔ=6`QG%L(c7׳۲J6h]M&ImJ%Ki.rKwoz,nc=fV> #V!JWENR5WĘ8p`3 Q7W K/%Bp>{pL}%:z{fxl["蝖:& (⢋~M%-HdJ`6"(;B.7KcX" xxF&,3ڎAǤ!{0D8u=̀湍=/ίgGJ%k| Y-.}⟉ Y(?p;x*yUK:"ben* W% #,yv%eEZ vbY%Zlc!MRbAqb*xi0_f|Q[vR&_ZREmDJ]'10^1P.D7 { @e9sq`pLT}S\::bq}*_LabL :ԲCb|Kj)P$E鮉^]cJ_%`k6F:˜uB% 1|':Uc?H W& =VGkB,h;3S8T=A7铬ȸOq|,<fr$$tlhYLrl,2cDWZ@sͳ!@k/-6D) J{M-l^&Z Yj}x~QQM;@ٻ[b}ْ-fDi빲]PZ̭dc'u"9;:O~m^֪]e-vD<9o~F:2>slbQsDCaAD2^ۮ(, 5ؔ0$h55i0:p\[:+yRx9wlaVQ%4)@W~ VVxi+862ԗ- 8^'{obkeZ# ::0}#s3t5.TJy׳ڪ=ƍo>𭌩1x6x.U,]lLH2UkUbse3+m0_dO]`gت^Z!Vzu>s;"BJЯYUSXעT I&@B Oknܞ/daLЍe[WW 7HUD>+Vªԁ`8p]Enk,jmDjIdV4 ?w_/?4ڟ'? u]F Vw4^:#+HO|Л+4''K5eК*CZՑd'7e9ĭXv ٴiӷX69QM,Ie-m\cK_F.lcW%Y!-s0뛕jCJXqp˔qjBXӺLa>駟vMk믻ve n h-[ތ_n@Yf9Ե3xpzg 0fJ c 4XK4Iuaq A-z jSņwW#P7GխL5‡YHD7 {-u{6 #4xA+0x ^kkŅ: R ŚCJeT2 Mb YD/7onxȐ!9/?5)H(`U=G {Z0.ن-f#VXt 5Tk%j52 <ֵkל{y?%NFbE<#i  :WQkA( pNQ{a3Uʆ5 Ik]椊aqlebKشz[E[wBTrV+ 0/SG.*v 47ph3@wR(5 VjNW^&wɍcou$0tQB-XD6sM\FL#[hZ1t]Tn/E 幔`*VRLT#!7ղ nǾ^MَjfVpDqU:Fz,Fp.| -OaZA;Nn)hZ#n141`ak("m (8M#Z= u-_3 1+ל.Q77xTJ֡+Kr[$j@kai:L RJZ8{[S؀mt qughQ`;Á!^f$>Gٔ Jܡ w؝ygyӦH>k=aKR2Z߆kG)a s1WЯ ;we> ׯCXq$DFAJ.0l)*@b.Qw4ns|X񁳍{JZ>CJP޸rU,FVBkmB=n`ˢK;NtmJ y}u`"%5xhI; }Aa?1W#YK9+ YEo ㏝9tМܶEKDқյfX$JT"Z2H7O\]ux yR mj6+eH9},|t4#w q覷 8WqU80 8W5ʘ27˔AMr $voJn:+Gt%2eIA|JX׽r(mVl J(^(-UՓ#R]Joa}TguoE:ז߽mb#۪*:0vOo*Ii&Zl|<bJ͊42oOjJ/=FCZY𰜱 _~e{ |cVh @|pN)MmnLm)6G})!Vk JR`QƝV%1Xgȩ>a{Ν֥e浮"ûո(&nMASjMr|7_MP["BD,.YHp(.B:EHJB1\_ ɭ7w=wf;;9sΙ9wgemϿ̷Do9rh%z%X?{U{6E|>{)pR7v-uDld> ZarX䅑E[lA:뵻so,W.m}T8okk|~+{[×'vS9ސ-Ir7wuwhMԁQ9`IClɣmW;kah/ϻW8p <77G{rqE [2amm-6{06k0juۢK!$?y҅Px?Y}_}W@JL?_ Ã=aS@1 vb!^ +XY.3x:>cm)]ǟߗ2蟽Z[JדNw&md{Fxj"ܳ-od6==;H:I9us<ܾ>N>;uc,ܲrn] L|Q:MOƆX{MxLc:??W"Q %` +lj|8ԏBˉ'ϱng`sDFxq&yJ\ѱ+^:ZNV۰y|r ]g7>{ˁryjV)-ϕ1K$̍=3OѣG4u5[]]CcOGX((3.\sw_luc0#&9QgAfH/# aR'皲=eЁ;12Y`qM 'Fe? hhbi44DFDcy) TŖ*?Ǫ| Ra?'Bl4TVjT䥌JvX.hgq;<߲0͹RgcR q"ĆN a4 pUb[mwՅɓxrb&by~?"66: :C@?jy= < s&f8M%9#y@06p(={{{1ӧO'qK'X\aK _ Viv3<-CCcyruu]yD9wʡ 3d1|S{F%ˍ~809`\*7;8OIw0apK1O|`ۥmyfۨ Za`<W^|_ΪsLS()dJ@40rKRH)F2% g%)$JJ#3 p %L HFn@  8҈@$L#D BIiD Sp["B@N4") 8-H! PRȔi`S()dJ@40rKRH)F2% g%)$JJ#3 p %L HFn@  8҈@$L#D BIiD Sp["B@N4") 8-H! PRȔi`S()dJ@40rKRH)F2% g%)$JJ#3 p %L HFn@  8҈@$L#D BIiD S%ɲIENDB`chunky_png-1.3.15/spec/resources/ztxt_chunk.png0000644000175000017500000000136113766004353021103 0ustar danieldanielPNG  IHDR )gAMA1_tEXtTitlePngSuiteOUL1tEXtAuthorWillem A.J. van Schaik (willem@schaik.com)GAzTXtCopyrightxs/,L(QIU(KSNHQKO,/JU04յ4aRZzTXtDescriptionx-0 D~MLX`\C"8J UWY{wwRg+]|?BO&2 .IlL} "$ܮg<-.=KMk.QI#|Ě=&'*EQ5 o>ᤊ{,SPm{͹<}mUxoGmҥVbz@zTXtSoftwarexs.JM,IMQSHTK).I,sJ3 rK ҕBQzTXtDisclaimerxs+JM-O,JU`ԮIDATx] 0 P*@# #T10lPF`ؠF=IQ*u`%qk H񚈩mߟ э=,fOK t(F ;P{xp]9/p*$(*yՃ@C  cqNU#)11.rf0gh(tEkIENDB`chunky_png-1.3.15/spec/resources/cropped.png0000644000175000017500000000026713766004353020342 0ustar danieldanielPNG  IHDR7`PLTEP`pP`pP`pP`pЀАРаBIDATxcb`b Z]1CIENDB`chunky_png-1.3.15/spec/resources/clock_bl_xdown_ydown.png0000644000175000017500000000047013766004353023111 0ustar danieldanielPNG  IHDR alPLTEz~$$#$pqrqrcIDATx 0ԟ@P}W^61b]wI¾3DXM7 @ ueeaz'ÅDsUxq$=*%i|IƔ* j{ȠnY[Zy6B?%+ IENDB`chunky_png-1.3.15/spec/resources/polygon_filled_horizontal.png0000644000175000017500000000027213766004353024161 0ustar danieldanielPNG  IHDRڄ PLTE=-tRNSDP!]IDATxE ! Փ_@A@@At@Y@wR֐*+Mi&7j[K?&;1O஻ ;2WA HpiIENDB`chunky_png-1.3.15/spec/resources/polygon_triangle_filled.png0000644000175000017500000000040513766004353023573 0ustar danieldanielPNG  IHDR6q-PLTE&-5=ZtRNS9UqnYD^xIDATxUϱ l4V`6l`c n"ZMx@!?ylj[o$t0}#aT5]UɀAwIfN2q C%{gVr7:`l)-ؿ8IENDB`chunky_png-1.3.15/spec/resources/operations.png0000644000175000017500000000020113766004353021055 0ustar danieldanielPNG  IHDRh6HIDATxcb`/_ 0o`?`310F5j:&ҫIENDB`chunky_png-1.3.15/spec/resources/clock_nn_xdown_ydown.png0000644000175000017500000000031113766004353023121 0ustar danieldanielPNG  IHDR [A'PLTEz"xܓ7]IDATxc:s ]k/80&{;u ׁQYT83/`qc,&&&6 ԥ"IENDB`chunky_png-1.3.15/spec/resources/cropped_height.png0000644000175000017500000000150013766004353021661 0ustar danieldanielPNG  IHDRǨgAMA a cHRMz&u0`:pQ<PLTEPP P0P@PPP`PpPPPPPPPPP`` `0`@`P```p`````````pp p0p@pPp`ppppppppppp 0@P`pЀ 0@P`pА 0@P`pР 0@P`pа 0@P`pAbKGDeh oFFs]BtIME 42' vpAg\ƭIDATc``dbfaecggPPTRVQUSg00426153gpptrvqusg  gHHLJNIMKg((,*.)-+3)%tEXtdate:create2015-10-25T16:52:50-07:00I%tEXtdate:modify2015-10-25T16:52:50-07:000IENDB`chunky_png-1.3.15/spec/resources/lines.png0000644000175000017500000000067413766004353020022 0ustar danieldanielPNG  IHDR(p 9PLTE2}%22>dd}#RU/U/____>IDATxur E %tL}K (mQ̜]"ܬC4:h?9"4Bl<B#lLqN=U WaK3,< tV$X 鸏W9 _cAƅ.uPp0= O=ye& NmeMڝ\̞i%0?7Y8 1& Sw8LFWyMtWRU2Dyt]T}y*HqO*·w%Ы۽PBUp1@H$X#Vc^RBǟ&eOGIENDB`chunky_png-1.3.15/spec/resources/clock_nn_xup_yup.png0000644000175000017500000000176013766004353022264 0ustar danieldanielPNG  IHDR-- PLTEz|"#CcsxO$IDATx SPA\R[qu5P=M̭3(癗sz2I㓙梳Mc4GOGN'1x``vѭNt/__]OGG*ԫ p\&^+)j|&P..4ϏOOϯ} EI0MH˝%BrZ/@t oh@XrV;fxG]2Ɗny^d yCqZ=rC}1f^`Ws CxP3 `&!Ҙ0g4C!Tp@!{X")5'|Im4W`G=o9?\c!qs:kᄑp1pbܺpǩ?Fc  \(H ́ox'Dmh ԏ UE0d4dI) nJCL#.H;wK}"%'J].Kp"'5qP]$=TPTP,3}|,!WհMӗkeOUI~~zei|4QsnޚN ? '9[ uؤAN&vAI:l_5]; aWq~dim GzV -Nf]X˫R)u<g( IENDB`chunky_png-1.3.15/spec/resources/bezier_four_point.png0000644000175000017500000000015613766004353022427 0ustar danieldanielPNG  IHDR U5IDATxc?AL~10}a```W ]~ :vIENDB`chunky_png-1.3.15/spec/png_suite/0000755000175000017500000000000013766004353016156 5ustar danieldanielchunky_png-1.3.15/spec/png_suite/chunk_ordering/0000755000175000017500000000000013766004353021157 5ustar danieldanielchunky_png-1.3.15/spec/png_suite/chunk_ordering/oi9n0g16.png0000755000175000017500000000240313766004353023143 0ustar danieldanielPNG  IHDR kgAMA1_IDATxvIDATсIDATށ[IDAT@IDAT1y}IDAT IDAT\XIDAT0MDIDAT !1IDATCmIDATQ4TIDAT9w=IDAT[ԁIDATIDATEIDATZ?mIDATܧ]IDATIDATA>IDAT\XIDAT(IDAT{\IDAT2,hIDATށ[IDAT*>IDAT/UIDATg珺IDAT7IDATU?YIDAT?^PIDAT8:vIDATŀIDAT`KIDATFVIDATW7IDAT<6IDAT<6IDATcM IDATIDAT:4ZIDAT+IDAT`eIDATK>1IDAT&IDATVIDAT(8}IDATZӁIDAT+IDAT|*IDAT@^/drs~='3_Zwt(p3p]`_yt?t(C\l52L̩g I@g.`(`g Dg&((`60Y`zl(P49܀:{z*zȟmt3ΞAO3B^IENDB`chunky_png-1.3.15/spec/png_suite/chunk_ordering/oi2n2c16.png0000755000175000017500000000047213766004353023136 0ustar danieldanielPNG  IHDR 1gAMA1_IDATxՖ 0DA~&fzE=֠B>/drs~='3_Zwt(p3p]`_yt?t(C\l52L̩g I@gseIDAT.`(`g Dg&((`60Y`zl(P49܀:{z*zȟmt3ΞAO3B^MwbIENDB`chunky_png-1.3.15/spec/png_suite/chunk_ordering/oi9n2c16.png0000755000175000017500000000573613766004353023155 0ustar danieldanielPNG  IHDR 1gAMA1_IDATxvIDATсIDATށ[IDAT1TKIDAT[IDAT IDAT\rIDAT0MDIDAT5mIDATDYaIDAT`KIDAT2IDATA) IDATEIDAT$fIDAT~!IDAT}=[IDAT4IIDAT_IDAT|*IDAT1TKIDAT7@IDAT[IDAT1,RIDAT69IDAT,[IDAT&5IDATf鹅IDATA>IDAT̺sIDATzIDAT;TIDAT,[IDATE.IDAT_[IDAT=pP1IDATG IDATIDATU?YIDAT5mIDATB]TIDAT(8}IDAT>Y`CIDAT/@IDAT2IDAT8IDATIDATdbةIDATr3mIDATs4]nIDAT~!IDAT;TIDAT=pP1IDAT'2ȃIDAT3IDAT_1IDATT_IDATf<IDAT NIDATZIDATwYwIDAT}=[IDAT6IDATFIDATT_IDATtPIDAT(IDATpx= IDAT,IDAT3IDATFS{IDAT NIDAT-IDAT{IDATpx= IDAT]=PIDAT쟪IDAT_?M~IDAT`eIDAT NIDAT WHIDAT^IDAT_1IDATIDAT쁄ӻIDAT{IDAT6IDATypIDATtPIDAT{5IDATYdIDAT\XIDAT?^PIDATtPIDAT{5IDAT_[IDATLS(IDAT(IDATCmIDATV 3IDAT\J`7IDATll1IDAT(8}IDAT :kQIDATgIDATV 3IDAT VUIDATKIDATDYaIDATgIDATIDAT&5IDATЮ<IDAT0pIDATIDATO7$IDAT\XIDAT(IDATKIDAT(IDAT`eIDAT WIDATK>1IDAT;TIDATRR'@IDATT_IDATWV|IDAT1uIDAT6qIDAT0MDIDAT1,RIDATIDATIDATY:IDAT`eIDAT7@IDATzIDATzPIDAT\XIDATllIDATzIDAT2⩕IDATȽ7jIDATH=IDAT_?M~IDATm;` IDATtPIDATT_IDAT3IDAT]IDAT𕅏IDATŀIDATT_IDAT?ëIDAT&IDAT^IDATA) IDATO[!IDAT/UIDATOIDATIDAT3IDATsIDATaQ:IDATB]TIDATr=IDAT^IENDB`chunky_png-1.3.15/spec/png_suite/chunk_ordering/oi1n0g16.png0000755000175000017500000000024713766004353023137 0ustar danieldanielPNG  IHDR kgAMA1_^IDATx1 0 CQ9[ܠ({2*ُ?8Wc:`݂@B&@=2 -hL`?oO8K_+IENDB`chunky_png-1.3.15/spec/png_suite/chunk_ordering/oi4n2c16.png0000755000175000017500000000052213766004353023134 0ustar danieldanielPNG  IHDR 1gAMA1_cIDATxՖ 0DA~&fzE=֠B>/drs~='3_Zwt(p3p]`_yt?t(CnTZXIDAT\l52L̩g I@gecIDAT.`(`g Dg&((`60Y`zl(P49܀:{z*zȟmt3ΞAO3B )IDAT^z zIENDB`chunky_png-1.3.15/spec/png_suite/metadata/0000755000175000017500000000000013766004353017736 5ustar danieldanielchunky_png-1.3.15/spec/png_suite/metadata/cm9n0g04.png0000755000175000017500000000044413766004353021712 0ustar danieldanielPNG  IHDR )gAMA1_tIME ;;u0IDATx] 0 P*@# #T10lPF`ؠF=IQ*u`%qk H񚈩mߟ э=,fOK t(F ;P{xp]9/p*$(*yՃ@C  cqNU#)11.rf0gh(tEkIENDB`chunky_png-1.3.15/spec/png_suite/metadata/cm0n0g04.png0000755000175000017500000000044413766004353021701 0ustar danieldanielPNG  IHDR )gAMA1_tIME "8ݜIDATx] 0 P*@# #T10lPF`ؠF=IQ*u`%qk H񚈩mߟ э=,fOK t(F ;P{xp]9/p*$(*yՃ@C  cqNU#)11.rf0gh(tEkIENDB`chunky_png-1.3.15/spec/png_suite/metadata/cm7n0g04.png0000755000175000017500000000044413766004353021710 0ustar danieldanielPNG  IHDR )gAMA1_tIME V IDATx] 0 P*@# #T10lPF`ؠF=IQ*u`%qk H񚈩mߟ э=,fOK t(F ;P{xp]9/p*$(*yՃ@C  cqNU#)11.rf0gh(tEkIENDB`chunky_png-1.3.15/spec/png_suite/basic/0000755000175000017500000000000013766004353017237 5ustar danieldanielchunky_png-1.3.15/spec/png_suite/basic/basi0g02.rgba0000644000175000017500000001000013766004353021372 0ustar danieldanielUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUchunky_png-1.3.15/spec/png_suite/basic/basi3p04.rgba0000644000175000017500000001000013766004353021410 0ustar danieldanielffffwwww""""DDDDffffwwww""""DDDDffffwwww""""DDDDffffwwww""""DDDDffffwwww""""DDDDffffwwww""""DDDDffffwwww""""DDDDffffwwww""""DDDDwwww""""DDDDwwww""""DDDDwwww""""DDDDwwww""""DDDDwwww""""DDDDDDDDwwww""""DDDDDDDDwwww""""DDDDDDDDwwww""""DDDDDDDDwwww""""DDDDDDDD""""wwww""""DDDDDDDD""""wwww""""DDDDDDDD""""wwww""""DDDDDDDD""""""""DDDDDDDD""""""""DDDDDDDD""""""""DDDDDDDD""""""""DDDDDDDD""""DDDDDDDD""""DDDDDDDD""""DDDDDDDD""""DDDDDDDD""""DDDD""""DDDD""""DDDD""""DDDD""""chunky_png-1.3.15/spec/png_suite/basic/basn6a16.png0000755000175000017500000000655313766004353021302 0ustar danieldanielPNG  IHDR #ꦷgAMA1_ "IDATxݙ_luKrM.ZKʴRM Y.R2Z[Ɋ^qb,ZɊ (0PrB&\)(D~D4KK/9wUU4AΝ;ww|猕i |ؘ}}+M!3߀ޱ&}߯ZfF5[,@f.3rZtL017qHÿto Z?TR4٢:O~-{D`kEU4")sZHfn2@6RtXO)ޯ;voR'zֽO۴=5ߣDOqD Oc3`co47e k$9SQ aȗ~U5`:OE5CJ :u'|VMt?\3%'*JS_7W&; 42@THtZ]m'Ի6tmrwK'Z׉^щ~QCbV b^NJ^Jr宁Z S _XfxNEOfͅz-/?ջu5Dp^vGVˏGW5| %˿\5ėf%XYhNf uSSn6Ys;;:p-'AW:UGpxqB?7GsZ g`/f@%6mW#r?zf7k.{zzz _NF٪ At5Huta>4u-⋋=wTK\*< e7 M@x޶"&nܛ9f5|>IEtSqEUvvhƫ ^R\TjVz=XtodɎap~~ p}c ׍{3lvB}ކ|9:^TyJz5%WkFƫ%X go|h7#aL$7u)7ݬP?*>WU4^Y+/WZO?{1+^ |{ w u'0oh5HM@3~ݸ7sf7k.7e-/NK?RxZ s'@/eAY@ [?p| HQtu yfKϼQ-ѪHM@3~ݸ7sf7k.ԇ _|IV?4ħځN,'[3kXf|>o$_NY%an }MEFj0&fqo攛n\~|[~|!~ݻj%VyN&#āHFq@ ҊU;2t`@2zKoT۵3=-nj7#aL$7Vlvkeݻμ K@pR (#8grRKX4L;V݌14׍{3\67aZi.3o~`i޻ {pi 3 =)opQqdv%)3{>5XaXiH4ь̤+[h7#aL$׀fiZV!_ Cɽ?x~;w1[o zDE3ؓI~]Rl~%yZ+1S5ISZj݈VF˜H.~xVv: N]mx⁍/43}Ȉ c'ݛ l/Ib*aS1Q&1%?3j݈VD\9߭;wR'3j=K3Y ҕx{vi 1\[SΦx>lOcJF ޒ4ŨuWGjO9hn$83*}}§Rw~;@4k7syv|aE%aL>  j0&3gƫˁȽ_BAqޓ=vA&folnon4|6jd.dndoGq?eí۵IENDB`chunky_png-1.3.15/spec/png_suite/basic/basn0g02.rgba0000644000175000017500000001000013766004353021377 0ustar danieldanielUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUchunky_png-1.3.15/spec/png_suite/basic/basn2c08.rgba0000644000175000017500000001000013766004353021403 0ustar danieldaniel~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!  ~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!  ~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!  ~~~}}}|||{{{zzzyyyxxxwwwvvvuuutttsssrrrqqqpppooonnnmmmlllkkkjjjiiihhhgggfffeeedddcccbbbaaa```___^^^]]]\\\[[[ZZZYYYXXXWWWVVVUUUTTTSSSRRRQQQPPPOOONNNMMMLLLKKKJJJIIIHHHGGGFFFEEEDDDCCCBBBAAA@@@???>>>===<<<;;;:::999888777666555444333222111000///...---,,,+++***)))((('''&&&%%%$$$###"""!!!  chunky_png-1.3.15/spec/png_suite/basic/basi6a08.png0000755000175000017500000000055113766004353021266 0ustar danieldanielPNG  IHDR }JbgAMA1_ IDATxŕAN0EAfAME*q *e@ՔAHɊ|?'dG` 9c:1Ⓣ{=k ֵS˰gc. 3{_m /N @-~'.lM1! jѠ D h=F`u@]`^-^%x zRhb!9:XF/h.䋱 lY PtP΀W(3mYm πu(ש P:JSiNsBIENDB`chunky_png-1.3.15/spec/png_suite/basic/basn4a08.png0000755000175000017500000000017613766004353021274 0ustar danieldanielPNG  IHDR sgAMA1_5IDATxch41",(,?a0T1`4GÀ*hP* }IENDB`chunky_png-1.3.15/spec/png_suite/basic/basn0g08.rgba0000644000175000017500000001000013766004353021405 0ustar danieldaniel  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~~~~}}}|||{{{zzzyyyxxxwwwvvvuuutttsssrrrqqqpppooonnnmmmlllkkkjjjiiihhhgggfffeeedddcccbbbaaa```___^^^]]]\\\[[[ZZZYYYXXXWWWVVVUUUTTTSSSRRRQQQPPPOOONNNMMMLLLKKKJJJIIIHHHGGGFFFEEEDDDCCCBBBAAA@@@???>>>===<<<;;;:::999888777666555444333222111000///...---,,,+++***)))((('''&&&%%%$$$###"""!!!    !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~~~~}}}|||{{{zzzyyyxxxwwwvvvuuutttsssrrrqqqpppooonnnmmmlllkkkjjjiiihhhgggfffeeedddcccbbbaaa```___^^^]]]\\\[[[ZZZYYYXXXWWWVVVUUUTTTSSSRRRQQQPPPOOONNNMMMLLLKKKJJJIIIHHHGGGFFFEEEDDDCCCBBBAAA@@@???>>>===<<<;;;:::999888777666555444333222111000///...---,,,+++***)))((('''&&&%%%$$$###"""!!!  chunky_png-1.3.15/spec/png_suite/basic/basn3p01.png0000755000175000017500000000016013766004353021274 0ustar danieldanielPNG  IHDR IgAMA1_PLTE""fl&IDATxc4?fYIENDB`chunky_png-1.3.15/spec/png_suite/basic/basn6a08.png0000755000175000017500000000027013766004353021271 0ustar danieldanielPNG  IHDR szzgAMA1_oIDATx1 0 F'dhO?U!ExRP(M(ي0^{~3uG XN5"}\TB\.y 6{@<P6@R LIENDB`chunky_png-1.3.15/spec/png_suite/basic/basn3p08.png0000755000175000017500000000240613766004353021310 0ustar danieldanielPNG  IHDR DgAMA1_PLTE"Dww :w""""Uffff"DDUU"DDUU3DDff3D"ffD33U*D˺[""f2Ucw:DDkfBkܺ33sJw{"w332fDwJf""UUDff3UwwDwffD"w"3333cU3{UUUUf܃wwUUww""DD3ԪU*U˴f3BSD̙"Sww333Ĉwff""UU"DD[wfwws33wD""U"f 3bIDATx GHd+;3 ekXegа**4h lޣY2W%syiHCL*;8"KE6sx'HK?Y9sģ>1~!'B 0IENDB`chunky_png-1.3.15/spec/png_suite/basic/basn3p01.rgba0000644000175000017500000001000013766004353021412 0ustar danieldaniel"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f""""chunky_png-1.3.15/spec/png_suite/basic/basi6a16.rgba0000644000175000017500000001000013766004353021377 0ustar danieldaniel{skcZRJB91)!{riaXOF=4,#!!!!!!!!!!!!!!{!q!h!^!U!K!B!8!/!%!!! !!!1111111111111z1p1f1\1Q1G1=131(111 11 !!1BBBBBBBBBBBBzBoBdBYBMBBB7B,B!BB BB 1!!!1BRRRRRRRRRRRyRmRaRURIR<R0R$RR RR B1!#)!1BRccccccccccyckc^cPcCc5c(cc cc RB1%!,1!1BRcsssssssssxsisZsKs<s-ssss cR!B(1/!49!1BRcswfUD3"sc$R,B318!=B!1BRcsvbN;'s(c0R7B=1B!FJ!1BRcst]E."-s5c>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~~~~}}}|||{{{zzzyyyxxxwwwvvvuuutttsssrrrqqqpppooonnnmmmlllkkkjjjiiihhhgggfffeeedddcccbbbaaa```___^^^]]]\\\[[[ZZZYYYXXXWWWVVVUUUTTTSSSRRRQQQPPPOOONNNMMMLLLKKKJJJIIIHHHGGGFFFEEEDDDCCCBBBAAA@@@???>>>===<<<;;;:::999888777666555444333222111000///...---,,,+++***)))((('''&&&%%%$$$###"""!!!    !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~~~~}}}|||{{{zzzyyyxxxwwwvvvuuutttsssrrrqqqpppooonnnmmmlllkkkjjjiiihhhgggfffeeedddcccbbbaaa```___^^^]]]\\\[[[ZZZYYYXXXWWWVVVUUUTTTSSSRRRQQQPPPOOONNNMMMLLLKKKJJJIIIHHHGGGFFFEEEDDDCCCBBBAAA@@@???>>>===<<<;;;:::999888777666555444333222111000///...---,,,+++***)))((('''&&&%%%$$$###"""!!!  chunky_png-1.3.15/spec/png_suite/basic/basi3p01.rgba0000644000175000017500000001000013766004353021405 0ustar danieldaniel"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f"""""f"f"f"f""""chunky_png-1.3.15/spec/png_suite/basic/basi0g01.png0000755000175000017500000000033113766004353021253 0ustar danieldanielPNG  IHDR ,wgAMA1_IDATx-10 EƂz.z' V9cX,e5|KxOp笹pi\Yc*L'Dd[6癹+MKOKzҍ2-czpEHp-/zQ!塌Qf"IENDB`chunky_png-1.3.15/spec/png_suite/basic/basi2c08.png0000755000175000017500000000047313766004353021267 0ustar danieldanielPNG  IHDR 5gAMA1_IDATxՓA! D{x=Ys3hhf gZYd1b|V%}֠Ɯ7~gv>^/-Jcm smXTmcm @/drs~='3_Zwt(p3p]`_yt?t(C\l52L̩g I@g.`(`g Dg&((`60Y`zl(P49܀:{z*zȟmt3ΞAO3B^IENDB`chunky_png-1.3.15/spec/png_suite/basic/basn2c08.png0000755000175000017500000000022113766004353021263 0ustar danieldanielPNG  IHDR gAMA1_HIDATx 0 @r;D++ ; }Lx@J„(t8#@pw^@KIENDB`chunky_png-1.3.15/spec/png_suite/basic/basn6a08.rgba0000644000175000017500000001000013766004353021405 0ustar danieldaniel )19AJRZbjs{ )19AJRZbjs{????? ?)?1?9?A?J?R?Z?b?j?s?{????????????????_____ _)_1_9_A_J_R_Z_b_j_s_{________________ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{Š͠ՠޠ )19AJRZbjs{ŀ̀Հހ````` `)`1`9`A`J`R`Z`b`j`s`{````````````````@@@@@ @)@1@9@A@J@R@Z@b@j@s@{@@@@@@@@@@@@@@@@      ) 1 9 A J R Z b j s {                 )19AJRZbjs{ )19AJRZbjs{????? ?)?1?9?A?J?R?Z?b?j?s?{????????????????_____ _)_1_9_A_J_R_Z_b_j_s_{________________ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{߃ߋߔߜߤ߬ߴ߽ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{````` `)`1`9`A`J`R`Z`b`j`s`{````````````````@@@@@ @)@1@9@A@J@R@Z@b@j@s@{@@@@@@@@@@@@@@@@    ) 1 9 A J R Z b j s { chunky_png-1.3.15/spec/png_suite/basic/basi2c16.png0000755000175000017500000000112313766004353021257 0ustar danieldanielPNG  IHDR ۏvgAMA1_ IDATxՖ!s0?8a>Vx?0.;Xx0p0 SW+c Y}+EI$ ottЅ Awj88:A?/ĠnqsT`z%K,: 뻇oތ! ~>6K2m4?)u7,㍝4ueqCYcgg' i!lB4M\x2s&߆OiҮC-hDýgt<Kzz)]}?30.W\ZQKiپnp7Bn(A ;`n~"OÎ, :X@apy(b.7 9-#X1s;WhPfS!6ئrͬ’(T(o+ܟ| KWcf޶܀ fC>A y(kfPEMpx4t! ٟ%kx370%8$NA-!xzX!jl5+ .S3;-[W~u>Gtu,_WՁS7_ի:aTnZM߳0ctµvx,jq(VAyOjoBe-oA>k+?3V̮ū!xS [3O01 'wj칷d/SU槜wpx6kfi]^.#jkFS dF0,~Fef[7}^X݆@-xOC^#AQA ̋ɇ2B*Kik * \,koBZJ<w||&f(o@)Lirm^[Я.OFJ[_ye_uJȿ:)d!?eC;2zFW^'d2)d2#z3\8MoU(P̮Yӗ,Q lȞuN]s#ǯ\++rB|\H!e7n5]߷#BR r ,9gG$  ; &(\ ?k%{M+0lrow[O2Ip An;nC[0c5 {r Fچl늺fVXH] Y6_{6 Y-%B !?2uHM̋B45VL? cp>f֖ܷfEW!+7oB`30~&k74z<©̋uC_?0/MoB3 cV2|*( aHA$aAy٫.}fs]yu]uN΁Uh = =Ms>=ㅢ0%-C/ SRRaHv {a`z>C0EPs ʜGB't:p4t@IBK,# )ȏ7 _MHoFH,k*nvR3΅!nw!WIЅQ2 o? 7O0`7ȓ0eHTlͷ5 ƺ ?@w.3`>aoW phJς~ CoA&A>cQHoR): ӡ\^7_cbtƮSQr '!<PoA^r]P+a"0$L0d2&ȝ0`pttͥ4Wce_׋ȺYqa@f3dR 0\N~ UTcZ$XCϙ!C(9q>3Z_3=g@ BޛW;I`RU}]N;ծTC.(σ@3;`|ۡh thA ϙHZ7A4۞$lZHxjks# !(M@LO@ ,{022 ' }g|P\_{@MLG25 >i!'505-0e 䅜Bk)F[a"J+[4Bo+PA(x|%EqXk@G>3c٤ehu![HPSB 9!! 9uyBROH)!}B?+Bz]H6!f9z@ȱ+*!'~ $_B*T2gys >!H{I9lIENDB`chunky_png-1.3.15/spec/png_suite/basic/basn0g16.png0000755000175000017500000000024713766004353021274 0ustar danieldanielPNG  IHDR kgAMA1_^IDATx1 0 CQ9[ܠ({2*ُ?8Wc:`݂@B&@=2 -hL`?oO8K_+IENDB`chunky_png-1.3.15/spec/png_suite/basic/basi3p02.png0000755000175000017500000000030113766004353021265 0ustar danieldanielPNG  IHDR ygAMA1_sBIT|.w PLTEe?+QIDATxcxǰ]̌!9J޸b.??dCH dJHR?t#B,*9Z}uIENDB`chunky_png-1.3.15/spec/png_suite/basic/basi3p01.png0000755000175000017500000000020413766004353021266 0ustar danieldanielPNG  IHDR >!gAMA1_PLTE""fl&)IDATxc`P CѰ1Ù3IH6?@@!( B-vÞIENDB`chunky_png-1.3.15/spec/png_suite/basic/basi0g04.png0000755000175000017500000000036713766004353021267 0ustar danieldanielPNG  IHDR gAMA1_IDATxeQ0D:(u AAAAA$TI~ò¶EuEĺCsGjw<# ^bs8Al.iGZ'(CYd:"k@i2Gpr:1(Kkce s {ig 826N'MIENDB`chunky_png-1.3.15/spec/png_suite/basic/basn0g02.png0000755000175000017500000000015013766004353021260 0ustar danieldanielPNG  IHDR =gAMA1_IDATxc`]0PS3 cI IENDB`chunky_png-1.3.15/spec/png_suite/basic/basi3p02.rgba0000644000175000017500000001000013766004353021406 0ustar danieldanielchunky_png-1.3.15/spec/png_suite/basic/basi0g01.rgba0000644000175000017500000001000013766004353021371 0ustar danieldanielchunky_png-1.3.15/spec/png_suite/basic/basn0g01.rgba0000644000175000017500000001000013766004353021376 0ustar danieldanielchunky_png-1.3.15/spec/png_suite/basic/basn0g04.png0000755000175000017500000000022113766004353021261 0ustar danieldanielPNG  IHDR )gAMA1_HIDATxc``TR26vq MK+/g CA*wLrPV#ݽT3r%GAIENDB`chunky_png-1.3.15/spec/png_suite/basic/basn4a08.rgba0000644000175000017500000001000013766004353021403 0ustar danieldaniel )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ރދޔޜޤެ޴޽ )19AJRZbjs{ՃՋՔ՜դլմս )19AJRZbjs{͔̓͋ͤͬ͜ʹͽ )19AJRZbjs{ŃŋŔŜŤŬŴŽ )19AJRZbjs{Žͽս޽潽 )19AJRZbjs{Ŵʹմ޴洴 )19AJRZbjs{Ŭͬլެ欬 )19AJRZbjs{Ťͤդޤ椤 )19AJRZbjs{Ŝ͜՜ޜ朜 )19AJRZbjs{Ŕ͔Քޔ攔 )19AJRZbjs{ŋ͋Ջދ拋 )19AJRZbjs{Ń̓Ճރ惃{{{{{{{{{{{{{{{ {{{){{{1{{{9{{{A{{{J{{{R{{{Z{{{b{{{j{{{s{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{sssssssssssssss sss)sss1sss9sssAsssJsssRsssZsssbsssjsssssss{ssssssssssssssssssssssssssssssssssssssssssssssssjjjjjjjjjjjjjjj jjj)jjj1jjj9jjjAjjjJjjjRjjjZjjjbjjjjjjjsjjj{jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjbbbbbbbbbbbbbbb bbb)bbb1bbb9bbbAbbbJbbbRbbbZbbbbbbbjbbbsbbb{bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbZZZZZZZZZZZZZZZ ZZZ)ZZZ1ZZZ9ZZZAZZZJZZZRZZZZZZZbZZZjZZZsZZZ{ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZRRRRRRRRRRRRRRR RRR)RRR1RRR9RRRARRRJRRRRRRRZRRRbRRRjRRRsRRR{RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRJJJJJJJJJJJJJJJ JJJ)JJJ1JJJ9JJJAJJJJJJJRJJJZJJJbJJJjJJJsJJJ{JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJAAAAAAAAAAAAAAA AAA)AAA1AAA9AAAAAAAJAAARAAAZAAAbAAAjAAAsAAA{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA999999999999999 999)99919999999A999J999R999Z999b999j999s999{999999999999999999999999999999999999999999999999111111111111111 111)11111119111A111J111R111Z111b111j111s111{111111111111111111111111111111111111111111111111))))))))))))))) )))))))1)))9)))A)))J)))R)))Z)))b)))j)))s))){))))))))))))))))))))))))))))))))))))))))))))))))    ) 1 9 A J R Z b j s {  )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{chunky_png-1.3.15/spec/png_suite/basic/basn6a16.rgba0000644000175000017500000001000013766004353021404 0ustar danieldaniel{skcZRJB91)!{riaXOF=4,#!!!!!!!!!!!!!!{!q!h!^!U!K!B!8!/!%!!! !!!1111111111111z1p1f1\1Q1G1=131(111 11 !!1BBBBBBBBBBBBzBoBdBYBMBBB7B,B!BB BB 1!!!1BRRRRRRRRRRRyRmRaRURIR<R0R$RR RR B1!#)!1BRccccccccccyckc^cPcCc5c(cc cc RB1%!,1!1BRcsssssssssxsisZsKs<s-ssss cR!B(1/!49!1BRcswfUD3"sc$R,B318!=B!1BRcsvbN;'s(c0R7B=1B!FJ!1BRcst]E."-s5c=<;:9876543210/.-,+*)('&%$#"!  ~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!  ~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!  ~~~}}}|||{{{zzzyyyxxxwwwvvvuuutttsssrrrqqqpppooonnnmmmlllkkkjjjiiihhhgggfffeeedddcccbbbaaa```___^^^]]]\\\[[[ZZZYYYXXXWWWVVVUUUTTTSSSRRRQQQPPPOOONNNMMMLLLKKKJJJIIIHHHGGGFFFEEEDDDCCCBBBAAA@@@???>>>===<<<;;;:::999888777666555444333222111000///...---,,,+++***)))((('''&&&%%%$$$###"""!!!  chunky_png-1.3.15/spec/png_suite/basic/basi2c16.rgba0000644000175000017500000001000013766004353021375 0ustar danieldaniel{skcZRJB91)!{skcZRJB91)!{skcZRJB91)!{skcZRJB91)!{skcZRJB91)!!{skcZRJB91)!!){skcZRJB91)!!)1{skcZRJB91)!!)19޽ֽνƽ{skcZRJB91)!!)19B޵ֵεƵ{skcZRJB91)!!)19BJޭ֭έƭ{skcZRJB91!))!19BJRޥ֥Υƥ{skcZRJB9!1))1!9BJRZޜ֜ΜƜ{skcZRJB!9)11)9!BJRZcޔ֔ΔƔ{skcZRJ!B)9119)B!JRZckތ֌Όƌ{skcZR!J)B1991B)J!RZcksބք΄Ƅ{skcZ!R)J1B99B1J)R!Zcks{{{{{{{{{{{{{{{{{{{s{k{c{!Z{)R{1J{9B{B9{J1{R){Z!{c{k{s{{{ssssssssssssssss{sssks!cs)Zs1Rs9JsBBsJ9sR1sZ)sc!sksss{sskkkkkkkkkkkkkkkk{ksk!kk)ck1Zk9RkBJkJBkR9kZ1kc)kk!ksk{kkkcccccccccccccccc{c!sc)kc1cc9ZcBRcJJcRBcZ9cc1ck)cs!c{ccccZZZZZZZZZZZZZZZZ!{Z)sZ1kZ9cZBZZJRZRJZZBZc9Zk1Zs)Z{!ZZZZZRRRRRRRRRRRRRRR!R){R1sR9kRBcRJZRRRRZJRcBRk9Rs1R{)R!RRRRRJJJJJJJJJJJJJJ!J)J1{J9sJBkJJcJRZJZRJcJJkBJs9J{1J)J!JJJJJBBBBBBBBBBBBB!B)B1B9{BBsBJkBRcBZZBcRBkJBsBB{9B1B)B!BBBBB999999999999!9)91999B{9Js9Rk9Zc9cZ9kR9sJ9{B99919)9!9999911111111111!1)11191B1J{1Rs1Zk1cc1kZ1sR1{J1B19111)1!11111))))))))))!)))1)9)B)J)R{)Zs)ck)kc)sZ){R)J)B)9)1)))!)))))!!!!!!!!!!!)!1!9!B!J!R!Z{!cs!kk!sc!{Z!R!J!B!9!1!)!!!!!!!!)19BJRZc{kssk{cZRJB91)!!)19BJRZck{ss{kcZRJB91)!!)19BJRZcks{{skcZRJB91)!!)19BJRZcks{{skcZRJB91)!chunky_png-1.3.15/spec/png_suite/basic/basi0g04.rgba0000644000175000017500000001000013766004353021374 0ustar danieldaniel""""""""""""333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww""""""""""""333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww""""""""""""333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww""""""""""""333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww""""""""""""333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww""""""""""""333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww""""""""""""333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww""""""""""""333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww""""""""""""333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww""""""""""""333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww""""""""""""333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww""""""""""""333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwww333333333333DDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwwwDDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwwwDDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwwwDDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwwwDDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwwwUUUUUUUUUUUUffffffffffffwwwwwwwwwwwwUUUUUUUUUUUUffffffffffffwwwwwwwwwwwwUUUUUUUUUUUUffffffffffffwwwwwwwwwwwwUUUUUUUUUUUUffffffffffffwwwwwwwwwwwwffffffffffffwwwwwwwwwwwwffffffffffffwwwwwwwwwwwwffffffffffffwwwwwwwwwwwwffffffffffffwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwchunky_png-1.3.15/spec/png_suite/basic/basi4a08.png0000755000175000017500000000032613766004353021264 0ustar danieldanielPNG  IHDR tgAMA1_IDATx 0 ֽA((sOAVG":ݙbH$ @ɾzM2x<7U[0t<n!Y~.,>RfqXAh٪wϤ50o0N N6 O= YU]IENDB`chunky_png-1.3.15/spec/png_suite/basic/basi6a08.rgba0000644000175000017500000001000013766004353021400 0ustar danieldaniel )19AJRZbjs{ )19AJRZbjs{????? ?)?1?9?A?J?R?Z?b?j?s?{????????????????_____ _)_1_9_A_J_R_Z_b_j_s_{________________ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{Š͠ՠޠ )19AJRZbjs{ŀ̀Հހ````` `)`1`9`A`J`R`Z`b`j`s`{````````````````@@@@@ @)@1@9@A@J@R@Z@b@j@s@{@@@@@@@@@@@@@@@@      ) 1 9 A J R Z b j s {                 )19AJRZbjs{ )19AJRZbjs{????? ?)?1?9?A?J?R?Z?b?j?s?{????????????????_____ _)_1_9_A_J_R_Z_b_j_s_{________________ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{߃ߋߔߜߤ߬ߴ߽ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{ )19AJRZbjs{````` `)`1`9`A`J`R`Z`b`j`s`{````````````````@@@@@ @)@1@9@A@J@R@Z@b@j@s@{@@@@@@@@@@@@@@@@    ) 1 9 A J R Z b j s { chunky_png-1.3.15/spec/png_suite/basic/basi0g16.png0000755000175000017500000000045313766004353021266 0ustar danieldanielPNG  IHDR qgAMA1_IDATx;0DI ]0S ,D @C#IVhk& \lX5R 2'+d~G @4 @4߁@\\y9۩ܵEkQט-} Q FAQ wbk@|DVIENDB`chunky_png-1.3.15/spec/png_suite/basic/basn2c16.rgba0000644000175000017500000001000013766004353021402 0ustar danieldaniel{skcZRJB91)!{skcZRJB91)!{skcZRJB91)!{skcZRJB91)!{skcZRJB91)!!{skcZRJB91)!!){skcZRJB91)!!)1{skcZRJB91)!!)19޽ֽνƽ{skcZRJB91)!!)19B޵ֵεƵ{skcZRJB91)!!)19BJޭ֭έƭ{skcZRJB91!))!19BJRޥ֥Υƥ{skcZRJB9!1))1!9BJRZޜ֜ΜƜ{skcZRJB!9)11)9!BJRZcޔ֔ΔƔ{skcZRJ!B)9119)B!JRZckތ֌Όƌ{skcZR!J)B1991B)J!RZcksބք΄Ƅ{skcZ!R)J1B99B1J)R!Zcks{{{{{{{{{{{{{{{{{{{s{k{c{!Z{)R{1J{9B{B9{J1{R){Z!{c{k{s{{{ssssssssssssssss{sssks!cs)Zs1Rs9JsBBsJ9sR1sZ)sc!sksss{sskkkkkkkkkkkkkkkk{ksk!kk)ck1Zk9RkBJkJBkR9kZ1kc)kk!ksk{kkkcccccccccccccccc{c!sc)kc1cc9ZcBRcJJcRBcZ9cc1ck)cs!c{ccccZZZZZZZZZZZZZZZZ!{Z)sZ1kZ9cZBZZJRZRJZZBZc9Zk1Zs)Z{!ZZZZZRRRRRRRRRRRRRRR!R){R1sR9kRBcRJZRRRRZJRcBRk9Rs1R{)R!RRRRRJJJJJJJJJJJJJJ!J)J1{J9sJBkJJcJRZJZRJcJJkBJs9J{1J)J!JJJJJBBBBBBBBBBBBB!B)B1B9{BBsBJkBRcBZZBcRBkJBsBB{9B1B)B!BBBBB999999999999!9)91999B{9Js9Rk9Zc9cZ9kR9sJ9{B99919)9!9999911111111111!1)11191B1J{1Rs1Zk1cc1kZ1sR1{J1B19111)1!11111))))))))))!)))1)9)B)J)R{)Zs)ck)kc)sZ){R)J)B)9)1)))!)))))!!!!!!!!!!!)!1!9!B!J!R!Z{!cs!kk!sc!{Z!R!J!B!9!1!)!!!!!!!!)19BJRZc{kssk{cZRJB91)!!)19BJRZck{ss{kcZRJB91)!!)19BJRZcks{{skcZRJB91)!!)19BJRZcks{{skcZRJB91)!chunky_png-1.3.15/spec/png_suite/basic/basi6a16.png0000755000175000017500000001012413766004353021262 0ustar danieldanielPNG  IHDR T!gAMA1_ IDATxݙktU՝}&$@h⍠("І`YЎ" BZ.oY(ZAG$P t 8B[#CIIk_̇$̸Y]gc}m&ZCmI6D#?$XVIͮl̏d׽OB6nnm|¶,()^VnhO*sL-5S< ΍:wx4SEݰ# 𛷍[w8[ u,ZZY: ʊ+$n Pna WݹP^ql}cC#~NZ`^Z$;VWãȖG68F쾰=0@@ SCMpmr/$0J?B?;燈tKi ">SͭVUgTYъ2sAw厊D|Ɏ$d:|sGW#ߘ,  PU*Ԇ͒4C=3Q86sS-C?"SWkK/:[l; ~-&g]۔Bj+SwݗTQ@id'D|_0$[]d)u]3/U'NJ&:=V!``wS W uTBWAUD|UjՒ9U>z1ulh0,9h H=0HF-_0M-R_V[[TSϪ C7dg$@~s9%@Vm֋㔷ޓKN]]35 _#igT}ޙ3c\s  {$)cng*c4&'[9X`}'p 1g>"@BlOjbc%Q4Ի`RVUuH3 eMл@,Lr%е̬i(?XQ-7z(m626 ~Pt2o@ċPgCƁy.`$Jg]q0pUzOs$ɒ%}䦿^)cdmXrSd8j -7!ܘNSו qpH@8fʹn]Ʉ*yU.O.~UZ_4ö*p2Ib늦-D/.l_YY0E%; )7eBkGD!-7KJ@&ڕI:Mi㊽FaPŀ!oyF(Qp\rs@I{5w_8*-#yfvC۫"ʒ*%&%iO:6@8-'*S,dHNn-L] 8tJo26VA8:jgM hw*OogY=|=@j>@3mq2N)@U̿)*K KGk;aR<\/| bp:C=&R2}F2\r~_Wڮێ5e:DV[:850b-{l4K uAzPY/V.yA p H%L;k*h gZ1=LlС \m$M=_/ ,50!2[s!B}n(ӽϽԡ}" RP\WzHlA+50$!8oo E3@Et6BشpdGC[ PHN> K.`C +P!ҿo!_aCA-CLnQhr ->u157"v0  a{6reDCB%[>C> }"03a8:LR ,0D=4C܆">$D@n1v]f=g3b1/BPv@͐!Xڌ.&Aö g>7jYJ} ^k @jm&+ceVUhlMTB+ o5@*ot'|զޱVL]vQP_ Z40+XhV{#I+f3*#-Y;;ɗi@^rBXbVQAD{0b_l $@ p3,n<(nPPޑ(-Z4%~eE"RR`2JY]Z1 _0ߗ(([Zc2uH.t w%@fg@fCҧE @II(ZD 흧U) E@ZBR;ijaKX,2uHtbLֻ~{ p*_|Vp; 2_:>(ux h2  R[ }d}#VL;.*>m[>:c,gL7piR )qv{.@zW3@aCVS%d6UVpޠTPuu J%z#?&|tE+QOl5jvyԑN-Cϛo7uS!9Cd2Rl{ ŵ 3`/vot.PpSGUY)UV12 P^4Т55 A8Z^۹NK4S)8Y܃rrMۖc;5vCơ R{cҳt/{VS2|WμdvdWBB\>dQ.w- k`"mm#M޷J<K9ʖ\R|+sK~ 8tqm ȫGUV)E*aeWTiRJјuJv8=g韮YtmQKa znޙh;:~+P򶎷JW=)gONzg}f%;in<9_UjCV@N¬ jYehɋ*PH$$q{/ϵP^gѽgCã_n>=Ő XUн:}Lzl4%?  J |!nI/;) ۟M\٫X+NާW4-4A,2z w 9\* KR"2@Lw+tJ()g`qb %!RϞ|O}S+ckY1 V|jT {;cޙPv W"6"!ׁCC8?u2 BcM_np5X' Qvzgn "CgqEL9j[^.V^ck;`m+ jtKOX$F@$Cp#$Ap$*!r Gb; .ਖ?0̀@ r"`s{#dGR-0# #)O&_hPT4y`}~(M C/BmPt!7C]CLksk\c{"8q쉆ppiCxCCd)w!q!6Ca kп0w4 }~C,)gi(x!Kܦ$]!2,$UIBH"*V$$ŏ Jl, lܲ 9:eSW}q@Şp쿣;_|?/ >`eaX=`Ԃ±8G?]e _^X.wGO3wd3 gZú|'fef[Z!p&ކuh`c:oȇtri^g)X]UVZ l-5C`T$u!Z??p߅Cgj@dƱt-YY˅ae}';Ȁ~B@3 x'͌B ^SA#nr2r:QfGR`fAdv <@_"AÃևðqXZ3S:܀{Ճ< ';B 1/s,ZYߣ` `pg=U<@#뿃נ/`6,њ̀U]轏@`7&kc{ ? %:4o6JވaKGaɄ ? @= !e/sVVCpA5AhΠ.Ak%,?%!V[?hv@[/  !~Q`ZJHV@ס, \Z-tVKuVai *(a0ס  =i0Vކ.a{hQf\5 |ee8 l9QF haeXC9wnA D`6_Bh^SygLgv]~dA_ lwSR; I[VF&;5l :F$g 8h=ox yot| 'Q2 ,44c94O!1DӲͷ+3߲M:$m+?=*|~5{Wb zC`W݄辥< Cu㰈"j Q}8p|$;>.;/؏a 4/@ н[/E})x6훏m^=t)y#!ËGT/So SMO]]#7*gǏl!qXkdQa59G2 5e5$l (+畸2ۥ_$`Wb?@0 9E{Bk80/ƲVRZYxYf\ Nv{k By_@SaJcKjT𔥞cVE^>[v]7R>܉\i@)]SBBy$Ғh('eϭia;cҹ; МkX]HM%I%ŎUz]„Xj"22CO+h ɴ )oF\ܼ27 h+' wCQRY\-4-dA.Hax4,@ᜲ$o{ dJ;v,& C˧T$3ӔgVWsϓ<yhB# FP!v Vޔl X$p]y]\._s=,l7Gd){&TLK(}Z)h+V %@{gd"iiqૢ]Z^<y wPg+ЮB;  s%/ڟ gU<=DPw?({!l7d̀t:9-xIb z 4thFk2*<#~WQ\ 4eύpNf.J,OAdžN 6lf-ȌcGR3*%dE((\0){n$KJ3`38`ek+-w53&7yiIq>>GGGPPPYYYbbbkkkttt}}} %%%...777@@@IIIRRR[[[dddmmmvvv '''000999BBBKKKTTT]]]fffoooxxx )))222;;;DDDMMMVVV___hhhqqqzzz"""+++444===FFFOOOXXXaaajjjsss|||$$$---666???HHHQQQZZZcccllluuu~~~&&&///888AAAJJJSSS\\\eeennnwww~~~(((111:::CCCLLLUUU^^^gggpppyyyxxx!!!***333<<>>GGGPPPYYYbbbkkkttt}}}lll%%%...777@@@IIIRRR[[[dddmmmvvvfff'''000999BBBKKKTTT]]]fffoooxxx{{{``` )))222;;;DDDMMMVVV___hhhqqqzzzuuuZZZ"""+++444===FFFOOOXXXaaajjjsss|||oooTTT$$$---666???HHHQQQZZZcccllluuu~~~iiiNNN&&&///888AAAJJJSSS\\\eeennnwww~~~cccHHH(((111:::CCCLLLUUU^^^gggpppyyyxxx]]]BBB***333<<>>GGGPPPYYYbbbkkkttt}}}lllQQQ666...777@@@IIIRRR[[[dddmmmvvvfffKKK000000999BBBKKKTTT]]]fffoooxxx{{{```EEE***222;;;DDDMMMVVV___hhhqqqzzzuuuZZZ???$$$444===FFFOOOXXXaaajjjsss|||oooTTT999666???HHHQQQZZZcccllluuu~~~iiiNNN333888AAAJJJSSS\\\eeennnwww~~~cccHHH---:::CCCLLLUUU^^^gggpppyyyxxx]]]BBB''' <<>>GGGPPPYYYbbbkkkttt}}}lllQQQ666chunky_png-1.3.15/spec/png_suite/basic/basi4a16.rgba0000644000175000017500000001000013766004353021375 0ustar danieldaniel!!!111BBBRRRcccsssssscccRRRBBB111!!!###444FFFXXXiii{{{{{{iiiXXXFFF444###!!!!!%%%!888!KKK!^^^!qqq!!!!!!!!!!!!!!!qqq!^^^!KKK!888!%%%!!!!!!111###!11(((1===1QQQ1fff1zzz1111111111111zzz1fff1QQQ1===1(((111!###111BBB444%%%!1BB,,,BBBBBYYYBoooBBBBBBBBBBBBBoooBYYYBBBBB,,,BBB1%%%!444BBBRRRFFF888!(((1BRR000RIIIRaaaRyyyRRRRRRRRRRRyyyRaaaRIIIR000RRRB(((1888!FFFRRRcccXXXKKK!===1,,,BRcc555cPPPckkkccccccccccckkkcPPPc555cccR,,,B===1KKK!XXXcccsssiii^^^!QQQ1BBBB000Rcss<<>&&*JNBKK_22x< 0: fiAޞ]\"rqa2|ۛdkj&&zz֦v 05MMuvnh`0TTd9:ƆFDȶmn%53 ke(/^S#Av/#,0 '0N5X S vc0jXw`  p>U0 N x!<۰ .^ nZs` z\?0! oa<~Np&023 0 tx C8, `#G0srIENDB`chunky_png-1.3.15/spec/png_suite/basic/basn0g01.png0000755000175000017500000000024413766004353021263 0ustar danieldanielPNG  IHDR [GYgAMA1_[IDATx-̱ 0 J z4o< aEQ/ҤlΩ%SS4W!K&=Bs%%^ڲoj0i.)ano0eI//IENDB`chunky_png-1.3.15/spec/png_suite/basic/basi0g16.rgba0000644000175000017500000001000013766004353021377 0ustar danieldaniel $$$---666???HHHQQQZZZcccllluuu~~~ &&&///888AAAJJJSSS\\\eeennnwww (((111:::CCCLLLUUU^^^gggpppyyy!!!***333<<>>GGGPPPYYYbbbkkkttt}}} %%%...777@@@IIIRRR[[[dddmmmvvv '''000999BBBKKKTTT]]]fffoooxxx )))222;;;DDDMMMVVV___hhhqqqzzz"""+++444===FFFOOOXXXaaajjjsss|||$$$---666???HHHQQQZZZcccllluuu~~~&&&///888AAAJJJSSS\\\eeennnwww~~~(((111:::CCCLLLUUU^^^gggpppyyyxxx!!!***333<<>>GGGPPPYYYbbbkkkttt}}}lll%%%...777@@@IIIRRR[[[dddmmmvvvfff'''000999BBBKKKTTT]]]fffoooxxx{{{``` )))222;;;DDDMMMVVV___hhhqqqzzzuuuZZZ"""+++444===FFFOOOXXXaaajjjsss|||oooTTT$$$---666???HHHQQQZZZcccllluuu~~~iiiNNN&&&///888AAAJJJSSS\\\eeennnwww~~~cccHHH(((111:::CCCLLLUUU^^^gggpppyyyxxx]]]BBB***333<<>>GGGPPPYYYbbbkkkttt}}}lllQQQ666...777@@@IIIRRR[[[dddmmmvvvfffKKK000000999BBBKKKTTT]]]fffoooxxx{{{```EEE***222;;;DDDMMMVVV___hhhqqqzzzuuuZZZ???$$$444===FFFOOOXXXaaajjjsss|||oooTTT999666???HHHQQQZZZcccllluuu~~~iiiNNN333888AAAJJJSSS\\\eeennnwww~~~cccHHH---:::CCCLLLUUU^^^gggpppyyyxxx]]]BBB''' <<>>GGGPPPYYYbbbkkkttt}}}lllQQQ666chunky_png-1.3.15/spec/png_suite/basic/basn3p08.rgba0000644000175000017500000001000013766004353021421 0ustar danieldaniel        """"""""""""""""""""""""""""""""""""""""""""33333333333333333333333333333333333333333333DDDDD"D"D"D"DDDDDDDD"D"D"D"DDDDDDDDDDDDDDDDDDDDDDDDDUUUUU*U*U*U*UUUUUUUU*U*U*U*UUUUUUUUUUUUUUUUUUUUUUUUUfffff2f2f2f2ffffffff2f2f2f2fffffffffffffffffffffffffwwwww:w:w:w:wwwwwwww:w:w:w:wwwwwwwwwwwwwwwwwwwwwwwwwBBBBBBBBJJJJJJJJSSSSSSSS[[[[[[[[cccccccckkkkkkkkssssssss{{{{{{{{""""""""""""""""""""""""""""""""""""""""""""33333333333333333333333333333333333333333333DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUffffffffffffffffffffffffffffffffffffffffffffwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwĈĈĈĈ̙̙̙̙ԪԪԪԪܺܺܺܺchunky_png-1.3.15/spec/png_suite/gamma/0000755000175000017500000000000013766004353017240 5ustar danieldanielchunky_png-1.3.15/spec/png_suite/gamma/g04n0g16.png0000755000175000017500000000055313766004353021122 0ustar danieldanielPNG  IHDR kgAMA7"IDATx90E'",$4)RpNOp  ea Ed1YƅRm hn簮q0@q~`4X. (`p4xp0{Dfө@0Wdi  ?xnG/P  (. l6I2+3JimvѠ=}?̀wsw[ H˅n1(Kt1@dRZ+ fpYk<_bFa32O @ >)4(cIENDB`chunky_png-1.3.15/spec/png_suite/gamma/g03n3p04.png0000755000175000017500000000032613766004353021130 0ustar danieldanielPNG  IHDR TggAMAIPLTEȭ1 cIDATxc(% 0A `@`@W,@G.@p`06`FRalb\%U@ l@-H*@ SRpIENDB`chunky_png-1.3.15/spec/png_suite/gamma/g10n2c08.png0000755000175000017500000000043513766004353021115 0ustar danieldanielPNG  IHDR gAMA1_IDATx1 0D }ǰ Bp 3H*M&S-g O 7C @T PĠ("kp3p _ (d Pu )-wzCDD2O5 H3oiۺvtv|GSMd"*?l kblt RUԭMTߏIENDB`chunky_png-1.3.15/spec/png_suite/gamma/g07n2c08.png0000755000175000017500000000052413766004353021122 0ustar danieldanielPNG  IHDR gAMAp;XV IDATxՖA 0E'"+/Ir&!ݖm)E+U:34`;"!WPMė"p)~~ ʒ~\( !C@@I LR4b2fI" Ā30FEow0R2~hp2@Z  6fMW^uP릪R|y?i ` DlknkYuKc,5U(s(28 3Z? IENDB`chunky_png-1.3.15/spec/png_suite/gamma/g25n3p04.png0000755000175000017500000000032713766004353021135 0ustar danieldanielPNG  IHDR TggAMAАTOPLTE--\\\RQdIDATxα 0 DQ[J+ ^msQ$s5HІ:P 5B) oe p/)FqjS!*$BB?x(:5tf;PIENDB`chunky_png-1.3.15/spec/png_suite/gamma/g05n2c08.png0000755000175000017500000000053613766004353021123 0ustar danieldanielPNG  IHDR gAMAOX2IDATx90ERBDAGChr8hnB @B"B$pT/ϱܪd?nGg 9 vi%(.tDj$" n Ejj@ͦ  2@5Eۢ`W f@ՠV *2 @StA鴸̲/klyС(BK@`n\"2z˷^dx!E" Vf@ˡE7j9M,WIENDB`chunky_png-1.3.15/spec/png_suite/gamma/g25n2c08.png0000755000175000017500000000062513766004353021124 0ustar danieldanielPNG  IHDR gAMAАTOLIDATxm1@Nj #؎8Pu-` @ T@|HsZOH0gi,a&^ݥݧSȀY@'%  @V T2H o2 @'J\^_^|AU ov2`4"236"yA瓲4 x pϋ< HHQ‰ b_`8,ߢ^-v@'N=yLӅw@["^] s2 pd@@?+ZD _-a2wIENDB`chunky_png-1.3.15/spec/png_suite/gamma/g03n2c08.png0000755000175000017500000000056213766004353021120 0ustar danieldanielPNG  IHDR gAMAI)IDATxM@F1Dw.x!.2dFgFˆ$øVJ_.b+gƻGq98`>//߀шVa" A+P :Π P Z-@TfSA2~4@]5Ȁ_>tl̮1333$RdV8$lB'4I^7m6MV78YmtC|wcn'[|(% jПA Ϻd@ ZY#[jIENDB`chunky_png-1.3.15/spec/png_suite/gamma/g04n3p04.png0000755000175000017500000000033313766004353021127 0ustar danieldanielPNG  IHDR TggAMA7PLTE",hIDATx 0D!ہ[\Ӷ` `N?eq]Q- q@SMť" DCܛ6 ѐ }~ uIENDB`chunky_png-1.3.15/spec/png_suite/gamma/g10n0g16.png0000755000175000017500000000040613766004353021114 0ustar danieldanielPNG  IHDR kgAMA1_IDATx1 E? ,=\!^KXHbřDdcfy΁HkMqye--HwZ_< JMGi =ضB;݃al>hly,kdǔ41{mi!,RIϴϫ IENDB`chunky_png-1.3.15/spec/png_suite/gamma/g04n2c08.png0000755000175000017500000000057113766004353021121 0ustar danieldanielPNG  IHDR gAMA70IDATxՖ=n@F̟D-}H(R"~_Oq)xw޼!Su4T矪'3p0R%F``\~ql\^6 >`k\Π1d@Wnw G/Ȼ15+:@ l`6 rD^_](iX."d@3V1yod@o3LzdR f8l6?VFpaHnnrenuݮ|4-b0]β  o1z0}we}&%ϧS<ے$m 664p_4f@B`2|? 8IENDB`chunky_png-1.3.15/spec/png_suite/gamma/g10n3p04.png0000755000175000017500000000032613766004353021126 0ustar danieldanielPNG  IHDR TggAMA1_PLTETTUZcIDATxcP H% `@`@W,@G.@` p00NK`CV`Ɩ *@-H*ـ"(/YLFə:IENDB`chunky_png-1.3.15/spec/png_suite/gamma/g05n0g16.png0000755000175000017500000000052313766004353021120 0ustar danieldanielPNG  IHDR kgAMAOX2 IDATxս 0~ H7i>AA U As GA/@ Q#99͹~ͅEX@p:)q)8WSp80/)n#vW <8`2C~8\x8ah`,uA׭ nV @_`+(W]X&*&5-~> ϧR#лtpDPGXDzЂd ?_fAIENDB`chunky_png-1.3.15/spec/png_suite/gamma/g05n3p04.png0000755000175000017500000000031613766004353021131 0ustar danieldanielPNG  IHDR TggAMAOX2PLTEs$[IDATxcP pA `@`P@W,@Gi@` p00vq`AV`CU ``gjARQ^w*x;JUIENDB`chunky_png-1.3.15/spec/png_suite/gamma/g07n3p04.png0000755000175000017500000000031713766004353021134 0ustar danieldanielPNG  IHDR TggAMAp;XVPLTEvvvNTl\IDATxc4 PA `@`0 @P p0S EX (//(Wbb 00 ``gRBQQ^w*#kr=IENDB`chunky_png-1.3.15/spec/png_suite/gamma/g03n0g16.png0000755000175000017500000000053113766004353021115 0ustar danieldanielPNG  IHDR kgAMAIIDATxM 0WZEP(]S4^"\"_mu!A3Ct7Hi|k,P1 Q`8  M$ z NA)]Wqm)*N ׫_ׂ X.TyecyZBJu2y"8MN7E@WP Mw; N'`2}&8MUeem`<u+DݷIENDB`chunky_png-1.3.15/spec/png_suite/gamma/g07n0g16.png0000755000175000017500000000050113766004353021116 0ustar danieldanielPNG  IHDR kgAMAp;XVIDATx͕ 0 B%hXI\1-Dl IL9P8j-Z׵Z{ۦ>2x n@i$qliE: - tA LZ߷4 di`Y8 }SU1@ͣAYhgphk \";9@39BuxC]] @ Ҡ >š,s,IENDB`chunky_png-1.3.15/spec/png_suite/other/0000755000175000017500000000000013766004353017277 5ustar danieldanielchunky_png-1.3.15/spec/png_suite/other/cs8n3p08.png0000755000175000017500000000040013766004353021270 0ustar danieldanielPNG  IHDR DgAMA1_`PLTE ߠ?_`@`@_ ?cKIDATx0[> |Gp "lX *A/P $BP Z.Y N-x)>SIENDB`chunky_png-1.3.15/spec/png_suite/other/ps2n0g08.png0000755000175000017500000000444513766004353021300 0ustar danieldanielPNG  IHDR V%(gAMA1_spALsix-cubePNG group 1996-10-223f3333f333ff3fffff3f3f3f3333f3333333333f3333333f3f33ff3f3f3f3333f3333333f3333333f333ff3ffffff3f33f3ff3f3f3ffff3fffffffffff3fffffff3fffffff3fffff3f3333f333ff3fffff3f3f3f3f3333f333ff3fffff3f3f3f3f3333f333ff3fffff3f3f3f AIDATxcd`$ȳ )?`y00gdy\ q10edPq5YIENDB`chunky_png-1.3.15/spec/png_suite/other/ccwn2c08.png0000755000175000017500000000275213766004353021345 0ustar danieldanielPNG  IHDR gAMA1_ cHRMz&u0`:pQ<uIDATxOg3g̜sfnbrBiFS06"ئwAE7q.Dq)R[PP1r9y'kJH^^f3<̫D;M }#x=_+s8Rr˰#O3#o!AK!EupKOFb=0Y1\d{9fҞߣ31AL|~}v@v {t{t#:aD'"rԌo·>6*@Hp4R1 Ng V㬞d<t:XAդ>#88z~ N{nCT8^@7f{0>c&`[M4.!E4&AKgڧ藘MtP]k):b$B`نDua֎ś1pi{nLO .Q*F搸 kº0求xIv |it(=7b65aC #a$ SANEG{G8gCa, 7^^=I]BЭ-Cʅ;8N{A$6$*0Ԍ>qل"a:&$2u>Of[9E fskTZ$ A27CRu,_9s_~G5b .07ɛmK9.r<};,%"o㞩6",5f}<ܞo@JLWW/FJ*y]^'; D(IENDB`chunky_png-1.3.15/spec/png_suite/other/ct0n0g04.png0000755000175000017500000000042113766004353021244 0ustar danieldanielPNG  IHDR )gAMA1_IDATx] 0 P*@# #T10lPF`ؠF=IQ*u`%qk H񚈩mߟ э=,fOK t(F ;P{xp]9/p*$(*yՃ@C  cqNU#)11.rf0gh(tEkIENDB`chunky_png-1.3.15/spec/png_suite/other/ps2n2c16.png0000755000175000017500000000471113766004353021271 0ustar danieldanielPNG  IHDR 1gAMA1_spALsix-cubePNG group 1996-10-223f3333f333ff3fffff3f3f3f3333f3333333333f3333333f3f33ff3f3f3f3333f3333333f3333333f333ff3ffffff3f33f3ff3f3f3ffff3fffffffffff3fffffff3fffffff3fffff3f3333f333ff3fffff3f3f3f3f3333f333ff3fffff3f3f3f3f3333f333ff3fffff3f3f3f IDATxՖ 0DA~&fzE=֠B>/drs~='3_Zwt(p3p]`_yt?t(C\l52L̩g I@g.`(`g Dg&((`60Y`zl(P49܀:{z*zȟmt3ΞAO3B^IENDB`chunky_png-1.3.15/spec/png_suite/other/cdfn2c08.png0000755000175000017500000000062413766004353021321 0ustar danieldanielPNG  IHDR gAMA1_sBITw pHYs2R0'IDATxUaq0 ௙SPp R8 @h Tv6]iW[dogsPz`qfX)\ةo_ճ_ժLNNq u*nVS pv5%/sPdG"Gx% ^ .Utl[mv=K#~e%ȥkQ|܏y1](+AFK F898%| OҎISSq-r3˯[œ"}p4=ABYi9Q@u3us IENDB`chunky_png-1.3.15/spec/png_suite/other/cs5n2c08.png0000755000175000017500000000027213766004353021256 0ustar danieldanielPNG  IHDR gAMA1_sBIT&CbIDATx핱@ Ô;/Awl@*QEDqm\a .[p=jE'-X@O0 WyH0o y<Aa=^ohIENDB`chunky_png-1.3.15/spec/png_suite/other/pp0n6a08.png0000755000175000017500000000146213766004353021267 0ustar danieldanielPNG  IHDR szzgAMA1_PLTE3f3333f333ff3fffff3f3f̙3f3333f3333333333f3333333f3f33ff3f3f3f3333f3333333f3̙333333f333ff3ffffff3f33f3ff3f3f3ffff3fffffffffff3fffffff3fff̙ffff3fffff3f̙3333f33̙3ff3ffff̙f3f̙3f̙̙3f̙3f3333f333ff3fffff̙̙3̙f̙̙̙3f̙3f3f3333f333ff3fffff3f3f̙3fcQUIDATx1 0 CQ<$o ԥKA4 z-qKjI`~Ux\Ku7UIENDB`chunky_png-1.3.15/spec/png_suite/other/ct1n0g04.png0000755000175000017500000000143013766004353021246 0ustar danieldanielPNG  IHDR )gAMA1_tEXtTitlePngSuiteOUL1tEXtAuthorWillem A.J. van Schaik (willem@schaik.com)G8tEXtCopyrightCopyright Willem van Schaik, Singapore 1995-96P8tEXtDescriptionA compilation of a set of images created to test the various color-types of the PNG format. Included are black&white, color, paletted, with alpha channel, with transparency formats. All bit-depths allowed according to the spec are present.M k9tEXtSoftwareCreated on a NeXTstation color using "pnmtopng".jdytEXtDisclaimerFreeware._,JIDATx] 0 P*@# #T10lPF`ؠF=IQ*u`%qk H񚈩mߟ э=,fOK t(F ;P{xp]9/p*$(*yՃ@C  cqNU#)11.rf0gh(tEkIENDB`chunky_png-1.3.15/spec/png_suite/other/cdhn2c08.png0000755000175000017500000000053013766004353021317 0ustar danieldanielPNG  IHDR jgAMA1_sBITw pHYsUeIDATx}k ZB,B,B,XX#!Btvw@Ÿ7xźG93zWڣ / X ]D,lpar )fK+J^?]OFsaM̂h$kQt0N;8t-F Bf =SpԕjAdf"(>/6ީ~k\hIENDB`chunky_png-1.3.15/spec/png_suite/other/cs3n3p08.png0000755000175000017500000000040313766004353021266 0ustar danieldanielPNG  IHDR DgAMA1_sBITBTPLTEmmI$$II$m$Imp+KIDATxbWT#$tNzC{h t. mtn:B"@3w&IENDB`chunky_png-1.3.15/spec/png_suite/other/ccwn3p08.png0000755000175000017500000000302213766004353021352 0ustar danieldanielPNG  IHDR DgAMA1_ cHRMz&u0`:pQ<PLTEމu k51I&442tQ#l-^>bOw3X .c$s=;Y\z<HA(-I`%{KR~o|dqj`+_p5]g+=dEcC=K&037c, G6ǫfYLqLَJ{AL;[@]~T<e9O)f ˝4foX㌖Ae=6&^?)gKT!i¿kK{|SiC/5^eMu@~rs>oݛ?@3'0|{O%?|{S 9OU>/WLbz Y+к+JIDATxc8B0m\ 4RR¥ #* \kjZ]OhM$]un6n SUrn: a(`Ywhsh \7Oڿ{ѣ[BCWǣ*Uy~55T]E$Wvwk^a\",:Ad9IW 45dVUO8\YI$.lkzÂzz $\\cz)nh4Ck']pn.OM^ȱr-e8q́_jխ[ Nl)s6SE^~YPtKQQ ۇNX&Ѷ]I{7(31s+H׀ ]jSRokV֩e+O>{bk'ܙt>/_N-[&$T"+a)P^Me#ΗS>BϜ93or S}}TTPe}P] aO_w |*/^֮IENDB`chunky_png-1.3.15/spec/png_suite/other/ctzn0g04.png0000755000175000017500000000136113766004353021362 0ustar danieldanielPNG  IHDR )gAMA1_tEXtTitlePngSuiteOUL1tEXtAuthorWillem A.J. van Schaik (willem@schaik.com)GAzTXtCopyrightxs/,L(QIU(KSNHQKO,/JU04յ4aRZzTXtDescriptionx-0 D~MLX`\C"8J UWY{wwRg+]|?BO&2 .IlL} "$ܮg<-.=KMk.QI#|Ě=&'*EQ5 o>ᤊ{,SPm{͹<}mUxoGmҥVbz@zTXtSoftwarexs.JM,IMQSHTK).I,sJ3 rK ҕBQzTXtDisclaimerxs+JM-O,JU`ԮIDATx] 0 P*@# #T10lPF`ؠF=IQ*u`%qk H񚈩mߟ э=,fOK t(F ;P{xp]9/p*$(*yՃ@C  cqNU#)11.rf0gh(tEkIENDB`chunky_png-1.3.15/spec/png_suite/other/cs5n3p08.png0000755000175000017500000000041713766004353021275 0ustar danieldanielPNG  IHDR DgAMA1_sBIT&C`PLTEB{c!Z::cZ!B{G/KIDATx0[>@Vj%(`R A XtY VA,`d\p *-hq>IENDB`chunky_png-1.3.15/spec/png_suite/other/pp0n2c16.png0000755000175000017500000000170213766004353021261 0ustar danieldanielPNG  IHDR 1gAMA1_PLTE3f3333f333ff3fffff3f3f̙3f3333f3333333333f3333333f3f33ff3f3f3f3333f3333333f3̙333333f333ff3ffffff3f33f3ff3f3f3ffff3fffffffffff3fffffff3fff̙ffff3fffff3f̙3333f33̙3ff3ffff̙f3f̙3f̙̙3f̙3f3333f333ff3fffff̙̙3̙f̙̙̙3f̙3f3f3333f333ff3fffff3f3f̙3fcQIDATxՖ 0DA~&fzE=֠B>/drs~='3_Zwt(p3p]`_yt?t(C\l52L̩g I@g.`(`g Dg&((`60Y`zl(P49܀:{z*zȟmt3ΞAO3B^IENDB`chunky_png-1.3.15/spec/png_suite/other/ch2n3p08.png0000755000175000017500000000342213766004353021256 0ustar danieldanielPNG  IHDR DgAMA1_PLTE"Dww :w""""Uffff"DDUU"DDUU3DDff3D"ffD33U*D˺[""f2Ucw:DDkfBkܺ33sJw{"w332fDwJf""UUDff3UwwDwffD"w"3333cU3{UUUUf܃wwUUww""DD3ԪU*U˴f3BSD̙"Sww333Ĉwff""UU"DD[wfwws33wD""U"f 3bhISTMIDATx GHd+;3 ekXegа**4h lޣY2W%syiHCL*;8"KE6sx'HK?Y9sģ>1~!'B 0IENDB`chunky_png-1.3.15/spec/png_suite/other/cdun2c08.png0000755000175000017500000000132413766004353021336 0ustar danieldanielPNG  IHDR gAMA1_sBITw pHYs{RkgIDATx qjhWK %ܵB q @ Fx7 zO/̃/[ü ̍ӑm@`]]6y~KXa;e" ṯ[I=du5V3>&?wVӶc[lJ6YՖ` ⤉)?euSX,QOyKN]J:#< CL=v%$ǃ'JwMk=b+\u;zwP^<'pbS d(@J,=9ּnH])rt`oIAtUۛDVbmB5˙(YkTns=xBd"\`"[ fRhz0'mehCMݨ3  Ldٯ?`~+Іdl*#p]q2^ɩ8|_ rj:2Ckiʙ9ff |ADJi?c~++ 範t>rl5yM.f P6@.lIENDB`chunky_png-1.3.15/spec/png_suite/other/ps1n0g08.png0000755000175000017500000000270513766004353021274 0ustar danieldanielPNG  IHDR V%(gAMA1_/spALsix-cubePNG group 1996-10-223f3333f333ff3fffff3f3f̙3f3333f3333333333f3333333f3f33ff3f3f3f3333f3333333f3̙333333f333ff3ffffff3f33f3ff3f3f3ffff3fffffffffff3fffffff3fff̙ffff3fffff3f3333f333ff3fffff3f3f̙3f3f3333f333ff3fffff̙̙3̙f̙̙̙3f̙3f3f3333f333ff3fffff3f3f̙3f@AIDATxcd`$ȳ )?`y00gdy\ q10edPq5YIENDB`chunky_png-1.3.15/spec/png_suite/other/ps1n2c16.png0000755000175000017500000000315113766004353021265 0ustar danieldanielPNG  IHDR 1gAMA1_/spALsix-cubePNG group 1996-10-223f3333f333ff3fffff3f3f̙3f3333f3333333333f3333333f3f33ff3f3f3f3333f3333333f3̙333333f333ff3ffffff3f33f3ff3f3f3ffff3fffffffffff3fffffff3fff̙ffff3fffff3f3333f333ff3fffff3f3f̙3f3f3333f333ff3fffff̙̙3̙f̙̙̙3f̙3f3f3333f333ff3fffff3f3f̙3f@IDATxՖ 0DA~&fzE=֠B>/drs~='3_Zwt(p3p]`_yt?t(C\l52L̩g I@g.`(`g Dg&((`60Y`zl(P49܀:{z*zȟmt3ΞAO3B^IENDB`chunky_png-1.3.15/spec/png_suite/other/cs8n2c08.png0000755000175000017500000000022513766004353021257 0ustar danieldanielPNG  IHDR gAMA1_LIDATxA 0 BQcjS҉hi Dk`j}0MA_uoFIENDB`chunky_png-1.3.15/spec/png_suite/other/ch1n3p04.png0000755000175000017500000000040213766004353021244 0ustar danieldanielPNG  IHDR TggAMA1_sBITw-PLTE""fwDDҰIhIST@p0`` P@0PpHYAGIDATxc =sfժrcwfd C+(H*ŅTݻq@*)#MK#G{7}IENDB`chunky_png-1.3.15/spec/png_suite/other/cs3n2c16.png0000755000175000017500000000032613766004353021253 0ustar danieldanielPNG  IHDR 1gAMA1_sBIT 7~IDATx @ H*)^ < 4\u wClPgXPR3ޛߟ:Ž~!N$#|G}LCli(?c"B@aEDG@0S&i\vgIENDB`chunky_png-1.3.15/spec/png_suite/transparency/0000755000175000017500000000000013766004353020667 5ustar danieldanielchunky_png-1.3.15/spec/png_suite/transparency/tbbn3p08.png0000755000175000017500000000215013766004353022736 0ustar danieldanielPNG  IHDR DgAMA1_PLTE""ssQ DDb ffs sssoookkkgggUUUggQQQMMM88 V 333////+/++w||+'s+++o'''x ###UMMM < oo/8' UUM M  bb UU3لww<<D ||| bbb"^^^ZZZ''xxxxssooDDD@@@ff<<< UU444MM|EE<<"""<33"D''<  // m4#tRNS@fbKGD \IDATxc`XW_!^.aaҡ^KE],C:OWF\` T%ڭg➗#~aξNlEdDU4.-S s D-JjA*t2g#3)"gįp(0I5{8 H\ _ $a}Fc7@y1`vY/rU&K­PqL%B}?aaDL*_*h>Md'R@C r+ ³L bXZ%J!F8N7) ( .&  $v̆Kwq10i^̼0>m8 jQ9/KxvYžL8xqI+s'IENDB`chunky_png-1.3.15/spec/png_suite/transparency/tbgn3p08.png0000755000175000017500000000215013766004353022743 0ustar danieldanielPNG  IHDR DgAMA1_PLTE""ssQ DDb ffs sssoookkkgggUUUggQQQMMM88 V 333////+/++w||+'s+++o'''x ###UMMM < oo/8' UUM M  bb UU3لww<<D ||| bbb"^^^ZZZ''xxxxssooDDD@@@ff<<< UU444MM|EE<<"""<33"D''<  // m4#tRNS@fbKGDf |dIDATxc`XW_!^.aaҡ^KE],C:OWF\` T%ڭg➗#~aξNlEdDU4.-S s D-JjA*t2g#3)"gįp(0I5{8 H\ _ $a}Fc7@y1`vY/rU&K­PqL%B}?aaDL*_*h>Md'R@C r+ ³L bXZ%J!F8N7) ( .&  $v̆Kwq10i^̼0>m8 jQ9/KxvYžL8xqI+s'IENDB`chunky_png-1.3.15/spec/png_suite/transparency/tbbn2c16.png0000755000175000017500000000371213766004353022724 0ustar danieldanielPNG  IHDR 1gAMA1_tRNSYbKGDGe]IDATxX_HbiD  I  ƅ 6.Bq`*(`‹6 jYBȅrIHA!9CFxSgwWs<{djjjjj ywwwww!ICJT*J\[[[[[[cccccC WVVVVV`0H$DcX,_=FfvrH$----Noǣh4bx `0:N\. d2ᅮt*JR?ŋFc>/d<$t:  B>p|||||lv{~UHo0Ғr\<< BQje3 B@M,S&JpKs:N\O,r)ټw·d^ '@  UnL&dxa>|8J% )BBx灭ݻwͲ, l6˗jj5wt~ ~}6jZ7 J΍hThrXxe#T*`zeOY~[l6*W`͛7oy<dz loBP(DU@>:::::v:Y@oI*y`xaB*&&vp:GF8Nux~o'ӧ'O'OVWb.rnx5 L&IzFh48&(x8< 4T&@0H `b"HDfl~xD"H7i@6f, RT*B(2ٗy[Z[|>EzI |x|JPЅV{gg$ˊ}y,b\kE89= LOWUUUt:N~g2L&)|P( 3i). P Drz)Td`0 Y V֖N3PyF?F 03,+d2YG ..({U`'6{vvvvv033 kk@#|| ={<{q:iZVKAL  X,SjhцE_?Qt:Nh4Fp8ٮ.1MD[ *>er\.cYeY奞Aޠ@Vj4< 0࠘ qnjFH>Na::`"Ijsn>7ҍ7*1yT@HI$@$2ܬ--bpohhhhh ޳Dzz^r\NHw0D|a2r&˦,KX^+Aoooooo%w8RAZ܌n:*WUd U~hfl6奻^uL<:::::Z>.H$I%+t NOOOOOwKwgѴ[b_XXXXXX _-Jw7JMGP2F2Dڇ(*{^-w)SRRդ߿J uZ*|+n}}}}}}''''''-/ ᵅ IENDB`chunky_png-1.3.15/spec/png_suite/transparency/tp0n3p08.png0000755000175000017500000000214013766004353022671 0ustar danieldanielPNG  IHDR DgAMA1_PLTE""ssQ DDb ffs sssoookkkgggUUUggQQQMMM88 V 333////+/++w||+'s+++o'''x ###UMMM < oo/8' UUM M  bb UU3لww<<D ||| bbb"^^^ZZZ''xxxxssooDDD@@@ff<<< UU444MM|EE<<"""<33"D''<  // 2lIDATx}_ QeBeF"[2YHȣ6%VVk }^( e ݋wѳO},g V1Mrnƾ@2 wfCx>+cKQ–[-ȱ䈻#0Dfݦ%#v3nwd@\ qjɲ TYs%IENDB`chunky_png-1.3.15/spec/png_suite/transparency/tbbn1g04.png0000755000175000017500000000064313766004353022724 0ustar danieldanielPNG  IHDR )gAMA1_tRNSXbKGD#2>IDATxUKAd[jH-5(5(O 'W:[":EEt-jv.<3`vBwqg&W? -³[#Τ}O$CptjL:dt}Kߩz}\}|x,ryT$eѐY\D5Z$ZEfiihsJGFr9Z\-}$Þ=w>z41qBW\_pW\gu$ s<929yVw7X>e2SSK1AAfaѺ:$DBFE?bNv`^NS2@mm}}Up"""cm lVy(*`cc:t V(OJ&ҥK SLddڅa~KQ> JKL|YxJ$VfVVʷSDWF\` T%ڭg➗#~aξNlEdDU4.-S s D-JjA*t2g#3)"gįp(0I5{8 H\ _ $a}Fc7@y1`vY/rU&K­PqL%B}?aaDL*_*h>Md'R@C r+ ³L bXZ%J!F8N7) ( .&  $v̆Kwq10i^̼0>m8 jQ9/KxvYžL8xqI+s'IENDB`chunky_png-1.3.15/spec/png_suite/transparency/tp1n3p08.png0000755000175000017500000000213313766004353022674 0ustar danieldanielPNG  IHDR DgAMA1_PLTE""ssQ DDb ffs sssoookkkgggUUUggQQQMMM88 V 333////+/++w||+'s+++o'''x ###UMMM < oo/8' UUM M  bb UU3لww<<D ||| bbb"^^^ZZZ''xxxxssooDDD@@@ff<<< UU444MM|EE<<"""<33"D''<  // m4#tRNS@fIDATxc`XW_!^.aaҡ^KE],C:OWF\` T%ڭg➗#~aξNlEdDU4.-S s D-JjA*t2g#3)"gįp(0I5{8 H\ _ $a}Fc7@y1`vY/rU&K­PqL%B}?aaDL*_*h>Md'R@C r+ ³L bXZ%J!F8N7) ( .&  $v̆Kwq10i^̼0>m8 jQ9/KxvYžL8xqI+s'IENDB`chunky_png-1.3.15/spec/png_suite/transparency/tbrn2c08.png0000755000175000017500000000250313766004353022742 0ustar danieldanielPNG  IHDR gAMA1_tRNS33ObKGD3'|IDATx_hSw?@#^i/4@of4Z`UVf )X1`}Hp` CO v,fP; -Hcb$=ddf]w=99k}6G^|:x(޽{&E1& @Q'O_>nO&#HooaqqC8W^]z5 x8D",/fۏNp8vm~~~`d21;KF\ZXX8"۷o'''3ѹ߅<002V+Ϟ01nϟ?~Dt]O,!˸,,`2q@4Lm3ĉPa0~dt^o8xK竵3/، d˔A-W:k;tuu5b\`0(IA 7յ`hp8𡅾;`hhHzoo(WhhM C8$)y@`0xIkJ:\.i4UUX,@N cǎEўZFUр'O={ӧO;;;Gz IENDB`chunky_png-1.3.15/spec/png_suite/transparency/tp0n2c08.png0000755000175000017500000000243713766004353022664 0ustar danieldanielPNG  IHDR gAMA1_IDATx_hSw?@#^i/4@of4Z`UVf )X1`}Hp` CO v,fP; -Hcb$=ddf]w=99k}6G^|:x(޽{&E1& @Q'O_>nO&#HooaqqC8W^]z5 x8D",/fۏNp8vm~~~`d21;KF\ZXX8"۷o'''3ѹ߅<002V+Ϟ01nϟ?~Dt]O,!˸,,`2q@4Lm3ĉPa0~dt^o8xK竵3/، d˔A-W:k;tuu5b\`0(IA 7յ`hp8𡅾;`hhHzoo(WhhM C8$)y@`0xIkJ:\.i4UUX,@N cǎEўZFUр'O={ӧO;;;Gz IENDB`chunky_png-1.3.15/spec/png_suite/transparency/tbyn3p08.png0000755000175000017500000000215313766004353022770 0ustar danieldanielPNG  IHDR DgAMA1_ PLTE""ssQ DDb ffs sssoookkkgggUUUggQQQMMM88 V 333////+/++w||+'s+++o'''x ###UMMM < oo/8' UUM M  bb UU3لww<<D ||| bbb"^^^ZZZ''xxxxssooDDD@@@ff<<< UU444MM|EE<<"""<33"D''<  // ftRNS@fbKGD bIDATxc`XW_!^.aaҡ^KE],C:OWF\` T%ڭg➗#~aξNlEdDU4.-S s D-JjA*t2g#3)"gįp(0I5{8 H\ _ $a}Fc7@y1`vY/rU&K­PqL%B}?aaDL*_*h>Md'R@C r+ ³L bXZ%J!F8N7) ( .&  $v̆Kwq10i^̼0>m8 jQ9/KxvYžL8xqI+s'IENDB`chunky_png-1.3.15/spec/png_suite/transparency/tbgn2c16.png0000755000175000017500000000371213766004353022731 0ustar danieldanielPNG  IHDR 1gAMA1_tRNSYbKGDމc]IDATxX_HbiD  I  ƅ 6.Bq`*(`‹6 jYBȅrIHA!9CFxSgwWs<{djjjjj ywwwww!ICJT*J\[[[[[[cccccC WVVVVV`0H$DcX,_=FfvrH$----Noǣh4bx `0:N\. d2ᅮt*JR?ŋFc>/d<$t:  B>p|||||lv{~UHo0Ғr\<< BQje3 B@M,S&JpKs:N\O,r)ټw·d^ '@  UnL&dxa>|8J% )BBx灭ݻwͲ, l6˗jj5wt~ ~}6jZ7 J΍hThrXxe#T*`zeOY~[l6*W`͛7oy<dz loBP(DU@>:::::v:Y@oI*y`xaB*&&vp:GF8Nux~o'ӧ'O'OVWb.rnx5 L&IzFh48&(x8< 4T&@0H `b"HDfl~xD"H7i@6f, RT*B(2ٗy[Z[|>EzI |x|JPЅV{gg$ˊ}y,b\kE89= LOWUUUt:N~g2L&)|P( 3i). P Drz)Td`0 Y V֖N3PyF?F 03,+d2YG ..({U`'6{vvvvv033 kk@#|| ={<{q:iZVKAL  X,SjhцE_?Qt:Nh4Fp8ٮ.1MD[ *>er\.cYeY奞Aޠ@Vj4< 0࠘ qnjFH>Na::`"Ijsn>7ҍ7*1yT@HI$@$2ܬ--bpohhhhh ޳Dzz^r\NHw0D|a2r&˦,KX^+Aoooooo%w8RAZ܌n:*WUd U~hfl6奻^uL<:::::Z>.H$I%+t NOOOOOwKwgѴ[b_XXXXXX _-Jw7JMGP2F2Dڇ(*{^-w)SRRդ߿J uZ*|+n}}}}}}''''''-/ ᵅ IENDB`chunky_png-1.3.15/spec/png_suite/transparency/tp0n1g08.png0000755000175000017500000000126113766004353022661 0ustar danieldanielPNG  IHDR V%(gAMA1_hIDATx}]HqM2?rY65K]hddaQQ؅ldHPAt}t+BRUaH E-6kL9[_du{\{9Gr-_[qŃ=CX=. .Zmm9sn'%.o=?Ud^g#!{}]DЯvu1ޤGb];;0~Y4$pϐKd[/{j:Ru>Gcèۚp!]v4[>ˎξu#~ENŒ].?+FSD^RSe:^z52E$8 &*az$"a lUEy9Z*XWr=+pȶSM l;h ϫt;\rlnn 0(RVۂP%>rB-)ԙnQIHL0˧2ky݄:Ubrd,)gk;(~V٬uGU@Y޾ vK$j 8P=MIƻ9WR'uf5]K.ݣ6< ,=ۂ?)͚IENDB`chunky_png-1.3.15/spec/png_suite/filtering/0000755000175000017500000000000013766004353020141 5ustar danieldanielchunky_png-1.3.15/spec/png_suite/filtering/f00n0g08.rgba0000644000175000017500000001000013766004353022127 0ustar danieldanielvvvooovvvgggooovvv```gggooovvvYYY```gggooovvvRRRYYY```gggooovvvLLLRRRYYY```gggooovvvEEELLLRRRYYY```gggooovvv???EEELLLRRRYYY```gggooovvv:::???EEELLLRRRYYY```gggooovvv444:::???EEELLLRRRYYY```gggooovvv///444:::???EEELLLRRRYYY```gggooo***///444:::???EEELLLRRRYYY```ggg&&&***///444:::???EEELLLRRRYYY```vvv!!!&&&***///444:::???EEELLLRRRYYYooovvv!!!&&&***///444:::???EEELLLRRRggg!!!&&&***///444:::???EEELLLvvv!!!&&&***///444:::???EEEgggooovvv!!!&&&***///444:::???YYY```gggooovvv!!!&&&***///444:::LLLRRRYYY```gggooo !!!&&&***///444LLLRRRYYY``` !!!&&&***///444ooovvv !!!&&&***///444```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvvchunky_png-1.3.15/spec/png_suite/filtering/f01n2c08.rgba0000644000175000017500000001000013766004353022126 0ustar danieldaniel ')/179?AGJORWZ_bgjosw{&-4;'C/J7Q?XG`OgWn_ug|owſ7;?EKQW']/d7j?pGvO|W_gowǷͿMSX_dins'x/}7?GOW_gowƧ˯зտ`gov'.7?GNW_gnwŎɗ͟ѦծٷݿoxЁߋ'.7?GNW_gnwЇӎٟ֗ܦ|Лߧ'.7?GNW_gnwе'.7?GNW_gnw&.6NV^fnv~p{&FNV^fnv~Xu`is|>FNV^fnv~BjIuPX_hpy5=EMU]emu}0_5j:u@FLSZahp-5=EMU]emu} U$_'j,u049>CHN`e t%y-~5=EMU]emu}LU_ju #&)-048;@RX%^-d5k=qEwM}U]emu}C L U _ ju.5<$C,K4RCHNTY`ej   & , 4;CLU_ju #&)-048;@F   &,4;C L U _ ju chunky_png-1.3.15/spec/png_suite/filtering/f04n0g08.png0000755000175000017500000000041513766004353022020 0ustar danieldanielPNG  IHDR V%(IDATxm0Lb )nK'-X B!9Dzѧي|o&C{/މsYk1#gZL B.mz#\@* // _,LPߪZQ[6@]8_fX`ⷹ=L[+/C qfS(RR| D_H/$@&L!`IENDB`chunky_png-1.3.15/spec/png_suite/filtering/f04n2c08_reference.rgba0000644000175000017500000001000013766004353024147 0ustar danieldaniel ')/179?AGJORWZ_bgjosw{&-4;'C/J7Q?XG`OgWn_ug|owſ7;?EKQW']/d7j?pGvO|W_gowǷͿMSX_dins'x/}7?GOW_gowƧ˯зտ`gov'.7?GNW_gnwŎɗ͟ѦծٷݿoxЁߋ'.7?GNW_gnwЇӎٟ֗ܦ|Лߧ'.7?GNW_gnwе'.7?GNW_gnw&.6NV^fnv~p{&.FNV^fnv~Xu`is|>FNV^fnv~BjIuPX_hpy -5=EMU]emu}0_5j:u@FLSZahpw%-5=EMU]emu} U$_'j,u049>CHNTYjot%y-~5=EMU]emu}LU_ju #&)-04F LRX%^-d5k=qEwM}U]emu}C L U _ ju ' .5<$C,K4RCHNTY`ej   & , 4;CLU_ju #&)-048;@F   &,4;C L U _ ju chunky_png-1.3.15/spec/png_suite/filtering/f04n0g08_reference.rgba0000644000175000017500000001000013766004353024151 0ustar danieldanielvvvooovvvgggooovvv```gggooovvvYYY```gggooovvvRRRYYY```gggooovvvLLLRRRYYY```gggooovvvEEELLLRRRYYY```gggooovvv???EEELLLRRRYYY```gggooovvv:::???EEELLLRRRYYY```gggooovvv444:::???EEELLLRRRYYY```gggooovvv///444:::???EEELLLRRRYYY```gggooovvv***///444:::???EEELLLRRRYYY```gggooovvv&&&***///444:::???EEELLLRRRYYY```ggg!!!&&&***///444:::???EEELLLRRRYYY```vvv!!!&&&***///444:::???EEELLLRRRooovvv!!!&&&***///444:::???EEELLL```gggooovvv!!!&&&***///444:::???EEEYYY```gggooo!!!&&&***///444:::???!!!&&&***///444::: !!!&&&***///444:::???EEELLLRRRYYYooovvv !!!&&&***///444:::???EEELLLRRRgggooovvv !!!&&&***///444:::???EEELLL```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvvchunky_png-1.3.15/spec/png_suite/filtering/f03n2c08.rgba0000644000175000017500000001000013766004353022130 0ustar danieldaniel ')/179?AGJORWZ_bgjosw{&-4;'C/J7Q?XG`OgWn_ug|owſ7;?EKQW']/d7j?pGvO|W_gowǷͿMSX_dins'x/}7?GOW_gowƧ˯зտ`gov'.7?GNW_gnwŎɗ͟ѦծٷݿoxЁߋ'.7?GNW_gnwЇӎٟ֗ܦ|Лߧ'.7?GNW_gnwе'.7?GNW_gnwnv~p{fnv~Xu`is|&.6V^fnv~BjIuPX_hpy %EMU]emu}0_5j:u@FLSZahpw 5=EMU]emu} U$_'j,u049>CHNTY`t%y-~5=EMU]emu}LU_ju #&)-048^-d5k=qEwM}U]emu}C L U _ ju ' C,K4RCHNTY`ej   & , 4;CLU_ju #&)-048;@F   &,4;C L U _ ju chunky_png-1.3.15/spec/png_suite/filtering/f03n0g08.rgba0000644000175000017500000001000013766004353022132 0ustar danieldanielvvvooovvvgggooovvv```gggooovvvYYY```gggooovvvRRRYYY```gggooovvvLLLRRRYYY```gggooovvvEEELLLRRRYYY```gggooovvv???EEELLLRRRYYY```gggooovvv:::???EEELLLRRRYYY```gggooovvv444:::???EEELLLRRRYYY```gggooovvv///444:::???EEELLLRRRYYY```gggooovvv***///444:::???EEELLLRRRYYY```gggooovvv&&&***///444:::???EEELLLRRRYYY```gggooo!!!&&&***///444:::???EEELLLRRRYYY```ggg!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::LLLRRRYYY```gggooo !!!&&&***///444LLLRRRYYY``` !!!&&&***///444ooovvv !!!&&&***///444```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvvchunky_png-1.3.15/spec/png_suite/filtering/f03n0g08_reference.png0000644000175000017500000000073113766004353024033 0ustar danieldanielPNG  IHDR szzIDATx-Q"`XLdٌ6f٦L&b,AAD; ݝ7M8}wv9 8.G1' )g9%W57 -w=#O3/ +o;wt:j"n@բ&f4 j"Pש@V&j~|JBɗ@\8pqT*n(Eᶍ qh0? J5 |Rn:r9 Q lA#$=L&à@rm#N3(R)ExMd!L/ld"H񮤫xA!l'hbFD Pp8LMB5xu&i!`α (t6enIENDB`chunky_png-1.3.15/spec/png_suite/filtering/f01n0g08.rgba0000644000175000017500000001000013766004353022130 0ustar danieldanielvvvooovvvgggooovvv```gggooovvvYYY```gggooovvvRRRYYY```gggooovvvLLLRRRYYY```gggooovvvEEELLLRRRYYY```gggooovvv???EEELLLRRRYYY```gggooovvv:::???EEELLLRRRYYY```gggooovvv444:::???EEELLLRRRYYY```gggooovvv///444:::???EEELLLRRRYYY```gggooo***///444:::???EEELLLRRRYYY```ggg&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooo!!!&&&***///444:::???EEELLLRRRYYY```ggg!!!&&&***///444:::???EEELLLRRRYYY```vvv!!!&&&***///444:::???EEELLLRRRYYYooovvv!!!&&&***///444:::???EEELLLRRRgggooovvv !!!&&&***///444:::???EEELLL```gggooovvv !!!&&&***///444ooovvv !!!&&&***///gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvvchunky_png-1.3.15/spec/png_suite/filtering/f00n2c08.rgba0000644000175000017500000001000013766004353022125 0ustar danieldaniel ')/179?AGJORWZ_bgjosw{&-4;'C/J7Q?XG`OgWn_ug|owſ7;?EKQW']/d7j?pGvO|W_gowǷͿMSX_dins'x/}7?GOW_gowƧ˯зտ`gov'.7?GNW_gnwŎɗ͟ѦծٷݿoxЁߋ'.7?GNW_gnwЇӎٟ֗ܦ|Лߧ'.7?GNW_gnwе'.7?GNW_gnw&^fnv~p{^fnv~Xu`is|&.6>^fnv~BjIuPX_hpy%-5=U]emu}0_5j:u@FLSZahp %-MU]emu} U$_'j,u049>CHN`e joEMU]emu}LU_ju #&)-0;@F k=qEwM}U]emu}C L U _ ju5K4RCHNTY`ej   & , 4;CLU_ju #&)-048;@F   &,4;C L U _ ju chunky_png-1.3.15/spec/png_suite/filtering/f02n2c08_reference.rgba0000644000175000017500000001000013766004353024145 0ustar danieldaniel ')/179?AGJORWZ_bgjosw{&-4;'C/J7Q?XG`OgWn_ug|owſ7;?EKQW']/d7j?pGvO|W_gowǷͿMSX_dins'x/}7?GOW_gowƧ˯зտ`gov'.7?GNW_gnwŎɗ͟ѦծٷݿoxЁߋ'.7?GNW_gnwЇӎٟ֗ܦ|Лߧ'.7?GNW_gnwе'.7?GNW_gnw&^fnv~p{^fnv~Xu`is|&.6>^fnv~BjIuPX_hpy%-5=U]emu}0_5j:u@FLSZahp %-5MU]emu} U$_'j,u049>CHNTY`e jot%y-EMU]emu}LU_ju #&)-048;@F LRX%k=qEwM}U]emu}C L U _ ju ' .K4RCHNTY`ej   & , 4;CLU_ju #&)-048;@F   &,4;C L U _ ju chunky_png-1.3.15/spec/png_suite/filtering/f03n2c08_reference.png0000644000175000017500000000464713766004353024043 0ustar danieldanielPNG  IHDR szz nIDATxڍ{PT."uAUD*nQxɘ:5#64/Qc$#VK„*^Rk6ޢ1Vjfg}>#&b⥄3U:Bz|9^zYiLiqmf-&nem'W@2MEyJDz b} 80/c N8 9߀[ hq djZ [A^GAq)g@48 9A\q?A\ 5 n  }%SCu@ԀhQ>~ hq5A&[ n; "]q}߃/ ڮ:@xCpQ4GƘt0=? B۵j@lc Z@UP   &Ԧ<K@(5 A4 QPN1}_tI~ooA|Dk AqX`s /)ʦj(n5]o;9EqhF cM'NaB~TZ_|ȵ48@Lb ^D1e Aģɫ5Z5z$Q/ o 8fƙmi0Ti 2@@dx 3@1DGJt =Z ցx&  D# S&6R A8A b(t \ Ƃ{"[@Qbw@ւaϻ,Vk@TXb- b'zO읽Aă 3 ɳGpإѬ\$n*gvmQ D=A>Aqq3DEC@)=A&Y+(}*o 0:=11@\/T @Z*t.n@'6qD++ @\q u MTL UcoOL%HiT  + D6\24f9Yrjj!!!|.0a2AD"D RAppb. ^- 2+dVWl$99*_zI}JCllD@"D$@H1 D& Y(d@,Q @ԀN >9c |t`c3 "B@"Do @8A 1 D 2ρxD\Ax  @l 6h ,~@A8@" D X}@$H1D  q & "  @b1b@Xb- X2X l  BA "D X}@(dH" D&@x@L1\\2_y@zap " D8 @D A @$H1D Adp bɝG&8p@X l l   B@@D"D bADDI A8i w-IENDB`chunky_png-1.3.15/spec/png_suite/filtering/f01n2c08.png0000755000175000017500000000223413766004353022014 0ustar danieldanielPNG  IHDR cIDATx?%UΜ34"XBc+АPF M(,hH((,10V5RB %,bܝ토x7}ost°ٙbW>y~Lk鯝U(3kAy=uesp+ȍ2:SAE7woyU~wz:`{('^TU*qVNzS)S[I[DY\O*_ ?+t|' E3ǧpդ$LTPMR54: @|6ۿ|a3Ƞ@ *TTA:0`pIvz (Ȓ"Dթhbvt;}@8}*(fDv8䨞ꨞi٫ُ3Zg { IdyW LδH Q=~w'%hr')<Gh9^zp70FR(;{ ^xڹ ,#)Q%1%j&2N? p(QT; d 5'ER E,%Δ̔G̨Zf; v:1#i%aL11oꂛctƬeQ !y{b$FR:cF^B)LRj56 1,ڠxOHLĵΗ%+XLJL@<4 CGb"5FȕRN l]B.Ot'R(60 P]~rpg߅8+G 䡲\ V.: pi!„& A뾝;:QDZc(? @&K炭@( D ~ @q'@ W@ .e|C;Nhf h9 iog߀65 jAԁn{AqaG@x@q  . e_hqD At\-a b < 8 ΂hqD .2 D Vm n]T-vj@q D3k Z@\ 67Aq߂h/~ 7{ @<LeMUU6ck$?;H<I% #cE7lB ;dA2Xb v88& hqD+V4߂h]wAq}߁xXb.b VXDq'A<.9A| +q  D+6@a^,Uʬʭ*ʪ9 lZ=>6ڮ }H.{R|2OCt@?'zY5&6G;KΨDZ&_ |̳ha F"dA b" AxvkWATu b/  Nha9"D&',#@A1@<.^LW~b1w@X @T N 801ŁH"Do oXl@xL?  A,Qb5 >Qu LۢPa "@DZUbQ4. b@Z`U"S69dW"b)#5VNP5M/)b0S3؎(-\F7&9$#k$U|o6O%+B?AȂ1&A-!,:g ?wPa ADD]$;'cYdJ Oc@L "Dy J@ץ8#@Dɸe" D,e=d!]V@Ye }r 6|GgTSIqS "DA8A J րb3- D='1zG,A8@spxhfb1U~!!!t*" DW A" XS@1$P,YQ%Vfk.Aq&,R5KԳw2- "Dh1 A$ " D: @1@C!b.b VXD]9/@A; VDa "AD"D_Adbx^y @Qb1@Tb;@Еu>Xaa "DH b@ăHTr @1 D6 xE ( *A "DP @D@C!Dt @18S@Ld} p = "D(p @tq  @p "D? 2A8A HA@ "D0N@C D @D"D,x @$H 7YIENDB`chunky_png-1.3.15/spec/png_suite/filtering/f00n2c08.png0000755000175000017500000000465313766004353022022 0ustar danieldanielPNG  IHDR  rIDATxuopTƟM6Ɇ ,I hE@v Fؖb k"d; MHCaVe(RzCH/||L(8ڞOwD2h\:9j<-B>YY󸠄X@WWs]-7sK#lfi<<p_hNKzNsL,sɧwKsi\U5󸾄n`cߪZg[#ۛxRϮvw|/eEiywn!>Ǥ-L%QI4be.WsxpC!7y|M wVpw[yi̓x?]9//[סFʤ16X-lql*BS=sV} XCS5߯Z ɛgx" :1è&v&QM,eW>O."%b/U=m&5hꝼ}w !iP㇍17QKbOd7Wym,`_!7~Sg5Zުwm k;}'D8,G"r4*bU5M3꺑4d&3҉<$א/udE r]Dߎ#qqxN7t)?7&+uŗo6p-[ca!I#’3"ccUyt4g9OWsN Xsvwf7؀d0D_ T% RRFD2cR?([stw2^qz},bpFf_MA!DRRRR>Q?z vqN|Y\P|q%A:m'Ix,)B E0U It)2Gw"9{nΚӹDzbV\D O*V M(Y"`l?KIV a?%cX:K= L*snXͿb#L w</[B!"8F?ǞԙR|-E \=k' ӹ׊6-Ng\p'<<4>Ẉ {C̆+n'<.x'7iPBkA)T'k4OMv{yv6TG6yp=7φ%B;&D-=x:&KX,Zan.nx pOhF)VXl1؁,']p1҃^LaS aQJˣXK*^#-]C¡{/! { 3L$[bNd`w#ǃ^8}O&XB/Q,XZ!72}yo]@ adE f;:Յ 72=E/a xDA~ &cJODaqkxNOHr9_ FLv$;`vb 7R=H& 6?(#a8#&p -a |4/ '.H$ fR@jȈ`p1 Qa' 9:o&Z$IENDB`chunky_png-1.3.15/spec/png_suite/filtering/f02n0g08.rgba0000644000175000017500000001000013766004353022131 0ustar danieldanielvvvooovvvgggooovvv```gggooovvvYYY```gggooovvvRRRYYY```gggooovvvLLLRRRYYY```gggooovvvEEELLLRRRYYY```gggooovvv???EEELLLRRRYYY```gggooovvv:::???EEELLLRRRYYY```gggooovvv444:::???EEELLLRRRYYY```gggooovvv///444:::???EEELLLRRRYYY```gggooo***///444:::???EEELLLRRRYYY```gggooovvv&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooo!!!&&&***///444:::???EEELLLRRRYYY```!!!&&&***///444:::???EEELLLRRRooovvv!!!&&&***///444:::???EEE```gggooovvv !!!&&&***///444:::RRRYYY```gggooovvv !!!&&&***///vvv !!!&&&***ooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvvchunky_png-1.3.15/spec/png_suite/filtering/f03n0g08.png0000755000175000017500000000060513766004353022020 0ustar danieldanielPNG  IHDR V%(LIDATx}ұKBQDQ !EP4.A!"!H_BA 84854 Ay{wpwu2ݽã\x~qyu}S*o+׭UM ۆJnJ -4IBFv&u0 C V1ň;୑BguNJ=:'N{1qO0">1qv7/M1 i"ޡE*plCǰby% &'0B{C* T L @cg\B 9IENDB`chunky_png-1.3.15/spec/png_suite/filtering/f02n2c08_reference.png0000644000175000017500000000466113766004353024036 0ustar danieldanielPNG  IHDR szz xIDATxڍ{PTw/  b$fmMd1&^ѩd4Q:/#V{#VK݈52Xm"*^BL;-3?a9{#%EuRԞ#K?Xzi(WqKI3 %|tdTRZ#m,nvHIGJ'NI祋R-).=| etz1 8@(Q b2 į@D== 8/@\qD ; xH&B'\ F b^Aq~AqD# qW A\ 6dvVX b 8  N8 ,FA4"K . AmwA9Q b hq D n -Aq7 @DD;{ > OA<:)S]mSMM^M>]]`BT Sߣ2e%z/}H!:H}*SRa+@.A5- [D6! > C̪"sA(: A@|3zV_:>_A\A4hqD+ 0xz VT[UXc%>mzDuЮM!]Pii^zʗ_3u\uJ-S _@\qUdr - b\c@䁘b7AQ7` D5 6zq A40| "p#AA1T"Ep8y@sA<h"&h[9N 80y%H"D "0. ƀ1Dǀ0?R VX CD=(D Xq,JD"$U}VH ؔ)#dCve)aԋ2Zy*TFj&q60e z]F7GS*3VPT |F6?T@0A a x BX9N/-( b@t "D]At"D  :H'\ 5$ @ D9s^vgg,xwL^7 R@H,սe' en(ufgiHbZùgLN|> @ 10/u*Qb (R Vz9]d2 &I+D/  Cp@/5P b> D5 6 b v5Ҹ{83strAxP.!%GwATb:^ >^Aq pH;sebL~L ,OVU ^7S{_o6CtD#K<X@0@ \ 1A hq 7A'4qq2 Br24LA&#8g$ e<2DkYtxede @A@@@@ LTLg H,@8A@A1Lo %2+jdyedvedt6diI][ץ^7xv6N AĂH"D:^ @dxp Ƃxn(d@Q J@Ԃw >gAę0fFD"D$) A8@1D ^, 72xDBs@X = 6 zXaa " D 8 At"Do  0 F1D) fx% *aA "Dh A$HDyB/, r@8A 1XAL1=Ĕ>FNV^fnv~BjIuPX_hpy -5=EMU]emu}0_5j:u@FLSZahpw%-5=EMU]emu} U$_'j,u049>CHNTYjot%y-~5=EMU]emu}LU_ju #&)-04F LRX%^-d5k=qEwM}U]emu}C L U _ ju ' .5<$C,K4RCHNTY`ej   & , 4;CLU_ju #&)-048;@F   &,4;C L U _ ju chunky_png-1.3.15/spec/png_suite/filtering/f00n0g08.png0000755000175000017500000000047713766004353022024 0ustar danieldanielPNG  IHDR V%(IDATx/LBQsc77bXHfiT#Hl&bX 7s vsd:-ǧ׷fޗcC02CCpe. \B JPd"ܖ " sTRz"'!]8Rݖ9pg>7[m0EuʩPs"'}H '8JEXiVE?h$"ˋ?CCP75Cp`,%/}XPζIENDB`chunky_png-1.3.15/spec/png_suite/filtering/f01n0g08_reference.png0000644000175000017500000000072713766004353024036 0ustar danieldanielPNG  IHDR szzIDATx+AUd `,&fmV6mf2,` "뿬0xpNt>y>}p8h4xbrjzfvn~pxt|rzv~|ERNCI6%hZDlRFAI:%j4Ex j&<@R."<r\.S<@T⯈pK4&"Ef:P(TE>)"\SrM `fix 2 M `NiPہT*ES0$I""HPiCuEnob1J"F)8D Ps%P#@x%K>IENDB`chunky_png-1.3.15/spec/png_suite/filtering/f04n0g08_reference.png0000644000175000017500000000073013766004353024033 0ustar danieldanielPNG  IHDR szzIDATx+A"`,d3l6jml6dX,`A_Ea`x/X8 'of3z<ᐣшɄ锳ٌł咫Պ͆햻ݎÁ㑧Ӊ˅땷ۍǃ瓯Snt:J"n)@բ$f4 J"P)@V-\VE(NR8P.iP]3RDSDX)B5b:P(h>E> B5@..¿oA R d2"Tt4u#LHRELvd&o-$ ")G<+A 1O (%D"D SP(DID@+TtIENDB`chunky_png-1.3.15/spec/png_suite/filtering/f02n0g08_reference.rgba0000644000175000017500000001000013766004353024147 0ustar danieldanielvvvooovvvgggooovvv```gggooovvvYYY```gggooovvvRRRYYY```gggooovvvLLLRRRYYY```gggooovvvEEELLLRRRYYY```gggooovvv???EEELLLRRRYYY```gggooovvv:::???EEELLLRRRYYY```gggooovvv444:::???EEELLLRRRYYY```gggooovvv///444:::???EEELLLRRRYYY```gggooo***///444:::???EEELLLRRRYYY```gggooovvv&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooo!!!&&&***///444:::???EEELLLRRRYYY```!!!&&&***///444:::???EEELLLRRRooovvv!!!&&&***///444:::???EEE```gggooovvv !!!&&&***///444:::RRRYYY```gggooovvv !!!&&&***///vvv !!!&&&***ooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvvchunky_png-1.3.15/spec/png_suite/filtering/f03n0g08_reference.rgba0000644000175000017500000001000013766004353024150 0ustar danieldanielvvvooovvvgggooovvv```gggooovvvYYY```gggooovvvRRRYYY```gggooovvvLLLRRRYYY```gggooovvvEEELLLRRRYYY```gggooovvv???EEELLLRRRYYY```gggooovvv:::???EEELLLRRRYYY```gggooovvv444:::???EEELLLRRRYYY```gggooovvv///444:::???EEELLLRRRYYY```gggooovvv***///444:::???EEELLLRRRYYY```gggooovvv&&&***///444:::???EEELLLRRRYYY```gggooo!!!&&&***///444:::???EEELLLRRRYYY```ggg!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::LLLRRRYYY```gggooo !!!&&&***///444LLLRRRYYY``` !!!&&&***///444ooovvv !!!&&&***///444```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvvchunky_png-1.3.15/spec/png_suite/filtering/f02n0g08_reference.png0000644000175000017500000000073713766004353024040 0ustar danieldanielPNG  IHDR szzIDATx+A+"`,d3l6jm6d2Y,"AD׹xad\ugv_4|mv:v]z=}CF#cN&NSf3s. .KV+kn6n[v;{GN'g^.^Wn7w>j(@٤$FuJ"P(@Z$Je"{l(J4A EX,-B;l( tPjBݛ<"TT&\.GħP@6_0iQd2ЋdN N+Sw|C J BH&EcD6=@<)BhآFt1A --%iklX($P`0HI%xV$uW_IENDB`chunky_png-1.3.15/spec/png_suite/filtering/f01n0g08_reference.rgba0000644000175000017500000001000013766004353024146 0ustar danieldanielvvvooovvvgggooovvv```gggooovvvYYY```gggooovvvRRRYYY```gggooovvvLLLRRRYYY```gggooovvvEEELLLRRRYYY```gggooovvv???EEELLLRRRYYY```gggooovvv:::???EEELLLRRRYYY```gggooovvv444:::???EEELLLRRRYYY```gggooovvv///444:::???EEELLLRRRYYY```gggooo***///444:::???EEELLLRRRYYY```ggg&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooovvv!!!&&&***///444:::???EEELLLRRRYYY```gggooo!!!&&&***///444:::???EEELLLRRRYYY```ggg!!!&&&***///444:::???EEELLLRRRYYY```vvv!!!&&&***///444:::???EEELLLRRRYYYooovvv!!!&&&***///444:::???EEELLLRRRgggooovvv !!!&&&***///444:::???EEELLL```gggooovvv !!!&&&***///444ooovvv !!!&&&***///gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvvchunky_png-1.3.15/spec/png_suite/filtering/f02n2c08.rgba0000644000175000017500000001000013766004353022127 0ustar danieldaniel ')/179?AGJORWZ_bgjosw{&-4;'C/J7Q?XG`OgWn_ug|owſ7;?EKQW']/d7j?pGvO|W_gowǷͿMSX_dins'x/}7?GOW_gowƧ˯зտ`gov'.7?GNW_gnwŎɗ͟ѦծٷݿoxЁߋ'.7?GNW_gnwЇӎٟ֗ܦ|Лߧ'.7?GNW_gnwе'.7?GNW_gnw&^fnv~p{^fnv~Xu`is|&.6>^fnv~BjIuPX_hpy%-5=U]emu}0_5j:u@FLSZahp %-5MU]emu} U$_'j,u049>CHNTY`e jot%y-EMU]emu}LU_ju #&)-048;@F LRX%k=qEwM}U]emu}C L U _ ju ' .K4RCHNTY`ej   & , 4;CLU_ju #&)-048;@F   &,4;C L U _ ju chunky_png-1.3.15/spec/png_suite/filtering/f01n2c08_reference.rgba0000644000175000017500000001000013766004353024144 0ustar danieldaniel ')/179?AGJORWZ_bgjosw{&-4;'C/J7Q?XG`OgWn_ug|owſ7;?EKQW']/d7j?pGvO|W_gowǷͿMSX_dins'x/}7?GOW_gowƧ˯зտ`gov'.7?GNW_gnwŎɗ͟ѦծٷݿoxЁߋ'.7?GNW_gnwЇӎٟ֗ܦ|Лߧ'.7?GNW_gnwе'.7?GNW_gnw&.6NV^fnv~p{&FNV^fnv~Xu`is|>FNV^fnv~BjIuPX_hpy5=EMU]emu}0_5j:u@FLSZahp-5=EMU]emu} U$_'j,u049>CHN`e t%y-~5=EMU]emu}LU_ju #&)-048;@RX%^-d5k=qEwM}U]emu}C L U _ ju.5<$C,K4RCHNTY`ej   & , 4;CLU_ju #&)-048;@F   &,4;C L U _ ju chunky_png-1.3.15/spec/png_suite/filtering/f00n2c08_reference.rgba0000644000175000017500000001000013766004353024143 0ustar danieldaniel ')/179?AGJORWZ_bgjosw{&-4;'C/J7Q?XG`OgWn_ug|owſ7;?EKQW']/d7j?pGvO|W_gowǷͿMSX_dins'x/}7?GOW_gowƧ˯зտ`gov'.7?GNW_gnwŎɗ͟ѦծٷݿoxЁߋ'.7?GNW_gnwЇӎٟ֗ܦ|Лߧ'.7?GNW_gnwе'.7?GNW_gnw&^fnv~p{^fnv~Xu`is|&.6>^fnv~BjIuPX_hpy%-5=U]emu}0_5j:u@FLSZahp %-MU]emu} U$_'j,u049>CHN`e joEMU]emu}LU_ju #&)-0;@F k=qEwM}U]emu}C L U _ ju5K4RCHNTY`ej   & , 4;CLU_ju #&)-048;@F   &,4;C L U _ ju chunky_png-1.3.15/spec/png_suite/filtering/f02n0g08.png0000755000175000017500000000054313766004353022020 0ustar danieldanielPNG  IHDR V%(*IDATx/KQ8 A M,XI0 V `M`b,AA}_æ½pp:<&1"iDD1Zw{B1FDJA)JAP R4A`w{s=4Jtm}L\$ EDj@M'TR$z.BĢ ..k>PZdOb 31z]贘G1(q tڷOr|1:S! ?C+k[;{CAE  䗿IENDB`chunky_png-1.3.15/spec/png_suite/filtering/f03n2c08_reference.rgba0000644000175000017500000001000013766004353024146 0ustar danieldaniel ')/179?AGJORWZ_bgjosw{&-4;'C/J7Q?XG`OgWn_ug|owſ7;?EKQW']/d7j?pGvO|W_gowǷͿMSX_dins'x/}7?GOW_gowƧ˯зտ`gov'.7?GNW_gnwŎɗ͟ѦծٷݿoxЁߋ'.7?GNW_gnwЇӎٟ֗ܦ|Лߧ'.7?GNW_gnwе'.7?GNW_gnwnv~p{fnv~Xu`is|&.6V^fnv~BjIuPX_hpy %EMU]emu}0_5j:u@FLSZahpw 5=EMU]emu} U$_'j,u049>CHNTY`t%y-~5=EMU]emu}LU_ju #&)-048^-d5k=qEwM}U]emu}C L U _ ju ' C,K4RCHNTY`ej   & , 4;CLU_ju #&)-048;@F   &,4;C L U _ ju chunky_png-1.3.15/spec/png_suite/filtering/f00n0g08_reference.rgba0000644000175000017500000001000013766004353024145 0ustar danieldanielvvvooovvvgggooovvv```gggooovvvYYY```gggooovvvRRRYYY```gggooovvvLLLRRRYYY```gggooovvvEEELLLRRRYYY```gggooovvv???EEELLLRRRYYY```gggooovvv:::???EEELLLRRRYYY```gggooovvv444:::???EEELLLRRRYYY```gggooovvv///444:::???EEELLLRRRYYY```gggooo***///444:::???EEELLLRRRYYY```ggg&&&***///444:::???EEELLLRRRYYY```vvv!!!&&&***///444:::???EEELLLRRRYYYooovvv!!!&&&***///444:::???EEELLLRRRggg!!!&&&***///444:::???EEELLLvvv!!!&&&***///444:::???EEEgggooovvv!!!&&&***///444:::???YYY```gggooovvv!!!&&&***///444:::LLLRRRYYY```gggooo !!!&&&***///444LLLRRRYYY``` !!!&&&***///444ooovvv !!!&&&***///444```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvvchunky_png-1.3.15/spec/png_suite/filtering/f03n2c08.png0000755000175000017500000000241313766004353022015 0ustar danieldanielPNG  IHDR IDATx}]TeϼggZ,(BB2B*M  zxc~PwUwMW{m "^XvabE|}893a?Qr6WŊG7٧7m/ڗ^wډ7I-]!{S{=qž~S̱u/EP2E(B ƚ%Eȃ/)¨2a&$&c-ˈZFGawK$qIJ 3<{=<{xjxJ=Z5fLP9 0U~4 ߄-|fm@+uVJP_Ӻrό9q'JB i9 "Ok-E4Ҹ2mr n:(UB 칻ZLwW)40)t\+ T_?G٪{wGN)t йj^0~_j 4hL| sIE@ ͐mʢv;̣8R^j" RC6>|+WAp )O'-a_[>w(e5H5:϶yz8㖫$CykU樏G.% )xr|Ok5 }MR9?Hұ5`_€Q .i P7|Hrel$Л\$XT)3T%?qoٿ*V%J(Kq Oq/nB]g_`GR|%mʹbZ2w̛@{?X&^~ MTPIF$->Ү|#0M&ؑd'Q($4 0Scs:yꆼU]Mj 0WGɱ٦o l` k;4 Xͽ {vG!@,'$iKqOaHOv}8Xg DBs96)j2"4~, *ڧ30ClZ(cՂU@ \Ҍivݙ!)̓r+LV71 jIENDB`chunky_png-1.3.15/spec/png_suite/filtering/f00n2c08_reference.png0000644000175000017500000000462113766004353024030 0ustar danieldanielPNG  IHDR szz XIDATxڅ{PTsvDEu!nc #:6BA͈ ˰HG&T륵1f-jeژET.VSv&(20>CFMJ-%s_&NϒHoIbtrTVڴE.%5NK/I_[R]zH#E "0N.ALDE @X [u 8 q- #]rxK &b p(D9Jk@V @a~@42f @o  @" Xjb  vhAqYA\ & oF(P`qB#wTX Z%! v9 /~'>Nhws h9I _ W @lD-/: b/ 8 ? N8D_ + hqDK26VXb+] 8  N8 ,FA4"K .; AmwA9]D= hq D n -Aq7 @DD;{ > /A<:)㱪**ߦ@`0BP;^tԐ~JIbJ>$~C$>QRa2+A D= h ߗD6! > CF~E (Qb j@ԁ ē>guNc;}%+_hVAas[ W+,XTPm%>[.OЦ![GzAGôOt@?Sɯ::Q .*ddb A䂘bA,Q| X8&OV`&Aqğ@qi { 2@d b,   5zD5m D} 1Fb$ R@"`@d.1Ϙ_Xb9Jk@!ޭQˆr"DT|A$鵨ґ 5hUZ)(v$yOV4Je9'[LaxQ1itDQ@2C CTɬW(TBߙ 9^0ts  2;^܎MWh1  zAę0]."D=A b! @|A WA(D A b3 ~O=0&FXA@D"DD@HD:y 1 r@ b9 ^D % *@' 0pd , l "AD "DD@n(d "DlcA@L1LCLyvBO LV6 "ADDx]ᆂ"D 4v@ b,?b| |@ LV66 "Atq ` b@Ă"Dw z"D Ti ҿZIENDB`chunky_png-1.3.15/spec/png_suite/filtering/f01n2c08_reference.png0000644000175000017500000000471113766004353024031 0ustar danieldanielPNG  IHDR szz IDATxڅ{P U!6HhT@2t* e 1"Z*'TXVRc6f/T|]g̴3ea=52aRX)&AJ+9ҤägH/,M"M%Η,<ҲRbyTCڵ[D:pX:zL:yZj ],}nI#L D* N.nALD! J@XFu >qQ'AEWAGduSb q &b9 (Q D9u փbu 8/ A\qD36 w^ < Xb!bK@,Qb-AlD5Aԃ@xAq%WA4 .[2ނ C_@iH95 *@i b'][ BS8tD#Q|/wSy ~EvyxD vQ   8  ΂hq D3 @Lm] A|b 8 8 N8F@4 ;k Ah6 A@qL{vnQ b hqD  ZA  ; "=q߃ ʦZ@xCQ4M~@R HƘǑ@uR6YM@#kB tIhM@v{@qDfAs[dv  :@q}@<= L^U (Qb Aԁh/Aѵx0| +_ -f- ZA .3c+ *Ҫ*(jiצwATkBx05B>8}W\y5AQ^'A8r-" D:LNY ^1d@31OQR AM @lAԃ0W|X"D@ "D&'̄ӕ< ~b1 A!@Tu Lۢp "ADy,.(DO j`U"$M~RNӊlFkJ$9AkzQfjRAQMz.#\O<ȺT*RKl^#'!Rab $Bp@ Y^0t! 2;Ce:@D$h=@ Dx t "D'7&"DB A(*ƧwqFq"DdYV,Ud!tY:րK6Qw=x4KC)," v\vϴN3;ˡhi  b$A}"\ @,Q b A> re5=dR)Nq']) H1=.b q r@LX  ր&[Al.u ;@$x  C@P7~mAp b@ĂH 4 "Sy >>]V6v! BADh='D"$)  H1DLN#A@d 0 DP\ @DD,8A$H "C75#&xIENDB`chunky_png-1.3.15/spec/png_suite/filtering/f04n0g08.rgba0000644000175000017500000001000013766004353022133 0ustar danieldanielvvvooovvvgggooovvv```gggooovvvYYY```gggooovvvRRRYYY```gggooovvvLLLRRRYYY```gggooovvvEEELLLRRRYYY```gggooovvv???EEELLLRRRYYY```gggooovvv:::???EEELLLRRRYYY```gggooovvv444:::???EEELLLRRRYYY```gggooovvv///444:::???EEELLLRRRYYY```gggooovvv***///444:::???EEELLLRRRYYY```gggooovvv&&&***///444:::???EEELLLRRRYYY```ggg!!!&&&***///444:::???EEELLLRRRYYY```vvv!!!&&&***///444:::???EEELLLRRRooovvv!!!&&&***///444:::???EEELLL```gggooovvv!!!&&&***///444:::???EEEYYY```gggooo!!!&&&***///444:::???!!!&&&***///444::: !!!&&&***///444:::???EEELLLRRRYYYooovvv !!!&&&***///444:::???EEELLLRRRgggooovvv !!!&&&***///444:::???EEELLL```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvv !!!&&&***///444:::???EEELLLRRRYYY```gggooovvvchunky_png-1.3.15/spec/png_suite/filtering/f00n0g08_reference.png0000644000175000017500000000077413766004353024037 0ustar danieldanielPNG  IHDR szzIDATx-1D `,&fmVh]L&b1X `;ن؞O}CF#cdtl|brjzfvn~>z]"vNCmDj%f.h4t@^)r Ph EZR!JB]ݤ(EU10)&Ju" X`Ln"u"@.&WBg;a2'穋qȝ& \vdb"R &cL9fNHE9@*.Bށ+<2#LWH$A|E!X,FDQD %D@\!W[`ŏFIENDB`chunky_png-1.3.15/spec/png_suite/filtering/f02n2c08.png0000755000175000017500000000330113766004353022011 0ustar danieldanielPNG  IHDR IDATxmI$yoD-YKWee xƓ s@#xƃ0" (*"҈=ݵuUU&eAjҞPRd91aTI)5aXL+Fxx\7enhƸ}Lʢ62+/PDa\KAe@(8sIs qU%2Wm:!UQeD))C&5k,xs[T/&\K\{pN`Wσ1+l)LxƄQaDÝ뱭w,;{q7Mt FO|Azz:ԡ-4AԣvZlĶeǛmWfy>ohy2DR:AO+16=MlemĶ쁳EEyOlnB79D#9FW-4z6Gj6cϑpײ*3%%sp&aKpmϱ(pвݘ-gV~jv݊)&Rz!6So'ގ%4[^l;ζm:[ ,X)HlW#PTL|o-o.)8iGqь{uHGg6[m~.~?7i7iæ5d=_0?IR'iК&x[]{ r>@Btttؤf2gLX2iɔX!.y,'͑ Jqie<z<}B ,;{3:rwBnNN,' -KQXq©pzv[llĬ98;bEAQyNfV|JK'DmN~ TªcrԿC(*ۡӥWfvJ'~ TºcHV5d +Kѡۥ2Q$+^B T+*abts:9yF^VӒ1{1;-Ǧc-C>We==IENDB`chunky_png-1.3.15/spec/png_suite/sizes/0000755000175000017500000000000013766004353017313 5ustar danieldanielchunky_png-1.3.15/spec/png_suite/sizes/s01i3p01.png0000755000175000017500000000016113766004353021202 0ustar danieldanielPNG  IHDRRf\gAMA1_sBITwPLTExW IDATxc`HqIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s05n3p02.png0000755000175000017500000000020113766004353021207 0ustar danieldanielPNG  IHDRvgAMA1_sBITw PLTEwAsIDATxcX0a"\*JIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s34i3p04.png0000755000175000017500000000053513766004353021220 0ustar danieldanielPNG  IHDR""n&ggAMA1_sBITw'PLTEwwwwww;IDATx} 0Fj[tdAk[]:8;9 jD{- q>oĻ$`*1sPxKE= "aXIDo-MBC:e֖)Zir450NRk[]IldnaȕGR[_{Q_E6bp&],P]I4?t3P]jP33/2Z߈)IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s09i3p02.png0000755000175000017500000000022313766004353021212 0ustar danieldanielPNG  IHDR gAMA1_sBITw PLTEwwVd#IDATxc`@ X ^3\b  duFIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s38n3p04.png0000755000175000017500000000036513766004353021232 0ustar danieldanielPNG  IHDR&&ZgAMA1_sBITw'PLTEwwwwww;jIDATxc`eG`$61 !fpCCl* L1lvX*`q*b 0h-܌81 bhb)`@da1-i+άDIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s07n3p02.png0000755000175000017500000000021213766004353021213 0ustar danieldanielPNG  IHDR<@gAMA1_sBITw PLTEwwCIDATxcj í9 7H qy ]mIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s38i3p04.png0000755000175000017500000000054513766004353021225 0ustar danieldanielPNG  IHDR&&- gAMA1_sBITw'PLTEwwwwww;IDATx? @XA_EZF6i4475 :E@Hީn#9SЎOQbdTw)q!o;s+;qZIc|jb[P4i|aljƕAn6 S4zg4Xn8m uM/$yC;+|<ױ;^y}kvob땗){zB|DyEIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s07i3p02.png0000755000175000017500000000022513766004353021212 0ustar danieldanielPNG  IHDR;gAMA1_sBITw PLTEwwC%IDATxc8p 3aH? Wcn UIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s04n3p01.png0000755000175000017500000000017113766004353021213 0ustar danieldanielPNG  IHDR? =gAMA1_sBITwPLTEwCIDATxc0?  IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s39n3p04.png0000755000175000017500000000054013766004353021226 0ustar danieldanielPNG  IHDR''~LgAMA1_sBITw'PLTEwwwwww;IDATxm!@GHzp 8,šW1CufICgf`2(6 'Zж =E'̪ 'QuEyJڟd0`7~mdUg/C>d(U>ѱb'LWtec+`:4nh7zob/ Ǫ O/5+IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s04i3p01.png0000755000175000017500000000017613766004353021213 0ustar danieldanielPNG  IHDR8<gAMA1_sBITwPLTEwCIDATxch`  >7IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s08i3p02.png0000755000175000017500000000022513766004353021213 0ustar danieldanielPNG  IHDRffgAMA1_sBITw PLTEwwY%IDATxc` ~`Ǡ0aXAAPPpJIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s06n3p02.png0000755000175000017500000000020313766004353021212 0ustar danieldanielPNG  IHDRgAMA1_sBITw PLTEwEhIDATxcXaj02V-9_)pIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s39i3p04.png0000755000175000017500000000064413766004353021226 0ustar danieldanielPNG  IHDR'' |gAMA1_sBITw'PLTEwwwwww;IDATxm1r0EǓ N."pʵ;ZJnQS:TV1/{f3T` Di@QhjZjvJJ85=P*0 zS`9!T5k=- K#<͚_.ܥ?Nubo&8:/G„"L,B MǔvkNhy>xxppWpX9çe(wx:Ι4e5߹?N|.@ny 5 IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s02n3p01.png0000755000175000017500000000016313766004353021212 0ustar danieldanielPNG  IHDRHxggAMA1_sBITwPLTE\/% IDATxc```8UIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s37i3p04.png0000755000175000017500000000061113766004353021216 0ustar danieldanielPNG  IHDR%%@ gAMA1_sBITw'PLTEwwwwww;IDATxe;0Q NL.`h쭨hSRڥr(70̲˷`FCj} |n(`,,䜖jK)#)(8n P M$0 6!a?ؑ\Zs!*V}6nJ_Dؐ w9)/ggL x q%x \r/Rx [JsFNk-8F߮Rz'#?IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s37n3p04.png0000755000175000017500000000052013766004353021222 0ustar danieldanielPNG  IHDR%%7=0gAMA1_sBITw'PLTEwwwwww;IDATxe-@/M' '؄ z Vp)3x3@|Iz#S<#u*dUa02qEKϾ,27Fxlj<ɹ)Pz?KwWs:kPѤW2 -W]s' jeG KޭۯG|QV IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s06i3p02.png0000755000175000017500000000021713766004353021212 0ustar danieldanielPNG  IHDR7MgAMA1_sBITw PLTEwEh"IDATxch`h` - +<"&0LH`XYLe$IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s33n3p04.png0000755000175000017500000000051113766004353021216 0ustar danieldanielPNG  IHDR!!\gAMA1_sBITw'PLTEwwwwww;IDATx]- W7pp ]WFp- +ƒ|Jop .[+j1Oe ]lD78Z;W갮kLWă⑻E=qx6U3QkT.:])T:IpPM#I?xxIJAhjW :M\ɩ]3b֓1%IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s33i3p04.png0000755000175000017500000000060113766004353021211 0ustar danieldanielPNG  IHDR!!gAMA1_sBITw'PLTEwwwwww;IDATx]1r0E?0(xFHܸSKε+4>y˒Vo 4Ĺp:4a`5$9k L~FQZ #J3^K?Z]T4Z?&ikEJ 3) ]aVYEh-a3Fު`Y\Ռ:T4b2(h%sn݆oEd=d2ܙBcF5μWhQd *F&Ny7zAwe\/x7rL#"ckLiq6>G΢HIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s35i3p04.png0000755000175000017500000000061713766004353021222 0ustar danieldanielPNG  IHDR##gAMA1_sBITw'PLTEwwwwww;IDATxe=PQv@Xـ  zs;&}!rBї 8ycec:1 4;0'9]L(PFڄ唪*1j1-FtH?׹HD"nƮMM;-ww+T0q=މ^fBu/ep cA%q)uMa8VFL`?Ha; L[ΉqЊiIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s01n3p01.png0000755000175000017500000000016113766004353021207 0ustar danieldanielPNG  IHDR%VgAMA1_sBITwPLTExW IDATxc`HqIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s32i3p04.png0000755000175000017500000000054313766004353021215 0ustar danieldanielPNG  IHDR SWQgAMA1_sBITw'PLTEwwwwww;IDATx}0F?E~S׆$F:5q,섁3$XV)16͗ù6H^|GO6vjPhq SXL!0Z(-`FUʂ $Lz*>=& r%@=E"t6;s2 q2wzE¥0l%=!޲Z;7R҄#IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s40i3p04.png0000755000175000017500000000054513766004353021216 0ustar danieldanielPNG  IHDR(( וgAMA1_sBITw'PLTEwwwwww;IDATx 0EZk]dRPN7upqg'B YP1BCᐼ<DZrQeA|$`O sc#WLYDOLILȾ{aWvD( cNٮaXDCapl5pQDV @"$֓I#U&!AHt xnR=͈ki^ jNp{xIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s02i3p01.png0000755000175000017500000000016213766004353021204 0ustar danieldanielPNG  IHDR?gAMA1_sBITwPLTE\/% IDATxc`gIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s05i3p02.png0000755000175000017500000000020613766004353021207 0ustar danieldanielPNG  IHDRgAMA1_sBITw PLTEwAsIDATxch`h`X";tOm"Mp$FEIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s08n3p02.png0000755000175000017500000000021313766004353021215 0ustar danieldanielPNG  IHDRaVgAMA1_sBITw PLTEwwYIDATxc```ZA =V=LIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s34n3p04.png0000755000175000017500000000037013766004353021222 0ustar danieldanielPNG  IHDR""igAMA1_sBITw'PLTEwwwwww;mIDATxϱ _ [2г0&G+.%:r*Hv4,$ĂY49n2=xNNm'c)*+ TxVR,v5H\-Y~H:+NIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s36i3p04.png0000755000175000017500000000054413766004353021222 0ustar danieldanielPNG  IHDR$$d)=gAMA1_sBITw'PLTEwwwwww;IDATx 0F?ӷ"]IQv iwqpvrTjbMI xA(;ވVLIFqxfSB0'zWs%Ñm rtȁ \Ap9*e} u} GT_`-U$ڪz3Q?, 0h+8郱oe(8Eb_zةj ,a*muuZ9?.IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s09n3p02.png0000755000175000017500000000021713766004353021222 0ustar danieldanielPNG  IHDR gAMA1_sBITw PLTEwwVdIDATxc` V,XB q iejC#IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s03i3p01.png0000755000175000017500000000016613766004353021211 0ustar danieldanielPNG  IHDRjgAMA1_sBITwPLTEw奟 IDATxc`LAIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s32n3p04.png0000755000175000017500000000040713766004353021221 0ustar danieldanielPNG  IHDR TggAMA1_sBITw'PLTEwwwwww;|IDATxѽ Wq zZ6w +'a0MHR\GrvX41$TxAsŖ?hnsof]A[ $ \V~01YB6ipaHz&.IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s40n3p04.png0000755000175000017500000000040013766004353021211 0ustar danieldanielPNG  IHDR((~Х^gAMA1_sBITw'PLTEwwwwww;uIDATxб 0 DC,!alE6H%2EHVNRbG@!FeQk7^ Bؚp877*,mWBMwG)Rc!wu'gWpH])IENDB`chunky_png-1.3.15/spec/png_suite/sizes/s03n3p01.png0000755000175000017500000000017013766004353021211 0ustar danieldanielPNG  IHDRl'gAMA1_sBITwPLTEw奟IDATxc``p``A91 KIENDB`chunky_png-1.3.15/spec/png_suite/sizes/s36n3p04.png0000755000175000017500000000040213766004353021220 0ustar danieldanielPNG  IHDR$$.gAMA1_sBITw'PLTEwwwwww;wIDATxc`eG$! .dp]]h*t !,[*`:B,!,Bl7݅,NEPWB BB Є$BB62`XoIӑIENDB`chunky_png-1.3.15/spec/png_suite/background_chunks/0000755000175000017500000000000013766004353021650 5ustar danieldanielchunky_png-1.3.15/spec/png_suite/background_chunks/bgai4a16.png0000755000175000017500000000544713766004353023671 0ustar danieldanielPNG  IHDR ^gAMA1_ IDATxpTu?Z=IowB2O߄5f pњf4b8O5vH\ Cc7Ԯ#-+06P ЂzW+tOL2m%K,: 뻇oތ! ~>6K2m4?)u7,㍝4ueqCYcgg' i!lB4M\x2s&߆OiҮC-hDýgt<Kzz)]}?30.W\ZQKiپnp7Bn(A ;`n~"OÎ, :X@apy(b.7 9-#X1s;WhPfS!6ئrͬ’(T(o+ܟ| KWcf޶܀ fC>A y(kfPEMpx4t! ٟ%kx370%8$NA-!xzX!jl5+ .S3;-[W~u>Gtu,_WՁS7_ի:aTnZM߳0ctµvx,jq(VAyOjoBe-oA>k+?3V̮ū!xS [3O01 'wj칷d/SU槜wpx6kfi]^.#jkFS dF0,~Fef[7}^X݆@-xOC^#AQA ̋ɇ2B*Kik * \,koBZJ<w||&f(o@)Lirm^[Я.OFJ[_ye_uJȿ:)d!?eC;2zFW^'d2)d2#z3\8MoU(P̮Yӗ,Q lȞuN]s#ǯ\++rB|\H!e7n5]߷#BR r ,9gG$  ; &(\ ?k%{M+0lrow[O2Ip An;nC[0c5 {r Fچl늺fVXH] Y6_{6 Y-%B !?2uHM̋B45VL? cp>f֖ܷfEW!+7oB`30~&k74z<©̋uC_?0/MoB3 cV2|*( aHA$aAy٫.}fs]yu]uN΁Uh = =Ms>=ㅢ0%-C/ SRRaHv {a`z>C0EPs ʜGB't:p4t@IBK,# )ȏ7 _MHoFH,k*nvR3΅!nw!WIЅQ2 o? 7O0`7ȓ0eHTlͷ5 ƺ ?@w.3`>aoW phJς~ CoA&A>cQHoR): ӡ\^7_cbtƮSQr '!<PoA^r]P+a"0$L0d2&ȝ0`pttͥ4Wce_׋ȺYqa@f3dR 0\N~ UTcZ$XCϙ!C(9q>3Z_3=g@ BޛW;I`RU}]N;ծTC.(σ@3;`|ۡh thA ϙHZ7A4۞$lZHxjks# !(M@LO@ ,{022 ' }g|P\_{@MLG25 >i!'505-0e 䅜Bk)F[a"J+[4Bo+PA(x|%EqXk@G>3c٤ehu![HPSB 9!! 9uyBROH)!}B?+Bz]H6!f9z@ȱ+*!'~ $_B*T2gys >!H{I9lIENDB`chunky_png-1.3.15/spec/png_suite/background_chunks/bgan6a16.png0000755000175000017500000000655313766004353023677 0ustar danieldanielPNG  IHDR #ꦷgAMA1_ "IDATxݙ_luKrM.ZKʴRM Y.R2Z[Ɋ^qb,ZɊ (0PrB&\)(D~D4KK/9wUU4AΝ;ww|猕i |ؘ}}+M!3߀ޱ&}߯ZfF5[,@f.3rZtL017qHÿto Z?TR4٢:O~-{D`kEU4")sZHfn2@6RtXO)ޯ;voR'zֽO۴=5ߣDOqD Oc3`co47e k$9SQ aȗ~U5`:OE5CJ :u'|VMt?\3%'*JS_7W&; 42@THtZ]m'Ի6tmrwK'Z׉^щ~QCbV b^NJ^Jr宁Z S _XfxNEOfͅz-/?ջu5Dp^vGVˏGW5| %˿\5ėf%XYhNf uSSn6Ys;;:p-'AW:UGpxqB?7GsZ g`/f@%6mW#r?zf7k.{zzz _NF٪ At5Huta>4u-⋋=wTK\*< e7 M@x޶"&nܛ9f5|>IEtSqEUvvhƫ ^R\TjVz=XtodɎap~~ p}c ׍{3lvB}ކ|9:^TyJz5%WkFƫ%X go|h7#aL$7u)7ݬP?*>WU4^Y+/WZO?{1+^ |{ w u'0oh5HM@3~ݸ7sf7k.7e-/NK?RxZ s'@/eAY@ [?p| HQtu yfKϼQ-ѪHM@3~ݸ7sf7k.ԇ _|IV?4ħځN,'[3kXf|>o$_NY%an }MEFj0&fqo攛n\~|[~|!~ݻj%VyN&#āHFq@ ҊU;2t`@2zKoT۵3=-nj7#aL$7Vlvkeݻμ K@pR (#8grRKX4L;V݌14׍{3\67aZi.3o~`i޻ {pi 3 =)opQqdv%)3{>5XaXiH4ь̤+[h7#aL$׀fiZV!_ Cɽ?x~;w1[o zDE3ؓI~]Rl~%yZ+1S5ISZj݈VF˜H.~xVv: N]mx⁍/43}Ȉ c'ݛ l/Ib*aS1Q&1%?3j݈VD\9߭;wR'3j=K3Y ҕx{vi 1\[SΦx>lOcJF ޒ4ŨuWGjO9hn$83*}}§Rw~;@4k7syv|aE%aL>  j0&3gƫˁȽ_BAqޓ=vA&folnon4|6jd.dndoGq?eí۵IENDB`chunky_png-1.3.15/spec/png_suite/background_chunks/bgwn6a08.png0000755000175000017500000000031213766004353023711 0ustar danieldanielPNG  IHDR szzgAMA1_bKGDoIDATx1 0 F'dhO?U!ExRP(M(ي0^{~3uG XN5"}\TB\.y 6{@<P6@R LIENDB`chunky_png-1.3.15/spec/png_suite/background_chunks/bgan6a08.png0000755000175000017500000000027013766004353023666 0ustar danieldanielPNG  IHDR szzgAMA1_oIDATx1 0 F'dhO?U!ExRP(M(ي0^{~3uG XN5"}\TB\.y 6{@<P6@R LIENDB`chunky_png-1.3.15/spec/png_suite/background_chunks/bgbn4a08.png0000755000175000017500000000021413766004353023663 0ustar danieldanielPNG  IHDR sgAMA1_bKGD#25IDATxch41",(,?a0T1`4GÀ*hP* }IENDB`chunky_png-1.3.15/spec/png_suite/background_chunks/bgai4a08.png0000755000175000017500000000032613766004353023661 0ustar danieldanielPNG  IHDR tgAMA1_IDATx 0 ֽA((sOAVG":ݙbH$ @ɾzM2x<7U[0t<n!Y~.,>RfqXAh٪wϤ50o0N N6 O= YU]IENDB`chunky_png-1.3.15/spec/png_suite/background_chunks/bgyn6a16.png0000755000175000017500000000657513766004353023733 0ustar danieldanielPNG  IHDR #ꦷgAMA1_bKGD~# "IDATxݙ_luKrM.ZKʴRM Y.R2Z[Ɋ^qb,ZɊ (0PrB&\)(D~D4KK/9wUU4AΝ;ww|猕i |ؘ}}+M!3߀ޱ&}߯ZfF5[,@f.3rZtL017qHÿto Z?TR4٢:O~-{D`kEU4")sZHfn2@6RtXO)ޯ;voR'zֽO۴=5ߣDOqD Oc3`co47e k$9SQ aȗ~U5`:OE5CJ :u'|VMt?\3%'*JS_7W&; 42@THtZ]m'Ի6tmrwK'Z׉^щ~QCbV b^NJ^Jr宁Z S _XfxNEOfͅz-/?ջu5Dp^vGVˏGW5| %˿\5ėf%XYhNf uSSn6Ys;;:p-'AW:UGpxqB?7GsZ g`/f@%6mW#r?zf7k.{zzz _NF٪ At5Huta>4u-⋋=wTK\*< e7 M@x޶"&nܛ9f5|>IEtSqEUvvhƫ ^R\TjVz=XtodɎap~~ p}c ׍{3lvB}ކ|9:^TyJz5%WkFƫ%X go|h7#aL$7u)7ݬP?*>WU4^Y+/WZO?{1+^ |{ w u'0oh5HM@3~ݸ7sf7k.7e-/NK?RxZ s'@/eAY@ [?p| HQtu yfKϼQ-ѪHM@3~ݸ7sf7k.ԇ _|IV?4ħځN,'[3kXf|>o$_NY%an }MEFj0&fqo攛n\~|[~|!~ݻj%VyN&#āHFq@ ҊU;2t`@2zKoT۵3=-nj7#aL$7Vlvkeݻμ K@pR (#8grRKX4L;V݌14׍{3\67aZi.3o~`i޻ {pi 3 =)opQqdv%)3{>5XaXiH4ь̤+[h7#aL$׀fiZV!_ Cɽ?x~;w1[o zDE3ؓI~]Rl~%yZ+1S5ISZj݈VF˜H.~xVv: N]mx⁍/43}Ȉ c'ݛ l/Ib*aS1Q&1%?3j݈VD\9߭;wR'3j=K3Y ҕx{vi 1\[SΦx>lOcJF ޒ4ŨuWGjO9hn$83*}}§Rw~;@4k7syv|aE%aL>  j0&3gƫˁȽ_BAqޓ=vA&folnon4|6jd.dndoGq?eí۵IENDB`chunky_png-1.3.15/spec/png_suite/background_chunks/bggn4a16.png0000755000175000017500000000425413766004353023677 0ustar danieldanielPNG  IHDR n<gAMA1_bKGD )UIDATxŗ_h[cHuRGT(8B-P.%4BiKaw]HɐXf%$zmiJWGc9w~t5O>>`eaX=`Ԃ±8G?]e _^X.wGO3wd3 gZú|'fef[Z!p&ކuh`c:oȇtri^g)X]UVZ l-5C`T$u!Z??p߅Cgj@dƱt-YY˅ae}';Ȁ~B@3 x'͌B ^SA#nr2r:QfGR`fAdv <@_"AÃևðqXZ3S:܀{Ճ< ';B 1/s,ZYߣ` `pg=U<@#뿃נ/`6,њ̀U]轏@`7&kc{ ? %:4o6JވaKGaɄ ? @= !e/sVVCpA5AhΠ.Ak%,?%!V[?hv@[/  !~Q`ZJHV@ס, \Z-tVKuVai *(a0ס  =i0Vކ.a{hQf\5 |ee8 l9QF haeXC9wnA D`6_Bh^SygLgv]~dA_ lwSR; I[VF&;5l :F$g 8h=ox yot| 'Q2 ,44c94O!1DӲͷ+3߲M:$m+?=*|~5{Wb zC`W݄辥< Cu㰈"j Q}8p|$;>.;/؏a 4/@ н[/E})x6훏m^=t)y#!ËGT/So SMO]]#7*gǏl!qXkdQa59G2 5e5$l (+畸2ۥ_$`Wb?@0 9E{Bk80/ƲVRZYxYf\ Nv{k By_@SaJcKjT𔥞cVE^>[v]7R>܉\i@)]SBBy$Ғh('eϭia;cҹ; МkX]HM%I%ŎUz]„Xj"22CO+h ɴ )oF\ܼ27 h+' wCQRY\-4-dA.Hax4,@ᜲ$o{ dJ;v,& C˧T$3ӔgVWsϓ<yhB# FP!v Vޔl X$p]y]\._s=,l7Gd){&TLK(}Z)h+V %@{gd"iiqૢ]Z^<y wPg+ЮB;  s%/ڟ gU<=DPw?({!l7d̀t:9-xIb z 4thFk2*<#~WQ\ 4eύpNf.J,OAdžN 6lf-ȌcGR3*%dE((\0){n$KJ3`38`ek+-w53&7yiIqW$n(8yKr-"ˡ %PzPE:wNh~uJLН#uOE" m(t:][ZWXtF]]Tҗ^}@'#si" |A~UUNn1R,IENDB`chunky_png-1.3.15/spec/png_suite/compression_levels/z03n2c08.png0000755000175000017500000000035013766004353023767 0ustar danieldanielPNG  IHDR IDATx^@0 J\l" [ןED=SJSf^տ> xЀ(9CNщ֠ .Gtx_AC:$%tgcݳ"n]ZWXtyF]؁]ȥ:Й1zd\ROx DWUNnCIENDB`chunky_png-1.3.15/spec/png_suite/compression_levels/z09n2c08.png0000755000175000017500000000034013766004353023774 0ustar danieldanielPNG  IHDR IDATxڵK Кv1~a>W$n(8yKr-"ˡ %PzPE:wNh~uJLН#uOE" m(t:][ZWXtF]]Tҗ^}@'#si" |A~UUNnIENDB`chunky_png-1.3.15/docs/0000755000175000017500000000000013766004353014157 5ustar danieldanielchunky_png-1.3.15/docs/_config.yml0000644000175000017500000000031413766004353016304 0ustar danieldanieltheme: jekyll-theme-slate title: ChunkyPNG email: willem@vanbergen.org description: Read/write access to PNG images in pure Ruby. url: "https://www.chunkypng.com" highlighter: rouge show_downloads: truechunky_png-1.3.15/docs/_posts/0000755000175000017500000000000013766004353015466 5ustar danieldanielchunky_png-1.3.15/docs/_posts/2014-11-07-the-value-of-a-pure-ruby-library.md0000644000175000017500000001764213766004353025155 0ustar danieldaniel--- author: Willem van Bergen title: The value of a pure Ruby library --- In late 2009, my employer at the time — [Floorplanner](https://www.floorplanner.com) — was struggling with memory leaks in [RMagick](https://www.imagemagick.org/RMagick/doc/), a Ruby wrapper around the image manipulation library [ImageMagick](https://www.imagemagick.org/). Because we only needed a small subset of RMagick's functionality, I decided to write a simple library so we could get rid of RMagick. Not much later, [ChunkyPNG was born](https://github.com/wvanbergen/chunky_png/commit/aa8a9378eedfc02aa1d0d1e05c313badc76594a7). Even though ChunkyPNG has grown in scope and complexity to cover the entire PNG standard, it still is a "pure Ruby" library: all of the code is Ruby, and it doesn't have any dependencies besides Ruby itself. Initially, this was purely for practical reasons: I knew Ruby wasn't the fastest language in the world, but I had no idea how to write Ruby C extensions. Performance was not an important concern for the problem at hand, and maybe RMagick being a C extension was the cause of its memory leaks? By writing pure Ruby, I could get results faster and let the Ruby interpreter do the hard work of managing memory for me. [1] ### Performance becomes important Mostly as a learning project, I ended up implementing the entire PNG standard. This made the library suitable for a broader set of problems, and more people started using it. Performance then became more important. I put a decent effort into optimizing the memory efficiency by [optimizing storing pixels in memory]({% post_url 2010-01-14-memory-efficiency-when-using-ruby %}), and I boosted performance by [short-circuiting the PNG encoding routine using Array#pack]({% post_url 2010-01-17-ode-to-array-pack-and-string-unpack %}). Even though these efforts resulted in sizable improvements, it became clear that there are limits on how far you can push performance in Ruby. The fact that I am implementing a library that by nature requires a lot of memory and computation is not going to change. So what are the options? I could recommend RMagick to people asking for more performance, but that is not going to happen after all my ImageMagick bashing. [2] In the end, I had to roll up my sleeves and program some C. ### Being pure Ruby is a feature To tackle the performance issue, I had the options of either implementing the C extension as part of ChunkyPNG, or build a separate library. [3] My initial gut feeling was to add a C extension to ChunkyPNG to give everyone a free performance boost. However, I soon discovered many people were using the library *because* it was pure Ruby. For me, it was a pragmatic implementation detail; for them, it was a feature. Including a C extension would require everybody that wants to install ChunkyPNG to have a compiler toolchain installed. For me, installing a compiler toolchain is the first thing I do when I get a new machine. This is true for many Ruby developers, but it turns out that many of the library users are not Ruby developers at all. [Compass](http://compass-style.org/), a popular CSS authoring framework, uses ChunkyPNG to generate sprite images. Most Compass users are front-end developers who primarily use HTML, CSS and Javascript, and not Ruby. Because OS X comes with Ruby and Rubygems installed, running `gem install compass` works out of the box. Telling them to install a C compiler chain is simply an unacceptable installation requirement. There are a couple of additional advantages of being a pure Ruby library. As an open source project ChunkyPNG can attract more contributors, because only a small percentage of Ruby developers are well-versed in C. Moreover, C extensions are MRI specific. This means that many C extensions won't work on Rubinius or JRuby, and I wanted my library to work in these environments as well. [4] Finally, libraries that require a C compiler inevitably get a lot of bug reports or support requests of people that are having issues installing the library, because of differences in development environments. [5] ### OilyPNG: a mixin library So instead of adding a C extension, I started working on a separate library: [OilyPNG](https://github.com/wvanbergen/oily_png). Rather than making this a standalone library, I designed it to be a mixin module that depends on ChunkyPNG. The approach is simple: OilyPNG consists of modules that implement some of the methods of ChunkyPNG in C. When OilyPNG is loaded with `require 'oily_png'`, it first loads ChunkyPNG and uses `Module#include` and `Module#extend` to [overwrite some methods in ChunkyPNG with OilyPNG's faster implementation](https://github.com/wvanbergen/oily_png/blob/master/lib/oily_png.rb). This approach allows us to keep ChunkyPNG pure Ruby, and make OilyPNG 100% API compatible with ChunkyPNG. It is even possible to make OilyPNG optional in your project: {% highlight ruby %} begin require 'oily_png' rescue LoadError require 'chunky_png' end {% endhighlight %} This approach has some other advantages as well. Instead of having to implement everything at once to get to a library that implements most of ChunkyPNG, we can do this step by step while always providing 100% functional parity. Profile ChunkyPNG to find a slow method, implement it in OilyPNG, and iterate. This way OilyPNG doesn't suffer from a bootstrapping problem of having to implement and minimum viable subset of ChunkyPNG right from the start. It can grow organically, one optimized method at the time. And because we have a well tested, pure Ruby implementation available to which OilyPNG is supposed to be 100% compatible, testing OilyPNG is simple. We just call a method on ChunkyPNG, run the exact same call on an OilyPNG-enhanced ChunkyPNG, and compare the results. ### To conclude Being pure Ruby can be an important feature of a library for many of its users. Don't give it up too easily, even though Ruby's lacking performance may be an issue. Using a hybrid approach of a pure Ruby library with a native companion library is a great way to have the best of both worlds. [6] --------------------------------------- #### Footnotes 1. This is also why I avoided using the [png gem](https://github.com/seattlerb/png), an "almost-pure-ruby" library that was available at the time. It uses [inline C](https://github.com/seattlerb/rubyinline) to speed up some of the algorithms. 2. Disclaimer: I should note that I haven't used ImageMagick and RMagick since 2010. So my knowledge about the current state of these libraries is extremely outdated at this point. 3. I could have leveraged the work of [libpng](http://www.libpng.org/pub/png/libpng.html) instead of implementing the algorithms myself. I decided not to, because libpng's API doesn't lend itself very well for the cherry-picking of hotspots approach I took with OilyPNG. You basically have to go all in if you want to use libpng. I think a Ruby PNG library that simply wraps libpng still has potential, but because of the reasons outlined in this article, I will leave that as an exercise to the reader. :) 4. Rubinius since has implemented most of MRI's C API so you can compile many C extensions against Rubinius as well, including OilyPNG. As an interesting side note: the Rubinius and JRuby developers have used ChunkyPNG as a performance benchmarking tool, because it contains a non-trivial amount of code and is computation heavy. 5. Unfortunately, OilyPNG is [not an exception](https://github.com/wvanbergen/oily_png/issues/12) to this rule. 6. My current employer — [Shopify](https://www.shopify.com) — is using the same approach for [Liquid](https://shopify.github.io/liquid/) and its C companion library [liquid-c](https://github.com/Shopify/liquid-c) with great success. Even though this requires matching Liquid's parsing behavior in certain edge cases quirk by quirk in the C implementation. Thanks to Simon Hørup Eskildsen, Emilie Noël, and Steven H. Noble for reviewing drafts of this post. chunky_png-1.3.15/docs/_posts/2010-01-14-memory-efficiency-when-using-ruby.md0000644000175000017500000001263613766004353025513 0ustar danieldaniel--- author: Willem van Bergen title: Memory efficiency when using Ruby --- I have been spending some time creating [a pure Ruby PNG library](https://github.com/wvanbergen/chunky_png). For this library, I need to have some representation of the image, which is composed of RGB pixels, supporting an alpha channel. Because images can be composed of a lot of pixels, I want the implementation to be as memory efficient as possible. I also would like decent performance. A very naive Ruby implementation for an image represents the red, green, blue and alpha channel using a floating point number between 0.0 and 1.0, and might look something like this: {% highlight ruby %} class Pixel attr_reader :r, :g, :b, :a def initialize(r, g, b, a = 1.0) @r, @g, @b, @a = r, g, b, a end end class Image attr_reader :width, :height def initialize(width, height) @width, @height = width, height @pixels = Array.new(width * height) end def [](x,y) @pixels[y * width + x] end def []=(x,y, pixel) @pixels[y * width + x] = pixel end end {% endhighlight %} For a 10×10 image, this representation requires 4 times 100 floating point numbers, which require 8 bytes each. That’s already over 3kB for such a small image just for the floating point numbers! Ouch. A simple improvement is to decide that 8-bit color depth is enough in the case, in which case each channel can be represented by an integer between 0 and 255. Storing such a number only costs one byte of memory. Ruby’s Fixnum class typically uses 4-byte integers. If only the 4 channels of one byte each could be combined into a single Fixnum instance… Behold! {% highlight ruby %} class Pixel attr_reader :value alias :to_i :value def initialize(value) @value = value end def self.rgba(r, g, b, a = 255) self.new(r << 24 | g << 16 | b << 8 | a) end def r; (@value & 0xff000000) >> 24; end def g; (@value & 0x00ff0000) >> 16; end def b; (@value & 0x0000ff00) >> 8; end def a; (@value & 0x000000ff); end end {% endhighlight %} Notice the bit operations, which are extremely fast. This only requires 100 times 4 bytes = 400 bytes for storing the RGBA values for a 10×10 image, an 8 times improvement! This implementation wraps every pixel inside an object. This is nice, because I want to access the separate channels of every pixel easily using the r, g, b, and a methods, and every other method that is defined for every pixel. However, a Ruby object instance has an overhead of at least 20 bytes. That’s 20 times 100 is about 2kB for our 10×10 image! To get rid of the object overhead, it is possible to simply store the Fixnum value for every pixel, and only wrapping it inside a Pixel object when it is accessed. This can be done by modifying the Image class: {% highlight ruby %} class Image # ... def [](x,y) Pixel.new(@pixels[y * width + x]) # wrap end def []=(x,y, pixel) @pixels[y * width + x] = pixel.to_i # unwrap end end {% endhighlight %} As you can see, some simply changes in the representation can really make a difference in the memory usage. Can this representation be improved further? ## Integer math calculations Because we are now using integers to represent a pixel, this can cause problems when the math requires you to use floating point numbers. For example, the formula for [alpha composition](https://en.wikipedia.org/wiki/Alpha_compositing) of two pixels is as follows: \\[ C_o = C_a \alpha_a + C_b \alpha_b (1 - \alpha_a) \\] in which \\(C_a\\) is the color component of the foreground pixel, \\(\alpha_a\\) the alpha channel of the foreground pixel, \\(C_b\\) and \\(\alpha_b\\) the same values for the background pixel, all of which should be values between 0 and 1. A naive implementation could convert the integer numbers to their floating point equivalents: {% highlight ruby %} def compose(fg, bg) return bg if fg.a == 0 return fg if fg.a == 255 fg_alpha = fg.a / 255.0 bg_alpha = fg.a / 255.0 alpha_complement = (1.0 - fg_alpha) * bg_alpha new_r = (fg_alpha * fg.r + alpha_complement * bg.r).round new_g = (fg_alpha * fg.g + alpha_complement * bg.g).round new_b = (fg_alpha * fg.b + alpha_complement * bg.b).round new_a = ((fg_alpha + alpha_complement) * 255).round Pixel.rgba(new_r, new_g, new_b, new_a) end {% endhighlight %} This implementation is already a little bit optimized: no unnecessary conversions and calculations are being performed. However, this composition can be done a lot quicker after realizing that 255 is almost a power of two, in which computers excel because it can use bitwise operators and shifting for some calculations. My new approach uses a quicker implementation of multiplication of 8-bit integers that represent floating numbers between 0 and 1: {% highlight ruby %} def compose(fg, bg) return bg if fg.a == 0 return fg if fg.a == 255 alpha_complement = multiply(255 - fg.a, bg.a) new_r = multiply(fg.a, fg.r) + multiply(alpha_complement, bg.r) new_g = multiply(fg.a, fg.g) + multiply(alpha_complement, bg.g) new_b = multiply(fg.a, fg.b) + multiply(alpha_complement, bg.b) new_a = fg.a + alpha_complement Pixel.rgba(new_r, new_g, new_b, new_a) end # Quicker alternative for (a * b / 255.0).round def multiply(a, b) t = a * b + 0x80 ((t >> 8) + t) >> 8 end {% endhighlight %} Note that the new implementation is less precise in theory, but this precision is lost anyway because we have to convert the values back to 8 bit RGBA values. Your thoughts? chunky_png-1.3.15/docs/_posts/2010-01-17-ode-to-array-pack-and-string-unpack.md0000644000175000017500000001300013766004353025567 0ustar danieldaniel--- author: Willem van Bergen title: Ode to Array#pack and String#unpack --- Remember [my last post]({% post_url 2010-01-14-memory-efficiency-when-using-ruby %}), where I representing a pixel with a Fixnum, storing the R, G, B and A value in its 4 bytes of memory? Well, I have been working some more on [my PNG library](https://github.com/wvanbergen/chunky_png) and I am now trying loading and saving an image. Using the [PNG specification](https://www.w3.org/TR/PNG/), building a PNG encoder/decoder isn’t that hard, but the required algorithmic calculations make sure that performance in Ruby is less than stellar. I have rewritten all calculations to only use fast integer math (plus, minus, multiply and bitwise operators), but simply the amount of code that is getting executed is slowing Ruby down. What more can I do to improve the performance? ## Encoding RGBA images Optimizing loading images is very hard, because PNG images can have many variations, and taking shortcuts means that some images are no longer supported. Not so with saving images: as long an image is saved using one of the valid variations, every PNG decoder will be able to read the file. Let’s see if it is possible to optimize one of these encoding variations. During encoding, the image get splits up into scanlines (rows) of pixels, which in turn get converted into bytes. These bytes can be filtered for optimal compression. For a 3×3 8-bit RGBA image, the result looks like this: F Rf Gf Bf Af Rf Gf Bf Af Rf Gf Bf Af F Rf Gf Bf Af Rf Gf Bf Af Rf Gf Bf Af F Rf Gf Bf Af Rf Gf Bf Af Rf Gf Bf Af Every line starts with a byte F indicating the filter method, followed by the filtered R, G and B value for every pixel on that line. Now, if we choose filter method 0, which means no filtering, the result looks like this: 0 Ro Go Bo Ao Ro Go Bo Ao Ro Go Bo Ao 0 Ro Go Bo Ao Ro Go Bo Ao Ro Go Bo Ao 0 Ro Go Bo Ao Ro Go Bo Ao Ro Go Bo Ao Now, the original R, G, B and A byte from the original pixel’s Fixnum, occur in [big-endian or network byte order](https://en.wikipedia.org/wiki/Endianness), starting with the top left pixel, moving left to right and then top to bottom. Exactly like the pixels are stored in our image’s pixel array! This means that we can use the Array#pack method to encode into this format. The Array#pack-notation for this is "xN3" in which x get translated into a null byte, and every N as 4-byte integer in network byte order. For optimal performance, it is best to not split the original array in lines, but to pack the complete pixel array at once. So, we can encode all pixels with this command: {% highlight ruby %} pixeldata = pixels.pack("xN#{width}" * height) {% endhighlight %} This way, the splitting the image into lines, splitting the pixels into bytes, and filtering the bytes can be skipped. In Ruby 1.8.7, this means a speedup of over 1500% (no typo)! Of course, because no filtering applied, the subsequent compression is not optimal, but that is a tradeoff that I am willing to make. ## Encoding RGB images What about RGB images without alpha channel? We can simply choose to encode these using the RGBA method, but that increases the file size with roughly 25%. Can we fix this somehow? The unfiltered pixel data should look something like this: 0 Ro Go Bo Ro Go Bo Ro Go Bo 0 Ro Go Bo Ro Go Bo Ro Go Bo 0 Ro Go Bo Ro Go Bo Ro Go Bo This means that for every pixel that is encoded as a 4-byte integer, the last byte should be ditched. Luckily, the `Array#pack` method offers a modifier that does just that: `X`. Packing a 3 pixel line can be done with `"xNXNXNX"`. Again we would like to pack the whole pixel array at once: {% highlight ruby %} pixeldata = pixels.pack(("x" + ('NX' * width)) * height) {% endhighlight %} Because all the encoding steps can get skipped once again, the speed improvement is again 1500%! And the result is 25% smaller than the RGBA method. This method is actually so speedy, that saving an image using Ruby 1.9.1 is only a little bit slower (< 10%) than saving a PNG image using RMagick! See my [performance comparison](https://github.com/wvanbergen/chunky_png/wiki/performance-comparison). ## Loading image Given the promising results of the Array#pack method, using its counterpart String#unpack looks promising for speedy image loading, if you know the image’s size and the encoding format beforehand. An RGBA formatted stream can be loaded quickly with this command: {% highlight ruby %} pixels = rgba_pixeldata.unpack("N#{width * height}") image = Image.new(width, height, pixels) {% endhighlight %} For an RGB formatted stream, we can use the X modifier again, but we have to make sure to set the alpha value for every pixel to 255: {% highlight ruby %} pixels = rgb_pixeldata.unpack("NX" * (width * height)) pixels.map! { |pixel| pixel | 0x000000ff } image = Image.new(width, height, pixels) {% endhighlight %} You can even use little-endian integers to load streams in ABGR format! {% highlight ruby %} pixels = abgr_pixeldata.unpack("V#{width * height}") image = Image.new(width, height, pixels) {% endhighlight %} Loading pixel data for an image like this is again over 1500% faster than decoding the same PNG image. However, this can only be applied if you have control over the input format of the image. ## To conclude `Array#pack` and `String#unpack` really have increased the performance for my code. If you can apply them for project, don’t hesitate and spread the love! For all other cases, use as little code as possible, and upgrade to Ruby 1.9 for improved algorithmic performance. chunky_png-1.3.15/docs/index.md0000644000175000017500000000721613766004353015616 0ustar danieldaniel--- author: Willem van Bergen title: ChunkyPNG --- # ChunkyPNG This library can read and write PNG files. It is written in pure Ruby for maximum portability. Let me rephrase: it does NOT require RMagick or any other memory leaking image library. - Source code: [https://github.com/wvanbergen/chunky_png/tree/master](http://github.com/wvanbergen/chunky_png/tree/master) - RDoc: [https://rdoc.info/gems/chunky_png](https://rdoc.info/gems/chunky_png) - Wiki: [https://github.com/wvanbergen/chunky_png/wiki](https://github.com/wvanbergen/chunky_png/wiki) - Issue tracker: [https://github.com/wvanbergen/chunky_png/issues](https://github.com/wvanbergen/chunky_png/issues) ## Features - Decodes any image that the PNG standard allows. This includes all standard color modes, all bit depths and all transparency, interlacing and filtering options. - Encodes images supports all color modes (true color, grayscale and indexed) and transparency for all these color modes. The best color mode will be chosen automatically, based on the amount of used colors. - R/W access to the image's pixels. - R/W access to all image metadata that is stored in chunks. - Memory efficient (uses a Fixnum, i.e. 4 or 8 bytes of memory per pixel, depending on the hardware) - Reasonably fast for Ruby standards, by only using integer math and a highly optimized saving routine. - Interoperability with RMagick if you really have to. Also, have a look at [OilyPNG](http://github.com/wvanbergen/oily_png). OilyPNG is a mixin module that implements some of the ChunkyPNG algorithms in C, which provides a massive speed boost to encoding and decoding. ## Usage ``` ruby require 'chunky_png' # Creating an image from scratch, save as an interlaced PNG png = ChunkyPNG::Image.new(16, 16, ChunkyPNG::Color::TRANSPARENT) png[1,1] = ChunkyPNG::Color.rgba(10, 20, 30, 128) png[2,1] = ChunkyPNG::Color('black @ 0.5') png.save('filename.png', :interlace => true) # Compose images using alpha blending. avatar = ChunkyPNG::Image.from_file('avatar.png') badge = ChunkyPNG::Image.from_file('no_ie_badge.png') avatar.compose!(badge, 10, 10) avatar.save('composited.png', :fast_rgba) # Force the fast saving routine. # Accessing metadata image = ChunkyPNG::Image.from_file('with_metadata.png') puts image.metadata['Title'] image.metadata['Author'] = 'Willem van Bergen' image.save('with_metadata.png') # Overwrite file # Low level access to PNG chunks png_stream = ChunkyPNG::Datastream.from_file('filename.png') png_stream.each_chunk { |chunk| p chunk.type } ``` For more information, see [the project wiki](http://github.com/wvanbergen/chunky_png/wiki) or the RDOC documentation on [http://rdoc.info/gems/chunky_png](http://rdoc.info/gems/chunky_png) ## Security warning ChunkyPNG is vulnerable to decompression bombs, which means that ChunkyPNG is vulnerable to DOS attacks by running out of memory when loading a specifically crafted PNG file. Because of the pure-Ruby nature of the library it is very hard to fix this problem in the library itself. In order to safely deal with untrusted images, you should make sure to do the image processing using ChunkyPNG in a separate process, e.g. by using fork or a background processing library. ## About The library is written by Willem van Bergen for Floorplanner.com, and released under the MIT license (see LICENSE). Please contact me for questions or remarks. Patches are greatly appreciated! Please check out the changelog on https://github.com/wvanbergen/chunky_png/wiki/Changelog to see what changed in all versions. P.S.: The name of this library is intentionally similar to Chunky Bacon and Chunky GIF. Use Google if you want to know _why. :-) chunky_png-1.3.15/docs/CNAME0000644000175000017500000000001513766004353014721 0ustar danieldanielchunkypng.comchunky_png-1.3.15/LICENSE0000644000175000017500000000205213766004353014233 0ustar danieldanielCopyright (c) 2010-2019 Willem van Bergen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. chunky_png-1.3.15/bin/0000755000175000017500000000000013766004353013777 5ustar danieldanielchunky_png-1.3.15/bin/standardrb0000755000175000017500000000144313766004353016053 0ustar danieldaniel#!/usr/bin/env ruby # frozen_string_literal: true # # This file was generated by Bundler. # # The application 'standardrb' is installed as part of a gem, and # this file is here to facilitate running it. # require "pathname" ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", Pathname.new(__FILE__).realpath) bundle_binstub = File.expand_path("../bundle", __FILE__) if File.file?(bundle_binstub) if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ load(bundle_binstub) else abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") end end require "rubygems" require "bundler/setup" load Gem.bin_path("standard", "standardrb") chunky_png-1.3.15/bin/rake0000755000175000017500000000142313766004353014647 0ustar danieldaniel#!/usr/bin/env ruby # frozen_string_literal: true # # This file was generated by Bundler. # # The application 'rake' is installed as part of a gem, and # this file is here to facilitate running it. # require "pathname" ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", Pathname.new(__FILE__).realpath) bundle_binstub = File.expand_path("../bundle", __FILE__) if File.file?(bundle_binstub) if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ load(bundle_binstub) else abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") end end require "rubygems" require "bundler/setup" load Gem.bin_path("rake", "rake") chunky_png-1.3.15/CONTRIBUTING.rdoc0000644000175000017500000000553613766004353016020 0ustar danieldaniel= Contributing to ChunkyPNG I will gladly accept any contributions from anybody to improve this library. However, this is a personal open source project, without an active community around it. As a result I am the only maintainer, and I’d like to keep the scope and complexity of this project small. This way, with the limited amount of time I dedicate to this project, this library remains a solid piece of software that many people rely on in their workflow. == Project scope The scope of this library is defined as: 1. Reading and writing any PNG image that conforms to the spec, with proper error handling of images that do not conform to the spec. 2. Basic canvas drawing and compositing operations to create new or edit existing images. However, I will generally be hesitant to add new drawing and compositing methods, because the list could be endless to support every niche use case. 3. Remain compatible with Ruby versions that are widely used. I generally consider this gem to be feature complete based on this scope. I will close issues and pull requests that go beyond this scope. If you want to work on something, but are not sure if it will be accepted, feel free to reach out to me by opening an issue before committing your time. For functionality that goes beyond the scope, I would suggest making it available as a separate “plugin” gem (e.g. chunky_png-). If anything in the gem needs to be refactored to support this kind of extensibility, I am happy to accept PRs for that. == Reporting bugs - First, see if somebody else has reported the problem already. - Try to include as much relevant information as possible, so I can recreate the problem. - If possible, include the PNG image that is causing the issue. - If possible, include a code snippet that exposes the problem. == Pull requests Title and description: - If you are not yet done, please include [WIP] in the title of your pull request. - Explain why your changes are relevant in the description of your pull request. - If your changes improve performance, include benchmark methodology and results in the PR. See BENCHMARKING.rdoc for more information. Code: - Don't break backwards compatibility. - Follow code conventions. They are enforced through Rubocop. - Add Yardoc comments as documentation. Specs: - Always include specs that test your changes, to prevent me from breaking your code later. - If your specs use PNG files, try to keep them as small as possible to keep the test suite snappy. - Make sure that the specs are passing for all Rubies on Travis CI: https://travis-ci.org/wvanbergen/chunky_png/ Misc: - Add an entry to CHANGELOG.rdoc. - Do not change ChunkyPNG::VERSION == Releases - Ensure the latest master is passing on Travis CI. - Update CHANGELOG.rdoc - Update ChunkyPNG::VERSION - Run bundle exec rake release chunky_png-1.3.15/.yardopts0000644000175000017500000000016013766004353015072 0ustar danieldaniel--title "ChunkyPNG - The pure Ruby PNG library" lib/**/*.rb - BENCHMARKING.rdoc --no-private --hide-void-return chunky_png-1.3.15/benchmarks/0000755000175000017500000000000013766004353015344 5ustar danieldanielchunky_png-1.3.15/benchmarks/encoding_benchmark.rb0000644000175000017500000000330113766004353021466 0ustar danieldanielrequire "rubygems" require "bundler/setup" require "benchmark" require "chunky_png" image = ChunkyPNG::Image.new(240, 180, ChunkyPNG::Color::TRANSPARENT) # set some random pixels # rubocop:disable Layout/ExtraSpacing, Layout/SpaceInsideParens image[10, 20] = ChunkyPNG::Color.rgba(255, 0, 0, 255) image[50, 87] = ChunkyPNG::Color.rgba( 0, 255, 0, 255) image[33, 99] = ChunkyPNG::Color.rgba( 0, 0, 255, 255) # rubocop:enable Layout/ExtraSpacing, Layout/SpaceInsideParens n = (ENV["N"] || "5").to_i puts "---------------------------------------------" puts "ChunkyPNG (#{ChunkyPNG::VERSION}) encoding benchmark (n=#{n})" puts "---------------------------------------------" puts Benchmark.bmbm do |x| x.report("Autodetect (indexed)") { n.times { image.to_blob } } # Presets x.report(":no_compression") { n.times { image.to_blob(:no_compression) } } x.report(":fast_rgba") { n.times { image.to_blob(:fast_rgba) } } x.report(":fast_rgb") { n.times { image.to_blob(:fast_rgb) } } x.report(":good_compression") { n.times { image.to_blob(:good_compression) } } x.report(":best_compression") { n.times { image.to_blob(:best_compression) } } # Some options x.report(":rgb") { n.times { image.to_blob(color_mode: ChunkyPNG::COLOR_TRUECOLOR) } } x.report(":rgba") { n.times { image.to_blob(color_mode: ChunkyPNG::COLOR_TRUECOLOR_ALPHA) } } x.report(":indexed") { n.times { image.to_blob(color_mode: ChunkyPNG::COLOR_INDEXED) } } x.report(":interlaced") { n.times { image.to_blob(interlaced: true) } } # Exports x.report("to RGBA pixelstream") { n.times { image.to_rgba_stream } } x.report("to RGB pixelstream") { n.times { image.to_rgb_stream } } end chunky_png-1.3.15/benchmarks/decoding_benchmark.rb0000644000175000017500000000252313766004353021461 0ustar danieldanielrequire "rubygems" require "bundler/setup" require "benchmark" require "chunky_png" def image_file(name) File.join(File.dirname(__FILE__), "..", "spec", "resources", name) end def image_data(name) data = nil File.open(image_file(name), "rb") { |f| data = f.read } data end no_filtering_stream = image_data("pixelstream_fast_rgba.png") up_filtering_stream = image_data("pixelstream_reference.png") paeth_filtering_stream = image_data("pixelstream_best_compression.png") rgba_pixelstream = image_data("pixelstream.rgba") rgb_pixelstream = image_data("pixelstream.rgb") n = (ENV["N"] || "5").to_i puts "---------------------------------------------" puts "ChunkyPNG (#{ChunkyPNG::VERSION}) decoding benchmark (n=#{n})" puts "---------------------------------------------" puts Benchmark.bmbm do |x| x.report("PNG - no filtering") { n.times { ChunkyPNG::Image.from_blob(no_filtering_stream) } } x.report("PNG - UP filtering") { n.times { ChunkyPNG::Image.from_blob(up_filtering_stream) } } x.report("PNG - PAETH filtering") { n.times { ChunkyPNG::Image.from_blob(paeth_filtering_stream) } } x.report("From RGBA pixelstream") { n.times { ChunkyPNG::Image.from_rgba_stream(240, 180, rgba_pixelstream) } } x.report("From RGB pixelstream") { n.times { ChunkyPNG::Image.from_rgb_stream(240, 180, rgb_pixelstream) } } end chunky_png-1.3.15/benchmarks/filesize_benchmark.rb0000644000175000017500000000212313766004353021513 0ustar danieldanielrequire "rubygems" require "bundler/setup" require "benchmark" require "chunky_png" files = ["pixelstream_reference.png", "operations.png", "clock.png"] def encode_png(image, constraints = {}) filesize = nil time = Benchmark.realtime { filesize = image.to_blob(constraints).bytesize } [filesize, time] end files.each do |file| filename = File.join(File.dirname(__FILE__), "..", "spec", "resources", file) image = ChunkyPNG::Canvas.from_file(filename) puts "#{file}: #{image.width}x#{image.height} - #{image.palette.size} colors" puts "------------------------------------------------" puts " : %8d bytes in %0.4fs" % encode_png(image) puts ":no_compression : %8d bytes in %0.4fs" % encode_png(image, :no_compression) puts ":fast_rgba : %8d bytes in %0.4fs" % encode_png(image, :fast_rgba) puts ":fast_rgb : %8d bytes in %0.4fs" % encode_png(image, :fast_rgb) puts ":good_compression : %8d bytes in %0.4fs" % encode_png(image, :good_compression) puts ":best_compression : %8d bytes in %0.4fs" % encode_png(image, :best_compression) puts end