imageutil 0.0.5

Image utilities
Documentation
use image::{
    imageops::{overlay, resize, FilterType},
    GenericImage, GenericImageView, Pixel,
};
use rusttype::{point, Font, GlyphId, PositionedGlyph, Rect, Scale};
use std::{cmp::max, future::Future};

use crate::{canvas::Canvas, util::Mixer};

#[derive(Clone)]
pub struct Fonts<'a> {
    fonts: Vec<Font<'a>>,
}

impl<'a> Fonts<'a> {
    pub fn new(fonts: Vec<Font<'a>>) -> Self {
        Self { fonts }
    }
    pub fn extend(&self, font: Font<'a>) -> Self {
        let mut fonts = self.fonts.clone();
        fonts.push(font);
        Self { fonts }
    }
    pub fn get_avalible_font(&self, char: char) -> Option<&Font<'a>> {
        self.fonts
            .iter()
            .find(|f| f.glyph(char).id() != GlyphId(0))
            .or(self.fonts.last())
    }
    #[inline(always)]
    pub fn layout_glyphs(
        scale: Scale,
        font: &Font,
        char: char,
        mut f: impl FnMut(PositionedGlyph, Rect<i32>),
    ) -> (i32, i32) {
        let v_metrics = font.v_metrics(scale);
        let pos = font
            .glyph(char)
            .scaled(scale)
            .positioned(point(0.0, v_metrics.ascent));
        let (mut w, mut h) = (0, 0);
        if let Some(bb) = pos.pixel_bounding_box() {
            w = max(w, bb.max.x);
            h = max(h, bb.max.y);
            f(pos, bb);
        }
        (w, h)
    }

    #[inline(always)]
    pub fn write_char<C: Canvas>(
        &self,
        img: &mut C,
        char: char,
        scale: Scale,
        color: C::Pixel,
        image_width: i32,
        image_height: i32,
        x: i32,
        y: i32,
        font: &Font,
    ) -> i32
    where
        C::Pixel: Pixel<Subpixel = u8>,
    {
        Self::layout_glyphs(scale, font, char, |pos, bb| {
            pos.draw(|gx, gy, gv| {
                let gx = gx as i32 + bb.min.x;
                let gy = gy as i32 + bb.min.y;
                let image_x = gx + x;
                let image_y = gy + y;

                if (0..image_width).contains(&image_x) && (0..image_height).contains(&image_y) {
                    let pixel = img.get_pixel(image_x as u32, image_y as u32);
                    let mixer = Mixer::new(&color, &pixel);
                    let weighted_color = mixer.at(1.0 - gv);
                    img.draw_pixel(image_x as u32, image_y as u32, weighted_color);
                }
            });
        })
        .0 + scale.x as i32 / 15
    }

    pub async fn text_size<'b, Fut>(
        &self,
        text: &'b str,
        scale: Scale,
        resolver: impl Fn(&'b str) -> Fut,
    ) -> (i32, i32)
    where
        Fut: Future<Output = String>,
    {
        let mut width = 0;
        let mut height = 0;
        let text = resolver(text).await;
        for char in text.chars() {
            let font = self.get_avalible_font(char).unwrap();
            let wh = Self::layout_glyphs(scale, font, char, |_, _| {});
            width += wh.0 + scale.x as i32 / 15;
            height = max(height, wh.1);
        }
        (width, height)
    }

    pub async fn write_to_middle<'b, C: Canvas + GenericImage, Fut, Fut2>(
        &self,
        img: &mut C,
        text: &'b str,
        mut scale: Scale,
        color: <C as Canvas>::Pixel,
        width: i32,
        x: i32,
        y: i32,
        resolver: impl Fn(&'b str) -> Fut,
        text_size_resolver: impl Fn(&'b str) -> Fut2,
        shrink: bool,
    ) where
        Fut: Future<Output = Vec<StrOrImg<'a, C>>>,
        Fut2: Future<Output = String>,
        <C as Canvas>::Pixel: Pixel<Subpixel = u8>,
        <C as GenericImageView>::Pixel: 'static,
    {
        let text_size = self.text_size(text, scale, text_size_resolver).await.0;
        let mut newx = x + (width - text_size) / 2;
        if newx < x && shrink {
            let per = width as f32 / text_size as f32;
            scale = Scale {
                x: scale.x * per,
                y: scale.y * per,
            };
            newx = x;
        }
        self.write_to(img, text, scale, color, newx, y, resolver)
            .await;
    }

    pub async fn write_to<'b, C: Canvas + GenericImage + GenericImageView, Fut>(
        &self,
        img: &mut C,
        text: &'b str,
        scale: Scale,
        color: <C as Canvas>::Pixel,
        mut x: i32,
        y: i32,
        resolver: impl Fn(&'b str) -> Fut,
    ) where
        Fut: Future<Output = Vec<StrOrImg<'a, C>>>,
        <C as Canvas>::Pixel: Pixel<Subpixel = u8>,
        <C as GenericImageView>::Pixel: 'static,
    {
        let image_width = GenericImageView::width(img) as i32;
        let image_height = GenericImageView::height(img) as i32;
        let text = resolver(text).await;
        for text in text.iter() {
            match text {
                StrOrImg::Str(text) => {
                    for char in text.chars() {
                        let font = self.get_avalible_font(char).unwrap();
                        x = x + self.write_char(
                            img,
                            char,
                            scale,
                            color,
                            image_width,
                            image_height,
                            x,
                            y,
                            font,
                        );
                    }
                }
                StrOrImg::Img(over) => {
                    let over = resize(
                        over,
                        (scale.x * 0.9) as u32,
                        (scale.y * 0.9) as u32,
                        FilterType::Nearest,
                    );
                    overlay(img, &over, (x + scale.x as i32 / 10) as i64, y as i64);
                    x = x + over.width() as i32 + scale.x as i32 / 5;
                }
            }
        }
    }
}

pub enum StrOrImg<'a, C: Canvas> {
    Str(&'a str),
    Img(C),
}

pub async fn empty_resolver<'a, C: Canvas>(s: &'a str) -> Vec<StrOrImg<C>> {
    vec![StrOrImg::Str(s)]
}

pub async fn empty_size_resolver<'a>(s: &'a str) -> String {
    s.to_string()
}