dithereens
Before (top) and after (bottom) dithering a gradient (uses simple_dither(), i.e. defaults).
Functions and traits for quantizing values with deterministic hash-based error-diffusion.
Quantizing from f64/f32/f16 to u32/u16/u8 without dithering
creates banding. This crate provides deterministic hash-based dithering to
reduce quantization artifacts.
Overview
- Deterministic: Same input with same seed always produces same output.
- Multiple dithering methods: Hash, R2, GoldenRatio for 1D; IGN, SpatialHash, BlueNoise for 2D.
- Single values:
dither(),simple_dither(). - Iterator processing:
dither_iter(),simple_dither_iter(). - In-place operations:
dither_slice(),simple_dither_slice(). - Image support: Both 1D methods (processing as flat array) and 2D methods (using coordinates).
- Custom methods: Use specific dithering algorithms via
*_with_method()functions. no_stdsupport: Works in embedded environments.- Generic types:
f32,f64,f16(withnightly_f16feature), or any type implementingDitherFloat. - Blue noise: High-quality blue noise dithering (with
blue_noisefeature).
Quick Start
use simple_dither;
let value: f32 = 0.5;
// Dither `value` to `127u8` or `128u8` deterministically.
// The same index and seed will always produce the same result.
let dithered_value: u8 =
simple_dither.clamp as u8;
assert!;
Dithering Methods
1D Methods (for sequential data and images as flat arrays)
- Hash (default): Fast hash-based dithering, good general-purpose quality.
- R2: Low-discrepancy sequence using the R2 sequence.
- GoldenRatio: Golden ratio-based sequence.
1D methods have been used successfully for image dithering for years by processing images as flat arrays. They work well when you don't need spatial correlation between pixels.
2D Methods (for images using spatial coordinates)
- InterleavedGradientNoise (IGN): Fast, good quality for real-time graphics.
- SpatialHash: Spatial hash function for blue noise-like properties.
- BlueNoiseApprox: Approximation combining IGN and SpatialHash.
- BlueNoise (requires
blue_noisefeature): True blue noise from precomputed tables.
2D methods use pixel coordinates to create spatially-aware dithering patterns, which can produce more visually pleasing results for images.
Using Custom Methods
use ;
let value = 0.5f32;
let seed = 42;
// Use different dithering methods.
let hash_method = new;
let r2_method = R2new;
let golden_method = new;
let dithered_hash = simple_dither_with;
let dithered_r2 = simple_dither_with;
let dithered_golden = simple_dither_with;
Image Dithering with 1D Methods
1D methods work great for images when processed as flat arrays:
use ;
// Example: dither a grayscale image.
let width = 256;
let height = 256;
let mut pixels: = vec!;
// Process entire image as flat array with 1D dithering.
simple_dither_slice;
// pixels now contains dithered values.
2D Dithering for Images
2D methods use spatial coordinates for better visual results:
use ;
// Example: dither a grayscale image.
let width = 256;
let height = 256;
let mut pixels: = vec!;
// Use IGN for 2D dithering.
let method = new;
simple_dither_slice_2d;
// pixels now contains dithered values.
Performance Guide
Benchmarks with 10,000 values:
- Single values:
dither(),simple_dither(). - In-place slice operations:
dither_slice(),simple_dither_slice()(>5× faster than iterator methods). - Iterator chains:
dither_iter(),simple_dither_iter(), orDitherIteratorExtadapters (allocation overhead).
Parallel Processing
Via rayon (enabled by default). With rayon enabled, _iter and
_slice functions use parallel processing automatically for better
performance on large datasets.
no_std Support
This crate supports no_std environments. The libm crate provides a
native round() implementation. Without libm, a manual implementation is
used.
[]
# `no_std`
= { = "0.3", = false }
[]
# Optional: uses `libm`'s `round()` function instead of a manual
# implementation for `no_std`.
= {
version = "0.3",
= false,
= ["libm"]
}
Native f16 Support
Enable the nightly_f16 feature to use native f16 types (requires nightly
Rust):
[]
= { = "0.3", = ["nightly_f16"] }
Blue Noise Support
Enable the blue_noise feature for high-quality blue noise dithering:
[]
= { = "0.3", = ["blue_noise"] }
This adds the BlueNoise struct which provides true blue noise dithering
using a precomputed 256×256×4 table.
This increases binary size by ~5M!
use ;
let width = 256;
let mut pixels: = vec!;
let blue_noise = new;
simple_dither_slice_2d;
Float-to-Float Dithering
Dither when converting between floating-point types of different precisions to reduce quantization artifacts like banding in smooth gradients.
Supported Conversions
- f64 → f32: Always available.
- f32 → f16: Requires
nightly_f16feature and nightly Rust. - f64 → f16: Requires
nightly_f16feature and nightly Rust.
Use Cases
Float-to-float dithering is particularly useful for:
- Converting HDR sky gradients from f32 to f16.
- Reducing banding in smooth color transitions.
- Maintaining visual quality when downsampling precision.
- Processing high-precision data for display or storage.
Example: HDR Gradient Conversion
use dither_float_slice;
// Smooth gradient in f64.
let gradient: = .map.collect;
// Convert to f32 with dithering to preserve smoothness.
let dithered: = dither_float_slice;
// Without dithering (simple cast) would show more banding.
Example: Image Conversion with 2D Methods
use ;
let width = 256;
let image_f32: = vec!;
// Use 2D dithering for spatially-aware noise patterns.
let method = new;
let image_f16: = dither_float_slice_2d;
Available Functions
Single values:
dither_float()-- Default hash method.dither_float_with()-- Custom 1D method.dither_float_2d()-- Custom 2D method.
Slices:
dither_float_slice()-- 1D processing.dither_float_slice_with()-- 1D with custom method.dither_float_slice_2d()-- 2D processing.
Iterators:
dither_float_iter()-- From iterator.dither_float_iter_with()-- With custom method.
Trait methods:
All LinearRng and SpatialRng implementations provide
dither_float* methods.
See examples/float_precision_dither.rs for complete examples.
License
Apache-2.0 OR BSD-3-Clause OR MIT OR Zlib at your discretion.