Skip to main content

resamplescope/
pattern.rs

1use imgref::ImgVec;
2
3// Dot pattern constants (matching C source exactly)
4pub const DOT_SRC_WIDTH: usize = 557;
5pub const DOT_HPIXELSPAN: usize = 25;
6pub const DOT_NUM_STRIPS: usize = DOT_HPIXELSPAN;
7pub const DOT_HCENTER: usize = (DOT_HPIXELSPAN - 1) / 2; // 12
8pub const DOT_STRIP_HEIGHT: usize = 11;
9pub const DOT_VCENTER: usize = (DOT_STRIP_HEIGHT - 1) / 2; // 5
10pub const DOT_SRC_HEIGHT: usize = DOT_NUM_STRIPS * DOT_STRIP_HEIGHT; // 275
11
12pub const DOT_DST_WIDTH: usize = DOT_SRC_WIDTH - 2; // 555
13pub const DOT_DST_HEIGHT: usize = DOT_SRC_HEIGHT; // 275
14
15// Line pattern constants
16pub const LINE_SRC_WIDTH: usize = 15;
17pub const LINE_SRC_HEIGHT: usize = 15;
18pub const LINE_DST_WIDTH: usize = 555;
19pub const LINE_DST_HEIGHT: usize = LINE_SRC_HEIGHT; // 15
20
21pub const DARK: u8 = 50;
22pub const BRIGHT: u8 = 250;
23
24/// Generate the dot test pattern for downscale analysis.
25/// 557x275 grayscale image with bright dots at phase-offset positions per strip.
26pub fn generate_dot_pattern() -> ImgVec<u8> {
27    let mut pixels = vec![DARK; DOT_SRC_WIDTH * DOT_SRC_HEIGHT];
28
29    for j in 0..DOT_SRC_HEIGHT {
30        let strip = j / DOT_STRIP_HEIGHT;
31        let strip_row = j % DOT_STRIP_HEIGHT;
32
33        if strip_row != DOT_VCENTER {
34            continue;
35        }
36
37        for i in DOT_HCENTER..(DOT_SRC_WIDTH - DOT_HCENTER) {
38            // Each strip shifts the dot positions by 1 pixel.
39            // When i < strip, the C code produces a negative modulus which never
40            // equals DOT_HCENTER, so no dot is placed. We replicate that by only
41            // checking when i >= strip.
42            if i >= strip && (i - strip) % DOT_HPIXELSPAN == DOT_HCENTER {
43                pixels[j * DOT_SRC_WIDTH + i] = BRIGHT;
44            }
45        }
46    }
47
48    ImgVec::new(pixels, DOT_SRC_WIDTH, DOT_SRC_HEIGHT)
49}
50
51/// Generate the line test pattern for upscale analysis.
52/// 15x15 grayscale image with a single bright column at the center (x=7).
53pub fn generate_line_pattern() -> ImgVec<u8> {
54    let middle = LINE_SRC_WIDTH / 2; // 7
55    let mut pixels = vec![DARK; LINE_SRC_WIDTH * LINE_SRC_HEIGHT];
56
57    for y in 0..LINE_SRC_HEIGHT {
58        pixels[y * LINE_SRC_WIDTH + middle] = BRIGHT;
59    }
60
61    ImgVec::new(pixels, LINE_SRC_WIDTH, LINE_SRC_HEIGHT)
62}
63
64/// Generate the edge test pattern for edge handling detection.
65/// 15x15 grayscale image with a bright column at x=1 (near left edge).
66pub fn generate_edge_pattern() -> ImgVec<u8> {
67    let mut pixels = vec![DARK; LINE_SRC_WIDTH * LINE_SRC_HEIGHT];
68
69    for y in 0..LINE_SRC_HEIGHT {
70        pixels[y * LINE_SRC_WIDTH + 1] = BRIGHT;
71    }
72
73    ImgVec::new(pixels, LINE_SRC_WIDTH, LINE_SRC_HEIGHT)
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn dot_pattern_dimensions() {
82        let img = generate_dot_pattern();
83        assert_eq!(img.width(), DOT_SRC_WIDTH);
84        assert_eq!(img.height(), DOT_SRC_HEIGHT);
85    }
86
87    #[test]
88    fn dot_pattern_has_bright_pixels() {
89        let img = generate_dot_pattern();
90        let bright_count = img.buf().iter().filter(|&&v| v == BRIGHT).count();
91        // Each strip has roughly (SRC_WIDTH - 2*HCENTER) / HPIXELSPAN dots
92        // = (557 - 24) / 25 = 21.32 → 22 dots per strip × 25 strips
93        assert!(bright_count > 500, "only {bright_count} bright pixels");
94        assert!(
95            bright_count < 600,
96            "{bright_count} bright pixels (too many)"
97        );
98    }
99
100    #[test]
101    fn dot_pattern_center_row_of_strip0() {
102        let img = generate_dot_pattern();
103        // Strip 0, center row is y = DOT_VCENTER = 5
104        let row = &img.buf()[DOT_VCENTER * DOT_SRC_WIDTH..][..DOT_SRC_WIDTH];
105        // First dot at x = DOT_HCENTER = 12 (since strip=0, (12-0)%25==12)
106        assert_eq!(row[DOT_HCENTER], BRIGHT);
107        assert_eq!(row[DOT_HCENTER - 1], DARK);
108        assert_eq!(row[DOT_HCENTER + 1], DARK);
109        // Next dot at x = 37
110        assert_eq!(row[DOT_HCENTER + DOT_HPIXELSPAN], BRIGHT);
111    }
112
113    #[test]
114    fn line_pattern_dimensions() {
115        let img = generate_line_pattern();
116        assert_eq!(img.width(), LINE_SRC_WIDTH);
117        assert_eq!(img.height(), LINE_SRC_HEIGHT);
118    }
119
120    #[test]
121    fn line_pattern_center_column() {
122        let img = generate_line_pattern();
123        for y in 0..LINE_SRC_HEIGHT {
124            for x in 0..LINE_SRC_WIDTH {
125                let expected = if x == 7 { BRIGHT } else { DARK };
126                assert_eq!(
127                    img.buf()[y * LINE_SRC_WIDTH + x],
128                    expected,
129                    "mismatch at ({x}, {y})"
130                );
131            }
132        }
133    }
134
135    #[test]
136    fn edge_pattern_column_at_x1() {
137        let img = generate_edge_pattern();
138        for y in 0..LINE_SRC_HEIGHT {
139            assert_eq!(img.buf()[y * LINE_SRC_WIDTH], DARK);
140            assert_eq!(img.buf()[y * LINE_SRC_WIDTH + 1], BRIGHT);
141            assert_eq!(img.buf()[y * LINE_SRC_WIDTH + 2], DARK);
142        }
143    }
144}