meme-rs 0.0.1

Generate memes from commandline or http server
Documentation
pub mod config;
pub mod server;

use conv::ValueInto;
use image::imageops::FilterType;
use image::{imageops, GenericImage, GenericImageView, ImageBuffer, Pixel, Rgba};
use imageproc::{
    definitions::Clamp,
    drawing::{draw_text_mut, Canvas},
};
use rusttype::{Font, Scale};
use std::{ffi::OsStr, fs, path::PathBuf};

pub fn new_id(filename: &str, text: &str) -> String {
    let data = format!("{}_{}", filename, text);
    format!("{:?}", md5::compute(&data))
}

pub fn read_images(img_dir: &str) -> Vec<String> {
    let mut images = vec![];
    for entry in fs::read_dir(img_dir).expect("could not read directory 'img'") {
        let entry = entry.expect("could not read directory entry");
        let path = entry.path();
        if !path.is_dir() {
            match path.extension().and_then(OsStr::to_str) {
                Some("jpeg") | Some("jpg") | Some("png") => {
                    let name = entry.file_name().to_str().unwrap().to_string();
                    images.push(name);
                }
                _ => (),
            }
        }
    }
    images
}

/// Writes a meme from a base image to an output file
/// Text may be split with a '|' character
///
/// ```
/// use std::path::PathBuf;
/// use meme_rs::write_meme;
///
/// write_meme(&PathBuf::from("./img/aliens.jpg"), &PathBuf::from("/tmp/output.jpg"), "YOLO | FOOBAR");
/// ```
pub fn write_meme(input_file: &PathBuf, output_file: &PathBuf, text: &str) {
    let image = image::open(input_file)
        .expect("failed reading image")
        .to_rgba8();
    let mut image = resize(&image, 500, FilterType::Nearest);

    let font = Vec::from(include_bytes!("./impact.ttf") as &[u8]);
    let font = Font::try_from_vec(font).unwrap();

    let (w, h) = image.dimensions();
    if !text.is_empty() {
        let text_parts = text.splitn(2, '|').collect::<Vec<_>>();
        let (text_top, text_bot) = if text_parts.len() == 1 {
            ("", text_parts[0].trim())
        } else {
            (text_parts[0].trim(), text_parts[1].trim())
        };

        if !text_top.is_empty() {
            let x_offset = (text_top.len() as u32 + 1) * 10;
            let x = w / 2 - x_offset;
            let y = 10;
            draw_stroked(
                &mut image,
                text_top,
                50.0,
                (x, y),
                &font,
                Rgba([255u8, 255u8, 255u8, 255u8]),
                Rgba([0u8, 0u8, 0u8, 255u8]),
            );
        }

        let x_offset = (text_bot.len() as u32 + 1) * 10;
        let x = w / 2 - x_offset;
        let y = h - 75;
        draw_stroked(
            &mut image,
            text_bot,
            50.0,
            (x, y),
            &font,
            Rgba([255u8, 255u8, 255u8, 255u8]),
            Rgba([0u8, 0u8, 0u8, 255u8]),
        );
    }
    image.save(output_file).expect("failed saving image");
}

fn resize<I: GenericImageView>(
    image: &I,
    nwidth: u32,
    filter: FilterType,
) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>>
where
    I::Pixel: 'static,
    <I::Pixel as Pixel>::Subpixel: 'static,
{
    let (w, h) = image.dimensions();
    if w <= nwidth {
        let mut b = ImageBuffer::new(w, h);
        b.copy_from(image, 0, 0)
            .expect("failed copying image buffer");
        b
    } else {
        let factor = w as f32 / nwidth as f32;
        let nheight = (h as f32 / factor) as u32;
        imageops::resize(image, nwidth, nheight, filter)
    }
}

fn draw_stroked<'a, C>(
    image: &'a mut C,
    text: &str,
    height: f32,
    (x, y): (u32, u32),
    font: &'a Font<'a>,
    color: C::Pixel,
    bg_color: C::Pixel,
) where
    C: Canvas,
    <C::Pixel as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32>,
{
    let scale = Scale {
        x: height,
        y: height * 1.2,
    };

    let offset = 2;
    let y_up = y - offset;
    let y_down = y + offset;
    let x_left = x - offset;
    let x_right = x + offset;

    draw_text_mut(image, bg_color, x, y_up, scale.clone(), &font, text);

    draw_text_mut(image, bg_color, x, y_down, scale.clone(), &font, text);
    draw_text_mut(image, bg_color, x_left, y, scale.clone(), &font, text);
    draw_text_mut(image, bg_color, x_right, y, scale.clone(), &font, text);
    draw_text_mut(image, color, x, y, scale, &font, text);
}