bevy_ratatui_camera 0.14.2

A bevy plugin for rendering your bevy app to the terminal using ratatui.
Documentation
use image::{DynamicImage, Rgb, Rgba};
use ratatui::style::Color;

use crate::{ColorChoice, RatatuiCameraEdgeDetection};

pub fn coords_from_index(index: usize, image: &DynamicImage) -> (u16, u16) {
    (
        index as u16 % image.width() as u16,
        index as u16 / image.width() as u16,
    )
}

pub fn replace_detected_edges(
    character: char,
    fg: Option<Color>,
    sobel_value: &Rgba<u8>,
    edge_detection: &RatatuiCameraEdgeDetection,
) -> (char, Option<Color>) {
    let edge_color = edge_detection.edge_color.or(fg);
    match edge_detection.edge_characters {
        crate::EdgeCharacters::Directional {
            vertical,
            horizontal,
            forward_diagonal,
            backward_diagonal,
        } => {
            let is_max_sobel = |current: u8| {
                sobel_value
                    .0
                    .iter()
                    .all(|val| (current > 0) && (current >= *val))
            };

            if is_max_sobel(sobel_value[0]) {
                (vertical, edge_color)
            } else if is_max_sobel(sobel_value[1]) {
                (horizontal, edge_color)
            } else if is_max_sobel(sobel_value[2]) {
                (forward_diagonal, edge_color)
            } else if is_max_sobel(sobel_value[3]) {
                (backward_diagonal, edge_color)
            } else {
                (character, fg)
            }
        }
        crate::EdgeCharacters::Single(edge_character) => {
            if sobel_value.0.iter().any(|val| *val > 0) {
                (edge_character, edge_color)
            } else {
                (character, fg)
            }
        }
    }
}

pub fn average_in_rgb(rgb_triplet: &[u8; 3], pixel: &Rgb<u8>) -> [u8; 3] {
    [
        ((rgb_triplet[0] as u16 + pixel[0] as u16) / 2) as u8,
        ((rgb_triplet[1] as u16 + pixel[1] as u16) / 2) as u8,
        ((rgb_triplet[2] as u16 + pixel[2] as u16) / 2) as u8,
    ]
}

pub fn average_in_rgba(rgba_quad: &[u8; 4], pixel: &Rgba<u8>) -> [u8; 4] {
    [
        ((rgba_quad[0] as u16 + pixel[0] as u16) / 2) as u8,
        ((rgba_quad[1] as u16 + pixel[1] as u16) / 2) as u8,
        ((rgba_quad[2] as u16 + pixel[2] as u16) / 2) as u8,
        ((rgba_quad[3] as u16 + pixel[3] as u16) / 2) as u8,
    ]
}

pub fn colors_for_color_choices(
    fg: Option<Color>,
    bg: Option<Color>,
    fg_color_choice: &Option<ColorChoice>,
    bg_color_choice: &Option<ColorChoice>,
) -> (Option<Color>, Option<Color>) {
    let new_fg = if let Some(color_choice) = fg_color_choice {
        color_for_color_choice(fg, bg, color_choice)
    } else {
        fg
    };

    let new_bg = if let Some(color_choice) = bg_color_choice {
        color_for_color_choice(fg, bg, color_choice)
    } else {
        bg
    };

    (new_fg, new_bg)
}

fn color_for_color_choice(
    fg: Option<Color>,
    bg: Option<Color>,
    color_choice: &ColorChoice,
) -> Option<Color> {
    match color_choice {
        ColorChoice::Color(color) => Some(*color),
        ColorChoice::Scale(scale) => match fg {
            Some(Color::Rgb(r, g, b)) => Some(Color::Rgb(
                (r as f32 * scale).min(u8::MAX as f32) as u8,
                (g as f32 * scale).min(u8::MAX as f32) as u8,
                (b as f32 * scale).min(u8::MAX as f32) as u8,
            )),
            _ => None,
        },
        ColorChoice::Callback(callback) => callback(fg, bg),
    }
}