1use imgref::ImgVec;
2use rgb::RGB8;
3
4use crate::analyze::FilterCurve;
5use crate::filters::KnownFilter;
6
7const WIDTH: usize = 600;
8const HEIGHT: usize = 300;
9const ZERO_X: f64 = 230.0;
10const UNIT_X: f64 = 90.0;
11const ZERO_Y: f64 = 220.0;
12const UNIT_Y: f64 = -200.0; const WHITE: RGB8 = RGB8 {
15 r: 255,
16 g: 255,
17 b: 255,
18};
19const BLACK: RGB8 = RGB8 { r: 0, g: 0, b: 0 };
20const GRID_GRAY: RGB8 = RGB8 {
21 r: 192,
22 g: 192,
23 b: 192,
24};
25const BORDER_GREEN: RGB8 = RGB8 {
26 r: 144,
27 g: 192,
28 b: 144,
29};
30const SCATTER_BLUE: RGB8 = RGB8 { r: 0, g: 0, b: 255 };
31const LINE_RED: RGB8 = RGB8 {
32 r: 224,
33 g: 64,
34 b: 64,
35};
36const REF_LIGHT: RGB8 = RGB8 {
37 r: 180,
38 g: 180,
39 b: 180,
40};
41
42fn xcoord(ix: f64) -> i32 {
43 (0.5 + ZERO_X + ix * UNIT_X) as i32
44}
45
46fn ycoord(iy: f64) -> i32 {
47 (0.5 + ZERO_Y + iy * UNIT_Y) as i32
48}
49
50fn set_pixel(buf: &mut [RGB8], x: i32, y: i32, color: RGB8) {
51 if x >= 0 && x < WIDTH as i32 && y >= 0 && y < HEIGHT as i32 {
52 buf[y as usize * WIDTH + x as usize] = color;
53 }
54}
55
56fn draw_line(buf: &mut [RGB8], x0: i32, y0: i32, x1: i32, y1: i32, color: RGB8) {
57 let dx = (x1 - x0).abs();
58 let dy = -(y1 - y0).abs();
59 let sx: i32 = if x0 < x1 { 1 } else { -1 };
60 let sy: i32 = if y0 < y1 { 1 } else { -1 };
61 let mut err = dx + dy;
62 let mut x = x0;
63 let mut y = y0;
64
65 loop {
66 set_pixel(buf, x, y, color);
67 if x == x1 && y == y1 {
68 break;
69 }
70 let e2 = 2 * err;
71 if e2 >= dy {
72 if x == x1 {
73 break;
74 }
75 err += dy;
76 x += sx;
77 }
78 if e2 <= dx {
79 if y == y1 {
80 break;
81 }
82 err += dx;
83 y += sy;
84 }
85 }
86}
87
88fn draw_dashed_line(buf: &mut [RGB8], x0: i32, y0: i32, x1: i32, y1: i32, color: RGB8) {
89 let dx = (x1 - x0).abs();
90 let dy = -(y1 - y0).abs();
91 let sx: i32 = if x0 < x1 { 1 } else { -1 };
92 let sy: i32 = if y0 < y1 { 1 } else { -1 };
93 let mut err = dx + dy;
94 let mut x = x0;
95 let mut y = y0;
96 let mut step = 0u32;
97
98 loop {
99 if step % 8 < 4 {
101 set_pixel(buf, x, y, color);
102 }
103 step += 1;
104 if x == x1 && y == y1 {
105 break;
106 }
107 let e2 = 2 * err;
108 if e2 >= dy {
109 if x == x1 {
110 break;
111 }
112 err += dy;
113 x += sx;
114 }
115 if e2 <= dx {
116 if y == y1 {
117 break;
118 }
119 err += dx;
120 y += sy;
121 }
122 }
123}
124
125fn draw_grid(buf: &mut [RGB8]) {
126 for i in -10..=10 {
128 let hx = xcoord(0.5 + i as f64);
129 draw_dashed_line(buf, hx, 0, hx, HEIGHT as i32 - 1, GRID_GRAY);
130 let hy = ycoord(0.5 + i as f64);
131 draw_dashed_line(buf, 0, hy, WIDTH as i32 - 1, hy, GRID_GRAY);
132 }
133
134 for i in -10..=10 {
136 let ix = xcoord(i as f64);
137 draw_line(buf, ix, 0, ix, HEIGHT as i32 - 1, GRID_GRAY);
138 let iy = ycoord(i as f64);
139 draw_line(buf, 0, iy, WIDTH as i32 - 1, iy, GRID_GRAY);
140 }
141
142 let ax = xcoord(0.0);
144 draw_line(buf, ax, 0, ax, HEIGHT as i32 - 1, BLACK);
145 let ay = ycoord(0.0);
146 draw_line(buf, 0, ay, WIDTH as i32 - 1, ay, BLACK);
147}
148
149fn draw_border(buf: &mut [RGB8], color: RGB8) {
150 let w = WIDTH as i32;
151 let h = HEIGHT as i32;
152 draw_line(buf, 0, 0, w - 1, 0, color);
153 draw_line(buf, 0, h - 1, w - 1, h - 1, color);
154 draw_line(buf, 0, 0, 0, h - 1, color);
155 draw_line(buf, w - 1, 0, w - 1, h - 1, color);
156}
157
158fn plot_scatter(buf: &mut [RGB8], points: &[(f64, f64)], color: RGB8) {
159 for &(x, y) in points {
160 let px = xcoord(x);
161 let py = ycoord(y);
162 set_pixel(buf, px, py, color);
163 }
164}
165
166fn plot_connected(buf: &mut [RGB8], points: &[(f64, f64)], color: RGB8) {
167 let mut last: Option<(i32, i32)> = None;
168 for &(x, y) in points {
169 let px = xcoord(x);
170 let py = ycoord(y);
171 if let Some((lx, ly)) = last {
172 draw_line(buf, lx, ly, px, py, color);
173 }
174 last = Some((px, py));
175 }
176}
177
178fn plot_reference(buf: &mut [RGB8], filter: KnownFilter, color: RGB8) {
179 let x_min = -ZERO_X / UNIT_X; let x_max = (WIDTH as f64 - ZERO_X) / UNIT_X; let steps = WIDTH * 2;
184 let mut last: Option<(i32, i32)> = None;
185 for i in 0..=steps {
186 let x = x_min + (x_max - x_min) * i as f64 / steps as f64;
187 let y = filter.evaluate(x);
188 let px = xcoord(x);
189 let py = ycoord(y);
190 if let Some((lx, ly)) = last {
191 draw_line(buf, lx, ly, px, py, color);
192 }
193 last = Some((px, py));
194 }
195}
196
197pub fn render(
199 downscale: Option<&FilterCurve>,
200 upscale: Option<&FilterCurve>,
201 reference: Option<KnownFilter>,
202) -> ImgVec<RGB8> {
203 let mut buf = vec![WHITE; WIDTH * HEIGHT];
204
205 draw_grid(&mut buf);
206
207 if let Some(filter) = reference {
208 plot_reference(&mut buf, filter, REF_LIGHT);
209 }
210
211 if let Some(ds) = downscale {
212 plot_scatter(&mut buf, &ds.points, SCATTER_BLUE);
213 }
214
215 if let Some(us) = upscale {
216 plot_connected(&mut buf, &us.points, LINE_RED);
217 }
218
219 draw_border(&mut buf, BORDER_GREEN);
220
221 ImgVec::new(buf, WIDTH, HEIGHT)
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn render_empty_graph() {
230 let img = render(None, None, None);
231 assert_eq!(img.width(), WIDTH);
232 assert_eq!(img.height(), HEIGHT);
233 assert_eq!(img.buf()[0], BORDER_GREEN);
235 }
236
237 #[test]
238 fn coordinate_system() {
239 assert_eq!(xcoord(0.0), 230);
240 assert_eq!(xcoord(1.0), 320);
241 assert_eq!(ycoord(0.0), 220);
242 assert_eq!(ycoord(1.0), 20);
243 }
244}