Skip to content

Releases: peterc/pure_jpeg

v0.4.0

Choose a tag to compare

@peterc peterc released this 28 Sep 17:07

PureJPEG 0.4.0 reduces encoding and decoding time and memory use, and adds experimental compatibility with Spinel.

Performance

Measured on Apple M5 with Ruby 4.0.2 + YJIT, using warmed median-of-nine timings:

Operation Before 0.4.0 Less time
Encode (color, q85) 154 ms 131 ms 15%
Encode (q95, optimized Huffman) 267 ms 235 ms 12%
Baseline decode 133 ms 112 ms 16%
Progressive decode 173 ms 139 ms 20%
Photograph decode (peppers.jpg) 56 ms 41 ms 28%

The first four workloads use the bundled 1024x1024 images. Baseline decode allocations fell from 76,994 to 114, and encoding array storage allocated fell from 29.4 MB to 12.6 MB. Results depend on the image and runtime.

Changes include simpler Huffman loops and category calculations, scan-reference lookup outside inner loops, combined quantization/zigzag passes, two-row chroma processing, and fewer temporary pixels during RawSource construction.

Encoded bytes and decoded pixels matched the previous implementation across 432 encoding cases and 10 JPEG decodes. Existing public APIs remain unchanged.

Experimental Spinel compatibility

  • Compatibility adjustments and benchmarks for Spinel revision 7f08abd9.
  • A rake test:spinel proof-of-concept runner that compiles selected existing tests and compares their output with CRuby.
  • Seven of eight selected RawSource tests pass on the documented Spinel revision. The remaining test fails because respond_to? is unavailable on its pixel struct; the runner preserves that failure.

See Spinel testing notes for setup and limitations. The README includes performance comparisons for CRuby, TruffleRuby, JRuby, and Spinel.

Tests

Added regression coverage for quantization rounding, progressive coefficient offsets, and chroma downsampling edges.

Full changelog

Release validation: all 103 tests pass on CRuby 4.0.2, CRuby 3.3.11, and TruffleRuby 34.0.1. The 0.4.0 gem builds successfully.

v0.3.2

Choose a tag to compare

@peterc peterc released this 21 Mar 17:03

Performance optimizations

Major performance improvements across the entire encode/decode pipeline, primarily from contributions by @paracycle:

  • Replaced float matrix-multiply DCT with integer-scaled AAN DCT (IJG reference implementation)
  • Fixed-point integer arithmetic for color space conversion
  • Eliminated hundreds of thousands of short-lived object allocations per encode
  • Unrolled inner loops in DCT, block extraction, and block writing

Benchmarks (1024x1024, Ruby 4.0.2 + YJIT):

Operation v0.3.1 v0.3.2 Speedup
Encode (color, q85) ~0.47s ~0.16s 2.9x
Decode (baseline) ~0.63s ~0.14s 4.6x
Decode (progressive) ~0.70s ~0.18s 3.9x

No public API changes.

v0.3.1

Choose a tag to compare

@peterc peterc released this 19 Mar 15:49

Fixes:

  • Fixed shared Pixel instance bug in decoder that could corrupt pixel data
  • Encoder validates return values from quantization_modifier blocks

v0.3.0

Choose a tag to compare

@peterc peterc released this 09 Mar 13:59

New features:

  • PureJPEG.info for reading dimensions and metadata without full decode
  • ICC color profile extraction (available on Info and Image)
  • Optional image-specific optimized Huffman tables (optimize_huffman: true)

Fixes:

  • Decoder validates Huffman table, quantization table, and component references with clear error messages
  • Color decoding looks up Y/Cb/Cr components by ID instead of assuming SOF array order
  • Support for non-standard component IDs (e.g. 0, 1, 2 as used by some Adobe tools)
  • Explicit error for unsupported component counts (e.g. CMYK)
  • Encoder no longer holds file handle open during encoding

0.2.0

Choose a tag to compare

@peterc peterc released this 06 Mar 14:53

New features:

  • Progressive JPEG decoding (SOF2) with spectral selection and successive approximation
  • Image#each_rgb for iterating pixels without per-pixel struct allocation
  • PureJPEG::DecodeError exception class for all decoding errors
  • Validation of custom quantization tables (length and value range)

Performance:

  • Packed integer pixel storage in Image eliminates per-pixel object allocation on decode (~6x faster decode)
  • Fast path for encoder pixel extraction from packed sources (ChunkyPNGSource, Image)
  • BitReader#read_bits fast path when buffer already has enough bits
  • BitWriter builds a String directly instead of Array + pack
  • Huffman.build_table returns an Array for O(1) lookup instead of Hash
  • Faster scan data extraction using String#index

Fixes:

  • JPEG data detection uses SOI marker check instead of null-byte heuristic
  • RawSource pixels default to black instead of nil
  • BitReader bounds check for truncated 0xFF sequences
  • JFIFReader bounds check when reading past end of data
  • Fixed dead tautological check in AC encoding EOB logic

0.1.0

Choose a tag to compare

@peterc peterc released this 05 Mar 21:32

Initial release.

  • Baseline DCT encoder (SOF0, 8-bit, Huffman)
  • YCbCr color with 4:2:0 chroma subsampling
  • Grayscale mode
  • Baseline DCT decoder with support for any chroma subsampling factor and restart markers
  • Creative encoding options: independent chroma quality, custom quantization tables, quantization modifier, scrambled quantization

Pure Ruby, no native dependencies