dithr 0.3.0

Buffer-first rust dithering and halftoning library.
Documentation
use super::core::write_quantized_pixel;
use crate::{
    core::{read_unit_pixel, PixelLayout, Sample},
    quantize_pixel, Buffer, BufferError, Error, QuantizeMode, Result,
};

const BLOCK_WIDTH: usize = 2;
const BLOCK_HEIGHT: usize = 2;
const BLOCK_WEIGHT_RIGHT: f32 = 7.0 / 16.0;
const BLOCK_WEIGHT_DOWN_LEFT: f32 = 3.0 / 16.0;
const BLOCK_WEIGHT_DOWN: f32 = 5.0 / 16.0;
const BLOCK_WEIGHT_DOWN_RIGHT: f32 = 1.0 / 16.0;

pub fn block_error_diffusion_in_place<S: Sample, L: PixelLayout>(
    buffer: &mut Buffer<'_, S, L>,
    mode: QuantizeMode<'_, S>,
) -> Result<()> {
    buffer.validate()?;

    if L::COLOR_CHANNELS != 1 || L::HAS_ALPHA {
        return Err(Error::UnsupportedFormat(
            "block error diffusion supports grayscale formats only",
        ));
    }

    let width = buffer.width;
    let height = buffer.height;
    let channels = L::CHANNELS;
    let blocks_w = width
        .checked_add(BLOCK_WIDTH - 1)
        .ok_or(Error::InvalidArgument("block grid width overflow"))?
        / BLOCK_WIDTH;
    let blocks_h = height
        .checked_add(BLOCK_HEIGHT - 1)
        .ok_or(Error::InvalidArgument("block grid height overflow"))?
        / BLOCK_HEIGHT;
    let block_count = blocks_w
        .checked_mul(blocks_h)
        .ok_or(Error::InvalidArgument("block grid size overflow"))?;
    let mut block_errors = vec![0.0_f32; block_count];

    for by in 0..blocks_h {
        for bx in 0..blocks_w {
            let block_idx = by * blocks_w + bx;
            let carry = block_errors[block_idx];

            let x_start = bx
                .checked_mul(BLOCK_WIDTH)
                .ok_or(Error::InvalidArgument("block x start overflow"))?;
            let y_start = by
                .checked_mul(BLOCK_HEIGHT)
                .ok_or(Error::InvalidArgument("block y start overflow"))?;
            let x_end = x_start
                .checked_add(BLOCK_WIDTH)
                .ok_or(Error::InvalidArgument("block x end overflow"))?
                .min(width);
            let y_end = y_start
                .checked_add(BLOCK_HEIGHT)
                .ok_or(Error::InvalidArgument("block y end overflow"))?
                .min(height);

            let mut residual_sum = 0.0_f32;
            let mut residual_count = 0_usize;

            for y in y_start..y_end {
                let row = buffer.try_row_mut(y)?;
                for x in x_start..x_end {
                    let offset = x.checked_mul(channels).ok_or(BufferError::OutOfBounds)?;
                    let end = offset
                        .checked_add(channels)
                        .ok_or(BufferError::OutOfBounds)?;
                    let pixel = row.get_mut(offset..end).ok_or(BufferError::OutOfBounds)?;
                    let source = read_unit_pixel::<S, L>(pixel)?;
                    let adjusted_unit = (source[0] + carry).clamp(0.0, 1.0);
                    let adjusted = [S::from_unit_f32(adjusted_unit); 4];
                    let quantized = quantize_pixel::<S, L>(&adjusted[..channels], mode)?;
                    let quantized_unit = read_unit_pixel::<S, L>(&quantized[..channels])?;
                    write_quantized_pixel::<S, L>(pixel, quantized);
                    residual_sum += adjusted_unit - quantized_unit[0];
                    residual_count += 1;
                }
            }

            if residual_count == 0 {
                continue;
            }

            let block_residual = residual_sum / residual_count as f32;
            add_block_error(
                &mut block_errors,
                blocks_w,
                blocks_h,
                bx as isize + 1,
                by as isize,
                block_residual * BLOCK_WEIGHT_RIGHT,
            );
            add_block_error(
                &mut block_errors,
                blocks_w,
                blocks_h,
                bx as isize - 1,
                by as isize + 1,
                block_residual * BLOCK_WEIGHT_DOWN_LEFT,
            );
            add_block_error(
                &mut block_errors,
                blocks_w,
                blocks_h,
                bx as isize,
                by as isize + 1,
                block_residual * BLOCK_WEIGHT_DOWN,
            );
            add_block_error(
                &mut block_errors,
                blocks_w,
                blocks_h,
                bx as isize + 1,
                by as isize + 1,
                block_residual * BLOCK_WEIGHT_DOWN_RIGHT,
            );
        }
    }

    Ok(())
}

fn add_block_error(
    block_errors: &mut [f32],
    blocks_w: usize,
    blocks_h: usize,
    bx: isize,
    by: isize,
    delta: f32,
) {
    if bx < 0 || by < 0 {
        return;
    }

    let bx = bx as usize;
    let by = by as usize;
    if bx >= blocks_w || by >= blocks_h {
        return;
    }

    let idx = by * blocks_w + bx;
    block_errors[idx] += delta;
}