Skip to main content

pfx_live/flat/effects/
blur.rs

1use super::super::cpu::{self, Paint};
2use super::super::icons::Icons;
3use super::super::sdf;
4use super::super::{
5    DASHED, FILL_SOLID, FILL_VERTICAL, Instance, KIND_CIRCLE, KIND_ICON_FILL, KIND_ICON_LINE,
6    KIND_RECT, KIND_RING, PATTERN_MASK, PATTERN_SHIFT,
7};
8
9pub const ROWS: u32 = 4;
10pub const STEPS: u32 = 48;
11pub const FLOOR: f32 = 0.35;
12
13fn distance(instance: &Instance, icons: Option<&Icons>, p: [f32; 2]) -> f32 {
14    let kind = instance.kind();
15    if kind == KIND_ICON_FILL || kind == KIND_ICON_LINE {
16        let [cx, cy, hx, hy] = instance.shape;
17        let c = [p[0].clamp(cx - hx, cx + hx), p[1].clamp(cy - hy, cy + hy)];
18        let outside = ((p[0] - c[0]).powi(2) + (p[1] - c[1]).powi(2)).sqrt();
19        if outside > 0.0 {
20            return outside + 0.5 * instance.stroke[0];
21        }
22        return cpu::field(instance, icons, c)[0];
23    }
24    cpu::field(instance, icons, p)[0]
25}
26
27fn band(instance: &Instance, icons: Option<&Icons>, stroke: bool, p: [f32; 2]) -> f32 {
28    let d = distance(instance, icons, p);
29    if !stroke {
30        return d;
31    }
32    let band = d.abs() - instance.stroke[0] * 0.5;
33    if instance.info[0] & DASHED == 0 {
34        return band;
35    }
36    let along = if instance.kind() == KIND_RECT {
37        sdf::rect_along(p, [instance.shape[0], instance.shape[1]], instance.corners)
38    } else {
39        sdf::circle_along(p, instance.shape[0])
40    };
41    let dash = sdf::dash(
42        along,
43        instance.stroke[1],
44        instance.stroke[2],
45        instance.stroke[3],
46    );
47    band.max(dash[0])
48}
49
50pub fn cdf(x: f32, span: f32) -> f32 {
51    let hi = 0.5 * (span + 1.0);
52    let lo = 0.5 * (span - 1.0).abs();
53    let ramp = span.min(1.0);
54    let height = 1.0 / span.max(1.0);
55    if x <= -hi {
56        0.0
57    } else if x >= hi {
58        1.0
59    } else if x < -lo {
60        let u = x + hi;
61        0.5 * height / ramp * u * u
62    } else if x > lo {
63        let u = hi - x;
64        1.0 - 0.5 * height / ramp * u * u
65    } else {
66        0.5 * height * ramp + height * (x + lo)
67    }
68}
69
70#[allow(clippy::too_many_arguments)]
71fn line(
72    instance: &Instance,
73    icons: Option<&Icons>,
74    stroke: bool,
75    start: [f32; 2],
76    along: [f32; 2],
77    unit: f32,
78    span: f32,
79) -> f32 {
80    let at = |x: f32| [start[0] + along[0] * x, start[1] + along[1] * x];
81    let hi = 0.5 * (span + 1.0);
82    let least = FLOOR.max(2.0 * hi / (STEPS - 8) as f32);
83    let mut x = -hi;
84    let mut s0 = band(instance, icons, stroke, at(x)) / unit;
85    let mut total = 0.0;
86    for _ in 0..STEPS {
87        if x >= hi {
88            break;
89        }
90        let x1 = (x + s0.abs().max(least)).min(hi);
91        let s1 = band(instance, icons, stroke, at(x1)) / unit;
92        if s0 <= 0.0 && s1 <= 0.0 {
93            total += cdf(x1, span) - cdf(x, span);
94        } else if s0 <= 0.0 {
95            let r = x + (x1 - x) * s0 / (s0 - s1);
96            total += cdf(r, span) - cdf(x, span);
97        } else if s1 <= 0.0 {
98            let r = x + (x1 - x) * s0 / (s0 - s1);
99            total += cdf(x1, span) - cdf(r, span);
100        }
101        x = x1;
102        s0 = s1;
103    }
104    if x < hi && s0 <= 0.0 {
105        total += 1.0 - cdf(x, span);
106    }
107    total
108}
109
110type Hit = [f32; 2];
111
112const EMPTY: Hit = [1.0, -1.0];
113
114fn merge(a: Hit, b: Hit) -> Hit {
115    if a[0] >= a[1] {
116        return b;
117    }
118    if b[0] >= b[1] {
119        return a;
120    }
121    [a[0].min(b[0]), a[1].max(b[1])]
122}
123
124fn disc(start: [f32; 2], along: [f32; 2], centre: [f32; 2], radius: f32) -> Hit {
125    if radius <= 0.0 {
126        return EMPTY;
127    }
128    let o = [start[0] - centre[0], start[1] - centre[1]];
129    let aa = along[0] * along[0] + along[1] * along[1];
130    let b = o[0] * along[0] + o[1] * along[1];
131    let c = o[0] * o[0] + o[1] * o[1] - radius * radius;
132    let d = b * b - aa * c;
133    if d <= 0.0 {
134        return EMPTY;
135    }
136    let root = d.sqrt();
137    [(-b - root) / aa, (-b + root) / aa]
138}
139
140fn slab(start: [f32; 2], along: [f32; 2], half: [f32; 2]) -> Hit {
141    if half[0] < 0.0 || half[1] < 0.0 {
142        return EMPTY;
143    }
144    let mut low = -1e30f32;
145    let mut high = 1e30f32;
146    for axis in 0..2 {
147        if along[axis].abs() < 1e-12 {
148            if start[axis].abs() > half[axis] {
149                return EMPTY;
150            }
151        } else {
152            let a = (-half[axis] - start[axis]) / along[axis];
153            let b = (half[axis] - start[axis]) / along[axis];
154            low = low.max(a.min(b));
155            high = high.min(a.max(b));
156        }
157    }
158    [low, high]
159}
160
161fn rounded(start: [f32; 2], along: [f32; 2], half: [f32; 2], radius: f32) -> Hit {
162    if half[0] <= 0.0 || half[1] <= 0.0 {
163        return EMPTY;
164    }
165    let r = radius.clamp(0.0, half[0].min(half[1]));
166    let mut hit = slab(start, along, [half[0], half[1] - r]);
167    hit = merge(hit, slab(start, along, [half[0] - r, half[1]]));
168    if r > 0.0 {
169        let c = [half[0] - r, half[1] - r];
170        for corner in [[c[0], c[1]], [-c[0], c[1]], [c[0], -c[1]], [-c[0], -c[1]]] {
171            hit = merge(hit, disc(start, along, corner, r));
172        }
173    }
174    hit
175}
176
177fn weight(hit: Hit, span: f32) -> f32 {
178    if hit[0] >= hit[1] {
179        return 0.0;
180    }
181    cdf(hit[1], span) - cdf(hit[0], span)
182}
183
184pub fn closed(instance: &Instance, stroke: bool) -> bool {
185    match instance.kind() {
186        KIND_RING => !stroke,
187        KIND_RECT => {
188            instance
189                .corners
190                .iter()
191                .all(|&radius| radius == instance.corners[0])
192                && (!stroke || instance.info[0] & DASHED == 0)
193        }
194        KIND_CIRCLE => !stroke || instance.info[0] & DASHED == 0,
195        _ => false,
196    }
197}
198
199fn row(instance: &Instance, stroke: bool, start: [f32; 2], along: [f32; 2], span: f32) -> f32 {
200    let shape = instance.shape;
201    let w = if stroke {
202        instance.stroke[0] * 0.5
203    } else {
204        0.0
205    };
206    let (outer, inner) = match instance.kind() {
207        KIND_RECT => (
208            rounded(
209                start,
210                along,
211                [shape[0] + w, shape[1] + w],
212                instance.corners[0] + w,
213            ),
214            if stroke {
215                rounded(
216                    start,
217                    along,
218                    [shape[0] - w, shape[1] - w],
219                    (instance.corners[0] - w).max(0.0),
220                )
221            } else {
222                EMPTY
223            },
224        ),
225        KIND_CIRCLE => (
226            disc(start, along, [0.0, 0.0], shape[0] + w),
227            if stroke {
228                disc(start, along, [0.0, 0.0], shape[0] - w)
229            } else {
230                EMPTY
231            },
232        ),
233        _ => (
234            disc(start, along, [0.0, 0.0], shape[0] + 0.5 * shape[1]),
235            disc(start, along, [0.0, 0.0], shape[0] - 0.5 * shape[1]),
236        ),
237    };
238    weight(outer, span) - weight(inner, span)
239}
240
241pub fn cover(instance: &Instance, icons: Option<&Icons>, stroke: bool, p: [f32; 2]) -> f32 {
242    cover_with(instance, icons, stroke, p, closed(instance, stroke))
243}
244
245pub fn cover_with(
246    instance: &Instance,
247    icons: Option<&Icons>,
248    stroke: bool,
249    p: [f32; 2],
250    closed: bool,
251) -> f32 {
252    let inverse = instance.inverse();
253    let shift = [instance.material[0], instance.material[1]];
254    let screen = [
255        instance.axes[0] * shift[0] + instance.axes[2] * shift[1],
256        instance.axes[1] * shift[0] + instance.axes[3] * shift[1],
257    ];
258    let span = (screen[0] * screen[0] + screen[1] * screen[1])
259        .sqrt()
260        .max(1e-6);
261    let direction = [screen[0] / span, screen[1] / span];
262    let map = |v: [f32; 2]| {
263        [
264            inverse[0][0] * v[0] + inverse[1][0] * v[1],
265            inverse[0][1] * v[0] + inverse[1][1] * v[1],
266        ]
267    };
268    let along = map(direction);
269    let across = map([-direction[1], direction[0]]);
270    let unit = (along[0] * along[0] + along[1] * along[1])
271        .sqrt()
272        .max(1e-12);
273    let mut total = 0.0;
274    for index in 0..ROWS {
275        let u = (index as f32 + 0.5) / ROWS as f32 - 0.5;
276        let start = [p[0] + across[0] * u, p[1] + across[1] * u];
277        total += if closed {
278            row(instance, stroke, start, along, span)
279        } else {
280            line(instance, icons, stroke, start, along, unit, span)
281        };
282    }
283    (total / ROWS as f32).clamp(0.0, 1.0)
284}
285
286fn premultiply(c: [f32; 4], cover: f32) -> [f32; 4] {
287    [
288        c[0] * c[3] * cover,
289        c[1] * c[3] * cover,
290        c[2] * c[3] * cover,
291        c[3] * cover,
292    ]
293}
294
295pub fn paint(instance: &Instance, icons: Option<&Icons>, p: [f32; 2]) -> Paint {
296    let inverse = instance.inverse();
297    let flags = instance.info[0];
298    let fill_mode = flags & (FILL_SOLID | FILL_VERTICAL);
299    let opacity = instance.opacity();
300    let mut fill = [0.0; 4];
301    let mut fill_cover = 0.0;
302    if fill_mode != 0 {
303        fill_cover = cover(instance, icons, false, p);
304        let t = if fill_mode == FILL_VERTICAL {
305            ((p[1] - instance.origin[2]) / (instance.origin[3] - instance.origin[2]))
306                .clamp(0.0, 1.0)
307        } else {
308            0.0
309        };
310        let colour: [f32; 4] = std::array::from_fn(|i| {
311            instance.fill_top[i] + (instance.fill_bottom[i] - instance.fill_top[i]) * t
312        });
313        let mut base = premultiply(colour, 1.0);
314        let pattern = (flags & PATTERN_MASK) >> PATTERN_SHIFT;
315        if pattern != 0 {
316            let packed = instance.info[2];
317            let colour = std::array::from_fn(|i| ((packed >> (8 * i)) & 255) as f32 / 255.0);
318            let cover = sdf::coverage(sdf::pattern(pattern, instance.icon, p), inverse);
319            let paint = premultiply(colour, cover);
320            base = std::array::from_fn(|i| paint[i] + base[i] * (1.0 - paint[3]));
321        }
322        fill = base.map(|value| value * fill_cover);
323    }
324    let mut stroke = [0.0; 4];
325    let mut stroke_cover = 0.0;
326    if instance.stroke[0] > 0.0 {
327        stroke_cover = cover(instance, icons, true, p);
328        stroke = premultiply(instance.stroke_colour, stroke_cover);
329    }
330    let colour = std::array::from_fn(|i| (stroke[i] + fill[i] * (1.0 - stroke[3])) * opacity);
331    let fill_seen = if fill_mode != 0 && instance.fill_top[3].max(instance.fill_bottom[3]) > 0.0 {
332        fill_cover
333    } else {
334        0.0
335    };
336    let stroke_seen = if instance.stroke_colour[3] > 0.0 {
337        stroke_cover
338    } else {
339        0.0
340    };
341    Paint {
342        colour,
343        footprint: if opacity > 0.0 {
344            fill_seen.max(stroke_seen)
345        } else {
346            0.0
347        },
348    }
349}