Skip to main content

devpage_design/cpu/
mod.rs

1//! The `vello_cpu` backend — an adapter of [`crate::display::Target`]: any
2//! area of a display list painted into a pixmap, and a tile's straight-alpha
3//! pixels, for the server's tiles, the PNG and a browser without WebGPU.
4
5use std::collections::HashMap;
6
7use kurbo::{Affine, BezPath, Rect as KRect, Shape};
8use peniko::{BlendMode, Compose, ImageBrush, Mix};
9use vello_cpu::{Pixmap, RenderContext, Resources};
10
11use crate::display::List;
12use crate::render::{Draw, Paint};
13#[cfg(test)]
14use crate::resolve::{Scene, Theme};
15use crate::tiles::{TILE, tile_rect};
16
17/// The `vello_cpu` backend of [`crate::display::paint`].
18pub struct Cpu<'a> {
19    pub ctx: &'a mut RenderContext,
20    pub sources: HashMap<u64, vello_cpu::ImageSource>,
21    pub resources: Resources,
22}
23
24impl crate::display::Target for Cpu<'_> {
25    fn draw(&mut self, draw: &Draw, t: Affine) {
26        let ctx = &mut *self.ctx;
27        ctx.set_transform(t);
28        match draw {
29            Draw::Fill(path, paint) => {
30                match paint {
31                    Paint::Solid(c) => ctx.set_paint(*c),
32                    Paint::Gradient(g) => ctx.set_paint(g.clone()),
33                    Paint::Image(data, place) => {
34                        let image = self
35                            .sources
36                            .entry(data.data.id())
37                            .or_insert_with(|| vello_cpu::ImageSource::from_peniko_image_data(data))
38                            .clone();
39                        ctx.set_paint(ImageBrush {
40                            image,
41                            sampler: Default::default(),
42                        });
43                        ctx.set_paint_transform(*place);
44                    }
45                }
46                ctx.fill_path(path);
47                ctx.reset_paint_transform();
48            }
49            Draw::Stroke(path, width, c) => {
50                ctx.set_paint(*c);
51                ctx.set_stroke(kurbo::Stroke::new(*width as f64));
52                ctx.stroke_path(path);
53            }
54            Draw::Glyphs(font, size, c, coords, glyphs) => {
55                ctx.set_paint(*c);
56                let _ = ctx
57                    .glyph_run(&mut self.resources, font)
58                    .font_size(*size)
59                    .normalized_coords(coords)
60                    .hint(true)
61                    .fill_glyphs(glyphs.iter().map(|g| vello_cpu::Glyph {
62                        id: g.id,
63                        x: g.x,
64                        y: g.y,
65                    }));
66            }
67        }
68    }
69    fn push_clip(&mut self, path: &BezPath, t: Affine) {
70        self.ctx.set_transform(t);
71        self.ctx.push_clip_layer(path);
72    }
73    fn push_layer(
74        &mut self,
75        blend: crate::display::Blend,
76        bounds: Option<&BezPath>,
77        t: Affine,
78        area: KRect,
79    ) {
80        let whole;
81        let bounds = match bounds {
82            Some(b) => {
83                self.ctx.set_transform(t);
84                b
85            }
86            None => {
87                self.ctx.set_transform(Affine::IDENTITY);
88                whole = area.to_path(0.1);
89                &whole
90            }
91        };
92        match blend {
93            crate::display::Blend::Opacity(o) => {
94                self.ctx.push_layer(Some(bounds), None, Some(o), None, None)
95            }
96            crate::display::Blend::Add => self.ctx.push_layer(
97                Some(bounds),
98                Some(BlendMode::new(Mix::Normal, Compose::Plus)),
99                None,
100                None,
101                None,
102            ),
103        }
104    }
105    fn pop(&mut self) {
106        self.ctx.pop_layer();
107    }
108}
109
110/// Paints `area` of `list` — in the design's coordinates, whole pixels at
111/// `scale` — with `vello_cpu`: one tile, band or whole drawing.
112pub fn paint_area(list: &crate::display::List, area: KRect, scale: f64) -> Pixmap {
113    // Whole pixels, the last partly covered one included — but not one a
114    // rounding error leaves past a tile's exact edge.
115    let side = |v: f64| ((v * scale - 1e-6).ceil().clamp(1.0, u16::MAX as f64)) as u16;
116    let (w, h) = (side(area.width()), side(area.height()));
117    let mut ctx = RenderContext::new(w, h);
118    let mut cpu = Cpu {
119        ctx: &mut ctx,
120        sources: HashMap::new(),
121        resources: Resources::default(),
122    };
123    crate::display::paint(list, area, scale, &mut cpu);
124    let mut resources = std::mem::take(&mut cpu.resources);
125    ctx.flush();
126    let mut pixmap = Pixmap::new(w, h);
127    ctx.render(&mut pixmap, &mut resources);
128    pixmap
129}
130
131#[cfg(test)]
132impl crate::render::Renderer {
133    /// The scene's whole drawing painted with `vello_cpu` at `scale`.
134    pub fn paint(&mut self, scene: &Scene, theme: Theme, scale: f32) -> Pixmap {
135        let d = self.drawing(scene, theme, None).expect("drawable");
136        let a = d.area();
137        let area = KRect::new(
138            a.x as f64,
139            a.y as f64,
140            (a.x + a.width) as f64,
141            (a.y + a.height) as f64,
142        );
143        paint_area(&d.list, area, scale as f64)
144    }
145}
146
147/// A tile's pixels, straight RGBA, `TILE` a side.
148pub fn tile(list: &List, scale: f64, col: u32, row: u32) -> Vec<u8> {
149    let pixmap = paint_area(list, tile_rect(list, scale, col, row), scale);
150    let mut rgba = pixmap.data_as_u8_slice().to_vec();
151    unpremultiply(&mut rgba);
152    rgba
153}
154
155/// A strip of the drawing at 1×, straight RGBA: `TILE` pixels wide at tile
156/// column `col`, `rows` tall from pixel row `y` — a piece of a PNG band too
157/// wide for whole tiles.
158pub fn strip(list: &List, col: u32, y: u32, rows: u32) -> Vec<u8> {
159    let (x0, y0) = (list.area.x as f64, list.area.y as f64);
160    let area = KRect::new(
161        x0 + (col * TILE) as f64,
162        y0 + y as f64,
163        x0 + ((col + 1) * TILE) as f64,
164        y0 + (y + rows) as f64,
165    );
166    let mut rgba = paint_area(list, area, 1.0).data_as_u8_slice().to_vec();
167    unpremultiply(&mut rgba);
168    rgba
169}
170
171/// Straight alpha from premultiplied, rounding as the PNG encoder does.
172pub fn unpremultiply(rgba: &mut [u8]) {
173    for px in rgba.as_chunks_mut::<4>().0 {
174        let a = px[3] as u32;
175        if a != 0 && a != 255 {
176            for c in &mut px[..3] {
177                *c = ((*c as u32 * 255 + a / 2) / a).min(255) as u8;
178            }
179        }
180    }
181}