Skip to main content

diffenator3_lib/render/
utils.rs

1use image::{GenericImage, GrayImage, ImageBuffer};
2use skrifa::outline::OutlinePen;
3use zeno::{Command, PathBuilder};
4
5/// Compute the bounding box of a path, in a terrible but fast way
6///
7/// This computes the bounding box of all control points in the path. Where
8/// curves extend beyond the control points, the real bounding box will be larger
9/// than the one returned here. But it's good enough for well-behaved curves
10/// with points on extrema.
11pub(crate) fn terrible_bounding_box(pen_buffer: &[Command]) -> (f32, f32, f32, f32) {
12    let mut max_x: f32 = 0.0;
13    let mut min_x: f32 = 0.0;
14    let mut max_y: f32 = 0.0;
15    let mut min_y: f32 = 0.0;
16
17    for command in pen_buffer {
18        match command {
19            Command::MoveTo(to) => {
20                min_x = min_x.min(to.x);
21                max_x = max_x.max(to.x);
22                min_y = min_y.min(to.y);
23                max_y = max_y.max(to.y);
24            }
25            Command::LineTo(to) => {
26                min_x = min_x.min(to.x);
27                max_x = max_x.max(to.x);
28                min_y = min_y.min(to.y);
29                max_y = max_y.max(to.y);
30            }
31            Command::QuadTo(ctrl, to) => {
32                min_x = min_x.min(ctrl.x);
33                max_x = max_x.max(ctrl.x);
34                min_y = min_y.min(ctrl.y);
35                max_y = max_y.max(ctrl.y);
36                min_x = min_x.min(to.x);
37                max_x = max_x.max(to.x);
38                min_y = min_y.min(to.y);
39                max_y = max_y.max(to.y);
40            }
41            Command::CurveTo(ctrl0, ctrl1, to) => {
42                min_x = min_x.min(ctrl0.x);
43                max_x = max_x.max(ctrl0.x);
44                min_y = min_y.min(ctrl0.y);
45                max_y = max_y.max(ctrl0.y);
46                min_x = min_x.min(ctrl1.x);
47                max_x = max_x.max(ctrl1.x);
48                min_y = min_y.min(ctrl1.y);
49                max_y = max_y.max(ctrl1.y);
50                min_x = min_x.min(to.x);
51                max_x = max_x.max(to.x);
52                min_y = min_y.min(to.y);
53                max_y = max_y.max(to.y);
54            }
55            Command::Close => {}
56        };
57    }
58    (min_x, min_y, max_x, max_y)
59}
60
61#[derive(Default)]
62pub struct RecordingPen {
63    pub buffer: Vec<Command>,
64    pub offset_x: f32,
65    pub offset_y: f32,
66}
67
68impl OutlinePen for RecordingPen {
69    fn move_to(&mut self, x: f32, y: f32) {
70        self.buffer.move_to([self.offset_x + x, self.offset_y + y]);
71    }
72
73    fn line_to(&mut self, x: f32, y: f32) {
74        self.buffer.line_to([self.offset_x + x, self.offset_y + y]);
75    }
76
77    fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
78        self.buffer.quad_to(
79            [self.offset_x + cx0, self.offset_y + cy0],
80            [self.offset_x + x, self.offset_y + y],
81        );
82    }
83
84    fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
85        self.buffer.curve_to(
86            [self.offset_x + cx0, self.offset_y + cy0],
87            [self.offset_x + cx1, self.offset_y + cy1],
88            [self.offset_x + x, self.offset_y + y],
89        );
90    }
91
92    fn close(&mut self) {
93        self.buffer.close();
94    }
95}
96
97/// Make two images the same size by padding the smaller one with white
98///
99/// The smaller image is anchored to the top left corner of the larger image.
100pub fn make_same_size(image_a: GrayImage, image_b: GrayImage) -> (GrayImage, GrayImage) {
101    let max_width = image_a.width().max(image_b.width());
102    let max_height = image_a.height().max(image_b.height());
103    let mut a = ImageBuffer::new(max_width, max_height);
104    let mut b = ImageBuffer::new(max_width, max_height);
105    a.copy_from(&image_a, 0, 0).unwrap();
106    b.copy_from(&image_b, 0, 0).unwrap();
107    (a, b)
108}
109
110/// Compare two images and return the count of differing pixels
111pub fn count_differences(img_a: GrayImage, img_b: GrayImage, fuzz: u8) -> usize {
112    let (img_a, img_b) = make_same_size(img_a, img_b);
113    let img_a_vec = img_a.to_vec();
114    img_a_vec
115        .iter()
116        .zip(img_b.to_vec())
117        .filter(|(&cha, chb)| cha.abs_diff(*chb) > fuzz)
118        .count()
119}