190 lines
4.7 KiB
Rust
190 lines
4.7 KiB
Rust
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<u8> = Rgb([255, 255, 255]);
|
|
const BLACK: Rgb<u8> = Rgb([0, 0, 0]);
|
|
|
|
static FONT_DATA: &[u8] = include_bytes!("../../../assets/XO_Thames_Nu.ttf");
|
|
|
|
static FONT: Lazy<Font<'static>> = 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<u8>,
|
|
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<u8>,
|
|
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();
|
|
}
|
|
}
|