Skip to main content

fret_core/scene/
mask.rs

1use crate::ids::ImageId;
2
3use super::{
4    Color, ColorSpace, GradientStop, ImageSamplingHint, LinearGradient, MAX_STOPS, RadialGradient,
5    TileMode, UvRect,
6};
7
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum Mask {
10    LinearGradient(LinearGradient),
11    RadialGradient(RadialGradient),
12    Image {
13        image: ImageId,
14        uv: UvRect,
15        sampling: ImageSamplingHint,
16    },
17}
18
19impl Mask {
20    pub const fn linear_gradient(g: LinearGradient) -> Self {
21        Self::LinearGradient(g)
22    }
23
24    pub const fn radial_gradient(g: RadialGradient) -> Self {
25        Self::RadialGradient(g)
26    }
27
28    pub const fn image(image: ImageId, uv: UvRect) -> Self {
29        Self::Image {
30            image,
31            uv,
32            sampling: ImageSamplingHint::Default,
33        }
34    }
35
36    pub const fn image_with_sampling(
37        image: ImageId,
38        uv: UvRect,
39        sampling: ImageSamplingHint,
40    ) -> Self {
41        Self::Image {
42            image,
43            uv,
44            sampling,
45        }
46    }
47
48    pub fn sanitize(self) -> Option<Self> {
49        fn color_is_finite(c: Color) -> bool {
50            c.r.is_finite() && c.g.is_finite() && c.b.is_finite() && c.a.is_finite()
51        }
52
53        fn stops_all_finite(count: u8, stops: &[GradientStop; MAX_STOPS]) -> bool {
54            let n = usize::from(count).min(MAX_STOPS);
55            for s in stops.iter().take(n) {
56                if !s.offset.is_finite() || !color_is_finite(s.color) {
57                    return false;
58                }
59            }
60            true
61        }
62
63        fn clamp01(x: f32) -> f32 {
64            x.clamp(0.0, 1.0)
65        }
66
67        fn sort_stops(
68            count: u8,
69            mut stops: [GradientStop; MAX_STOPS],
70        ) -> [GradientStop; MAX_STOPS] {
71            let n = usize::from(count).min(MAX_STOPS);
72            for stop in stops.iter_mut().take(n) {
73                stop.offset = clamp01(stop.offset);
74            }
75
76            for i in 1..n {
77                let key = stops[i];
78                let mut j = i;
79                while j > 0 && stops[j - 1].offset > key.offset {
80                    stops[j] = stops[j - 1];
81                    j -= 1;
82                }
83                stops[j] = key;
84            }
85            stops
86        }
87
88        fn normalize_stop_count(count: u8) -> u8 {
89            count.min(MAX_STOPS as u8)
90        }
91
92        fn degrade_tile_mode(tile_mode: TileMode) -> TileMode {
93            tile_mode
94        }
95
96        fn degrade_color_space(color_space: ColorSpace) -> ColorSpace {
97            color_space
98        }
99
100        match self {
101            Mask::LinearGradient(mut g) => {
102                g.stop_count = normalize_stop_count(g.stop_count);
103                g.tile_mode = degrade_tile_mode(g.tile_mode);
104                g.color_space = degrade_color_space(g.color_space);
105
106                if !g.start.x.0.is_finite()
107                    || !g.start.y.0.is_finite()
108                    || !g.end.x.0.is_finite()
109                    || !g.end.y.0.is_finite()
110                    || !stops_all_finite(g.stop_count, &g.stops)
111                {
112                    return None;
113                }
114
115                g.stops = sort_stops(g.stop_count, g.stops);
116                Some(Mask::LinearGradient(g))
117            }
118            Mask::RadialGradient(mut g) => {
119                g.stop_count = normalize_stop_count(g.stop_count);
120                g.tile_mode = degrade_tile_mode(g.tile_mode);
121                g.color_space = degrade_color_space(g.color_space);
122
123                if !g.center.x.0.is_finite()
124                    || !g.center.y.0.is_finite()
125                    || !g.radius.width.0.is_finite()
126                    || !g.radius.height.0.is_finite()
127                    || !stops_all_finite(g.stop_count, &g.stops)
128                {
129                    return None;
130                }
131
132                g.stops = sort_stops(g.stop_count, g.stops);
133                Some(Mask::RadialGradient(g))
134            }
135            Mask::Image {
136                image,
137                mut uv,
138                sampling,
139            } => {
140                if !uv.u0.is_finite()
141                    || !uv.v0.is_finite()
142                    || !uv.u1.is_finite()
143                    || !uv.v1.is_finite()
144                {
145                    return None;
146                }
147
148                uv.u0 = clamp01(uv.u0);
149                uv.v0 = clamp01(uv.v0);
150                uv.u1 = clamp01(uv.u1);
151                uv.v1 = clamp01(uv.v1);
152
153                if uv.u0 > uv.u1 {
154                    std::mem::swap(&mut uv.u0, &mut uv.u1);
155                }
156                if uv.v0 > uv.v1 {
157                    std::mem::swap(&mut uv.v0, &mut uv.v1);
158                }
159
160                Some(Mask::Image {
161                    image,
162                    uv,
163                    sampling,
164                })
165            }
166        }
167    }
168}