Repository navigation
Releases: peterc/pure_jpeg
Release list
v0.4.0
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:spinelproof-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.
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
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
v0.3.0
New features:
PureJPEG.infofor reading dimensions and metadata without full decode- ICC color profile extraction (available on
InfoandImage) - 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
New features:
- Progressive JPEG decoding (SOF2) with spectral selection and successive approximation
Image#each_rgbfor iterating pixels without per-pixel struct allocationPureJPEG::DecodeErrorexception class for all decoding errors- Validation of custom quantization tables (length and value range)
Performance:
- Packed integer pixel storage in
Imageeliminates per-pixel object allocation on decode (~6x faster decode) - Fast path for encoder pixel extraction from packed sources (
ChunkyPNGSource,Image) BitReader#read_bitsfast path when buffer already has enough bitsBitWriterbuilds aStringdirectly instead ofArray+packHuffman.build_tablereturns anArrayfor O(1) lookup instead ofHash- Faster scan data extraction using
String#index
Fixes:
- JPEG data detection uses SOI marker check instead of null-byte heuristic
RawSourcepixels default to black instead ofnilBitReaderbounds check for truncated 0xFF sequencesJFIFReaderbounds check when reading past end of data- Fixed dead tautological check in AC encoding EOB logic
0.1.0
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