use super::canvas::{Canvas, Premultiplied};
use crate::gpu::scene::MonochromeSprite;
pub struct AtlasView<'a> {
pub pixels: &'a [u8],
pub size: u32,
pub channels: u32,
}
impl AtlasView<'_> {
#[inline]
fn texel(&self, x: u32, y: u32) -> [f32; 4] {
let index = ((y.min(self.size - 1) * self.size + x.min(self.size - 1)) * self.channels) as usize;
let value = |offset: usize| self.pixels[index + offset] as f32 / 255.0;
if self.channels == 1 { [value(0); 4] } else { [value(0), value(1), value(2), value(3)] }
}
fn sample(&self, u: f32, v: f32) -> [f32; 4] {
let (x, y) = ((u - 0.5).max(0.0), (v - 0.5).max(0.0));
let (x0, y0) = (x.floor() as u32, y.floor() as u32);
let (fx, fy) = (x.fract(), y.fract());
let [a, b, c, d] =
[self.texel(x0, y0), self.texel(x0 + 1, y0), self.texel(x0, y0 + 1), self.texel(x0 + 1, y0 + 1)];
std::array::from_fn(|i| {
let top = a[i] + (b[i] - a[i]) * fx;
let bottom = c[i] + (d[i] - c[i]) * fx;
top + (bottom - top) * fy
})
}
}
pub fn sprite(canvas: &mut Canvas, atlas: &AtlasView, sprite: &MonochromeSprite, polychrome: bool) {
let bounds = sprite.bounds;
let (columns, rows) = canvas.area(bounds, 0.0, sprite.clip);
if columns.is_empty() || rows.is_empty() || bounds[2] <= 0.0 || bounds[3] <= 0.0 {
return;
}
let size = atlas.size as f32;
let [u0, v0, u1, v1] = sprite.uv.map(|uv| uv * size);
let tint = Premultiplied::from_straight(sprite.color);
let exact =
((u1 - u0) - bounds[2] * canvas.scale).abs() < 0.5 && ((v1 - v0) - bounds[3] * canvas.scale).abs() < 0.5;
for y in rows {
let ty = (canvas.logical(y) - bounds[1]) / bounds[3];
for x in columns.clone() {
let tx = (canvas.logical(x) - bounds[0]) / bounds[2];
let (u, v) = (u0 + tx * (u1 - u0), v0 + ty * (v1 - v0));
let texel = if exact { atlas.texel(u as u32, v as u32) } else { atlas.sample(u, v) };
let color = if polychrome {
Premultiplied { r: texel[0], g: texel[1], b: texel[2], a: texel[3] }.scaled(tint.a)
} else {
tint.scaled(texel[0])
};
if color.a > 0.002 {
canvas.blend_at(x, y, color);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{Bounds, Point, Rgba, Size, px};
#[test]
fn un_glifo_se_tine_con_su_color_y_respeta_el_recorte() {
let mut mask = vec![0u8; 16];
for index in [0, 1, 4, 5] {
mask[index] = 255;
}
let atlas = AtlasView { pixels: &mask, size: 4, channels: 1 };
let mut pixels = vec![0xffffff; 16];
let mut canvas = Canvas { pixels: &mut pixels, width: 4, height: 4, scale: 1.0 };
let bounds = Bounds::new(Point::new(px(1.0), px(1.0)), Size::new(px(2.0), px(2.0)));
let clip = Bounds::new(Point::new(px(0.0), px(0.0)), Size::new(px(2.0), px(4.0)));
let glyph = MonochromeSprite::new(bounds, clip, [0.0, 0.0, 0.5, 0.5], Rgba::new(0.0, 0.0, 0.0, 1.0));
sprite(&mut canvas, &atlas, &glyph, false);
assert_eq!(pixels[4 + 1], 0x000000, "dentro del recorte, negro");
assert_eq!(pixels[4 + 2], 0xffffff, "fuera del recorte, intacto");
}
}