katana-document-viewer 0.1.4

KatanA document viewer artifact, render evaluation, and export foundation.
Documentation
use cosmic_text::Color;
use image::{Rgba, RgbaImage};

use super::{COLOR_CHANNEL_COUNT, DEFAULT_ALPHA, UNDERLINE_STROKE_THICKNESS};

pub(super) fn paste_inline_rgba(target: &mut RgbaImage, source: &RgbaImage, x: u32, y: u32) {
    for (source_x, source_y, pixel) in source.enumerate_pixels() {
        blend_pixel(
            target,
            x.saturating_add(source_x) as i32,
            y.saturating_add(source_y) as i32,
            *pixel,
        );
    }
}

pub(super) fn draw_horizontal_line(
    image: &mut RgbaImage,
    x: u32,
    y: u32,
    width: u32,
    color: Rgba<u8>,
) {
    for dy in 0..UNDERLINE_STROKE_THICKNESS {
        for dx in 0..width {
            PixelWriter::put(image, x + dx, y + dy, color);
        }
    }
}

pub(super) fn fill_rect(
    image: &mut RgbaImage,
    x: u32,
    y: u32,
    width: u32,
    height: u32,
    color: Rgba<u8>,
) {
    for dy in 0..height {
        for dx in 0..width {
            PixelWriter::put(image, x + dx, y + dy, color);
        }
    }
}

#[derive(Clone, Copy)]
pub(super) struct SurfaceGlyphPixel {
    origin_x: u32,
    origin_y: u32,
    glyph_x: i32,
    glyph_y: i32,
    width: u32,
    height: u32,
    color: Color,
}

pub(super) fn fill_glyph_pixel(image: &mut RgbaImage, glyph: SurfaceGlyphPixel) {
    let [red, green, blue, alpha] = glyph.color.as_rgba();
    for dy in 0..glyph.height {
        for dx in 0..glyph.width {
            blend_pixel(
                image,
                glyph.origin_x as i32 + glyph.glyph_x + dx as i32,
                glyph.origin_y as i32 + glyph.glyph_y + dy as i32,
                Rgba([red, green, blue, alpha]),
            );
        }
    }
}

pub(super) fn blend_pixel(image: &mut RgbaImage, x: i32, y: i32, color: Rgba<u8>) {
    if x < 0 || y < 0 || x >= image.width() as i32 || y >= image.height() as i32 {
        return;
    }
    let alpha = f32::from(color[COLOR_CHANNEL_COUNT]) / f32::from(DEFAULT_ALPHA);
    let pixel = image.get_pixel_mut(x as u32, y as u32);
    for index in 0..COLOR_CHANNEL_COUNT {
        let source = f32::from(color[index]);
        let target = f32::from(pixel[index]);
        pixel[index] = (source * alpha + target * (1.0 - alpha)) as u8;
    }
    pixel[COLOR_CHANNEL_COUNT] = DEFAULT_ALPHA;
}

struct PixelWriter;

impl PixelWriter {
    fn put(image: &mut RgbaImage, target_x: u32, target_y: u32, color: Rgba<u8>) {
        if target_x < image.width() && target_y < image.height() {
            image.put_pixel(target_x, target_y, color);
        }
    }
}