use crate::text_width::get_text_width; use image::{GenericImage, ImageBuffer, Rgb, RgbImage}; use imageproc::drawing::draw_text_mut; use once_cell::sync::Lazy; use rusttype::{Font, Scale}; const WHITE: Rgb = Rgb([255, 255, 255]); const BLACK: Rgb = Rgb([0, 0, 0]); static FONT_DATA: &[u8] = include_bytes!("../../../assets/XO_Thames_Nu.ttf"); static FONT: Lazy> = Lazy::new(|| Font::try_from_bytes(FONT_DATA).unwrap()); #[derive(Clone, Copy)] struct Border { pub top: u32, pub left: u32, pub bottom: u32, pub right: u32, } /// Makes a demotivator meme pub fn demotivate(image: &RgbImage, top_text: &str, bottom_text: &str) -> RgbImage { let (width, height) = image.dimensions(); // Calculate a border let border_size = (if width > height { width } else { height }) / 8; let border = Border { top: border_size, left: border_size, bottom: border_size * 2, right: border_size, }; // Add a black border let mut image_with_border = add_border(image, BLACK, border); let (width, height) = image_with_border.dimensions(); // Calculate the padding and the line width let padding = border_size / 10; let line_width = padding / 3; // Add a white box next to the image add_box(&mut image_with_border, WHITE, border, line_width, padding); // Calculate the scale and position of the top text { let scale = Scale::uniform(border_size as f32); let text_width = get_text_width(scale, &FONT, top_text); let x_text_position = (width - text_width) / 2; // center by width let y_text_position = height - (border.bottom - padding); // Draw the top text draw_text_mut( &mut image_with_border, WHITE, x_text_position as i32, y_text_position as i32, scale, &FONT, top_text, ); } // Calculate the scale and position of the bottom text { let scale = Scale::uniform(border_size as f32 / 2f32); let text_width = get_text_width(scale, &FONT, bottom_text); let x_text_position = (width - text_width) / 2; // center by width let y_text_position = height - (border.bottom - padding) / 2; // Draw the bottom text draw_text_mut( &mut image_with_border, WHITE, x_text_position as i32, y_text_position as i32, scale, &FONT, bottom_text, ); } image_with_border } fn add_border( image: &RgbImage, color: Rgb, Border { top, left, bottom, right, }: Border, ) -> RgbImage { // Get the dimensions of the image let (width, height) = image.dimensions(); // Create a new image with a border #[rustfmt::skip] let mut out_image = ImageBuffer::from_fn( width + left + right, height + top + bottom, |_, _| color ); // Copy the original image into the center of the new image out_image.copy_from(image, top, left).unwrap(); out_image } fn add_box( image: &mut RgbImage, color: Rgb, Border { mut top, mut left, mut bottom, mut right, }: Border, line_width: u32, padding: u32, ) { top -= padding; left -= padding; bottom -= padding; right -= padding; // Get the dimensions of the image let (width, height) = image.dimensions(); // Calculate the position and dimensions of the box let x1 = left; let y1 = top; let x2 = width - (right + 1); let y2 = height - (bottom + 1); // Iterate over the pixels of the line and set the pixel values to color for x in x1..=x2 { for i in 0..line_width { // top line // +i to go bottom image.put_pixel(x, y1 + i, color); // bottom line // -i to go top image.put_pixel(x, y2 - i, color); } } // Because we have already painted the corners for y in (y1 + line_width)..=(y2 - line_width) { for i in 0..line_width { // left line // +i to go right image.put_pixel(x1 + i, y, color); // right line // -i to go left image.put_pixel(x2 - i, y, color); } } } #[cfg(test)] mod tests { use super::*; use image::io::Reader as ImageReader; #[test] fn demotivate_test() { let image = ImageReader::open("./patterns/test.jpg") .unwrap() .decode() .unwrap() .into_rgb8(); let demotivated_image = demotivate(&image, "He is crazy!", "RUN"); demotivated_image.save("dem.jpg").unwrap(); } }