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}