Skip to main content

ass_renderer/backends/coverage/
composite.rs

1//! Compositing of coverage tiles and positioned bitmaps onto a frame buffer.
2//!
3//! Source-over blends an A8 coverage tile (with a straight colour) or a
4//! [`RenderBitmap`] onto a premultiplied-RGBA8 frame, reusing the low-level
5//! blend helpers in [`super::blend`].
6
7use super::blend::{blend_row, mul255};
8use super::{CoverageTile, RenderBitmap};
9
10/// Source-over an A8 `cov` (`cov_w * cov_h`) onto a premultiplied-RGBA8 buffer at
11/// `(x, y)` using a straight (non-premultiplied) `color`.
12///
13/// `dst` is `dst_w * dst_h * 4` bytes in tiny-skia's premultiplied RGBA layout.
14/// The paint is premultiplied once, then scaled by each pixel's coverage — the
15/// same result as filling the path directly with a solid-colour paint.
16fn composite_coverage(
17    dst: &mut [u8],
18    dst_dim: (u32, u32),
19    src: (&[u8], u32, u32),
20    pos: (i32, i32),
21    color: [u8; 4],
22) {
23    let pa = u16::from(color[3]);
24    if pa == 0 {
25        return;
26    }
27    let pr = mul255(u16::from(color[0]), pa);
28    let pg = mul255(u16::from(color[1]), pa);
29    let pb = mul255(u16::from(color[2]), pa);
30
31    let (dst_w, dst_h) = dst_dim;
32    let (cov, cov_w, cov_h) = src;
33    let (x, y) = pos;
34
35    // Clip the tile against the destination once so the row blend is bounds-free.
36    let (tw, th) = (cov_w as i32, cov_h as i32);
37    let (dw, dh) = (dst_w as i32, dst_h as i32);
38    let ty0 = (-y).max(0);
39    let ty1 = th.min(dh - y);
40    let tx0 = (-x).max(0);
41    let tx1 = tw.min(dw - x);
42    if ty1 <= ty0 || tx1 <= tx0 {
43        return;
44    }
45    let run = (tx1 - tx0) as usize;
46    for ty in ty0..ty1 {
47        let tile_base = (ty * tw + tx0) as usize;
48        let cov_row = &cov[tile_base..tile_base + run];
49        let dst_start = ((y + ty) * dw + x + tx0) as usize * 4;
50        blend_row(dst, dst_start, cov_row, pr, pg, pb, pa);
51    }
52}
53
54/// Composite an A8 coverage tile at `(x, y)` in `color` (see the private
55/// `composite_coverage` helper).
56pub fn composite(
57    dst: &mut [u8],
58    dst_w: u32,
59    dst_h: u32,
60    tile: &CoverageTile,
61    x: i32,
62    y: i32,
63    color: [u8; 4],
64) {
65    composite_coverage(
66        dst,
67        (dst_w, dst_h),
68        (&tile.data, tile.width, tile.height),
69        (x, y),
70        color,
71    );
72}
73
74/// Composite a [`RenderBitmap`] at its own position onto a premultiplied-RGBA8
75/// frame buffer with source-over blending.
76pub fn composite_bitmap(dst: &mut [u8], dst_w: u32, dst_h: u32, bmp: &RenderBitmap) {
77    match bmp {
78        RenderBitmap::Coverage {
79            width,
80            height,
81            coverage,
82            x,
83            y,
84            color,
85        } => composite_coverage(
86            dst,
87            (dst_w, dst_h),
88            (coverage, *width, *height),
89            (*x, *y),
90            *color,
91        ),
92        RenderBitmap::Rgba {
93            width,
94            height,
95            pixels,
96            x,
97            y,
98        } => composite_rgba(dst, (dst_w, dst_h), (pixels, *width, *height), (*x, *y)),
99    }
100}
101
102/// Source-over a premultiplied-RGBA `src` tile onto a premultiplied-RGBA frame.
103fn composite_rgba(dst: &mut [u8], dst_dim: (u32, u32), src: (&[u8], u32, u32), pos: (i32, i32)) {
104    let (dst_w, dst_h) = dst_dim;
105    let (pixels, sw, sh) = src;
106    let (x, y) = pos;
107    let (tw, th) = (sw as i32, sh as i32);
108    let (dw, dh) = (dst_w as i32, dst_h as i32);
109    let ty0 = (-y).max(0);
110    let ty1 = th.min(dh - y);
111    let tx0 = (-x).max(0);
112    let tx1 = tw.min(dw - x);
113    if ty1 <= ty0 || tx1 <= tx0 {
114        return;
115    }
116    for ty in ty0..ty1 {
117        let mut si = ((ty * tw + tx0) as usize) * 4;
118        let mut di = ((y + ty) * dw + x + tx0) as usize * 4;
119        for _ in tx0..tx1 {
120            let sa = u16::from(pixels[si + 3]);
121            if sa != 0 {
122                let inv = 255 - sa;
123                dst[di] = (u16::from(pixels[si]) + mul255(u16::from(dst[di]), inv)) as u8;
124                dst[di + 1] =
125                    (u16::from(pixels[si + 1]) + mul255(u16::from(dst[di + 1]), inv)) as u8;
126                dst[di + 2] =
127                    (u16::from(pixels[si + 2]) + mul255(u16::from(dst[di + 2]), inv)) as u8;
128                dst[di + 3] =
129                    (u16::from(pixels[si + 3]) + mul255(u16::from(dst[di + 3]), inv)) as u8;
130            }
131            si += 4;
132            di += 4;
133        }
134    }
135}