//! **Costura entre celdas** (*conflation*) — la medición que respalda la //! cuantización de `shuma_module_shell::view::tui::cuantiza`. //! //! Dos rects que comparten borde y se antialiasan **por separado** no suman //! 1.0 en el píxel de la junta: cada uno lo cubre a medias y la composición da //! ~0,75. Un grid de terminal pinta un rect por celda (fondos, y los bloques //! `▀▄█` con los que un TUI rellena zonas), así que eso es una costura más //! clara en CADA frontera — a escala de pantalla, una cuadrícula sobre lo que //! debería ser una zona sólida. //! //! No se arregla haciendo coincidir los bordes: ya coincidían. Se arregla //! haciéndolos caer en **enteros**, así cada píxel queda cubierto por completo //! por uno solo de los dos rects. use llimphi_compositor::View; use llimphi_layout::taffy::prelude::{percent, Size, Style}; use llimphi_raster::peniko::{Color, Fill}; use llimphi_raster::vello::kurbo::{Affine, Rect as KRect}; use llimphi_test::with_harness; const W: u32 = 120; const H: u32 = 40; /// Ancho de celda fraccionario, como el del grid real (la mono a 14 px no da /// un avance entero). const CELL_W: f64 = 8.4; const CELDAS: usize = 12; const X0: f64 = 5.0; /// Franja de píxeles estrictamente interior al área pintada. const INTERIOR: std::ops::RangeInclusive = 6..=104; #[derive(Clone, Copy)] enum Modo { /// Un `fill` por celda con bordes fraccionarios — cómo estaba. PorCelda, /// Un `fill` por celda con los bordes cuantizados a entero — cómo quedó. Cuantizado, /// El área entera de un solo `fill` — el ideal contra el que comparamos. UnRect, } fn escena(modo: Modo) -> View<()> { View::<()>::new(Style { size: Size { width: percent(1.0_f32), height: percent(1.0_f32) }, ..Default::default() }) .paint_with(move |scene: &mut llimphi_raster::vello::Scene, _ts: &mut llimphi_text::Typesetter, _r| { let blanco = Color::from_rgba8(255, 255, 255, 255); let (y0, y1): (f64, f64) = (10.0, 30.0); match modo { Modo::PorCelda => { for c in 0..CELDAS { let x = X0 + c as f64 * CELL_W; scene.fill(Fill::NonZero, Affine::IDENTITY, blanco, None, &KRect::new(x, y0, x + CELL_W, y1)); } } Modo::Cuantizado => { for c in 0..CELDAS { let xa = (X0 + c as f64 * CELL_W).round(); let xb = (X0 + (c + 1) as f64 * CELL_W).round(); scene.fill(Fill::NonZero, Affine::IDENTITY, blanco, None, &KRect::new(xa, y0.round(), xb, y1.round())); } } Modo::UnRect => { scene.fill(Fill::NonZero, Affine::IDENTITY, blanco, None, &KRect::new(X0, y0, X0 + CELDAS as f64 * CELL_W, y1)); } } }) } /// Los píxeles del canal rojo de una fila del frame. fn perfil(rgba: &[u8], w: u32, fila: u32) -> Vec { (0..w).map(|x| rgba[((fila * w + x) * 4) as usize]).collect() } /// `(mínimo, cuántos píxeles hundidos)` en el interior del área pintada. En una /// zona sólida blanca, lo sano es `(255, 0)`. fn hundidos(rgba: &[u8]) -> (u8, usize) { let p = perfil(rgba, W, 20); let min = INTERIOR.map(|x| p[x]).min().unwrap(); let n = INTERIOR.filter(|x| p[*x] < 250).count(); (min, n) } #[test] fn cuantizar_los_bordes_de_celda_cierra_la_costura() { with_harness(|h| { let negro = Color::from_rgba8(0, 0, 0, 255); let por_celda = hundidos(&h.render(escena(Modo::PorCelda), W, H, negro).rgba); let cuantizado = hundidos(&h.render(escena(Modo::Cuantizado), W, H, negro).rgba); let un_rect = hundidos(&h.render(escena(Modo::UnRect), W, H, negro).rgba); // El artefacto existe y es visible: ~0,75 de cobertura en cada junta. assert!( por_celda.1 >= CELDAS - 3 && por_celda.0 < 230, "el fill por celda con bordes fraccionarios debería dejar costura, \ y midió min={} hundidos={} — si esto falla, vello dejó de \ antialiasar por shape y la cuantización sobra", por_celda.0, por_celda.1 ); // Cuantizado queda idéntico a pintar el área de un solo rect. assert_eq!(cuantizado, (255, 0), "cuantizado tiene que quedar sólido"); assert_eq!(un_rect, (255, 0), "el rect único es el patrón de comparación"); }); }