meme_rs/
lib.rs

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
37/// Writes a meme from a base image to an output file
38/// Text may be split with a '|' character
39///
40/// ```
41/// use std::path::PathBuf;
42/// use meme_rs::write_meme;
43///
44/// write_meme(&PathBuf::from("./img/aliens.jpg"), &PathBuf::from("/tmp/output.jpg"), "YOLO | FOOBAR");
45/// ```
46pub 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}