use crate::color::{blend_over, lerp_premul, scale_premul, Color}; use crate::geom::{pt, Point, Rect, Transform, IDENTITY}; use crate::path::Path; use crate::raster::Rasterizer; use crate::simd; pub const TOL: f32 = 0.08; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Cap { Butt, Round, Square, } #[derive(Clone, Debug)] pub struct Stroke { pub width: f32, pub cap: Cap, } impl Stroke { pub fn new(width: f32) -> Stroke { Stroke { width, cap: Cap::Butt } } pub fn round(width: f32) -> Stroke { Stroke { width, cap: Cap::Round } } } #[derive(Clone, Debug)] pub enum Paint { Solid(Color), Linear { p0: Point, p1: Point, stops: Vec<(f32, Color)> }, Radial { c: Point, r: f32, stops: Vec<(f32, Color)> }, } impl Paint { pub fn solid(c: Color) -> Paint { Paint::Solid(c) } pub fn vgrad(r: Rect, top: Color, bottom: Color) -> Paint { Paint::Linear { p0: pt(r.x0, r.y0), p1: pt(r.x0, r.y1), stops: vec![(0.0, top), (1.0, bottom)] } } pub fn hgrad(r: Rect, left: Color, right: Color) -> Paint { Paint::Linear { p0: pt(r.x0, r.y0), p1: pt(r.x1, r.y0), stops: vec![(0.0, left), (1.0, right)] } } pub fn dgrad(r: Rect, a: Color, b: Color) -> Paint { Paint::Linear { p0: pt(r.x0, r.y0), p1: pt(r.x1, r.y1), stops: vec![(0.0, a), (1.0, b)] } } pub fn glow(c: Point, r: f32, inner: Color, outer: Color) -> Paint { Paint::Radial { c, r, stops: vec![(0.0, inner), (1.0, outer)] } } } fn build_lut(stops: &[(f32, Color)]) -> [u32; 256] { let mut lut = [0u32; 256]; if stops.is_empty() { return lut; } for (i, slot) in lut.iter_mut().enumerate() { let t = i as f32 / 255.0; let mut col = stops[0].1; if t >= stops[stops.len() - 1].0 { col = stops[stops.len() - 1].1; } else if t > stops[0].0 { for w in stops.windows(2) { let (t0, c0) = w[0]; let (t1, c1) = w[1]; if t >= t0 && t <= t1 { col = c0.lerp(c1, (t - t0) / (t1 - t0).max(1e-6)); break; } } } *slot = col.premul(); } lut } enum Shader { Solid(u32), Linear { ox: f32, oy: f32, dx: f32, dy: f32, lut: Box<[u32; 256]>, opaque: bool }, Radial { cx: f32, cy: f32, inv_r: f32, lut: Box<[u32; 256]>, opaque: bool }, } impl Shader { fn build(paint: &Paint, xf: &Transform) -> Shader { match paint { Paint::Solid(c) => Shader::Solid(c.premul()), Paint::Linear { p0, p1, stops } => { let a = xf.apply(*p0); let b = xf.apply(*p1); let d = b - a; let len2 = d.len_sq().max(1e-6); let lut = build_lut(stops); let opaque = lut.iter().all(|v| v >> 24 == 255); Shader::Linear { ox: a.x, oy: a.y, dx: d.x / len2, dy: d.y / len2, lut: Box::new(lut), opaque } } Paint::Radial { c, r, stops } => { let cc = xf.apply(*c); let rr = (r * xf.scale_factor()).max(1e-4); let lut = build_lut(stops); let opaque = lut.iter().all(|v| v >> 24 == 255); Shader::Radial { cx: cc.x, cy: cc.y, inv_r: 1.0 / rr, lut: Box::new(lut), opaque } } } } } #[inline] fn cov_u32(c: f32) -> u32 { (c * 255.0 + 0.5) as u32 } pub struct Layer { pub x: i32, pub y: i32, pub w: usize, pub h: usize, pub scale: usize, pub px: Vec, } impl Layer { #[inline] pub fn sample(&self, dx: usize, dy: usize) -> u32 { if self.w == 0 || self.h == 0 { return 0; } if self.scale == 1 { let lx = dx as i32 - self.x; let ly = dy as i32 - self.y; let lx = lx.clamp(0, self.w as i32 - 1) as usize; let ly = ly.clamp(0, self.h as i32 - 1) as usize; return self.px[ly * self.w + lx]; } let k = self.scale as f32; let fx = (dx as f32 + 0.5 - self.x as f32) / k - 0.5; let fy = (dy as f32 + 0.5 - self.y as f32) / k - 0.5; bilinear_u32(&self.px, self.w, self.h, fx, fy) } } #[inline] fn bilinear_u32(px: &[u32], w: usize, h: usize, fx: f32, fy: f32) -> u32 { let xf = fx.floor(); let yf = fy.floor(); let tx = ((fx - xf) * 255.0) as u32; let ty = ((fy - yf) * 255.0) as u32; let x0 = (xf as i32).clamp(0, w as i32 - 1) as usize; let y0 = (yf as i32).clamp(0, h as i32 - 1) as usize; let x1 = (x0 + 1).min(w - 1); let y1 = (y0 + 1).min(h - 1); let r0 = y0 * w; let r1 = y1 * w; let a = lerp_premul(px[r0 + x0], px[r0 + x1], tx); let b = lerp_premul(px[r1 + x0], px[r1 + x1], tx); lerp_premul(a, b, ty) } pub struct Canvas { pub w: usize, pub h: usize, pub px: Vec, ras: Rasterizer, cov: Vec, clip: Rect, clip_stack: Vec, scratch: Path, sh_ras: Rasterizer, sh_mask: Vec, sh_tmp: Vec, sh_row: Vec, } impl Canvas { pub fn new(w: usize, h: usize) -> Canvas { Canvas { w, h, px: vec![0; w * h], ras: Rasterizer::new(w, h), cov: Vec::new(), clip: Rect::new(0.0, 0.0, w as f32, h as f32), clip_stack: Vec::new(), scratch: Path::new(), sh_ras: Rasterizer::new(0, 0), sh_mask: Vec::new(), sh_tmp: Vec::new(), sh_row: Vec::new(), } } pub fn resize(&mut self, w: usize, h: usize) { if w == self.w && h == self.h { return; } self.w = w; self.h = h; self.px.clear(); self.px.resize(w * h, 0); self.ras.resize(w, h); self.clip = Rect::new(0.0, 0.0, w as f32, h as f32); self.clip_stack.clear(); } pub fn clear(&mut self, c: Color) { let v = c.premul(); for p in &mut self.px { *p = v; } } pub fn full(&self) -> Rect { Rect::new(0.0, 0.0, self.w as f32, self.h as f32) } pub fn clip(&self) -> Rect { self.clip } pub fn push_clip(&mut self, r: Rect) { self.clip_stack.push(self.clip); let snapped = Rect::new(r.x0.round(), r.y0.round(), r.x1.round(), r.y1.round()); self.clip = self.clip.intersect(snapped); } pub fn pop_clip(&mut self) { if let Some(r) = self.clip_stack.pop() { self.clip = r; } } fn clip_bounds(&self) -> (usize, usize, usize, usize) { let c = self.clip; let x0 = (c.x0.max(0.0) as usize).min(self.w); let y0 = (c.y0.max(0.0) as usize).min(self.h); let x1 = (c.x1.max(0.0) as usize).min(self.w); let y1 = (c.y1.max(0.0) as usize).min(self.h); (x0, y0, x1.max(x0), y1.max(y0)) } pub fn fill(&mut self, path: &Path, xf: &Transform, paint: &Paint) { if self.clip.is_empty() { return; } self.ras.reset(); { let ras = &mut self.ras; path.flatten(xf, TOL, |a, b| ras.line(a, b)); } let shader = Shader::build(paint, xf); self.composite(&shader); } fn composite(&mut self, shader: &Shader) { let (cx0, cy0, cx1, cy1) = self.clip_bounds(); let (ras, px, cov, w) = (&self.ras, &mut self.px, &mut self.cov, self.w); ras.for_rows(cov, |y, xs, covs| { if y < cy0 || y >= cy1 { return; } let lo = xs.max(cx0); let hi = (xs + covs.len()).min(cx1); if hi <= lo { return; } let row = &mut px[y * w + lo..y * w + hi]; let cs = &covs[lo - xs..hi - xs]; match shader { Shader::Solid(v) => simd::blend_solid_cov(row, *v, cs), Shader::Linear { ox, oy, dx, dy, lut, opaque } => { let t0 = (lo as f32 + 0.5 - ox) * dx + (y as f32 + 0.5 - oy) * dy; simd::blend_lut_cov(row, lut, t0, *dx, cs, *opaque); } Shader::Radial { cx, cy, inv_r, lut, opaque } => { let py = y as f32 + 0.5 - cy; simd::blend_radial_cov(row, lut, lo as f32 + 0.5 - cx, py * py, *inv_r, cs, *opaque); } } }); } pub fn fill_layer(&mut self, path: &Path, xf: &Transform, layer: &Layer, tint: Color) { if self.clip.is_empty() { return; } self.ras.reset(); { let ras = &mut self.ras; path.flatten(xf, TOL, |a, b| ras.line(a, b)); } let (cx0, cy0, cx1, cy1) = self.clip_bounds(); let tint_p = tint.premul(); let (ras, px, cov, w) = (&self.ras, &mut self.px, &mut self.cov, self.w); ras.for_rows(cov, |y, xs, covs| { if y < cy0 || y >= cy1 { return; } let lo = xs.max(cx0); let hi = (xs + covs.len()).min(cx1); if hi <= lo { return; } let row = &mut px[y * w + lo..y * w + hi]; let cs = &covs[lo - xs..hi - xs]; for (i, (d, &c)) in row.iter_mut().zip(cs).enumerate() { if c <= simd::ZERO { continue; } let base = layer.sample(lo + i, y) | 0xff00_0000; let v = blend_over(base, tint_p); *d = if c >= simd::FULL { v } else { blend_over(*d, scale_premul(v, cov_u32(c))) }; } }); } pub fn stroke(&mut self, path: &Path, xf: &Transform, paint: &Paint, style: &Stroke) { if self.clip.is_empty() || style.width <= 0.0 { return; } let hw = (style.width * xf.scale_factor()) * 0.5; let mut segs: Vec<(Vec, bool)> = Vec::new(); path.subpaths(xf, TOL, |pts, closed| segs.push((pts.to_vec(), closed))); self.ras.reset(); { let ras = &mut self.ras; for (pts, closed) in &segs { emit_stroke(ras, pts, *closed, hw, style.cap); } } let shader = Shader::build(paint, xf); self.composite(&shader); } pub fn fill_rect(&mut self, r: Rect, c: Color) { let mut p = core::mem::take(&mut self.scratch); p.clear(); p.rect(r); self.fill(&p, &IDENTITY, &Paint::Solid(c)); self.scratch = p; } pub fn fill_rrect(&mut self, r: Rect, radius: f32, paint: &Paint) { let mut p = core::mem::take(&mut self.scratch); p.clear(); p.rrect(r, radius); self.fill(&p, &IDENTITY, paint); self.scratch = p; } pub fn stroke_rrect(&mut self, r: Rect, radius: f32, c: Color, width: f32) { let mut p = core::mem::take(&mut self.scratch); p.clear(); p.rrect(r, radius); self.stroke(&p, &IDENTITY, &Paint::Solid(c), &Stroke::new(width)); self.scratch = p; } pub fn fill_circle(&mut self, c: Point, r: f32, paint: &Paint) { let mut p = core::mem::take(&mut self.scratch); p.clear(); p.circle(c, r); self.fill(&p, &IDENTITY, paint); self.scratch = p; } /// Blits an 8-bit coverage mask tinted with `color`. This is how text lands on /// the canvas: the glyph cache owns the mask, the canvas owns the clipping. pub fn blit_mask(&mut self, x: i32, y: i32, w: usize, h: usize, mask: &[u8], color: Color) { if w == 0 || h == 0 || color.a == 0 || mask.len() < w * h { return; } let (cx0, cy0, cx1, cy1) = self.clip_bounds(); let sx = (cx0 as i64 - x as i64).max(0) as usize; let sy = (cy0 as i64 - y as i64).max(0) as usize; if sx >= w || sy >= h { return; } let ex = ((cx1 as i64 - x as i64).max(0) as usize).min(w); let ey = ((cy1 as i64 - y as i64).max(0) as usize).min(h); if ex <= sx || ey <= sy { return; } let cp = color.premul(); let n = ex - sx; for row in sy..ey { let dy = (y + row as i32) as usize; let dx = (x + sx as i32) as usize; let base = dy * self.w + dx; simd::blend_solid_alpha( &mut self.px[base..base + n], cp, &mask[row * w + sx..row * w + ex], ); } } pub fn snapshot(&self, r: Rect) -> Layer { let x0 = (r.x0.floor() as i64).clamp(0, self.w as i64) as usize; let y0 = (r.y0.floor() as i64).clamp(0, self.h as i64) as usize; let x1 = (r.x1.ceil() as i64).clamp(0, self.w as i64) as usize; let y1 = (r.y1.ceil() as i64).clamp(0, self.h as i64) as usize; let (lw, lh) = (x1.saturating_sub(x0), y1.saturating_sub(y0)); let mut px = vec![0u32; lw * lh]; for y in 0..lh { let src = (y0 + y) * self.w + x0; px[y * lw..(y + 1) * lw].copy_from_slice(&self.px[src..src + lw]); } Layer { x: x0 as i32, y: y0 as i32, w: lw, h: lh, scale: 1, px } } pub fn blurred_layer(&self, r: Rect, sigma: f32) -> Layer { let k = ((sigma / 3.0).round() as usize).clamp(1, 8); let pad = (sigma * 2.5).ceil(); let x0 = ((r.x0 - pad).floor() as i64).clamp(0, self.w as i64) as usize; let y0 = ((r.y0 - pad).floor() as i64).clamp(0, self.h as i64) as usize; let x1 = ((r.x1 + pad).ceil() as i64).clamp(0, self.w as i64) as usize; let y1 = ((r.y1 + pad).ceil() as i64).clamp(0, self.h as i64) as usize; let (rw, rh) = (x1.saturating_sub(x0), y1.saturating_sub(y0)); if rw == 0 || rh == 0 { return Layer { x: 0, y: 0, w: 0, h: 0, scale: 1, px: Vec::new() }; } let lw = rw.div_ceil(k); let lh = rh.div_ceil(k); let mut px = vec![0u32; lw * lh]; for j in 0..lh { for i in 0..lw { let mut acc = [0u32; 4]; let mut n = 0u32; for sy in 0..k { let yy = y0 + j * k + sy; if yy >= y1 { break; } for sx in 0..k { let xx = x0 + i * k + sx; if xx >= x1 { break; } let v = self.px[yy * self.w + xx]; for (ch, a) in acc.iter_mut().enumerate() { *a += (v >> (ch * 8)) & 0xff; } n += 1; } } if n > 0 { let mut o = 0u32; for (ch, a) in acc.iter().enumerate() { o |= (a / n) << (ch * 8); } px[j * lw + i] = o; } } } blur_premul(&mut px, lw, lh, sigma / k as f32); Layer { x: x0 as i32, y: y0 as i32, w: lw, h: lh, scale: k, px } } pub fn shadow(&mut self, path: &Path, xf: &Transform, sigma: f32, color: Color, offset: Point) { self.shadow_occluded(path, xf, sigma, color, offset, Rect::new(0.0, 0.0, 0.0, 0.0)); } pub fn shadow_occluded( &mut self, path: &Path, xf: &Transform, sigma: f32, color: Color, offset: Point, occluder: Rect, ) { if self.clip.is_empty() || color.a == 0 { return; } let k = ((sigma / 3.0).round() as usize).clamp(1, 8) as f32; let b = path.transform(xf).bounds(); let pad = sigma * 3.0 + 2.0; let ox = (b.x0 + offset.x - pad).floor(); let oy = (b.y0 + offset.y - pad).floor(); let dev_w = (b.x1 + offset.x + pad).ceil() - ox; let dev_h = (b.y1 + offset.y + pad).ceil() - oy; if dev_w <= 0.0 || dev_h <= 0.0 { return; } let mw = (dev_w / k).ceil() as usize + 1; let mh = (dev_h / k).ceil() as usize + 1; if mw == 0 || mh == 0 || mw > 4096 || mh > 4096 { return; } self.sh_ras.resize(mw, mh); self.sh_ras.reset(); let total = xf .then(&Transform::translate(offset.x - ox, offset.y - oy)) .then(&Transform::scale(1.0 / k, 1.0 / k)); { let ras = &mut self.sh_ras; path.flatten(&total, TOL / 2.0, |a, bb| ras.line(a, bb)); } self.sh_mask.clear(); self.sh_mask.resize(mw * mh, 0); { let mask = &mut self.sh_mask; self.sh_ras.spans(|x, y, c| mask[y * mw + x] = (c * 255.0 + 0.5) as u8); } let s = sigma / k; if s > 0.3 { self.sh_tmp.clear(); self.sh_tmp.resize(mw * mh, 0); box_blur(&mut self.sh_mask, &mut self.sh_tmp, mw, mh, s); } let (mut bx0, mut by0, mut bx1, mut by1) = (mw, mh, 0usize, 0usize); for y in 0..mh { for x in 0..mw { if self.sh_mask[y * mw + x] > 1 { bx0 = bx0.min(x); by0 = by0.min(y); bx1 = bx1.max(x + 1); by1 = by1.max(y + 1); } } } if bx1 <= bx0 || by1 <= by0 { return; } let (cx0, cy0, cx1, cy1) = self.clip_bounds(); let dx0 = ((ox + bx0 as f32 * k).floor() as i64).clamp(cx0 as i64, cx1 as i64) as usize; let dy0 = ((oy + by0 as f32 * k).floor() as i64).clamp(cy0 as i64, cy1 as i64) as usize; let dx1 = ((ox + bx1 as f32 * k).ceil() as i64).clamp(cx0 as i64, cx1 as i64) as usize; let dy1 = ((oy + by1 as f32 * k).ceil() as i64).clamp(cy0 as i64, cy1 as i64) as usize; let cp = color.premul(); let inv = 1.0 / k; let fx0 = (dx0 as f32 + 0.5 - ox) * inv - 0.5; let step = (inv * 65536.0) as i32; let width = dx1 - dx0; self.sh_row.clear(); self.sh_row.resize(width, 0); let occ = occluder.intersect(Rect::new(dx0 as f32, dy0 as f32, dx1 as f32, dy1 as f32)); let (ox0, ox1) = if occ.is_empty() { (0usize, 0usize) } else { ((occ.x0 as usize - dx0), (occ.x1 as usize - dx0)) }; for y in dy0..dy1 { let fy = (y as f32 + 0.5 - oy) * inv - 0.5; let yfl = fy.floor(); let ty = (((fy - yfl) * 256.0) as u32).min(256); let y0i = (yfl as i32).clamp(0, mh as i32 - 1) as usize; let y1i = (y0i + 1).min(mh - 1); let m0 = &self.sh_mask[y0i * mw..y0i * mw + mw]; let m1 = &self.sh_mask[y1i * mw..y1i * mw + mw]; let occluded = !occ.is_empty() && (y as f32) >= occ.y0 && (y as f32) < occ.y1; let mut fixed = (fx0 * 65536.0) as i32; let last = mw as i32 - 1; for a in self.sh_row[..width].iter_mut() { let xi = fixed >> 16; let tx = ((fixed >> 8) & 0xff) as u32; let x0i = xi.clamp(0, last) as usize; let x1i = (xi + 1).clamp(0, last) as usize; let p = m0[x0i] as u32 * (256 - tx) + m0[x1i] as u32 * tx; let q = m1[x0i] as u32 * (256 - tx) + m1[x1i] as u32 * tx; *a = ((((p >> 8) * (256 - ty) + (q >> 8) * ty) >> 8) as u8).min(255); fixed += step; } let base = y * self.w + dx0; if occluded { if ox0 > 0 { simd::blend_solid_alpha( &mut self.px[base..base + ox0], cp, &self.sh_row[..ox0], ); } if ox1 < width { simd::blend_solid_alpha( &mut self.px[base + ox1..base + width], cp, &self.sh_row[ox1..width], ); } } else { simd::blend_solid_alpha( &mut self.px[base..base + width], cp, &self.sh_row[..width], ); } } } pub fn blur_region(&mut self, r: Rect, sigma: f32) { let x0 = (r.x0.floor() as i64).clamp(0, self.w as i64) as usize; let y0 = (r.y0.floor() as i64).clamp(0, self.h as i64) as usize; let x1 = (r.x1.ceil() as i64).clamp(0, self.w as i64) as usize; let y1 = (r.y1.ceil() as i64).clamp(0, self.h as i64) as usize; let (lw, lh) = (x1.saturating_sub(x0), y1.saturating_sub(y0)); if lw == 0 || lh == 0 { return; } let mut buf = vec![0u32; lw * lh]; for y in 0..lh { let src = (y0 + y) * self.w + x0; buf[y * lw..(y + 1) * lw].copy_from_slice(&self.px[src..src + lw]); } blur_premul(&mut buf, lw, lh, sigma); for y in 0..lh { let dst = (y0 + y) * self.w + x0; self.px[dst..dst + lw].copy_from_slice(&buf[y * lw..(y + 1) * lw]); } } pub fn to_rgba(&self) -> Vec { let mut out = vec![0u8; self.w * self.h * 4]; for (i, v) in self.px.iter().enumerate() { let c = Color::from_premul(*v); out[i * 4] = c.r; out[i * 4 + 1] = c.g; out[i * 4 + 2] = c.b; out[i * 4 + 3] = c.a; } out } pub fn to_png(&self) -> Vec { crate::png::encode_rgba(self.w, self.h, &self.to_rgba()) } } fn emit_stroke(ras: &mut Rasterizer, pts: &[Point], closed: bool, hw: f32, cap: Cap) { if hw <= 0.0 { return; } if pts.len() < 2 { if cap == Cap::Round && pts.len() == 1 { emit_disc(ras, pts[0], hw); } return; } let n = pts.len(); let last = if closed { n } else { n - 1 }; for i in 0..last { let a = pts[i]; let b = pts[(i + 1) % n]; let d = b - a; if d.len_sq() < 1e-12 { continue; } let dir = d.norm(); let (mut a, mut b) = (a, b); if !closed && cap == Cap::Square { if i == 0 { a = a - dir * hw; } if i == last - 1 { b = b + dir * hw; } } let nrm = dir.perp() * hw; let p0 = a + nrm; let p1 = b + nrm; let p2 = b - nrm; let p3 = a - nrm; ras.line(p0, p1); ras.line(p1, p2); ras.line(p2, p3); ras.line(p3, p0); } let joint_start = if closed { 0 } else { 1 }; let joint_end = if closed { n } else { n - 1 }; if hw > 0.6 { for p in pts.iter().take(joint_end).skip(joint_start) { emit_disc(ras, *p, hw); } } if !closed && cap == Cap::Round { emit_disc(ras, pts[0], hw); emit_disc(ras, pts[n - 1], hw); } } fn emit_disc(ras: &mut Rasterizer, c: Point, r: f32) { if r < 0.35 { return; } let steps = ((r * 2.2) as usize).clamp(6, 48); let mut prev = pt(c.x + r, c.y); for i in 1..=steps { let a = -(i as f32) / steps as f32 * core::f32::consts::TAU; let p = pt(c.x + r * a.cos(), c.y + r * a.sin()); ras.line(prev, p); prev = p; } } fn box_sizes(sigma: f32) -> [usize; 3] { const N: f32 = 3.0; let s = sigma.max(0.0); let ideal = (12.0 * s * s / N + 1.0).sqrt(); let mut wl = ideal.floor() as i32; if wl % 2 == 0 { wl -= 1; } let wl = wl.max(1); let wu = wl + 2; let m = ((12.0 * s * s - N * (wl * wl) as f32 - 4.0 * N * wl as f32 - 3.0 * N) / (-4.0 * wl as f32 - 4.0)) .round() as i32; let mut out = [0usize; 3]; for (i, o) in out.iter_mut().enumerate() { let w = if (i as i32) < m { wl } else { wu }; *o = ((w - 1) / 2).max(0) as usize; } out } fn box_blur(mask: &mut [u8], tmp: &mut [u8], w: usize, h: usize, sigma: f32) { for r in box_sizes(sigma) { if r == 0 { continue; } blur_h_u8(mask, tmp, w, h, r); blur_v_u8(tmp, mask, w, h, r); } } fn blur_h_u8(src: &[u8], dst: &mut [u8], w: usize, h: usize, r: usize) { let win = (2 * r + 1) as u32; let mul = (1u32 << 16) / win; for y in 0..h { let s = &src[y * w..y * w + w]; let d = &mut dst[y * w..y * w + w]; let mut sum = s[0] as u32 * (r + 1) as u32; for x in 1..=r.min(w - 1) { sum += s[x] as u32; } if r >= w { sum += s[w - 1] as u32 * (r + 1 - w) as u32; } for x in 0..w { d[x] = ((sum * mul + (1 << 15)) >> 16) as u8; sum += s[(x + r + 1).min(w - 1)] as u32; sum -= s[x.saturating_sub(r)] as u32; } } } fn blur_v_u8(src: &[u8], dst: &mut [u8], w: usize, h: usize, r: usize) { let win = (2 * r + 1) as u32; let mul = (1u32 << 16) / win; let mut col = vec![0u32; w]; for (x, c) in col.iter_mut().enumerate() { *c = src[x] as u32 * (r + 1) as u32; } for y in 1..=r.min(h - 1) { for x in 0..w { col[x] += src[y * w + x] as u32; } } if r >= h { for x in 0..w { col[x] += src[(h - 1) * w + x] as u32 * (r + 1 - h) as u32; } } for y in 0..h { let add = (y + r + 1).min(h - 1) * w; let sub = y.saturating_sub(r) * w; for x in 0..w { dst[y * w + x] = ((col[x] * mul + (1 << 15)) >> 16) as u8; col[x] += src[add + x] as u32; col[x] -= src[sub + x] as u32; } } } pub fn blur_premul(buf: &mut [u32], w: usize, h: usize, sigma: f32) { if w == 0 || h == 0 || sigma <= 0.0 { return; } let mut tmp = vec![0u32; w * h]; for r in box_sizes(sigma) { if r == 0 { continue; } blur_h_u32(buf, &mut tmp, w, h, r); blur_v_u32(&tmp, buf, w, h, r); } } #[inline] fn split(v: u32) -> (u32, u32) { (v & 0x00ff_00ff, (v >> 8) & 0x00ff_00ff) } #[inline] fn joinc(lo: u32, hi: u32) -> u32 { (lo & 0x00ff_00ff) | ((hi & 0x00ff_00ff) << 8) } fn blur_h_u32(src: &[u32], dst: &mut [u32], w: usize, h: usize, r: usize) { let win = (2 * r + 1) as u32; let mul = (1u32 << 14) / win; for y in 0..h { let s = &src[y * w..y * w + w]; let d = &mut dst[y * w..y * w + w]; let (a, b) = split(s[0]); let mut lo = a * (r + 1) as u32; let mut hi = b * (r + 1) as u32; for x in 1..=r.min(w - 1) { let (a, b) = split(s[x]); lo += a; hi += b; } if r >= w { let (a, b) = split(s[w - 1]); lo += a * (r + 1 - w) as u32; hi += b * (r + 1 - w) as u32; } for x in 0..w { let ol = ((lo >> 16) * mul >> 14) << 16 | (((lo & 0xffff) * mul) >> 14); let oh = ((hi >> 16) * mul >> 14) << 16 | (((hi & 0xffff) * mul) >> 14); d[x] = joinc(ol, oh); let (a1, b1) = split(s[(x + r + 1).min(w - 1)]); let (a2, b2) = split(s[x.saturating_sub(r)]); lo = lo + a1 - a2; hi = hi + b1 - b2; } } } fn blur_v_u32(src: &[u32], dst: &mut [u32], w: usize, h: usize, r: usize) { let win = (2 * r + 1) as u32; let mul = (1u32 << 14) / win; let mut clo = vec![0u32; w]; let mut chi = vec![0u32; w]; for x in 0..w { let (a, b) = split(src[x]); clo[x] = a * (r + 1) as u32; chi[x] = b * (r + 1) as u32; } for y in 1..=r.min(h - 1) { for x in 0..w { let (a, b) = split(src[y * w + x]); clo[x] += a; chi[x] += b; } } if r >= h { for x in 0..w { let (a, b) = split(src[(h - 1) * w + x]); clo[x] += a * (r + 1 - h) as u32; chi[x] += b * (r + 1 - h) as u32; } } for y in 0..h { let add = (y + r + 1).min(h - 1) * w; let sub = y.saturating_sub(r) * w; for x in 0..w { let lo = clo[x]; let hi = chi[x]; let ol = ((lo >> 16) * mul >> 14) << 16 | (((lo & 0xffff) * mul) >> 14); let oh = ((hi >> 16) * mul >> 14) << 16 | (((hi & 0xffff) * mul) >> 14); dst[y * w + x] = joinc(ol, oh); let (a1, b1) = split(src[add + x]); let (a2, b2) = split(src[sub + x]); clo[x] = lo + a1 - a2; chi[x] = hi + b1 - b2; } } } #[inline] pub fn lerp_u32(a: u32, b: u32, t: f32) -> u32 { lerp_premul(a, b, (t.clamp(0.0, 1.0) * 255.0 + 0.5) as u32) } #[inline] pub fn shade(a: Color, b: Color, t: f32) -> Color { a.lerp(b, t) }