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