use image::{Rgba, RgbaImage};
use crate::frame::{CellColor, CellStyle, TerminalFrame};
const CELL_WIDTH: u32 = 8;
const CELL_HEIGHT: u32 = 16;
const DEFAULT_FG: CellColor = CellColor {
red: 204,
green: 204,
blue: 204,
};
const DEFAULT_BG: CellColor = CellColor {
red: 30,
green: 30,
blue: 30,
};
pub(crate) fn render_to_image(frame: &TerminalFrame) -> RgbaImage {
let image_width = u32::from(frame.cols()) * CELL_WIDTH;
let image_height = u32::from(frame.rows()) * CELL_HEIGHT;
let mut image = RgbaImage::from_pixel(image_width, image_height, color_to_rgba(DEFAULT_BG));
for row in 0..frame.rows() {
for col in 0..frame.cols() {
let foreground = frame.fg_color(row, col).unwrap_or(DEFAULT_FG);
let background = frame.bg_color(row, col).unwrap_or(DEFAULT_BG);
let style = frame.cell_style(row, col).unwrap_or_default();
let cell_text = cell_text_at(frame, row, col);
let character = cell_text.chars().next().unwrap_or(' ');
render_cell(
&mut image,
u32::from(col) * CELL_WIDTH,
u32::from(row) * CELL_HEIGHT,
character,
foreground,
background,
style,
);
}
}
image
}
fn cell_text_at(frame: &TerminalFrame, row: u16, col: u16) -> &str {
frame.cell_text(row, col)
}
fn render_cell(
image: &mut RgbaImage,
pixel_x: u32,
pixel_y: u32,
character: char,
foreground: CellColor,
background: CellColor,
style: CellStyle,
) {
let (text_color, fill_color) = if style.inverse() {
(background, foreground)
} else {
(foreground, background)
};
let final_fg = apply_brightness(text_color, style);
let bg_rgba = color_to_rgba(fill_color);
let fg_rgba = color_to_rgba(final_fg);
fill_rect(image, pixel_x, pixel_y, CELL_WIDTH, CELL_HEIGHT, bg_rgba);
let glyph = lookup_glyph(character);
draw_glyph(image, pixel_x, pixel_y, glyph, fg_rgba);
if style.underline() {
let underline_y = pixel_y + CELL_HEIGHT - 1;
for offset_x in 0..CELL_WIDTH {
let target_x = pixel_x + offset_x;
if target_x < image.width() && underline_y < image.height() {
image.put_pixel(target_x, underline_y, fg_rgba);
}
}
}
}
fn apply_brightness(color: CellColor, style: CellStyle) -> CellColor {
if style.bold() {
CellColor::new(
brighten_component(color.red),
brighten_component(color.green),
brighten_component(color.blue),
)
} else if style.dim() {
CellColor::new(
dim_component(color.red),
dim_component(color.green),
dim_component(color.blue),
)
} else {
color
}
}
fn brighten_component(value: u8) -> u8 {
let brightened = u16::from(value) * 140 / 100;
u8::try_from(brightened.min(255)).unwrap_or(255)
}
fn dim_component(value: u8) -> u8 {
let dimmed = u16::from(value) * 60 / 100;
u8::try_from(dimmed).unwrap_or(0)
}
fn fill_rect(
image: &mut RgbaImage,
start_x: u32,
start_y: u32,
width: u32,
height: u32,
color: Rgba<u8>,
) {
for offset_y in 0..height {
for offset_x in 0..width {
let target_x = start_x + offset_x;
let target_y = start_y + offset_y;
if target_x < image.width() && target_y < image.height() {
image.put_pixel(target_x, target_y, color);
}
}
}
}
fn draw_glyph(
image: &mut RgbaImage,
pixel_x: u32,
pixel_y: u32,
glyph: &[u8; 16],
color: Rgba<u8>,
) {
for (glyph_row, &byte) in glyph.iter().enumerate() {
let row_offset = u32::try_from(glyph_row).unwrap_or(0);
for bit in 0..8u32 {
if byte & (0x80 >> bit) != 0 {
let target_x = pixel_x + bit;
let target_y = pixel_y + row_offset;
if target_x < image.width() && target_y < image.height() {
image.put_pixel(target_x, target_y, color);
}
}
}
}
}
fn color_to_rgba(color: CellColor) -> Rgba<u8> {
Rgba([color.red, color.green, color.blue, 255])
}
const EMPTY_GLYPH: [u8; 16] = [0; 16];
fn lookup_glyph(character: char) -> &'static [u8; 16] {
let code = character as u32;
if (0x20..=0x7E).contains(&code) {
let index = (code - 0x20) as usize;
&ASCII_GLYPHS[index]
} else if (0x2500..=0x257F).contains(&code) {
let index = (code - 0x2500) as usize;
&BOX_DRAWING_GLYPHS[index]
} else {
&EMPTY_GLYPH
}
}
#[rustfmt::skip]
pub(crate) static ASCII_GLYPHS: [[u8; 16]; 95] = [
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00],
[0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x6C,0x6C,0xFE,0x6C,0x6C,0x6C,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00],
[0x18,0x18,0x7C,0xC6,0xC2,0xC0,0x7C,0x06,0x06,0x86,0xC6,0x7C,0x18,0x18,0x00,0x00],
[0x00,0x00,0x00,0x00,0xC2,0xC6,0x0C,0x18,0x30,0x60,0xC6,0x86,0x00,0x00,0x00,0x00],
[0x00,0x00,0x38,0x6C,0x6C,0x38,0x76,0xDC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00],
[0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x0C,0x18,0x30,0x30,0x30,0x30,0x30,0x30,0x18,0x0C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x30,0x18,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x18,0x30,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x30,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x02,0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x00],
[0x00,0x00,0x7C,0xC6,0xC6,0xCE,0xDE,0xF6,0xE6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x18,0x38,0x78,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00],
[0x00,0x00,0x7C,0xC6,0x06,0x0C,0x18,0x30,0x60,0xC0,0xC6,0xFE,0x00,0x00,0x00,0x00],
[0x00,0x00,0x7C,0xC6,0x06,0x06,0x3C,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x0C,0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00],
[0x00,0x00,0xFE,0xC0,0xC0,0xC0,0xFC,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x38,0x60,0xC0,0xC0,0xFC,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0xFE,0xC6,0x06,0x06,0x0C,0x18,0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00],
[0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7E,0x06,0x06,0x06,0x0C,0x78,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x60,0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06,0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00],
[0x00,0x00,0x7C,0xC6,0xC6,0x0C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00],
[0x00,0x00,0x7C,0xC6,0xC6,0xDE,0xDE,0xDE,0xDC,0xC0,0xC0,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00],
[0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x66,0x66,0x66,0x66,0xFC,0x00,0x00,0x00,0x00],
[0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xC0,0xC0,0xC2,0x66,0x3C,0x00,0x00,0x00,0x00],
[0x00,0x00,0xF8,0x6C,0x66,0x66,0x66,0x66,0x66,0x66,0x6C,0xF8,0x00,0x00,0x00,0x00],
[0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00],
[0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00],
[0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xDE,0xC6,0xC6,0x66,0x3A,0x00,0x00,0x00,0x00],
[0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00],
[0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00],
[0x00,0x00,0xE6,0x66,0x66,0x6C,0x78,0x78,0x6C,0x66,0x66,0xE6,0x00,0x00,0x00,0x00],
[0x00,0x00,0xF0,0x60,0x60,0x60,0x60,0x60,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00],
[0x00,0x00,0xC6,0xEE,0xFE,0xFE,0xD6,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00],
[0x00,0x00,0xC6,0xE6,0xF6,0xFE,0xDE,0xCE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00],
[0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x60,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00],
[0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xD6,0xDE,0x7C,0x0C,0x0E,0x00,0x00],
[0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x6C,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00],
[0x00,0x00,0x7C,0xC6,0xC6,0x60,0x38,0x0C,0x06,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0xFF,0xDB,0x99,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00],
[0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00],
[0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0xEE,0x6C,0x00,0x00,0x00,0x00],
[0x00,0x00,0xC6,0xC6,0x6C,0x7C,0x38,0x38,0x7C,0x6C,0xC6,0xC6,0x00,0x00,0x00,0x00],
[0x00,0x00,0xC6,0xC6,0xC6,0x6C,0x38,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00],
[0x00,0x00,0xFE,0xC6,0x86,0x0C,0x18,0x30,0x60,0xC2,0xC6,0xFE,0x00,0x00,0x00,0x00],
[0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x38,0x1C,0x0E,0x06,0x02,0x00,0x00,0x00,0x00],
[0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00],
[0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00],
[0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x78,0x0C,0x7C,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00],
[0x00,0x00,0xE0,0x60,0x60,0x78,0x6C,0x66,0x66,0x66,0x66,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC0,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x1C,0x0C,0x0C,0x3C,0x6C,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xFE,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x38,0x6C,0x64,0x60,0xF0,0x60,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0xCC,0x78,0x00],
[0x00,0x00,0xE0,0x60,0x60,0x6C,0x76,0x66,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00],
[0x00,0x00,0x18,0x18,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x06,0x06,0x00,0x0E,0x06,0x06,0x06,0x06,0x06,0x06,0x66,0x66,0x3C,0x00],
[0x00,0x00,0xE0,0x60,0x60,0x66,0x6C,0x78,0x78,0x6C,0x66,0xE6,0x00,0x00,0x00,0x00],
[0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0xEC,0xFE,0xD6,0xD6,0xD6,0xD6,0xC6,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00],
[0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0x0C,0x1E,0x00],
[0x00,0x00,0x00,0x00,0x00,0xDC,0x76,0x66,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0x60,0x38,0x0C,0xC6,0x7C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x10,0x30,0x30,0xFC,0x30,0x30,0x30,0x30,0x36,0x1C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0x6C,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0xC6,0x6C,0x38,0x38,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7E,0x06,0x0C,0xF8,0x00],
[0x00,0x00,0x00,0x00,0x00,0xFE,0xCC,0x18,0x30,0x60,0xC6,0xFE,0x00,0x00,0x00,0x00],
[0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18,0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00],
[0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00],
[0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18,0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00],
[0x00,0x00,0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
];
#[rustfmt::skip]
static BOX_DRAWING_GLYPHS: [[u8; 16]; 128] = [
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x3F,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0x00,0xFC,0xFC,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3F,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFC,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3F,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3F,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3F,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3F,0x3F,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFC,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFC,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFC,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFC,0xFC,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFF,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFF,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFF,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFF,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFF,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0xFF,0xFF,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
[0x00,0x00,0x00,0x00,0x00,0x00,0xFC,0xFC,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x3F,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C],
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
[0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18],
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render_to_image_produces_correct_dimensions() {
let frame = TerminalFrame::new(80, 24, b"Hello");
let image = render_to_image(&frame);
assert_eq!(image.width(), 640);
assert_eq!(image.height(), 384);
}
#[test]
fn render_to_image_small_terminal() {
let frame = TerminalFrame::new(10, 5, b"Hi");
let image = render_to_image(&frame);
assert_eq!(image.width(), 80);
assert_eq!(image.height(), 80);
}
#[test]
fn render_plain_cell_has_correct_background() {
let frame = TerminalFrame::new(1, 1, b" ");
let image = render_to_image(&frame);
let pixel = image.get_pixel(0, 0);
assert_eq!(pixel.0[0], DEFAULT_BG.red);
assert_eq!(pixel.0[1], DEFAULT_BG.green);
assert_eq!(pixel.0[2], DEFAULT_BG.blue);
}
#[test]
fn render_colored_cell_uses_ansi_color() {
let data = b"\x1b[41m \x1b[0m";
let frame = TerminalFrame::new(1, 1, data);
let image = render_to_image(&frame);
let pixel = image.get_pixel(0, 0);
assert_eq!(pixel.0[0], 128);
assert_eq!(pixel.0[1], 0);
assert_eq!(pixel.0[2], 0);
}
#[test]
fn render_bold_brightens_foreground() {
let data = b"\x1b[1;31mA\x1b[0m";
let frame = TerminalFrame::new(1, 1, data);
let _rendered = render_to_image(&frame);
let brightened = brighten_component(128);
assert!(brightened > 128);
}
#[test]
fn render_inverse_swaps_colors() {
let data = b"\x1b[7m \x1b[0m";
let frame = TerminalFrame::new(1, 1, data);
let image = render_to_image(&frame);
let pixel = image.get_pixel(0, 0);
assert_eq!(pixel.0[0], DEFAULT_FG.red);
assert_eq!(pixel.0[1], DEFAULT_FG.green);
assert_eq!(pixel.0[2], DEFAULT_FG.blue);
}
#[test]
fn lookup_glyph_returns_known_ascii() {
let glyph_a = lookup_glyph('A');
let glyph_space = lookup_glyph(' ');
assert!(glyph_a.iter().any(|&byte| byte != 0));
assert!(glyph_space.iter().all(|&byte| byte == 0));
}
#[test]
fn lookup_glyph_returns_empty_for_unsupported() {
let glyph = lookup_glyph('\u{1F600}');
assert_eq!(glyph, &EMPTY_GLYPH);
}
#[test]
fn lookup_glyph_returns_box_drawing() {
let glyph = lookup_glyph('─');
assert!(glyph.iter().any(|&byte| byte != 0));
}
#[test]
fn color_to_rgba_converts_correctly() {
let color = CellColor::new(100, 150, 200);
let rgba = color_to_rgba(color);
assert_eq!(rgba, Rgba([100, 150, 200, 255]));
}
#[test]
fn brighten_component_clamps_at_255() {
let result = brighten_component(200);
assert_eq!(result, 255);
}
#[test]
fn dim_component_reduces_brightness() {
let result = dim_component(200);
assert_eq!(result, 120);
}
#[test]
fn dim_component_handles_zero() {
assert_eq!(dim_component(0), 0);
}
#[test]
fn render_dim_darkens_foreground() {
let data = b"\x1b[2mA\x1b[0m";
let frame = TerminalFrame::new(1, 1, data);
let _rendered = render_to_image(&frame);
let dimmed = dim_component(DEFAULT_FG.red);
assert!(dimmed < DEFAULT_FG.red);
}
}