Skip to main content

pfx_live/flat/
cpu.rs

1use super::cpu_text::{CpuText, Label};
2use super::effects::blur;
3use super::icons::Icons;
4use super::sdf::{self, Field};
5use super::sprites::SpriteImage;
6use super::style;
7use super::{
8    ADDITIVE, BLUR, DASHED, FILL_GRADIENT, FILL_IMAGE, FILL_MASK, FILL_VERTICAL, Fill, Fit,
9    FlatScene, Gradient, Instance, KIND_ARC, KIND_CIRCLE, KIND_ICON_FILL, KIND_ICON_LINE,
10    KIND_RECT, KIND_RING, KIND_SHADOW, Order, PATTERN_MASK, PATTERN_SHIFT, Shape, Step, UNPICKED,
11    pack_shadow, pack_shape, placed, positive, shadow,
12};
13
14pub struct Paint {
15    pub colour: [f32; 4],
16    pub footprint: f32,
17}
18
19pub(crate) fn field(instance: &Instance, icons: Option<&Icons>, p: [f32; 2]) -> Field {
20    let shape = instance.shape;
21    match instance.kind() {
22        KIND_RECT => sdf::rect(p, [shape[0], shape[1]], instance.corners),
23        KIND_CIRCLE => sdf::circle(p, shape[0]),
24        KIND_RING => sdf::ring(p, shape[0], shape[1]),
25        KIND_ARC => sdf::arc(p, shape[0], shape[1], shape[2], shape[3]),
26        _ => {
27            let icon = instance.icon;
28            let distance = icons.map_or(1.0e4, |icons| {
29                icons.sample([icon[0] + p[0] * icon[2], icon[1] + p[1] * icon[3]])
30            });
31            [distance, 0.0, 0.0]
32        }
33    }
34}
35
36fn mix(a: [f32; 4], b: [f32; 4], t: f32) -> [f32; 4] {
37    std::array::from_fn(|i| a[i] + (b[i] - a[i]) * t)
38}
39
40fn premultiply(c: [f32; 4], cover: f32) -> [f32; 4] {
41    [
42        c[0] * c[3] * cover,
43        c[1] * c[3] * cover,
44        c[2] * c[3] * cover,
45        c[3] * cover,
46    ]
47}
48
49pub fn paint(instance: &Instance, icons: Option<&Icons>, p: [f32; 2]) -> Paint {
50    paint_with(instance, icons, None, p)
51}
52
53pub fn paint_with(
54    instance: &Instance,
55    icons: Option<&Icons>,
56    gradient: Option<&Gradient>,
57    p: [f32; 2],
58) -> Paint {
59    paint_sprite(instance, icons, gradient, None, p)
60}
61
62pub fn paint_sprite(
63    instance: &Instance,
64    icons: Option<&Icons>,
65    gradient: Option<&Gradient>,
66    sprites: Option<&SpriteImage>,
67    p: [f32; 2],
68) -> Paint {
69    let flags = instance.info[0];
70    let mut paint = if flags & BLUR != 0 {
71        blur::paint(instance, icons, p)
72    } else {
73        plain(instance, icons, gradient, sprites, p)
74    };
75    if flags & ADDITIVE != 0 {
76        paint.colour[3] = 0.0;
77    }
78    paint
79}
80
81fn plain(
82    instance: &Instance,
83    icons: Option<&Icons>,
84    gradient: Option<&Gradient>,
85    sprites: Option<&SpriteImage>,
86    p: [f32; 2],
87) -> Paint {
88    let inverse = instance.inverse();
89    let kind = instance.kind();
90    if kind == KIND_SHADOW {
91        let centre = [p[0] - instance.icon[2], p[1] - instance.icon[3]];
92        let cover = shadow::rounded_box(
93            centre,
94            [instance.shape[0], instance.shape[1]],
95            instance.corners,
96            [instance.icon[0], instance.icon[1]],
97        );
98        return Paint {
99            colour: premultiply(instance.fill_top, cover),
100            footprint: 0.0,
101        };
102    }
103    let flags = instance.info[0];
104    let fill_mode = flags & FILL_MASK;
105    let opacity = instance.opacity();
106    let icon = kind == KIND_ICON_FILL || kind == KIND_ICON_LINE;
107    let field = field(instance, icons, p);
108    let mut fill = [0.0; 4];
109    let mut fill_cover = 0.0;
110    if fill_mode != 0 {
111        fill_cover = if icon {
112            sdf::coverage_even(field[0], inverse)
113        } else {
114            sdf::coverage(field, inverse)
115        };
116        let t = if fill_mode == FILL_VERTICAL {
117            ((p[1] - instance.origin[2]) / (instance.origin[3] - instance.origin[2]))
118                .clamp(0.0, 1.0)
119        } else {
120            0.0
121        };
122        let mut base = if fill_mode == FILL_GRADIENT {
123            gradient.map_or([0.0; 4], |gradient| gradient.paint(p))
124        } else if fill_mode == FILL_IMAGE {
125            let uv = [
126                instance.icon[0] + p[0] * instance.icon[2],
127                instance.icon[1] + p[1] * instance.icon[3],
128            ];
129            let texel = sprites.map_or([1.0; 4], |sprites| sprites.sample(uv));
130            premultiply(
131                std::array::from_fn(|k| texel[k] * instance.fill_top[k]),
132                1.0,
133            )
134        } else {
135            premultiply(mix(instance.fill_top, instance.fill_bottom, t), 1.0)
136        };
137        let pattern = (flags & PATTERN_MASK) >> PATTERN_SHIFT;
138        if pattern != 0 {
139            let packed = instance.info[2];
140            let colour = std::array::from_fn(|i| ((packed >> (8 * i)) & 255) as f32 / 255.0);
141            let cover = sdf::coverage(sdf::pattern(pattern, instance.icon, p), inverse);
142            let paint = premultiply(colour, cover);
143            base = std::array::from_fn(|i| paint[i] + base[i] * (1.0 - paint[3]));
144        }
145        fill = base.map(|value| value * fill_cover);
146        fill = style::bevel_over(instance, p, field, inverse, fill, fill_cover);
147    }
148    let mut stroke = [0.0; 4];
149    let mut stroke_cover = 0.0;
150    if instance.stroke[0] > 0.0 {
151        let s = if field[0] >= 0.0 { 1.0 } else { -1.0 };
152        let band = [
153            field[0].abs() - instance.stroke[0] * 0.5,
154            s * field[1],
155            s * field[2],
156        ];
157        stroke_cover = if icon {
158            sdf::coverage_even(band[0], inverse)
159        } else {
160            sdf::coverage(band, inverse)
161        };
162        if flags & DASHED != 0 {
163            let along = if kind == KIND_RECT {
164                sdf::rect_along(p, [instance.shape[0], instance.shape[1]], instance.corners)
165            } else {
166                sdf::circle_along(p, instance.shape[0])
167            };
168            stroke_cover *= sdf::coverage(
169                sdf::dash(
170                    along,
171                    instance.stroke[1],
172                    instance.stroke[2],
173                    instance.stroke[3],
174                ),
175                inverse,
176            );
177        }
178        stroke = premultiply(instance.stroke_colour, stroke_cover);
179    }
180    let colour = std::array::from_fn(|i| (stroke[i] + fill[i] * (1.0 - stroke[3])) * opacity);
181    let fill_alpha = if fill_mode == FILL_GRADIENT {
182        instance.fill_bottom[3]
183    } else {
184        instance.fill_top[3].max(instance.fill_bottom[3])
185    };
186    let fill_seen = if fill_mode != 0 && fill_alpha > 0.0 {
187        fill_cover
188    } else {
189        0.0
190    };
191    let stroke_seen = if instance.stroke_colour[3] > 0.0 {
192        stroke_cover
193    } else {
194        0.0
195    };
196    Paint {
197        colour,
198        footprint: if opacity > 0.0 {
199            fill_seen.max(stroke_seen)
200        } else {
201            0.0
202        },
203    }
204}
205
206fn over(dst: &mut [f32; 4], src: [f32; 4]) {
207    for i in 0..4 {
208        dst[i] = src[i] + dst[i] * (1.0 - src[3]);
209    }
210}
211
212fn blend(
213    image: &mut [[f32; 4]],
214    size: [u32; 2],
215    instance: &Instance,
216    icons: Option<&Icons>,
217    gradient: Option<&Gradient>,
218    sprites: Option<&SpriteImage>,
219) {
220    let [x0, y0, x1, y1] = instance.pixel_bounds();
221    let x0 = x0.floor().max(0.0) as u32;
222    let y0 = y0.floor().max(0.0) as u32;
223    let x1 = (x1.ceil().max(0.0) as u32).min(size[0]);
224    let y1 = (y1.ceil().max(0.0) as u32).min(size[1]);
225    for y in y0..y1 {
226        for x in x0..x1 {
227            let local = instance.to_local([x as f32 + 0.5, y as f32 + 0.5]);
228            let [cx, cy, hx, hy] = instance.bounds;
229            let margin = instance.margin();
230            if (local[0] - cx).abs() > hx + margin[0] || (local[1] - cy).abs() > hy + margin[1] {
231                continue;
232            }
233            let paint = paint_sprite(instance, icons, gradient, sprites, local);
234            over(&mut image[(y * size[0] + x) as usize], paint.colour);
235        }
236    }
237}
238
239pub fn render(
240    scene: &FlatScene<'_>,
241    icons: Option<&Icons>,
242    size: [u32; 2],
243) -> Result<Vec<[f32; 4]>, String> {
244    render_scene(scene, icons, None, size)
245}
246
247pub fn render_with_text(
248    scene: &FlatScene<'_>,
249    icons: Option<&Icons>,
250    texts: &[CpuText<'_>],
251    size: [u32; 2],
252) -> Result<Vec<[f32; 4]>, String> {
253    render_scene(scene, icons, Some(texts), size)
254}
255
256fn label<'a>(
257    texts: &[CpuText<'a>],
258    index: usize,
259    draw: &super::Draw,
260    fit: &Fit,
261    size: [u32; 2],
262) -> Result<Option<Label<'a>>, String> {
263    let text = texts
264        .get(index)
265        .ok_or_else(|| format!("a draw names text {index}, which is missing"))?;
266    if !positive(draw.opacity) || placed(&draw.transform, fit).is_none() {
267        return Ok(None);
268    }
269    Ok(Label::new(text, draw, fit, size))
270}
271
272fn render_scene(
273    scene: &FlatScene<'_>,
274    icons: Option<&Icons>,
275    texts: Option<&[CpuText<'_>]>,
276    size: [u32; 2],
277) -> Result<Vec<[f32; 4]>, String> {
278    let fit = Fit::new(scene.layout, size)?;
279    let sprites = scene.sprites.map(|sprites| &sprites.image);
280    let mut order = Order::default();
281    order.build(scene.draws, scene.groups.len())?;
282    let clear = scene
283        .clear
284        .map_or([0.0; 4], |colour| colour.premultiplied());
285    let pixels = (size[0] * size[1]) as usize;
286    let mut image = vec![clear; pixels];
287    let mut layer: Option<(Vec<[f32; 4]>, f32)> = None;
288    for step in &order.steps {
289        match *step {
290            Step::Layer(group) => {
291                layer = Some((
292                    vec![[0.0; 4]; pixels],
293                    scene.groups[usize::from(group)].opacity,
294                ));
295            }
296            Step::EndLayer(_) => {
297                if let Some((pixels, opacity)) = layer.take() {
298                    for (dst, src) in image.iter_mut().zip(pixels) {
299                        over(dst, src.map(|value| value * opacity));
300                    }
301                }
302            }
303            Step::Draw(index) => {
304                let draw = &scene.draws[index];
305                let target = match layer.as_mut() {
306                    Some((pixels, _)) => pixels,
307                    None => &mut image,
308                };
309                if let Shape::Text(index) = draw.shape {
310                    if let Some(texts) = texts
311                        && let Some(label) = label(texts, index, draw, &fit, size)?
312                    {
313                        label.blend(target, size);
314                    }
315                    continue;
316                }
317                let gradient = match &draw.fill {
318                    Fill::Gradient(gradient) => Some(gradient),
319                    _ => None,
320                };
321                if let Some(instance) = pack_shadow(draw, &fit, scene.curve, &scene.light) {
322                    blend(target, size, &instance, icons, None, None);
323                }
324                if let Some(instance) = pack_shape(draw, &fit, icons) {
325                    blend(target, size, &instance, icons, gradient, sprites);
326                }
327            }
328        }
329    }
330    Ok(image)
331}
332
333pub fn pick(
334    scene: &FlatScene<'_>,
335    icons: Option<&Icons>,
336    size: [u32; 2],
337    pixel: [u32; 2],
338) -> Result<u32, String> {
339    pick_scene(scene, icons, None, size, pixel)
340}
341
342pub fn pick_with_text(
343    scene: &FlatScene<'_>,
344    icons: Option<&Icons>,
345    texts: &[CpuText<'_>],
346    size: [u32; 2],
347    pixel: [u32; 2],
348) -> Result<u32, String> {
349    pick_scene(scene, icons, Some(texts), size, pixel)
350}
351
352fn pick_scene(
353    scene: &FlatScene<'_>,
354    icons: Option<&Icons>,
355    texts: Option<&[CpuText<'_>]>,
356    size: [u32; 2],
357    pixel: [u32; 2],
358) -> Result<u32, String> {
359    let fit = Fit::new(scene.layout, size)?;
360    let mut order = Order::default();
361    order.build(scene.draws, scene.groups.len())?;
362    let centre = [pixel[0] as f32 + 0.5, pixel[1] as f32 + 0.5];
363    let mut id = 0;
364    for step in &order.steps {
365        if let Step::Draw(index) = *step
366            && let Shape::Text(at) = scene.draws[index].shape
367        {
368            let draw = &scene.draws[index];
369            if draw.id != 0
370                && let Some(texts) = texts
371                && let Some(label) = label(texts, at, draw, &fit, size)?
372                && label.picked(pixel)
373            {
374                id = draw.id;
375            }
376            continue;
377        }
378        if let Step::Draw(index) = *step
379            && let Some(instance) = pack_shape(&scene.draws[index], &fit, icons)
380            && instance.info[0] & UNPICKED == 0
381        {
382            let [x0, y0, x1, y1] = instance.pixel_bounds();
383            if centre[0] < x0 || centre[0] > x1 || centre[1] < y0 || centre[1] > y1 {
384                continue;
385            }
386            if plain(&instance, icons, None, None, instance.to_local(centre)).footprint >= 0.5 {
387                id = instance.info[1];
388            }
389        }
390    }
391    Ok(id)
392}