butteraugli-oxide
Pure Rust implementation of Google's butteraugli perceptual image quality metric from libjxl.
What is Butteraugli?
Butteraugli is a psychovisual image quality metric that estimates the perceived difference between two images. Unlike simple metrics like PSNR or MSE, butteraugli models human vision to produce scores that correlate well with subjective quality assessments.
The metric is based on:
- Opsin dynamics: Models photosensitive chemical responses in the retina
- XYB color space: A hybrid opponent/trichromatic color representation
- Visual masking: How image features hide or reveal differences
- Multi-scale analysis: Examines differences at multiple frequency bands
Quality Thresholds
| Score | Interpretation |
|---|---|
| < 1.0 | Images appear identical to most viewers |
| 1.0 - 2.0 | Subtle differences may be noticeable |
| > 2.0 | Visible differences between images |
Usage
Add to your Cargo.toml:
[]
= "0.1"
Input Formats
Two input APIs are provided:
| Function | Input Type | Color Space | Use Case |
|---|---|---|---|
compute_butteraugli |
&[u8] |
sRGB (gamma-encoded) | Standard 8-bit images |
compute_butteraugli_linear |
&[f32] |
Linear RGB (0.0-1.0) | HDR, 16-bit, float pipelines |
Both APIs require:
- Channel order: RGB (red, green, blue)
- Layout: Row-major, interleaved (see below)
- Minimum size: 8×8 pixels
The sRGB function internally applies gamma decoding before comparison.
Pixel Layout
Data is row-major, interleaved RGB:
Data: [R0, G0, B0, R1, G1, B1, R2, G2, B2, ...]
└─pixel 0─┘ └─pixel 1─┘ └─pixel 2─┘
For a 3×2 image:
Row 0: [R00, G00, B00, R01, G01, B01, R02, G02, B02]
Row 1: [R10, G10, B10, R11, G11, B11, R12, G12, B12]
Memory: [R00, G00, B00, R01, G01, B01, R02, G02, B02, R10, G10, B10, ...]
└────────────── row 0 ──────────────────┘ └──── row 1 ────...
To access pixel at (x, y): index = (y * width + x) * 3
Basic Example
use ;
// Load two RGB images (u8, 3 bytes per pixel, row-major order)
let original: & = &;
let compressed: & = &;
let width = 640;
let height = 480;
// Compare images
let params = default;
let result = compute_butteraugli
.expect;
println!;
if result.score < 1.0 else if result.score < 2.0 else
// Optional: access per-pixel difference map
if let Some = result.diffmap
Linear RGB Example (HDR/16-bit)
use ;
// Convert 16-bit image to linear f32
let original_16bit: & = &;
let original_linear: = original_16bit.iter
.map // Assuming already linear
.collect;
// Or convert 8-bit sRGB manually
let original_srgb: & = &;
let original_linear: = original_srgb.iter
.map
.collect;
let result = compute_butteraugli_linear
.expect;
Custom Parameters
use ButteraugliParams;
let params = new
.with_hf_asymmetry // Penalize new artifacts more than blurring
.with_xmul // X channel multiplier (1.0 = neutral)
.with_intensity_target; // HDR display brightness in nits
Helper Functions
use ;
// Convert score to 0-100 quality percentage
let quality = score_to_quality; // ~62.5%
// Get fuzzy classification (2.0 = perfect, 1.0 = ok, 0.0 = bad)
let class = butteraugli_fuzzy_class; // ~1.25
Features
simd(default): Enable SIMD optimizations via thewidecrate
Performance
This implementation uses SIMD operations where available for gaussian blur and other compute-intensive operations. Performance is comparable to the C++ implementation for typical image sizes.
Accuracy
The implementation includes 195 synthetic test cases validated against the C++ libjxl butteraugli. Reference values are captured from C++ and hard-coded for regression testing without requiring FFI bindings at runtime.
Comparison with Other Crates
| Crate | Type | Notes |
|---|---|---|
butteraugli-oxide |
Pure Rust | Full implementation, no C++ dependency |
butteraugli |
FFI wrapper | Wraps C++ butteraugli library |
butteraugli-sys |
FFI bindings | Low-level C++ bindings |
API Comparison with C++ libjxl
| Feature | C++ butteraugli | butteraugli-oxide |
|---|---|---|
| Input format | Linear RGB float | sRGB u8 or linear RGB f32 |
| Bit depth | Any (via float) | 8-bit u8 or f32 |
| Color space | Linear RGB only | sRGB (auto-converted) or linear RGB |
| HDR support | Yes | Yes (via compute_butteraugli_linear) |
| Channel layout | Planar (separate R, G, B arrays) | Interleaved (RGBRGB...) |
XYB Color Space Note
Butteraugli's internal XYB is NOT the same as jpegli's XYB.
| Aspect | Butteraugli XYB | jpegli XYB |
|---|---|---|
| Nonlinearity | Gamma (FastLog2f-based) | Cube root |
| Opsin matrix | Different coefficients | Different coefficients |
| Dynamic sensitivity | Yes (blur-based adaptation) | No |
| XY formula | X = L - M, Y = L + M | X = (L-M)/2, Y = (L+M)/2 |
This crate does NOT accept XYB input directly because there are multiple incompatible XYB definitions. Always provide RGB input and let butteraugli perform its own internal conversion.
References
- Original butteraugli repository
- JPEG XL (libjxl) - Contains the reference implementation
- Butteraugli paper
AI-Generated Code Notice
This crate was developed with significant assistance from Claude (Anthropic). While the code has been tested against the C++ libjxl butteraugli implementation and passes 195 synthetic test cases with exact numerical parity, not all code has been manually reviewed or human-audited.
Before using in production:
- Review critical code paths for your use case
- Run your own validation against expected outputs
- Consider the test suite coverage for your specific requirements
License
BSD-3-Clause, same as the original libjxl implementation.