Initial repo
This commit is contained in:
3
crates/meow_image/.gitignore
vendored
Normal file
3
crates/meow_image/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target
|
||||
dem.jpg
|
||||
font.ttf
|
14
crates/meow_image/Cargo.toml
Normal file
14
crates/meow_image/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "meow_image"
|
||||
description = "Blazing fast 🚀 higher-level image proccessing library"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
image = "0.24"
|
||||
imageproc = "0.23"
|
||||
rusttype = "0.9"
|
||||
once_cell = "1"
|
||||
|
12
crates/meow_image/README.md
Normal file
12
crates/meow_image/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Meow Image
|
||||
|
||||
Blazing fast 🚀 higher-level image proccessing library
|
||||
|
||||
# Building
|
||||
|
||||
1. **Move your font to the root of the project.**
|
||||
|
||||
2. Then run
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
189
crates/meow_image/src/demotivate.rs
Normal file
189
crates/meow_image/src/demotivate.rs
Normal file
@ -0,0 +1,189 @@
|
||||
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();
|
||||
}
|
||||
}
|
4
crates/meow_image/src/lib.rs
Normal file
4
crates/meow_image/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod demotivate;
|
||||
mod text_width;
|
||||
|
||||
pub use demotivate::demotivate;
|
17
crates/meow_image/src/text_width.rs
Normal file
17
crates/meow_image/src/text_width.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use rusttype::{point, Font, Scale};
|
||||
|
||||
pub fn get_text_width(scale: Scale, font: &Font, text: &str) -> u32 {
|
||||
let mut layout = font.layout(text, scale, point(0.0, 0.0));
|
||||
|
||||
let min = layout
|
||||
.next()
|
||||
.map(|g| g.pixel_bounding_box().unwrap().min.x)
|
||||
.unwrap();
|
||||
|
||||
let max = layout
|
||||
.last()
|
||||
.map(|g| g.pixel_bounding_box().unwrap().max.x)
|
||||
.unwrap();
|
||||
|
||||
(max - min) as u32
|
||||
}
|
Reference in New Issue
Block a user