Skip to main content

oxihuman_viewer/
error_diffusion_view.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5pub struct ErrorDiffusionView {
6    pub color_levels: u32,
7    pub serpentine_scan: bool,
8    pub strength: f32,
9}
10
11pub fn new_error_diffusion_view() -> ErrorDiffusionView {
12    ErrorDiffusionView {
13        color_levels: 256,
14        serpentine_scan: true,
15        strength: 1.0,
16    }
17}
18
19pub fn ed_set_color_levels(v: &mut ErrorDiffusionView, n: u32) {
20    v.color_levels = n.clamp(2, 256);
21}
22
23/// Floyd-Steinberg quantize a single channel value.
24pub fn ed_quantize(v: &ErrorDiffusionView, value: f32) -> (f32, f32) {
25    let levels = v.color_levels as f32;
26    let quantized = (value * (levels - 1.0)).round() / (levels - 1.0);
27    let error = (value - quantized) * v.strength;
28    (quantized.clamp(0.0, 1.0), error)
29}
30
31/// Distribute error to neighbors (returns weights for right, down-left, down, down-right).
32pub fn ed_floyd_steinberg_weights() -> (f32, f32, f32, f32) {
33    (7.0 / 16.0, 3.0 / 16.0, 5.0 / 16.0, 1.0 / 16.0)
34}
35
36pub fn ed_is_high_fidelity(v: &ErrorDiffusionView) -> bool {
37    v.color_levels >= 128
38}
39
40pub fn ed_blend(a: &ErrorDiffusionView, b: &ErrorDiffusionView, t: f32) -> ErrorDiffusionView {
41    let t = t.clamp(0.0, 1.0);
42    let cl = (a.color_levels as f32 + (b.color_levels as f32 - a.color_levels as f32) * t).round()
43        as u32;
44    ErrorDiffusionView {
45        color_levels: cl.clamp(2, 256),
46        serpentine_scan: if t < 0.5 {
47            a.serpentine_scan
48        } else {
49            b.serpentine_scan
50        },
51        strength: a.strength + (b.strength - a.strength) * t,
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn test_new() {
61        /* default color levels */
62        let v = new_error_diffusion_view();
63        assert_eq!(v.color_levels, 256);
64    }
65
66    #[test]
67    fn test_quantize_zero() {
68        /* zero quantizes to zero */
69        let v = new_error_diffusion_view();
70        let (q, _e) = ed_quantize(&v, 0.0);
71        assert!(q.abs() < 1e-6);
72    }
73
74    #[test]
75    fn test_floyd_steinberg_weights_sum() {
76        /* weights sum to 1 */
77        let (w0, w1, w2, w3) = ed_floyd_steinberg_weights();
78        let sum = w0 + w1 + w2 + w3;
79        assert!((sum - 1.0).abs() < 1e-6);
80    }
81
82    #[test]
83    fn test_high_fidelity_by_default() {
84        /* 256 levels is high fidelity */
85        let v = new_error_diffusion_view();
86        assert!(ed_is_high_fidelity(&v));
87    }
88
89    #[test]
90    fn test_blend() {
91        /* midpoint strength */
92        let a = ErrorDiffusionView {
93            color_levels: 16,
94            serpentine_scan: false,
95            strength: 0.0,
96        };
97        let b = ErrorDiffusionView {
98            color_levels: 16,
99            serpentine_scan: false,
100            strength: 2.0,
101        };
102        let c = ed_blend(&a, &b, 0.5);
103        assert!((c.strength - 1.0).abs() < 1e-5);
104    }
105}