1use imgref::ImgRef;
2
3use crate::pattern::{
4 BRIGHT, DARK, DOT_DST_HEIGHT, DOT_DST_WIDTH, DOT_HCENTER, DOT_HPIXELSPAN, DOT_NUM_STRIPS,
5 DOT_SRC_WIDTH, DOT_STRIP_HEIGHT, LINE_DST_HEIGHT, LINE_DST_WIDTH, LINE_SRC_WIDTH,
6};
7
8#[derive(Debug, Clone)]
10pub struct FilterCurve {
11 pub points: Vec<(f64, f64)>,
14 pub area: f64,
16 pub scale_factor: f64,
18 pub is_scatter: bool,
20}
21
22fn srgb_to_linear(v: f64) -> f64 {
23 if v <= 0.04045 {
24 v / 12.92
25 } else {
26 ((v + 0.055) / 1.055).powf(2.4)
27 }
28}
29
30fn read_pixel(img: &ImgRef<'_, u8>, x: usize, y: usize, srgb: bool) -> f64 {
33 let raw = img.buf()[y * img.stride() + x] as f64;
34 if srgb {
35 let srgb50_lin = srgb_to_linear(50.0 / 255.0);
36 let srgb250_lin = srgb_to_linear(250.0 / 255.0);
37 let v_lin = srgb_to_linear(raw / 255.0);
38 (v_lin - srgb50_lin) * ((BRIGHT as f64 - DARK as f64) / (srgb250_lin - srgb50_lin))
39 + DARK as f64
40 } else {
41 raw
42 }
43}
44
45pub fn analyze_dot(img: &ImgRef<'_, u8>, srgb: bool) -> FilterCurve {
51 let w = img.width();
52 let h = img.height();
53 let scale_factor = w as f64 / DOT_SRC_WIDTH as f64;
54
55 assert_eq!(
56 h, DOT_DST_HEIGHT,
57 "dot image height must be {DOT_DST_HEIGHT}"
58 );
59
60 let mut points = Vec::new();
61
62 for strip in 0..DOT_NUM_STRIPS {
63 for dstpos in 0..w {
64 let mut offset = 10000.0_f64;
66
67 let mut k = DOT_HCENTER + strip;
68 while k < DOT_SRC_WIDTH - DOT_HCENTER {
69 let zp = scale_factor * (k as f64 + 0.5 - DOT_SRC_WIDTH as f64 / 2.0)
71 + (w as f64 / 2.0)
72 - 0.5;
73
74 let tmp_offset = dstpos as f64 - zp;
75
76 if tmp_offset.abs() < offset.abs() {
77 offset = tmp_offset;
78 }
79
80 k += DOT_HPIXELSPAN;
81 }
82
83 if offset.abs() > scale_factor * DOT_HCENTER as f64 {
85 continue;
86 }
87
88 let mut tot = 0.0;
90 for row in 0..DOT_STRIP_HEIGHT {
91 let y = DOT_STRIP_HEIGHT * strip + row;
92 let v = read_pixel(img, dstpos, y, srgb);
93 tot += v - DARK as f64;
94 }
95
96 let mut weight = tot / (BRIGHT as f64 - DARK as f64);
98
99 if scale_factor < 1.0 {
100 weight /= scale_factor;
102 } else {
103 offset /= scale_factor;
105 }
106
107 points.push((offset, weight));
108 }
109 }
110
111 FilterCurve {
112 points,
113 area: 0.0, scale_factor,
115 is_scatter: true,
116 }
117}
118
119pub fn analyze_line(img: &ImgRef<'_, u8>, srgb: bool) -> FilterCurve {
124 let w = img.width();
125 let h = img.height();
126 let scale_factor = w as f64 / LINE_SRC_WIDTH as f64;
127 let scanline = h / 2;
128
129 let mut points = Vec::new();
130 let mut tot = 0.0;
131
132 for i in 0..w {
133 let y = if h >= 3 {
136 let cycle_offset = (i % 3) as isize - 1;
137 (scanline as isize + cycle_offset).clamp(0, h as isize - 1) as usize
138 } else {
139 scanline
140 };
141
142 let v = read_pixel(img, i, y, srgb);
143 let mut weight = (v - DARK as f64) / (BRIGHT as f64 - DARK as f64);
144 tot += weight;
145
146 let mut offset = 0.5 + i as f64 - (w as f64 / 2.0);
147
148 if scale_factor < 1.0 {
149 weight /= scale_factor;
150 } else {
151 offset /= scale_factor;
152 }
153
154 points.push((offset, weight));
155 }
156
157 let area = tot / scale_factor;
158
159 FilterCurve {
160 points,
161 area,
162 scale_factor,
163 is_scatter: false,
164 }
165}
166
167pub fn dot_target() -> (usize, usize) {
169 (DOT_DST_WIDTH, DOT_DST_HEIGHT)
170}
171
172pub fn line_target() -> (usize, usize) {
174 (LINE_DST_WIDTH, LINE_DST_HEIGHT)
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use crate::pattern;
181 use imgref::ImgVec;
182
183 fn nn_resize(src: ImgRef<'_, u8>, dst_w: usize, dst_h: usize) -> ImgVec<u8> {
185 let mut dst = vec![0u8; dst_w * dst_h];
186 for y in 0..dst_h {
187 let sy = ((y as f64 + 0.5) * src.height() as f64 / dst_h as f64 - 0.5)
188 .round()
189 .clamp(0.0, (src.height() - 1) as f64) as usize;
190 for x in 0..dst_w {
191 let sx = ((x as f64 + 0.5) * src.width() as f64 / dst_w as f64 - 0.5)
192 .round()
193 .clamp(0.0, (src.width() - 1) as f64) as usize;
194 dst[y * dst_w + x] = src.buf()[sy * src.stride() + sx];
195 }
196 }
197 ImgVec::new(dst, dst_w, dst_h)
198 }
199
200 #[test]
201 fn dot_analysis_produces_points() {
202 let dot = pattern::generate_dot_pattern();
203 let (tw, th) = dot_target();
204 let resized = nn_resize(dot.as_ref(), tw, th);
205 let curve = analyze_dot(&resized.as_ref(), false);
206 assert!(!curve.points.is_empty());
207 assert!(curve.scale_factor < 1.0);
208 }
209
210 #[test]
211 fn line_analysis_produces_curve() {
212 let line = pattern::generate_line_pattern();
213 let (tw, th) = line_target();
214 let resized = nn_resize(line.as_ref(), tw, th);
215 let curve = analyze_line(&resized.as_ref(), false);
216 assert_eq!(curve.points.len(), tw);
217 assert!(curve.scale_factor > 1.0);
218 assert!((curve.area - 1.0).abs() < 0.5, "area = {}", curve.area);
220 }
221}