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::dbs::{
clustered_dot_direct_multibit_search_in_place, direct_binary_search_in_place,
direct_pattern_control_in_place, electrostatic_halftoning_in_place,
hierarchical_colorant_dbs_in_place, lattice_boltzmann_in_place,
least_squares_model_based_in_place, model_based_med_in_place,
};
use dithr::dot_diffusion::{knuth_dot_diffusion_in_place, optimized_dot_diffusion_in_place};
use dithr::riemersma::riemersma_in_place;
use dithr::QuantizeMode;
#[test]
fn dot_diffusion_class_matrix_valid() {
let matrix = [
[34_u8, 48, 40, 32, 29, 15, 23, 31],
[42, 58, 56, 53, 21, 5, 7, 10],
[50, 62, 61, 45, 13, 1, 2, 18],
[38, 46, 54, 37, 25, 17, 9, 26],
[28, 14, 22, 30, 35, 49, 41, 33],
[20, 4, 6, 11, 43, 59, 57, 52],
[12, 0, 3, 19, 51, 63, 60, 44],
[24, 16, 8, 27, 39, 47, 55, 36],
];
let mut seen = [false; 64];
for row in matrix {
for class in row {
let idx = usize::from(class);
assert!(idx < 64);
assert!(!seen[idx]);
seen[idx] = true;
}
}
assert!(seen.iter().all(|&value| value));
}
#[test]
fn knuth_dot_diffusion_runs() {
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");
knuth_dot_diffusion_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("knuth dot diffusion should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn optimized_dot_diffusion_runs() {
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");
optimized_dot_diffusion_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("optimized dot diffusion should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn dbs_runs_small_fixture() {
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");
direct_binary_search_in_place(&mut buffer, 4).expect("direct binary search should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn dbs_objective_nonincreasing_over_iterations() {
let target: Vec<u8> = (0_u16..64).map(|value| (value * 4) as u8).collect();
let mut data_0 = target.clone();
let mut data_1 = target.clone();
let mut data_2 = target.clone();
let mut buffer_0 = dithr::gray_u8(&mut data_0, 8, 8, 8).expect("valid buffer should construct");
let mut buffer_1 = dithr::gray_u8(&mut data_1, 8, 8, 8).expect("valid buffer should construct");
let mut buffer_2 = dithr::gray_u8(&mut data_2, 8, 8, 8).expect("valid buffer should construct");
direct_binary_search_in_place(&mut buffer_0, 0).expect("direct binary search should succeed");
direct_binary_search_in_place(&mut buffer_1, 1).expect("direct binary search should succeed");
direct_binary_search_in_place(&mut buffer_2, 2).expect("direct binary search should succeed");
let objective_0 = dbs_objective_for_test(&target, &data_0, 8, 8);
let objective_1 = dbs_objective_for_test(&target, &data_1, 8, 8);
let objective_2 = dbs_objective_for_test(&target, &data_2, 8, 8);
assert!(objective_1 <= objective_0);
assert!(objective_2 <= objective_1);
}
#[test]
fn dbs_u16_smoke() {
let mut data: Vec<u16> = (0_u32..64)
.map(|value| ((value * 1024) % 65_536) as u16)
.collect();
let mut buffer = dithr::gray_u16(&mut data, 8, 8, 8).expect("valid buffer should construct");
direct_binary_search_in_place(&mut buffer, 4).expect("direct binary search should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn clustered_dot_direct_multibit_search_runs_small_fixture() {
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");
clustered_dot_direct_multibit_search_in_place(&mut buffer, 4, 4)
.expect("clustered-dot direct multibit search should succeed");
let allowed = [0_u8, 85_u8, 170_u8, 255_u8];
assert!(data.iter().all(|value| allowed.contains(value)));
}
#[test]
fn clustered_dot_direct_multibit_search_objective_nonincreasing_over_iterations() {
let target: Vec<u8> = (0_u16..64).map(|value| (value * 4) as u8).collect();
let mut data_0 = target.clone();
let mut data_1 = target.clone();
let mut data_2 = target.clone();
let mut buffer_0 = dithr::gray_u8(&mut data_0, 8, 8, 8).expect("valid buffer should construct");
let mut buffer_1 = dithr::gray_u8(&mut data_1, 8, 8, 8).expect("valid buffer should construct");
let mut buffer_2 = dithr::gray_u8(&mut data_2, 8, 8, 8).expect("valid buffer should construct");
clustered_dot_direct_multibit_search_in_place(&mut buffer_0, 0, 4)
.expect("clustered-dot direct multibit search should succeed");
clustered_dot_direct_multibit_search_in_place(&mut buffer_1, 1, 4)
.expect("clustered-dot direct multibit search should succeed");
clustered_dot_direct_multibit_search_in_place(&mut buffer_2, 2, 4)
.expect("clustered-dot direct multibit search should succeed");
let objective_0 = multibit_objective_for_test(&target, &data_0, 8, 8);
let objective_1 = multibit_objective_for_test(&target, &data_1, 8, 8);
let objective_2 = multibit_objective_for_test(&target, &data_2, 8, 8);
assert!(objective_1 <= objective_0);
assert!(objective_2 <= objective_1);
}
#[test]
fn clustered_dot_direct_multibit_search_rejects_non_gray_or_float() {
let mut rgb = rgb_gradient_8x8();
let mut rgb_buffer = dithr::rgb_u8(&mut rgb, 8, 8, 24).expect("valid buffer should construct");
let rgb_result = clustered_dot_direct_multibit_search_in_place(&mut rgb_buffer, 2, 4);
assert_eq!(
rgb_result,
Err(dithr::Error::UnsupportedFormat(
"research algorithms support Gray8 and Gray16 only"
))
);
let mut gray_f32: Vec<f32> = (0..64).map(|index| index as f32 / 63.0).collect();
let mut f32_buffer =
dithr::gray_32f(&mut gray_f32, 8, 8, 8).expect("valid buffer should construct");
let f32_result = clustered_dot_direct_multibit_search_in_place(&mut f32_buffer, 2, 4);
assert_eq!(
f32_result,
Err(dithr::Error::UnsupportedFormat(
"research algorithms support Gray8 and Gray16 only"
))
);
}
#[test]
fn direct_pattern_control_rgb_primary_membership() {
let mut data = rgb_gradient_8x8();
let mut buffer = dithr::rgb_u8(&mut data, 8, 8, 24).expect("valid buffer should construct");
direct_pattern_control_in_place(&mut buffer, 3).expect("direct pattern control should succeed");
for px in data.chunks_exact(3) {
assert!(px[0] == 0 || px[0] == 255);
assert!(px[1] == 0 || px[1] == 255);
assert!(px[2] == 0 || px[2] == 255);
}
}
#[test]
fn direct_pattern_control_rgba_preserves_alpha() {
let mut data = Vec::with_capacity(8 * 8 * 4);
for y in 0_u8..8 {
for x in 0_u8..8 {
data.push(x.saturating_mul(32));
data.push(y.saturating_mul(32));
data.push((x ^ y).saturating_mul(32));
data.push(x.wrapping_mul(17).wrapping_add(y.wrapping_mul(29)));
}
}
let alpha_before: Vec<u8> = data.iter().skip(3).step_by(4).copied().collect();
let mut buffer = dithr::rgba_u8(&mut data, 8, 8, 32).expect("valid buffer should construct");
direct_pattern_control_in_place(&mut buffer, 3).expect("direct pattern control should succeed");
let alpha_after: Vec<u8> = data.iter().skip(3).step_by(4).copied().collect();
assert_eq!(alpha_before, alpha_after);
for px in data.chunks_exact(4) {
assert!(px[0] == 0 || px[0] == 255);
assert!(px[1] == 0 || px[1] == 255);
assert!(px[2] == 0 || px[2] == 255);
}
}
#[test]
fn hierarchical_colorant_dbs_hierarchy_order_is_deterministic() {
let mut data_a = rgb_gradient_8x8();
let mut data_b = rgb_gradient_8x8();
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");
hierarchical_colorant_dbs_in_place(&mut buffer_a, 3)
.expect("hierarchical colorant dbs should succeed");
hierarchical_colorant_dbs_in_place(&mut buffer_b, 3)
.expect("hierarchical colorant dbs should succeed");
assert_eq!(fnv1a64(&data_a), fnv1a64(&data_b));
for px in data_a.chunks_exact(3) {
assert!(px[0] == 0 || px[0] == 255);
assert!(px[1] == 0 || px[1] == 255);
assert!(px[2] == 0 || px[2] == 255);
}
}
#[test]
fn hierarchical_colorant_dbs_quality_regression_vs_baseline_colorant_fixture() {
let target = rgb_gradient_8x8();
let mut baseline = target.clone();
let mut optimized = target.clone();
let mut baseline_buffer =
dithr::rgb_u8(&mut baseline, 8, 8, 24).expect("valid buffer should construct");
let mut optimized_buffer =
dithr::rgb_u8(&mut optimized, 8, 8, 24).expect("valid buffer should construct");
hierarchical_colorant_dbs_in_place(&mut baseline_buffer, 0)
.expect("hierarchical colorant dbs should succeed");
hierarchical_colorant_dbs_in_place(&mut optimized_buffer, 3)
.expect("hierarchical colorant dbs should succeed");
let baseline_obj = rgb_hvs_objective_for_test(&target, &baseline, 8, 8);
let optimized_obj = rgb_hvs_objective_for_test(&target, &optimized, 8, 8);
assert!(optimized_obj <= baseline_obj);
}
#[test]
fn hierarchical_colorant_dbs_rejects_rgba_or_float() {
let mut rgba = Vec::with_capacity(8 * 8 * 4);
for y in 0_u8..8 {
for x in 0_u8..8 {
rgba.push(x.saturating_mul(32));
rgba.push(y.saturating_mul(32));
rgba.push((x ^ y).saturating_mul(32));
rgba.push(255);
}
}
let mut rgba_buffer =
dithr::rgba_u8(&mut rgba, 8, 8, 32).expect("valid buffer should construct");
let rgba_result = hierarchical_colorant_dbs_in_place(&mut rgba_buffer, 2);
assert_eq!(
rgba_result,
Err(dithr::Error::UnsupportedFormat(
"research algorithms support Rgb8 and Rgb16 only"
))
);
let mut rgb_f32 = rgb_gradient_8x8_f32();
let mut f32_buffer =
dithr::rgb_32f(&mut rgb_f32, 8, 8, 24).expect("valid buffer should construct");
let f32_result = hierarchical_colorant_dbs_in_place(&mut f32_buffer, 2);
assert_eq!(
f32_result,
Err(dithr::Error::UnsupportedFormat(
"research algorithms support Rgb8 and Rgb16 only"
))
);
}
#[test]
fn lattice_boltzmann_runs_small_fixture() {
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");
lattice_boltzmann_in_place(&mut buffer, 6).expect("lattice-boltzmann should succeed");
assert_eq!(data.len(), 64);
}
#[test]
fn lattice_boltzmann_binary_only_output() {
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");
lattice_boltzmann_in_place(&mut buffer, 8).expect("lattice-boltzmann should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn lattice_boltzmann_u16_smoke() {
let mut data: Vec<u16> = (0_u32..64)
.map(|value| ((value * 1024) % 65_536) as u16)
.collect();
let mut buffer = dithr::gray_u16(&mut data, 8, 8, 8).expect("valid buffer should construct");
lattice_boltzmann_in_place(&mut buffer, 8).expect("lattice-boltzmann should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn electrostatic_halftoning_runs_small_fixture() {
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");
electrostatic_halftoning_in_place(&mut buffer, 8)
.expect("electrostatic halftoning should succeed");
assert_eq!(data.len(), 64);
}
#[test]
fn electrostatic_halftoning_binary_only_output() {
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");
electrostatic_halftoning_in_place(&mut buffer, 10)
.expect("electrostatic halftoning should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn electrostatic_halftoning_u16_smoke() {
let mut data: Vec<u16> = (0_u32..64)
.map(|value| ((value * 1024) % 65_536) as u16)
.collect();
let mut buffer = dithr::gray_u16(&mut data, 8, 8, 8).expect("valid buffer should construct");
electrostatic_halftoning_in_place(&mut buffer, 10)
.expect("electrostatic halftoning should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn model_based_med_runs_small_fixture() {
let mut data_a: Vec<u8> = (0_u16..64).map(|value| (value * 4) as u8).collect();
let mut data_b = data_a.clone();
let mut buffer_a = dithr::gray_u8(&mut data_a, 8, 8, 8).expect("valid buffer should construct");
let mut buffer_b = dithr::gray_u8(&mut data_b, 8, 8, 8).expect("valid buffer should construct");
model_based_med_in_place(&mut buffer_a).expect("model-based med should succeed");
model_based_med_in_place(&mut buffer_b).expect("model-based med should succeed");
assert!(data_a.iter().all(|&value| value == 0 || value == 255));
assert_eq!(fnv1a64(&data_a), fnv1a64(&data_b));
}
#[test]
fn least_squares_model_based_runs_small_fixture() {
let mut data_a: Vec<u8> = (0_u16..64).map(|value| (value * 4) as u8).collect();
let mut data_b = data_a.clone();
let mut buffer_a = dithr::gray_u8(&mut data_a, 8, 8, 8).expect("valid buffer should construct");
let mut buffer_b = dithr::gray_u8(&mut data_b, 8, 8, 8).expect("valid buffer should construct");
least_squares_model_based_in_place(&mut buffer_a, 3)
.expect("least-squares model-based should succeed");
least_squares_model_based_in_place(&mut buffer_b, 3)
.expect("least-squares model-based should succeed");
assert!(data_a.iter().all(|&value| value == 0 || value == 255));
assert_eq!(fnv1a64(&data_a), fnv1a64(&data_b));
}
#[test]
fn model_based_methods_improve_objective_vs_threshold_baseline() {
let target = gray_ramp_16x16();
let mut med_data = target.clone();
let mut lsmb_data = target.clone();
let baseline_data = target
.iter()
.copied()
.map(|value| if value >= 128 { 255_u8 } else { 0_u8 })
.collect::<Vec<u8>>();
let mut med_buffer =
dithr::gray_u8(&mut med_data, 16, 16, 16).expect("valid buffer should construct");
let mut lsmb_buffer =
dithr::gray_u8(&mut lsmb_data, 16, 16, 16).expect("valid buffer should construct");
model_based_med_in_place(&mut med_buffer).expect("model-based med should succeed");
least_squares_model_based_in_place(&mut lsmb_buffer, 4)
.expect("least-squares model-based should succeed");
let baseline_obj = model_based_objective_for_test(&target, &baseline_data, 16, 16);
let med_obj = model_based_objective_for_test(&target, &med_data, 16, 16);
let lsmb_obj = model_based_objective_for_test(&target, &lsmb_data, 16, 16);
assert!(med_obj < baseline_obj);
assert!(lsmb_obj < baseline_obj);
}
#[test]
fn riemersma_runs() {
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");
riemersma_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("riemersma should succeed");
assert_eq!(data.len(), 256);
}
#[test]
fn riemersma_binary_only_for_graybits1() {
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");
riemersma_in_place(
&mut buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("riemersma should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 255));
}
#[test]
fn riemersma_u16_smoke() {
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");
riemersma_in_place(&mut buffer, QuantizeMode::GrayLevels(2)).expect("riemersma should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn riemersma_f32_smoke() {
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");
riemersma_in_place(&mut buffer, QuantizeMode::GrayLevels(2)).expect("riemersma should succeed");
assert!(data.iter().all(|&value| value == 0.0 || value == 1.0));
}
#[test]
fn dot_diffusion_u16_smoke() {
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");
knuth_dot_diffusion_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("knuth dot diffusion should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn dot_diffusion_f32_smoke() {
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");
knuth_dot_diffusion_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("knuth dot diffusion should succeed");
assert!(data.iter().all(|&value| value == 0.0 || value == 1.0));
}
#[test]
fn optimized_dot_diffusion_u16_smoke() {
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");
optimized_dot_diffusion_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("optimized dot diffusion should succeed");
assert!(data.iter().all(|&value| value == 0 || value == 65_535));
}
#[test]
fn optimized_dot_diffusion_f32_smoke() {
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");
optimized_dot_diffusion_in_place(&mut buffer, QuantizeMode::GrayLevels(2))
.expect("optimized dot diffusion should succeed");
assert!(data.iter().all(|&value| value == 0.0 || value == 1.0));
}
#[test]
fn optimized_dot_diffusion_differs_from_knuth_on_canonical_fixture() {
let mut knuth_data = gray_ramp_16x16();
let mut optimized_data = gray_ramp_16x16();
let mut knuth_buffer =
dithr::gray_u8(&mut knuth_data, 16, 16, 16).expect("valid buffer should construct");
let mut optimized_buffer =
dithr::gray_u8(&mut optimized_data, 16, 16, 16).expect("valid buffer should construct");
knuth_dot_diffusion_in_place(
&mut knuth_buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("knuth dot diffusion should succeed");
optimized_dot_diffusion_in_place(
&mut optimized_buffer,
QuantizeMode::gray_bits(1).expect("valid bit depth"),
)
.expect("optimized dot diffusion should succeed");
assert_ne!(fnv1a64(&knuth_data), fnv1a64(&optimized_data));
}
fn dbs_objective_for_test(target: &[u8], binary: &[u8], width: usize, height: usize) -> f64 {
const RADIUS: usize = 3;
const SIZE: usize = RADIUS * 2 + 1;
const SIGMA: f64 = 1.0;
let mut kernel = [0.0_f32; SIZE * SIZE];
let mut sum = 0.0_f64;
for ky in 0..SIZE {
for kx in 0..SIZE {
let dx = kx as isize - RADIUS as isize;
let dy = ky as isize - RADIUS as isize;
let dist2 = (dx * dx + dy * dy) as f64;
let value = (-dist2 / (2.0 * SIGMA * SIGMA)).exp();
kernel[ky * SIZE + kx] = value as f32;
sum += value;
}
}
for value in &mut kernel {
*value = (*value as f64 / sum) as f32;
}
let target_unit: Vec<f32> = target.iter().map(|&v| f32::from(v) / 255.0).collect();
let binary_unit: Vec<f32> = binary
.iter()
.map(|&v| if v == 0 { 0.0_f32 } else { 1.0_f32 })
.collect();
let mut energy = 0.0_f64;
for y in 0..height {
for x in 0..width {
let mut filtered = 0.0_f32;
for ky in 0..SIZE {
for kx in 0..SIZE {
let sx = x as isize + kx as isize - RADIUS as isize;
let sy = y as isize + ky as isize - RADIUS as isize;
if sx < 0 || sy < 0 || sx as usize >= width || sy as usize >= height {
continue;
}
let sidx = sy as usize * width + sx as usize;
filtered += kernel[ky * SIZE + kx] * (binary_unit[sidx] - target_unit[sidx]);
}
}
energy += f64::from(filtered) * f64::from(filtered);
}
}
energy
}
fn multibit_objective_for_test(target: &[u8], output: &[u8], width: usize, height: usize) -> f64 {
const RADIUS: usize = 3;
const SIZE: usize = RADIUS * 2 + 1;
const SIGMA: f64 = 1.0;
let mut kernel = [0.0_f32; SIZE * SIZE];
let mut sum = 0.0_f64;
for ky in 0..SIZE {
for kx in 0..SIZE {
let dx = kx as isize - RADIUS as isize;
let dy = ky as isize - RADIUS as isize;
let dist2 = (dx * dx + dy * dy) as f64;
let value = (-dist2 / (2.0 * SIGMA * SIGMA)).exp();
kernel[ky * SIZE + kx] = value as f32;
sum += value;
}
}
for value in &mut kernel {
*value = (*value as f64 / sum) as f32;
}
let target_unit: Vec<f32> = target.iter().map(|&v| f32::from(v) / 255.0).collect();
let output_unit: Vec<f32> = output.iter().map(|&v| f32::from(v) / 255.0).collect();
let mut energy = 0.0_f64;
for y in 0..height {
for x in 0..width {
let mut filtered = 0.0_f32;
for ky in 0..SIZE {
for kx in 0..SIZE {
let sx = x as isize + kx as isize - RADIUS as isize;
let sy = y as isize + ky as isize - RADIUS as isize;
if sx < 0 || sy < 0 || sx as usize >= width || sy as usize >= height {
continue;
}
let sidx = sy as usize * width + sx as usize;
filtered += kernel[ky * SIZE + kx] * (output_unit[sidx] - target_unit[sidx]);
}
}
energy += f64::from(filtered) * f64::from(filtered);
}
}
energy
}
fn rgb_hvs_objective_for_test(target: &[u8], output: &[u8], width: usize, height: usize) -> f64 {
const RADIUS: usize = 3;
const SIZE: usize = RADIUS * 2 + 1;
const SIGMA: f64 = 1.0;
let mut kernel = [0.0_f32; SIZE * SIZE];
let mut sum = 0.0_f64;
for ky in 0..SIZE {
for kx in 0..SIZE {
let dx = kx as isize - RADIUS as isize;
let dy = ky as isize - RADIUS as isize;
let dist2 = (dx * dx + dy * dy) as f64;
let value = (-dist2 / (2.0 * SIGMA * SIGMA)).exp();
kernel[ky * SIZE + kx] = value as f32;
sum += value;
}
}
for value in &mut kernel {
*value = (*value as f64 / sum) as f32;
}
let target_unit = target
.iter()
.copied()
.map(|value| f32::from(value) / 255.0)
.collect::<Vec<f32>>();
let output_unit = output
.iter()
.copied()
.map(|value| f32::from(value) / 255.0)
.collect::<Vec<f32>>();
let mut energy = 0.0_f64;
for y in 0..height {
for x in 0..width {
for c in 0..3 {
let mut filtered = 0.0_f32;
for ky in 0..SIZE {
for kx in 0..SIZE {
let sx = x as isize + kx as isize - RADIUS as isize;
let sy = y as isize + ky as isize - RADIUS as isize;
if sx < 0 || sy < 0 || sx as usize >= width || sy as usize >= height {
continue;
}
let sidx = (sy as usize * width + sx as usize) * 3 + c;
filtered +=
kernel[ky * SIZE + kx] * (output_unit[sidx] - target_unit[sidx]);
}
}
energy += f64::from(filtered) * f64::from(filtered);
}
}
}
energy
}
fn model_based_objective_for_test(
target: &[u8],
binary: &[u8],
width: usize,
height: usize,
) -> f64 {
const PRINTER_KERNEL: [f32; 9] = [
0.025, 0.07, 0.025, 0.07, 0.62, 0.07, 0.025, 0.07, 0.025,
];
const EYE_RADIUS: usize = 3;
const EYE_SIZE: usize = EYE_RADIUS * 2 + 1;
const EYE_SIGMA: f64 = 1.2;
let mut eye = [0.0_f32; EYE_SIZE * EYE_SIZE];
let mut eye_sum = 0.0_f64;
for ky in 0..EYE_SIZE {
for kx in 0..EYE_SIZE {
let dx = kx as isize - EYE_RADIUS as isize;
let dy = ky as isize - EYE_RADIUS as isize;
let dist2 = (dx * dx + dy * dy) as f64;
let value = (-dist2 / (2.0 * EYE_SIGMA * EYE_SIGMA)).exp();
eye[ky * EYE_SIZE + kx] = value as f32;
eye_sum += value;
}
}
for value in &mut eye {
*value = (*value as f64 / eye_sum) as f32;
}
let kernel_size = EYE_SIZE + 2;
let kernel_radius = kernel_size / 2;
let mut kernel = vec![0.0_f32; kernel_size * kernel_size];
for py in 0..3 {
for px in 0..3 {
let printer = PRINTER_KERNEL[py * 3 + px];
for ey in 0..EYE_SIZE {
for ex in 0..EYE_SIZE {
kernel[(py + ey) * kernel_size + (px + ex)] +=
printer * eye[ey * EYE_SIZE + ex];
}
}
}
}
let target_unit = target
.iter()
.copied()
.map(|value| f32::from(value) / 255.0)
.collect::<Vec<f32>>();
let binary_unit = binary
.iter()
.copied()
.map(|value| if value == 0 { 0.0_f32 } else { 1.0_f32 })
.collect::<Vec<f32>>();
let mut energy = 0.0_f64;
for y in 0..height {
for x in 0..width {
let mut filtered = 0.0_f32;
for ky in 0..kernel_size {
for kx in 0..kernel_size {
let sx = x as isize + kx as isize - kernel_radius as isize;
let sy = y as isize + ky as isize - kernel_radius as isize;
if sx < 0 || sy < 0 || sx as usize >= width || sy as usize >= height {
continue;
}
let sidx = sy as usize * width + sx as usize;
filtered +=
kernel[ky * kernel_size + kx] * (binary_unit[sidx] - target_unit[sidx]);
}
}
energy += f64::from(filtered) * f64::from(filtered);
}
}
energy
}
#[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()));
}