#[cfg(feature = "nostd")]
use alloc::{sync::Arc, vec, vec::Vec};
#[cfg(not(feature = "nostd"))]
use std::sync::Arc;
use tiny_skia::{Path, PathSegment};
use crate::backends::raster::Rasterizer;
mod blend;
mod composite;
pub use composite::{composite, composite_bitmap};
#[derive(Clone)]
pub struct CoverageTile {
pub width: u32,
pub height: u32,
pub data: Arc<Vec<u8>>,
}
#[derive(Clone)]
pub enum RenderBitmap {
Coverage {
width: u32,
height: u32,
coverage: Arc<Vec<u8>>,
x: i32,
y: i32,
color: [u8; 4],
},
Rgba {
width: u32,
height: u32,
pixels: Arc<Vec<u8>>,
x: i32,
y: i32,
},
}
impl CoverageTile {
#[must_use]
pub fn rasterize(path: &Path) -> Option<(Self, i32, i32)> {
let bounds = path.bounds();
let min_x = bounds.left().floor() as i32 - 1;
let min_y = bounds.top().floor() as i32 - 1;
let max_x = bounds.right().ceil() as i32 + 1;
let max_y = bounds.bottom().ceil() as i32 + 1;
let width = u32::try_from((max_x - min_x).max(1)).ok()?;
let height = u32::try_from((max_y - min_y).max(1)).ok()?;
let ox = min_x as f32;
let oy = min_y as f32;
let mut raster = Rasterizer::new(width as usize, height as usize);
let mut start = (0.0_f32, 0.0_f32);
let mut cur = (0.0_f32, 0.0_f32);
let mut open = false;
for segment in path.segments() {
match segment {
PathSegment::MoveTo(p) => {
if open {
raster.line(cur.0, cur.1, start.0, start.1);
}
start = (p.x - ox, p.y - oy);
cur = start;
open = true;
}
PathSegment::LineTo(p) => {
let next = (p.x - ox, p.y - oy);
raster.line(cur.0, cur.1, next.0, next.1);
cur = next;
}
PathSegment::QuadTo(c, p) => {
let cc = (c.x - ox, c.y - oy);
let next = (p.x - ox, p.y - oy);
raster.quad(cur.0, cur.1, cc.0, cc.1, next.0, next.1);
cur = next;
}
PathSegment::CubicTo(c1, c2, p) => {
let a = (c1.x - ox, c1.y - oy);
let b = (c2.x - ox, c2.y - oy);
let next = (p.x - ox, p.y - oy);
raster.cubic(cur.0, cur.1, a.0, a.1, b.0, b.1, next.0, next.1);
cur = next;
}
PathSegment::Close => {
raster.line(cur.0, cur.1, start.0, start.1);
cur = start;
}
}
}
if open {
raster.line(cur.0, cur.1, start.0, start.1);
}
Some((
Self {
width,
height,
data: Arc::new(raster.finish()),
},
min_x,
min_y,
))
}
}
#[cfg(test)]
mod tests {
use super::{composite, Arc, CoverageTile};
#[test]
fn composite_blends_known_pixel() {
let tile = CoverageTile {
width: 1,
height: 1,
data: Arc::new(vec![128]),
};
let mut dst = vec![0u8, 0, 0, 255]; composite(&mut dst, 1, 1, &tile, 0, 0, [255, 0, 0, 255]);
assert_eq!(dst[0], 128, "red");
assert_eq!(dst[1], 0, "green");
assert_eq!(dst[2], 0, "blue");
assert_eq!(dst[3], 255, "alpha");
}
#[test]
fn composite_clips_offscreen() {
let tile = CoverageTile {
width: 4,
height: 4,
data: Arc::new(vec![255; 16]),
};
let mut dst = vec![0u8; 2 * 2 * 4];
composite(&mut dst, 2, 2, &tile, -3, -3, [10, 20, 30, 255]);
assert_eq!(&dst[0..3], &[10, 20, 30], "the one in-bounds pixel blended");
assert_eq!(&dst[4..8], &[0, 0, 0, 0], "neighbours untouched");
}
}