| Original | Dithered |
|---|---|
![]() |
![]() |
Before (left) and after (right) using yliluoma_2_in_place.
Buffer-first rust dithering and halftoning library.
Quantizing grayscale/RGB/RGBA buffers without dithering creates visible
banding and contouring. dithr provides deterministic ordered dithering,
diffusion, stochastic binary methods, palette-constrained workflows, and
advanced halftoning methods over typed mutable slices.
- Buffer-first API: Works directly on mutable pixel slices with explicit width, height, and stride.
- Typed formats: Supports
u8,u16, andf32sample types across Gray, Rgb, and Rgba layouts. - Quantization control: Uses
QuantizeModefor grayscale levels, RGB levels, palette mapping, or single-color workflows. - Broad algorithm coverage: Includes stochastic, ordered, palette-oriented ordered, diffusion, variable diffusion, and advanced halftoning families.
- Palette workflow: Includes
Palette<S>andIndexedImage<S>for constrained output and indexed results. - Optional integrations:
imageadapters forDynamicImageworkflows andrayonparallel wrappers for selected families.
cargo add dithr[dependencies]
dithr = "0.3.0"cargo add dithr --features image
cargo add dithr --features rayonuse dithr::{gray_u8, QuantizeMode, Result};
use dithr::ordered::bayer_8x8_in_place;
fn main() -> Result<()> {
let width = 64_usize;
let height = 64_usize;
let mut data = Vec::with_capacity(width * height);
for y in 0..height {
for x in 0..width {
let value = ((x + y * width) * 255 / (width * height - 1)) as u8;
data.push(value);
}
}
let mut buffer = gray_u8(&mut data, width, height, width)?;
bayer_8x8_in_place(&mut buffer, QuantizeMode::gray_bits(1)?)?;
assert!(data.iter().all(|&v| v == 0 || v == 255));
Ok(())
}dithr is organized around a small set of types that are shared across
algorithm families.
Buffer<'a, S, L>: Mutable typed view of image data (S= sample type,L= layout).BufferKind/PixelFormat: Runtime format metadata (PixelFormatis an alias ofBufferKind).Palette<S>: Palette storage for fixed-color workflows (1 to 256 entries).IndexedImage<S>: Indexed output (Vec<u8>indices) paired with a typed palette.QuantizeMode<'a, S>: Common quantization target model used by ordered/diffusion/stochastic/dot/Riemersma families.Error/Result<T>: Crate-level error and result surface.
Supported runtime kinds:
Gray8,Rgb8,Rgba8Gray16,Rgb16,Rgba16Gray32F,Rgb32F,Rgba32F
Typed buffer aliases:
GrayBuffer8,RgbBuffer8,RgbaBuffer8GrayBuffer16,RgbBuffer16,RgbaBuffer16GrayBuffer32F,RgbBuffer32F,RgbaBuffer32F
Constructor helpers:
- Gray:
gray_u8,gray_u16,gray_32f - Rgb:
rgb_u8,rgb_u16,rgb_32f - Rgba:
rgba_u8,rgba_u16,rgba_32f - Packed variants:
gray_u8_packed,rgb_u16_packed,rgba_32f_packed, etc.
Generic constructors remain available on Buffer:
Buffer::new_typed(...)Buffer::new_packed_typed(...)- Compatibility constructors with runtime kind checking:
Buffer::new(...)Buffer::new_packed(...)
QuantizeMode<'a, S> is the canonical quantization model:
GrayLevels(u16)RgbLevels(u16)Palette(&Palette<S>)SingleColor { fg: [S; 3], levels: u16 }
Convenience constructors:
- Generic:
QuantizeMode::gray_levels(levels)QuantizeMode::rgb_levels(levels)QuantizeMode::palette(&palette)QuantizeMode::single_color(fg, levels)
u8compatibility helpers:QuantizeMode::gray_bits(bits)QuantizeMode::rgb_bits(bits)
- Shared conversion helper:
levels_from_bits(bits)
Use GrayLevels/gray_bits when output should be grayscale levels,
RgbLevels/rgb_bits for uniform per-channel color quantization, Palette
for strict membership in a fixed color set, and SingleColor for
foreground-scaled tonal output.
Fast binary dithering with fixed or randomized threshold behavior.
threshold_binary_in_placerandom_binary_in_place
Parallel variants (rayon feature):
threshold_binary_in_place_parrandom_binary_in_place_par
Deterministic threshold-map methods with predictable structure and straightforward benchmarking.
Bayer:
bayer_2x2_in_placebayer_4x4_in_placebayer_8x8_in_placebayer_16x16_in_place
Cluster-dot:
cluster_dot_4x4_in_placecluster_dot_8x8_in_placevoid_and_cluster_in_place
Custom map:
custom_ordered_in_placeadaptive_ordered_dither_in_placespace_filling_curve_ordered_dither_in_placeranked_dither_in_placeimage_based_dither_screen_in_placepolyomino_ordered_dither_in_placestochastic_clustered_dot_in_placeam_fm_hybrid_halftoning_in_placeclustered_am_fm_halftoning_in_placeblue_noise_multitone_dither_in_place
Parallel variants (rayon feature):
bayer_2x2_in_place_parbayer_4x4_in_place_parbayer_8x8_in_place_parbayer_16x16_in_place_parcluster_dot_4x4_in_place_parcluster_dot_8x8_in_place_parcustom_ordered_in_place_par
Ordered methods designed for fixed-palette workflows.
yliluoma_1_in_placeyliluoma_2_in_placeyliluoma_3_in_place
Scanline error diffusion kernels for higher local tonal quality.
floyd_steinberg_in_placefalse_floyd_steinberg_in_placejarvis_judice_ninke_in_placestucki_in_placeburkes_in_placesierra_in_placetwo_row_sierra_in_placesierra_lite_in_placestevenson_arce_in_placeatkinson_in_place
Additional diffusion kernels with different spread patterns.
fan_in_placeshiau_fan_in_placeshiau_fan_2_in_placeblock_error_diffusion_in_place
Scope note: block_error_diffusion_in_place is grayscale-only.
Tone-dependent coefficient families.
ostromoukhov_in_placezhou_fang_in_placehvs_optimized_error_diffusion_in_placegradient_based_error_diffusion_in_placemultiscale_error_diffusion_in_placefeature_preserving_msed_in_placegreen_noise_msed_in_placelinear_pixel_shuffling_in_placetone_dependent_error_diffusion_in_placestructure_aware_error_diffusion_in_placeadaptive_vector_error_diffusion_in_placevector_error_diffusion_in_placesemivector_error_diffusion_in_placehierarchical_error_diffusion_in_placembvq_color_error_diffusion_in_placeneugebauer_color_error_diffusion_in_placemultichannel_green_noise_error_diffusion_in_place
Scope note: variable diffusion methods are grayscale-only except
adaptive_vector_error_diffusion_in_place, vector_error_diffusion_in_place,
semivector_error_diffusion_in_place, and
hierarchical_error_diffusion_in_place,
mbvq_color_error_diffusion_in_place, and
neugebauer_color_error_diffusion_in_place, and
multichannel_green_noise_error_diffusion_in_place, which support Rgb/Rgba
with alpha preservation on Rgba.
Specialized methods with narrower scope than the ordered/diffusion baseline.
riemersma_in_placeknuth_dot_diffusion_in_placeoptimized_dot_diffusion_in_placedirect_binary_search_in_placeclustered_dot_direct_multibit_search_in_placedirect_pattern_control_in_placehierarchical_colorant_dbs_in_placelattice_boltzmann_in_placeelectrostatic_halftoning_in_placemodel_based_med_in_placeleast_squares_model_based_in_place
Scope notes:
direct_binary_search_in_place,clustered_dot_direct_multibit_search_in_place,lattice_boltzmann_in_place,electrostatic_halftoning_in_place,model_based_med_in_place, andleast_squares_model_based_in_placerequire integer grayscale buffers.direct_pattern_control_in_placesupports integerRgb/Rgbabuffers; alpha is preserved forRgba.hierarchical_colorant_dbs_in_placesupports integerRgbbuffers.riemersma_in_place,knuth_dot_diffusion_in_place, andoptimized_dot_diffusion_in_placesupport Gray/Rgb/Rgba layouts, with alpha preserved for Rgba paths.
dithr keeps palette workflows explicit: define a palette, dither into it, and
optionally build indexed output.
use dithr::{rgb_u8, IndexedImage, Palette, Result};
use dithr::ordered::yliluoma_1_in_place;
fn main() -> Result<()> {
let width = 32_usize;
let height = 32_usize;
let mut data = vec![0_u8; width * height * 3];
for y in 0..height {
for x in 0..width {
let i = (y * width + x) * 3;
data[i] = (x * 255 / (width - 1)) as u8;
data[i + 1] = (y * 255 / (height - 1)) as u8;
data[i + 2] = ((x + y) * 255 / (width + height - 2)) as u8;
}
}
let palette = Palette::new(vec![
[0, 0, 0],
[255, 255, 255],
[255, 0, 0],
[0, 0, 255],
])?;
let mut buffer = rgb_u8(&mut data, width, height, width * 3)?;
yliluoma_1_in_place(&mut buffer, &palette)?;
let mut indices = Vec::with_capacity(width * height);
for px in data.chunks_exact(3) {
indices.push(palette.nearest_rgb_index([px[0], px[1], px[2]]) as u8);
}
let indexed = IndexedImage::new(indices, width, height, palette)?;
assert_eq!(indexed.len(), width * height);
Ok(())
}Built-in palette helpers are also exported:
cga_palette()grayscale_2()grayscale_4()grayscale_16()
Enable image to adapt image crate buffers into dithr buffers.
Typed adapters:
gray8_image_as_bufferrgb8_image_as_bufferrgba8_image_as_buffergray16_image_as_bufferrgb16_image_as_bufferrgba16_image_as_bufferrgb32f_image_as_bufferrgba32f_image_as_buffer
Dynamic adapter:
dynamic_image_as_buffer(&mut image::DynamicImage) -> Result<DynamicImageBuffer<'_>>
Dynamic variants:
DynamicImageBuffer::Gray8DynamicImageBuffer::Rgb8DynamicImageBuffer::Rgba8DynamicImageBuffer::Gray16DynamicImageBuffer::Rgb16DynamicImageBuffer::Rgba16DynamicImageBuffer::Rgb32FDynamicImageBuffer::Rgba32F
DynamicImage::ImageLumaA8 and DynamicImage::ImageLumaA16 are promoted to
DynamicImageBuffer::Rgba8 and DynamicImageBuffer::Rgba16 during adaptation.
Current manifest configuration enables PNG and JPEG codecs for the optional
image dependency.
Enable rayon for parallel wrappers where available.
Parallelized families:
- Ordered: all
*_in_place_parordered wrappers - Yliluoma:
yliluoma_1_in_place_par,yliluoma_2_in_place_par,yliluoma_3_in_place_par - Binary stochastic:
threshold_binary_in_place_par,random_binary_in_place_par
Current serial-only families:
- Diffusion (classic/extended/variable)
- Advanced halftoning
Raw buffer workflows:
cargo run --example gray_buffer
cargo run --example rgb_buffer
cargo run --example indexed_paletteImage workflows (image feature):
cargo run --example image_bayer_png --features image -- input.png output.png
cargo run --example image_palette_png --features image -- input.png output.pngBench families (criterion):
stochasticorderedyliluomadiffusionadvanced
cargo bench --no-run
cargo bench --bench stochastic
cargo bench --bench ordered
cargo bench --bench yliluoma
cargo bench --bench diffusion
cargo bench --bench advancedDevelopment checks:
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo check --all-features
cargo test --workspace --all-features --lib --tests --examples
cargo test --doc --all-features- Core processing is in-memory and buffer-first; it does not provide general image editing workflows.
- Not every algorithm supports every format/layout/sample combination.
am_fm_hybrid_halftoning_in_placeandclustered_am_fm_halftoning_in_placeare grayscale-only.blue_noise_multitone_dither_in_placeis grayscale-only.- Variable diffusion methods are grayscale-only except
adaptive_vector_error_diffusion_in_place,vector_error_diffusion_in_place,semivector_error_diffusion_in_place,mbvq_color_error_diffusion_in_place, andneugebauer_color_error_diffusion_in_place, andmultichannel_green_noise_error_diffusion_in_placefor Rgb/Rgba. block_error_diffusion_in_placeis grayscale-only.direct_binary_search_in_place,clustered_dot_direct_multibit_search_in_place,lattice_boltzmann_in_place, andelectrostatic_halftoning_in_placeare integer grayscale-only.- Parallel wrappers are currently provided for ordered, Yliluoma, and binary stochastic families.
- Dither: https://en.wikipedia.org/wiki/Dither
- Ordered dithering: https://en.wikipedia.org/wiki/Ordered_dithering
- Error diffusion: https://en.wikipedia.org/wiki/Error_diffusion
- Yliluoma positional dithering: http://bisqwit.iki.fi/story/howto/dither/jy/
- Dithering eleven algorithms: https://tannerhelland.com/2012/12/28/dithering-eleven-algorithms-source-code.html
- Ostromoukhov variable-coefficient diffusion: https://www.iro.umontreal.ca/~ostrom/publications/pdf/SIGGRAPH01_varcoeffED.pdf
- Zhou-Fang threshold modulation: https://history.siggraph.org/learning/improving-mid-tone-quality-of-variable-coefficient-error-diffusion-using-threshold-modulation-by-zhou-and-fang/
- Multiscale error diffusion: https://doi.org/10.1109/83.557360, https://mcl.usc.edu/wp-content/uploads/2014/01/1997-03-A-multiscale-error-diffusion-technique-for-digital-Halftoning.pdf
- Feature-preserving multiscale error diffusion: https://ira.lib.polyu.edu.hk/bitstream/10397/1524/1/J-JEI-Feature-preserving%20multiscale%20error%20diffusion_04.pdf, https://doi.org/10.1117/1.1758728
- Green-noise multiscale error diffusion: https://pubmed.ncbi.nlm.nih.gov/20215075/, https://www.eie.polyu.edu.hk/~enyhchan/J-TIP-Green_noise_digital_halftoning_with_MED.pdf
- Multichannel green-noise error diffusion: https://doi.org/10.1109/83.841537, https://doi.org/10.1364/JOSAA.16.001575
- Adaptive vector error diffusion: https://pubmed.ncbi.nlm.nih.gov/18282985/, https://doi.org/10.1109/83.597270
- HVS-optimized error diffusion (Kolpatzik-Bouman): https://engineering.purdue.edu/~bouman/publications/pdf/jei1scan.pdf, https://engineering.purdue.edu/~bouman/publications/pub_doc.html
- Vector and semivector color error diffusion: https://pubmed.ncbi.nlm.nih.gov/18255498/, https://doi.org/10.1109/83.951540, https://users.ece.utexas.edu/~bevans/papers/2003/colorDiffusion/index.html
- MBVQ/Neugebauer color error diffusion: https://www.mdpi.com/2313-433X/6/4/23, https://doi.org/10.3390/jimaging6040023, https://patents.google.com/patent/US5991438A/en, https://patents.google.com/patent/EP0895408A2/en
- Block error diffusion: https://doi.org/10.1109/TIP.2005.859776, https://shiftleft.com/mirrors/www.hpl.hp.com/personal/Niranjan_Damera-Venkata/files/ibc.pdf
- Hierarchical error diffusion: https://pubmed.ncbi.nlm.nih.gov/19473943/, https://doi.org/10.1109/TIP.2009.2019778
- Tone-dependent error diffusion: https://pubmed.ncbi.nlm.nih.gov/15376941/, https://pubmed.ncbi.nlm.nih.gov/17283778/
- Structure-aware error diffusion: https://perso.liris.cnrs.fr/ostrom/publications/pdf/SIGGRAPH-ASIA09_saed.pdf
- Linear pixel shuffling error diffusion: https://repository.rit.edu/other/391/, https://www.imaging.org/common/uploaded%20files/pdfs/Papers/2000/PICS-0-81/1625.pdf
- Riemersma dithering: https://www.compuphase.com/riemer.htm
- Knuth dot diffusion: https://dl.acm.org/doi/10.1145/35039.35040
- Optimized dot diffusion: https://doi.org/10.1109/83.841944
- Direct binary search halftoning: https://doi.org/10.1117/12.135959
- Model-based halftoning (MED + LSMB): https://pubmed.ncbi.nlm.nih.gov/18282991/, https://doi.org/10.1117/12.135965, https://users.eecs.northwestern.edu/~pappas/papers/pappas_neuhoff_tip99.pdf
- Lattice-Boltzmann halftoning: https://www.mia.uni-saarland.de/Publications/hagenburg-isvc09.pdf
- Electrostatic halftoning: https://onlinelibrary.wiley.com/doi/10.1111/j.1467-8659.2010.01716.x
- Void-and-cluster dithering: https://docslib.org/doc/9596963/the-void-and-cluster-method-for-dither-array-generation, https://cv.ulichney.com/papers/1994-filter-design.pdf
- Adaptive ordered dither: https://doi.org/10.1006/gmip.1996.0414, https://www.sciencedirect.com/science/article/pii/S1077316996904141
- Space-filling curve ordered dither: https://doi.org/10.1016/S0097-8493(98)00043-0, https://www.sciencedirect.com/science/article/pii/S0097849398000430
- Ranked dither: https://www.mayagupta.org/publications/GuptaBowenSPIE07.pdf
- Image-based dither screens: https://graphicsinterface.org/wp-content/uploads/gi1999-22.pdf, https://doi.org/10.20380/GI1999.22
- Polyomino-based digital halftoning: https://arxiv.org/abs/0812.1647, https://doi.org/10.48550/arXiv.0812.1647
- Stochastic clustered-dot dithering: https://perso.liris.cnrs.fr/victor.ostromoukhov/publications/pdf/SPIE99_StochasticClust.pdf, https://pubmed.ncbi.nlm.nih.gov/18255440/
- AM/FM hybrid halftoning: https://engineering.purdue.edu/~bouman/publications/orig-pdf/jei8.pdf, https://doi.org/10.1117/12.643690
- Clustered AM/FM halftoning: https://doi.org/10.2352/ISSN.2169-4451.2004.20.1.art00025_2, https://dblp.org/db/conf/clrimg/clrimg2006.html
- Blue-noise multitone dithering: https://doi.org/10.1109/TIP.2008.926145, https://www.eecis.udel.edu/~arce/files/Publications/5-Multitone.pdf
- Clustered-dot direct multibit search: https://pubmed.ncbi.nlm.nih.gov/28113172/, https://doi.org/10.1109/TIP.2016.2552723, https://research.ibm.com/publications/hybrid-halftoning-using-direct-multi-bit-search-dms-screen-algorithm
- Direct pattern control halftoning: https://pubmed.ncbi.nlm.nih.gov/28613170/, https://doi.org/10.1109/TIP.2017.2713939, https://cv.ulichney.com/papers/2017-PARAWACS-IEEE.pdf
- Hierarchical colorant DBS (MBVQ-guided): https://pubmed.ncbi.nlm.nih.gov/20236895/, https://doi.org/10.1109/TIP.2010.2045690, https://pubmed.ncbi.nlm.nih.gov/28613170/
MIT. See LICENSE.

