use zeno::{Cap, Command, Fill, Join, Mask, Placement, Stroke, Style, Vector};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct CoverageBitmap {
pub w: usize,
pub h: usize,
pub x: i32,
pub y: i32,
pub data: Vec<u8>,
}
impl CoverageBitmap {
pub(crate) fn is_empty(&self) -> bool {
self.w == 0 || self.h == 0
}
fn from_mask(data: Vec<u8>, placement: Placement) -> Self {
Self {
w: placement.width as usize,
h: placement.height as usize,
x: placement.left,
y: placement.top,
data,
}
}
pub(crate) fn max_with(&self, other: &CoverageBitmap) -> CoverageBitmap {
if self.is_empty() {
return other.clone();
}
if other.is_empty() {
return self.clone();
}
let x0 = self.x.min(other.x);
let y0 = self.y.min(other.y);
let x1 = (self.x + self.w as i32).max(other.x + other.w as i32);
let y1 = (self.y + self.h as i32).max(other.y + other.h as i32);
let w = (x1 - x0) as usize;
let h = (y1 - y0) as usize;
let mut data = vec![0u8; w * h];
for src in [self, other] {
let ox = (src.x - x0) as usize;
let oy = (src.y - y0) as usize;
for row in 0..src.h {
let dst_row = &mut data[(oy + row) * w + ox..(oy + row) * w + ox + src.w];
let src_row = &src.data[row * src.w..(row + 1) * src.w];
for (d, s) in dst_row.iter_mut().zip(src_row) {
*d = (*d).max(*s);
}
}
}
CoverageBitmap {
w,
h,
x: x0,
y: y0,
data,
}
}
pub(crate) fn clip_rect(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, inverse: bool) {
if self.is_empty() {
return;
}
for row in 0..self.h {
let frame_y = self.y + row as i32;
let inside_row = frame_y >= y0 && frame_y < y1;
for col in 0..self.w {
let frame_x = self.x + col as i32;
let inside = inside_row && frame_x >= x0 && frame_x < x1;
if inside == inverse {
self.data[row * self.w + col] = 0;
}
}
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct SharedBitmap {
pub w: usize,
pub h: usize,
pub x: i32,
pub y: i32,
pub data: std::sync::Arc<Vec<u8>>,
}
impl SharedBitmap {
pub(crate) fn is_empty(&self) -> bool {
self.w == 0 || self.h == 0
}
pub(crate) fn clip_rect(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, inverse: bool) {
let mut work = CoverageBitmap {
w: self.w,
h: self.h,
x: self.x,
y: self.y,
data: std::mem::take(std::sync::Arc::make_mut(&mut self.data)),
};
work.clip_rect(x0, y0, x1, y1, inverse);
*self = work.into();
}
}
impl From<CoverageBitmap> for SharedBitmap {
fn from(bitmap: CoverageBitmap) -> Self {
Self {
w: bitmap.w,
h: bitmap.h,
x: bitmap.x,
y: bitmap.y,
data: std::sync::Arc::new(bitmap.data),
}
}
}
pub(crate) fn path_extent(commands: &[Command]) -> Option<(f64, f64)> {
let mut min = (f64::INFINITY, f64::INFINITY);
let mut max = (f64::NEG_INFINITY, f64::NEG_INFINITY);
let mut any = false;
let mut poisoned = false;
let mut visit = |p: &Vector| {
any = true;
let (x, y) = (f64::from(p.x), f64::from(p.y));
if !(x.is_finite() && y.is_finite()) {
poisoned = true;
return;
}
min.0 = min.0.min(x);
min.1 = min.1.min(y);
max.0 = max.0.max(x);
max.1 = max.1.max(y);
};
for command in commands {
match command {
Command::MoveTo(p) | Command::LineTo(p) => visit(p),
Command::QuadTo(c, p) => {
visit(c);
visit(p);
}
Command::CurveTo(c1, c2, p) => {
visit(c1);
visit(c2);
visit(p);
}
Command::Close => {}
}
}
if !any {
return None;
}
if poisoned {
return Some((f64::INFINITY, f64::INFINITY));
}
Some((max.0 - min.0, max.1 - min.1))
}
const MAX_MASK_AREA_PX: f64 = (1u64 << 28) as f64;
fn raster_area_safe(commands: &[Command], pad: f64) -> bool {
match path_extent(commands) {
None => true,
Some((w, h)) => {
let side_w = w + 2.0 * pad + 4.0;
let side_h = h + 2.0 * pad + 4.0;
side_w.is_finite()
&& side_h.is_finite()
&& side_w * side_h <= MAX_MASK_AREA_PX
}
}
}
pub(crate) fn fill_path(commands: &[Command]) -> CoverageBitmap {
if commands.is_empty() || !raster_area_safe(commands, 0.0) {
return CoverageBitmap::default();
}
let (data, placement) = Mask::new(commands)
.style(Style::Fill(Fill::NonZero))
.render();
CoverageBitmap::from_mask(data, placement)
}
pub(crate) fn border_path(
commands: &[Command],
fill: &CoverageBitmap,
border_x: f32,
border_y: f32,
) -> CoverageBitmap {
if commands.is_empty() || (border_x <= 0.0 && border_y <= 0.0) {
return CoverageBitmap::default();
}
let rx = border_x.max(1.0 / 64.0);
let ry = border_y.max(1.0 / 64.0);
if (rx - ry).abs() <= rx.max(ry) * 1e-3 {
if !raster_area_safe(commands, f64::from(rx.max(ry))) {
return fill.clone();
}
let mut stroke = Stroke::new(2.0 * rx.max(ry));
stroke.cap(Cap::Round).join(Join::Round);
let (data, placement) = Mask::new(commands).style(Style::Stroke(stroke)).render();
return CoverageBitmap::from_mask(data, placement).max_with(fill);
}
let squeeze = (rx / ry).clamp(1.0 / 32.0, 32.0);
let squeezed: Vec<Command> = commands
.iter()
.map(|c| transform_y(*c, f64::from(squeeze)))
.collect();
if !raster_area_safe(&squeezed, f64::from(rx)) {
return fill.clone();
}
let mut stroke = Stroke::new(2.0 * rx);
stroke.cap(Cap::Round).join(Join::Round);
let (data, placement) = Mask::new(&squeezed).style(Style::Stroke(stroke)).render();
let squeezed_bitmap = CoverageBitmap::from_mask(data, placement);
resample_rows_union(&squeezed_bitmap, f64::from(squeeze), fill)
}
fn transform_y(command: Command, sy: f64) -> Command {
let map = |p: Vector| Vector::new(p.x, (f64::from(p.y) * sy) as f32);
match command {
Command::MoveTo(p) => Command::MoveTo(map(p)),
Command::LineTo(p) => Command::LineTo(map(p)),
Command::QuadTo(c, p) => Command::QuadTo(map(c), map(p)),
Command::CurveTo(c1, c2, p) => Command::CurveTo(map(c1), map(c2), map(p)),
Command::Close => Command::Close,
}
}
fn resample_extent(src: &CoverageBitmap, sy: f64) -> (i32, usize) {
let y0 = (f64::from(src.y) / sy).floor() as i32;
let y1 = ((f64::from(src.y) + src.h as f64) / sy).ceil() as i32;
(y0, (y1 - y0).max(1) as usize)
}
fn resample_into(
src: &CoverageBitmap,
sy: f64,
dst: &mut [u8],
dst_w: usize,
off_x: usize,
off_y: usize,
) {
let (y0, rh) = resample_extent(src, sy);
for row in 0..rh {
let a = (f64::from(y0 + row as i32)) * sy - f64::from(src.y);
let b = a + sy;
let (a0, b0) = (a.max(0.0), b.min(src.h as f64));
if b0 <= a0 {
continue;
}
let base = (off_y + row) * dst_w + off_x;
for col in 0..src.w {
let mut acc = 0.0f64;
let mut sr = a0.floor() as usize;
while (sr as f64) < b0 {
let cover = (b0.min(sr as f64 + 1.0) - a0.max(sr as f64)).max(0.0);
acc += cover * f64::from(src.data[sr * src.w + col]);
sr += 1;
}
dst[base + col] = ((acc / (b0 - a0)) + 0.5) as u8;
}
}
}
fn resample_rows(src: &CoverageBitmap, sy: f64) -> CoverageBitmap {
if src.is_empty() {
return CoverageBitmap::default();
}
let (y0, h) = resample_extent(src, sy);
let mut data = vec![0u8; src.w * h];
resample_into(src, sy, &mut data, src.w, 0, 0);
CoverageBitmap {
w: src.w,
h,
x: src.x,
y: y0,
data,
}
}
fn resample_rows_union(
squeezed: &CoverageBitmap,
sy: f64,
fill: &CoverageBitmap,
) -> CoverageBitmap {
if squeezed.is_empty() {
return fill.clone();
}
if fill.is_empty() {
return resample_rows(squeezed, sy);
}
let (ry0, rh) = resample_extent(squeezed, sy);
let rx = squeezed.x;
let rw = squeezed.w as i32;
let x0 = rx.min(fill.x);
let y0 = ry0.min(fill.y);
let x1 = (rx + rw).max(fill.x + fill.w as i32);
let y1 = (ry0 + rh as i32).max(fill.y + fill.h as i32);
let w = (x1 - x0) as usize;
let h = (y1 - y0) as usize;
let mut data = vec![0u8; w * h];
resample_into(
squeezed,
sy,
&mut data,
w,
(rx - x0) as usize,
(ry0 - y0) as usize,
);
let fox = (fill.x - x0) as usize;
let foy = (fill.y - y0) as usize;
for row in 0..fill.h {
let base = (foy + row) * w + fox;
let dst_row = &mut data[base..base + fill.w];
let src_row = &fill.data[row * fill.w..(row + 1) * fill.w];
for (d, &s) in dst_row.iter_mut().zip(src_row) {
*d = (*d).max(s);
}
}
CoverageBitmap {
w,
h,
x: x0,
y: y0,
data,
}
}
pub(crate) fn fix_outline(glyph: &CoverageBitmap, outline: &mut CoverageBitmap) {
if glyph.is_empty() || outline.is_empty() {
return;
}
let l = outline.x.max(glyph.x);
let t = outline.y.max(glyph.y);
let r = (outline.x + outline.w as i32).min(glyph.x + glyph.w as i32);
let b = (outline.y + outline.h as i32).min(glyph.y + glyph.h as i32);
for y in t..b {
for x in l..r {
let g = glyph.data[(y - glyph.y) as usize * glyph.w + (x - glyph.x) as usize];
let o =
&mut outline.data[(y - outline.y) as usize * outline.w + (x - outline.x) as usize];
*o = if *o > g { *o - g / 2 } else { 0 };
}
}
}
fn be_padding(be: i32) -> usize {
match be {
b if b <= 3 => b.max(0) as usize,
b if b <= 7 => 4,
_ => 5,
}
}
pub(crate) fn be_blur(bitmap: &mut CoverageBitmap, passes: i32) {
let mut be = passes.clamp(0, 127);
if be <= 0 || bitmap.is_empty() {
return;
}
pad(bitmap, be_padding(be));
let w = bitmap.w;
let h = bitmap.h;
if w < 2 || h < 2 {
return;
}
let mut col_pix = vec![0u16; w];
let mut col_sum = vec![0u16; w];
let mut horiz = vec![0u16; w];
if be > 1 {
for v in bitmap.data.iter_mut() {
*v = ((*v >> 1) + 1) >> 1;
}
while be > 1 {
be_blur_pass(
&mut bitmap.data,
w,
h,
&mut col_pix,
&mut col_sum,
&mut horiz,
);
be -= 1;
}
for v in bitmap.data.iter_mut() {
*v = ((i32::from(*v) << 2) - i32::from(*v > 32)) as u8;
}
}
be_blur_pass(
&mut bitmap.data,
w,
h,
&mut col_pix,
&mut col_sum,
&mut horiz,
);
}
fn be_row_sums(src: &[u8], out: &mut [u16]) {
let w = src.len();
let mut old_pix = u16::from(src[0]);
let mut old_sum = old_pix;
for x in 1..w {
let cur = u16::from(src[x]);
let pair = old_pix + cur;
old_pix = cur;
out[x - 1] = old_sum + pair;
old_sum = pair;
}
out[w - 1] = old_sum + old_pix;
}
fn be_blur_pass(
buf: &mut [u8],
w: usize,
h: usize,
col_pix: &mut [u16],
col_sum: &mut [u16],
horiz: &mut [u16],
) {
be_row_sums(&buf[..w], horiz);
col_pix.copy_from_slice(horiz);
col_sum.copy_from_slice(horiz);
for y in 1..h {
be_row_sums(&buf[y * w..(y + 1) * w], horiz);
let dst = &mut buf[(y - 1) * w..y * w];
for x in 0..w {
let vert = col_pix[x] + horiz[x];
col_pix[x] = horiz[x];
dst[x] = ((col_sum[x] + vert) >> 4) as u8;
col_sum[x] = vert;
}
}
let dst = &mut buf[(h - 1) * w..h * w];
for x in 0..w {
dst[x] = ((col_sum[x] + col_pix[x]) >> 4) as u8;
}
}
pub(crate) fn gaussian_blur(bitmap: &mut CoverageBitmap, sigma_x: f64, sigma_y: f64) {
if bitmap.is_empty() || (sigma_x <= 0.0 && sigma_y <= 0.0) {
return;
}
let boxes_x = boxes_for_gauss(sigma_x.max(0.0));
let boxes_y = boxes_for_gauss(sigma_y.max(0.0));
pad_xy(
bitmap,
(sigma_x.max(0.0).ceil() as usize + 1) * 2,
(sigma_y.max(0.0).ceil() as usize + 1) * 2,
);
let mut tmp = bitmap.data.clone();
let w = bitmap.w;
let h = bitmap.h;
let mut col_acc = vec![0u32; w];
for (rx, ry) in boxes_x.into_iter().zip(boxes_y) {
if rx > 0 {
box_blur_h(&bitmap.data, &mut tmp, w, h, rx);
} else {
tmp.copy_from_slice(&bitmap.data);
}
if ry > 0 {
box_blur_v(&tmp, &mut bitmap.data, w, h, ry, &mut col_acc);
} else {
bitmap.data.copy_from_slice(&tmp);
}
}
}
fn boxes_for_gauss(sigma: f64) -> [usize; 3] {
const N: f64 = 3.0;
let w_ideal = (12.0 * sigma * sigma / N + 1.0).sqrt();
let mut wl = w_ideal.floor() as i64;
if wl % 2 == 0 {
wl -= 1;
}
let wu = wl + 2;
let m_ideal =
(12.0 * sigma * sigma - N * (wl as f64) * (wl as f64) - 4.0 * N * wl as f64 - 3.0 * N)
/ (-4.0 * wl as f64 - 4.0);
let m = m_ideal.round() as i64;
let mut boxes = [0usize; 3];
for (i, b) in boxes.iter_mut().enumerate() {
let width = if (i as i64) < m { wl } else { wu };
*b = (width.max(1) as usize - 1) / 2;
}
boxes
}
fn box_blur_h(src: &[u8], dst: &mut [u8], w: usize, h: usize, r: usize) {
let norm = (2 * r + 1) as u32;
for y in 0..h {
let row = &src[y * w..(y + 1) * w];
let mut acc: u32 = 0;
for &v in &row[..=r.min(w.saturating_sub(1))] {
acc += u32::from(v);
}
for x in 0..w {
dst[y * w + x] = (acc / norm) as u8;
if x + r + 1 < w {
acc += u32::from(row[x + r + 1]);
}
if x >= r {
acc -= u32::from(row[x - r]);
}
}
}
}
fn box_blur_v(src: &[u8], dst: &mut [u8], w: usize, h: usize, r: usize, acc: &mut [u32]) {
let norm = (2 * r + 1) as u32;
acc.fill(0);
let last = r.min(h.saturating_sub(1));
for yy in 0..=last {
for (a, &s) in acc.iter_mut().zip(&src[yy * w..(yy + 1) * w]) {
*a += u32::from(s);
}
}
for y in 0..h {
for (d, &a) in dst[y * w..(y + 1) * w].iter_mut().zip(acc.iter()) {
*d = (a / norm) as u8;
}
if y + r + 1 < h {
for (a, &s) in acc.iter_mut().zip(&src[(y + r + 1) * w..(y + r + 2) * w]) {
*a += u32::from(s);
}
}
if y >= r {
for (a, &s) in acc.iter_mut().zip(&src[(y - r) * w..(y - r + 1) * w]) {
*a -= u32::from(s);
}
}
}
}
fn pad(bitmap: &mut CoverageBitmap, margin: usize) {
pad_xy(bitmap, margin, margin);
}
fn pad_xy(bitmap: &mut CoverageBitmap, margin_x: usize, margin_y: usize) {
if margin_x == 0 && margin_y == 0 {
return;
}
let w = bitmap.w + margin_x * 2;
let h = bitmap.h + margin_y * 2;
let mut data = vec![0u8; w * h];
for row in 0..bitmap.h {
let dst = (row + margin_y) * w + margin_x;
data[dst..dst + bitmap.w]
.copy_from_slice(&bitmap.data[row * bitmap.w..(row + 1) * bitmap.w]);
}
bitmap.w = w;
bitmap.h = h;
bitmap.x -= margin_x as i32;
bitmap.y -= margin_y as i32;
bitmap.data = data;
}
pub(crate) struct OutlinePath {
pub commands: Vec<Command>,
scale_x: f32,
scale_y: f32,
dx: f32,
dy: f32,
}
impl OutlinePath {
pub(crate) fn new(scale_x: f32, scale_y: f32, dx: f32, dy: f32) -> Self {
Self {
commands: Vec::new(),
scale_x,
scale_y,
dx,
dy,
}
}
fn point(&self, x: f32, y: f32) -> Vector {
Vector::new(self.dx + x * self.scale_x, self.dy - y * self.scale_y)
}
}
impl ttf_parser::OutlineBuilder for OutlinePath {
fn move_to(&mut self, x: f32, y: f32) {
let p = self.point(x, y);
self.commands.push(Command::MoveTo(p));
}
fn line_to(&mut self, x: f32, y: f32) {
let p = self.point(x, y);
self.commands.push(Command::LineTo(p));
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
let c = self.point(x1, y1);
let p = self.point(x, y);
self.commands.push(Command::QuadTo(c, p));
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
let c1 = self.point(x1, y1);
let c2 = self.point(x2, y2);
let p = self.point(x, y);
self.commands.push(Command::CurveTo(c1, c2, p));
}
fn close(&mut self) {
self.commands.push(Command::Close);
}
}
pub(crate) fn drawing_commands(
drawing: &crate::subtitle::ass::Drawing,
scale_x: f32,
scale_y: f32,
dx: f32,
dy: f32,
) -> Vec<Command> {
use crate::subtitle::ass::{DrawCmd, Point6};
let map = |p: Point6| -> Vector {
Vector::new(
dx + (p.x as f32 / 64.0) * scale_x,
dy + (p.y as f32 / 64.0) * scale_y,
)
};
drawing
.cmds
.iter()
.map(|cmd| match *cmd {
DrawCmd::Move(p) => Command::MoveTo(map(p)),
DrawCmd::Line(p) => Command::LineTo(map(p)),
DrawCmd::Cubic(c1, c2, p) => Command::CurveTo(map(c1), map(c2), map(p)),
DrawCmd::Close => Command::Close,
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn square(side: f32) -> Vec<Command> {
vec![
Command::MoveTo(Vector::new(0.0, 0.0)),
Command::LineTo(Vector::new(side, 0.0)),
Command::LineTo(Vector::new(side, side)),
Command::LineTo(Vector::new(0.0, side)),
Command::Close,
]
}
#[test]
fn fill_produces_opaque_interior() {
let bitmap = fill_path(&square(20.0));
assert!(!bitmap.is_empty());
let cx = bitmap.w / 2;
let cy = bitmap.h / 2;
assert_eq!(bitmap.data[cy * bitmap.w + cx], 255);
}
#[test]
fn border_extends_beyond_fill() {
let fill = fill_path(&square(20.0));
let border = border_path(&square(20.0), &fill, 3.0, 3.0);
assert!(border.w >= fill.w + 4, "stroke must widen the bitmap");
assert!(border.x <= fill.x - 2);
let cx = border.w / 2;
let cy = border.h / 2;
assert_eq!(border.data[cy * border.w + cx], 255);
}
#[test]
fn oversized_paths_degrade_instead_of_overflowing_mask_sizing() {
assert!(fill_path(&square(70_000.0)).is_empty());
assert!(fill_path(&square(61_440.0)).is_empty());
assert!(!fill_path(&square(7_680.0)).is_empty());
let fill = fill_path(&square(20.0));
let bordered = border_path(&square(61_440.0), &fill, 7_680.0, 7_680.0);
assert_eq!(bordered, fill, "unsafe stroke must fall back to the fill");
let aniso = border_path(&square(61_440.0), &fill, 7_680.0, 240.0);
assert_eq!(aniso, fill, "unsafe aniso stroke must fall back to the fill");
}
#[test]
fn asymmetric_border_extends_per_axis() {
let fill = fill_path(&square(20.0));
let border = border_path(&square(20.0), &fill, 8.0, 1.0);
let grow_x = (fill.x - border.x).min(border.x + border.w as i32 - fill.x - fill.w as i32);
let grow_y = (fill.y - border.y).min(border.y + border.h as i32 - fill.y - fill.h as i32);
assert!(grow_x >= 7, "horizontal growth {grow_x} must be ~8px");
assert!(grow_y <= 3, "vertical growth {grow_y} must be ~1px");
let cx = (fill.x - border.x) as usize + fill.w / 2;
let cy = (fill.y - border.y) as usize + fill.h / 2;
assert_eq!(border.data[cy * border.w + cx], 255);
}
#[test]
fn be_blur_pads_once_by_libass_budget() {
let mut bitmap = fill_path(&square(8.0));
let before_w = bitmap.w;
be_blur(&mut bitmap, 2);
assert_eq!(bitmap.w, before_w + 4, "be=2 pads by 2 per side");
assert!(bitmap.data.iter().any(|&v| v > 0 && v < 255));
let mut big = fill_path(&square(8.0));
let before_w = big.w;
be_blur(&mut big, 20);
assert_eq!(big.w, before_w + 10, "be=20 pads by the 5px cap");
}
#[test]
fn fix_outline_hollows_border_under_fill() {
let fill = fill_path(&square(20.0));
let mut border = border_path(&square(20.0), &fill, 3.0, 3.0);
fix_outline(&fill, &mut border);
let cx = (fill.x - border.x) as usize + fill.w / 2;
let cy = (fill.y - border.y) as usize + fill.h / 2;
assert_eq!(border.data[cy * border.w + cx], 0);
assert_eq!(
border.data[border.w / 2],
border_path(&square(20.0), &fill, 3.0, 3.0).data[border.w / 2]
);
}
#[test]
fn gaussian_blur_preserves_total_energy_roughly() {
let mut bitmap = fill_path(&square(10.0));
let sum_before: u64 = bitmap.data.iter().map(|&v| u64::from(v)).sum();
gaussian_blur(&mut bitmap, 2.0, 2.0);
let sum_after: u64 = bitmap.data.iter().map(|&v| u64::from(v)).sum();
let ratio = sum_after as f64 / sum_before as f64;
assert!((0.85..=1.15).contains(&ratio), "energy ratio {ratio}");
}
#[test]
fn clip_rect_zeroes_outside() {
let mut bitmap = fill_path(&square(10.0));
let (bx, by) = (bitmap.x, bitmap.y);
bitmap.clip_rect(bx, by, bx + 3, by + 3, false);
assert_eq!(bitmap.data[0], 255, "inside the clip");
assert_eq!(bitmap.data[5 * bitmap.w + 5], 0, "outside the clip");
let mut inv = fill_path(&square(10.0));
inv.clip_rect(bx, by, bx + 3, by + 3, true);
assert_eq!(inv.data[0], 0, "inverse clip zeroes the inside");
assert_eq!(inv.data[5 * inv.w + 5], 255);
}
#[test]
fn max_with_unions_disjoint_bitmaps() {
let a = CoverageBitmap {
w: 2,
h: 1,
x: 0,
y: 0,
data: vec![10, 20],
};
let b = CoverageBitmap {
w: 1,
h: 1,
x: 3,
y: 0,
data: vec![30],
};
let union = a.max_with(&b);
assert_eq!((union.w, union.h, union.x, union.y), (4, 1, 0, 0));
assert_eq!(union.data, vec![10, 20, 0, 30]);
}
fn fnv1a(bytes: &[u8]) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for &b in bytes {
h ^= u64::from(b);
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
h
}
fn bitmap_hash(b: &CoverageBitmap) -> u64 {
let mut bytes = Vec::with_capacity(32 + b.data.len());
bytes.extend_from_slice(&(b.w as u64).to_le_bytes());
bytes.extend_from_slice(&(b.h as u64).to_le_bytes());
bytes.extend_from_slice(&b.x.to_le_bytes());
bytes.extend_from_slice(&b.y.to_le_bytes());
bytes.extend_from_slice(&b.data);
fnv1a(&bytes)
}
fn det_mask(w: usize, h: usize, f: impl Fn(usize, usize) -> u8) -> CoverageBitmap {
let mut data = vec![0u8; w * h];
for (i, px) in data.iter_mut().enumerate() {
*px = f(i % w, i / w);
}
CoverageBitmap {
w,
h,
x: 5,
y: 7,
data,
}
}
fn masks() -> Vec<(&'static str, CoverageBitmap)> {
let pat = |x: usize, y: usize| ((x * 31 + y * 17) % 256) as u8;
vec![
("16x32", det_mask(16, 32, pat)),
("128x24", det_mask(128, 24, pat)),
("24x128", det_mask(24, 128, pat)),
("all255_32x32", det_mask(32, 32, |_, _| 255)),
(
"sparse_40x40",
det_mask(
40,
40,
|x, y| if (x * 7 + y * 3) % 29 == 0 { 180 } else { 0 },
),
),
]
}
fn bench_masks() -> Vec<(&'static str, CoverageBitmap)> {
let pat = |x: usize, y: usize| ((x * 31 + y * 17) % 256) as u8;
let mut v = masks();
v.push(("512x512", det_mask(512, 512, pat)));
v.push(("1024x256", det_mask(1024, 256, pat)));
v
}
fn glyph_like() -> Vec<Command> {
vec![
Command::MoveTo(Vector::new(2.0, 2.0)),
Command::LineTo(Vector::new(18.0, 4.0)),
Command::QuadTo(Vector::new(24.0, 10.0), Vector::new(20.0, 22.0)),
Command::LineTo(Vector::new(6.0, 18.0)),
Command::CurveTo(
Vector::new(3.0, 14.0),
Vector::new(1.0, 9.0),
Vector::new(2.0, 2.0),
),
Command::Close,
]
}
const BE_PASSES: [i32; 4] = [1, 2, 5, 20];
const GAUSS_SIGMAS: [(f64, f64); 4] = [(0.5, 0.5), (2.0, 2.0), (5.0, 1.0), (0.0, 3.0)];
const BORDER_ANISO: [(f32, f32); 2] = [(3.0, 1.5), (1.0, 4.0)];
#[test]
#[ignore = "prints golden hashes for the parity constants"]
fn dump_goldens() {
for (name, mask) in masks() {
for passes in BE_PASSES {
let mut b = mask.clone();
be_blur(&mut b, passes);
println!(
"be_blur {name:<14} {passes:<3} => {:#018x}",
bitmap_hash(&b)
);
}
}
for (name, mask) in masks() {
for (sx, sy) in GAUSS_SIGMAS {
let mut b = mask.clone();
gaussian_blur(&mut b, sx, sy);
println!(
"gaussian {name:<14} {sx},{sy} => {:#018x}",
bitmap_hash(&b)
);
}
}
let fill = fill_path(&glyph_like());
for (bx, by) in BORDER_ANISO {
let b = border_path(&glyph_like(), &fill, bx, by);
println!("border {bx},{by} => {:#018x}", bitmap_hash(&b));
}
}
#[rustfmt::skip]
const BE_GOLDENS: [u64; 20] = [
0xd803_8531_e1bf_58f1, 0x6b9b_baf9_cba0_cd9c, 0xa4c8_97f5_4ae6_d86b, 0xe2a2_b89d_364f_5ee5,
0xbf02_2691_b2a6_8151, 0x4b5a_e950_a4dd_10c9, 0x4e22_f94f_7eb4_573a, 0x3b77_5636_6cd3_ebde,
0xc192_95e4_b0d9_b6a5, 0x8bfa_1295_4771_4068, 0x4aae_83be_92e2_3f4f, 0xf177_4dc9_d811_4556,
0xc08f_1b96_b3fa_04b3, 0x61fd_6cd3_7e0f_3a9f, 0x4575_4522_d745_3a67, 0x54b2_a781_61fe_55e3,
0x1198_c1c5_06b9_3135, 0x84de_fae0_6796_87b9, 0x9b0e_fed0_2429_7baf, 0xa7ed_8ca1_ef98_e917,
];
#[rustfmt::skip]
const GAUSS_GOLDENS: [u64; 20] = [
0x83b1_cce7_4655_6c87, 0xabf2_d2c0_f06b_721a, 0xd9e4_7912_af7d_6728, 0x6ade_2182_5cee_d5ee,
0x726d_3a16_e645_a43f, 0x8676_eb28_98d4_3dd2, 0x3bb4_da75_45dd_754f, 0xde06_8d27_3355_6dc2,
0x4b75_aff0_f679_8cff, 0xaf8d_4273_9e70_be94, 0xe4ee_eb46_9c16_3bbb, 0xf7c9_85cb_c5d4_3ce5,
0xb46c_035f_4007_9657, 0x771a_5b64_408b_2214, 0x5507_dd2e_c82d_6050, 0x38fe_e6e7_6247_3c8e,
0xeaf2_a6d6_566b_745f, 0x136f_8e4d_1fdc_67dc, 0x8388_a4b7_367c_44ac, 0x6ec0_d23c_5ee6_091c,
];
const BORDER_GOLDENS: [u64; 2] = [0xff22_7254_d918_c628, 0x1dce_d946_2715_012a];
#[test]
fn golden_hashes_match() {
let mut i = 0;
for (name, mask) in masks() {
for passes in BE_PASSES {
let mut b = mask.clone();
be_blur(&mut b, passes);
assert_eq!(
bitmap_hash(&b),
BE_GOLDENS[i],
"be_blur {name} passes={passes} diverged from golden"
);
i += 1;
}
}
let mut j = 0;
for (name, mask) in masks() {
for (sx, sy) in GAUSS_SIGMAS {
let mut b = mask.clone();
gaussian_blur(&mut b, sx, sy);
assert_eq!(
bitmap_hash(&b),
GAUSS_GOLDENS[j],
"gaussian_blur {name} sigma=({sx},{sy}) diverged from golden"
);
j += 1;
}
}
let fill = fill_path(&glyph_like());
for (k, (bx, by)) in BORDER_ANISO.into_iter().enumerate() {
let b = border_path(&glyph_like(), &fill, bx, by);
assert_eq!(
bitmap_hash(&b),
BORDER_GOLDENS[k],
"border_path aniso ({bx},{by}) diverged from golden"
);
}
}
fn assert_bitmap_eq(a: &CoverageBitmap, b: &CoverageBitmap, ctx: &str) {
assert_eq!(
(a.w, a.h, a.x, a.y),
(b.w, b.h, b.x, b.y),
"{ctx}: geometry"
);
assert_eq!(a.data, b.data, "{ctx}: coverage data");
}
fn reference_be_blur_pass(buf: &mut [u8], w: usize, h: usize) {
let mut col_pix = vec![0u16; w];
let mut col_sum = vec![0u16; w];
let mut horiz = vec![0u16; w];
be_row_sums(&buf[..w], &mut horiz);
col_pix.copy_from_slice(&horiz);
col_sum.copy_from_slice(&horiz);
for y in 1..h {
let src = buf[y * w..(y + 1) * w].to_vec();
be_row_sums(&src, &mut horiz);
let dst = &mut buf[(y - 1) * w..y * w];
for x in 0..w {
let vert = col_pix[x] + horiz[x];
col_pix[x] = horiz[x];
dst[x] = ((col_sum[x] + vert) >> 4) as u8;
col_sum[x] = vert;
}
}
let dst = &mut buf[(h - 1) * w..h * w];
for x in 0..w {
dst[x] = ((col_sum[x] + col_pix[x]) >> 4) as u8;
}
}
fn reference_be_blur(bitmap: &mut CoverageBitmap, passes: i32) {
let mut be = passes.clamp(0, 127);
if be <= 0 || bitmap.is_empty() {
return;
}
pad(bitmap, be_padding(be));
let w = bitmap.w;
let h = bitmap.h;
if w < 2 || h < 2 {
return;
}
if be > 1 {
for v in bitmap.data.iter_mut() {
*v = ((*v >> 1) + 1) >> 1;
}
while be > 1 {
reference_be_blur_pass(&mut bitmap.data, w, h);
be -= 1;
}
for v in bitmap.data.iter_mut() {
*v = ((i32::from(*v) << 2) - i32::from(*v > 32)) as u8;
}
}
reference_be_blur_pass(&mut bitmap.data, w, h);
}
fn reference_boxes_for_gauss(sigma: f64, n: usize) -> Vec<usize> {
let w_ideal = (12.0 * sigma * sigma / n as f64 + 1.0).sqrt();
let mut wl = w_ideal.floor() as i64;
if wl % 2 == 0 {
wl -= 1;
}
let wu = wl + 2;
let m_ideal = (12.0 * sigma * sigma
- (n as f64) * (wl as f64) * (wl as f64)
- 4.0 * n as f64 * wl as f64
- 3.0 * n as f64)
/ (-4.0 * wl as f64 - 4.0);
let m = m_ideal.round() as i64;
(0..n as i64)
.map(|i| {
let width = if i < m { wl } else { wu };
(width.max(1) as usize - 1) / 2
})
.collect()
}
fn reference_box_blur_v(src: &[u8], dst: &mut [u8], w: usize, h: usize, r: usize) {
let norm = (2 * r + 1) as u32;
for x in 0..w {
let mut acc: u32 = 0;
for y in 0..=r.min(h.saturating_sub(1)) {
acc += u32::from(src[y * w + x]);
}
for y in 0..h {
dst[y * w + x] = (acc / norm) as u8;
if y + r + 1 < h {
acc += u32::from(src[(y + r + 1) * w + x]);
}
if y >= r {
acc -= u32::from(src[(y - r) * w + x]);
}
}
}
}
fn reference_gaussian_blur(bitmap: &mut CoverageBitmap, sigma_x: f64, sigma_y: f64) {
if bitmap.is_empty() || (sigma_x <= 0.0 && sigma_y <= 0.0) {
return;
}
let boxes_x = reference_boxes_for_gauss(sigma_x.max(0.0), 3);
let boxes_y = reference_boxes_for_gauss(sigma_y.max(0.0), 3);
pad_xy(
bitmap,
(sigma_x.max(0.0).ceil() as usize + 1) * 2,
(sigma_y.max(0.0).ceil() as usize + 1) * 2,
);
let mut tmp = bitmap.data.clone();
let w = bitmap.w;
let h = bitmap.h;
for (rx, ry) in boxes_x.into_iter().zip(boxes_y) {
if rx > 0 {
box_blur_h(&bitmap.data, &mut tmp, w, h, rx);
} else {
tmp.copy_from_slice(&bitmap.data);
}
if ry > 0 {
reference_box_blur_v(&tmp, &mut bitmap.data, w, h, ry);
} else {
bitmap.data.copy_from_slice(&tmp);
}
}
}
fn reference_resample_rows(src: &CoverageBitmap, sy: f64) -> CoverageBitmap {
if src.is_empty() {
return CoverageBitmap::default();
}
let y0 = (f64::from(src.y) / sy).floor() as i32;
let y1 = ((f64::from(src.y) + src.h as f64) / sy).ceil() as i32;
let h = (y1 - y0).max(1) as usize;
let mut data = vec![0u8; src.w * h];
for row in 0..h {
let a = (f64::from(y0 + row as i32)) * sy - f64::from(src.y);
let b = a + sy;
let (a0, b0) = (a.max(0.0), b.min(src.h as f64));
if b0 <= a0 {
continue;
}
for col in 0..src.w {
let mut acc = 0.0f64;
let mut sr = a0.floor() as usize;
while (sr as f64) < b0 {
let cover = (b0.min(sr as f64 + 1.0) - a0.max(sr as f64)).max(0.0);
acc += cover * f64::from(src.data[sr * src.w + col]);
sr += 1;
}
data[row * src.w + col] = ((acc / (b0 - a0)) + 0.5) as u8;
}
}
CoverageBitmap {
w: src.w,
h,
x: src.x,
y: y0,
data,
}
}
fn reference_border_path_aniso(
commands: &[Command],
fill: &CoverageBitmap,
border_x: f32,
border_y: f32,
) -> CoverageBitmap {
let rx = border_x.max(1.0 / 64.0);
let ry = border_y.max(1.0 / 64.0);
let squeeze = (rx / ry).clamp(1.0 / 32.0, 32.0);
let squeezed: Vec<Command> = commands
.iter()
.map(|c| transform_y(*c, f64::from(squeeze)))
.collect();
let mut stroke = Stroke::new(2.0 * rx);
stroke.cap(Cap::Round).join(Join::Round);
let (data, placement) = Mask::new(&squeezed).style(Style::Stroke(stroke)).render();
let squeezed_bitmap = CoverageBitmap::from_mask(data, placement);
reference_resample_rows(&squeezed_bitmap, f64::from(squeeze)).max_with(fill)
}
#[test]
fn be_blur_matches_reference() {
for (name, mask) in masks() {
for passes in BE_PASSES {
let mut new = mask.clone();
let mut old = mask.clone();
be_blur(&mut new, passes);
reference_be_blur(&mut old, passes);
assert_bitmap_eq(&new, &old, &format!("be_blur {name} passes={passes}"));
}
}
}
#[test]
fn gaussian_blur_matches_reference() {
for (name, mask) in masks() {
for (sx, sy) in GAUSS_SIGMAS {
let mut new = mask.clone();
let mut old = mask.clone();
gaussian_blur(&mut new, sx, sy);
reference_gaussian_blur(&mut old, sx, sy);
assert_bitmap_eq(&new, &old, &format!("gaussian {name} ({sx},{sy})"));
}
}
}
#[test]
fn box_blur_v_matches_reference() {
for (name, mask) in masks() {
let (w, h) = (mask.w, mask.h);
for r in [1usize, 2, 3, 5, 9, 50] {
let mut new_dst = vec![0u8; w * h];
let mut old_dst = vec![0u8; w * h];
let mut acc = vec![0u32; w];
box_blur_v(&mask.data, &mut new_dst, w, h, r, &mut acc);
reference_box_blur_v(&mask.data, &mut old_dst, w, h, r);
assert_eq!(new_dst, old_dst, "box_blur_v {name} r={r}");
}
}
}
#[test]
fn boxes_for_gauss_matches_reference() {
for sigma in [0.0, 0.3, 0.5, 1.0, 2.0, 3.7, 5.0, 12.5, 40.0] {
let new = boxes_for_gauss(sigma);
let old = reference_boxes_for_gauss(sigma, 3);
assert_eq!(new.to_vec(), old, "boxes_for_gauss sigma={sigma}");
}
}
#[test]
fn resample_rows_matches_reference() {
for (bx, by) in BORDER_ANISO {
let rx = bx.max(1.0 / 64.0);
let ry = by.max(1.0 / 64.0);
let squeeze = (rx / ry).clamp(1.0 / 32.0, 32.0);
let squeezed: Vec<Command> = glyph_like()
.iter()
.map(|c| transform_y(*c, f64::from(squeeze)))
.collect();
let mut stroke = Stroke::new(2.0 * rx);
stroke.cap(Cap::Round).join(Join::Round);
let (data, placement) = Mask::new(&squeezed).style(Style::Stroke(stroke)).render();
let sb = CoverageBitmap::from_mask(data, placement);
let new = resample_rows(&sb, f64::from(squeeze));
let old = reference_resample_rows(&sb, f64::from(squeeze));
assert_bitmap_eq(&new, &old, &format!("resample_rows squeeze={squeeze}"));
}
}
#[test]
fn border_path_aniso_matches_reference() {
let fill = fill_path(&glyph_like());
for (bx, by) in BORDER_ANISO {
let new = border_path(&glyph_like(), &fill, bx, by);
let old = reference_border_path_aniso(&glyph_like(), &fill, bx, by);
assert_bitmap_eq(&new, &old, &format!("border_path aniso ({bx},{by})"));
}
let empty = CoverageBitmap::default();
for (bx, by) in BORDER_ANISO {
let new = border_path(&glyph_like(), &empty, bx, by);
let old = reference_border_path_aniso(&glyph_like(), &empty, bx, by);
assert_bitmap_eq(
&new,
&old,
&format!("border_path aniso empty-fill ({bx},{by})"),
);
}
let sb_empty = CoverageBitmap::default();
assert_bitmap_eq(
&resample_rows_union(&sb_empty, 2.0, &fill),
&fill,
"resample_rows_union empty-squeezed",
);
}
fn bench_measure(mut run: impl FnMut()) -> f64 {
run(); let start = std::time::Instant::now();
run();
let once = start.elapsed().as_nanos().max(1);
let iters = (50_000_000 / once).clamp(20, 10_000) as usize;
let mut best = f64::INFINITY;
for _ in 0..3 {
let start = std::time::Instant::now();
for _ in 0..iters {
run();
}
best = best.min(start.elapsed().as_nanos() as f64 / iters as f64);
}
best
}
#[test]
#[ignore = "manual micro-benchmark; run in release with --ignored --nocapture"]
fn bench_be_blur() {
println!("be_blur kernel ns/call (best-of-3, clone-subtracted):");
for (name, mask) in bench_masks() {
let clone_ns = bench_measure(|| {
let b = mask.clone();
std::hint::black_box(b.data.first().copied());
});
for passes in [2, 5, 20] {
let old = bench_measure(|| {
let mut b = mask.clone();
reference_be_blur(&mut b, passes);
std::hint::black_box(b.data.first().copied());
});
let new = bench_measure(|| {
let mut b = mask.clone();
be_blur(&mut b, passes);
std::hint::black_box(b.data.first().copied());
});
let (o, n) = ((old - clone_ns).max(0.0), (new - clone_ns).max(0.0));
println!(
" {name:<14} passes={passes:<2} old {o:>9.0} new {n:>9.0} {:>4.2}x",
o / n.max(1.0)
);
}
}
}
#[test]
#[ignore = "manual micro-benchmark; run in release with --ignored --nocapture"]
fn bench_gaussian_blur() {
println!("gaussian_blur kernel ns/call (best-of-3, clone-subtracted):");
for (name, mask) in bench_masks() {
let clone_ns = bench_measure(|| {
let b = mask.clone();
std::hint::black_box(b.data.first().copied());
});
for (sx, sy) in [(2.0, 2.0), (5.0, 1.0), (5.0, 5.0)] {
let old = bench_measure(|| {
let mut b = mask.clone();
reference_gaussian_blur(&mut b, sx, sy);
std::hint::black_box(b.data.first().copied());
});
let new = bench_measure(|| {
let mut b = mask.clone();
gaussian_blur(&mut b, sx, sy);
std::hint::black_box(b.data.first().copied());
});
let (o, n) = ((old - clone_ns).max(0.0), (new - clone_ns).max(0.0));
println!(
" {name:<14} sigma=({sx},{sy}) old {o:>9.0} new {n:>9.0} {:>4.2}x",
o / n.max(1.0)
);
}
}
}
}