1#![warn(clippy::pedantic)]
2#![warn(clippy::nursery)]
3
4mod body;
5mod subbody;
6mod title;
7mod utils;
8mod via;
9
10use image::{imageops, DynamicImage, Rgba, RgbaImage};
11
12const BLACK: Rgba<u8> = Rgba([0, 0, 0, 255]);
13const TRANSPARENT: Rgba<u8> = Rgba([0, 0, 0, 0]);
14
15#[must_use]
17pub fn generate_graphic(
18 title: &str,
19 body: Option<&str>,
20 subbody: Option<&str>,
21 via: Option<&str>,
22 image: &DynamicImage,
23) -> DynamicImage {
24 let title = title.trim().to_uppercase();
25 let body = body.map(|body| body.trim().to_uppercase());
26 let subbody = subbody.map(|subbody| subbody.trim().to_uppercase());
27 let via = via.map(|via| via.trim().to_uppercase());
28
29 let mut img = background(image);
30
31 title::draw(&mut img, &title);
32
33 if let Some(body) = &body {
34 body::draw(&mut img, body);
35 }
36
37 if let Some(subbody) = &subbody {
38 subbody::draw(&mut img, subbody);
39 }
40
41 if let Some(via) = &via {
42 via::draw(&mut img, via);
43 }
44
45 img
46}
47
48fn background(img: &DynamicImage) -> DynamicImage {
49 let photo = {
50 let (img_width, img_height) = (img.width(), img.height());
51
52 let aspect_ratio = 3.0 / 2.0;
53
54 let (width, height) = if f64::from(img_width) > f64::from(img_height) * aspect_ratio {
55 (img_height * 3 / 2, img_height)
56 } else {
57 (img_width, img_width / 3 * 2)
58 };
59
60 let x = (img_width - width) / 2;
61 let y = (img_height - height) / 2;
62
63 img.crop_imm(x, y, width, height)
64 };
65
66 let mut img =
67 DynamicImage::ImageRgba8(RgbaImage::from_pixel(photo.width(), photo.width(), BLACK));
68
69 imageops::overlay(&mut img, &photo, 0, 0);
70
71 let mut img = img.thumbnail_exact(1024, 1024);
72
73 let side_length = img.height();
74
75 let overlay_height = utils::div_ceil(side_length, 3);
76 let gradient = {
77 let mut gradient = RgbaImage::new(side_length, overlay_height);
78 imageops::vertical_gradient(&mut gradient, &TRANSPARENT, &BLACK);
79
80 gradient
81 };
82
83 imageops::overlay(&mut img, &gradient, 0, overlay_height.into());
84
85 img
86}