1use crate::pattern::{self, BRIGHT, DARK, LINE_DST_WIDTH, LINE_SRC_WIDTH};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum EdgeMode {
6 Clamp,
8 Reflect,
10 Wrap,
12 Zero,
14 Unknown,
16}
17
18impl std::fmt::Display for EdgeMode {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 match self {
21 Self::Clamp => f.write_str("Clamp"),
22 Self::Reflect => f.write_str("Reflect"),
23 Self::Wrap => f.write_str("Wrap"),
24 Self::Zero => f.write_str("Zero"),
25 Self::Unknown => f.write_str("Unknown"),
26 }
27 }
28}
29
30pub fn detect(resize: &crate::ResizeFn) -> EdgeMode {
36 let edge_img = pattern::generate_edge_pattern();
37 let dst_w = LINE_DST_WIDTH;
38 let dst_h = edge_img.height();
39 let resized = resize(edge_img.as_ref(), dst_w, dst_h);
40
41 if resized.width() != dst_w || resized.height() != dst_h {
42 return EdgeMode::Unknown;
43 }
44
45 let scale_factor = dst_w as f64 / LINE_SRC_WIDTH as f64;
46 let scanline = resized.height() / 2;
47 let row = &resized.buf()[scanline * resized.stride()..][..dst_w];
48
49 let weights: Vec<f64> = row
51 .iter()
52 .map(|&v| (v as f64 - DARK as f64) / (BRIGHT as f64 - DARK as f64))
53 .collect();
54
55 let expected_peak = ((1.0 + 0.5) * scale_factor - 0.5) as usize;
57 let search_start = expected_peak.saturating_sub(5);
58 let search_end = (expected_peak + 6).min(dst_w);
59 let peak_idx = (search_start..search_end)
60 .max_by(|&a, &b| weights[a].partial_cmp(&weights[b]).unwrap())
61 .unwrap_or(expected_peak);
62
63 let left_extent = peak_idx;
67 let right_extent = (dst_w - 1 - peak_idx).min(left_extent);
68
69 let extent = left_extent.min(right_extent).min(dst_w / 4);
71
72 if extent < 3 {
73 return EdgeMode::Unknown;
74 }
75
76 let left_energy: f64 = (1..=extent).map(|d| weights[peak_idx - d].abs()).sum();
77 let right_energy: f64 = (1..=extent).map(|d| weights[peak_idx + d].abs()).sum();
78
79 let far_right_start = dst_w.saturating_sub((2.0 * scale_factor) as usize);
83 let far_energy: f64 = (far_right_start..dst_w)
84 .map(|i| weights[i].abs())
85 .sum::<f64>()
86 / (dst_w - far_right_start) as f64;
87
88 let left_has_negative = (0..peak_idx).any(|i| weights[i] < -0.03);
90
91 let energy_ratio = if right_energy > 1e-6 {
93 left_energy / right_energy
94 } else {
95 1.0
96 };
97
98 if left_has_negative || energy_ratio < 0.5 {
100 EdgeMode::Zero
102 } else if far_energy > 0.02 {
103 EdgeMode::Wrap
105 } else if energy_ratio > 1.5 {
106 EdgeMode::Reflect
108 } else if energy_ratio > 0.7 {
109 EdgeMode::Clamp
111 } else {
112 EdgeMode::Unknown
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use imgref::{ImgRef, ImgVec};
120
121 fn nn_resize(src: ImgRef<'_, u8>, dst_w: usize, dst_h: usize) -> ImgVec<u8> {
123 let mut dst = vec![0u8; dst_w * dst_h];
124 for y in 0..dst_h {
125 let sy = ((y as f64 + 0.5) * src.height() as f64 / dst_h as f64 - 0.5)
126 .round()
127 .clamp(0.0, (src.height() - 1) as f64) as usize;
128 for x in 0..dst_w {
129 let sx = ((x as f64 + 0.5) * src.width() as f64 / dst_w as f64 - 0.5)
130 .round()
131 .clamp(0.0, (src.width() - 1) as f64) as usize;
132 dst[y * dst_w + x] = src.buf()[sy * src.stride() + sx];
133 }
134 }
135 ImgVec::new(dst, dst_w, dst_h)
136 }
137
138 #[test]
139 fn nn_produces_some_result() {
140 let mode = detect(&nn_resize);
141 let _ = mode;
144 }
145}