node-demotivator-native/crates/meow_image/src/demotivate.rs

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();
}
}