Buffer-first rust dithering and halftoning library.
See the codedithr| 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.
u8, u16, and f32 sample types across Gray,
Rgb, and Rgba layouts.QuantizeMode for grayscale levels, RGB
levels, palette mapping, or single-color workflows.Palette<S> and IndexedImage<S> for
constrained output and indexed results.image adapters for DynamicImage workflows and
rayon parallel wrappers for selected families.cargo add dithr
[dependencies]
dithr = "0.3.0"
cargo add dithr --features image
cargo add dithr --features rayon
use 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 (PixelFormat is
an alias of BufferKind).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, Rgba32FTyped buffer aliases:
GrayBuffer8, RgbBuffer8, RgbaBuffer8GrayBuffer16, RgbBuffer16, RgbaBuffer16GrayBuffer32F, RgbBuffer32F, RgbaBuffer32FConstructor helpers:
gray_u8, gray_u16, gray_32frgb_u8, rgb_u16, rgb_32frgba_u8, rgba_u16, rgba_32fgray_u8_packed, rgb_u16_packed, rgba_32f_packed, etc.Generic constructors remain available on Buffer:
Buffer::new_typed(...)Buffer::new_packed_typed(...)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:
QuantizeMode::gray_levels(levels)QuantizeMode::rgb_levels(levels)QuantizeMode::palette(&palette)QuantizeMode::single_color(fg, levels)u8 compatibility helpers:
QuantizeMode::gray_bits(bits)QuantizeMode::rgb_bits(bits)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_placeParallel variants (rayon feature):
threshold_binary_in_place_parrandom_binary_in_place_parDeterministic threshold-map methods with predictable structure and straightforward benchmarking.
Bayer:
bayer_2x2_in_placebayer_4x4_in_placebayer_8x8_in_placebayer_16x16_in_placeCluster-dot:
cluster_dot_4x4_in_placecluster_dot_8x8_in_placevoid_and_cluster_in_placeCustom 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_placeParallel 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_parOrdered methods designed for fixed-palette workflows.
yliluoma_1_in_placeyliluoma_2_in_placeyliluoma_3_in_placeScanline 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_placeAdditional diffusion kernels with different spread patterns.
fan_in_placeshiau_fan_in_placeshiau_fan_2_in_placeblock_error_diffusion_in_placeScope 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_placeScope 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_placeScope 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, and least_squares_model_based_in_place require
integer grayscale buffers.direct_pattern_control_in_place supports integer Rgb/Rgba buffers;
alpha is preserved for Rgba.hierarchical_colorant_dbs_in_place supports integer Rgb buffers.riemersma_in_place, knuth_dot_diffusion_in_place, and
optimized_dot_diffusion_in_place support 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_bufferDynamic adapter:
dynamic_image_as_buffer(&mut image::DynamicImage) -> Result<DynamicImageBuffer<'_>>Dynamic variants:
DynamicImageBuffer::Gray8DynamicImageBuffer::Rgb8DynamicImageBuffer::Rgba8DynamicImageBuffer::Gray16DynamicImageBuffer::Rgb16DynamicImageBuffer::Rgba16DynamicImageBuffer::Rgb32FDynamicImageBuffer::Rgba32FDynamicImage::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:
*_in_place_par ordered wrappersyliluoma_1_in_place_par, yliluoma_2_in_place_par,
yliluoma_3_in_place_parthreshold_binary_in_place_par,
random_binary_in_place_parCurrent serial-only families:
Raw buffer workflows:
cargo run --example gray_buffer
cargo run --example rgb_buffer
cargo run --example indexed_palette
Image 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.png
Bench families (criterion):
stochasticorderedyliluomadiffusionadvancedcargo bench --no-run
cargo bench --bench stochastic
cargo bench --bench ordered
cargo bench --bench yliluoma
cargo bench --bench diffusion
cargo bench --bench advanced
Development 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
am_fm_hybrid_halftoning_in_place and
clustered_am_fm_halftoning_in_place are grayscale-only.blue_noise_multitone_dither_in_place is grayscale-only.adaptive_vector_error_diffusion_in_place,
vector_error_diffusion_in_place,
semivector_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 for Rgb/Rgba.block_error_diffusion_in_place is grayscale-only.direct_binary_search_in_place,
clustered_dot_direct_multibit_search_in_place,
lattice_boltzmann_in_place, and electrostatic_halftoning_in_place are
integer grayscale-only.MIT. See LICENSE.
171 commits
Rust
100.0%
Buffer-first rust dithering and halftoning library.
See the codedithr| 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.
u8, u16, and f32 sample types across Gray,
Rgb, and Rgba layouts.QuantizeMode for grayscale levels, RGB
levels, palette mapping, or single-color workflows.Palette<S> and IndexedImage<S> for
constrained output and indexed results.image adapters for DynamicImage workflows and
rayon parallel wrappers for selected families.cargo add dithr
[dependencies]
dithr = "0.3.0"
cargo add dithr --features image
cargo add dithr --features rayon
use 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 (PixelFormat is
an alias of BufferKind).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, Rgba32FTyped buffer aliases:
GrayBuffer8, RgbBuffer8, RgbaBuffer8GrayBuffer16, RgbBuffer16, RgbaBuffer16GrayBuffer32F, RgbBuffer32F, RgbaBuffer32FConstructor helpers:
gray_u8, gray_u16, gray_32frgb_u8, rgb_u16, rgb_32frgba_u8, rgba_u16, rgba_32fgray_u8_packed, rgb_u16_packed, rgba_32f_packed, etc.Generic constructors remain available on Buffer:
Buffer::new_typed(...)Buffer::new_packed_typed(...)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:
QuantizeMode::gray_levels(levels)QuantizeMode::rgb_levels(levels)QuantizeMode::palette(&palette)QuantizeMode::single_color(fg, levels)u8 compatibility helpers:
QuantizeMode::gray_bits(bits)QuantizeMode::rgb_bits(bits)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_placeParallel variants (rayon feature):
threshold_binary_in_place_parrandom_binary_in_place_parDeterministic threshold-map methods with predictable structure and straightforward benchmarking.
Bayer:
bayer_2x2_in_placebayer_4x4_in_placebayer_8x8_in_placebayer_16x16_in_placeCluster-dot:
cluster_dot_4x4_in_placecluster_dot_8x8_in_placevoid_and_cluster_in_placeCustom 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_placeParallel 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_parOrdered methods designed for fixed-palette workflows.
yliluoma_1_in_placeyliluoma_2_in_placeyliluoma_3_in_placeScanline 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_placeAdditional diffusion kernels with different spread patterns.
fan_in_placeshiau_fan_in_placeshiau_fan_2_in_placeblock_error_diffusion_in_placeScope 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_placeScope 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_placeScope 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, and least_squares_model_based_in_place require
integer grayscale buffers.direct_pattern_control_in_place supports integer Rgb/Rgba buffers;
alpha is preserved for Rgba.hierarchical_colorant_dbs_in_place supports integer Rgb buffers.riemersma_in_place, knuth_dot_diffusion_in_place, and
optimized_dot_diffusion_in_place support 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_bufferDynamic adapter:
dynamic_image_as_buffer(&mut image::DynamicImage) -> Result<DynamicImageBuffer<'_>>Dynamic variants:
DynamicImageBuffer::Gray8DynamicImageBuffer::Rgb8DynamicImageBuffer::Rgba8DynamicImageBuffer::Gray16DynamicImageBuffer::Rgb16DynamicImageBuffer::Rgba16DynamicImageBuffer::Rgb32FDynamicImageBuffer::Rgba32FDynamicImage::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:
*_in_place_par ordered wrappersyliluoma_1_in_place_par, yliluoma_2_in_place_par,
yliluoma_3_in_place_parthreshold_binary_in_place_par,
random_binary_in_place_parCurrent serial-only families:
Raw buffer workflows:
cargo run --example gray_buffer
cargo run --example rgb_buffer
cargo run --example indexed_palette
Image 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.png
Bench families (criterion):
stochasticorderedyliluomadiffusionadvancedcargo bench --no-run
cargo bench --bench stochastic
cargo bench --bench ordered
cargo bench --bench yliluoma
cargo bench --bench diffusion
cargo bench --bench advanced
Development 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
am_fm_hybrid_halftoning_in_place and
clustered_am_fm_halftoning_in_place are grayscale-only.blue_noise_multitone_dither_in_place is grayscale-only.adaptive_vector_error_diffusion_in_place,
vector_error_diffusion_in_place,
semivector_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 for Rgb/Rgba.block_error_diffusion_in_place is grayscale-only.direct_binary_search_in_place,
clustered_dot_direct_multibit_search_in_place,
lattice_boltzmann_in_place, and electrostatic_halftoning_in_place are
integer grayscale-only.MIT. See LICENSE.
171 commits
Rust
100.0%