mod common;
use common::{
checker_8x8, fnv1a64, gray_ramp_16x16, gray_ramp_8x8, gray_ramp_8x8_u16, rgb_cube_strip,
rgb_gradient_8x8, rgb_gradient_8x8_f32, rgb_gradient_8x8_u16,
};
use dithr::core::PixelLayout;
use dithr::data::maps::generate_void_and_cluster_64x64_flat;
use dithr::data::{
generate_bayer_16x16, BAYER_2X2, BAYER_4X4, BAYER_8X8, CLUSTER_DOT_4X4, CLUSTER_DOT_8X8,
};
use dithr::ordered::{
adaptive_ordered_dither_in_place, am_fm_hybrid_halftoning_in_place, bayer_16x16_in_place,
bayer_2x2_in_place, bayer_4x4_in_place, bayer_8x8_in_place,
blue_noise_multitone_dither_in_place, cluster_dot_4x4_in_place, cluster_dot_8x8_in_place,
clustered_am_fm_halftoning_in_place, custom_ordered_in_place,
image_based_dither_screen_in_place, polyomino_ordered_dither_in_place, ranked_dither_in_place,
space_filling_curve_ordered_dither_in_place, stochastic_clustered_dot_in_place,
void_and_cluster_in_place, yliluoma_1_in_place, yliluoma_2_in_place, yliluoma_3_in_place,
};
#[cfg(feature = "rayon")]
use dithr::ordered::{yliluoma_1_in_place_par, yliluoma_2_in_place_par, yliluoma_3_in_place_par};
use dithr::{Error, IndexedImage, IndexedImage16, OrderedError, Palette, QuantizeMode};
const BAYER_2X2_FLAT: [u8; 4] = [0, 2, 3, 1];
const BAYER_4X4_FLAT: [u8; 16] = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5];
#[test]
fn bayer_2x2_contains_unique_values() {
assert_unique_square_coverage_2(BAYER_2X2);
}
#[test]
fn bayer_4x4_contains_unique_values() {
assert_unique_square_coverage_4(BAYER_4X4);
}
#[test]
fn bayer_8x8_contains_unique_values() {
assert_unique_square_coverage_8(BAYER_8X8);
}
#[test]
fn generated_bayer_16x16_contains_0_to_255_once() {
let map = generate_bayer_16x16();
let mut seen = [false; 256];
for row in map {
for value in row {
let idx = usize::from(value);
assert!(idx < 256);
assert!(!seen[idx]);
seen[idx] = true;
}
}
assert!(seen.into_iter().all(|entry| entry));
}
#[test]
fn cluster_maps_have_expected_dimensions() {
assert_eq!(CLUSTER_DOT_4X4.len(), 4);
for row in CLUSTER_DOT_4X4 {
assert_eq!(row.len(), 4);
}
assert_eq!(CLUSTER_DOT_8X8.len(), 8);
for row in CLUSTER_DOT_8X8 {
assert_eq!(row.len(), 8);
}
}
#[test]
fn bayer_2x2_quantizes_only_to_allowed_values() {
let mut data: Vec<u8> = (0_u16..64).map(|value| (value * 4) as u8).collect();
let mut buffer = dithr::gray_u8(&mut data, 8, 8, 8).expect("valid buffer should construct");
bayer_2x2_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("bayer 2x2 should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn bayer_4x4_periodicity_matches_4() {
let mut data = vec![127_u8; 64];
let mut buffer = dithr::gray_u8(&mut data, 8, 8, 8).expect("valid buffer should construct");
bayer_4x4_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("bayer 4x4 should succeed");
for y in 0..8 {
for x in 0..4 {
assert_eq!(data[y * 8 + x], data[y * 8 + x + 4]);
}
}
for y in 0..4 {
for x in 0..8 {
assert_eq!(data[y * 8 + x], data[(y + 4) * 8 + x]);
}
}
}
#[test]
fn bayer_8x8_periodicity_matches_8() {
let mut data = vec![127_u8; 256];
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
bayer_8x8_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("bayer 8x8 should succeed");
for y in 0..16 {
for x in 0..8 {
assert_eq!(data[y * 16 + x], data[y * 16 + x + 8]);
}
}
for y in 0..8 {
for x in 0..16 {
assert_eq!(data[y * 16 + x], data[(y + 8) * 16 + x]);
}
}
}
#[test]
fn bayer_16x16_runs_on_16x16_without_panic() {
let mut data: Vec<u8> = (0_u16..256).map(|value| value as u8).collect();
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
bayer_16x16_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("bayer 16x16 should succeed");
assert_eq!(data.len(), 256);
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn cluster_dot_4x4_runs_and_quantizes() {
let mut data: Vec<u8> = (0_u16..64).map(|value| (value * 4) as u8).collect();
let mut buffer = dithr::gray_u8(&mut data, 8, 8, 8).expect("valid buffer should construct");
cluster_dot_4x4_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("cluster-dot 4x4 should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn cluster_dot_8x8_runs_and_quantizes() {
let mut data: Vec<u8> = (0_u16..256).map(|value| value as u8).collect();
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
cluster_dot_8x8_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("cluster-dot 8x8 should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn void_and_cluster_map_is_deterministic_and_ranked() {
let map_a = generate_void_and_cluster_64x64_flat();
let map_b = generate_void_and_cluster_64x64_flat();
assert_eq!(map_a, map_b);
let mut seen = vec![false; 64 * 64];
for &rank in &map_a {
let idx = usize::from(rank);
assert!(idx < 64 * 64);
assert!(!seen[idx]);
seen[idx] = true;
}
assert!(seen.into_iter().all(|entry| entry));
let mut bytes = Vec::with_capacity((64 * 64) * 2);
for &rank in &map_a {
bytes.extend_from_slice(&rank.to_le_bytes());
}
assert_eq!(fnv1a64(&bytes), 14_735_008_391_156_396_837_u64);
}
#[test]
fn void_and_cluster_runs_and_quantizes_u8() {
let mut data = gray_ramp_16x16();
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
void_and_cluster_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("void-and-cluster should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn void_and_cluster_u16_binary_invariant() {
let mut data = gray_ramp_8x8_u16();
let mut buffer = dithr::gray_u16(&mut data, 8, 8, 8).expect("valid buffer should construct");
void_and_cluster_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("void-and-cluster should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn void_and_cluster_f32_binary_invariant() {
let mut data: Vec<f32> = (0..(8 * 8))
.map(|index| index as f32 / ((8 * 8 - 1) as f32))
.collect();
let mut buffer = dithr::gray_32f(&mut data, 8, 8, 8).expect("valid buffer should construct");
void_and_cluster_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("void-and-cluster should succeed");
assert!(data.iter().all(|&value| value == 0.0 || value == 1.0));
}
#[test]
fn adaptive_ordered_runs_and_quantizes_u8() {
let mut data = gray_ramp_16x16();
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
adaptive_ordered_dither_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("adaptive ordered dithering should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn adaptive_ordered_u16_binary_invariant() {
let mut data = gray_ramp_8x8_u16();
let mut buffer = dithr::gray_u16(&mut data, 8, 8, 8).expect("valid buffer should construct");
adaptive_ordered_dither_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("adaptive ordered dithering should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn adaptive_ordered_f32_binary_invariant() {
let mut data: Vec<f32> = (0..(8 * 8))
.map(|index| index as f32 / ((8 * 8 - 1) as f32))
.collect();
let mut buffer = dithr::gray_32f(&mut data, 8, 8, 8).expect("valid buffer should construct");
adaptive_ordered_dither_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("adaptive ordered dithering should succeed");
assert!(data.iter().all(|&value| value == 0.0 || value == 1.0));
}
#[test]
fn adaptive_ordered_rgba_alpha_preserved() {
let mut data: Vec<u8> = (0_u16..80)
.map(|value| ((value * 37 + 19) % 256) as u8)
.collect();
let before_alpha: Vec<u8> = data.iter().skip(3).step_by(4).copied().collect();
let mut buffer = dithr::rgba_u8(&mut data, 4, 5, 16).expect("valid buffer should construct");
adaptive_ordered_dither_in_place(
&mut buffer,
QuantizeMode::rgb_bits(2).expect("valid bit depth"),
)
.expect("adaptive ordered dithering should succeed");
let after_alpha: Vec<u8> = data.iter().skip(3).step_by(4).copied().collect();
assert_eq!(before_alpha, after_alpha);
}
#[test]
fn space_filling_curve_ordered_runs_and_quantizes_u8() {
let mut data = gray_ramp_16x16();
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
space_filling_curve_ordered_dither_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("space-filling ordered dithering should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn space_filling_curve_ordered_u16_binary_invariant() {
let mut data = gray_ramp_8x8_u16();
let mut buffer = dithr::gray_u16(&mut data, 8, 8, 8).expect("valid buffer should construct");
space_filling_curve_ordered_dither_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("space-filling ordered dithering should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn space_filling_curve_ordered_f32_binary_invariant() {
let mut data: Vec<f32> = (0..(8 * 8))
.map(|index| index as f32 / ((8 * 8 - 1) as f32))
.collect();
let mut buffer = dithr::gray_32f(&mut data, 8, 8, 8).expect("valid buffer should construct");
space_filling_curve_ordered_dither_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("space-filling ordered dithering should succeed");
assert!(data.iter().all(|&value| value == 0.0 || value == 1.0));
}
#[test]
fn space_filling_curve_ordered_traversal_order_is_stable() {
let mut data_a = vec![127_u8; 16 * 16];
let mut data_b = vec![127_u8; 16 * 16];
let mut buffer_a =
dithr::gray_u8(&mut data_a, 16, 16, 16).expect("valid buffer should construct");
let mut buffer_b =
dithr::gray_u8(&mut data_b, 16, 16, 16).expect("valid buffer should construct");
space_filling_curve_ordered_dither_in_place(
&mut buffer_a,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("space-filling ordered dithering should succeed");
space_filling_curve_ordered_dither_in_place(
&mut buffer_b,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("space-filling ordered dithering should succeed");
assert_eq!(data_a, data_b);
assert_eq!(fnv1a64(&data_a), 10_367_374_070_803_408_373_u64);
}
#[test]
fn ranked_dither_runs_and_quantizes_u8() {
let mut data = gray_ramp_16x16();
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
ranked_dither_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("ranked dither should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn ranked_dither_u16_binary_invariant() {
let mut data = gray_ramp_8x8_u16();
let mut buffer = dithr::gray_u16(&mut data, 8, 8, 8).expect("valid buffer should construct");
ranked_dither_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("ranked dither should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn ranked_dither_f32_binary_invariant() {
let mut data: Vec<f32> = (0..(8 * 8))
.map(|index| index as f32 / ((8 * 8 - 1) as f32))
.collect();
let mut buffer = dithr::gray_32f(&mut data, 8, 8, 8).expect("valid buffer should construct");
ranked_dither_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("ranked dither should succeed");
assert!(data.iter().all(|&value| value == 0.0 || value == 1.0));
}
#[test]
fn ranked_dither_rank_map_is_deterministic() {
let mut data_a = vec![127_u8; 16 * 16];
let mut data_b = vec![127_u8; 16 * 16];
let mut buffer_a =
dithr::gray_u8(&mut data_a, 16, 16, 16).expect("valid buffer should construct");
let mut buffer_b =
dithr::gray_u8(&mut data_b, 16, 16, 16).expect("valid buffer should construct");
ranked_dither_in_place(
&mut buffer_a,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("ranked dither should succeed");
ranked_dither_in_place(
&mut buffer_b,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("ranked dither should succeed");
assert_eq!(data_a, data_b);
assert_eq!(fnv1a64(&data_a), 690_402_364_730_067_173_u64);
}
#[test]
fn image_based_dither_screen_runs_and_quantizes_u8() {
let mut data = gray_ramp_16x16();
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
image_based_dither_screen_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("image-based dither screen should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn image_based_dither_screen_u16_binary_invariant() {
let mut data = gray_ramp_8x8_u16();
let mut buffer = dithr::gray_u16(&mut data, 8, 8, 8).expect("valid buffer should construct");
image_based_dither_screen_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("image-based dither screen should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn image_based_dither_screen_f32_binary_invariant() {
let mut data: Vec<f32> = (0..(8 * 8))
.map(|index| index as f32 / ((8 * 8 - 1) as f32))
.collect();
let mut buffer = dithr::gray_32f(&mut data, 8, 8, 8).expect("valid buffer should construct");
image_based_dither_screen_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("image-based dither screen should succeed");
assert!(data.iter().all(|&value| value == 0.0 || value == 1.0));
}
#[test]
fn image_based_dither_screen_generated_screen_is_deterministic() {
let mut data_a = vec![127_u8; 16 * 16];
let mut data_b = vec![127_u8; 16 * 16];
let mut buffer_a =
dithr::gray_u8(&mut data_a, 16, 16, 16).expect("valid buffer should construct");
let mut buffer_b =
dithr::gray_u8(&mut data_b, 16, 16, 16).expect("valid buffer should construct");
image_based_dither_screen_in_place(
&mut buffer_a,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("image-based dither screen should succeed");
image_based_dither_screen_in_place(
&mut buffer_b,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("image-based dither screen should succeed");
assert_eq!(data_a, data_b);
assert_eq!(fnv1a64(&data_a), 14_844_052_982_348_062_879_u64);
}
#[test]
fn polyomino_ordered_runs_and_quantizes_u8() {
let mut data = gray_ramp_16x16();
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
polyomino_ordered_dither_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("polyomino ordered dithering should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn polyomino_ordered_ranking_is_deterministic() {
let mut data_a = vec![127_u8; 48 * 48];
let mut data_b = vec![127_u8; 48 * 48];
let mut buffer_a =
dithr::gray_u8(&mut data_a, 48, 48, 48).expect("valid buffer should construct");
let mut buffer_b =
dithr::gray_u8(&mut data_b, 48, 48, 48).expect("valid buffer should construct");
polyomino_ordered_dither_in_place(
&mut buffer_a,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("polyomino ordered dithering should succeed");
polyomino_ordered_dither_in_place(
&mut buffer_b,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("polyomino ordered dithering should succeed");
assert_eq!(data_a, data_b);
assert_eq!(fnv1a64(&data_a), 8_198_126_068_382_287_799_u64);
}
#[test]
fn polyomino_ordered_rejects_non_gray_layout() {
let mut data = rgb_gradient_8x8();
let mut buffer = dithr::rgb_u8(&mut data, 8, 8, 24).expect("valid buffer should construct");
let result = polyomino_ordered_dither_in_place(
&mut buffer,
QuantizeMode::rgb_bits(2).expect("valid bit depth"),
);
assert_eq!(
result,
Err(Error::UnsupportedFormat(
"polyomino ordered dithering supports Gray layouts only"
))
);
}
#[test]
fn stochastic_clustered_dot_runs_and_quantizes_u8() {
let mut data = gray_ramp_16x16();
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
stochastic_clustered_dot_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("stochastic clustered-dot should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn stochastic_clustered_dot_u16_binary_invariant() {
let mut data = gray_ramp_8x8_u16();
let mut buffer = dithr::gray_u16(&mut data, 8, 8, 8).expect("valid buffer should construct");
stochastic_clustered_dot_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("stochastic clustered-dot should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn stochastic_clustered_dot_f32_binary_invariant() {
let mut data: Vec<f32> = (0..(8 * 8))
.map(|index| index as f32 / ((8 * 8 - 1) as f32))
.collect();
let mut buffer = dithr::gray_32f(&mut data, 8, 8, 8).expect("valid buffer should construct");
stochastic_clustered_dot_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("stochastic clustered-dot should succeed");
assert!(data.iter().all(|&value| value == 0.0 || value == 1.0));
}
#[test]
fn stochastic_clustered_dot_generated_screen_is_deterministic() {
let mut data_a = vec![127_u8; 64 * 64];
let mut data_b = vec![127_u8; 64 * 64];
let mut buffer_a =
dithr::gray_u8(&mut data_a, 64, 64, 64).expect("valid buffer should construct");
let mut buffer_b =
dithr::gray_u8(&mut data_b, 64, 64, 64).expect("valid buffer should construct");
stochastic_clustered_dot_in_place(
&mut buffer_a,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("stochastic clustered-dot should succeed");
stochastic_clustered_dot_in_place(
&mut buffer_b,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("stochastic clustered-dot should succeed");
assert_eq!(data_a, data_b);
assert_eq!(fnv1a64(&data_a), 15_527_226_259_204_765_055_u64);
}
#[test]
fn am_fm_hybrid_halftoning_runs_and_quantizes_u8() {
let mut data = gray_ramp_16x16();
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
am_fm_hybrid_halftoning_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("am/fm hybrid halftoning should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn am_fm_hybrid_halftoning_u16_binary_invariant() {
let mut data = gray_ramp_8x8_u16();
let mut buffer = dithr::gray_u16(&mut data, 8, 8, 8).expect("valid buffer should construct");
am_fm_hybrid_halftoning_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("am/fm hybrid halftoning should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn am_fm_hybrid_halftoning_f32_binary_invariant() {
let mut data: Vec<f32> = (0..(8 * 8))
.map(|index| index as f32 / ((8 * 8 - 1) as f32))
.collect();
let mut buffer = dithr::gray_32f(&mut data, 8, 8, 8).expect("valid buffer should construct");
am_fm_hybrid_halftoning_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("am/fm hybrid halftoning should succeed");
assert!(data.iter().all(|&value| value == 0.0 || value == 1.0));
}
#[test]
fn am_fm_hybrid_halftoning_rejects_non_gray_layout() {
let mut data = rgb_gradient_8x8();
let mut buffer = dithr::rgb_u8(&mut data, 8, 8, 24).expect("valid buffer should construct");
let result = am_fm_hybrid_halftoning_in_place(
&mut buffer,
QuantizeMode::rgb_bits(2).expect("valid bit depth"),
);
assert_eq!(
result,
Err(Error::UnsupportedFormat(
"AM/FM hybrid halftoning supports Gray layouts only"
))
);
}
#[test]
fn am_fm_hybrid_halftoning_is_deterministic() {
let mut data_a = vec![127_u8; 16 * 16];
let mut data_b = vec![127_u8; 16 * 16];
let mut buffer_a =
dithr::gray_u8(&mut data_a, 16, 16, 16).expect("valid buffer should construct");
let mut buffer_b =
dithr::gray_u8(&mut data_b, 16, 16, 16).expect("valid buffer should construct");
am_fm_hybrid_halftoning_in_place(
&mut buffer_a,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("am/fm hybrid halftoning should succeed");
am_fm_hybrid_halftoning_in_place(
&mut buffer_b,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("am/fm hybrid halftoning should succeed");
assert_eq!(data_a, data_b);
assert_eq!(fnv1a64(&data_a), 15_927_645_420_284_732_981_u64);
}
#[test]
fn clustered_am_fm_halftoning_runs_and_quantizes_u8() {
let mut data = gray_ramp_16x16();
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
clustered_am_fm_halftoning_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("clustered am/fm halftoning should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn clustered_am_fm_halftoning_u16_binary_invariant() {
let mut data = gray_ramp_8x8_u16();
let mut buffer = dithr::gray_u16(&mut data, 8, 8, 8).expect("valid buffer should construct");
clustered_am_fm_halftoning_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("clustered am/fm halftoning should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn clustered_am_fm_halftoning_f32_binary_invariant() {
let mut data: Vec<f32> = (0..(8 * 8))
.map(|index| index as f32 / ((8 * 8 - 1) as f32))
.collect();
let mut buffer = dithr::gray_32f(&mut data, 8, 8, 8).expect("valid buffer should construct");
clustered_am_fm_halftoning_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("clustered am/fm halftoning should succeed");
assert!(data.iter().all(|&value| value == 0.0 || value == 1.0));
}
#[test]
fn clustered_am_fm_halftoning_rejects_non_gray_layout() {
let mut data = rgb_gradient_8x8();
let mut buffer = dithr::rgb_u8(&mut data, 8, 8, 24).expect("valid buffer should construct");
let result = clustered_am_fm_halftoning_in_place(
&mut buffer,
QuantizeMode::rgb_bits(2).expect("valid bit depth"),
);
assert_eq!(
result,
Err(Error::UnsupportedFormat(
"clustered AM/FM halftoning supports Gray layouts only"
))
);
}
#[test]
fn clustered_am_fm_halftoning_is_deterministic() {
let mut data_a = vec![127_u8; 16 * 16];
let mut data_b = vec![127_u8; 16 * 16];
let mut buffer_a =
dithr::gray_u8(&mut data_a, 16, 16, 16).expect("valid buffer should construct");
let mut buffer_b =
dithr::gray_u8(&mut data_b, 16, 16, 16).expect("valid buffer should construct");
clustered_am_fm_halftoning_in_place(
&mut buffer_a,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("clustered am/fm halftoning should succeed");
clustered_am_fm_halftoning_in_place(
&mut buffer_b,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("clustered am/fm halftoning should succeed");
assert_eq!(data_a, data_b);
assert_eq!(fnv1a64(&data_a), 423_163_558_945_169_899_u64);
}
#[test]
fn blue_noise_multitone_dither_runs_and_quantizes_u8() {
let mut data = gray_ramp_16x16();
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
blue_noise_multitone_dither_in_place(&mut buffer, QuantizeMode::GrayLevels(4))
.expect("blue-noise multitone dithering should succeed");
assert!(data
.iter()
.all(|&value| matches!(value, 0 | 85 | 170 | 255)));
}
#[test]
fn blue_noise_multitone_dither_multilevel_occupancy_invariant() {
let mut data = gray_ramp_16x16();
let mut buffer = dithr::gray_u8(&mut data, 16, 16, 16).expect("valid buffer should construct");
blue_noise_multitone_dither_in_place(&mut buffer, QuantizeMode::GrayLevels(8))
.expect("blue-noise multitone dithering should succeed");
let mut seen = [false; 8];
for &value in &data {
let idx = usize::from(value) * 7 / 255;
seen[idx] = true;
}
let used = seen.into_iter().filter(|&entry| entry).count();
assert!(used >= 4);
}
#[test]
fn blue_noise_multitone_dither_rejects_non_gray_layout() {
let mut data = rgb_gradient_8x8();
let mut buffer = dithr::rgb_u8(&mut data, 8, 8, 24).expect("valid buffer should construct");
let result = blue_noise_multitone_dither_in_place(
&mut buffer,
QuantizeMode::rgb_bits(2).expect("valid bit depth"),
);
assert_eq!(
result,
Err(Error::UnsupportedFormat(
"blue-noise multitone dithering supports Gray layouts only"
))
);
}
#[test]
fn blue_noise_multitone_dither_is_deterministic() {
let mut data_a = vec![127_u8; 16 * 16];
let mut data_b = vec![127_u8; 16 * 16];
let mut buffer_a =
dithr::gray_u8(&mut data_a, 16, 16, 16).expect("valid buffer should construct");
let mut buffer_b =
dithr::gray_u8(&mut data_b, 16, 16, 16).expect("valid buffer should construct");
blue_noise_multitone_dither_in_place(&mut buffer_a, QuantizeMode::GrayLevels(8))
.expect("blue-noise multitone dithering should succeed");
blue_noise_multitone_dither_in_place(&mut buffer_b, QuantizeMode::GrayLevels(8))
.expect("blue-noise multitone dithering should succeed");
assert_eq!(data_a, data_b);
assert_eq!(fnv1a64(&data_a), 4_651_372_225_787_809_868_u64);
}
#[test]
fn custom_ordered_rejects_empty_map() {
let mut data = vec![0_u8; 4];
let mut buffer = dithr::gray_u8(&mut data, 2, 2, 2).expect("valid buffer should construct");
let result = custom_ordered_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
&[],
0,
0,
64,
);
assert_eq!(result, Err(Error::Ordered(OrderedError::EmptyMap)));
}
#[test]
fn custom_ordered_rejects_bad_dimensions() {
let mut data = vec![0_u8; 4];
let mut buffer = dithr::gray_u8(&mut data, 2, 2, 2).expect("valid buffer should construct");
let map = [0_u8, 1, 2];
let result = custom_ordered_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
&map,
2,
2,
64,
);
assert_eq!(result, Err(Error::Ordered(OrderedError::InvalidDimensions)));
}
#[test]
fn custom_ordered_rejects_out_of_range_map_values() {
let mut data = gray_ramp_8x8();
let mut buffer = dithr::gray_u8(&mut data, 8, 8, 8).expect("valid buffer should construct");
let normalized_map = [0_u8, 85, 170, 255];
let result = custom_ordered_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
&normalized_map,
2,
2,
64,
);
assert_eq!(result, Err(Error::Ordered(OrderedError::ValueOutOfRange)));
}
#[test]
fn ordered_rejects_malformed_layout_invariants() {
#[derive(Clone, Copy)]
struct InvalidColorLayout;
impl PixelLayout for InvalidColorLayout {
const CHANNELS: usize = 3;
const COLOR_CHANNELS: usize = 0;
const HAS_ALPHA: bool = false;
const IS_GRAY: bool = false;
}
let mut data = vec![0_u8; 2 * 2 * 3];
let mut buffer = dithr::Buffer::<u8, InvalidColorLayout>::new_typed(&mut data, 2, 2, 6)
.expect("custom layout buffer should construct");
let result = bayer_2x2_in_place(&mut buffer, QuantizeMode::GrayLevels(2));
assert_eq!(
result,
Err(Error::UnsupportedFormat(
"pixel layout must define at least one color channel"
))
);
}
#[test]
fn custom_ordered_2x2_matches_manual_small_case() {
let mut data_a: Vec<u8> = (0_u8..16).map(|value| value.saturating_mul(16)).collect();
let mut data_b = data_a.clone();
let mut buffer_a = dithr::gray_u8(&mut data_a, 4, 4, 4).expect("valid buffer should construct");
let mut buffer_b = dithr::gray_u8(&mut data_b, 4, 4, 4).expect("valid buffer should construct");
custom_ordered_in_place(
&mut buffer_a,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
&BAYER_2X2_FLAT,
2,
2,
64,
)
.expect("custom ordered dither should succeed");
bayer_2x2_in_place(
&mut buffer_b,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("bayer 2x2 should succeed");
assert_eq!(data_a, data_b);
}
#[test]
fn yliluoma_1_output_is_always_palette_member() {
let mut data = rgb_gradient_8x8();
let palette = Palette::new(vec![
[0_u8, 0, 0],
[255, 255, 255],
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[255, 255, 0],
[0, 255, 255],
[255, 0, 255],
])
.expect("palette should be valid");
let mut buffer = dithr::rgb_u8(&mut data, 8, 8, 24).expect("valid buffer should construct");
yliluoma_1_in_place(&mut buffer, &palette).expect("yliluoma 1 should succeed");
for chunk in data.chunks_exact(3) {
let rgb = [chunk[0], chunk[1], chunk[2]];
assert!(palette.as_slice().contains(&rgb));
}
}
#[test]
fn yliluoma_1_deterministic_rgb_fixture() {
let mut data_a = rgb_gradient_8x8();
let mut data_b = rgb_gradient_8x8();
let palette = Palette::new(vec![
[0_u8, 0, 0],
[255, 255, 255],
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[255, 255, 0],
[0, 255, 255],
[255, 0, 255],
])
.expect("palette should be valid");
let mut buffer_a = dithr::rgb_u8(&mut data_a, 8, 8, 24).expect("valid buffer should construct");
let mut buffer_b = dithr::rgb_u8(&mut data_b, 8, 8, 24).expect("valid buffer should construct");
yliluoma_1_in_place(&mut buffer_a, &palette).expect("yliluoma 1 should succeed");
yliluoma_1_in_place(&mut buffer_b, &palette).expect("yliluoma 1 should succeed");
assert_eq!(data_a, data_b);
}
#[test]
fn yliluoma_2_output_is_always_palette_member() {
let mut data = rgb_gradient_8x8();
let palette = Palette::new(vec![
[0_u8, 0, 0],
[255, 255, 255],
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[255, 255, 0],
[0, 255, 255],
[255, 0, 255],
])
.expect("palette should be valid");
let mut buffer = dithr::rgb_u8(&mut data, 8, 8, 24).expect("valid buffer should construct");
yliluoma_2_in_place(&mut buffer, &palette).expect("yliluoma 2 should succeed");
for chunk in data.chunks_exact(3) {
let rgb = [chunk[0], chunk[1], chunk[2]];
assert!(palette.as_slice().contains(&rgb));
}
}
#[test]
fn yliluoma_3_output_is_always_palette_member() {
let mut data = rgb_gradient_8x8();
let palette = Palette::new(vec![
[0_u8, 0, 0],
[255, 255, 255],
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[255, 255, 0],
[0, 255, 255],
[255, 0, 255],
])
.expect("palette should be valid");
let mut buffer = dithr::rgb_u8(&mut data, 8, 8, 24).expect("valid buffer should construct");
yliluoma_3_in_place(&mut buffer, &palette).expect("yliluoma 3 should succeed");
for chunk in data.chunks_exact(3) {
let rgb = [chunk[0], chunk[1], chunk[2]];
assert!(palette.as_slice().contains(&rgb));
}
}
#[test]
fn yliluoma_u16_palette_member_invariant() {
let width = 8_usize;
let height = 8_usize;
let mut data = vec![0_u16; width * height * 3];
for y in 0..height {
for x in 0..width {
let offset = (y * width + x) * 3;
data[offset] = ((x * 65_535) / (width - 1)) as u16;
data[offset + 1] = ((y * 65_535) / (height - 1)) as u16;
data[offset + 2] = (((x + y) * 65_535) / (width + height - 2)) as u16;
}
}
let palette = Palette::<u16>::new(vec![
[0, 0, 0],
[65_535, 65_535, 65_535],
[65_535, 0, 0],
[0, 65_535, 0],
[0, 0, 65_535],
[65_535, 65_535, 0],
[0, 65_535, 65_535],
[65_535, 0, 65_535],
])
.expect("palette should be valid");
let mut buffer =
dithr::rgb_u16(&mut data, width, height, width * 3).expect("valid buffer should construct");
yliluoma_1_in_place(&mut buffer, &palette).expect("yliluoma 1 should succeed");
for chunk in data.chunks_exact(3) {
let rgb = [chunk[0], chunk[1], chunk[2]];
assert!(palette.contains(rgb));
}
}
#[test]
fn yliluoma_f32_palette_member_invariant() {
let width = 8_usize;
let height = 8_usize;
let mut data = vec![0.0_f32; width * height * 3];
for y in 0..height {
for x in 0..width {
let offset = (y * width + x) * 3;
data[offset] = x as f32 / (width - 1) as f32;
data[offset + 1] = y as f32 / (height - 1) as f32;
data[offset + 2] = (x + y) as f32 / (width + height - 2) as f32;
}
}
let palette = Palette::<f32>::new(vec![
[0.0, 0.0, 0.0],
[1.0, 1.0, 1.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 1.0],
[1.0, 0.0, 1.0],
])
.expect("palette should be valid");
let mut buffer =
dithr::rgb_32f(&mut data, width, height, width * 3).expect("valid buffer should construct");
yliluoma_1_in_place(&mut buffer, &palette).expect("yliluoma 1 should succeed");
for chunk in data.chunks_exact(3) {
let rgb = [chunk[0], chunk[1], chunk[2]];
assert!(palette.contains(rgb));
}
}
#[test]
fn yliluoma_rgba_alpha_preserved_u16() {
let width = 8_usize;
let height = 8_usize;
let mut data: Vec<u16> = (0_u32..(width * height * 4) as u32)
.map(|v| ((v * 977) % 65_536) as u16)
.collect();
let before_alpha: Vec<u16> = data.iter().skip(3).step_by(4).copied().collect();
let palette = Palette::<u16>::new(vec![
[0, 0, 0],
[65_535, 65_535, 65_535],
[65_535, 0, 0],
[0, 65_535, 0],
[0, 0, 65_535],
])
.expect("palette should be valid");
let mut buffer = dithr::rgba_u16(&mut data, width, height, width * 4)
.expect("valid buffer should construct");
yliluoma_2_in_place(&mut buffer, &palette).expect("yliluoma 2 should succeed");
let after_alpha: Vec<u16> = data.iter().skip(3).step_by(4).copied().collect();
assert_eq!(before_alpha, after_alpha);
}
#[test]
fn yliluoma_to_indexed_u16_palette_member_consistency() {
let width = 8_usize;
let height = 8_usize;
let mut data = vec![0_u16; width * height * 3];
for y in 0..height {
for x in 0..width {
let offset = (y * width + x) * 3;
data[offset] = ((x * 65_535) / (width - 1)) as u16;
data[offset + 1] = ((y * 65_535) / (height - 1)) as u16;
data[offset + 2] = (((x + y) * 65_535) / (width + height - 2)) as u16;
}
}
let palette = Palette::<u16>::new(vec![
[0, 0, 0],
[65_535, 65_535, 65_535],
[65_535, 0, 0],
[0, 65_535, 0],
[0, 0, 65_535],
[65_535, 65_535, 0],
[0, 65_535, 65_535],
[65_535, 0, 65_535],
])
.expect("palette should be valid");
let mut buffer =
dithr::rgb_u16(&mut data, width, height, width * 3).expect("valid buffer should construct");
yliluoma_1_in_place(&mut buffer, &palette).expect("yliluoma 1 should succeed");
let mut indices = Vec::with_capacity(width * height);
for chunk in data.chunks_exact(3) {
let rgb = [chunk[0], chunk[1], chunk[2]];
assert!(palette.contains(rgb));
let idx = palette.nearest_rgb_index(rgb) as u8;
assert_eq!(palette.get(usize::from(idx)), Some(rgb));
indices.push(idx);
}
let indexed: IndexedImage16 =
IndexedImage::new(indices, width, height, palette).expect("valid indexed image");
for y in 0..height {
for x in 0..width {
let offset = (y * width + x) * 3;
let rgb = [data[offset], data[offset + 1], data[offset + 2]];
assert_eq!(indexed.color_at(x, y), Some(rgb));
}
}
}
#[test]
fn ordered_engine_gray_uses_only_quantized_values() {
let mut data: Vec<u8> = (0_u16..64).map(|value| (value * 4) as u8).collect();
let mut buffer = dithr::gray_u8(&mut data, 8, 8, 8).expect("valid buffer should construct");
custom_ordered_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
&BAYER_2X2_FLAT,
2,
2,
64,
)
.expect("custom ordered dither should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn ordered_engine_rgb_palette_output_is_palette_member() {
let mut data = vec![
16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 24, 40, 56, 72, 88, 104, 120, 136,
152, 168, 184, 200, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 40, 56, 72, 88,
104, 120, 136, 152, 168, 184, 200, 216,
];
let palette = Palette::new(vec![
[0_u8, 0, 0],
[255, 255, 255],
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
])
.expect("palette should be valid");
let mut buffer = dithr::rgb_u8(&mut data, 4, 4, 12).expect("valid buffer should construct");
custom_ordered_in_place(
&mut buffer,
QuantizeMode::Palette(&palette),
&BAYER_4X4_FLAT,
4,
4,
64,
)
.expect("custom ordered dither should succeed");
for chunk in data.chunks_exact(3) {
let rgb = [chunk[0], chunk[1], chunk[2]];
assert!(palette.as_slice().contains(&rgb));
}
}
#[test]
fn ordered_engine_is_deterministic() {
let seed_data: Vec<u8> = (0_u16..120)
.map(|value| ((value * 13 + 7) % 256) as u8)
.collect();
let mut a = seed_data.clone();
let mut b = seed_data;
let mut buffer_a = dithr::rgb_u8(&mut a, 8, 5, 24).expect("valid buffer should construct");
let mut buffer_b = dithr::rgb_u8(&mut b, 8, 5, 24).expect("valid buffer should construct");
custom_ordered_in_place(
&mut buffer_a,
QuantizeMode::rgb_bits(3).expect("valid bit depth"),
&BAYER_4X4_FLAT,
4,
4,
40,
)
.expect("custom ordered dither should succeed");
custom_ordered_in_place(
&mut buffer_b,
QuantizeMode::rgb_bits(3).expect("valid bit depth"),
&BAYER_4X4_FLAT,
4,
4,
40,
)
.expect("custom ordered dither should succeed");
assert_eq!(a, b);
}
#[test]
fn ordered_engine_preserves_alpha_channel() {
let mut data: Vec<u8> = (0_u16..80)
.map(|value| ((value * 19 + 11) % 256) as u8)
.collect();
let before_alpha: Vec<u8> = data.iter().skip(3).step_by(4).copied().collect();
let mut buffer = dithr::rgba_u8(&mut data, 4, 5, 16).expect("valid buffer should construct");
custom_ordered_in_place(
&mut buffer,
QuantizeMode::rgb_bits(2).expect("valid bit depth"),
&BAYER_2X2_FLAT,
2,
2,
48,
)
.expect("custom ordered dither should succeed");
let after_alpha: Vec<u8> = data.iter().skip(3).step_by(4).copied().collect();
assert_eq!(before_alpha, after_alpha);
}
#[test]
fn bayer_8x8_u16_runs_and_quantizes() {
let mut data: Vec<u16> = (0_u32..256)
.map(|value| ((value * 257) % 65_536) as u16)
.collect();
let mut buffer = dithr::gray_u16(&mut data, 16, 16, 16).expect("valid buffer should construct");
bayer_8x8_in_place(&mut buffer, QuantizeMode::GrayLevels(2)).expect("bayer 8x8 should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn bayer_8x8_f32_runs_and_quantizes() {
let mut data: Vec<f32> = (0_u32..(16 * 16 * 3))
.map(|value| (value % 256) as f32 / 255.0)
.collect();
let mut buffer = dithr::rgb_32f(&mut data, 16, 16, 48).expect("valid buffer should construct");
bayer_8x8_in_place(&mut buffer, QuantizeMode::RgbLevels(2)).expect("bayer 8x8 should succeed");
assert!(data.iter().all(|&value| value == 0.0 || value == 1.0));
}
#[test]
fn ordered_bayer_u16_runs_with_same_invariants() {
let mut data: Vec<u16> = (0_u32..256)
.map(|value| ((value * 257) % 65_536) as u16)
.collect();
let mut buffer = dithr::gray_u16(&mut data, 16, 16, 16).expect("valid buffer should construct");
bayer_8x8_in_place(&mut buffer, QuantizeMode::GrayLevels(2)).expect("bayer 8x8 should succeed");
assert_eq!(data.len(), 256);
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn ordered_bayer_f32_runs_with_same_invariants() {
let mut data: Vec<f32> = (0_u32..(16 * 16 * 3))
.map(|value| (value % 256) as f32 / 255.0)
.collect();
let mut buffer = dithr::rgb_32f(&mut data, 16, 16, 48).expect("valid buffer should construct");
bayer_8x8_in_place(&mut buffer, QuantizeMode::RgbLevels(2)).expect("bayer 8x8 should succeed");
assert_eq!(data.len(), 16 * 16 * 3);
assert!(data.iter().all(|&value| value == 0.0 || value == 1.0));
}
#[test]
fn ordered_engine_preserves_alpha_across_u8_u16_f32() {
let map = [0_u8, 2, 3, 1];
let mut rgba_u8: Vec<u8> = (0_u16..64).map(|v| (v * 3) as u8).collect();
let before_u8: Vec<u8> = rgba_u8.iter().skip(3).step_by(4).copied().collect();
let mut buffer_u8 =
dithr::rgba_u8(&mut rgba_u8, 4, 4, 16).expect("valid buffer should construct");
custom_ordered_in_place(
&mut buffer_u8,
QuantizeMode::rgb_bits(2).expect("valid bit depth"),
&map,
2,
2,
48,
)
.expect("u8 ordered dithering should succeed");
let after_u8: Vec<u8> = rgba_u8.iter().skip(3).step_by(4).copied().collect();
assert_eq!(before_u8, after_u8);
let mut rgba_u16: Vec<u16> = (0_u32..64).map(|v| ((v * 1009) % 65_536) as u16).collect();
let before_u16: Vec<u16> = rgba_u16.iter().skip(3).step_by(4).copied().collect();
let mut buffer_u16 =
dithr::rgba_u16(&mut rgba_u16, 4, 4, 16).expect("valid buffer should construct");
custom_ordered_in_place(&mut buffer_u16, QuantizeMode::RgbLevels(4), &map, 2, 2, 48)
.expect("u16 ordered dithering should succeed");
let after_u16: Vec<u16> = rgba_u16.iter().skip(3).step_by(4).copied().collect();
assert_eq!(before_u16, after_u16);
let mut rgba_f32: Vec<f32> = (0_u32..64).map(|v| (v as f32) / 63.0).collect();
let before_f32: Vec<f32> = rgba_f32.iter().skip(3).step_by(4).copied().collect();
let mut buffer_f32 =
dithr::rgba_32f(&mut rgba_f32, 4, 4, 16).expect("valid buffer should construct");
custom_ordered_in_place(&mut buffer_f32, QuantizeMode::RgbLevels(4), &map, 2, 2, 48)
.expect("f32 ordered dithering should succeed");
let after_f32: Vec<f32> = rgba_f32.iter().skip(3).step_by(4).copied().collect();
assert_eq!(before_f32, after_f32);
}
#[test]
fn ordered_alpha_preserved_all_sample_types() {
let map = [0_u8, 2, 3, 1];
let mut rgba_u8: Vec<u8> = (0_u16..64).map(|v| (v * 3) as u8).collect();
let before_u8: Vec<u8> = rgba_u8.iter().skip(3).step_by(4).copied().collect();
let mut buffer_u8 =
dithr::rgba_u8(&mut rgba_u8, 4, 4, 16).expect("valid buffer should construct");
custom_ordered_in_place(&mut buffer_u8, QuantizeMode::GrayLevels(2), &map, 2, 2, 48)
.expect("u8 ordered dithering should succeed");
let after_u8: Vec<u8> = rgba_u8.iter().skip(3).step_by(4).copied().collect();
assert_eq!(before_u8, after_u8);
let mut rgba_u16: Vec<u16> = (0_u32..64).map(|v| ((v * 1009) % 65_536) as u16).collect();
let before_u16: Vec<u16> = rgba_u16.iter().skip(3).step_by(4).copied().collect();
let mut buffer_u16 =
dithr::rgba_u16(&mut rgba_u16, 4, 4, 16).expect("valid buffer should construct");
custom_ordered_in_place(&mut buffer_u16, QuantizeMode::GrayLevels(2), &map, 2, 2, 48)
.expect("u16 ordered dithering should succeed");
let after_u16: Vec<u16> = rgba_u16.iter().skip(3).step_by(4).copied().collect();
assert_eq!(before_u16, after_u16);
let mut rgba_f32: Vec<f32> = (0_u32..64).map(|v| (v as f32) / 63.0).collect();
let before_f32: Vec<f32> = rgba_f32.iter().skip(3).step_by(4).copied().collect();
let mut buffer_f32 =
dithr::rgba_32f(&mut rgba_f32, 4, 4, 16).expect("valid buffer should construct");
custom_ordered_in_place(&mut buffer_f32, QuantizeMode::GrayLevels(2), &map, 2, 2, 48)
.expect("f32 ordered dithering should succeed");
let after_f32: Vec<f32> = rgba_f32.iter().skip(3).step_by(4).copied().collect();
assert_eq!(before_f32, after_f32);
}
#[test]
fn ordered_core_no_duplicate_integer_float_paths_smoke() {
let mut gray_u8 = gray_ramp_16x16();
let mut gray_f32: Vec<f32> = gray_u8.iter().map(|&v| f32::from(v) / 255.0).collect();
let mut buffer_u8 =
dithr::gray_u8(&mut gray_u8, 16, 16, 16).expect("valid buffer should construct");
let mut buffer_f32 =
dithr::gray_32f(&mut gray_f32, 16, 16, 16).expect("valid buffer should construct");
bayer_8x8_in_place(&mut buffer_u8, QuantizeMode::GrayLevels(2)).expect("u8 run should succeed");
bayer_8x8_in_place(&mut buffer_f32, QuantizeMode::GrayLevels(2))
.expect("f32 run should succeed");
let mask_u8: Vec<u8> = gray_u8.iter().map(|&v| u8::from(v > 127)).collect();
let mask_f32: Vec<u8> = gray_f32.iter().map(|&v| u8::from(v > 0.5)).collect();
assert_eq!(mask_u8, mask_f32);
}
#[test]
fn ordered_threshold_tile_logic_matches_u8_u16_f32() {
let map = [0_u8, 2, 3, 1];
let width = 4_usize;
let height = 4_usize;
let mut data_u8 = vec![127_u8; width * height];
let mut data_u16 = vec![32_767_u16; width * height];
let mut data_f32 = vec![0.5_f32; width * height];
let mut buffer_u8 =
dithr::gray_u8(&mut data_u8, width, height, width).expect("valid buffer should construct");
let mut buffer_u16 = dithr::gray_u16(&mut data_u16, width, height, width)
.expect("valid buffer should construct");
let mut buffer_f32 = dithr::gray_32f(&mut data_f32, width, height, width)
.expect("valid buffer should construct");
custom_ordered_in_place(&mut buffer_u8, QuantizeMode::GrayLevels(2), &map, 2, 2, 64)
.expect("u8 run should succeed");
custom_ordered_in_place(&mut buffer_u16, QuantizeMode::GrayLevels(2), &map, 2, 2, 64)
.expect("u16 run should succeed");
custom_ordered_in_place(&mut buffer_f32, QuantizeMode::GrayLevels(2), &map, 2, 2, 64)
.expect("f32 run should succeed");
let mask_u8: Vec<u8> = data_u8.iter().map(|&v| u8::from(v > 127)).collect();
let mask_u16: Vec<u8> = data_u16.iter().map(|&v| u8::from(v > 32_767)).collect();
let mask_f32: Vec<u8> = data_f32.iter().map(|&v| u8::from(v > 0.5)).collect();
assert_eq!(mask_u8, mask_u16);
assert_eq!(mask_u8, mask_f32);
}
#[test]
fn custom_ordered_map_tiling_is_consistent() {
let mut data = vec![127_u8; 64];
let mut buffer = dithr::gray_u8(&mut data, 8, 8, 8).expect("valid buffer should construct");
custom_ordered_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
&BAYER_2X2_FLAT,
2,
2,
64,
)
.expect("custom ordered dither should succeed");
for y in 0..8 {
for x in 0..6 {
assert_eq!(data[y * 8 + x], data[y * 8 + x + 2]);
}
}
for y in 0..6 {
for x in 0..8 {
assert_eq!(data[y * 8 + x], data[(y + 2) * 8 + x]);
}
}
}
#[test]
fn fixture_builders_are_deterministic() {
let gray8 = gray_ramp_8x8();
let gray16 = gray_ramp_16x16();
let gray8_u16 = gray_ramp_8x8_u16();
let checker = checker_8x8();
let gradient = rgb_gradient_8x8();
let gradient_u16 = rgb_gradient_8x8_u16();
let gradient_f32 = rgb_gradient_8x8_f32();
let cube = rgb_cube_strip();
assert_eq!(gray8.len(), 64);
assert_eq!(gray16.len(), 256);
assert_eq!(gray8_u16.len(), 64);
assert_eq!(checker.len(), 64);
assert_eq!(gradient.len(), 8 * 8 * 3);
assert_eq!(gradient_u16.len(), 8 * 8 * 3);
assert_eq!(gradient_f32.len(), 8 * 8 * 3);
assert_eq!(cube.len(), 27 * 3);
assert_eq!(checker.iter().filter(|&&value| value == 0).count(), 32);
assert_eq!(checker.iter().filter(|&&value| value == 255).count(), 32);
assert_eq!(gray8_u16.first().copied(), Some(0));
assert_eq!(gray8_u16.last().copied(), Some(65_535));
assert_eq!(gradient_u16.iter().copied().min(), Some(0));
assert_eq!(gradient_u16.iter().copied().max(), Some(65_535));
assert!(gradient_f32.iter().all(|&v| (0.0..=1.0).contains(&v)));
assert_eq!(fnv1a64(&gray8), fnv1a64(&gray_ramp_8x8()));
assert_eq!(fnv1a64(&gray16), fnv1a64(&gray_ramp_16x16()));
assert_eq!(fnv1a64(&checker), fnv1a64(&checker_8x8()));
assert_eq!(fnv1a64(&gradient), fnv1a64(&rgb_gradient_8x8()));
assert_eq!(fnv1a64(&cube), fnv1a64(&rgb_cube_strip()));
}
#[test]
fn ordered_public_api_packed_constructor_smoke() {
let mut data = gray_ramp_8x8();
let mut buffer =
dithr::gray_u8_packed(&mut data, 8, 8).expect("valid packed gray buffer should construct");
bayer_2x2_in_place(&mut buffer, QuantizeMode::GrayLevels(2)).expect("bayer should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[cfg(feature = "rayon")]
#[test]
fn bayer_8x8_parallel_matches_sequential() {
use dithr::ordered::bayer_8x8_in_place_par;
let mut seq = gray_ramp_16x16();
let mut par = seq.clone();
let mut seq_buffer =
dithr::gray_u8(&mut seq, 16, 16, 16).expect("valid buffer should construct");
let mut par_buffer =
dithr::gray_u8(&mut par, 16, 16, 16).expect("valid buffer should construct");
bayer_8x8_in_place(
&mut seq_buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("sequential should succeed");
bayer_8x8_in_place_par(
&mut par_buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("parallel should succeed");
assert_eq!(seq, par);
}
#[cfg(feature = "rayon")]
#[test]
fn cluster_dot_8x8_parallel_matches_sequential() {
use dithr::ordered::cluster_dot_8x8_in_place_par;
let mut seq = gray_ramp_16x16();
let mut par = seq.clone();
let mut seq_buffer =
dithr::gray_u8(&mut seq, 16, 16, 16).expect("valid buffer should construct");
let mut par_buffer =
dithr::gray_u8(&mut par, 16, 16, 16).expect("valid buffer should construct");
cluster_dot_8x8_in_place(
&mut seq_buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("sequential should succeed");
cluster_dot_8x8_in_place_par(
&mut par_buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("parallel should succeed");
assert_eq!(seq, par);
}
#[cfg(feature = "rayon")]
#[test]
fn custom_ordered_parallel_matches_sequential() {
use dithr::ordered::custom_ordered_in_place_par;
let map = [0_u8, 2, 3, 1];
let mut seq = gray_ramp_16x16();
let mut par = seq.clone();
let mut seq_buffer =
dithr::gray_u8(&mut seq, 16, 16, 16).expect("valid buffer should construct");
let mut par_buffer =
dithr::gray_u8(&mut par, 16, 16, 16).expect("valid buffer should construct");
custom_ordered_in_place(
&mut seq_buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
&map,
2,
2,
64,
)
.expect("sequential should succeed");
custom_ordered_in_place_par(
&mut par_buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
&map,
2,
2,
64,
)
.expect("parallel should succeed");
assert_eq!(seq, par);
}
#[cfg(feature = "rayon")]
#[test]
fn yliluoma_1_parallel_matches_sequential() {
let mut seq = rgb_gradient_8x8();
let mut par = seq.clone();
let palette = Palette::new(vec![
[0_u8, 0, 0],
[255, 255, 255],
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[255, 255, 0],
[0, 255, 255],
[255, 0, 255],
])
.expect("palette should be valid");
let mut seq_buffer = dithr::rgb_u8(&mut seq, 8, 8, 24).expect("valid buffer should construct");
let mut par_buffer = dithr::rgb_u8(&mut par, 8, 8, 24).expect("valid buffer should construct");
yliluoma_1_in_place(&mut seq_buffer, &palette).expect("sequential should succeed");
yliluoma_1_in_place_par(&mut par_buffer, &palette).expect("parallel should succeed");
assert_eq!(seq, par);
}
#[cfg(feature = "rayon")]
#[test]
fn yliluoma_2_parallel_matches_sequential() {
let mut seq = rgb_gradient_8x8();
let mut par = seq.clone();
let palette = Palette::new(vec![
[0_u8, 0, 0],
[255, 255, 255],
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[255, 255, 0],
[0, 255, 255],
[255, 0, 255],
])
.expect("palette should be valid");
let mut seq_buffer = dithr::rgb_u8(&mut seq, 8, 8, 24).expect("valid buffer should construct");
let mut par_buffer = dithr::rgb_u8(&mut par, 8, 8, 24).expect("valid buffer should construct");
yliluoma_2_in_place(&mut seq_buffer, &palette).expect("sequential should succeed");
yliluoma_2_in_place_par(&mut par_buffer, &palette).expect("parallel should succeed");
assert_eq!(seq, par);
}
#[cfg(feature = "rayon")]
#[test]
fn yliluoma_3_parallel_matches_sequential() {
let mut seq = rgb_gradient_8x8();
let mut par = seq.clone();
let palette = Palette::new(vec![
[0_u8, 0, 0],
[255, 255, 255],
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[255, 255, 0],
[0, 255, 255],
[255, 0, 255],
])
.expect("palette should be valid");
let mut seq_buffer = dithr::rgb_u8(&mut seq, 8, 8, 24).expect("valid buffer should construct");
let mut par_buffer = dithr::rgb_u8(&mut par, 8, 8, 24).expect("valid buffer should construct");
yliluoma_3_in_place(&mut seq_buffer, &palette).expect("sequential should succeed");
yliluoma_3_in_place_par(&mut par_buffer, &palette).expect("parallel should succeed");
assert_eq!(seq, par);
}
fn assert_unique_square_coverage_2(map: [[u8; 2]; 2]) {
let mut seen = [false; 4];
for row in map {
for value in row {
let idx = usize::from(value);
assert!(idx < 4);
assert!(!seen[idx]);
seen[idx] = true;
}
}
assert!(seen.into_iter().all(|entry| entry));
}
fn assert_unique_square_coverage_4(map: [[u8; 4]; 4]) {
let mut seen = [false; 16];
for row in map {
for value in row {
let idx = usize::from(value);
assert!(idx < 16);
assert!(!seen[idx]);
seen[idx] = true;
}
}
assert!(seen.into_iter().all(|entry| entry));
}
fn assert_unique_square_coverage_8(map: [[u8; 8]; 8]) {
let mut seen = [false; 64];
for row in map {
for value in row {
let idx = usize::from(value);
assert!(idx < 64);
assert!(!seen[idx]);
seen[idx] = true;
}
}
assert!(seen.into_iter().all(|entry| entry));
}