1use crate::blurred_rounded_rect::BlurredRoundedRectangle;
7use crate::color::palette::css::BLACK;
8use crate::color::{ColorSpaceTag, HueDirection, Srgb, gradient};
9use crate::kurbo::{Affine, Point, Vec2};
10use crate::math::{FloatExt, compute_erf7};
11use crate::paint::{Image, ImageSource, IndexedPaint, Paint, PremulColor, Tint};
12use crate::peniko::{ColorStop, ColorStops, Extend, Gradient, GradientKind, ImageQuality};
13use crate::util::f32_to_u8;
14use alloc::borrow::Cow;
15use alloc::fmt::Debug;
16use alloc::vec;
17use alloc::vec::Vec;
18use bytemuck::Pod;
19#[cfg(not(feature = "multithreading"))]
20use core::cell::OnceCell;
21use core::hash::{Hash, Hasher};
22use fearless_simd::{Simd, SimdBase, SimdFloat, SimdFrom, f32x4, f32x16, mask32x16};
23use peniko::color::cache_key::{BitEq, BitHash, CacheKey};
24use peniko::color::gradient_unpremultiplied;
25use peniko::{
26 ImageSampler, InterpolationAlphaSpace, LinearGradientPosition, RadialGradientPosition,
27 SweepGradientPosition,
28};
29use smallvec::ToSmallVec;
30#[cfg(feature = "multithreading")]
32use std::sync::OnceLock as OnceCell;
33
34use crate::simd::{Splat4thExt, element_wise_splat};
35#[cfg(not(feature = "std"))]
36use peniko::kurbo::common::FloatFuncs as _;
37
38const DEGENERATE_THRESHOLD: f32 = 1.0e-6;
39const NUDGE_VAL: f32 = 1.0e-7;
40#[cfg(feature = "std")]
41fn exp(val: f32) -> f32 {
42 val.exp()
43}
44
45#[cfg(not(feature = "std"))]
46fn exp(val: f32) -> f32 {
47 #[cfg(feature = "libm")]
48 return libm::expf(val);
49 #[cfg(not(feature = "libm"))]
50 compile_error!("vello_common requires either the `std` or `libm` feature");
51}
52
53pub trait EncodeExt: private::Sealed {
55 fn encode_into(
58 &self,
59 paints: &mut Vec<EncodedPaint>,
60 transform: Affine,
61 tint: Option<Tint>,
62 ) -> Paint;
63}
64
65impl EncodeExt for Gradient {
66 fn encode_into(
68 &self,
69 paints: &mut Vec<EncodedPaint>,
70 transform: Affine,
71 _tint: Option<Tint>,
72 ) -> Paint {
73 if let Err(paint) = validate(self) {
75 return paint;
76 }
77
78 let mut may_have_transparency = self.stops.iter().any(|s| s.color.components[3] != 1.0);
79
80 let mut base_transform;
81
82 let mut stops = Cow::Borrowed(&self.stops.0);
83
84 let first_stop = &stops[0];
85 let last_stop = &stops[stops.len() - 1];
86
87 if first_stop.offset != 0.0 || last_stop.offset != 1.0 {
88 let mut vec = stops.to_smallvec();
89
90 if first_stop.offset != 0.0 {
91 let mut first_stop = *first_stop;
92 first_stop.offset = 0.0;
93 vec.insert(0, first_stop);
94 }
95
96 if last_stop.offset != 1.0 {
97 let mut last_stop = *last_stop;
98 last_stop.offset = 1.0;
99 vec.push(last_stop);
100 }
101
102 stops = Cow::Owned(vec);
103 }
104
105 let kind = match self.kind {
106 GradientKind::Linear(LinearGradientPosition { start: p0, end: p1 }) => {
107 base_transform = ts_from_line_to_line(p0, p1, Point::ZERO, Point::new(1.0, 0.0));
111
112 EncodedKind::Linear(LinearKind)
113 }
114 GradientKind::Radial(RadialGradientPosition {
115 start_center: c0,
116 start_radius: r0,
117 end_center: c1,
118 end_radius: r1,
119 }) => {
120 let d_radius = r1 - r0;
126
127 let radial_kind = if ((c1 - c0).length() as f32).is_nearly_zero() {
129 base_transform = Affine::translate((-c1.x, -c1.y));
130 base_transform = base_transform.then_scale(1.0 / r0.max(r1) as f64);
131
132 let scale = r1.max(r0) / d_radius;
133 let bias = -r0 / d_radius;
134
135 RadialKind::Radial { bias, scale }
136 } else {
137 base_transform =
138 ts_from_line_to_line(c0, c1, Point::ZERO, Point::new(1.0, 0.0));
139
140 if (r1 - r0).is_nearly_zero() {
141 let scaled_r0 = r1 / (c1 - c0).length() as f32;
142 RadialKind::Strip {
143 scaled_r0_squared: scaled_r0 * scaled_r0,
144 }
145 } else {
146 let d_center = (c0 - c1).length() as f32;
147
148 let focal_data =
149 FocalData::create(r0 / d_center, r1 / d_center, &mut base_transform);
150
151 let fp0 = 1.0 / focal_data.fr1;
152 let fp1 = focal_data.f_focal_x;
153
154 RadialKind::Focal {
155 focal_data,
156 fp0,
157 fp1,
158 }
159 }
160 };
161
162 may_have_transparency |= radial_kind.has_undefined();
167
168 EncodedKind::Radial(radial_kind)
169 }
170 GradientKind::Sweep(SweepGradientPosition {
171 center,
172 start_angle,
173 end_angle,
174 }) => {
175 let x_offset = -center.x as f32;
178 let y_offset = -center.y as f32;
179 base_transform = Affine::translate((x_offset as f64, y_offset as f64));
180
181 EncodedKind::Sweep(SweepKind {
182 start_angle,
183 inv_angle_delta: 1.0 / (end_angle - start_angle),
185 })
186 }
187 };
188
189 let ranges = encode_stops(
190 &stops,
191 self.interpolation_cs,
192 self.hue_direction,
193 self.interpolation_alpha_space,
194 );
195
196 let transform = base_transform * transform.inverse();
202
203 let (x_advance, y_advance) = x_y_advances(&transform);
211
212 let cache_key = CacheKey(GradientCacheKey {
213 stops: self.stops.clone(),
214 interpolation_cs: self.interpolation_cs,
215 hue_direction: self.hue_direction,
216 });
217
218 let has_undefined = kind.has_undefined();
219
220 let encoded = EncodedGradient {
221 cache_key,
222 kind,
223 has_undefined,
224 transform,
225 x_advance,
226 y_advance,
227 ranges,
228 extend: self.extend,
229 may_have_transparency,
230 u8_lut: OnceCell::new(),
231 f32_lut: OnceCell::new(),
232 };
233
234 let idx = paints.len();
235 paints.push(encoded.into());
236
237 Paint::Indexed(IndexedPaint::new(idx))
238 }
239}
240
241fn validate(gradient: &Gradient) -> Result<(), Paint> {
245 let black = Err(BLACK.into());
246
247 if gradient.stops.is_empty() {
249 return black;
250 }
251
252 let first = Err(gradient.stops[0].color.to_alpha_color::<Srgb>().into());
253
254 if gradient.stops.len() == 1 {
255 return first;
256 }
257
258 for stops in gradient.stops.windows(2) {
259 let f = stops[0];
260 let n = stops[1];
261
262 if !(0.0..=1.0).contains(&f.offset) {
264 return first;
265 }
266
267 if f.offset > n.offset {
269 return first;
270 }
271 }
272
273 let last = gradient.stops.last().unwrap();
275 if !(0.0..=1.0).contains(&last.offset) {
276 return first;
277 }
278
279 let degenerate_point = |p1: &Point, p2: &Point| {
280 (p1.x - p2.x).abs() as f32 <= DEGENERATE_THRESHOLD
281 && (p1.y - p2.y).abs() as f32 <= DEGENERATE_THRESHOLD
282 };
283
284 let degenerate_val = |v1: f32, v2: f32| (v2 - v1).abs() <= DEGENERATE_THRESHOLD;
285
286 match &gradient.kind {
287 GradientKind::Linear(LinearGradientPosition { start, end }) => {
288 if degenerate_point(start, end) {
290 return first;
291 }
292 }
293 GradientKind::Radial(RadialGradientPosition {
294 start_center,
295 start_radius,
296 end_center,
297 end_radius,
298 }) => {
299 if *start_radius < 0.0 || *end_radius < 0.0 {
301 return first;
302 }
303
304 if degenerate_point(start_center, end_center)
306 && degenerate_val(*start_radius, *end_radius)
307 {
308 return first;
309 }
310 }
311 GradientKind::Sweep(SweepGradientPosition {
312 start_angle,
313 end_angle,
314 ..
315 }) => {
316 if degenerate_val(*start_angle, *end_angle) {
318 return first;
319 }
320
321 if end_angle <= start_angle {
322 return first;
323 }
324 }
325 }
326
327 Ok(())
328}
329
330fn encode_stops(
332 stops: &[ColorStop],
333 cs: ColorSpaceTag,
334 hue_dir: HueDirection,
335 interpolation_alpha_space: InterpolationAlphaSpace,
336) -> Vec<GradientRange> {
337 #[derive(Debug)]
338 struct EncodedColorStop {
339 offset: f32,
340 color: crate::color::AlphaColor<Srgb>,
341 }
342
343 let create_range = |left_stop: &EncodedColorStop, right_stop: &EncodedColorStop| {
344 let clamp = |mut color: [f32; 4]| {
345 for c in &mut color {
348 *c = c.clamp(0.0, 1.0);
349 }
350
351 color
352 };
353
354 let x0 = left_stop.offset;
355 let x1 = right_stop.offset;
356 let c0 = if interpolation_alpha_space == InterpolationAlphaSpace::Unpremultiplied {
357 clamp(left_stop.color.components)
358 } else {
359 clamp(left_stop.color.premultiply().components)
360 };
361 let c1 = if interpolation_alpha_space == InterpolationAlphaSpace::Unpremultiplied {
362 clamp(right_stop.color.components)
363 } else {
364 clamp(right_stop.color.premultiply().components)
365 };
366
367 let x1_minus_x0 = (x1 - x0).max(NUDGE_VAL);
373 let mut scale = [0.0; 4];
374 let mut bias = c0;
375
376 for i in 0..4 {
377 scale[i] = (c1[i] - c0[i]) / x1_minus_x0;
378 bias[i] = c0[i] - x0 * scale[i];
379 }
380
381 GradientRange {
382 x1,
383 bias,
384 scale,
385 interpolation_alpha_space,
386 }
387 };
388
389 if cs != ColorSpaceTag::Srgb {
392 let interpolated_stops = if interpolation_alpha_space
393 == InterpolationAlphaSpace::Premultiplied
394 {
395 stops
396 .windows(2)
397 .flat_map(|s| {
398 let left_stop = &s[0];
399 let right_stop = &s[1];
400
401 let interpolated =
402 gradient::<Srgb>(left_stop.color, right_stop.color, cs, hue_dir, 0.01);
403
404 interpolated.map(|st| EncodedColorStop {
405 offset: left_stop.offset + (right_stop.offset - left_stop.offset) * st.0,
406 color: st.1.un_premultiply(),
407 })
408 })
409 .collect::<Vec<_>>()
410 } else {
411 stops
412 .windows(2)
413 .flat_map(|s| {
414 let left_stop = &s[0];
415 let right_stop = &s[1];
416
417 let interpolated = gradient_unpremultiplied::<Srgb>(
418 left_stop.color,
419 right_stop.color,
420 cs,
421 hue_dir,
422 0.01,
423 );
424
425 interpolated.map(|st| EncodedColorStop {
426 offset: left_stop.offset + (right_stop.offset - left_stop.offset) * st.0,
427 color: st.1,
428 })
429 })
430 .collect::<Vec<_>>()
431 };
432
433 interpolated_stops
434 .windows(2)
435 .map(|s| {
436 let left_stop = &s[0];
437 let right_stop = &s[1];
438
439 create_range(left_stop, right_stop)
440 })
441 .collect()
442 } else {
443 stops
444 .windows(2)
445 .map(|c| {
446 let c0 = EncodedColorStop {
447 offset: c[0].offset,
448 color: c[0].color.to_alpha_color::<Srgb>(),
449 };
450
451 let c1 = EncodedColorStop {
452 offset: c[1].offset,
453 color: c[1].color.to_alpha_color::<Srgb>(),
454 };
455
456 create_range(&c0, &c1)
457 })
458 .collect()
459 }
460}
461
462pub(crate) fn x_y_advances(transform: &Affine) -> (Vec2, Vec2) {
463 let scale_skew_transform = {
464 let c = transform.as_coeffs();
465 Affine::new([c[0], c[1], c[2], c[3], 0.0, 0.0])
466 };
467
468 let x_advance = scale_skew_transform * Point::new(1.0, 0.0);
469 let y_advance = scale_skew_transform * Point::new(0.0, 1.0);
470
471 (
472 Vec2::new(x_advance.x, x_advance.y),
473 Vec2::new(y_advance.x, y_advance.y),
474 )
475}
476
477impl private::Sealed for Image {}
478
479impl EncodeExt for Image {
480 fn encode_into(
481 &self,
482 paints: &mut Vec<EncodedPaint>,
483 transform: Affine,
484 tint: Option<Tint>,
485 ) -> Paint {
486 let idx = paints.len();
487
488 let mut sampler = self.sampler;
489
490 if sampler.alpha != 1.0 {
491 unimplemented!("Applying opacity to image commands");
493 }
494
495 let c = transform.as_coeffs();
496
497 if (c[0] as f32 - 1.0).is_nearly_zero()
499 && (c[1] as f32).is_nearly_zero()
500 && (c[2] as f32).is_nearly_zero()
501 && (c[3] as f32 - 1.0).is_nearly_zero()
502 && ((c[4] - c[4].floor()) as f32).is_nearly_zero()
503 && ((c[5] - c[5].floor()) as f32).is_nearly_zero()
504 && sampler.quality == ImageQuality::Medium
505 {
506 sampler.quality = ImageQuality::Low;
507 }
508
509 let transform = transform.inverse();
510
511 let (x_advance, y_advance) = x_y_advances(&transform);
512
513 let has_opacity = tint.as_ref().is_some_and(|t| t.color.components[3] < 1.0)
516 || sampler.alpha != 1.0;
518
519 let encoded = EncodedImage {
520 may_have_transparency: self.image.may_have_transparency() || has_opacity,
521 source: self.image.clone(),
522 sampler,
523 transform,
524 x_advance,
525 y_advance,
526 tint,
527 };
528
529 paints.push(EncodedPaint::Image(encoded));
530
531 Paint::Indexed(IndexedPaint::new(idx))
532 }
533}
534
535#[derive(Debug)]
537pub enum EncodedPaint {
538 Gradient(EncodedGradient),
540 Image(EncodedImage),
542 BlurredRoundedRect(EncodedBlurredRoundedRectangle),
544}
545
546impl EncodedPaint {
547 pub fn may_have_transparency(&self) -> bool {
549 match self {
550 Self::Gradient(gradient) => gradient.may_have_transparency,
551 Self::Image(image) => image.may_have_transparency,
552 Self::BlurredRoundedRect(_) => true,
553 }
554 }
555}
556
557impl Paint {
558 pub fn may_have_transparency(&self, encoded_paints: &[EncodedPaint]) -> bool {
560 match self {
561 Self::Solid(color) => !color.is_opaque(),
562 Self::Indexed(index) => encoded_paints[index.index()].may_have_transparency(),
563 }
564 }
565}
566
567impl From<EncodedGradient> for EncodedPaint {
568 fn from(value: EncodedGradient) -> Self {
569 Self::Gradient(value)
570 }
571}
572
573impl From<EncodedBlurredRoundedRectangle> for EncodedPaint {
574 fn from(value: EncodedBlurredRoundedRectangle) -> Self {
575 Self::BlurredRoundedRect(value)
576 }
577}
578
579#[derive(Debug)]
581pub struct EncodedImage {
582 pub source: ImageSource,
584 pub sampler: ImageSampler,
586 pub may_have_transparency: bool,
588 pub transform: Affine,
590 pub x_advance: Vec2,
592 pub y_advance: Vec2,
594 pub tint: Option<Tint>,
596}
597
598#[derive(Debug, Copy, Clone)]
600pub struct LinearKind;
601
602#[derive(Debug, PartialEq, Copy, Clone)]
604pub struct FocalData {
605 pub fr1: f32,
607 pub f_focal_x: f32,
609 pub f_is_swapped: bool,
611}
612
613impl FocalData {
614 pub fn create(mut r0: f32, mut r1: f32, matrix: &mut Affine) -> Self {
616 let mut swapped = false;
617 let mut f_focal_x = r0 / (r0 - r1);
618
619 if (f_focal_x - 1.0).is_nearly_zero() {
620 *matrix = matrix.then_translate(Vec2::new(-1.0, 0.0));
621 *matrix = matrix.then_scale_non_uniform(-1.0, 1.0);
622 core::mem::swap(&mut r0, &mut r1);
623 f_focal_x = 0.0;
624 swapped = true;
625 }
626
627 let focal_matrix = ts_from_line_to_line(
628 Point::new(f_focal_x as f64, 0.0),
629 Point::new(1.0, 0.0),
630 Point::new(0.0, 0.0),
631 Point::new(1.0, 0.0),
632 );
633 *matrix = focal_matrix * *matrix;
634
635 let fr1 = r1 / (1.0 - f_focal_x).abs();
636
637 let data = Self {
638 fr1,
639 f_focal_x,
640 f_is_swapped: swapped,
641 };
642
643 if data.is_focal_on_circle() {
644 *matrix = matrix.then_scale(0.5);
645 } else {
646 *matrix = matrix.then_scale_non_uniform(
647 (fr1 / (fr1 * fr1 - 1.0)) as f64,
648 1.0 / (fr1 * fr1 - 1.0).abs().sqrt() as f64,
649 );
650 }
651
652 *matrix = matrix.then_scale((1.0 - f_focal_x).abs() as f64);
653
654 data
655 }
656
657 pub fn is_focal_on_circle(&self) -> bool {
659 (1.0 - self.fr1).is_nearly_zero()
660 }
661
662 pub fn is_swapped(&self) -> bool {
664 self.f_is_swapped
665 }
666
667 pub fn is_well_behaved(&self) -> bool {
669 !self.is_focal_on_circle() && self.fr1 > 1.0
670 }
671
672 pub fn is_natively_focal(&self) -> bool {
674 self.f_focal_x.is_nearly_zero()
675 }
676}
677
678#[derive(Debug, PartialEq, Copy, Clone)]
680pub enum RadialKind {
681 Radial {
683 bias: f32,
688 scale: f32,
692 },
693 Strip {
695 scaled_r0_squared: f32,
697 },
698 Focal {
700 focal_data: FocalData,
702 fp0: f32,
704 fp1: f32,
706 },
707}
708
709impl RadialKind {
710 pub fn has_undefined(&self) -> bool {
712 match self {
713 Self::Radial { .. } => false,
714 Self::Strip { .. } => true,
715 Self::Focal { focal_data, .. } => !focal_data.is_well_behaved(),
716 }
717 }
718}
719
720#[derive(Debug)]
722pub struct SweepKind {
723 pub start_angle: f32,
725 pub inv_angle_delta: f32,
727}
728
729#[derive(Debug)]
731pub enum EncodedKind {
732 Linear(LinearKind),
734 Radial(RadialKind),
736 Sweep(SweepKind),
738}
739
740impl EncodedKind {
741 fn has_undefined(&self) -> bool {
743 match self {
744 Self::Radial(radial_kind) => radial_kind.has_undefined(),
745 _ => false,
746 }
747 }
748}
749
750#[derive(Debug)]
752pub struct EncodedGradient {
753 pub cache_key: CacheKey<GradientCacheKey>,
755 pub kind: EncodedKind,
757 pub has_undefined: bool,
759 pub transform: Affine,
761 pub x_advance: Vec2,
763 pub y_advance: Vec2,
765 pub ranges: Vec<GradientRange>,
767 pub extend: Extend,
769 pub may_have_transparency: bool,
771 u8_lut: OnceCell<GradientLut<u8>>,
772 f32_lut: OnceCell<GradientLut<f32>>,
773}
774
775impl EncodedGradient {
776 pub fn u8_lut<S: Simd>(&self, simd: S) -> &GradientLut<u8> {
779 self.u8_lut
780 .get_or_init(|| GradientLut::new(simd, &self.ranges))
781 }
782
783 pub fn f32_lut<S: Simd>(&self, simd: S) -> &GradientLut<f32> {
786 self.f32_lut
787 .get_or_init(|| GradientLut::new(simd, &self.ranges))
788 }
789}
790
791#[derive(Debug, Clone)]
793pub struct GradientCacheKey {
794 pub stops: ColorStops,
796 pub interpolation_cs: ColorSpaceTag,
798 pub hue_direction: HueDirection,
800}
801
802impl BitHash for GradientCacheKey {
803 fn bit_hash<H: Hasher>(&self, state: &mut H) {
804 self.stops.bit_hash(state);
805 core::mem::discriminant(&self.interpolation_cs).hash(state);
806 core::mem::discriminant(&self.hue_direction).hash(state);
807 }
808}
809
810impl BitEq for GradientCacheKey {
811 fn bit_eq(&self, other: &Self) -> bool {
812 self.stops.bit_eq(&other.stops)
813 && self.interpolation_cs == other.interpolation_cs
814 && self.hue_direction == other.hue_direction
815 }
816}
817
818#[derive(Debug, Clone)]
820pub struct GradientRange {
821 pub x1: f32,
823 pub bias: [f32; 4],
826 pub scale: [f32; 4],
829 pub interpolation_alpha_space: InterpolationAlphaSpace,
831}
832
833#[derive(Debug)]
835pub struct EncodedBlurredRoundedRectangle {
836 pub exponent: f32,
838 pub recip_exponent: f32,
840 pub scale: f32,
842 pub std_dev_inv: f32,
844 pub min_edge: f32,
846 pub w: f32,
848 pub h: f32,
850 pub width: f32,
852 pub height: f32,
854 pub r1: f32,
856 pub invert: bool,
861 pub color: PremulColor,
863 pub transform: Affine,
865 pub x_advance: Vec2,
867 pub y_advance: Vec2,
869}
870
871impl private::Sealed for BlurredRoundedRectangle {}
872
873impl EncodeExt for BlurredRoundedRectangle {
874 fn encode_into(
875 &self,
876 paints: &mut Vec<EncodedPaint>,
877 transform: Affine,
878 _tint: Option<Tint>,
879 ) -> Paint {
880 let rect = {
881 let mut rect = self.rect;
883
884 if self.rect.x0 > self.rect.x1 {
885 core::mem::swap(&mut rect.x0, &mut rect.x1);
886 }
887
888 if self.rect.y0 > self.rect.y1 {
889 core::mem::swap(&mut rect.y0, &mut rect.y1);
890 }
891
892 rect
893 };
894
895 let transform = Affine::translate((-rect.x0, -rect.y0)) * transform.inverse();
896
897 let (x_advance, y_advance) = x_y_advances(&transform);
898
899 let width = rect.width() as f32;
900 let height = rect.height() as f32;
901 let radius = self.radius.min(0.5 * width.min(height));
902
903 let std_dev = self.std_dev.max(1e-6);
905
906 let min_edge = width.min(height);
907 let rmax = 0.5 * min_edge;
908 let r0 = radius.hypot(std_dev * 1.15).min(rmax);
909 let r1 = radius.hypot(std_dev * 2.0).min(rmax);
910
911 let exponent = 2.0 * r1 / r0;
912
913 let std_dev_inv = std_dev.recip();
914
915 let delta = 1.25
917 * std_dev
918 * (exp(-(0.5 * std_dev_inv * width).powi(2))
919 - exp(-(0.5 * std_dev_inv * height).powi(2)));
920 let w = width + delta.min(0.0);
921 let h = height - delta.max(0.0);
922
923 let recip_exponent = exponent.recip();
924 let scale = 0.5 * compute_erf7(std_dev_inv * 0.5 * (w.max(h) - 0.5 * radius));
925
926 let encoded = EncodedBlurredRoundedRectangle {
927 exponent,
928 recip_exponent,
929 width,
930 height,
931 scale,
932 r1,
933 std_dev_inv,
934 min_edge,
935 invert: self.invert,
936 color: PremulColor::from_alpha_color(self.color),
937 w,
938 h,
939 transform,
940 x_advance,
941 y_advance,
942 };
943
944 let idx = paints.len();
945 paints.push(encoded.into());
946
947 Paint::Indexed(IndexedPaint::new(idx))
948 }
949}
950
951fn ts_from_line_to_line(src1: Point, src2: Point, dst1: Point, dst2: Point) -> Affine {
959 let unit_to_line1 = unit_to_line(src1, src2);
960 let line1_to_unit = unit_to_line1.inverse();
962 let unit_to_line2 = unit_to_line(dst1, dst2);
964
965 unit_to_line2 * line1_to_unit
966}
967
968fn unit_to_line(p0: Point, p1: Point) -> Affine {
971 Affine::new([
972 p1.y - p0.y,
973 p0.x - p1.x,
974 p1.x - p0.x,
975 p1.y - p0.y,
976 p0.x,
977 p0.y,
978 ])
979}
980
981pub trait GradientLutExt: Sized + Debug + Copy + Clone + Pod {
983 const ZERO: Self;
985 fn from_f32x16<S: Simd>(color: f32x16<S>) -> [Self; 16];
987}
988
989impl GradientLutExt for f32 {
990 const ZERO: Self = 0.0;
991
992 #[inline(always)]
993 fn from_f32x16<S: Simd>(color: f32x16<S>) -> [Self; 16] {
994 color.into()
995 }
996}
997
998impl GradientLutExt for u8 {
999 const ZERO: Self = 0;
1000
1001 #[inline(always)]
1002 fn from_f32x16<S: Simd>(color: f32x16<S>) -> [Self; 16] {
1003 let simd = color.simd;
1004 let color = color.mul_add(f32x16::splat(simd, 255.0), f32x16::splat(simd, 0.5));
1005 f32_to_u8(color).into()
1006 }
1007}
1008
1009#[derive(Debug)]
1011pub struct GradientLut<T: GradientLutExt> {
1012 lut: Vec<[T; 4]>,
1013 scale: f32,
1014}
1015
1016impl<T: GradientLutExt> GradientLut<T> {
1017 fn new<S: Simd>(simd: S, ranges: &[GradientRange]) -> Self {
1019 simd.vectorize(
1020 #[inline(always)]
1021 || Self::new_inner(simd, ranges),
1022 )
1023 }
1024
1025 #[inline(always)]
1026 fn new_inner<S: Simd>(simd: S, ranges: &[GradientRange]) -> Self {
1027 let lut_size = determine_lut_size(ranges);
1028 let mut lut = vec![[T::ZERO; 4]; lut_size];
1029 let lut_flat = bytemuck::cast_slice_mut::<[T; 4], T>(&mut lut);
1030
1031 let ramps = {
1033 let mut ramps = Vec::with_capacity(ranges.len());
1034 let mut prev_idx = 0;
1035
1036 for range in ranges {
1037 let max_idx = (range.x1 * lut_size as f32) as usize;
1038
1039 ramps.push((prev_idx..max_idx, range));
1040 prev_idx = max_idx;
1041 }
1042
1043 ramps
1044 };
1045
1046 let scale = lut_size as f32 - 1.0;
1047
1048 let inv_lut_scale = f32x4::splat(simd, 1.0 / scale);
1049 let add_factor = f32x4::from_slice(simd, &[0.0, 1.0, 2.0, 3.0]) * inv_lut_scale;
1050
1051 for (ramp_range, range) in ramps {
1052 let biases = f32x16::block_splat(f32x4::from_slice(simd, &range.bias));
1053 let scales = f32x16::block_splat(f32x4::from_slice(simd, &range.scale));
1054
1055 ramp_range.clone().step_by(4).for_each(|idx| {
1056 let t_vals = f32x4::splat(simd, idx as f32).mul_add(inv_lut_scale, add_factor);
1057
1058 let t_vals = element_wise_splat(simd, t_vals);
1059
1060 let mut result = scales.mul_add(t_vals, biases);
1061 let alphas = result.splat_4th();
1062 if range.interpolation_alpha_space == InterpolationAlphaSpace::Unpremultiplied {
1064 result = {
1065 let mask = mask32x16::simd_from(
1066 simd,
1067 [-1, -1, -1, 0, -1, -1, -1, 0, -1, -1, -1, 0, -1, -1, -1, 0],
1068 );
1069 simd.select_f32x16(mask, result * alphas, alphas)
1070 };
1071 }
1072
1073 result = result.min(1.0).min(alphas);
1078 let rs = T::from_f32x16(result);
1079
1080 let start = idx * 4;
1083 let end = (idx + 4).min(lut_size) * 4;
1084 lut_flat[start..end].copy_from_slice(&rs[..end - start]);
1085 });
1086 }
1087
1088 Self { lut, scale }
1089 }
1090
1091 #[inline(always)]
1093 pub fn get(&self, idx: usize) -> [T; 4] {
1094 self.lut[idx]
1095 }
1096
1097 #[inline(always)]
1099 pub fn lut(&self) -> &[[T; 4]] {
1100 &self.lut
1101 }
1102
1103 #[inline(always)]
1105 pub fn width(&self) -> usize {
1106 self.lut.len()
1107 }
1108
1109 #[inline(always)]
1112 pub fn scale_factor(&self) -> f32 {
1113 self.scale
1114 }
1115}
1116
1117pub const MAX_GRADIENT_LUT_SIZE: usize = 4096;
1122
1123fn determine_lut_size(ranges: &[GradientRange]) -> usize {
1124 let stop_len = match ranges.len() {
1130 1 => 256,
1131 2 => 512,
1132 _ => 1024,
1133 };
1134
1135 let mut last_x1 = 0.0;
1138 let mut min_size = 0;
1139
1140 for x1 in ranges.iter().map(|e| e.x1) {
1141 let res = ((1.0 / (x1 - last_x1)).ceil() as usize)
1144 .min(MAX_GRADIENT_LUT_SIZE)
1145 .next_power_of_two();
1146 min_size = min_size.max(res);
1147 last_x1 = x1;
1148 }
1149
1150 stop_len.max(min_size)
1152}
1153
1154mod private {
1155 #[expect(unnameable_types, reason = "Sealed trait pattern.")]
1156 pub trait Sealed {}
1157
1158 impl Sealed for super::Gradient {}
1159}
1160
1161#[cfg(test)]
1162mod tests {
1163 use super::{EncodeExt, Gradient};
1164 use crate::color::DynamicColor;
1165 use crate::color::palette::css::{BLACK, BLUE, GREEN};
1166 use crate::kurbo::{Affine, Point};
1167 use crate::peniko::{ColorStop, ColorStops};
1168 use alloc::vec;
1169 use peniko::{LinearGradientPosition, RadialGradientPosition};
1170 use smallvec::smallvec;
1171
1172 #[test]
1173 fn gradient_missing_stops() {
1174 let mut buf = vec![];
1175
1176 let gradient = Gradient {
1177 kind: LinearGradientPosition {
1178 start: Point::new(0.0, 0.0),
1179 end: Point::new(20.0, 0.0),
1180 }
1181 .into(),
1182 ..Default::default()
1183 };
1184
1185 assert_eq!(
1186 gradient.encode_into(&mut buf, Affine::IDENTITY, None),
1187 BLACK.into()
1188 );
1189 }
1190
1191 #[test]
1192 fn gradient_one_stop() {
1193 let mut buf = vec![];
1194
1195 let gradient = Gradient {
1196 kind: LinearGradientPosition {
1197 start: Point::new(0.0, 0.0),
1198 end: Point::new(20.0, 0.0),
1199 }
1200 .into(),
1201 stops: ColorStops(smallvec![ColorStop {
1202 offset: 0.0,
1203 color: DynamicColor::from_alpha_color(GREEN),
1204 }]),
1205 ..Default::default()
1206 };
1207
1208 assert_eq!(
1210 gradient.encode_into(&mut buf, Affine::IDENTITY, None),
1211 GREEN.into()
1212 );
1213 }
1214
1215 #[test]
1216 fn gradient_not_sorted_stops() {
1217 let mut buf = vec![];
1218
1219 let gradient = Gradient {
1220 kind: LinearGradientPosition {
1221 start: Point::new(0.0, 0.0),
1222 end: Point::new(20.0, 0.0),
1223 }
1224 .into(),
1225 stops: ColorStops(smallvec![
1226 ColorStop {
1227 offset: 1.0,
1228 color: DynamicColor::from_alpha_color(GREEN),
1229 },
1230 ColorStop {
1231 offset: 0.0,
1232 color: DynamicColor::from_alpha_color(BLUE),
1233 },
1234 ]),
1235 ..Default::default()
1236 };
1237
1238 assert_eq!(
1239 gradient.encode_into(&mut buf, Affine::IDENTITY, None),
1240 GREEN.into()
1241 );
1242 }
1243
1244 #[test]
1245 fn gradient_linear_degenerate() {
1246 let mut buf = vec![];
1247
1248 let gradient = Gradient {
1249 kind: LinearGradientPosition {
1250 start: Point::new(0.0, 0.0),
1251 end: Point::new(0.0, 0.0),
1252 }
1253 .into(),
1254 stops: ColorStops(smallvec![
1255 ColorStop {
1256 offset: 0.0,
1257 color: DynamicColor::from_alpha_color(GREEN),
1258 },
1259 ColorStop {
1260 offset: 1.0,
1261 color: DynamicColor::from_alpha_color(BLUE),
1262 },
1263 ]),
1264 ..Default::default()
1265 };
1266
1267 assert_eq!(
1268 gradient.encode_into(&mut buf, Affine::IDENTITY, None),
1269 GREEN.into()
1270 );
1271 }
1272
1273 #[test]
1274 fn gradient_last_stop_with_infinity_offset() {
1275 let mut buf = vec![];
1276
1277 let gradient = Gradient {
1278 kind: LinearGradientPosition {
1279 start: Point::new(0.0, 0.0),
1280 end: Point::new(20.0, 0.0),
1281 }
1282 .into(),
1283 stops: ColorStops(smallvec![
1284 ColorStop {
1285 offset: 0.0,
1286 color: DynamicColor::from_alpha_color(GREEN),
1287 },
1288 ColorStop {
1289 offset: f32::INFINITY,
1290 color: DynamicColor::from_alpha_color(BLUE),
1291 },
1292 ]),
1293 ..Default::default()
1294 };
1295
1296 assert_eq!(
1298 gradient.encode_into(&mut buf, Affine::IDENTITY, None),
1299 GREEN.into()
1300 );
1301 }
1302
1303 #[test]
1304 fn gradient_stop_with_nan_offset() {
1305 let mut buf = vec![];
1306
1307 let gradient = Gradient {
1308 kind: LinearGradientPosition {
1309 start: Point::new(0.0, 0.0),
1310 end: Point::new(20.0, 0.0),
1311 }
1312 .into(),
1313 stops: ColorStops(smallvec![
1314 ColorStop {
1315 offset: 0.0,
1316 color: DynamicColor::from_alpha_color(GREEN),
1317 },
1318 ColorStop {
1319 offset: f32::NAN,
1320 color: DynamicColor::from_alpha_color(BLUE),
1321 },
1322 ]),
1323 ..Default::default()
1324 };
1325
1326 assert_eq!(
1328 gradient.encode_into(&mut buf, Affine::IDENTITY, None),
1329 GREEN.into()
1330 );
1331 }
1332
1333 #[test]
1334 fn gradient_radial_degenerate() {
1335 let mut buf = vec![];
1336
1337 let gradient = Gradient {
1338 kind: RadialGradientPosition {
1339 start_center: Point::new(0.0, 0.0),
1340 start_radius: 20.0,
1341 end_center: Point::new(0.0, 0.0),
1342 end_radius: 20.0,
1343 }
1344 .into(),
1345 stops: ColorStops(smallvec![
1346 ColorStop {
1347 offset: 0.0,
1348 color: DynamicColor::from_alpha_color(GREEN),
1349 },
1350 ColorStop {
1351 offset: 1.0,
1352 color: DynamicColor::from_alpha_color(BLUE),
1353 },
1354 ]),
1355 ..Default::default()
1356 };
1357
1358 assert_eq!(
1359 gradient.encode_into(&mut buf, Affine::IDENTITY, None),
1360 GREEN.into()
1361 );
1362 }
1363}