nitai/projects
noxstrap / src / crates / nox-gfx / src / canvas.rs
885 lines · 28.5 KB Raw
1use crate::color::{blend_over, lerp_premul, scale_premul, Color};
2use crate::geom::{pt, Point, Rect, Transform, IDENTITY};
3use crate::path::Path;
4use crate::raster::Rasterizer;
5use crate::simd;
6
7pub const TOL: f32 = 0.08;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum Cap {
11 Butt,
12 Round,
13 Square,
14}
15
16#[derive(Clone, Debug)]
17pub struct Stroke {
18 pub width: f32,
19 pub cap: Cap,
20}
21
22impl Stroke {
23 pub fn new(width: f32) -> Stroke {
24 Stroke { width, cap: Cap::Butt }
25 }
26 pub fn round(width: f32) -> Stroke {
27 Stroke { width, cap: Cap::Round }
28 }
29}
30
31#[derive(Clone, Debug)]
32pub enum Paint {
33 Solid(Color),
34 Linear { p0: Point, p1: Point, stops: Vec<(f32, Color)> },
35 Radial { c: Point, r: f32, stops: Vec<(f32, Color)> },
36}
37
38impl Paint {
39 pub fn solid(c: Color) -> Paint {
40 Paint::Solid(c)
41 }
42 pub fn vgrad(r: Rect, top: Color, bottom: Color) -> Paint {
43 Paint::Linear { p0: pt(r.x0, r.y0), p1: pt(r.x0, r.y1), stops: vec![(0.0, top), (1.0, bottom)] }
44 }
45 pub fn hgrad(r: Rect, left: Color, right: Color) -> Paint {
46 Paint::Linear { p0: pt(r.x0, r.y0), p1: pt(r.x1, r.y0), stops: vec![(0.0, left), (1.0, right)] }
47 }
48 pub fn dgrad(r: Rect, a: Color, b: Color) -> Paint {
49 Paint::Linear { p0: pt(r.x0, r.y0), p1: pt(r.x1, r.y1), stops: vec![(0.0, a), (1.0, b)] }
50 }
51 pub fn glow(c: Point, r: f32, inner: Color, outer: Color) -> Paint {
52 Paint::Radial { c, r, stops: vec![(0.0, inner), (1.0, outer)] }
53 }
54}
55
56fn build_lut(stops: &[(f32, Color)]) -> [u32; 256] {
57 let mut lut = [0u32; 256];
58 if stops.is_empty() {
59 return lut;
60 }
61 for (i, slot) in lut.iter_mut().enumerate() {
62 let t = i as f32 / 255.0;
63 let mut col = stops[0].1;
64 if t >= stops[stops.len() - 1].0 {
65 col = stops[stops.len() - 1].1;
66 } else if t > stops[0].0 {
67 for w in stops.windows(2) {
68 let (t0, c0) = w[0];
69 let (t1, c1) = w[1];
70 if t >= t0 && t <= t1 {
71 col = c0.lerp(c1, (t - t0) / (t1 - t0).max(1e-6));
72 break;
73 }
74 }
75 }
76 *slot = col.premul();
77 }
78 lut
79}
80
81enum Shader {
82 Solid(u32),
83 Linear { ox: f32, oy: f32, dx: f32, dy: f32, lut: Box<[u32; 256]>, opaque: bool },
84 Radial { cx: f32, cy: f32, inv_r: f32, lut: Box<[u32; 256]>, opaque: bool },
85}
86
87impl Shader {
88 fn build(paint: &Paint, xf: &Transform) -> Shader {
89 match paint {
90 Paint::Solid(c) => Shader::Solid(c.premul()),
91 Paint::Linear { p0, p1, stops } => {
92 let a = xf.apply(*p0);
93 let b = xf.apply(*p1);
94 let d = b - a;
95 let len2 = d.len_sq().max(1e-6);
96 let lut = build_lut(stops);
97 let opaque = lut.iter().all(|v| v >> 24 == 255);
98 Shader::Linear { ox: a.x, oy: a.y, dx: d.x / len2, dy: d.y / len2, lut: Box::new(lut), opaque }
99 }
100 Paint::Radial { c, r, stops } => {
101 let cc = xf.apply(*c);
102 let rr = (r * xf.scale_factor()).max(1e-4);
103 let lut = build_lut(stops);
104 let opaque = lut.iter().all(|v| v >> 24 == 255);
105 Shader::Radial { cx: cc.x, cy: cc.y, inv_r: 1.0 / rr, lut: Box::new(lut), opaque }
106 }
107 }
108 }
109
110}
111
112#[inline]
113fn cov_u32(c: f32) -> u32 {
114 (c * 255.0 + 0.5) as u32
115}
116
117pub struct Layer {
118 pub x: i32,
119 pub y: i32,
120 pub w: usize,
121 pub h: usize,
122 pub scale: usize,
123 pub px: Vec<u32>,
124}
125
126impl Layer {
127 #[inline]
128 pub fn sample(&self, dx: usize, dy: usize) -> u32 {
129 if self.w == 0 || self.h == 0 {
130 return 0;
131 }
132 if self.scale == 1 {
133 let lx = dx as i32 - self.x;
134 let ly = dy as i32 - self.y;
135 let lx = lx.clamp(0, self.w as i32 - 1) as usize;
136 let ly = ly.clamp(0, self.h as i32 - 1) as usize;
137 return self.px[ly * self.w + lx];
138 }
139 let k = self.scale as f32;
140 let fx = (dx as f32 + 0.5 - self.x as f32) / k - 0.5;
141 let fy = (dy as f32 + 0.5 - self.y as f32) / k - 0.5;
142 bilinear_u32(&self.px, self.w, self.h, fx, fy)
143 }
144}
145
146#[inline]
147fn bilinear_u32(px: &[u32], w: usize, h: usize, fx: f32, fy: f32) -> u32 {
148 let xf = fx.floor();
149 let yf = fy.floor();
150 let tx = ((fx - xf) * 255.0) as u32;
151 let ty = ((fy - yf) * 255.0) as u32;
152 let x0 = (xf as i32).clamp(0, w as i32 - 1) as usize;
153 let y0 = (yf as i32).clamp(0, h as i32 - 1) as usize;
154 let x1 = (x0 + 1).min(w - 1);
155 let y1 = (y0 + 1).min(h - 1);
156 let r0 = y0 * w;
157 let r1 = y1 * w;
158 let a = lerp_premul(px[r0 + x0], px[r0 + x1], tx);
159 let b = lerp_premul(px[r1 + x0], px[r1 + x1], tx);
160 lerp_premul(a, b, ty)
161}
162
163
164pub struct Canvas {
165 pub w: usize,
166 pub h: usize,
167 pub px: Vec<u32>,
168 ras: Rasterizer,
169 cov: Vec<f32>,
170 clip: Rect,
171 clip_stack: Vec<Rect>,
172 scratch: Path,
173 sh_ras: Rasterizer,
174 sh_mask: Vec<u8>,
175 sh_tmp: Vec<u8>,
176 sh_row: Vec<u8>,
177}
178
179impl Canvas {
180 pub fn new(w: usize, h: usize) -> Canvas {
181 Canvas {
182 w,
183 h,
184 px: vec![0; w * h],
185 ras: Rasterizer::new(w, h),
186 cov: Vec::new(),
187 clip: Rect::new(0.0, 0.0, w as f32, h as f32),
188 clip_stack: Vec::new(),
189 scratch: Path::new(),
190 sh_ras: Rasterizer::new(0, 0),
191 sh_mask: Vec::new(),
192 sh_tmp: Vec::new(),
193 sh_row: Vec::new(),
194 }
195 }
196
197 pub fn resize(&mut self, w: usize, h: usize) {
198 if w == self.w && h == self.h {
199 return;
200 }
201 self.w = w;
202 self.h = h;
203 self.px.clear();
204 self.px.resize(w * h, 0);
205 self.ras.resize(w, h);
206 self.clip = Rect::new(0.0, 0.0, w as f32, h as f32);
207 self.clip_stack.clear();
208 }
209
210 pub fn clear(&mut self, c: Color) {
211 let v = c.premul();
212 for p in &mut self.px {
213 *p = v;
214 }
215 }
216
217 pub fn full(&self) -> Rect {
218 Rect::new(0.0, 0.0, self.w as f32, self.h as f32)
219 }
220
221 pub fn clip(&self) -> Rect {
222 self.clip
223 }
224
225 pub fn push_clip(&mut self, r: Rect) {
226 self.clip_stack.push(self.clip);
227 let snapped = Rect::new(r.x0.round(), r.y0.round(), r.x1.round(), r.y1.round());
228 self.clip = self.clip.intersect(snapped);
229 }
230
231 pub fn pop_clip(&mut self) {
232 if let Some(r) = self.clip_stack.pop() {
233 self.clip = r;
234 }
235 }
236
237 fn clip_bounds(&self) -> (usize, usize, usize, usize) {
238 let c = self.clip;
239 let x0 = (c.x0.max(0.0) as usize).min(self.w);
240 let y0 = (c.y0.max(0.0) as usize).min(self.h);
241 let x1 = (c.x1.max(0.0) as usize).min(self.w);
242 let y1 = (c.y1.max(0.0) as usize).min(self.h);
243 (x0, y0, x1.max(x0), y1.max(y0))
244 }
245
246 pub fn fill(&mut self, path: &Path, xf: &Transform, paint: &Paint) {
247 if self.clip.is_empty() {
248 return;
249 }
250 self.ras.reset();
251 {
252 let ras = &mut self.ras;
253 path.flatten(xf, TOL, |a, b| ras.line(a, b));
254 }
255 let shader = Shader::build(paint, xf);
256 self.composite(&shader);
257 }
258
259 fn composite(&mut self, shader: &Shader) {
260 let (cx0, cy0, cx1, cy1) = self.clip_bounds();
261 let (ras, px, cov, w) = (&self.ras, &mut self.px, &mut self.cov, self.w);
262 ras.for_rows(cov, |y, xs, covs| {
263 if y < cy0 || y >= cy1 {
264 return;
265 }
266 let lo = xs.max(cx0);
267 let hi = (xs + covs.len()).min(cx1);
268 if hi <= lo {
269 return;
270 }
271 let row = &mut px[y * w + lo..y * w + hi];
272 let cs = &covs[lo - xs..hi - xs];
273 match shader {
274 Shader::Solid(v) => simd::blend_solid_cov(row, *v, cs),
275 Shader::Linear { ox, oy, dx, dy, lut, opaque } => {
276 let t0 = (lo as f32 + 0.5 - ox) * dx + (y as f32 + 0.5 - oy) * dy;
277 simd::blend_lut_cov(row, lut, t0, *dx, cs, *opaque);
278 }
279 Shader::Radial { cx, cy, inv_r, lut, opaque } => {
280 let py = y as f32 + 0.5 - cy;
281 simd::blend_radial_cov(row, lut, lo as f32 + 0.5 - cx, py * py, *inv_r, cs, *opaque);
282 }
283 }
284 });
285 }
286
287 pub fn fill_layer(&mut self, path: &Path, xf: &Transform, layer: &Layer, tint: Color) {
288 if self.clip.is_empty() {
289 return;
290 }
291 self.ras.reset();
292 {
293 let ras = &mut self.ras;
294 path.flatten(xf, TOL, |a, b| ras.line(a, b));
295 }
296 let (cx0, cy0, cx1, cy1) = self.clip_bounds();
297 let tint_p = tint.premul();
298 let (ras, px, cov, w) = (&self.ras, &mut self.px, &mut self.cov, self.w);
299 ras.for_rows(cov, |y, xs, covs| {
300 if y < cy0 || y >= cy1 {
301 return;
302 }
303 let lo = xs.max(cx0);
304 let hi = (xs + covs.len()).min(cx1);
305 if hi <= lo {
306 return;
307 }
308 let row = &mut px[y * w + lo..y * w + hi];
309 let cs = &covs[lo - xs..hi - xs];
310 for (i, (d, &c)) in row.iter_mut().zip(cs).enumerate() {
311 if c <= simd::ZERO {
312 continue;
313 }
314 let base = layer.sample(lo + i, y) | 0xff00_0000;
315 let v = blend_over(base, tint_p);
316 *d = if c >= simd::FULL { v } else { blend_over(*d, scale_premul(v, cov_u32(c))) };
317 }
318 });
319 }
320
321 pub fn stroke(&mut self, path: &Path, xf: &Transform, paint: &Paint, style: &Stroke) {
322 if self.clip.is_empty() || style.width <= 0.0 {
323 return;
324 }
325 let hw = (style.width * xf.scale_factor()) * 0.5;
326 let mut segs: Vec<(Vec<Point>, bool)> = Vec::new();
327 path.subpaths(xf, TOL, |pts, closed| segs.push((pts.to_vec(), closed)));
328 self.ras.reset();
329 {
330 let ras = &mut self.ras;
331 for (pts, closed) in &segs {
332 emit_stroke(ras, pts, *closed, hw, style.cap);
333 }
334 }
335 let shader = Shader::build(paint, xf);
336 self.composite(&shader);
337 }
338
339 pub fn fill_rect(&mut self, r: Rect, c: Color) {
340 let mut p = core::mem::take(&mut self.scratch);
341 p.clear();
342 p.rect(r);
343 self.fill(&p, &IDENTITY, &Paint::Solid(c));
344 self.scratch = p;
345 }
346
347 pub fn fill_rrect(&mut self, r: Rect, radius: f32, paint: &Paint) {
348 let mut p = core::mem::take(&mut self.scratch);
349 p.clear();
350 p.rrect(r, radius);
351 self.fill(&p, &IDENTITY, paint);
352 self.scratch = p;
353 }
354
355 pub fn stroke_rrect(&mut self, r: Rect, radius: f32, c: Color, width: f32) {
356 let mut p = core::mem::take(&mut self.scratch);
357 p.clear();
358 p.rrect(r, radius);
359 self.stroke(&p, &IDENTITY, &Paint::Solid(c), &Stroke::new(width));
360 self.scratch = p;
361 }
362
363 pub fn fill_circle(&mut self, c: Point, r: f32, paint: &Paint) {
364 let mut p = core::mem::take(&mut self.scratch);
365 p.clear();
366 p.circle(c, r);
367 self.fill(&p, &IDENTITY, paint);
368 self.scratch = p;
369 }
370
371 /// Blits an 8-bit coverage mask tinted with `color`. This is how text lands on
372 /// the canvas: the glyph cache owns the mask, the canvas owns the clipping.
373 pub fn blit_mask(&mut self, x: i32, y: i32, w: usize, h: usize, mask: &[u8], color: Color) {
374 if w == 0 || h == 0 || color.a == 0 || mask.len() < w * h {
375 return;
376 }
377 let (cx0, cy0, cx1, cy1) = self.clip_bounds();
378 let sx = (cx0 as i64 - x as i64).max(0) as usize;
379 let sy = (cy0 as i64 - y as i64).max(0) as usize;
380 if sx >= w || sy >= h {
381 return;
382 }
383 let ex = ((cx1 as i64 - x as i64).max(0) as usize).min(w);
384 let ey = ((cy1 as i64 - y as i64).max(0) as usize).min(h);
385 if ex <= sx || ey <= sy {
386 return;
387 }
388 let cp = color.premul();
389 let n = ex - sx;
390 for row in sy..ey {
391 let dy = (y + row as i32) as usize;
392 let dx = (x + sx as i32) as usize;
393 let base = dy * self.w + dx;
394 simd::blend_solid_alpha(
395 &mut self.px[base..base + n],
396 cp,
397 &mask[row * w + sx..row * w + ex],
398 );
399 }
400 }
401
402 pub fn snapshot(&self, r: Rect) -> Layer {
403 let x0 = (r.x0.floor() as i64).clamp(0, self.w as i64) as usize;
404 let y0 = (r.y0.floor() as i64).clamp(0, self.h as i64) as usize;
405 let x1 = (r.x1.ceil() as i64).clamp(0, self.w as i64) as usize;
406 let y1 = (r.y1.ceil() as i64).clamp(0, self.h as i64) as usize;
407 let (lw, lh) = (x1.saturating_sub(x0), y1.saturating_sub(y0));
408 let mut px = vec![0u32; lw * lh];
409 for y in 0..lh {
410 let src = (y0 + y) * self.w + x0;
411 px[y * lw..(y + 1) * lw].copy_from_slice(&self.px[src..src + lw]);
412 }
413 Layer { x: x0 as i32, y: y0 as i32, w: lw, h: lh, scale: 1, px }
414 }
415
416 pub fn blurred_layer(&self, r: Rect, sigma: f32) -> Layer {
417 let k = ((sigma / 3.0).round() as usize).clamp(1, 8);
418 let pad = (sigma * 2.5).ceil();
419 let x0 = ((r.x0 - pad).floor() as i64).clamp(0, self.w as i64) as usize;
420 let y0 = ((r.y0 - pad).floor() as i64).clamp(0, self.h as i64) as usize;
421 let x1 = ((r.x1 + pad).ceil() as i64).clamp(0, self.w as i64) as usize;
422 let y1 = ((r.y1 + pad).ceil() as i64).clamp(0, self.h as i64) as usize;
423 let (rw, rh) = (x1.saturating_sub(x0), y1.saturating_sub(y0));
424 if rw == 0 || rh == 0 {
425 return Layer { x: 0, y: 0, w: 0, h: 0, scale: 1, px: Vec::new() };
426 }
427 let lw = rw.div_ceil(k);
428 let lh = rh.div_ceil(k);
429 let mut px = vec![0u32; lw * lh];
430 for j in 0..lh {
431 for i in 0..lw {
432 let mut acc = [0u32; 4];
433 let mut n = 0u32;
434 for sy in 0..k {
435 let yy = y0 + j * k + sy;
436 if yy >= y1 {
437 break;
438 }
439 for sx in 0..k {
440 let xx = x0 + i * k + sx;
441 if xx >= x1 {
442 break;
443 }
444 let v = self.px[yy * self.w + xx];
445 for (ch, a) in acc.iter_mut().enumerate() {
446 *a += (v >> (ch * 8)) & 0xff;
447 }
448 n += 1;
449 }
450 }
451 if n > 0 {
452 let mut o = 0u32;
453 for (ch, a) in acc.iter().enumerate() {
454 o |= (a / n) << (ch * 8);
455 }
456 px[j * lw + i] = o;
457 }
458 }
459 }
460 blur_premul(&mut px, lw, lh, sigma / k as f32);
461 Layer { x: x0 as i32, y: y0 as i32, w: lw, h: lh, scale: k, px }
462 }
463
464 pub fn shadow(&mut self, path: &Path, xf: &Transform, sigma: f32, color: Color, offset: Point) {
465 self.shadow_occluded(path, xf, sigma, color, offset, Rect::new(0.0, 0.0, 0.0, 0.0));
466 }
467
468 pub fn shadow_occluded(
469 &mut self,
470 path: &Path,
471 xf: &Transform,
472 sigma: f32,
473 color: Color,
474 offset: Point,
475 occluder: Rect,
476 ) {
477 if self.clip.is_empty() || color.a == 0 {
478 return;
479 }
480 let k = ((sigma / 3.0).round() as usize).clamp(1, 8) as f32;
481 let b = path.transform(xf).bounds();
482 let pad = sigma * 3.0 + 2.0;
483 let ox = (b.x0 + offset.x - pad).floor();
484 let oy = (b.y0 + offset.y - pad).floor();
485 let dev_w = (b.x1 + offset.x + pad).ceil() - ox;
486 let dev_h = (b.y1 + offset.y + pad).ceil() - oy;
487 if dev_w <= 0.0 || dev_h <= 0.0 {
488 return;
489 }
490 let mw = (dev_w / k).ceil() as usize + 1;
491 let mh = (dev_h / k).ceil() as usize + 1;
492 if mw == 0 || mh == 0 || mw > 4096 || mh > 4096 {
493 return;
494 }
495 self.sh_ras.resize(mw, mh);
496 self.sh_ras.reset();
497 let total = xf
498 .then(&Transform::translate(offset.x - ox, offset.y - oy))
499 .then(&Transform::scale(1.0 / k, 1.0 / k));
500 {
501 let ras = &mut self.sh_ras;
502 path.flatten(&total, TOL / 2.0, |a, bb| ras.line(a, bb));
503 }
504 self.sh_mask.clear();
505 self.sh_mask.resize(mw * mh, 0);
506 {
507 let mask = &mut self.sh_mask;
508 self.sh_ras.spans(|x, y, c| mask[y * mw + x] = (c * 255.0 + 0.5) as u8);
509 }
510 let s = sigma / k;
511 if s > 0.3 {
512 self.sh_tmp.clear();
513 self.sh_tmp.resize(mw * mh, 0);
514 box_blur(&mut self.sh_mask, &mut self.sh_tmp, mw, mh, s);
515 }
516
517 let (mut bx0, mut by0, mut bx1, mut by1) = (mw, mh, 0usize, 0usize);
518 for y in 0..mh {
519 for x in 0..mw {
520 if self.sh_mask[y * mw + x] > 1 {
521 bx0 = bx0.min(x);
522 by0 = by0.min(y);
523 bx1 = bx1.max(x + 1);
524 by1 = by1.max(y + 1);
525 }
526 }
527 }
528 if bx1 <= bx0 || by1 <= by0 {
529 return;
530 }
531
532 let (cx0, cy0, cx1, cy1) = self.clip_bounds();
533 let dx0 = ((ox + bx0 as f32 * k).floor() as i64).clamp(cx0 as i64, cx1 as i64) as usize;
534 let dy0 = ((oy + by0 as f32 * k).floor() as i64).clamp(cy0 as i64, cy1 as i64) as usize;
535 let dx1 = ((ox + bx1 as f32 * k).ceil() as i64).clamp(cx0 as i64, cx1 as i64) as usize;
536 let dy1 = ((oy + by1 as f32 * k).ceil() as i64).clamp(cy0 as i64, cy1 as i64) as usize;
537 let cp = color.premul();
538 let inv = 1.0 / k;
539 let fx0 = (dx0 as f32 + 0.5 - ox) * inv - 0.5;
540 let step = (inv * 65536.0) as i32;
541 let width = dx1 - dx0;
542 self.sh_row.clear();
543 self.sh_row.resize(width, 0);
544 let occ = occluder.intersect(Rect::new(dx0 as f32, dy0 as f32, dx1 as f32, dy1 as f32));
545 let (ox0, ox1) = if occ.is_empty() {
546 (0usize, 0usize)
547 } else {
548 ((occ.x0 as usize - dx0), (occ.x1 as usize - dx0))
549 };
550 for y in dy0..dy1 {
551 let fy = (y as f32 + 0.5 - oy) * inv - 0.5;
552 let yfl = fy.floor();
553 let ty = (((fy - yfl) * 256.0) as u32).min(256);
554 let y0i = (yfl as i32).clamp(0, mh as i32 - 1) as usize;
555 let y1i = (y0i + 1).min(mh - 1);
556 let m0 = &self.sh_mask[y0i * mw..y0i * mw + mw];
557 let m1 = &self.sh_mask[y1i * mw..y1i * mw + mw];
558 let occluded = !occ.is_empty() && (y as f32) >= occ.y0 && (y as f32) < occ.y1;
559 let mut fixed = (fx0 * 65536.0) as i32;
560 let last = mw as i32 - 1;
561 for a in self.sh_row[..width].iter_mut() {
562 let xi = fixed >> 16;
563 let tx = ((fixed >> 8) & 0xff) as u32;
564 let x0i = xi.clamp(0, last) as usize;
565 let x1i = (xi + 1).clamp(0, last) as usize;
566 let p = m0[x0i] as u32 * (256 - tx) + m0[x1i] as u32 * tx;
567 let q = m1[x0i] as u32 * (256 - tx) + m1[x1i] as u32 * tx;
568 *a = ((((p >> 8) * (256 - ty) + (q >> 8) * ty) >> 8) as u8).min(255);
569 fixed += step;
570 }
571 let base = y * self.w + dx0;
572 if occluded {
573 if ox0 > 0 {
574 simd::blend_solid_alpha(
575 &mut self.px[base..base + ox0],
576 cp,
577 &self.sh_row[..ox0],
578 );
579 }
580 if ox1 < width {
581 simd::blend_solid_alpha(
582 &mut self.px[base + ox1..base + width],
583 cp,
584 &self.sh_row[ox1..width],
585 );
586 }
587 } else {
588 simd::blend_solid_alpha(
589 &mut self.px[base..base + width],
590 cp,
591 &self.sh_row[..width],
592 );
593 }
594 }
595 }
596
597 pub fn blur_region(&mut self, r: Rect, sigma: f32) {
598 let x0 = (r.x0.floor() as i64).clamp(0, self.w as i64) as usize;
599 let y0 = (r.y0.floor() as i64).clamp(0, self.h as i64) as usize;
600 let x1 = (r.x1.ceil() as i64).clamp(0, self.w as i64) as usize;
601 let y1 = (r.y1.ceil() as i64).clamp(0, self.h as i64) as usize;
602 let (lw, lh) = (x1.saturating_sub(x0), y1.saturating_sub(y0));
603 if lw == 0 || lh == 0 {
604 return;
605 }
606 let mut buf = vec![0u32; lw * lh];
607 for y in 0..lh {
608 let src = (y0 + y) * self.w + x0;
609 buf[y * lw..(y + 1) * lw].copy_from_slice(&self.px[src..src + lw]);
610 }
611 blur_premul(&mut buf, lw, lh, sigma);
612 for y in 0..lh {
613 let dst = (y0 + y) * self.w + x0;
614 self.px[dst..dst + lw].copy_from_slice(&buf[y * lw..(y + 1) * lw]);
615 }
616 }
617
618 pub fn to_rgba(&self) -> Vec<u8> {
619 let mut out = vec![0u8; self.w * self.h * 4];
620 for (i, v) in self.px.iter().enumerate() {
621 let c = Color::from_premul(*v);
622 out[i * 4] = c.r;
623 out[i * 4 + 1] = c.g;
624 out[i * 4 + 2] = c.b;
625 out[i * 4 + 3] = c.a;
626 }
627 out
628 }
629
630 pub fn to_png(&self) -> Vec<u8> {
631 crate::png::encode_rgba(self.w, self.h, &self.to_rgba())
632 }
633}
634
635fn emit_stroke(ras: &mut Rasterizer, pts: &[Point], closed: bool, hw: f32, cap: Cap) {
636 if hw <= 0.0 {
637 return;
638 }
639 if pts.len() < 2 {
640 if cap == Cap::Round && pts.len() == 1 {
641 emit_disc(ras, pts[0], hw);
642 }
643 return;
644 }
645 let n = pts.len();
646 let last = if closed { n } else { n - 1 };
647 for i in 0..last {
648 let a = pts[i];
649 let b = pts[(i + 1) % n];
650 let d = b - a;
651 if d.len_sq() < 1e-12 {
652 continue;
653 }
654 let dir = d.norm();
655 let (mut a, mut b) = (a, b);
656 if !closed && cap == Cap::Square {
657 if i == 0 {
658 a = a - dir * hw;
659 }
660 if i == last - 1 {
661 b = b + dir * hw;
662 }
663 }
664 let nrm = dir.perp() * hw;
665 let p0 = a + nrm;
666 let p1 = b + nrm;
667 let p2 = b - nrm;
668 let p3 = a - nrm;
669 ras.line(p0, p1);
670 ras.line(p1, p2);
671 ras.line(p2, p3);
672 ras.line(p3, p0);
673 }
674 let joint_start = if closed { 0 } else { 1 };
675 let joint_end = if closed { n } else { n - 1 };
676 if hw > 0.6 {
677 for p in pts.iter().take(joint_end).skip(joint_start) {
678 emit_disc(ras, *p, hw);
679 }
680 }
681 if !closed && cap == Cap::Round {
682 emit_disc(ras, pts[0], hw);
683 emit_disc(ras, pts[n - 1], hw);
684 }
685}
686
687fn emit_disc(ras: &mut Rasterizer, c: Point, r: f32) {
688 if r < 0.35 {
689 return;
690 }
691 let steps = ((r * 2.2) as usize).clamp(6, 48);
692 let mut prev = pt(c.x + r, c.y);
693 for i in 1..=steps {
694 let a = -(i as f32) / steps as f32 * core::f32::consts::TAU;
695 let p = pt(c.x + r * a.cos(), c.y + r * a.sin());
696 ras.line(prev, p);
697 prev = p;
698 }
699}
700
701fn box_sizes(sigma: f32) -> [usize; 3] {
702 const N: f32 = 3.0;
703 let s = sigma.max(0.0);
704 let ideal = (12.0 * s * s / N + 1.0).sqrt();
705 let mut wl = ideal.floor() as i32;
706 if wl % 2 == 0 {
707 wl -= 1;
708 }
709 let wl = wl.max(1);
710 let wu = wl + 2;
711 let m = ((12.0 * s * s - N * (wl * wl) as f32 - 4.0 * N * wl as f32 - 3.0 * N)
712 / (-4.0 * wl as f32 - 4.0))
713 .round() as i32;
714 let mut out = [0usize; 3];
715 for (i, o) in out.iter_mut().enumerate() {
716 let w = if (i as i32) < m { wl } else { wu };
717 *o = ((w - 1) / 2).max(0) as usize;
718 }
719 out
720}
721
722fn box_blur(mask: &mut [u8], tmp: &mut [u8], w: usize, h: usize, sigma: f32) {
723 for r in box_sizes(sigma) {
724 if r == 0 {
725 continue;
726 }
727 blur_h_u8(mask, tmp, w, h, r);
728 blur_v_u8(tmp, mask, w, h, r);
729 }
730}
731
732fn blur_h_u8(src: &[u8], dst: &mut [u8], w: usize, h: usize, r: usize) {
733 let win = (2 * r + 1) as u32;
734 let mul = (1u32 << 16) / win;
735 for y in 0..h {
736 let s = &src[y * w..y * w + w];
737 let d = &mut dst[y * w..y * w + w];
738 let mut sum = s[0] as u32 * (r + 1) as u32;
739 for x in 1..=r.min(w - 1) {
740 sum += s[x] as u32;
741 }
742 if r >= w {
743 sum += s[w - 1] as u32 * (r + 1 - w) as u32;
744 }
745 for x in 0..w {
746 d[x] = ((sum * mul + (1 << 15)) >> 16) as u8;
747 sum += s[(x + r + 1).min(w - 1)] as u32;
748 sum -= s[x.saturating_sub(r)] as u32;
749 }
750 }
751}
752
753fn blur_v_u8(src: &[u8], dst: &mut [u8], w: usize, h: usize, r: usize) {
754 let win = (2 * r + 1) as u32;
755 let mul = (1u32 << 16) / win;
756 let mut col = vec![0u32; w];
757 for (x, c) in col.iter_mut().enumerate() {
758 *c = src[x] as u32 * (r + 1) as u32;
759 }
760 for y in 1..=r.min(h - 1) {
761 for x in 0..w {
762 col[x] += src[y * w + x] as u32;
763 }
764 }
765 if r >= h {
766 for x in 0..w {
767 col[x] += src[(h - 1) * w + x] as u32 * (r + 1 - h) as u32;
768 }
769 }
770 for y in 0..h {
771 let add = (y + r + 1).min(h - 1) * w;
772 let sub = y.saturating_sub(r) * w;
773 for x in 0..w {
774 dst[y * w + x] = ((col[x] * mul + (1 << 15)) >> 16) as u8;
775 col[x] += src[add + x] as u32;
776 col[x] -= src[sub + x] as u32;
777 }
778 }
779}
780
781pub fn blur_premul(buf: &mut [u32], w: usize, h: usize, sigma: f32) {
782 if w == 0 || h == 0 || sigma <= 0.0 {
783 return;
784 }
785 let mut tmp = vec![0u32; w * h];
786 for r in box_sizes(sigma) {
787 if r == 0 {
788 continue;
789 }
790 blur_h_u32(buf, &mut tmp, w, h, r);
791 blur_v_u32(&tmp, buf, w, h, r);
792 }
793}
794
795#[inline]
796fn split(v: u32) -> (u32, u32) {
797 (v & 0x00ff_00ff, (v >> 8) & 0x00ff_00ff)
798}
799
800#[inline]
801fn joinc(lo: u32, hi: u32) -> u32 {
802 (lo & 0x00ff_00ff) | ((hi & 0x00ff_00ff) << 8)
803}
804
805fn blur_h_u32(src: &[u32], dst: &mut [u32], w: usize, h: usize, r: usize) {
806 let win = (2 * r + 1) as u32;
807 let mul = (1u32 << 14) / win;
808 for y in 0..h {
809 let s = &src[y * w..y * w + w];
810 let d = &mut dst[y * w..y * w + w];
811 let (a, b) = split(s[0]);
812 let mut lo = a * (r + 1) as u32;
813 let mut hi = b * (r + 1) as u32;
814 for x in 1..=r.min(w - 1) {
815 let (a, b) = split(s[x]);
816 lo += a;
817 hi += b;
818 }
819 if r >= w {
820 let (a, b) = split(s[w - 1]);
821 lo += a * (r + 1 - w) as u32;
822 hi += b * (r + 1 - w) as u32;
823 }
824 for x in 0..w {
825 let ol = ((lo >> 16) * mul >> 14) << 16 | (((lo & 0xffff) * mul) >> 14);
826 let oh = ((hi >> 16) * mul >> 14) << 16 | (((hi & 0xffff) * mul) >> 14);
827 d[x] = joinc(ol, oh);
828 let (a1, b1) = split(s[(x + r + 1).min(w - 1)]);
829 let (a2, b2) = split(s[x.saturating_sub(r)]);
830 lo = lo + a1 - a2;
831 hi = hi + b1 - b2;
832 }
833 }
834}
835
836fn blur_v_u32(src: &[u32], dst: &mut [u32], w: usize, h: usize, r: usize) {
837 let win = (2 * r + 1) as u32;
838 let mul = (1u32 << 14) / win;
839 let mut clo = vec![0u32; w];
840 let mut chi = vec![0u32; w];
841 for x in 0..w {
842 let (a, b) = split(src[x]);
843 clo[x] = a * (r + 1) as u32;
844 chi[x] = b * (r + 1) as u32;
845 }
846 for y in 1..=r.min(h - 1) {
847 for x in 0..w {
848 let (a, b) = split(src[y * w + x]);
849 clo[x] += a;
850 chi[x] += b;
851 }
852 }
853 if r >= h {
854 for x in 0..w {
855 let (a, b) = split(src[(h - 1) * w + x]);
856 clo[x] += a * (r + 1 - h) as u32;
857 chi[x] += b * (r + 1 - h) as u32;
858 }
859 }
860 for y in 0..h {
861 let add = (y + r + 1).min(h - 1) * w;
862 let sub = y.saturating_sub(r) * w;
863 for x in 0..w {
864 let lo = clo[x];
865 let hi = chi[x];
866 let ol = ((lo >> 16) * mul >> 14) << 16 | (((lo & 0xffff) * mul) >> 14);
867 let oh = ((hi >> 16) * mul >> 14) << 16 | (((hi & 0xffff) * mul) >> 14);
868 dst[y * w + x] = joinc(ol, oh);
869 let (a1, b1) = split(src[add + x]);
870 let (a2, b2) = split(src[sub + x]);
871 clo[x] = lo + a1 - a2;
872 chi[x] = hi + b1 - b2;
873 }
874 }
875}
876
877#[inline]
878pub fn lerp_u32(a: u32, b: u32, t: f32) -> u32 {
879 lerp_premul(a, b, (t.clamp(0.0, 1.0) * 255.0 + 0.5) as u32)
880}
881
882#[inline]
883pub fn shade(a: Color, b: Color, t: f32) -> Color {
884 a.lerp(b, t)
885}