katana-document-viewer 0.1.4

KatanA document viewer artifact, render evaluation, and export foundation.
Documentation
use crate::export_surface_helpers::SurfaceHelpers;
use image::RgbaImage;

use super::icons_constants::{
    STROKE_ARC_COLOR, STROKE_ARC_PAD_HEIGHT, STROKE_ARC_PAD_WIDTH, STROKE_ARC_PADDING_XY,
    STROKE_OUTLINE_INNER_SUB, STROKE_PIXEL_SIZE,
};

use super::icons_constants::CAUTION_MID_Y;

pub(super) fn draw_stroked_line(
    image: &mut RgbaImage,
    start_x: u32,
    start_y: u32,
    end_x: u32,
    end_y: u32,
    color: image::Rgba<u8>,
) {
    let mut state = StrokedLineState::new(start_x, start_y, end_x, end_y);
    loop {
        draw_stroked_point(image, state.x, state.y, color);
        if state.is_finished() {
            break;
        }
        state.advance();
    }
}

struct StrokedLineState {
    x: i32,
    y: i32,
    end_x: i32,
    end_y: i32,
    dx: i32,
    sx: i32,
    dy: i32,
    sy: i32,
    error: i32,
}

impl StrokedLineState {
    fn new(start_x: u32, start_y: u32, end_x: u32, end_y: u32) -> Self {
        let x = start_x as i32;
        let y = start_y as i32;
        let end_x = end_x as i32;
        let end_y = end_y as i32;
        let dx = (end_x - x).abs();
        let dy = -(end_y - y).abs();
        Self {
            x,
            y,
            end_x,
            end_y,
            dx,
            sx: if x < end_x { 1 } else { -1 },
            dy,
            sy: if y < end_y { 1 } else { -1 },
            error: dx + dy,
        }
    }

    fn is_finished(&self) -> bool {
        self.x == self.end_x && self.y == self.end_y
    }

    fn advance(&mut self) {
        let doubled_error = 2 * self.error;
        if doubled_error >= self.dy {
            self.error += self.dy;
            self.x += self.sx;
        }
        if doubled_error <= self.dx {
            self.error += self.dx;
            self.y += self.sy;
        }
    }
}

fn draw_stroked_point(image: &mut RgbaImage, x: i32, y: i32, color: image::Rgba<u8>) {
    for offset_y in -1..=1 {
        for offset_x in -1..=1 {
            if let (Ok(pixel_x), Ok(pixel_y)) =
                (u32::try_from(x + offset_x), u32::try_from(y + offset_y))
            {
                SurfaceHelpers::fill_rect(
                    image,
                    pixel_x,
                    pixel_y,
                    STROKE_PIXEL_SIZE,
                    STROKE_PIXEL_SIZE,
                    color,
                );
            }
        }
    }
}

pub(super) fn draw_filled_circle(
    image: &mut RgbaImage,
    center_x: u32,
    center_y: u32,
    radius: u32,
    color: image::Rgba<u8>,
) {
    let radius_squared = (radius * radius) as i32;
    for y in center_y.saturating_sub(radius)..=center_y + radius {
        for x in center_x.saturating_sub(radius)..=center_x + radius {
            let dx = x as i32 - center_x as i32;
            let dy = y as i32 - center_y as i32;
            if dx * dx + dy * dy <= radius_squared {
                SurfaceHelpers::fill_rect(image, x, y, STROKE_PIXEL_SIZE, STROKE_PIXEL_SIZE, color);
            }
        }
    }
}

pub(super) fn draw_stroked_circle(
    image: &mut RgbaImage,
    center_x: u32,
    center_y: u32,
    radius: u32,
    color: image::Rgba<u8>,
) {
    let inner = radius.saturating_sub(STROKE_OUTLINE_INNER_SUB);
    let outer_squared = (radius * radius) as i32;
    let inner_squared = (inner * inner) as i32;
    for y in center_y.saturating_sub(radius)..=center_y + radius {
        for x in center_x.saturating_sub(radius)..=center_x + radius {
            let dx = x as i32 - center_x as i32;
            let dy = y as i32 - center_y as i32;
            let distance = dx * dx + dy * dy;
            if distance <= outer_squared && distance >= inner_squared {
                SurfaceHelpers::fill_rect(image, x, y, STROKE_PIXEL_SIZE, STROKE_PIXEL_SIZE, color);
            }
        }
    }
}

pub(super) fn draw_stroked_circle_arc(
    image: &mut RgbaImage,
    center_x: u32,
    center_y: u32,
    radius: u32,
    color: image::Rgba<u8>,
) {
    draw_stroked_circle(image, center_x, center_y, radius, color);
    SurfaceHelpers::fill_rect(
        image,
        center_x.saturating_sub(radius + STROKE_ARC_PADDING_XY),
        center_y + CAUTION_MID_Y,
        radius * 2 + STROKE_ARC_PAD_WIDTH,
        radius + STROKE_ARC_PAD_HEIGHT,
        STROKE_ARC_COLOR,
    );
}

#[cfg(test)]
#[path = "icons_primitives_tests.rs"]
mod tests;