1use crate::color::{AlphaColor, ColorComponents, ColorSpace};
4use crate::interpret::state::ActiveTransferFunction;
5use crate::pattern::ShadingPattern;
6use crate::shading::{ShadingFunction, ShadingType, Triangle};
7use crate::util::x_y_advances;
8use kurbo::{Affine, Point, Rect};
9use rustc_hash::FxHashMap;
10use smallvec::{ToSmallVec, smallvec};
11use std::sync::Arc;
12
13#[derive(Clone, Debug)]
15pub struct EncodedShadingPattern {
16 pub base_transform: Affine,
18 pub(crate) color_space: ColorSpace,
19 pub(crate) background_color: AlphaColor,
20 pub shading_type: EncodedShadingType,
22 pub(crate) opacity: f32,
23 pub(crate) transfer_function: Option<ActiveTransferFunction>,
24}
25
26#[derive(Clone, Debug)]
28pub enum EncodedShadingType {
29 FunctionBased(EncodedFunctionBasedShading),
31 RadialAxial(EncodedRadialAxialShading),
33 Sampled(EncodedSampledShading),
35 Dummy(EncodedDummyShading),
37}
38
39#[derive(Clone, Debug)]
41pub struct EncodedFunctionBasedShading {
42 pub(crate) domain: Rect,
43 pub(crate) function: ShadingFunction,
44}
45
46#[derive(Clone, Debug)]
48pub struct EncodedRadialAxialShading {
49 pub(crate) function: ShadingFunction,
50 pub(crate) params: RadialAxialParams,
51 pub(crate) domain: [f32; 2],
52 pub(crate) extend: [bool; 2],
53}
54
55#[derive(Clone, Debug)]
57pub struct EncodedSampledShading {
58 pub(crate) samples: Arc<FxHashMap<(u16, u16), ColorComponents>>,
59 pub(crate) function: Option<ShadingFunction>,
60}
61
62#[derive(Clone, Debug)]
64pub struct EncodedDummyShading;
65
66pub struct ShadingTexture {
68 pub data: Vec<u8>,
70 pub width: u32,
72 pub height: u32,
74 pub transform: Affine,
77 pub has_transparency: bool,
79}
80
81pub fn texture_dimensions(bbox: Rect, scale: f32) -> (u32, u32) {
87 (
88 (bbox.width() as f32 * scale).max(1.0).ceil() as u32,
89 (bbox.height() as f32 * scale).max(1.0).ceil() as u32,
90 )
91}
92
93impl EncodedShadingPattern {
94 #[inline]
96 pub fn sample(&self, pos: Point) -> [f32; 4] {
97 self.shading_type
98 .eval(pos, self.background_color, &self.color_space)
99 .map(|v| {
100 let mut components = v.components();
101 components[3] *= self.opacity;
102
103 if let Some(tf) = &self.transfer_function {
104 return tf.apply(&AlphaColor::new(components)).components();
105 }
106
107 components
108 })
109 .unwrap_or([0.0, 0.0, 0.0, 0.0])
110 }
111
112 pub fn sample_texture(
122 &self,
123 bbox: Rect,
124 scale: f32,
125 mut f: impl FnMut([f32; 4]),
126 ) -> (u32, u32, Affine) {
127 let (width, height) = texture_dimensions(bbox, scale);
128 let inv_scale = 1.0 / scale as f64;
129
130 let (x_advance, y_advance) =
131 x_y_advances(&(Affine::scale(inv_scale) * self.base_transform));
132
133 let mut start_point = self.base_transform
134 * Affine::translate((0.5 * inv_scale, 0.5 * inv_scale))
135 * Point::new(bbox.x0, bbox.y0);
136
137 for _ in 0..height {
138 let mut point = start_point;
139
140 for _ in 0..width {
141 f(self.sample(point));
142
143 point += x_advance;
144 }
145
146 start_point += y_advance;
147 }
148
149 (
150 width,
151 height,
152 Affine::translate((bbox.x0, bbox.y0)) * Affine::scale(inv_scale),
153 )
154 }
155
156 pub fn render_texture(&self, bbox: Rect, scale: f32) -> ShadingTexture {
161 let (width, height) = texture_dimensions(bbox, scale);
162 let mut data = Vec::with_capacity(width as usize * height as usize * 4);
163 let mut has_transparency = false;
164
165 let (_, _, transform) = self.sample_texture(bbox, scale, |sample| {
166 let converted = [
167 (sample[0] * 255.0 + 0.5) as u8,
168 (sample[1] * 255.0 + 0.5) as u8,
169 (sample[2] * 255.0 + 0.5) as u8,
170 (sample[3] * 255.0 + 0.5) as u8,
171 ];
172
173 has_transparency |= converted[3] != 255;
174 data.extend_from_slice(&converted);
175 });
176
177 ShadingTexture {
178 data,
179 width,
180 height,
181 transform,
182 has_transparency,
183 }
184 }
185}
186
187impl ShadingPattern {
188 pub fn encode(&self) -> EncodedShadingPattern {
190 let base_transform;
191
192 let shading_type = match self.shading.shading_type.as_ref() {
193 ShadingType::FunctionBased {
194 domain,
195 matrix,
196 function,
197 } => {
198 base_transform = (self.matrix * *matrix).inverse();
199 encode_function_shading(domain, function)
200 }
201 ShadingType::RadialAxial {
202 coords,
203 domain,
204 function,
205 extend,
206 axial,
207 } => {
208 let (encoded, initial_transform) =
209 encode_axial_shading(*coords, *domain, function, *extend, *axial);
210
211 base_transform = initial_transform * self.matrix.inverse();
212
213 encoded
214 }
215 ShadingType::TriangleMesh {
216 triangles,
217 function,
218 } => {
219 let full_transform = self.matrix;
220 let samples = sample_triangles(triangles, full_transform);
221
222 base_transform = Affine::IDENTITY;
223
224 EncodedShadingType::Sampled(EncodedSampledShading {
225 samples: Arc::new(samples),
226 function: function.clone(),
227 })
228 }
229 ShadingType::CoonsPatchMesh { patches, function } => {
230 let mut triangles = vec![];
231 for patch in patches {
232 patch.to_triangles(&mut triangles);
233 }
234
235 let full_transform = self.matrix;
236 let samples = sample_triangles(&triangles, full_transform);
237
238 base_transform = Affine::IDENTITY;
239
240 EncodedShadingType::Sampled(EncodedSampledShading {
241 samples: Arc::new(samples),
242 function: function.clone(),
243 })
244 }
245 ShadingType::TensorProductPatchMesh { patches, function } => {
246 let mut triangles = vec![];
247 for patch in patches {
248 patch.to_triangles(&mut triangles);
249 }
250
251 let full_transform = self.matrix;
252 let samples = sample_triangles(&triangles, full_transform);
253
254 base_transform = Affine::IDENTITY;
255
256 EncodedShadingType::Sampled(EncodedSampledShading {
257 samples: Arc::new(samples),
258 function: function.clone(),
259 })
260 }
261 ShadingType::Dummy => {
262 base_transform = Affine::IDENTITY;
263
264 EncodedShadingType::Dummy(EncodedDummyShading)
265 }
266 };
267
268 let color_space = self.shading.color_space.clone();
269
270 let background_color = self
271 .shading
272 .background
273 .as_ref()
274 .map(|b| color_space.to_rgba(b, 1.0))
275 .unwrap_or(AlphaColor::TRANSPARENT);
276
277 EncodedShadingPattern {
278 color_space,
279 background_color,
280 shading_type,
281 base_transform,
282 opacity: self.opacity,
283 transfer_function: self.transfer_function.clone(),
284 }
285 }
286}
287
288fn encode_axial_shading(
289 coords: [f32; 6],
290 domain: [f32; 2],
291 function: &ShadingFunction,
292 extend: [bool; 2],
293 is_axial: bool,
294) -> (EncodedShadingType, Affine) {
295 let initial_transform;
296
297 let params = if is_axial {
298 let [x_0, y_0, x_1, y_1, _, _] = coords;
299
300 initial_transform = ts_from_line_to_line(
301 Point::new(x_0 as f64, y_0 as f64),
302 Point::new(x_1 as f64, y_1 as f64),
303 Point::ZERO,
304 Point::new(1.0, 0.0),
305 );
306
307 RadialAxialParams::Axial
308 } else {
309 let [x_0, y_0, r0, x_1, y_1, r_1] = coords;
310
311 initial_transform = Affine::translate((-x_0 as f64, -y_0 as f64));
312 let new_x1 = x_1 - x_0;
313 let new_y1 = y_1 - y_0;
314
315 let p1 = Point::new(new_x1 as f64, new_y1 as f64);
316 let r = Point::new(r0 as f64, r_1 as f64);
317
318 RadialAxialParams::Radial { p1, r }
319 };
320
321 (
322 EncodedShadingType::RadialAxial(EncodedRadialAxialShading {
323 function: function.clone(),
324 params,
325 domain,
326 extend,
327 }),
328 initial_transform,
329 )
330}
331
332fn sample_triangles(
333 triangles: &[Triangle],
334 transform: Affine,
335) -> FxHashMap<(u16, u16), ColorComponents> {
336 let mut map = FxHashMap::default();
337
338 for t in triangles {
339 let t = {
340 let p0 = transform * t.p0.point;
341 let p1 = transform * t.p1.point;
342 let p2 = transform * t.p2.point;
343
344 let mut v0 = t.p0.clone();
345 v0.point = p0;
346 let mut v1 = t.p1.clone();
347 v1.point = p1;
348 let mut v2 = t.p2.clone();
349 v2.point = p2;
350
351 Triangle::new(v0, v1, v2)
352 };
353
354 let bbox = t.bounding_box();
355
356 for y in (bbox.y0.floor() as u16)..(bbox.y1.ceil() as u16) {
357 for x in (bbox.x0.floor() as u16)..(bbox.x1.ceil() as u16) {
358 let point = Point::new(x as f64, y as f64);
359 if t.contains_point(point) {
360 map.insert((x, y), t.interpolate(point));
361 }
362 }
363 }
364 }
365
366 map
367}
368
369fn encode_function_shading(domain: &[f32; 4], function: &ShadingFunction) -> EncodedShadingType {
370 let domain = Rect::new(
371 domain[0] as f64,
372 domain[2] as f64,
373 domain[1] as f64,
374 domain[3] as f64,
375 );
376
377 EncodedShadingType::FunctionBased(EncodedFunctionBasedShading {
378 domain,
379 function: function.clone(),
380 })
381}
382
383#[derive(Clone, Debug)]
384pub(crate) enum RadialAxialParams {
385 Axial,
386 Radial { p1: Point, r: Point },
387}
388
389impl EncodedShadingType {
390 pub(crate) fn eval(
391 &self,
392 pos: Point,
393 bg_color: AlphaColor,
394 color_space: &ColorSpace,
395 ) -> Option<AlphaColor> {
396 match self {
397 Self::FunctionBased(EncodedFunctionBasedShading { domain, function }) => {
398 if !domain.contains(pos) {
399 Some(bg_color)
400 } else {
401 let out = function.eval(&smallvec![pos.x as f32, pos.y as f32])?;
402 Some(color_space.to_rgba(&out, 1.0))
404 }
405 }
406 Self::RadialAxial(EncodedRadialAxialShading {
407 function,
408 params,
409 domain,
410 extend,
411 }) => {
412 let (t0, t1) = (domain[0], domain[1]);
413
414 let mut t = match params {
415 RadialAxialParams::Axial => pos.x as f32,
416 RadialAxialParams::Radial { p1, r } => {
417 radial_pos(&pos, p1, *r, extend[0], extend[1]).unwrap_or(f32::MIN)
418 }
419 };
420
421 if t == f32::MIN {
422 return Some(bg_color);
423 }
424
425 if t < 0.0 {
426 if extend[0] {
427 t = 0.0;
428 } else {
429 return Some(bg_color);
430 }
431 } else if t > 1.0 {
432 if extend[1] {
433 t = 1.0;
434 } else {
435 return Some(bg_color);
436 }
437 }
438
439 let t = t0 + (t1 - t0) * t;
440
441 let val = function.eval(&smallvec![t])?;
442
443 Some(color_space.to_rgba(&val, 1.0))
444 }
445 Self::Sampled(EncodedSampledShading { samples, function }) => {
446 let sample_point = (pos.x as u16, pos.y as u16);
447
448 if let Some(color) = samples.get(&sample_point) {
449 if let Some(function) = function {
450 let val = function.eval(&color.to_smallvec())?;
451 Some(color_space.to_rgba(&val, 1.0))
452 } else {
453 Some(color_space.to_rgba(color, 1.0))
454 }
455 } else {
456 Some(bg_color)
457 }
458 }
459 Self::Dummy(_) => Some(AlphaColor::TRANSPARENT),
460 }
461 }
462}
463
464fn ts_from_line_to_line(src1: Point, src2: Point, dst1: Point, dst2: Point) -> Affine {
465 let unit_to_line1 = unit_to_line(src1, src2);
466 let line1_to_unit = unit_to_line1.inverse();
467 let unit_to_line2 = unit_to_line(dst1, dst2);
468
469 unit_to_line2 * line1_to_unit
470}
471
472fn unit_to_line(p0: Point, p1: Point) -> Affine {
473 Affine::new([
474 p1.y - p0.y,
475 p0.x - p1.x,
476 p1.x - p0.x,
477 p1.y - p0.y,
478 p0.x,
479 p0.y,
480 ])
481}
482
483fn radial_pos(
484 pos: &Point,
485 p1: &Point,
486 r: Point,
487 min_extend: bool,
488 max_extend: bool,
489) -> Option<f32> {
490 let r0 = r.x as f32;
491 let dx = p1.x as f32;
492 let dy = p1.y as f32;
493 let dr = r.y as f32 - r0;
494
495 let px = pos.x as f32;
496 let py = pos.y as f32;
497
498 let a = dx * dx + dy * dy - dr * dr;
499 let b = -2.0 * (px * dx + py * dy + r0 * dr);
500 let c = px * px + py * py - r0 * r0;
501
502 let discriminant = b * b - 4.0 * a * c;
503
504 if discriminant < 0.0 {
506 return None;
507 }
508
509 if a.abs() < 1e-6 {
510 if b.abs() < 1e-6 {
511 return None;
512 }
513
514 let t = -c / b;
515
516 if (!min_extend && t < 0.0) || (!max_extend && t > 1.0) {
517 return None;
518 }
519
520 let r_t = r0 + dr * t;
521 if r_t < 0.0 {
522 return None;
523 }
524
525 return Some(t);
526 }
527
528 let sqrt_d = discriminant.sqrt();
529 let t1 = (-b - sqrt_d) / (2.0 * a);
530 let t2 = (-b + sqrt_d) / (2.0 * a);
531
532 let max = t1.max(t2);
533 let mut take_max = Some(max);
534 let min = t1.min(t2);
535 let mut take_min = Some(min);
536
537 if (!min_extend && min < 0.0) || r0 + dr * min < 0.0 {
538 take_min = None;
539 }
540
541 if (!max_extend && max > 1.0) || r0 + dr * max < 0.0 {
542 take_max = None;
543 }
544
545 match (take_min, take_max) {
546 (Some(_), Some(max)) => Some(max),
547 (Some(min), None) => Some(min),
548 (None, Some(max)) => Some(max),
549 (None, None) => None,
550 }
551}