1use cosmic_text::{FontSystem, SwashCache, SwashContent, SwashImage};
10use crabshell_ui::{
11 CaptureView, Color, Content, Gradient, GradientShape, IconView, ImageView, InputView, NodeView,
12 Rect, Shadow, Size, Style, TextSystem, TextView, WindowView,
13};
14use tiny_skia::{
15 FillRule, GradientStop, LinearGradient, Mask, Paint, PathBuilder, Pixmap, PixmapMut, PixmapPaint,
16 RadialGradient, Shader, SpreadMode, Transform,
17};
18
19use crate::damage::{BufferAge, DamageHistory, PixelRect};
20use crate::icons::Icons;
21
22const TILE: u32 = 64;
27
28const GLYPH_CACHE_LIMIT: usize = 2048;
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum PixelFormat {
36 Argb8888,
38 Abgr8888,
40}
41
42pub struct Canvas<'a> {
44 pub pixels: &'a mut [u8],
46 pub width: u32,
50 pub height: u32,
52 pub scale: f32,
54 pub format: PixelFormat,
56 pub age: &'a mut BufferAge,
58}
59
60pub struct Renderer {
63 glyphs: SwashCache,
64 icons: Icons,
65 scratch: Vec<u8>,
68 work: Work,
69}
70
71#[derive(Clone, Copy, Default)]
74#[cfg_attr(not(test), allow(dead_code, reason = "only tests read it"))]
75struct Work {
76 layer_pixels: u64,
78 nodes: u64,
80}
81
82impl Renderer {
83 pub fn new() -> Renderer {
86 Renderer {
87 glyphs: SwashCache::new(),
88 icons: Icons::new(),
89 scratch: Vec::new(),
90 work: Work::default(),
91 }
92 }
93
94 #[cfg(test)]
96 pub(crate) fn cached_glyphs(&self) -> usize {
97 self.glyphs.image_cache.len()
98 }
99
100 #[cfg(test)]
103 pub(crate) fn work(&self) -> (u64, u64) {
104 (self.work.layer_pixels, self.work.nodes)
105 }
106
107 #[cfg(test)]
110 pub(crate) fn with_icon_dirs(bases: Vec<std::path::PathBuf>) -> Renderer {
111 Renderer {
112 icons: Icons::with_themes(crate::theme::Themes::new(bases)),
113 ..Renderer::new()
114 }
115 }
116
117 pub fn set_icon_theme(&mut self, theme: impl Into<String>) {
119 self.icons.set_theme(theme.into());
120 }
121
122 #[cfg(test)]
124 pub(crate) fn cached_icons(&self) -> usize {
125 self.icons.cached()
126 }
127
128 #[cfg(test)]
130 pub(crate) fn rasterized_icons(&self) -> usize {
131 self.icons.rasterized()
132 }
133
134 pub fn forget_captures(&mut self, gone: &[u64]) {
138 self.icons.forget_captures(gone);
139 }
140
141 #[cfg(test)]
143 pub(crate) fn cached_captures(&self) -> usize {
144 self.icons.captures()
145 }
146
147 pub fn draw(
165 &mut self,
166 view: &WindowView<'_>,
167 damage: &[Rect],
168 text: &mut TextSystem,
169 history: &mut DamageHistory,
170 canvas: Canvas<'_>,
171 ) -> Result<Vec<PixelRect>, CanvasError> {
172 let Canvas {
173 pixels,
174 width,
175 height,
176 scale,
177 format,
178 age,
179 } = canvas;
180 if pixels.len() as u64 != u64::from(width) * u64::from(height) * 4
181 || !(scale.is_finite() && scale > 0.0)
182 {
183 return Err(CanvasError {
184 width,
185 height,
186 scale,
187 bytes: pixels.len(),
188 });
189 }
190 let plan = history.plan(damage, scale, width, height, age);
191 let mut tiles: Vec<(u32, u32)> = Vec::new();
192 for area in plan.repaint.iter().filter(|area| area.area() > 0) {
193 for row in area.y / TILE..=(area.y + area.height - 1) / TILE {
194 tiles
195 .extend((area.x / TILE..=(area.x + area.width - 1) / TILE).map(|column| (row, column)));
196 }
197 }
198 tiles.sort_unstable();
199 tiles.dedup();
200 for (row, column) in tiles {
201 let (x, y) = (column * TILE, row * TILE);
202 let size = (width.saturating_sub(x), height.saturating_sub(y));
203 let area = PixelRect::new(x, y, TILE.min(size.0), TILE.min(size.1));
204 self.scratch.clear();
205 self.scratch.resize(area.area() as usize * 4, 0);
206 let Some(mut pixmap) = PixmapMut::from_bytes(&mut self.scratch, area.width, area.height)
207 else {
208 continue;
209 };
210 let mut painter = Painter {
211 grid: Grid {
212 scale,
213 window: view.size(),
214 width,
215 height,
216 left: area.x as f32,
217 top: area.y as f32,
218 },
219 glyphs: &mut self.glyphs,
220 icons: &mut self.icons,
221 fonts: text.font_system(),
222 work: &mut self.work,
223 };
224 painter.node(&mut pixmap, view.root());
225 copy(&self.scratch, area, pixels, width, format);
226 }
227 self.icons.end_frame();
228 if self.glyphs.image_cache.len() > GLYPH_CACHE_LIMIT {
229 self.glyphs.image_cache.clear();
230 self.glyphs.outline_command_cache.clear();
231 }
232 Ok(plan.report)
233 }
234}
235
236#[derive(Clone, Copy, Debug, PartialEq)]
239pub struct CanvasError {
240 pub width: u32,
242 pub height: u32,
244 pub scale: f32,
246 pub bytes: usize,
248}
249
250impl std::fmt::Display for CanvasError {
251 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252 write!(
253 f,
254 "a {}x{} canvas at scale {} needs {} bytes, but has {}",
255 self.width,
256 self.height,
257 self.scale,
258 u64::from(self.width) * u64::from(self.height) * 4,
259 self.bytes
260 )
261 }
262}
263
264impl std::error::Error for CanvasError {}
265
266impl Default for Renderer {
267 fn default() -> Self {
268 Self::new()
269 }
270}
271
272fn copy(scratch: &[u8], area: PixelRect, pixels: &mut [u8], width: u32, format: PixelFormat) {
274 let stride = width as usize * 4;
275 let row_bytes = area.width as usize * 4;
276 for (row, source) in scratch.chunks_exact(row_bytes).enumerate() {
277 let start = (area.y as usize + row) * stride + area.x as usize * 4;
278 let target = &mut pixels[start..start + row_bytes];
279 match format {
280 PixelFormat::Abgr8888 => target.copy_from_slice(source),
281 PixelFormat::Argb8888 => {
282 let (to, _) = target.as_chunks_mut::<4>();
283 let (from, _) = source.as_chunks::<4>();
284 for (to, from) in to.iter_mut().zip(from) {
285 *to = [from[2], from[1], from[0], from[3]];
286 }
287 }
288 }
289 }
290}
291
292#[derive(Clone, Copy)]
294struct Grid {
295 scale: f32,
296 window: Size,
298 width: u32,
300 height: u32,
301 left: f32,
303 top: f32,
304}
305
306impl Grid {
307 fn x(&self, x: f32) -> f32 {
310 let pixel = if x >= self.window.width {
311 self.width as f32
312 } else {
313 (x * self.scale).round()
314 };
315 pixel - self.left
316 }
317
318 fn y(&self, y: f32) -> f32 {
320 let pixel = if y >= self.window.height {
321 self.height as f32
322 } else {
323 (y * self.scale).round()
324 };
325 pixel - self.top
326 }
327
328 fn cover(&self, rect: Rect, width: u32, height: u32) -> Option<(u32, u32, u32, u32)> {
332 if !(rect.width > 0.0 && rect.height > 0.0) {
333 return None;
334 }
335 let left = ((rect.x * self.scale).floor() - self.left - 1.0).max(0.0) as u32;
337 let top = ((rect.y * self.scale).floor() - self.top - 1.0).max(0.0) as u32;
338 let right = (((rect.right() * self.scale).ceil() - self.left + 1.0).max(0.0) as u32).min(width);
339 let bottom =
340 (((rect.bottom() * self.scale).ceil() - self.top + 1.0).max(0.0) as u32).min(height);
341 (left < right && top < bottom).then(|| (left, top, right - left, bottom - top))
342 }
343
344 fn moved(&self, x: u32, y: u32) -> Grid {
346 Grid {
347 left: self.left + x as f32,
348 top: self.top + y as f32,
349 ..*self
350 }
351 }
352
353 fn rect(&self, rect: Rect) -> Option<tiny_skia::Rect> {
356 tiny_skia::Rect::from_ltrb(
357 self.x(rect.x),
358 self.y(rect.y),
359 self.x(rect.right()),
360 self.y(rect.bottom()),
361 )
362 .filter(|rect| rect.width() >= 1.0 && rect.height() >= 1.0)
363 }
364}
365
366struct Painter<'a> {
368 grid: Grid,
369 glyphs: &'a mut SwashCache,
370 icons: &'a mut Icons,
371 fonts: &'a mut FontSystem,
372 work: &'a mut Work,
373}
374
375impl Painter<'_> {
376 fn node(&mut self, pixmap: &mut PixmapMut<'_>, node: NodeView<'_>) {
380 let Some((x, y, width, height)) =
382 self
383 .grid
384 .cover(node.bounds(), pixmap.width(), pixmap.height())
385 else {
386 return;
387 };
388 let style = node.style();
389 let opacity = style.opacity.unwrap_or(1.0);
390 let opacity = if opacity.is_nan() {
391 1.0
392 } else {
393 opacity.clamp(0.0, 1.0)
394 };
395 if opacity == 0.0 {
396 return;
397 }
398 if opacity < 1.0 {
399 let Some(mut layer) = Pixmap::new(width, height) else {
401 return;
402 };
403 self.work.layer_pixels += u64::from(layer.width()) * u64::from(layer.height());
404 let mut inner = Painter {
405 grid: self.grid.moved(x, y),
406 glyphs: &mut *self.glyphs,
407 icons: &mut *self.icons,
408 fonts: &mut *self.fonts,
409 work: &mut *self.work,
410 };
411 inner.node_contents(&mut layer.as_mut(), node, &style);
412 let paint = PixmapPaint {
413 opacity,
414 ..PixmapPaint::default()
415 };
416 let (x, y) = (x as i32, y as i32);
417 pixmap.draw_pixmap(x, y, layer.as_ref(), &paint, Transform::identity(), None);
418 } else {
419 self.node_contents(pixmap, node, &style);
420 }
421 }
422
423 fn node_contents(&mut self, pixmap: &mut PixmapMut<'_>, node: NodeView<'_>, style: &Style) {
424 self.work.nodes += 1;
425 if let Some(shadow) = style.shadow {
426 self.shadow(pixmap, node.rect(), style, &shadow);
427 }
428 self.draw_box(pixmap, node.rect(), style);
429 match node.content() {
430 Content::Text(text) => self.text(pixmap, &text),
431 Content::Icon(icon) => self.icon(pixmap, &icon),
432 Content::Image(image) => self.image(pixmap, &image),
433 Content::Capture(capture) => self.capture(pixmap, &capture),
434 Content::Input(input) => self.input(pixmap, &input),
435 Content::None => {}
436 }
437 if node.clips() {
438 self.clipped_children(pixmap, node, style);
439 } else {
440 for child in node.children() {
441 self.node(pixmap, child);
442 }
443 }
444 }
445
446 fn clipped_children(&mut self, pixmap: &mut PixmapMut<'_>, node: NodeView<'_>, style: &Style) {
451 let Some((area, radius)) = self.box_shape(node.rect(), style) else {
452 return;
453 };
454 let width = self.border_width(style);
455 let Some(inside) = tiny_skia::Rect::from_ltrb(
456 area.left() + width,
457 area.top() + width,
458 area.right() - width,
459 area.bottom() - width,
460 ) else {
461 return;
462 };
463 let radius = (radius - width).max(0.0);
464 self.through_mask(pixmap, node.rect(), (inside, radius), |painter, layer| {
465 for child in node.children() {
466 painter.node(layer, child);
467 }
468 });
469 }
470
471 fn through_mask(
474 &mut self,
475 pixmap: &mut PixmapMut<'_>,
476 cover: Rect,
477 (shape, radius): (tiny_skia::Rect, f32),
478 draw: impl FnOnce(&mut Painter<'_>, &mut PixmapMut<'_>),
479 ) {
480 if !visible(pixmap, shape) {
481 return;
482 }
483 let (Some(mut mask), Some((x, y, layer_width, layer_height))) = (
484 Mask::new(pixmap.width(), pixmap.height()),
485 self.grid.cover(cover, pixmap.width(), pixmap.height()),
486 ) else {
487 return;
488 };
489 let Some(mut layer) = Pixmap::new(layer_width, layer_height) else {
490 return;
491 };
492 self.work.layer_pixels += u64::from(layer_width) * u64::from(layer_height);
493 let mut inner = Painter {
494 grid: self.grid.moved(x, y),
495 glyphs: &mut *self.glyphs,
496 icons: &mut *self.icons,
497 fonts: &mut *self.fonts,
498 work: &mut *self.work,
499 };
500 draw(&mut inner, &mut layer.as_mut());
501 let mut path = PathBuilder::new();
502 rounded_rect(&mut path, shape, radius);
503 let Some(path) = path.finish() else {
504 return;
505 };
506 mask.fill_path(&path, FillRule::Winding, true, Transform::identity());
507 pixmap.draw_pixmap(
508 x as i32,
509 y as i32,
510 layer.as_ref(),
511 &PixmapPaint::default(),
512 Transform::identity(),
513 Some(&mask),
514 );
515 }
516
517 fn box_shape(&self, rect: Rect, style: &Style) -> Option<(tiny_skia::Rect, f32)> {
521 let area = self.grid.rect(rect)?;
522 let radius = (style.radius.unwrap_or(0.0) * self.grid.scale)
523 .clamp(0.0, area.width().min(area.height()) / 2.0);
524 let radius = if radius.is_nan() { 0.0 } else { radius };
525 Some((area, radius))
526 }
527
528 fn border_width(&self, style: &Style) -> f32 {
531 let Some(border) = style.border else {
532 return 0.0;
533 };
534 let width = (border.width * self.grid.scale).floor();
535 if border.width > 0.0 {
536 width.max(1.0)
537 } else {
538 width.max(0.0)
539 }
540 }
541
542 fn draw_box(&self, pixmap: &mut PixmapMut<'_>, rect: Rect, style: &Style) {
544 if style.background.is_none() && style.gradient.is_none() && style.border.is_none() {
545 return;
546 }
547 let Some((area, radius)) = self.box_shape(rect, style) else {
548 return;
549 };
550 if !visible(pixmap, area) {
551 return;
552 }
553
554 if let Some(color) = style.background {
555 let mut path = PathBuilder::new();
556 rounded_rect(&mut path, area, radius);
557 fill(pixmap, path, color, FillRule::Winding);
558 }
559 if let Some(gradient) = style.gradient {
560 let mut path = PathBuilder::new();
561 rounded_rect(&mut path, area, radius);
562 if let (Some(path), Some(shader)) = (path.finish(), self.shader(&gradient, area)) {
563 let paint = Paint {
564 shader,
565 anti_alias: true,
566 ..Paint::default()
567 };
568 pixmap.fill_path(
569 &path,
570 &paint,
571 FillRule::Winding,
572 Transform::identity(),
573 None,
574 );
575 }
576 }
577 if let Some(border) = style.border {
578 let width = self.border_width(style);
579 if width >= 1.0 {
580 let mut path = PathBuilder::new();
582 rounded_rect(&mut path, area, radius);
583 if let Some(inner) = tiny_skia::Rect::from_ltrb(
584 area.left() + width,
585 area.top() + width,
586 area.right() - width,
587 area.bottom() - width,
588 ) {
589 rounded_rect(&mut path, inner, (radius - width).max(0.0));
590 }
591 fill(pixmap, path, border.color, FillRule::EvenOdd);
592 }
593 }
594 }
595
596 fn shadow(&self, pixmap: &mut PixmapMut<'_>, rect: Rect, style: &Style, shadow: &Shadow) {
601 let Grid {
602 scale, left, top, ..
603 } = self.grid;
604 let numbers = [shadow.x, shadow.y, shadow.blur, shadow.spread];
605 if shadow.color.a == 0 || !numbers.iter().all(|n| n.is_finite()) {
606 return;
607 }
608 let Some((area, radius)) = self.box_shape(rect, style) else {
609 return;
610 };
611 let spread = shadow.spread * scale;
613 let Some(shape) = tiny_skia::Rect::from_ltrb(
614 area.left() + left + shadow.x * scale - spread,
615 area.top() + top + shadow.y * scale - spread,
616 area.right() + left + shadow.x * scale + spread,
617 area.bottom() + top + shadow.y * scale + spread,
618 ) else {
619 return;
620 };
621 let radius = if spread > 0.0 && radius < spread {
624 radius + spread * (1.0 + (radius / spread - 1.0).powi(3))
625 } else {
626 radius + spread
627 };
628 let radius = radius.clamp(0.0, shape.width().min(shape.height()) / 2.0);
629 let cut = if shadow.cut_out {
631 self.box_mask(pixmap, rect, style)
632 } else {
633 None
634 };
635 let sigma = shadow.blur.max(0.0) * scale / 2.0;
637 if sigma < 0.25 {
638 let mut path = PathBuilder::new();
639 let Some(shape) = shape.transform(Transform::from_translate(-left, -top)) else {
640 return;
641 };
642 rounded_rect(&mut path, shape, radius);
643 let Some(path) = path.finish() else {
644 return;
645 };
646 let mut paint = Paint::default();
647 let color = shadow.color;
648 paint.set_color_rgba8(color.r, color.g, color.b, color.a);
649 paint.anti_alias = true;
650 let outside = cut.map(|mut cut| {
651 cut.invert();
652 cut
653 });
654 let (rule, at) = (FillRule::Winding, Transform::identity());
655 pixmap.fill_path(&path, &paint, rule, at, outside.as_ref());
656 return;
657 }
658 let reach = 3.0 * sigma;
659 let columns = (shape.left() - reach - left).floor().max(0.0) as u32
660 ..((shape.right() + reach - left).ceil().max(0.0) as u32).min(pixmap.width());
661 let rows = (shape.top() - reach - top).floor().max(0.0) as u32
662 ..((shape.bottom() + reach - top).ceil().max(0.0) as u32).min(pixmap.height());
663 if columns.is_empty() || rows.is_empty() {
664 return;
665 }
666 let blurred = BlurredBox::new(shape, radius, sigma);
667 let mut mask = Vec::with_capacity(columns.len() * rows.len());
668 for row in rows.clone() {
669 let xs = columns.clone().map(|column| left + column as f32 + 0.5);
670 blurred.row(top + row as f32 + 0.5, xs, &mut mask);
671 }
672 if let Some(cut) = cut {
673 let stride = pixmap.width() as usize;
674 let under = rows.clone().flat_map(|row| {
675 columns
676 .clone()
677 .map(move |column| row as usize * stride + column as usize)
678 });
679 for (alpha, at) in mask.iter_mut().zip(under) {
680 *alpha = mul(*alpha, 255 - cut.data()[at]);
681 }
682 }
683 let width = columns.len() as u32;
684 let height = rows.len() as u32;
685 let (x, y) = (columns.start as i32, rows.start as i32);
686 blend_mask(pixmap, x, y, width, height, &mask, shadow.color);
687 }
688
689 fn box_mask(&self, pixmap: &PixmapMut<'_>, rect: Rect, style: &Style) -> Option<Mask> {
692 let (area, radius) = self.box_shape(rect, style)?;
693 let mut path = PathBuilder::new();
694 rounded_rect(&mut path, area, radius);
695 let mut mask = Mask::new(pixmap.width(), pixmap.height())?;
696 let (rule, at) = (FillRule::Winding, Transform::identity());
697 mask.fill_path(&path.finish()?, rule, true, at);
698 Some(mask)
699 }
700
701 fn shader(&self, gradient: &Gradient, area: tiny_skia::Rect) -> Option<Shader<'static>> {
705 let (left, top) = (area.left() + self.grid.left, area.top() + self.grid.top);
706 let (width, height) = (area.width(), area.height());
707 let middle = tiny_skia::Point::from_xy(left + width / 2.0, top + height / 2.0);
708 let given = gradient.stops();
709 let mut stops = Vec::with_capacity(given.len() * 2);
710 let stop = |at: f32, color: Color, alpha: u8| {
711 GradientStop::new(
712 at,
713 tiny_skia::Color::from_rgba8(color.r, color.g, color.b, alpha),
714 )
715 };
716 for (index, &(at, color)) in given.iter().enumerate() {
717 if color.a > 0 {
718 stops.push(stop(at, color, color.a));
719 continue;
720 }
721 for neighbor in [index.checked_sub(1), Some(index + 1)]
725 .into_iter()
726 .flatten()
727 {
728 if let Some(&(_, near)) = given.get(neighbor) {
729 stops.push(stop(at, near, 0));
730 }
731 }
732 }
733 let moved = Transform::from_translate(-self.grid.left, -self.grid.top);
734 match gradient.shape {
735 GradientShape::Linear(angle) => {
736 let (sin, cos) = angle.to_radians().sin_cos();
739 let half = (width * sin.abs() + height * cos.abs()) / 2.0;
740 let (dx, dy) = (sin * half, -cos * half);
741 let start = tiny_skia::Point::from_xy(middle.x - dx, middle.y - dy);
742 let end = tiny_skia::Point::from_xy(middle.x + dx, middle.y + dy);
743 LinearGradient::new(start, end, stops, SpreadMode::Pad, moved)
744 }
745 GradientShape::Radial => {
746 let reach = (width * width + height * height).sqrt() / 2.0;
747 RadialGradient::new(middle, 0.0, middle, reach, stops, SpreadMode::Pad, moved)
748 }
749 }
750 }
751
752 fn text(&mut self, pixmap: &mut PixmapMut<'_>, text: &TextView<'_>) {
756 let color = text.color();
757 if color.a == 0 {
758 return;
759 }
760 let Grid {
761 scale, left, top, ..
762 } = self.grid;
763 let origin = text.origin();
764 let x = origin.x * scale - left;
765 for run in text.runs() {
766 let baseline = ((origin.y + run.line_y) * scale).round() - top;
768 if baseline + run.line_height * scale < 0.0
769 || baseline - run.line_height * scale > pixmap.height() as f32
770 {
771 continue;
772 }
773 for glyph in run.glyphs {
774 let physical = glyph.physical((x, baseline), scale);
775 let Some(image) = self.glyphs.get_image(self.fonts, physical.cache_key) else {
776 continue;
777 };
778 let left = physical.x + image.placement.left;
779 let top = physical.y - image.placement.top;
780 let (width, height) = (image.placement.width, image.placement.height);
781 match image.content {
782 SwashContent::Mask => {
783 blend_mask(pixmap, left, top, width, height, &image.data, color);
784 }
785 SwashContent::Color => {
786 let premultiplied = is_color_outline(image);
787 let rgba = (image.data.as_slice(), premultiplied);
788 blend_rgba(pixmap, left, top, width, height, rgba, color.a);
789 }
790 SwashContent::SubpixelMask => {}
792 }
793 }
794 }
795 }
796
797 fn input(&mut self, pixmap: &mut PixmapMut<'_>, input: &InputView<'_>) {
799 let Some(area) = self.grid.rect(input.area) else {
800 return;
801 };
802 self.through_mask(pixmap, input.area, (area, 0.0), |painter, layer| {
803 painter.input_contents(layer, input);
804 });
805 }
806
807 fn input_contents(&mut self, pixmap: &mut PixmapMut<'_>, input: &InputView<'_>) {
810 let color = input.text.color();
811 for selection in input.selection {
812 let Some(area) = self.grid.rect(*selection) else {
813 continue;
814 };
815 let mut path = PathBuilder::new();
816 path.push_rect(area);
817 let tint = Color::rgba(color.r, color.g, color.b, color.a / 3);
818 fill(pixmap, path, tint, FillRule::Winding);
819 }
820 self.text(pixmap, &input.text);
821 for line in input.underline {
822 if let Some(area) = self.grid.rect(*line) {
823 let mut path = PathBuilder::new();
824 path.push_rect(area);
825 fill(pixmap, path, color, FillRule::Winding);
826 }
827 }
828 if let Some(caret) = input.caret {
829 let left = self.grid.x(caret.x);
831 let right = self.grid.x(caret.right()).max(left + 1.0);
832 if let Some(area) = tiny_skia::Rect::from_ltrb(
833 left,
834 self.grid.y(caret.y),
835 right,
836 self.grid.y(caret.bottom()),
837 ) {
838 let mut path = PathBuilder::new();
839 path.push_rect(area);
840 fill(pixmap, path, color, FillRule::Winding);
841 }
842 }
843 }
844
845 fn image(&mut self, pixmap: &mut PixmapMut<'_>, image: &ImageView<'_>) {
847 let Some((x, y, width, height)) = self.picture_area(pixmap, image.rect) else {
848 return;
849 };
850 if let Some(pixels) = self.icons.image(image.path, width, height, image.fit) {
851 draw_at(pixmap, x, y, pixels);
852 }
853 }
854
855 fn capture(&mut self, pixmap: &mut PixmapMut<'_>, capture: &CaptureView<'_>) {
857 let Some((x, y, width, height)) = self.picture_area(pixmap, capture.rect) else {
858 return;
859 };
860 let frame = capture.frame;
861 if let Some(pixels) = self
862 .icons
863 .capture(capture.id, frame, width, height, capture.fit)
864 {
865 draw_at(pixmap, x, y, pixels);
866 }
867 }
868
869 fn picture_area(&self, pixmap: &PixmapMut<'_>, rect: Rect) -> Option<(i32, i32, u32, u32)> {
874 let Grid {
875 scale, left, top, ..
876 } = self.grid;
877 let (width, height) = ((rect.width * scale).round(), (rect.height * scale).round());
878 let (x, y) = (
879 (rect.x * scale).round() - left,
880 (rect.y * scale).round() - top,
881 );
882 if !(width >= 1.0 && height >= 1.0) {
883 return None;
884 }
885 let area = tiny_skia::Rect::from_xywh(x, y, width, height)?;
886 visible(pixmap, area).then_some((x as i32, y as i32, width as u32, height as u32))
888 }
889
890 fn icon(&mut self, pixmap: &mut PixmapMut<'_>, icon: &IconView<'_>) {
892 let Grid {
893 scale, left, top, ..
894 } = self.grid;
895 let side = (icon.rect.width * scale).round();
896 if side.is_nan() || side < 1.0 {
897 return;
898 }
899 let x = (icon.rect.x * scale).round() - left;
900 let y = (icon.rect.y * scale).round() - top;
901 let size = icon.rect.width.round() as u32;
903 let theme_scale = scale.ceil().max(1.0) as u32;
904 if let Some(image) = self.icons.get(icon.source, side as u32, size, theme_scale) {
905 pixmap.draw_pixmap(
906 x as i32,
907 y as i32,
908 image.as_ref(),
909 &PixmapPaint::default(),
910 Transform::identity(),
911 None,
912 );
913 }
914 }
915}
916
917fn draw_at(pixmap: &mut PixmapMut<'_>, x: i32, y: i32, pixels: &Pixmap) {
919 let paint = PixmapPaint::default();
920 pixmap.draw_pixmap(x, y, pixels.as_ref(), &paint, Transform::identity(), None);
921}
922
923fn visible(pixmap: &PixmapMut<'_>, area: tiny_skia::Rect) -> bool {
925 area.right() > 0.0
926 && area.bottom() > 0.0
927 && area.left() < pixmap.width() as f32
928 && area.top() < pixmap.height() as f32
929}
930
931fn fill(pixmap: &mut PixmapMut<'_>, path: PathBuilder, color: Color, rule: FillRule) {
932 let Some(path) = path.finish() else {
933 return;
934 };
935 let mut paint = Paint::default();
936 paint.set_color_rgba8(color.r, color.g, color.b, color.a);
937 paint.anti_alias = true;
938 pixmap.fill_path(&path, &paint, rule, Transform::identity(), None);
939}
940
941fn rounded_rect(path: &mut PathBuilder, rect: tiny_skia::Rect, radius: f32) {
943 if radius <= 0.0 {
944 path.push_rect(rect);
945 return;
946 }
947 const KAPPA: f32 = 0.552_284_8;
950 let (l, t, r, b) = (rect.left(), rect.top(), rect.right(), rect.bottom());
951 let k = radius * (1.0 - KAPPA);
952 path.move_to(l + radius, t);
953 path.line_to(r - radius, t);
954 path.cubic_to(r - k, t, r, t + k, r, t + radius);
955 path.line_to(r, b - radius);
956 path.cubic_to(r, b - k, r - k, b, r - radius, b);
957 path.line_to(l + radius, b);
958 path.cubic_to(l + k, b, l, b - k, l, b - radius);
959 path.line_to(l, t + radius);
960 path.cubic_to(l, t + k, l + k, t, l + radius, t);
961 path.close();
962}
963
964fn mul(a: u8, b: u8) -> u8 {
966 ((u16::from(a) * u16::from(b) + 127) / 255) as u8
967}
968
969fn over(data: &mut [u8], index: usize, source: [u8; 4]) {
971 let rest = 255 - source[3];
972 for (channel, value) in data[index..index + 4].iter_mut().zip(source) {
973 *channel = value.saturating_add(mul(*channel, rest));
974 }
975}
976
977fn overlap(
981 pixmap: &PixmapMut<'_>,
982 left: i32,
983 top: i32,
984 width: u32,
985 height: u32,
986) -> Option<(usize, usize, usize, usize)> {
987 let column_start = (-i64::from(left)).max(0);
988 let row_start = (-i64::from(top)).max(0);
989 let column_end = i64::from(width).min(i64::from(pixmap.width()) - i64::from(left));
990 let row_end = i64::from(height).min(i64::from(pixmap.height()) - i64::from(top));
991 (column_start < column_end && row_start < row_end).then_some((
992 column_start as usize,
993 column_end as usize,
994 row_start as usize,
995 row_end as usize,
996 ))
997}
998
999fn blend_mask(
1001 pixmap: &mut PixmapMut<'_>,
1002 left: i32,
1003 top: i32,
1004 width: u32,
1005 height: u32,
1006 mask: &[u8],
1007 color: Color,
1008) {
1009 if mask.len() < width as usize * height as usize {
1010 return;
1011 }
1012 let Some((columns, columns_end, rows, rows_end)) = overlap(pixmap, left, top, width, height)
1013 else {
1014 return;
1015 };
1016 let stride = pixmap.width() as usize;
1017 let data = pixmap.data_mut();
1018 for row in rows..rows_end {
1019 for column in columns..columns_end {
1020 let coverage = mask[row * width as usize + column];
1021 if coverage == 0 {
1022 continue;
1023 }
1024 let alpha = mul(color.a, coverage);
1025 let source = [
1026 mul(color.r, alpha),
1027 mul(color.g, alpha),
1028 mul(color.b, alpha),
1029 alpha,
1030 ];
1031 let x = (i64::from(left) + column as i64) as usize;
1032 let y = (i64::from(top) + row as i64) as usize;
1033 over(data, (y * stride + x) * 4, source);
1034 }
1035 }
1036}
1037
1038const SAMPLES: usize = 8;
1040
1041struct BlurredBox {
1047 middle: (f32, f32),
1048 half: (f32, f32),
1049 radius: f32,
1050 sigma: f32,
1051}
1052
1053impl BlurredBox {
1054 fn new(shape: tiny_skia::Rect, radius: f32, sigma: f32) -> BlurredBox {
1055 BlurredBox {
1056 middle: (
1057 (shape.left() + shape.right()) / 2.0,
1058 (shape.top() + shape.bottom()) / 2.0,
1059 ),
1060 half: (shape.width() / 2.0, shape.height() / 2.0),
1061 radius,
1062 sigma,
1063 }
1064 }
1065
1066 fn row(&self, y: f32, xs: impl Iterator<Item = f32>, mask: &mut Vec<u8>) {
1069 let y = y - self.middle.1;
1070 let (half_width, half_height) = self.half;
1071 let reach = 3.0 * self.sigma;
1072 let down = self.along(y, half_height);
1073 let clear_of_corners = y.abs() <= half_height - self.radius - reach;
1074 let rows = if clear_of_corners {
1075 [(0.0, 0.0); SAMPLES]
1076 } else {
1077 self.rows_near(y)
1078 };
1079 for x in xs {
1080 let x = x - self.middle.0;
1081 let coverage = if clear_of_corners || x.abs() <= half_width - self.radius - reach {
1082 self.along(x, half_width) * down
1083 } else {
1084 let sum: f32 = rows
1085 .iter()
1086 .map(|&(span, weight)| self.along(x, span) * weight)
1087 .sum();
1088 sum.clamp(0.0, 1.0)
1089 };
1090 mask.push((coverage * 255.0).round() as u8);
1091 }
1092 }
1093
1094 fn along(&self, p: f32, half: f32) -> f32 {
1097 if p.abs() <= half - 3.0 * self.sigma {
1098 return 1.0;
1099 }
1100 let scale = std::f32::consts::FRAC_1_SQRT_2 / self.sigma;
1101 0.5 * (erf((p + half) * scale) - erf((p - half) * scale))
1102 }
1103
1104 fn rows_near(&self, y: f32) -> [(f32, f32); SAMPLES] {
1108 let (half_width, half_height) = self.half;
1109 let reach = 3.0 * self.sigma;
1110 let start = (y - half_height).clamp(-reach, reach);
1111 let end = (y + half_height).clamp(-reach, reach);
1112 let step = (end - start) / SAMPLES as f32;
1113 std::array::from_fn(|sample| {
1114 let offset = start + step * (sample as f32 + 0.5);
1115 let row = y - offset;
1118 let into_corner = (row.abs() - (half_height - self.radius)).max(0.0);
1119 let span = half_width - self.radius
1120 + (self.radius * self.radius - into_corner * into_corner)
1121 .max(0.0)
1122 .sqrt();
1123 (span, gaussian(offset, self.sigma) * step)
1124 })
1125 }
1126}
1127
1128fn gaussian(x: f32, sigma: f32) -> f32 {
1130 let root_two_pi = (2.0 * std::f32::consts::PI).sqrt();
1131 (-(x * x) / (2.0 * sigma * sigma)).exp() / (root_two_pi * sigma)
1132}
1133
1134fn erf(x: f32) -> f32 {
1137 let a = x.abs();
1138 let d = 1.0 + a * (0.278_393 + a * (0.230_389 + a * (0.000_972 + a * 0.078_108)));
1139 let d = d * d;
1140 (1.0 - 1.0 / (d * d)).copysign(x)
1141}
1142
1143fn is_color_outline(image: &SwashImage) -> bool {
1150 format!("{:?}", image.source).starts_with("ColorOutline")
1151}
1152
1153fn blend_rgba(
1156 pixmap: &mut PixmapMut<'_>,
1157 left: i32,
1158 top: i32,
1159 width: u32,
1160 height: u32,
1161 (rgba, premultiplied): (&[u8], bool),
1162 opacity: u8,
1163) {
1164 if rgba.len() < width as usize * height as usize * 4 {
1165 return;
1166 }
1167 let Some((columns, columns_end, rows, rows_end)) = overlap(pixmap, left, top, width, height)
1168 else {
1169 return;
1170 };
1171 let stride = pixmap.width() as usize;
1172 let data = pixmap.data_mut();
1173 for row in rows..rows_end {
1174 for column in columns..columns_end {
1175 let index = (row * width as usize + column) * 4;
1176 let alpha = mul(rgba[index + 3], opacity);
1177 if alpha == 0 {
1178 continue;
1179 }
1180 let factor = if premultiplied { opacity } else { alpha };
1183 let source = [
1184 mul(rgba[index], factor),
1185 mul(rgba[index + 1], factor),
1186 mul(rgba[index + 2], factor),
1187 alpha,
1188 ];
1189 let x = (i64::from(left) + column as i64) as usize;
1190 let y = (i64::from(top) + row as i64) as usize;
1191 over(data, (y * stride + x) * 4, source);
1192 }
1193 }
1194}