1pub mod config;
2pub mod server;
3
4use conv::ValueInto;
5use image::imageops::FilterType;
6use image::{imageops, GenericImage, GenericImageView, ImageBuffer, Pixel, Rgba};
7use imageproc::{
8 definitions::Clamp,
9 drawing::{draw_text_mut, Canvas},
10};
11use rusttype::{Font, Scale};
12use std::{ffi::OsStr, fs, path::PathBuf};
13
14pub fn new_id(filename: &str, text: &str) -> String {
15 let data = format!("{}_{}", filename, text);
16 format!("{:?}", md5::compute(&data))
17}
18
19pub fn read_images(img_dir: &str) -> Vec<String> {
20 let mut images = vec![];
21 for entry in fs::read_dir(img_dir).expect("could not read directory 'img'") {
22 let entry = entry.expect("could not read directory entry");
23 let path = entry.path();
24 if !path.is_dir() {
25 match path.extension().and_then(OsStr::to_str) {
26 Some("jpeg") | Some("jpg") | Some("png") => {
27 let name = entry.file_name().to_str().unwrap().to_string();
28 images.push(name);
29 }
30 _ => (),
31 }
32 }
33 }
34 images
35}
36
37pub fn write_meme(input_file: &PathBuf, output_file: &PathBuf, text: &str) {
47 let image = image::open(input_file)
48 .expect("failed reading image")
49 .to_rgba8();
50 let mut image = resize(&image, 500, FilterType::Nearest);
51
52 let font = Vec::from(include_bytes!("./impact.ttf") as &[u8]);
53 let font = Font::try_from_vec(font).unwrap();
54
55 let (w, h) = image.dimensions();
56 if !text.is_empty() {
57 let text_parts = text.splitn(2, '|').collect::<Vec<_>>();
58 let (text_top, text_bot) = if text_parts.len() == 1 {
59 ("", text_parts[0].trim())
60 } else {
61 (text_parts[0].trim(), text_parts[1].trim())
62 };
63
64 if !text_top.is_empty() {
65 let x_offset = (text_top.len() as u32 + 1) * 10;
66 let x = w / 2 - x_offset;
67 let y = 10;
68 draw_stroked(
69 &mut image,
70 text_top,
71 50.0,
72 (x, y),
73 &font,
74 Rgba([255u8, 255u8, 255u8, 255u8]),
75 Rgba([0u8, 0u8, 0u8, 255u8]),
76 );
77 }
78
79 let x_offset = (text_bot.len() as u32 + 1) * 10;
80 let x = w / 2 - x_offset;
81 let y = h - 75;
82 draw_stroked(
83 &mut image,
84 text_bot,
85 50.0,
86 (x, y),
87 &font,
88 Rgba([255u8, 255u8, 255u8, 255u8]),
89 Rgba([0u8, 0u8, 0u8, 255u8]),
90 );
91 }
92 image.save(output_file).expect("failed saving image");
93}
94
95fn resize<I: GenericImageView>(
96 image: &I,
97 nwidth: u32,
98 filter: FilterType,
99) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>>
100where
101 I::Pixel: 'static,
102 <I::Pixel as Pixel>::Subpixel: 'static,
103{
104 let (w, h) = image.dimensions();
105 if w <= nwidth {
106 let mut b = ImageBuffer::new(w, h);
107 b.copy_from(image, 0, 0)
108 .expect("failed copying image buffer");
109 b
110 } else {
111 let factor = w as f32 / nwidth as f32;
112 let nheight = (h as f32 / factor) as u32;
113 imageops::resize(image, nwidth, nheight, filter)
114 }
115}
116
117fn draw_stroked<'a, C>(
118 image: &'a mut C,
119 text: &str,
120 height: f32,
121 (x, y): (u32, u32),
122 font: &'a Font<'a>,
123 color: C::Pixel,
124 bg_color: C::Pixel,
125) where
126 C: Canvas,
127 <C::Pixel as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32>,
128{
129 let scale = Scale {
130 x: height,
131 y: height * 1.2,
132 };
133
134 let offset = 2;
135 let y_up = y - offset;
136 let y_down = y + offset;
137 let x_left = x - offset;
138 let x_right = x + offset;
139
140 draw_text_mut(image, bg_color, x, y_up, scale.clone(), &font, text);
141
142 draw_text_mut(image, bg_color, x, y_down, scale.clone(), &font, text);
143 draw_text_mut(image, bg_color, x_left, y, scale.clone(), &font, text);
144 draw_text_mut(image, bg_color, x_right, y, scale.clone(), &font, text);
145 draw_text_mut(image, color, x, y, scale, &font, text);
146}