1use crate::color::{AlphaColor, ColorComponents, ColorSpace};
4use crate::pattern::ShadingPattern;
5use crate::shading::{ShadingFunction, ShadingType, Triangle};
6use kurbo::{Affine, Point};
7use rustc_hash::FxHashMap;
8use smallvec::{ToSmallVec, smallvec};
9
10#[derive(Debug)]
12pub struct EncodedShadingPattern {
13 pub base_transform: Affine,
15 pub(crate) color_space: ColorSpace,
16 pub(crate) background_color: AlphaColor,
17 pub(crate) shading_type: EncodedShadingType,
18}
19
20impl EncodedShadingPattern {
21 pub fn sample(&self, pos: Point) -> [f32; 4] {
23 self.shading_type
24 .eval(pos, self.background_color, &self.color_space)
25 .map(|v| v.components())
26 .unwrap_or([0.0, 0.0, 0.0, 0.0])
27 }
28}
29
30impl ShadingPattern {
31 pub fn encode(&self) -> EncodedShadingPattern {
33 let base_transform;
34
35 let shading_type = match self.shading.shading_type.as_ref() {
36 ShadingType::FunctionBased {
37 domain,
38 matrix,
39 function,
40 } => {
41 base_transform = (self.matrix * *matrix).inverse();
42 encode_function_shading(domain, function)
43 }
44 ShadingType::RadialAxial {
45 coords,
46 domain,
47 function,
48 extend,
49 axial,
50 } => {
51 let (encoded, initial_transform) =
52 encode_axial_shading(*coords, *domain, function, *extend, *axial);
53
54 base_transform = initial_transform * self.matrix.inverse();
55
56 encoded
57 }
58 ShadingType::TriangleMesh {
59 triangles,
60 function,
61 } => {
62 let full_transform = self.matrix;
63 let samples = sample_triangles(triangles, full_transform);
64
65 base_transform = Affine::IDENTITY;
66
67 EncodedShadingType::Sampled {
68 samples,
69 function: function.clone(),
70 }
71 }
72 ShadingType::CoonsPatchMesh { patches, function } => {
73 let triangles = patches
74 .iter()
75 .flat_map(|p| p.to_triangles())
76 .collect::<Vec<_>>();
77
78 let full_transform = self.matrix;
79 let samples = sample_triangles(&triangles, full_transform);
80
81 base_transform = Affine::IDENTITY;
82
83 EncodedShadingType::Sampled {
84 samples,
85 function: function.clone(),
86 }
87 }
88 ShadingType::TensorProductPatchMesh { patches, function } => {
89 let triangles = patches
90 .iter()
91 .flat_map(|p| p.to_triangles())
92 .collect::<Vec<_>>();
93
94 let full_transform = self.matrix;
95 let samples = sample_triangles(&triangles, full_transform);
96
97 base_transform = Affine::IDENTITY;
98
99 EncodedShadingType::Sampled {
100 samples,
101 function: function.clone(),
102 }
103 }
104 ShadingType::Dummy => {
105 base_transform = Affine::IDENTITY;
106
107 EncodedShadingType::Dummy
108 }
109 };
110
111 let color_space = self.shading.color_space.clone();
112
113 let background_color = self
114 .shading
115 .background
116 .as_ref()
117 .map(|b| color_space.to_rgba(b, 1.0))
118 .unwrap_or(AlphaColor::TRANSPARENT);
119
120 EncodedShadingPattern {
121 color_space,
122 background_color,
123 shading_type,
124 base_transform,
125 }
126 }
127}
128
129fn encode_axial_shading(
130 coords: [f32; 6],
131 domain: [f32; 2],
132 function: &ShadingFunction,
133 extend: [bool; 2],
134 is_axial: bool,
135) -> (EncodedShadingType, Affine) {
136 let initial_transform;
137
138 let params = if is_axial {
139 let [x_0, y_0, x_1, y_1, _, _] = coords;
140
141 initial_transform = ts_from_line_to_line(
142 Point::new(x_0 as f64, y_0 as f64),
143 Point::new(x_1 as f64, y_1 as f64),
144 Point::ZERO,
145 Point::new(1.0, 0.0),
146 );
147
148 RadialAxialParams::Axial
149 } else {
150 let [x_0, y_0, r0, x_1, y_1, r_1] = coords;
151
152 initial_transform = Affine::translate((-x_0 as f64, -y_0 as f64));
153 let new_x1 = x_1 - x_0;
154 let new_y1 = y_1 - y_0;
155
156 let p1 = Point::new(new_x1 as f64, new_y1 as f64);
157 let r = Point::new(r0 as f64, r_1 as f64);
158
159 RadialAxialParams::Radial { p1, r }
160 };
161
162 (
163 EncodedShadingType::RadialAxial {
164 function: function.clone(),
165 params,
166 domain,
167 extend,
168 },
169 initial_transform,
170 )
171}
172
173fn sample_triangles(
174 triangles: &[Triangle],
175 transform: Affine,
176) -> FxHashMap<(u16, u16), ColorComponents> {
177 let mut map = FxHashMap::default();
178
179 for t in triangles {
180 let t = {
181 let p0 = transform * t.p0.point;
182 let p1 = transform * t.p1.point;
183 let p2 = transform * t.p2.point;
184
185 let mut v0 = t.p0.clone();
186 v0.point = p0;
187 let mut v1 = t.p1.clone();
188 v1.point = p1;
189 let mut v2 = t.p2.clone();
190 v2.point = p2;
191
192 Triangle::new(v0, v1, v2)
193 };
194
195 let bbox = t.bounding_box();
196
197 for y in (bbox.y0.floor() as u16)..(bbox.y1.ceil() as u16) {
198 for x in (bbox.x0.floor() as u16)..(bbox.x1.ceil() as u16) {
199 let point = Point::new(x as f64, y as f64);
200 if t.contains_point(point) {
201 map.insert((x, y), t.interpolate(point));
202 }
203 }
204 }
205 }
206
207 map
208}
209
210fn encode_function_shading(domain: &[f32; 4], function: &ShadingFunction) -> EncodedShadingType {
211 let domain = kurbo::Rect::new(
212 domain[0] as f64,
213 domain[2] as f64,
214 domain[1] as f64,
215 domain[3] as f64,
216 );
217
218 EncodedShadingType::FunctionBased {
219 domain,
220 function: function.clone(),
221 }
222}
223
224#[derive(Debug)]
225pub(crate) enum RadialAxialParams {
226 Axial,
227 Radial { p1: Point, r: Point },
228}
229
230#[derive(Debug)]
231pub(crate) enum EncodedShadingType {
232 FunctionBased {
233 domain: kurbo::Rect,
234 function: ShadingFunction,
235 },
236 RadialAxial {
237 function: ShadingFunction,
238 params: RadialAxialParams,
239 domain: [f32; 2],
240 extend: [bool; 2],
241 },
242 Sampled {
243 samples: FxHashMap<(u16, u16), ColorComponents>,
244 function: Option<ShadingFunction>,
245 },
246 Dummy,
247}
248
249impl EncodedShadingType {
250 pub(crate) fn eval(
251 &self,
252 pos: Point,
253 bg_color: AlphaColor,
254 color_space: &ColorSpace,
255 ) -> Option<AlphaColor> {
256 match self {
257 EncodedShadingType::FunctionBased { domain, function } => {
258 if !domain.contains(pos) {
259 Some(bg_color)
260 } else {
261 let out = function.eval(&smallvec![pos.x as f32, pos.y as f32])?;
262 Some(color_space.to_rgba(&out, 1.0))
264 }
265 }
266 EncodedShadingType::RadialAxial {
267 function,
268 params,
269 domain,
270 extend,
271 } => {
272 let (t0, t1) = (domain[0], domain[1]);
273
274 let mut t = match params {
275 RadialAxialParams::Axial => pos.x as f32,
276 RadialAxialParams::Radial { p1, r } => {
277 radial_pos(&pos, p1, *r, extend[0], extend[1]).unwrap_or(f32::MIN)
278 }
279 };
280
281 if t == f32::MIN {
282 return Some(bg_color);
283 }
284
285 if t < 0.0 {
286 if extend[0] {
287 t = 0.0;
288 } else {
289 return Some(bg_color);
290 }
291 } else if t > 1.0 {
292 if extend[1] {
293 t = 1.0;
294 } else {
295 return Some(bg_color);
296 }
297 }
298
299 let t = t0 + (t1 - t0) * t;
300
301 let val = function.eval(&smallvec![t])?;
302
303 Some(color_space.to_rgba(&val, 1.0))
304 }
305 EncodedShadingType::Sampled { samples, function } => {
306 let sample_point = (pos.x as u16, pos.y as u16);
307
308 if let Some(color) = samples.get(&sample_point) {
309 if let Some(function) = function {
310 let val = function.eval(&color.to_smallvec())?;
311 Some(color_space.to_rgba(&val, 1.0))
312 } else {
313 Some(color_space.to_rgba(color, 1.0))
314 }
315 } else {
316 Some(bg_color)
317 }
318 }
319 EncodedShadingType::Dummy => Some(AlphaColor::TRANSPARENT),
320 }
321 }
322}
323
324fn ts_from_line_to_line(src1: Point, src2: Point, dst1: Point, dst2: Point) -> Affine {
325 let unit_to_line1 = unit_to_line(src1, src2);
326 let line1_to_unit = unit_to_line1.inverse();
327 let unit_to_line2 = unit_to_line(dst1, dst2);
328
329 unit_to_line2 * line1_to_unit
330}
331
332fn unit_to_line(p0: Point, p1: Point) -> Affine {
333 Affine::new([
334 p1.y - p0.y,
335 p0.x - p1.x,
336 p1.x - p0.x,
337 p1.y - p0.y,
338 p0.x,
339 p0.y,
340 ])
341}
342
343fn radial_pos(
344 pos: &Point,
345 p1: &Point,
346 r: Point,
347 min_extend: bool,
348 max_extend: bool,
349) -> Option<f32> {
350 let r0 = r.x as f32;
351 let dx = p1.x as f32;
352 let dy = p1.y as f32;
353 let dr = r.y as f32 - r0;
354
355 let px = pos.x as f32;
356 let py = pos.y as f32;
357
358 let a = dx * dx + dy * dy - dr * dr;
359 let b = -2.0 * (px * dx + py * dy + r0 * dr);
360 let c = px * px + py * py - r0 * r0;
361
362 let discriminant = b * b - 4.0 * a * c;
363
364 if discriminant < 0.0 {
366 return None;
367 }
368
369 if a.abs() < 1e-6 {
370 if b.abs() < 1e-6 {
371 return None;
372 }
373
374 let t = -c / b;
375
376 if (!min_extend && t < 0.0) || (!max_extend && t > 1.0) {
377 return None;
378 }
379
380 let r_t = r0 + dr * t;
381 if r_t < 0.0 {
382 return None;
383 }
384
385 return Some(t);
386 }
387
388 let sqrt_d = discriminant.sqrt();
389 let t1 = (-b - sqrt_d) / (2.0 * a);
390 let t2 = (-b + sqrt_d) / (2.0 * a);
391
392 let max = t1.max(t2);
393 let mut take_max = Some(max);
394 let min = t1.min(t2);
395 let mut take_min = Some(min);
396
397 if (!min_extend && min < 0.0) || r0 + dr * min < 0.0 {
398 take_min = None;
399 }
400
401 if (!max_extend && max > 1.0) || r0 + dr * max < 0.0 {
402 take_max = None;
403 }
404
405 match (take_min, take_max) {
406 (Some(_), Some(max)) => Some(max),
407 (Some(min), None) => Some(min),
408 (None, Some(max)) => Some(max),
409 (None, None) => None,
410 }
411}