ChunkyPNG Documentation

repository·master·Indexed 22 days ago

https://github.com/wvanbergen/chunky_png

A pure-Ruby library for reading and writing PNG files without requiring external C libraries. It provides tools for image creation, alpha blending, metadata manipulation, and low-level chunk access. The library includes a drawing API for primitive shapes, Bezier curves, and polygons, and optimizes memory by representing pixels as 32-bit integers. For performance-critical tasks, it can be paired with the OilyPNG C-extension mixin.

Tokens
10.8K
Snippets
51
Records
60
Agent score
76%

What's inside ChunkyPNG

  1. Understand ChunkyPNG's memory-efficient pixel representation

    master

    ChunkyPNG optimizes memory usage by representing pixels as single 32-bit integers (Fixnums) rather than individual objects for each color channel. This approach avoids the high overhead of Ruby object instances and floating-point numbers.

    How it works:

    • Bit Packing: Each pixel stores Red, Green, Blue, and Alpha channels in a single integer using bitwise shifts.
    • Channel Layout: The channels are packed as r << 24 | g << 16 | b << 8 | a.
    • On-demand Wrapping: To provide a developer-friendly API, the Image class wraps the raw integer in a Pixel object only when a specific pixel is accessed via []. When setting a pixel via []=, the Pixel object is unwrapped back into its integer representation.
    # Conceptual representation of how ChunkyPNG handles pixels
    class Pixel
      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
    
    # How the Image class manages memory
    class Image
      def [](x, y)
        Pixel.new(@pixels[y * width + x]) # Wrap integer in object for access
      end
    
      def []=(x, y, pixel)
        @pixels[y * width + x] = pixel.to_i # Unwrap object to integer for storage
      end
    end
  2. Encode RGB images using Array#pack

    master

    For RGB images (without an alpha channel), you can optimize encoding by using the X modifier in Array#pack. This allows you to pack 4-byte integers but discard the last byte (the alpha channel) for every pixel, resulting in a file size approximately 25% smaller than the RGBA method while maintaining high performance.

    # pixels is an array of Fixnums representing RGB pixels
    # width and height are the image dimensions
    pixeldata = pixels.pack(("x" + ('NX' * width)) * height)
  3. Encode RGBA images using Array#pack

    master

    To achieve high-performance encoding for RGBA images, you can bypass manual scanline splitting and filtering by using Ruby's Array#pack method with the xN notation. This method treats the pixel array as a sequence of 4-byte integers in network byte order (big-endian) preceded by a null byte (filter method 0).

    Note: This approach skips PNG filtering, which means the resulting file compression may not be optimal, but it provides a significant speedup.

    # pixels is an array of Fixnums representing RGBA pixels
    # width and height are the image dimensions
    pixeldata = pixels.pack("xN#{width}" * height)
  4. Load RGB pixel data using String#unpack

    master

    To load an RGB formatted stream, use the X modifier with String#unpack. Because the alpha channel is missing from the stream, you must manually set the alpha value for every pixel to 255 (0x000000ff) to ensure correct transparency in the resulting Image object.

    # rgb_pixeldata is the raw byte stream
    # width and height are the image dimensions
    pixels = rgb_pixeldata.unpack("NX" * (width * height))
    # Set alpha channel to 255 for every pixel
    pixels.map! { |pixel| pixel | 0x000000ff }
    image = Image.new(width, height, pixels)
  5. Load RGBA pixel data using String#unpack

    master

    If you have access to the raw RGBA formatted pixel stream and know the image dimensions, you can load pixels extremely quickly using String#unpack with the N (network byte order) directive.

    # rgba_pixeldata is the raw byte stream
    # width and height are the image dimensions
    pixels = rgba_pixeldata.unpack("N#{width * height}")
    image = Image.new(width, height, pixels)
  6. Use OilyPNG for performance optimization

    master

    ChunkyPNG is a pure Ruby library with no dependencies, making it easy to install without a compiler toolchain. However, for performance-critical tasks, you can use OilyPNG, a companion mixin library.

    OilyPNG provides C-optimized implementations of specific ChunkyPNG methods. When loaded, it uses Module#include and Module#extend to overwrite ChunkyPNG's pure Ruby methods with faster versions while maintaining 100% API compatibility.

    To make OilyPNG optional in your project (falling back to pure Ruby if the C extension is unavailable), use a begin/rescue block during your requirement phase.

    begin
      require 'oily_png'
    rescue LoadError
      require 'chunky_png'
    end
  7. Core components of ChunkyPNG

    master

    ChunkyPNG is a pure Ruby library for accessing and manipulating PNG files. The library is organized under the ChunkyPNG namespace and consists of several key classes and modules:

    • ChunkyPNG::Image: The primary class used to represent PNG images, including their metadata.
    • ChunkyPNG::Canvas: Represents the image's canvas (the pixel data area).
    • ChunkyPNG::Color: A module for working with color values.
    • ChunkyPNG::Palette: Represents the color palette used by a ChunkyPNG::Canvas.
    • ChunkyPNG::Datastream: Represents the internal structure of a PNG ChunkyPNG::Image.
    • ChunkyPNG::Chunk: Represents individual chunks of data within a ChunkyPNG::Datastream.
    • ChunkyPNG::Point, ChunkyPNG::Dimension, and ChunkyPNG::Vector: Geometry helper classes for 2D coordinates, dimensions (width x height), and series of points.
  8. Manage metadata with Text and InternationalText chunks

    master

    ChunkyPNG supports several ways to store metadata:

    tEXt (Uncompressed Text)

    Use ChunkyPNG::Chunk::Text for Latin-1 encoded keyword/value pairs.

    zTXt (Compressed Text)

    Use ChunkyPNG::Chunk::CompressedText for Deflate-compressed keyword/value pairs.

    iTXt (International Text)

    Use ChunkyPNG::Chunk::InternationalText for UTF-8 encoded metadata. This supports language tags and translated keywords.

    # Example: Creating a tEXt chunk
    text_chunk = ChunkyPNG::Chunk::Text.new("Author", "Jane Doe")
    
    # Example: Creating an iTXt chunk
    intl_chunk = ChunkyPNG::Chunk::InternationalText.new(
      "Description", 
      "A beautiful sunset", 
      "en", 
      "Beschreibung", 
      ChunkyPNG::UNCOMPRESSED_CONTENT
    )
  9. Draw primitive shapes on a ChunkyPNG::Canvas

    master

    The ChunkyPNG::Canvas::Drawing module provides several primitive drawing methods.

    Important Behaviors:

    • In-place modification: All drawing methods modify the existing ChunkyPNG::Canvas instance and return self. They do not create a new canvas.
    • Bounds handling: Drawing operations will not fail if they occur outside the canvas bounds; pixels outside the canvas are simply ignored.
    • Color parsing: Most methods accept color values that can be parsed via ChunkyPNG::Color.parse (e.g., hex strings or integers).
  10. How PNG chunks are represented in ChunkyPNG

    master

    A PNG datastream is composed of multiple chunks. ChunkyPNG maps specific four-character chunk type indicators to specialized classes to provide easy access to their data:

    • IHDR $\rightarrow$ ChunkyPNG::Chunk::Header: Contains image dimensions and color settings.
    • IEND $\rightarrow$ ChunkyPNG::Chunk::End: Marks the end of the stream.
    • IDAT $\rightarrow$ ChunkyPNG::Chunk::ImageData: Contains compressed pixel data.
    • PLTE $\rightarrow$ ChunkyPNG::Chunk::Palette: Contains the image palette.
    • tRNS $\rightarrow$ ChunkyPNG::Chunk::Transparency: Defines transparency settings.
    • tEXt $\rightarrow$ ChunkyPNG::Chunk::Text: Uncompressed keyword/value metadata.
    • zTXt $\rightarrow$ ChunkyPNG::Chunk::CompressedText: Compressed keyword/value metadata.
    • iTXt $\rightarrow$ ChunkyPNG::Chunk::InternationalText: UTF-8 metadata with language support.
    • pHYs $\rightarrow$ ChunkyPNG::Chunk::Physical: Physical pixel aspect ratio.

    If a chunk type is not recognized, it is returned as a ChunkyPNG::Chunk::Generic instance, which allows raw access to the content.

  11. Security warning: Handling untrusted images

    master

    ChunkyPNG is vulnerable to decompression bombs, which can lead to Denial of Service (DoS) attacks by exhausting memory when loading specially crafted PNG files.

    Mitigation: When processing untrusted images, perform the ChunkyPNG operations in a separate process (e.g., using fork or a background processing library) to isolate the impact of potential memory exhaustion.