pura-png

A pure Ruby PNG decoder/encoder without additional image-processing libraries.

Part of the pura-* series — pure Ruby image codec gems.

Features

  • Color types: Grayscale (0), RGB (2), Indexed (3), Grayscale+Alpha (4), RGBA (6)
  • Bit depths: 1, 2, 4, 8, 16
  • All 5 filter types: None, Sub, Up, Average, Paeth
  • Zlib compression/decompression via Ruby stdlib
  • Image resizing (bilinear / nearest-neighbor / fit / fill)
  • Uses Ruby standard-library zlib for compression; no additional image library needed
  • CLI tool included

Installation

gem install pura-png

Usage

require "pura-png"

# Decode
image = Pura::Png.decode("photo.png")
image.width      #=> 800
image.height     #=> 600
image.pixels     #=> Raw RGB byte string
image.pixel_at(x, y) #=> [r, g, b]

# Encode
Pura::Png.encode(image, "output.png")

# Resize
thumb = image.resize(200, 200)
fitted = image.resize_fit(800, 600)
fill = image.resize_fill(200, 200)

# From raw pixels
image = Pura::Png::Image.new(width, height, rgb_pixels_string)
Pura::Png.encode(image, "output.png")

CLI

pura-png decode input.png --info
pura-png decode input.png --out pixels.dat
pura-png encode input.dat --width W --height H --out output.png
pura-png resize input.png --width 200 --height 200 --out thumb.png
pura-png resize input.png --fit 800x600 --out fitted.png

Benchmark

These historical measurements include ffmpeg process startup. They do not compare against an in-process C codec or establish Rails pipeline throughput.

400×400 image, Ruby 4.0.2 + YJIT.

Decode

Decoder Time Notes
chunky_png (Ruby) 59 ms Uses Zlib C extension
ffmpeg (C) 60 ms C
pura-png 111 ms Pure Ruby (uses Zlib C ext for compression)

Encode

Encoder Time vs ffmpeg
pura-png 52 ms 0.8× — faster than ffmpeg!
ffmpeg (C) 61 ms

pura-png is slower than chunky_png for decoding, but supports more color types and bit depths, and is part of the pura-* ecosystem for seamless format conversion. Both use Ruby's built-in Zlib (a C extension) for compression.

Why pure Ruby?

  • gem install and go — no brew install, no apt install, no C compiler needed
  • Non-interlaced PNG color types and valid bit depths; decoded pixels are 8-bit RGB
  • Part of pura-* — convert between JPEG, PNG, BMP, GIF, TIFF, WebP seamlessly
Gem Format Status
pura-jpeg JPEG ✅ Available
pura-png PNG ✅ Available
pura-bmp BMP ✅ Available
pura-gif GIF ✅ Available
pura-tiff TIFF ✅ Available
pura-ico ICO ✅ Available
pura-webp WebP ✅ Available
pura-image All formats ✅ Available

Pixel model and limitations

Images contain 8-bit RGB pixels. Alpha and tRNS transparency are discarded, not composited. Adam7 interlacing is unsupported. Sixteen-bit samples are reduced to eight bits.

crop(x, y, width, height) requires integer coordinates, positive dimensions, and a region entirely inside the image; invalid regions raise ArgumentError.

Decode limits

Pura::Png.decode(input, max_input_bytes: 64 * 1024 * 1024, max_pixels: 40_000_000, max_decoded_bytes: 256 * 1024 * 1024) accepts a path or binary data. All limits must be positive integers. These defaults bound input, pixel count, and decoded data size; they are not a cap on the Ruby process's total memory or CPU time.

Invalid image data raises Pura::Png::DecodeError; exceeding a configured limit raises its subclass Pura::Png::LimitExceeded. PNG decoding validates chunk CRCs and the IHDR layout, rejects invalid color/depth combinations, and checks scanline length during bounded decompression.

License

MIT