1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use image::{DynamicImage, ImageResult, Rgb, RgbImage};
use imageproc::{
    drawing::{draw_filled_rect, draw_hollow_rect},
    rect::Rect,
};
use rusttype::Scale;

use crate::{geometry::Orientation, words::Font};

pub struct Image {
    img: RgbImage,
    bg_color: Rgb<u8>,
    width: u32,
    height: u32,
}

impl Image {
    pub fn new(width: u32, height: u32, bg_color: Option<Rgb<u8>>) -> Self {
        let mut image = Image {
            img: DynamicImage::new_rgb8(width, height).to_rgb8(),
            bg_color: match bg_color {
                Some(color) => color,
                None => Rgb([255, 255, 255]),
            },
            width,
            height,
        };

        image.draw_filled_rect(0, 0, width, height, image.bg_color);

        image
    }

    pub fn save(&self, filename: &str) -> ImageResult<()> {
        self.img.save(filename)
    }

    pub fn draw_filled_rect(&mut self, x: i32, y: i32, w: u32, h: u32, color: Rgb<u8>) {
        let rect = Rect::at(x, y).of_size(w, h);
        self.img = draw_filled_rect(&self.img, rect, color)
    }

    pub fn draw_rect(&mut self, x: i32, y: i32, w: u32, h: u32, color: Rgb<u8>) {
        let rect = Rect::at(x, y).of_size(w, h);
        self.img = draw_hollow_rect(&self.img, rect, color)
    }

    pub fn draw_text(
        &mut self,
        pos: (i32, i32),
        text: &str,
        font: &Font,
        scale: Scale,
        color: Rgb<u8>,
        orientation: &Orientation,
    ) {
        let data = font.get_glyph_data(text, scale);
        let r = data
            .bounding_box(orientation)
            .displace(pos.0 as isize, pos.1 as isize)
            .unwrap();

        let v_metrics = font.get_metrics(scale);

        for glyph in data.glyphs {
            if let Some(bounding_box) = glyph.pixel_bounding_box() {
                glyph.draw(|x, y, v| {
                    let c = Self::alpha_blend(self.bg_color, color, v);

                    let new_x = match orientation {
                        Orientation::Horizontal => x + bounding_box.min.x as u32 + r.pos().0,
                        Orientation::Vertical => {
                            (bounding_box.min.y
                                + r.pos().0 as i32
                                + r.size().0 as i32
                                + v_metrics.descent as i32
                                + y as i32) as u32
                        }
                    };

                    let new_y = match orientation {
                        Orientation::Horizontal => {
                            (y as i32
                                + bounding_box.min.y
                                + r.pos().1 as i32
                                + r.size().1 as i32
                                + v_metrics.descent as i32) as u32
                        }
                        Orientation::Vertical => {
                            (-bounding_box.min.x + r.pos().1 as i32 + r.size().1 as i32 - x as i32)
                                as u32
                        }
                    };

                    if (new_x < self.width) && (new_y < self.height) {
                        self.img.put_pixel(new_x, new_y, c)
                    }
                });
            }
        }
    }

    fn alpha_blend(bg: Rgb<u8>, fg: Rgb<u8>, percentage: f32) -> Rgb<u8> {
        fn ab(bg: f32, fg: f32, percentage: f32) -> f32 {
            (fg * percentage) + (bg * (1.0 - percentage))
        }

        Rgb([
            ab(bg.0[0] as f32, fg.0[0] as f32, percentage) as u8,
            ab(bg.0[1] as f32, fg.0[1] as f32, percentage) as u8,
            ab(bg.0[2] as f32, fg.0[2] as f32, percentage) as u8,
        ])
    }
}