dithr 0.3.0

Buffer-first rust dithering and halftoning library.
Documentation
mod adaptive;
mod am_fm;
mod bayer;
mod cluster;
pub(crate) mod core;
mod image_based_screen;
mod multitone;
mod polyomino;
mod ranked;
mod space_filling;
mod stochastic_cluster;
mod void_cluster;
mod yliluoma;

use crate::{
    core::{PixelLayout, Sample},
    Buffer, Palette, QuantizeMode, Result,
};
pub(crate) const DEFAULT_STRENGTH: f32 = 64.0 / 255.0;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrderedError {
    EmptyMap,
    InvalidDimensions,
    ValueOutOfRange,
}

impl std::fmt::Display for OrderedError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::EmptyMap => f.write_str("ordered threshold map must not be empty"),
            Self::InvalidDimensions => f.write_str("ordered threshold map dimensions are invalid"),
            Self::ValueOutOfRange => {
                f.write_str("ordered threshold map contains values out of range")
            }
        }
    }
}

impl std::error::Error for OrderedError {}

pub use adaptive::adaptive_ordered_dither_in_place;
pub use am_fm::{am_fm_hybrid_halftoning_in_place, clustered_am_fm_halftoning_in_place};
pub use bayer::{bayer_16x16_in_place, bayer_2x2_in_place, bayer_4x4_in_place, bayer_8x8_in_place};
#[cfg(feature = "rayon")]
pub use bayer::{
    bayer_16x16_in_place_par, bayer_2x2_in_place_par, bayer_4x4_in_place_par,
    bayer_8x8_in_place_par,
};
pub use cluster::{cluster_dot_4x4_in_place, cluster_dot_8x8_in_place};
#[cfg(feature = "rayon")]
pub use cluster::{cluster_dot_4x4_in_place_par, cluster_dot_8x8_in_place_par};
pub use image_based_screen::image_based_dither_screen_in_place;
pub use multitone::blue_noise_multitone_dither_in_place;
pub use polyomino::polyomino_ordered_dither_in_place;
pub use ranked::ranked_dither_in_place;
pub use space_filling::space_filling_curve_ordered_dither_in_place;
pub use stochastic_cluster::stochastic_clustered_dot_in_place;
pub use void_cluster::void_and_cluster_in_place;

#[doc(hidden)]
pub(crate) fn ordered_dither_in_place<S: Sample, L: PixelLayout>(
    buffer: &mut Buffer<'_, S, L>,
    mode: QuantizeMode<'_, S>,
    map: &[u16],
    map_w: usize,
    map_h: usize,
    strength: f32,
) -> Result<()> {
    core::ordered_dither_in_place(buffer, mode, map, map_w, map_h, strength)
}

#[cfg(feature = "rayon")]
pub(crate) fn ordered_dither_in_place_par<S: Sample, L: PixelLayout>(
    buffer: &mut Buffer<'_, S, L>,
    mode: QuantizeMode<'_, S>,
    map: &[u16],
    map_w: usize,
    map_h: usize,
    strength: f32,
) -> Result<()> {
    core::ordered_dither_in_place_par(buffer, mode, map, map_w, map_h, strength)
}

pub fn custom_ordered_in_place<S: Sample, L: PixelLayout>(
    buffer: &mut Buffer<'_, S, L>,
    mode: QuantizeMode<'_, S>,
    map: &[u8],
    map_w: usize,
    map_h: usize,
    strength: i16,
) -> Result<()> {
    validate_custom_map(map, map_w, map_h)?;
    let ranked_map: Vec<u16> = map.iter().map(|&value| u16::from(value)).collect();

    ordered_dither_in_place(
        buffer,
        mode,
        &ranked_map,
        map_w,
        map_h,
        f32::from(strength) / 255.0,
    )
}

#[cfg(feature = "rayon")]
pub fn custom_ordered_in_place_par<S: Sample, L: PixelLayout>(
    buffer: &mut Buffer<'_, S, L>,
    mode: QuantizeMode<'_, S>,
    map: &[u8],
    map_w: usize,
    map_h: usize,
    strength: i16,
) -> Result<()> {
    validate_custom_map(map, map_w, map_h)?;
    let ranked_map: Vec<u16> = map.iter().map(|&value| u16::from(value)).collect();

    ordered_dither_in_place_par(
        buffer,
        mode,
        &ranked_map,
        map_w,
        map_h,
        f32::from(strength) / 255.0,
    )
}

pub fn yliluoma_1_in_place<S: Sample, L: PixelLayout>(
    buffer: &mut Buffer<'_, S, L>,
    palette: &Palette<S>,
) -> Result<()> {
    yliluoma::yliluoma_1_in_place(buffer, palette)
}

#[cfg(feature = "rayon")]
pub fn yliluoma_1_in_place_par<S: Sample, L: PixelLayout>(
    buffer: &mut Buffer<'_, S, L>,
    palette: &Palette<S>,
) -> Result<()> {
    yliluoma::yliluoma_1_in_place_par(buffer, palette)
}

pub fn yliluoma_2_in_place<S: Sample, L: PixelLayout>(
    buffer: &mut Buffer<'_, S, L>,
    palette: &Palette<S>,
) -> Result<()> {
    yliluoma::yliluoma_2_in_place(buffer, palette)
}

#[cfg(feature = "rayon")]
pub fn yliluoma_2_in_place_par<S: Sample, L: PixelLayout>(
    buffer: &mut Buffer<'_, S, L>,
    palette: &Palette<S>,
) -> Result<()> {
    yliluoma::yliluoma_2_in_place_par(buffer, palette)
}

pub fn yliluoma_3_in_place<S: Sample, L: PixelLayout>(
    buffer: &mut Buffer<'_, S, L>,
    palette: &Palette<S>,
) -> Result<()> {
    yliluoma::yliluoma_3_in_place(buffer, palette)
}

#[cfg(feature = "rayon")]
pub fn yliluoma_3_in_place_par<S: Sample, L: PixelLayout>(
    buffer: &mut Buffer<'_, S, L>,
    palette: &Palette<S>,
) -> Result<()> {
    yliluoma::yliluoma_3_in_place_par(buffer, palette)
}

fn validate_custom_map(
    map: &[u8],
    map_w: usize,
    map_h: usize,
) -> std::result::Result<(), OrderedError> {
    if map.is_empty() {
        return Err(OrderedError::EmptyMap);
    }

    let expected_len = map_w
        .checked_mul(map_h)
        .ok_or(OrderedError::InvalidDimensions)?;
    if expected_len == 0 || expected_len != map.len() {
        return Err(OrderedError::InvalidDimensions);
    }

    if map.iter().any(|&value| usize::from(value) >= expected_len) {
        return Err(OrderedError::ValueOutOfRange);
    }

    Ok(())
}