use crate::base::Rgba;
use crate::gfx::bitmap::Bitmap;
use crate::gfx::mosaic::{render, MosaicMode};
fn glyph_rows(src: &Bitmap, cols: u32, rows: u32, mode: MosaicMode) -> Vec<String> {
let grid = render(src, cols, rows, mode);
(0..rows)
.map(|r| (0..cols).map(|c| grid.get(c, r).unwrap().ch).collect())
.collect()
}
#[test]
fn hard_vertical_edge_picks_vertical_partitions() {
let src = Bitmap::from_fn(8, 4, |x, _| if x < 3 { Rgba::WHITE } else { Rgba::BLACK });
let rows = glyph_rows(&src, 4, 2, MosaicMode::Quadrant);
for row in &rows {
assert_eq!(row.chars().count(), 4);
let cells: Vec<char> = row.chars().collect();
assert_eq!(cells[0], ' ', "uniform cell must be space+bg: {row:?}");
assert!(
cells[1] == '▌' || cells[1] == '▐',
"edge cell must pick a vertical half, got {:?} in {row:?}",
cells[1]
);
assert_eq!(cells[2], ' ');
assert_eq!(cells[3], ' ');
}
}
#[test]
fn hard_horizontal_edge_picks_horizontal_partitions() {
let src = Bitmap::from_fn(2, 6, |_, y| if y < 1 { Rgba::WHITE } else { Rgba::BLACK });
let rows = glyph_rows(&src, 1, 2, MosaicMode::Sextant);
assert_eq!(rows[1], " ", "fully black cell is space+bg");
let top = rows[0].chars().next().unwrap();
assert!(
top == '🬂' || top == '🬹',
"top-row-band partition expected, got {top:?}"
);
}
#[test]
fn diagonal_edge_picks_diagonal_partition() {
let src = Bitmap::from_pixels(
2,
2,
vec![Rgba::WHITE, Rgba::BLACK, Rgba::BLACK, Rgba::WHITE],
)
.unwrap();
let rows = glyph_rows(&src, 1, 1, MosaicMode::Quadrant);
let ch = rows[0].chars().next().unwrap();
assert!(
ch == '▚' || ch == '▞',
"diagonal partition expected, got {ch:?}"
);
}
#[test]
fn gradient_golden_quadrant() {
let src = Bitmap::from_fn(16, 8, |x, _| {
let v = (x * 255 / 15) as u8;
Rgba::rgb(v, v, v)
});
let rows = glyph_rows(&src, 8, 4, MosaicMode::Quadrant);
for row in &rows {
assert_eq!(row, &rows[0], "gradient rows must match");
for ch in row.chars() {
assert!(
ch == ' ' || ch == '▌' || ch == '▐',
"gradient invented structure: {ch:?} in {row:?}"
);
}
}
}
#[test]
fn noise_snapshot_goldens() {
let mut state = 0x2545F491u32;
let mut rand = move || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
let mut px = Vec::with_capacity(8 * 6);
for _ in 0..(8 * 6) {
let v = rand();
px.push(Rgba::rgb(
(v & 0xFF) as u8,
((v >> 8) & 0xFF) as u8,
((v >> 16) & 0xFF) as u8,
));
}
let src = Bitmap::from_pixels(8, 6, px).unwrap();
let quad = glyph_rows(&src, 4, 3, MosaicMode::Quadrant);
let sext = glyph_rows(&src, 4, 2, MosaicMode::Sextant);
let half = glyph_rows(&src, 8, 3, MosaicMode::HalfBlock);
if std::env::var("CAPTURE_MOSAIC_GOLDENS").is_ok() {
eprintln!("quad: {quad:?}\nsext: {sext:?}\nhalf: {half:?}");
}
assert_eq!(quad, vec!["▐▝▄▗", "▟▗▗▐", "▐▖▐▞"], "quadrant noise golden");
assert_eq!(sext, vec!["🬘🬁🬋🬖", "🬦🬑▐🬢"], "sextant noise golden");
assert_eq!(
half,
vec!["▀▀▀▀▀▀▀▀", "▀▀▀▀▀▀▀▀", "▀▀▀▀▀▀▀▀"],
"half-block noise golden"
);
}
#[test]
fn dither_option_mixes_gradients() {
use crate::base::Rect;
use crate::gfx::mosaic::MosaicMode;
use crate::gfx::pipeline::{ImageOutput, ImageRenderer, MosaicOpts};
use crate::term::caps::GraphicsCaps;
let caps = GraphicsCaps {
wrap: None,
kitty_graphics: false,
iterm2_images: false,
sixel: false,
sixel_max_registers: None,
cell_pixel_size: None,
};
let src = Bitmap::from_fn(32, 8, |x, _| {
let v = (x * 255 / 31) as u8;
Rgba::rgb(v, v, v)
});
let mut r = ImageRenderer::new();
r.config.mosaic = MosaicOpts {
mode: MosaicMode::HalfBlock,
dither: Some(2),
};
let out = r.render(&src, Rect::new(0, 0, 32, 4), &caps);
let ImageOutput::Cells(cells) = &out.output else {
panic!()
};
let mid: Vec<_> = cells
.iter()
.filter(|c| c.pos.x >= 10 && c.pos.x < 22)
.collect();
let has_dark = mid.iter().any(|c| c.bg.r < 100 || c.fg.r < 100);
let has_light = mid.iter().any(|c| c.bg.r > 150 || c.fg.r > 150);
assert!(
has_dark && has_light,
"dither must interleave both palette colors"
);
let mut r2 = ImageRenderer::new();
r2.config.mosaic = MosaicOpts {
mode: MosaicMode::HalfBlock,
dither: Some(2),
};
let out2 = r2.render(&src, Rect::new(0, 0, 32, 4), &caps);
let ImageOutput::Cells(cells2) = &out2.output else {
panic!()
};
assert_eq!(cells, cells2);
}