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  Color, Content, IconView, NodeView, Rect, Size, Style, TextSystem, TextView, WindowView,
12};
13use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, PixmapMut, PixmapPaint, Transform};
14
15use crate::damage::{BufferAge, DamageHistory, PixelRect};
16use crate::icons::Icons;
17
18/// The side of a tile, in buffer pixels. tiny-skia cuts a curve where it
19/// leaves the pixmap, which changes the antialiasing beside the cut, so
20/// every pixel is drawn in the same tile whatever the damage, and a repaint
21/// matches drawing everything.
22const TILE: u32 = 64;
23
24/// How many glyph images cosmic-text's cache may hold after a draw before
25/// it is cleared. A bar needs a few hundred; the cache never shrinks itself.
26const GLYPH_CACHE_LIMIT: usize = 2048;
27
28/// How a buffer stores each pixel, as `wl_shm` names the formats. Both are
29/// 32 bits per pixel with premultiplied alpha.
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum PixelFormat {
32  /// Bytes B, G, R, A in memory. Every compositor supports it.
33  Argb8888,
34  /// Bytes R, G, B, A in memory, as tiny-skia draws. Copied as is.
35  Abgr8888,
36}
37
38/// A buffer to draw a window into.
39pub struct Canvas<'a> {
40  /// The buffer's memory: `width * height` pixels of 4 bytes, row by row.
41  pub pixels: &'a mut [u8],
42  /// The width in pixels: the window's logical width times `scale`,
43  /// rounded halfway away from zero, as `wp_fractional_scale_v1` says.
44  /// The window's edge is drawn at the buffer's edge either way.
45  pub width: u32,
46  /// The height in pixels, likewise.
47  pub height: u32,
48  /// Physical pixels per logical pixel.
49  pub scale: f32,
50  /// How the buffer stores pixels.
51  pub format: PixelFormat,
52  /// The frame the buffer last held, updated by the draw.
53  pub age: &'a mut BufferAge,
54}
55
56/// Draws windows. One serves every window; it holds the glyph and icon
57/// caches.
58pub struct Renderer {
59  glyphs: SwashCache,
60  icons: Icons,
61  /// Where each tile is drawn before it is copied into the buffer, kept
62  /// between frames.
63  scratch: Vec<u8>,
64  work: Work,
65}
66
67/// What drawing has cost so far, for tests to check that a draw stays
68/// small.
69#[derive(Clone, Copy, Default)]
70#[cfg_attr(not(test), allow(dead_code, reason = "only tests read it"))]
71struct Work {
72  /// Pixels of opacity layers allocated.
73  layer_pixels: u64,
74  /// Nodes drawn.
75  nodes: u64,
76}
77
78impl Renderer {
79  /// A renderer with empty caches, looking icons up in the `hicolor`
80  /// theme.
81  pub fn new() -> Renderer {
82    Renderer {
83      glyphs: SwashCache::new(),
84      icons: Icons::new(),
85      scratch: Vec::new(),
86      work: Work::default(),
87    }
88  }
89
90  /// The number of glyph images cached, for tests.
91  #[cfg(test)]
92  pub(crate) fn cached_glyphs(&self) -> usize {
93    self.glyphs.image_cache.len()
94  }
95
96  /// The pixels of opacity layers allocated and the nodes drawn so far,
97  /// for tests.
98  #[cfg(test)]
99  pub(crate) fn work(&self) -> (u64, u64) {
100    (self.work.layer_pixels, self.work.nodes)
101  }
102
103  /// A renderer that looks icon names up in `bases` instead of the
104  /// environment's directories, for tests.
105  #[cfg(test)]
106  pub(crate) fn with_icon_dirs(bases: Vec<std::path::PathBuf>) -> Renderer {
107    Renderer {
108      icons: Icons::with_themes(crate::theme::Themes::new(bases)),
109      ..Renderer::new()
110    }
111  }
112
113  /// Sets the icon theme names are looked up in.
114  pub fn set_icon_theme(&mut self, theme: impl Into<String>) {
115    self.icons.set_theme(theme.into());
116  }
117
118  /// The number of rasterized icons cached, for tests.
119  #[cfg(test)]
120  pub(crate) fn cached_icons(&self) -> usize {
121    self.icons.cached()
122  }
123
124  /// The number of times an icon was rasterized, for tests.
125  #[cfg(test)]
126  pub(crate) fn rasterized_icons(&self) -> usize {
127    self.icons.rasterized()
128  }
129
130  /// Draws the laid-out window `view` into `canvas` and returns the areas
131  /// to report with `wl_surface.damage_buffer`.
132  ///
133  /// `damage` is what the window reported since the last draw, from
134  /// [`Window::take_damage`](crabshell_ui::Window::take_damage). The draw
135  /// repaints that, plus what changed in frames this buffer missed
136  /// according to `history`, or the whole buffer when that is more than
137  /// half of it or the buffer is new. Text and glyphs come from `text`.
138  ///
139  /// Commit the buffer after every successful draw, in order: the history
140  /// counts each one as a frame the compositor showed.
141  ///
142  /// # Errors
143  ///
144  /// If `canvas` holds the wrong number of bytes for its size, or its
145  /// scale isn't a positive number. Nothing is drawn and the history
146  /// doesn't advance.
147  pub fn draw(
148    &mut self,
149    view: &WindowView<'_>,
150    damage: &[Rect],
151    text: &mut TextSystem,
152    history: &mut DamageHistory,
153    canvas: Canvas<'_>,
154  ) -> Result<Vec<PixelRect>, CanvasError> {
155    let Canvas {
156      pixels,
157      width,
158      height,
159      scale,
160      format,
161      age,
162    } = canvas;
163    if pixels.len() as u64 != u64::from(width) * u64::from(height) * 4
164      || !(scale.is_finite() && scale > 0.0)
165    {
166      return Err(CanvasError {
167        width,
168        height,
169        scale,
170        bytes: pixels.len(),
171      });
172    }
173    let plan = history.plan(damage, scale, width, height, age);
174    let mut tiles: Vec<(u32, u32)> = Vec::new();
175    for area in plan.repaint.iter().filter(|area| area.area() > 0) {
176      for row in area.y / TILE..=(area.y + area.height - 1) / TILE {
177        tiles
178          .extend((area.x / TILE..=(area.x + area.width - 1) / TILE).map(|column| (row, column)));
179      }
180    }
181    tiles.sort_unstable();
182    tiles.dedup();
183    for (row, column) in tiles {
184      let (x, y) = (column * TILE, row * TILE);
185      let size = (width.saturating_sub(x), height.saturating_sub(y));
186      let area = PixelRect::new(x, y, TILE.min(size.0), TILE.min(size.1));
187      self.scratch.clear();
188      self.scratch.resize(area.area() as usize * 4, 0);
189      let Some(mut pixmap) = PixmapMut::from_bytes(&mut self.scratch, area.width, area.height)
190      else {
191        continue;
192      };
193      let mut painter = Painter {
194        grid: Grid {
195          scale,
196          window: view.size(),
197          width,
198          height,
199          left: area.x as f32,
200          top: area.y as f32,
201        },
202        glyphs: &mut self.glyphs,
203        icons: &mut self.icons,
204        fonts: text.font_system(),
205        work: &mut self.work,
206      };
207      painter.node(&mut pixmap, view.root());
208      copy(&self.scratch, area, pixels, width, format);
209    }
210    self.icons.end_frame();
211    if self.glyphs.image_cache.len() > GLYPH_CACHE_LIMIT {
212      self.glyphs.image_cache.clear();
213      self.glyphs.outline_command_cache.clear();
214    }
215    Ok(plan.report)
216  }
217}
218
219/// A canvas whose bytes don't fit its size, or whose scale isn't a positive
220/// number.
221#[derive(Clone, Copy, Debug, PartialEq)]
222pub struct CanvasError {
223  /// The canvas's width in pixels.
224  pub width: u32,
225  /// The canvas's height in pixels.
226  pub height: u32,
227  /// The canvas's scale.
228  pub scale: f32,
229  /// How many bytes the canvas held.
230  pub bytes: usize,
231}
232
233impl std::fmt::Display for CanvasError {
234  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235    write!(
236      f,
237      "a {}x{} canvas at scale {} needs {} bytes, but has {}",
238      self.width,
239      self.height,
240      self.scale,
241      u64::from(self.width) * u64::from(self.height) * 4,
242      self.bytes
243    )
244  }
245}
246
247impl std::error::Error for CanvasError {}
248
249impl Default for Renderer {
250  fn default() -> Self {
251    Self::new()
252  }
253}
254
255/// Copies an area drawn in RGBA into the buffer, converting byte order.
256fn copy(scratch: &[u8], area: PixelRect, pixels: &mut [u8], width: u32, format: PixelFormat) {
257  let stride = width as usize * 4;
258  let row_bytes = area.width as usize * 4;
259  for (row, source) in scratch.chunks_exact(row_bytes).enumerate() {
260    let start = (area.y as usize + row) * stride + area.x as usize * 4;
261    let target = &mut pixels[start..start + row_bytes];
262    match format {
263      PixelFormat::Abgr8888 => target.copy_from_slice(source),
264      PixelFormat::Argb8888 => {
265        let (to, _) = target.as_chunks_mut::<4>();
266        let (from, _) = source.as_chunks::<4>();
267        for (to, from) in to.iter_mut().zip(from) {
268          *to = [from[2], from[1], from[0], from[3]];
269        }
270      }
271    }
272  }
273}
274
275/// Turns logical coordinates into pixels of the area being drawn.
276#[derive(Clone, Copy)]
277struct Grid {
278  scale: f32,
279  /// The window's logical size.
280  window: Size,
281  /// The buffer's size in pixels.
282  width: u32,
283  height: u32,
284  /// Where the area being drawn starts in the buffer.
285  left: f32,
286  top: f32,
287}
288
289impl Grid {
290  /// A logical x coordinate on the pixel grid. The window's right edge is
291  /// the buffer's, even where rounding would leave a column out.
292  fn x(&self, x: f32) -> f32 {
293    let pixel = if x >= self.window.width {
294      self.width as f32
295    } else {
296      (x * self.scale).round()
297    };
298    pixel - self.left
299  }
300
301  /// A logical y coordinate on the pixel grid, likewise.
302  fn y(&self, y: f32) -> f32 {
303    let pixel = if y >= self.window.height {
304      self.height as f32
305    } else {
306      (y * self.scale).round()
307    };
308    pixel - self.top
309  }
310
311  /// The pixels of a `width` by `height` area that `rect`, in logical
312  /// pixels, touches: rounded outward, a pixel added around for
313  /// antialiasing, and cut to the area. `None` if it misses the area.
314  fn cover(&self, rect: Rect, width: u32, height: u32) -> Option<(u32, u32, u32, u32)> {
315    if !(rect.width > 0.0 && rect.height > 0.0) {
316      return None;
317    }
318    // `as` saturates, so huge or negative values stay in range.
319    let left = ((rect.x * self.scale).floor() - self.left - 1.0).max(0.0) as u32;
320    let top = ((rect.y * self.scale).floor() - self.top - 1.0).max(0.0) as u32;
321    let right = (((rect.right() * self.scale).ceil() - self.left + 1.0).max(0.0) as u32).min(width);
322    let bottom =
323      (((rect.bottom() * self.scale).ceil() - self.top + 1.0).max(0.0) as u32).min(height);
324    (left < right && top < bottom).then(|| (left, top, right - left, bottom - top))
325  }
326
327  /// This grid for a pixmap placed at `x`, `y` within this one.
328  fn moved(&self, x: u32, y: u32) -> Grid {
329    Grid {
330      left: self.left + x as f32,
331      top: self.top + y as f32,
332      ..*self
333    }
334  }
335
336  /// A logical rectangle on the pixel grid, or `None` if it covers no
337  /// pixel.
338  fn rect(&self, rect: Rect) -> Option<tiny_skia::Rect> {
339    tiny_skia::Rect::from_ltrb(
340      self.x(rect.x),
341      self.y(rect.y),
342      self.x(rect.right()),
343      self.y(rect.bottom()),
344    )
345    .filter(|rect| rect.width() >= 1.0 && rect.height() >= 1.0)
346  }
347}
348
349/// What drawing needs besides the pixmap.
350struct Painter<'a> {
351  grid: Grid,
352  glyphs: &'a mut SwashCache,
353  icons: &'a mut Icons,
354  fonts: &'a mut FontSystem,
355  work: &'a mut Work,
356}
357
358impl Painter<'_> {
359  /// Draws `node` and everything inside it. With an opacity below 1, they
360  /// are drawn into a layer first, which is then blended in at that
361  /// opacity, so overlapping children don't show through each other.
362  fn node(&mut self, pixmap: &mut PixmapMut<'_>, node: NodeView<'_>) {
363    // Nothing the node or its descendants paint reaches this area.
364    let Some((x, y, width, height)) =
365      self
366        .grid
367        .cover(node.bounds(), pixmap.width(), pixmap.height())
368    else {
369      return;
370    };
371    let style = node.style();
372    let opacity = style.opacity.unwrap_or(1.0);
373    let opacity = if opacity.is_nan() {
374      1.0
375    } else {
376      opacity.clamp(0.0, 1.0)
377    };
378    if opacity == 0.0 {
379      return;
380    }
381    if opacity < 1.0 {
382      // The layer covers only what the subtree paints in this area.
383      let Some(mut layer) = Pixmap::new(width, height) else {
384        return;
385      };
386      self.work.layer_pixels += u64::from(layer.width()) * u64::from(layer.height());
387      let mut inner = Painter {
388        grid: self.grid.moved(x, y),
389        glyphs: &mut *self.glyphs,
390        icons: &mut *self.icons,
391        fonts: &mut *self.fonts,
392        work: &mut *self.work,
393      };
394      inner.node_contents(&mut layer.as_mut(), node, &style);
395      let paint = PixmapPaint {
396        opacity,
397        ..PixmapPaint::default()
398      };
399      let (x, y) = (x as i32, y as i32);
400      pixmap.draw_pixmap(x, y, layer.as_ref(), &paint, Transform::identity(), None);
401    } else {
402      self.node_contents(pixmap, node, &style);
403    }
404  }
405
406  fn node_contents(&mut self, pixmap: &mut PixmapMut<'_>, node: NodeView<'_>, style: &Style) {
407    self.work.nodes += 1;
408    self.draw_box(pixmap, node.rect(), style);
409    match node.content() {
410      Content::Text(text) => self.text(pixmap, &text),
411      Content::Icon(icon) => self.icon(pixmap, &icon),
412      Content::None => {}
413    }
414    for child in node.children() {
415      self.node(pixmap, child);
416    }
417  }
418
419  /// The background and border of a widget's rectangle.
420  fn draw_box(&self, pixmap: &mut PixmapMut<'_>, rect: Rect, style: &Style) {
421    if style.background.is_none() && style.border.is_none() {
422      return;
423    }
424    let Some(area) = self.grid.rect(rect) else {
425      return;
426    };
427    if !visible(pixmap, area) {
428      return;
429    }
430    let radius = (style.radius.unwrap_or(0.0) * self.grid.scale)
431      .clamp(0.0, area.width().min(area.height()) / 2.0);
432    let radius = if radius.is_nan() { 0.0 } else { radius };
433
434    if let Some(color) = style.background {
435      let mut path = PathBuilder::new();
436      rounded_rect(&mut path, area, radius);
437      fill(pixmap, path, color, FillRule::Winding);
438    }
439    if let Some(border) = style.border {
440      // As in CSS: rounded down to whole pixels, but at least one.
441      let width = (border.width * self.grid.scale).floor();
442      let width = if border.width > 0.0 {
443        width.max(1.0)
444      } else {
445        width
446      };
447      if width >= 1.0 {
448        // A ring: the outer shape with the inner one cut out.
449        let mut path = PathBuilder::new();
450        rounded_rect(&mut path, area, radius);
451        if let Some(inner) = tiny_skia::Rect::from_ltrb(
452          area.left() + width,
453          area.top() + width,
454          area.right() - width,
455          area.bottom() - width,
456        ) {
457          rounded_rect(&mut path, inner, (radius - width).max(0.0));
458        }
459        fill(pixmap, path, border.color, FillRule::EvenOdd);
460      }
461    }
462  }
463
464  /// Glyphs, drawn as masks in the text's color. The baseline sits on a
465  /// whole pixel so horizontal strokes stay sharp; glyphs keep their
466  /// sub-pixel horizontal position.
467  fn text(&mut self, pixmap: &mut PixmapMut<'_>, text: &TextView<'_>) {
468    let color = text.color();
469    if color.a == 0 {
470      return;
471    }
472    let Grid {
473      scale, left, top, ..
474    } = self.grid;
475    let origin = text.origin();
476    let x = origin.x * scale - left;
477    for run in text.runs() {
478      // Rounded in buffer coordinates, so every area drawn agrees.
479      let baseline = ((origin.y + run.line_y) * scale).round() - top;
480      if baseline + run.line_height * scale < 0.0
481        || baseline - run.line_height * scale > pixmap.height() as f32
482      {
483        continue;
484      }
485      for glyph in run.glyphs {
486        let physical = glyph.physical((x, baseline), scale);
487        let Some(image) = self.glyphs.get_image(self.fonts, physical.cache_key) else {
488          continue;
489        };
490        let left = physical.x + image.placement.left;
491        let top = physical.y - image.placement.top;
492        let (width, height) = (image.placement.width, image.placement.height);
493        match image.content {
494          SwashContent::Mask => {
495            blend_mask(pixmap, left, top, width, height, &image.data, color);
496          }
497          SwashContent::Color => {
498            let premultiplied = is_color_outline(image);
499            let rgba = (image.data.as_slice(), premultiplied);
500            blend_rgba(pixmap, left, top, width, height, rgba, color.a);
501          }
502          // Not requested: there is no subpixel antialiasing.
503          SwashContent::SubpixelMask => {}
504        }
505      }
506    }
507  }
508
509  /// An icon, rasterized at its size in pixels.
510  fn icon(&mut self, pixmap: &mut PixmapMut<'_>, icon: &IconView<'_>) {
511    let Grid {
512      scale, left, top, ..
513    } = self.grid;
514    let side = (icon.rect.width * scale).round();
515    if side.is_nan() || side < 1.0 {
516      return;
517    }
518    let x = (icon.rect.x * scale).round() - left;
519    let y = (icon.rect.y * scale).round() - top;
520    // Theme directories come in whole scales; 1.5 looks for @2 icons.
521    let size = icon.rect.width.round() as u32;
522    let theme_scale = scale.ceil().max(1.0) as u32;
523    if let Some(image) = self.icons.get(icon.source, side as u32, size, theme_scale) {
524      pixmap.draw_pixmap(
525        x as i32,
526        y as i32,
527        image.as_ref(),
528        &PixmapPaint::default(),
529        Transform::identity(),
530        None,
531      );
532    }
533  }
534}
535
536/// Whether any of `area` lies inside the pixmap.
537fn visible(pixmap: &PixmapMut<'_>, area: tiny_skia::Rect) -> bool {
538  area.right() > 0.0
539    && area.bottom() > 0.0
540    && area.left() < pixmap.width() as f32
541    && area.top() < pixmap.height() as f32
542}
543
544fn fill(pixmap: &mut PixmapMut<'_>, path: PathBuilder, color: Color, rule: FillRule) {
545  let Some(path) = path.finish() else {
546    return;
547  };
548  let mut paint = Paint::default();
549  paint.set_color_rgba8(color.r, color.g, color.b, color.a);
550  paint.anti_alias = true;
551  pixmap.fill_path(&path, &paint, rule, Transform::identity(), None);
552}
553
554/// Adds a rectangle with circular corners of `radius` to `path`.
555fn rounded_rect(path: &mut PathBuilder, rect: tiny_skia::Rect, radius: f32) {
556  if radius <= 0.0 {
557    path.push_rect(rect);
558    return;
559  }
560  // How far a cubic curve's control points sit along the tangents to
561  // follow a quarter circle closely.
562  const KAPPA: f32 = 0.552_284_8;
563  let (l, t, r, b) = (rect.left(), rect.top(), rect.right(), rect.bottom());
564  let k = radius * (1.0 - KAPPA);
565  path.move_to(l + radius, t);
566  path.line_to(r - radius, t);
567  path.cubic_to(r - k, t, r, t + k, r, t + radius);
568  path.line_to(r, b - radius);
569  path.cubic_to(r, b - k, r - k, b, r - radius, b);
570  path.line_to(l + radius, b);
571  path.cubic_to(l + k, b, l, b - k, l, b - radius);
572  path.line_to(l, t + radius);
573  path.cubic_to(l, t + k, l + k, t, l + radius, t);
574  path.close();
575}
576
577/// `a * b / 255`, rounded.
578fn mul(a: u8, b: u8) -> u8 {
579  ((u16::from(a) * u16::from(b) + 127) / 255) as u8
580}
581
582/// Blends a premultiplied `source` over the pixel at `index` (in bytes).
583fn over(data: &mut [u8], index: usize, source: [u8; 4]) {
584  let rest = 255 - source[3];
585  for (channel, value) in data[index..index + 4].iter_mut().zip(source) {
586    *channel = value.saturating_add(mul(*channel, rest));
587  }
588}
589
590/// The part of a `width` by `height` image placed at `left`, `top` that
591/// lies inside the pixmap: the first and last column and row, as image
592/// coordinates.
593fn overlap(
594  pixmap: &PixmapMut<'_>,
595  left: i32,
596  top: i32,
597  width: u32,
598  height: u32,
599) -> Option<(usize, usize, usize, usize)> {
600  let column_start = (-i64::from(left)).max(0);
601  let row_start = (-i64::from(top)).max(0);
602  let column_end = i64::from(width).min(i64::from(pixmap.width()) - i64::from(left));
603  let row_end = i64::from(height).min(i64::from(pixmap.height()) - i64::from(top));
604  (column_start < column_end && row_start < row_end).then_some((
605    column_start as usize,
606    column_end as usize,
607    row_start as usize,
608    row_end as usize,
609  ))
610}
611
612/// Blends a glyph's coverage mask in `color`.
613fn blend_mask(
614  pixmap: &mut PixmapMut<'_>,
615  left: i32,
616  top: i32,
617  width: u32,
618  height: u32,
619  mask: &[u8],
620  color: Color,
621) {
622  if mask.len() < width as usize * height as usize {
623    return;
624  }
625  let Some((columns, columns_end, rows, rows_end)) = overlap(pixmap, left, top, width, height)
626  else {
627    return;
628  };
629  let stride = pixmap.width() as usize;
630  let data = pixmap.data_mut();
631  for row in rows..rows_end {
632    for column in columns..columns_end {
633      let coverage = mask[row * width as usize + column];
634      if coverage == 0 {
635        continue;
636      }
637      let alpha = mul(color.a, coverage);
638      let source = [
639        mul(color.r, alpha),
640        mul(color.g, alpha),
641        mul(color.b, alpha),
642        alpha,
643      ];
644      let x = (i64::from(left) + column as i64) as usize;
645      let y = (i64::from(top) + row as i64) as usize;
646      over(data, (y * stride + x) * 4, source);
647    }
648  }
649}
650
651/// Whether a color glyph came from vector color layers (COLR), which swash
652/// renders premultiplied, rather than an embedded bitmap, which it hands
653/// over as stored: straight alpha.
654///
655/// cosmic-text doesn't re-export swash's `Source`, and swash isn't a
656/// dependency of this crate, so the variant is read from its name.
657fn is_color_outline(image: &SwashImage) -> bool {
658  format!("{:?}", image.source).starts_with("ColorOutline")
659}
660
661/// Blends a color glyph's RGBA pixels at `opacity` out of 255. `rgba` says
662/// whether they are premultiplied already.
663fn blend_rgba(
664  pixmap: &mut PixmapMut<'_>,
665  left: i32,
666  top: i32,
667  width: u32,
668  height: u32,
669  (rgba, premultiplied): (&[u8], bool),
670  opacity: u8,
671) {
672  if rgba.len() < width as usize * height as usize * 4 {
673    return;
674  }
675  let Some((columns, columns_end, rows, rows_end)) = overlap(pixmap, left, top, width, height)
676  else {
677    return;
678  };
679  let stride = pixmap.width() as usize;
680  let data = pixmap.data_mut();
681  for row in rows..rows_end {
682    for column in columns..columns_end {
683      let index = (row * width as usize + column) * 4;
684      let alpha = mul(rgba[index + 3], opacity);
685      if alpha == 0 {
686        continue;
687      }
688      // Premultiplied colors only need the opacity; straight ones need
689      // their own alpha too.
690      let factor = if premultiplied { opacity } else { alpha };
691      let source = [
692        mul(rgba[index], factor),
693        mul(rgba[index + 1], factor),
694        mul(rgba[index + 2], factor),
695        alpha,
696      ];
697      let x = (i64::from(left) + column as i64) as usize;
698      let y = (i64::from(top) + row as i64) as usize;
699      over(data, (y * stride + x) * 4, source);
700    }
701  }
702}