Skip to main content

crabshell_render/
draw.rs

1//! Drawing a window's view into a buffer.
2//!
3//! The areas to repaint are drawn from scratch, one tile of a fixed grid at
4//! a time, into an RGBA pixmap of the tile's size, then copied into the
5//! buffer. The pixmap clips the drawing to the tile, and the copy converts
6//! the pixels to the buffer's byte order, so tiny-skia, glyph masks, resvg
7//! and PNG decoding all work in the RGBA order they share.
8
9use 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
22/// The side of a tile, in buffer pixels. tiny-skia cuts a curve where it
23/// leaves the pixmap, which changes the antialiasing beside the cut, so
24/// every pixel is drawn in the same tile whatever the damage, and a repaint
25/// matches drawing everything.
26const TILE: u32 = 64;
27
28/// How many glyph images cosmic-text's cache may hold after a draw before
29/// it is cleared. A bar needs a few hundred; the cache never shrinks itself.
30const GLYPH_CACHE_LIMIT: usize = 2048;
31
32/// How a buffer stores each pixel, as `wl_shm` names the formats. Both are
33/// 32 bits per pixel with premultiplied alpha.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum PixelFormat {
36  /// Bytes B, G, R, A in memory. Every compositor supports it.
37  Argb8888,
38  /// Bytes R, G, B, A in memory, as tiny-skia draws. Copied as is.
39  Abgr8888,
40}
41
42/// A buffer to draw a window into.
43pub struct Canvas<'a> {
44  /// The buffer's memory: `width * height` pixels of 4 bytes, row by row.
45  pub pixels: &'a mut [u8],
46  /// The width in pixels: the window's logical width times `scale`,
47  /// rounded halfway away from zero, as `wp_fractional_scale_v1` says.
48  /// The window's edge is drawn at the buffer's edge either way.
49  pub width: u32,
50  /// The height in pixels, likewise.
51  pub height: u32,
52  /// Physical pixels per logical pixel.
53  pub scale: f32,
54  /// How the buffer stores pixels.
55  pub format: PixelFormat,
56  /// The frame the buffer last held, updated by the draw.
57  pub age: &'a mut BufferAge,
58}
59
60/// Draws windows. One serves every window; it holds the glyph and icon
61/// caches.
62pub struct Renderer {
63  glyphs: SwashCache,
64  icons: Icons,
65  /// Where each tile is drawn before it is copied into the buffer, kept
66  /// between frames.
67  scratch: Vec<u8>,
68  work: Work,
69}
70
71/// What drawing has cost so far, for tests to check that a draw stays
72/// small.
73#[derive(Clone, Copy, Default)]
74#[cfg_attr(not(test), allow(dead_code, reason = "only tests read it"))]
75struct Work {
76  /// Pixels of opacity layers allocated.
77  layer_pixels: u64,
78  /// Nodes drawn.
79  nodes: u64,
80}
81
82impl Renderer {
83  /// A renderer with empty caches, looking icons up in the `hicolor`
84  /// theme.
85  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  /// The number of glyph images cached, for tests.
95  #[cfg(test)]
96  pub(crate) fn cached_glyphs(&self) -> usize {
97    self.glyphs.image_cache.len()
98  }
99
100  /// The pixels of opacity layers allocated and the nodes drawn so far,
101  /// for tests.
102  #[cfg(test)]
103  pub(crate) fn work(&self) -> (u64, u64) {
104    (self.work.layer_pixels, self.work.nodes)
105  }
106
107  /// A renderer that looks icon names up in `bases` instead of the
108  /// environment's directories, for tests.
109  #[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  /// Sets the icon theme names are looked up in.
118  pub fn set_icon_theme(&mut self, theme: impl Into<String>) {
119    self.icons.set_theme(theme.into());
120  }
121
122  /// The number of rasterized icons cached, for tests.
123  #[cfg(test)]
124  pub(crate) fn cached_icons(&self) -> usize {
125    self.icons.cached()
126  }
127
128  /// The number of times an icon was rasterized, for tests.
129  #[cfg(test)]
130  pub(crate) fn rasterized_icons(&self) -> usize {
131    self.icons.rasterized()
132  }
133
134  /// Drops the scaled frames kept for screencopy views `gone`, at once:
135  /// each is as large as its view, and an idle shell may draw nothing
136  /// for a long while.
137  pub fn forget_captures(&mut self, gone: &[u64]) {
138    self.icons.forget_captures(gone);
139  }
140
141  /// The number of screencopy frames kept scaled, for tests.
142  #[cfg(test)]
143  pub(crate) fn cached_captures(&self) -> usize {
144    self.icons.captures()
145  }
146
147  /// Draws the laid-out window `view` into `canvas` and returns the areas
148  /// to report with `wl_surface.damage_buffer`.
149  ///
150  /// `damage` is what the window reported since the last draw, from
151  /// [`Window::take_damage`](crabshell_ui::Window::take_damage). The draw
152  /// repaints that, plus what changed in frames this buffer missed
153  /// according to `history`, or the whole buffer when that is more than
154  /// half of it or the buffer is new. Text and glyphs come from `text`.
155  ///
156  /// Commit the buffer after every successful draw, in order: the history
157  /// counts each one as a frame the compositor showed.
158  ///
159  /// # Errors
160  ///
161  /// If `canvas` holds the wrong number of bytes for its size, or its
162  /// scale isn't a positive number. Nothing is drawn and the history
163  /// doesn't advance.
164  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/// A canvas whose bytes don't fit its size, or whose scale isn't a positive
237/// number.
238#[derive(Clone, Copy, Debug, PartialEq)]
239pub struct CanvasError {
240  /// The canvas's width in pixels.
241  pub width: u32,
242  /// The canvas's height in pixels.
243  pub height: u32,
244  /// The canvas's scale.
245  pub scale: f32,
246  /// How many bytes the canvas held.
247  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
272/// Copies an area drawn in RGBA into the buffer, converting byte order.
273fn 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/// Turns logical coordinates into pixels of the area being drawn.
293#[derive(Clone, Copy)]
294struct Grid {
295  scale: f32,
296  /// The window's logical size.
297  window: Size,
298  /// The buffer's size in pixels.
299  width: u32,
300  height: u32,
301  /// Where the area being drawn starts in the buffer.
302  left: f32,
303  top: f32,
304}
305
306impl Grid {
307  /// A logical x coordinate on the pixel grid. The window's right edge is
308  /// the buffer's, even where rounding would leave a column out.
309  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  /// A logical y coordinate on the pixel grid, likewise.
319  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  /// The pixels of a `width` by `height` area that `rect`, in logical
329  /// pixels, touches: rounded outward, a pixel added around for
330  /// antialiasing, and cut to the area. `None` if it misses the area.
331  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    // `as` saturates, so huge or negative values stay in range.
336    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  /// This grid for a pixmap placed at `x`, `y` within this one.
345  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  /// A logical rectangle on the pixel grid, or `None` if it covers no
354  /// pixel.
355  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
366/// What drawing needs besides the pixmap.
367struct 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  /// Draws `node` and everything inside it. With an opacity below 1, they
377  /// are drawn into a layer first, which is then blended in at that
378  /// opacity, so overlapping children don't show through each other.
379  fn node(&mut self, pixmap: &mut PixmapMut<'_>, node: NodeView<'_>) {
380    // Nothing the node or its descendants paint reaches this area.
381    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      // The layer covers only what the subtree paints in this area.
400      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  /// `node`'s children, drawn into a layer that is blended in through a
447  /// mask of the inside of the node's border. The mask is drawn in the
448  /// area's own pixels, on the same fixed grid as everything else, so a
449  /// repaint cuts every edge exactly as a full draw does.
450  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  /// What `draw` paints within `cover`, a logical rectangle, blended in
472  /// through a mask of `shape`, a pixel rectangle with rounded corners.
473  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  /// A widget's rectangle on the pixel grid and its corner radius in
518  /// pixels, clamped to half the shorter side. `None` if it covers no
519  /// pixel.
520  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  /// The border's width in pixels: as in CSS, rounded down to whole
529  /// pixels, but at least one. 0 without a border.
530  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  /// The background and border of a widget's rectangle.
543  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        // A ring: the outer shape with the inner one cut out.
581        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  /// `shadow` of the box `rect` with `style`'s corners. A sharp one is a
597  /// filled shape; a blurred one is worked out pixel by pixel in buffer
598  /// pixels, so every tile agrees, and only within one and a half blurs
599  /// of its shape, past which it fades below a 255th.
600  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    // The shape in buffer pixels.
612    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    // As CSS: a corner smaller than the spread grows by less, so a square
622    // box keeps square corners.
623    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    // A cut-out shadow leaves clear what the box's background covers.
630    let cut = if shadow.cut_out {
631      self.box_mask(pixmap, rect, style)
632    } else {
633      None
634    };
635    // CSS draws a blur as a Gaussian of half its length.
636    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  /// How much of each of this pixmap's pixels the box `rect` with
690  /// `style`'s corners covers, as its background fills it.
691  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  /// `gradient` across `area`, a box in this pixmap's pixels. It is laid
702  /// out in buffer pixels and moved onto the pixmap, so every tile works
703  /// out the same color for a pixel.
704  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      // tiny-skia blends colors before their alpha, so a clear stop
722      // would fade through its own color. As in CSS, each side fades
723      // out in its neighbor's.
724      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        // As CSS: 0 degrees points up, and the line is long enough for
737        // its ends to meet the corners.
738        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  /// Glyphs, drawn as masks in the text's color. The baseline sits on a
753  /// whole pixel so horizontal strokes stay sharp; glyphs keep their
754  /// sub-pixel horizontal position.
755  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      // Rounded in buffer coordinates, so every area drawn agrees.
767      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          // Not requested: there is no subpixel antialiasing.
791          SwashContent::SubpixelMask => {}
792        }
793      }
794    }
795  }
796
797  /// A text field, cut to its content box.
798  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  /// A field's selection in a third of the text's color, the text, and a
808  /// caret in the text's color.
809  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      // At least one pixel wide whatever the scale.
830      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  /// An image, scaled to its box in pixels.
846  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  /// A screencopy view's frame, scaled to its box in pixels.
856  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  /// Where a picture in `rect` goes in the pixmap, and its size in
870  /// pixels; `None` if it covers no pixel of it. Placed and sized apart,
871  /// as an icon is, so a picture past the window's edge is cut there
872  /// instead of squeezed inside it.
873  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    // `as` saturates, so a huge box stays in range.
887    visible(pixmap, area).then_some((x as i32, y as i32, width as u32, height as u32))
888  }
889
890  /// An icon, rasterized at its size in pixels.
891  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    // Theme directories come in whole scales; 1.5 looks for @2 icons.
902    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
917/// Draws `pixels` unscaled with its corner at `x`, `y`.
918fn 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
923/// Whether any of `area` lies inside the pixmap.
924fn 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
941/// Adds a rectangle with circular corners of `radius` to `path`.
942fn 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  // How far a cubic curve's control points sit along the tangents to
948  // follow a quarter circle closely.
949  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
964/// `a * b / 255`, rounded.
965fn mul(a: u8, b: u8) -> u8 {
966  ((u16::from(a) * u16::from(b) + 127) / 255) as u8
967}
968
969/// Blends a premultiplied `source` over the pixel at `index` (in bytes).
970fn 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
977/// The part of a `width` by `height` image placed at `left`, `top` that
978/// lies inside the pixmap: the first and last column and row, as image
979/// coordinates.
980fn 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
999/// Blends a glyph's coverage mask in `color`.
1000fn 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
1038/// How many rows a point near a corner sums.
1039const SAMPLES: usize = 8;
1040
1041/// A rectangle with rounded corners blurred by a Gaussian: how much of it
1042/// covers each point. Away from the corners it is a plain rectangle,
1043/// whose blur is the product of each axis's, from the error function.
1044/// Near a corner, each row's span is blurred across and the rows are
1045/// summed over eight samples of the Gaussian down.
1046struct 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  /// Pushes the coverage at each of `xs` on the row at `y`, out of 255,
1067  /// onto `mask`.
1068  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  /// The blur of the span from `-half` to `half` at `p`. One the blur
1095  /// stays inside of is covered whole.
1096  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  /// The rows of the shape a point at height `y` near a corner sums: for
1105  /// each sample within the shape and the Gaussian's reach, the row's
1106  /// half span and its weight.
1107  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      // How far into a corner's rows this one is, and how much it
1116      // narrows there.
1117      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
1128/// The normal distribution's density at `x`, for a deviation of `sigma`.
1129fn 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
1134/// The error function, within 5e-4, an eighth of a color level, by
1135/// Abramowitz and Stegun's 7.1.27, which needs no exponential.
1136fn 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
1143/// Whether a color glyph came from vector color layers (COLR), which swash
1144/// renders premultiplied, rather than an embedded bitmap, which it hands
1145/// over as stored: straight alpha.
1146///
1147/// cosmic-text doesn't re-export swash's `Source`, and swash isn't a
1148/// dependency of this crate, so the variant is read from its name.
1149fn is_color_outline(image: &SwashImage) -> bool {
1150  format!("{:?}", image.source).starts_with("ColorOutline")
1151}
1152
1153/// Blends a color glyph's RGBA pixels at `opacity` out of 255. `rgba` says
1154/// whether they are premultiplied already.
1155fn 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      // Premultiplied colors only need the opacity; straight ones need
1181      // their own alpha too.
1182      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}