rustic_zen/image/
software.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Image contains the full colour depth framebuffer, and provides functions to export it.
6
7use crate::{geom::Point, ray::RayResult};
8
9use std::mem::swap;
10
11use super::{ExportImage, RenderImage};
12
13/// Represents an image while ray rendering is happening
14///
15/// This struct uses floats to represent each pixel of the image so normalisation
16/// can happen after rendering finishes.
17///
18/// Image is created and populated by the renderer. Only export functions are exposed.
19pub struct Image {
20    width: usize,
21    height: usize,
22    pixels: Vec<(f32, f32, f32)>,
23    lightpower: f32,
24}
25
26impl Image {
27    /// Create new image with the given width and hieght;
28    pub fn new(width: usize, height: usize) -> Self {
29        let len = width * height;
30        let pixels: Vec<(f32, f32, f32)> = vec![(0.0, 0.0, 0.0); len];
31        Image {
32            width,
33            height,
34            pixels,
35            lightpower: 0.0,
36        }
37    }
38
39    #[inline(always)]
40    fn plot(&self, colour: (f32, f32, f32), pixel: usize, intensity: f32) {
41        if pixel >= self.pixels.len() {
42            return;
43        };
44
45        unsafe {
46            // This is using statistical safety. A tyical image has over 8 million data points in it,
47            // while even a high end CPU will likely only have 64 threads. Safety while not guarenteed
48            // is provided "good enough" by the diminishinly small change of a read before write error
49            // occuring in this large dataset, and the miniscule values the pixels are updated with by
50            // each ray.
51            //
52            // NB: This is not actually safe. Small data errors will occur. We however don't consider
53            // them significant.
54            let s = (self as *const Self) as *mut Self;
55            (*s).pixels[pixel].0 += colour.0 * intensity;
56            (*s).pixels[pixel].1 += colour.1 * intensity;
57            (*s).pixels[pixel].2 += colour.2 * intensity;
58        }
59    }
60
61    #[inline(always)]
62    fn inner_draw_line(&self, ray: &RayResult) {
63        /*
64         * Modified version of Xiaolin Wu's antialiased line algorithm:
65         * http://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm
66         *
67         * Brightness compensation:
68         *   The total brightness of the line should be proportional to its
69         *   length, but with Wu's algorithm it's proportional to dx.
70         *   We scale the brightness of each pixel to compensate.
71         */
72
73        let bounds =
74            Self::check_bounds(ray.origin, ray.termination, self.width - 1, self.height + 2);
75        if bounds.is_none() {
76            return;
77        }
78
79        let colour = ray.color();
80
81        let (p0, p1) = bounds.unwrap();
82
83        let mut hx: i64 = 1;
84        let mut hy: i64 = self.width as i64;
85
86        let mut x0 = p0.x;
87        let mut y0 = p0.y;
88        let mut x1 = p1.x;
89        let mut y1 = p1.y;
90
91        let dx = (x1 - x0).abs();
92        let dy = (y1 - y0).abs();
93
94        // Axis swap. The virtual 'x' is always the major axis.
95        if dy > dx {
96            swap(&mut x0, &mut y0);
97            swap(&mut x1, &mut y1);
98            swap(&mut hx, &mut hy);
99        }
100
101        // We expect x0->x1 to be in the +X direction
102        if x0 > x1 {
103            swap(&mut x0, &mut x1);
104            swap(&mut y0, &mut y1);
105        }
106
107        // calculate gradient
108        let dx = x1 - x0;
109        let dy = y1 - y0;
110        let gradient = if dx == 0.0 { 1.0 } else { dy / dx };
111
112        const SHIFT: f64 = 0.25;
113
114        x0 -= SHIFT;
115        x1 += SHIFT;
116        y0 -= gradient * SHIFT;
117        y1 += gradient * SHIFT;
118
119        // Brightness Calculation
120        let br = 128.0 * f64::sqrt(dx * dx + dy * dy) / dx;
121
122        // TODO clipping here
123
124        // Handle first endpoint
125        let xend = x0.round();
126        let yend = y0 + gradient * (xend - x0);
127        let xpxl1: i64 = xend as i64;
128        let ypxl1: i64 = yend.floor() as i64;
129
130        let xgap = br * (1.0 - (x0 + 0.5) + xend); // 0 to br
131        let ygap = yend - yend.floor(); // 0 to 1
132
133        self.plot(
134            colour,
135            (xpxl1 * hx + ypxl1 * hy) as usize,
136            (xgap * (1.0 - ygap)) as f32,
137        );
138        self.plot(
139            colour,
140            (xpxl1 * hx + (ypxl1 + 1) * hy) as usize,
141            (xgap * ygap) as f32,
142        );
143
144        let mut intery = yend + gradient;
145
146        //Handle Second endpoint
147        let xend = x1.round();
148        let yend = y1 + gradient * (xend - x1);
149        let xpxl2: i64 = xend as i64;
150        let ypxl2: i64 = yend.floor() as i64;
151
152        let xgap = br * (1.0 - (x1 + 0.5) + xend); // 0 to br
153        let ygap = yend - yend.floor(); // 0 to 1
154
155        self.plot(
156            colour,
157            (xpxl2 * hx + ypxl2 * hy) as usize,
158            (xgap * (1.0 - ygap)) as f32,
159        );
160        self.plot(
161            colour,
162            (xpxl2 * hx + (ypxl2 + 1) * hy) as usize,
163            (xgap * ygap) as f32,
164        );
165
166        // Loop Over line
167        for x in xpxl1 + 1..xpxl2 {
168            let iy: i64 = intery.floor() as i64;
169            let fy: f64 = intery - intery.floor(); // 0 to 1
170
171            self.plot(
172                colour,
173                (x * hx + iy * hy) as usize,
174                (br * (1.0 - fy)) as f32,
175            );
176            self.plot(colour, (x * hx + (iy + 1) * hy) as usize, (br * fy) as f32);
177
178            intery += gradient;
179        }
180    }
181
182    #[inline(always)]
183    fn check_bounds(
184        mut p0: Point,
185        mut p1: Point,
186        width: usize,
187        height: usize,
188    ) -> Option<(Point, Point)> {
189        let width = (width) as f64;
190        let height = (height) as f64;
191
192        if (p0.x < 0.0 && p1.x < 0.0) ||  // if both are less than 0 the line is not in frame
193           (p0.y < 0.0 && p1.y < 0.0) || // if both are less than 0 the line is not in frame
194           (p0.x > width && p1.x > width) || // if both are greater than width the line is not in frame
195           (p0.y > height && p1.y > height)
196        // if both are greater than height the line is not in frame
197        {
198            return None;
199        }
200
201        if p0.x > p1.x {
202            swap(&mut p0, &mut p1);
203        }
204
205        let d = (p1 - p0).normalized();
206
207        if d.x == 0.0 {
208            p0.y = p0.y.clamp(0.0, height);
209            p1.y = p1.y.clamp(0.0, height);
210        } else {
211            if p0.x < 0.0 {
212                p0 = p0 - (d * (p0.x / d.x));
213            }
214            if p1.x > width {
215                p1 = p1 - d * ((p1.x - width) / d.x);
216            }
217            if p0.y < 0.0 {
218                p0 = p0 - (d * (p0.y / d.y));
219            } else {
220                if p0.y > height {
221                    p0 = p0 - d * ((p0.y - height) / d.y);
222                }
223            }
224            if p1.y < 0.0 {
225                p1 = p1 - (d * (p1.y / d.y));
226            } else {
227                if p1.y > height {
228                    p1 = p1 - d * ((p1.y - height) / d.y);
229                }
230            }
231        }
232        Some((p0, p1))
233    }
234}
235
236impl RenderImage for Image {
237    fn draw_line(&self, ray: RayResult) {
238        self.inner_draw_line(&ray);
239    }
240
241    fn prepare_render(&mut self, lightpower: f32) {
242        self.lightpower = lightpower;
243    }
244}
245
246impl ExportImage for Image {
247    fn get_lightpower(&self) -> f32 {
248        self.lightpower
249    }
250
251    fn get_size(&self) -> (usize, usize) {
252        (self.width, self.height)
253    }
254
255    fn to_rgbaf32(&self) -> Vec<f32> {
256        self.pixels
257            .clone()
258            .into_iter()
259            .map(|x| [x.0, x.1, x.2, 1.0f32])
260            .flatten()
261            .collect()
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::Image;
268    use crate::image::{ExportImage, RenderImage};
269    use crate::prelude::Point;
270    use crate::ray::RayResult;
271
272    #[test]
273    fn traced_ray_is_not_black() {
274        let i = Image::new(100, 100);
275        i.draw_line(RayResult::new((10.0, 10.0), (90.0, 90.0), 620.0)); //red
276        i.draw_line(RayResult::new((20.0, 10.0), (90.0, 80.0), 520.0)); //green
277        i.draw_line(RayResult::new((10.0, 20.0), (80.0, 90.0), 470.0)); //blue
278        let mut r_count = 0.0;
279        let mut g_count = 0.0;
280        let mut b_count = 0.0;
281        for (r, g, b) in i.pixels.iter() {
282            r_count += r;
283            g_count += g;
284            b_count += b;
285        }
286        assert_ne!(r_count, 0.0);
287        assert_ne!(g_count, 0.0);
288        assert_ne!(b_count, 0.0);
289    }
290
291    #[test]
292    fn empty_image_is_black() {
293        let mut i = Image::new(1920, 1080);
294        i.prepare_render(1.0);
295        let v = i.to_rgba8(0, 1.0, 1.0);
296        for i in v.chunks(4) {
297            assert_eq!(i[0], 0u8);
298            assert_eq!(i[1], 0u8);
299            assert_eq!(i[2], 0u8);
300            assert_eq!(i[3], 255u8);
301        }
302    }
303
304    #[test]
305    fn output_len_u8() {
306        let i = Image::new(1920, 1080);
307        let v = i.to_rgba8(0, 1.0, 1.0);
308        assert_eq!(v.len(), 1920 * 1080 * 4);
309    }
310
311    #[test]
312    fn output_len_f32() {
313        let i = Image::new(1920, 1080);
314        let v = i.to_rgbaf32();
315        assert_eq!(v.len(), 1920 * 1080 * 4);
316    }
317
318    #[test]
319    fn contigious_lines() {
320        let i = Image::new(400, 400);
321        i.draw_line(RayResult::new((10.0, 10.0), (100.0, 100.0), 0.0));
322        i.draw_line(RayResult::new((100.0, 100.0), (200.0, 200.0), 0.0));
323        i.draw_line(RayResult::new((200.0, 200.0), (300.0, 300.0), 0.0));
324        i.draw_line(RayResult::new((300.0, 300.0), (390.0, 390.0), 0.0));
325
326        for x in 0..10 {
327            for y in 0..400 {
328                let p = i.pixels[(y * 400) + x];
329                assert_eq!(p.0, 0.0);
330                assert_eq!(p.1, 0.0);
331                assert_eq!(p.2, 0.0);
332            }
333        }
334
335        for x in 10..=390 {
336            for y in 0..400 {
337                let p = i.pixels[(y * 400) + x];
338                if x == y {
339                    assert!(p.0 > 0.0);
340                    assert!(p.1 > 0.0);
341                    assert!(p.2 > 0.0);
342                } else {
343                    assert_eq!(p.0, 0.0);
344                    assert_eq!(p.1, 0.0);
345                    assert_eq!(p.2, 0.0);
346                }
347            }
348        }
349
350        for x in 391..400 {
351            for y in 0..400 {
352                let p = i.pixels[(y * 400) + x];
353                assert_eq!(p.0, 0.0);
354                assert_eq!(p.1, 0.0);
355                assert_eq!(p.2, 0.0);
356            }
357        }
358    }
359
360    #[test]
361    fn line_correct() {
362        let i = Image::new(100, 100);
363        i.draw_line(RayResult::new((0.0, 0.0), (100.0, 100.0), 0.0)); //green
364        for x in 0..100 {
365            for y in 0..100 {
366                let p = i.pixels[(y * 100) + x];
367                if x == y {
368                    assert!(p.0 > 0.0);
369                    assert!(p.1 > 0.0);
370                    assert!(p.2 > 0.0);
371                } else {
372                    assert_eq!(p.0, 0.0);
373                    assert_eq!(p.1, 0.0);
374                    assert_eq!(p.2, 0.0);
375                }
376            }
377        }
378    }
379
380    #[test]
381    fn bounds_check_left1() {
382        let p0 = Point::new(-10.0, 0.0);
383        let p1 = Point::new(10.0, 20.0);
384
385        let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
386
387        assert_eq!(p0, (0.0, 10.0).into());
388        assert_eq!(p1, (10.0, 20.0).into());
389    }
390
391    #[test]
392    fn bounds_check_left2() {
393        let p1 = Point::new(-10.0, 0.0);
394        let p0 = Point::new(10.0, 20.0);
395
396        let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
397
398        assert_eq!(p0, (0.0, 10.0).into());
399        assert_eq!(p1, (10.0, 20.0).into());
400    }
401
402    #[test]
403    fn bounds_check_right1() {
404        let p0 = Point::new(60.0, 0.0);
405        let p1 = Point::new(40.0, 20.0);
406
407        let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
408
409        assert_eq!(p1, (50.0, 10.0).into());
410        assert_eq!(p0, (40.0, 20.0).into());
411    }
412
413    #[test]
414    fn bounds_check_right2() {
415        let p1 = Point::new(60.0, 0.0);
416        let p0 = Point::new(40.0, 20.0);
417
418        let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
419
420        assert_eq!(p1, (50.0, 10.0).into());
421        assert_eq!(p0, (40.0, 20.0).into());
422    }
423
424    #[test]
425    fn bounds_check_top1() {
426        let p0 = Point::new(20.0, -10.0);
427        let p1 = Point::new(40.0, 10.0);
428
429        let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
430
431        assert_eq!(p0, (30.0, 0.0).into());
432        assert_eq!(p1, (40.0, 10.0).into());
433    }
434
435    #[test]
436    fn bounds_check_top2() {
437        let p1 = Point::new(20.0, -10.0);
438        let p0 = Point::new(40.0, 10.0);
439
440        let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
441
442        assert_eq!(p0, (30.0, 0.0).into());
443        assert_eq!(p1, (40.0, 10.0).into());
444    }
445
446    #[test]
447    fn bounds_check_bottom1() {
448        let p0 = Point::new(10.0, 60.0);
449        let p1 = Point::new(40.0, 30.0);
450
451        let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
452
453        assert_eq!(p0, (20.0, 50.0).into());
454        assert_eq!(p1, (40.0, 30.0).into());
455    }
456
457    #[test]
458    fn bounds_check_bottom2() {
459        let p1 = Point::new(10.0, 60.0);
460        let p0 = Point::new(40.0, 30.0);
461
462        let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
463
464        assert_eq!(p0, (20.0, 50.0).into());
465        assert_eq!(p1, (40.0, 30.0).into());
466    }
467}