word_cloud/visualization/
image.rs1use image::{DynamicImage, ImageResult, Rgb, RgbImage};
2use imageproc::{
3 drawing::{draw_filled_rect, draw_hollow_rect},
4 rect::Rect,
5};
6use rusttype::Scale;
7
8use crate::{geometry::Orientation, words::Font};
9
10pub struct Image {
11 img: RgbImage,
12 bg_color: Rgb<u8>,
13 width: u32,
14 height: u32,
15}
16
17impl Image {
18 pub fn new(width: u32, height: u32, bg_color: Option<Rgb<u8>>) -> Self {
19 let mut image = Image {
20 img: DynamicImage::new_rgb8(width, height).to_rgb8(),
21 bg_color: match bg_color {
22 Some(color) => color,
23 None => Rgb([255, 255, 255]),
24 },
25 width,
26 height,
27 };
28
29 image.draw_filled_rect(0, 0, width, height, image.bg_color);
30
31 image
32 }
33
34 pub fn save(&self, filename: &str) -> ImageResult<()> {
35 self.img.save(filename)
36 }
37
38 pub fn draw_filled_rect(&mut self, x: i32, y: i32, w: u32, h: u32, color: Rgb<u8>) {
39 let rect = Rect::at(x, y).of_size(w, h);
40 self.img = draw_filled_rect(&self.img, rect, color)
41 }
42
43 pub fn draw_rect(&mut self, x: i32, y: i32, w: u32, h: u32, color: Rgb<u8>) {
44 let rect = Rect::at(x, y).of_size(w, h);
45 self.img = draw_hollow_rect(&self.img, rect, color)
46 }
47
48 pub fn draw_text(
49 &mut self,
50 pos: (i32, i32),
51 text: &str,
52 font: &Font,
53 scale: Scale,
54 color: Rgb<u8>,
55 orientation: &Orientation,
56 ) {
57 let data = font.get_glyph_data(text, scale);
58 let r = data
59 .bounding_box(orientation)
60 .displace(pos.0 as isize, pos.1 as isize)
61 .unwrap();
62
63 let v_metrics = font.get_metrics(scale);
64
65 for glyph in data.glyphs {
66 if let Some(bounding_box) = glyph.pixel_bounding_box() {
67 glyph.draw(|x, y, v| {
68 let c = Self::alpha_blend(self.bg_color, color, v);
69
70 let new_x = match orientation {
71 Orientation::Horizontal => x + bounding_box.min.x as u32 + r.pos().0,
72 Orientation::Vertical => {
73 (bounding_box.min.y
74 + r.pos().0 as i32
75 + r.size().0 as i32
76 + v_metrics.descent as i32
77 + y as i32) as u32
78 }
79 };
80
81 let new_y = match orientation {
82 Orientation::Horizontal => {
83 (y as i32
84 + bounding_box.min.y
85 + r.pos().1 as i32
86 + r.size().1 as i32
87 + v_metrics.descent as i32) as u32
88 }
89 Orientation::Vertical => {
90 (-bounding_box.min.x + r.pos().1 as i32 + r.size().1 as i32 - x as i32)
91 as u32
92 }
93 };
94
95 if (new_x < self.width) && (new_y < self.height) {
96 self.img.put_pixel(new_x, new_y, c)
97 }
98 });
99 }
100 }
101 }
102
103 fn alpha_blend(bg: Rgb<u8>, fg: Rgb<u8>, percentage: f32) -> Rgb<u8> {
104 fn ab(bg: f32, fg: f32, percentage: f32) -> f32 {
105 (fg * percentage) + (bg * (1.0 - percentage))
106 }
107
108 Rgb([
109 ab(bg.0[0] as f32, fg.0[0] as f32, percentage) as u8,
110 ab(bg.0[1] as f32, fg.0[1] as f32, percentage) as u8,
111 ab(bg.0[2] as f32, fg.0[2] as f32, percentage) as u8,
112 ])
113 }
114}