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};
12use crate::peniko::{ColorStop, Extend, Gradient, GradientKind, ImageQuality};
13use alloc::borrow::Cow;
14use alloc::fmt::Debug;
15use alloc::vec;
16use alloc::vec::Vec;
17#[cfg(not(feature = "multithreading"))]
18use core::cell::OnceCell;
19use fearless_simd::{Simd, SimdBase, SimdFloat, f32x4, f32x16};
20use smallvec::{SmallVec, ToSmallVec};
21#[cfg(feature = "multithreading")]
23use std::sync::OnceLock as OnceCell;
24
25use crate::simd::{Splat4thExt, element_wise_splat};
26#[cfg(not(feature = "std"))]
27use peniko::kurbo::common::FloatFuncs as _;
28
29const DEGENERATE_THRESHOLD: f32 = 1.0e-6;
30const NUDGE_VAL: f32 = 1.0e-7;
31const PIXEL_CENTER_OFFSET: f64 = 0.5;
32
33#[cfg(feature = "std")]
34fn exp(val: f32) -> f32 {
35 val.exp()
36}
37
38#[cfg(not(feature = "std"))]
39fn exp(val: f32) -> f32 {
40 #[cfg(feature = "libm")]
41 return libm::expf(val);
42 #[cfg(not(feature = "libm"))]
43 compile_error!("vello_common requires either the `std` or `libm` feature");
44}
45
46pub trait EncodeExt: private::Sealed {
48 fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint;
51}
52
53impl EncodeExt for Gradient {
54 fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint {
56 if let Err(paint) = validate(self) {
58 return paint;
59 }
60
61 let mut has_opacities = self.stops.iter().any(|s| s.color.components[3] != 1.0);
62 let pad = self.extend == Extend::Pad;
63
64 let mut base_transform;
65
66 let mut stops = Cow::Borrowed(&self.stops.0);
67
68 let first_stop = &stops[0];
69 let last_stop = &stops[stops.len() - 1];
70
71 if first_stop.offset != 0.0 || last_stop.offset != 1.0 {
72 let mut vec = stops.to_smallvec();
73
74 if first_stop.offset != 0.0 {
75 let mut first_stop = *first_stop;
76 first_stop.offset = 0.0;
77 vec.insert(0, first_stop);
78 }
79
80 if last_stop.offset != 1.0 {
81 let mut last_stop = *last_stop;
82 last_stop.offset = 1.0;
83 vec.push(last_stop);
84 }
85
86 stops = Cow::Owned(vec);
87 }
88
89 let kind = match self.kind {
90 GradientKind::Linear {
91 start: p0,
92 end: mut p1,
93 } => {
94 if self.extend == Extend::Reflect {
98 p1.x += p1.x - p0.x;
99 p1.y += p1.y - p0.y;
100 stops = Cow::Owned(apply_reflect(&stops));
101 }
102
103 base_transform = ts_from_line_to_line(p0, p1, Point::ZERO, Point::new(1.0, 0.0));
107
108 EncodedKind::Linear(LinearKind)
109 }
110 GradientKind::Radial {
111 start_center: c0,
112 start_radius: r0,
113 end_center: mut c1,
114 end_radius: mut r1,
115 } => {
116 if self.extend == Extend::Reflect {
125 c1 += c1 - c0;
126 r1 += r1 - r0;
127 stops = Cow::Owned(apply_reflect(&stops));
128 }
129
130 let d_radius = r1 - r0;
131
132 let radial_kind = if ((c1 - c0).length() as f32).is_nearly_zero() {
134 base_transform = Affine::translate((-c1.x, -c1.y));
135 base_transform = base_transform.then_scale(1.0 / r0.max(r1) as f64);
136
137 let scale = r1.max(r0) / d_radius;
138 let bias = -r0 / d_radius;
139
140 RadialKind::Radial { bias, scale }
141 } else {
142 base_transform =
143 ts_from_line_to_line(c0, c1, Point::ZERO, Point::new(1.0, 0.0));
144
145 if (r1 - r0).is_nearly_zero() {
146 let scaled_r0 = r1 / (c1 - c0).length() as f32;
147 RadialKind::Strip {
148 scaled_r0_squared: scaled_r0 * scaled_r0,
149 }
150 } else {
151 let d_center = (c0 - c1).length() as f32;
152
153 let focal_data =
154 FocalData::create(r0 / d_center, r1 / d_center, &mut base_transform);
155
156 let fp0 = 1.0 / focal_data.fr1;
157 let fp1 = focal_data.f_focal_x;
158
159 RadialKind::Focal {
160 focal_data,
161 fp0,
162 fp1,
163 }
164 }
165 };
166
167 has_opacities |= radial_kind.has_undefined();
172
173 EncodedKind::Radial(radial_kind)
174 }
175 GradientKind::Sweep {
176 center,
177 start_angle,
178 end_angle,
179 } => {
180 let start_angle = start_angle.to_radians();
183 let mut end_angle = end_angle.to_radians();
184
185 if self.extend == Extend::Reflect {
187 end_angle += end_angle - start_angle;
188 stops = Cow::Owned(apply_reflect(&stops));
189 }
190
191 let x_offset = -center.x as f32;
194 let y_offset = -center.y as f32;
195 base_transform = Affine::translate((x_offset as f64, y_offset as f64));
196
197 EncodedKind::Sweep(SweepKind {
198 start_angle,
199 inv_angle_delta: 1.0 / (end_angle - start_angle),
201 })
202 }
203 };
204
205 let ranges = encode_stops(&stops, self.interpolation_cs, self.hue_direction);
206
207 let transform = base_transform
215 * transform.inverse()
216 * Affine::translate((PIXEL_CENTER_OFFSET, PIXEL_CENTER_OFFSET));
217
218 let (x_advance, y_advance) = x_y_advances(&transform);
231
232 let encoded = EncodedGradient {
233 kind,
234 transform,
235 x_advance,
236 y_advance,
237 ranges,
238 pad,
239 has_opacities,
240 u8_lut: OnceCell::new(),
241 f32_lut: OnceCell::new(),
242 };
243
244 let idx = paints.len();
245 paints.push(encoded.into());
246
247 Paint::Indexed(IndexedPaint::new(idx))
248 }
249}
250
251fn validate(gradient: &Gradient) -> Result<(), Paint> {
255 let black = Err(BLACK.into());
256
257 if gradient.stops.is_empty() {
259 return black;
260 }
261
262 let first = Err(gradient.stops[0].color.to_alpha_color::<Srgb>().into());
263
264 if gradient.stops.len() == 1 {
265 return first;
266 }
267
268 for stops in gradient.stops.windows(2) {
269 let f = stops[0];
270 let n = stops[1];
271
272 if f.offset > 1.0 || f.offset < 0.0 {
274 return first;
275 }
276
277 if f.offset > n.offset {
279 return first;
280 }
281 }
282
283 let degenerate_point = |p1: &Point, p2: &Point| {
284 (p1.x - p2.x).abs() as f32 <= DEGENERATE_THRESHOLD
285 && (p1.y - p2.y).abs() as f32 <= DEGENERATE_THRESHOLD
286 };
287
288 let degenerate_val = |v1: f32, v2: f32| (v2 - v1).abs() <= DEGENERATE_THRESHOLD;
289
290 match &gradient.kind {
291 GradientKind::Linear { start, end } => {
292 if degenerate_point(start, end) {
294 return first;
295 }
296 }
297 GradientKind::Radial {
298 start_center,
299 start_radius,
300 end_center,
301 end_radius,
302 } => {
303 if *start_radius < 0.0 || *end_radius < 0.0 {
305 return first;
306 }
307
308 if degenerate_point(start_center, end_center)
310 && degenerate_val(*start_radius, *end_radius)
311 {
312 return first;
313 }
314 }
315 GradientKind::Sweep {
316 start_angle,
317 end_angle,
318 ..
319 } => {
320 if degenerate_val(*start_angle, *end_angle) {
322 return first;
323 }
324
325 if end_angle <= start_angle {
326 return first;
327 }
328 }
329 }
330
331 Ok(())
332}
333
334fn apply_reflect(stops: &[ColorStop]) -> SmallVec<[ColorStop; 4]> {
336 let first_half = stops.iter().map(|s| ColorStop {
337 offset: s.offset / 2.0,
338 color: s.color,
339 });
340
341 let second_half = stops.iter().rev().map(|s| ColorStop {
342 offset: 0.5 + (1.0 - s.offset) / 2.0,
343 color: s.color,
344 });
345
346 first_half.chain(second_half).collect::<SmallVec<_>>()
347}
348
349fn encode_stops(
351 stops: &[ColorStop],
352 cs: ColorSpaceTag,
353 hue_dir: HueDirection,
354) -> Vec<GradientRange> {
355 #[derive(Debug)]
356 struct EncodedColorStop {
357 offset: f32,
358 color: crate::color::PremulColor<Srgb>,
359 }
360
361 let create_range = |left_stop: &EncodedColorStop, right_stop: &EncodedColorStop| {
362 let clamp = |mut color: [f32; 4]| {
363 for c in &mut color {
366 *c = c.clamp(0.0, 1.0);
367 }
368
369 color
370 };
371
372 let x0 = left_stop.offset;
373 let x1 = right_stop.offset;
374 let c0 = clamp(left_stop.color.components);
375 let c1 = clamp(right_stop.color.components);
376
377 let x1_minus_x0 = (x1 - x0).max(NUDGE_VAL);
383 let mut scale = [0.0; 4];
384 let mut bias = c0;
385
386 for i in 0..4 {
387 scale[i] = (c1[i] - c0[i]) / x1_minus_x0;
388 bias[i] = c0[i] - x0 * scale[i];
389 }
390
391 GradientRange { x1, bias, scale }
392 };
393
394 if cs != ColorSpaceTag::Srgb {
397 let interpolated_stops = stops
398 .windows(2)
399 .flat_map(|s| {
400 let left_stop = &s[0];
401 let right_stop = &s[1];
402
403 let interpolated =
404 gradient::<Srgb>(left_stop.color, right_stop.color, cs, hue_dir, 0.01);
405
406 interpolated.map(|st| EncodedColorStop {
407 offset: left_stop.offset + (right_stop.offset - left_stop.offset) * st.0,
408 color: st.1,
409 })
410 })
411 .collect::<Vec<_>>();
412
413 interpolated_stops
414 .windows(2)
415 .map(|s| {
416 let left_stop = &s[0];
417 let right_stop = &s[1];
418
419 create_range(left_stop, right_stop)
420 })
421 .collect()
422 } else {
423 stops
424 .windows(2)
425 .map(|c| {
426 let c0 = EncodedColorStop {
427 offset: c[0].offset,
428 color: c[0].color.to_alpha_color::<Srgb>().premultiply(),
429 };
430
431 let c1 = EncodedColorStop {
432 offset: c[1].offset,
433 color: c[1].color.to_alpha_color::<Srgb>().premultiply(),
434 };
435
436 create_range(&c0, &c1)
437 })
438 .collect()
439 }
440}
441
442pub(crate) fn x_y_advances(transform: &Affine) -> (Vec2, Vec2) {
443 let scale_skew_transform = {
444 let c = transform.as_coeffs();
445 Affine::new([c[0], c[1], c[2], c[3], 0.0, 0.0])
446 };
447
448 let x_advance = scale_skew_transform * Point::new(1.0, 0.0);
449 let y_advance = scale_skew_transform * Point::new(0.0, 1.0);
450
451 (
452 Vec2::new(x_advance.x, x_advance.y),
453 Vec2::new(y_advance.x, y_advance.y),
454 )
455}
456
457impl private::Sealed for Image {}
458
459impl EncodeExt for Image {
460 fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint {
461 let idx = paints.len();
462
463 let mut quality = self.quality;
464
465 let c = transform.as_coeffs();
466
467 if (c[0] as f32 - 1.0).is_nearly_zero()
469 && (c[1] as f32).is_nearly_zero()
470 && (c[2] as f32).is_nearly_zero()
471 && (c[3] as f32 - 1.0).is_nearly_zero()
472 && ((c[4] - c[4].floor()) as f32).is_nearly_zero()
473 && ((c[5] - c[5].floor()) as f32).is_nearly_zero()
474 && quality == ImageQuality::Medium
475 {
476 quality = ImageQuality::Low;
477 }
478
479 let transform = transform.inverse() * Affine::translate((0.5, 0.5));
482
483 let (x_advance, y_advance) = x_y_advances(&transform);
484
485 let encoded = match &self.source {
486 ImageSource::Pixmap(pixmap) => {
487 EncodedImage {
488 source: ImageSource::Pixmap(pixmap.clone()),
489 extends: (self.x_extend, self.y_extend),
490 quality,
491 has_opacities: true,
493 transform,
494 x_advance,
495 y_advance,
496 }
497 }
498 ImageSource::OpaqueId(image) => EncodedImage {
499 source: ImageSource::OpaqueId(*image),
500 extends: (self.x_extend, self.y_extend),
501 quality: self.quality,
502 has_opacities: true,
503 transform,
504 x_advance,
505 y_advance,
506 },
507 };
508
509 paints.push(EncodedPaint::Image(encoded));
510
511 Paint::Indexed(IndexedPaint::new(idx))
512 }
513}
514
515#[derive(Debug)]
517pub enum EncodedPaint {
518 Gradient(EncodedGradient),
520 Image(EncodedImage),
522 BlurredRoundedRect(EncodedBlurredRoundedRectangle),
524}
525
526impl From<EncodedGradient> for EncodedPaint {
527 fn from(value: EncodedGradient) -> Self {
528 Self::Gradient(value)
529 }
530}
531
532impl From<EncodedBlurredRoundedRectangle> for EncodedPaint {
533 fn from(value: EncodedBlurredRoundedRectangle) -> Self {
534 Self::BlurredRoundedRect(value)
535 }
536}
537
538#[derive(Debug)]
540pub struct EncodedImage {
541 pub source: ImageSource,
543 pub extends: (Extend, Extend),
545 pub quality: ImageQuality,
547 pub has_opacities: bool,
549 pub transform: Affine,
551 pub x_advance: Vec2,
553 pub y_advance: Vec2,
555}
556
557#[derive(Debug, Copy, Clone)]
559pub struct LinearKind;
560
561#[derive(Debug, PartialEq, Copy, Clone)]
563pub struct FocalData {
564 pub fr1: f32,
566 pub f_focal_x: f32,
568 pub f_is_swapped: bool,
570}
571
572impl FocalData {
573 pub fn create(mut r0: f32, mut r1: f32, matrix: &mut Affine) -> Self {
575 let mut swapped = false;
576 let mut f_focal_x = r0 / (r0 - r1);
577
578 if (f_focal_x - 1.0).is_nearly_zero() {
579 *matrix = matrix.then_translate(Vec2::new(-1.0, 0.0));
580 *matrix = matrix.then_scale_non_uniform(-1.0, 1.0);
581 core::mem::swap(&mut r0, &mut r1);
582 f_focal_x = 0.0;
583 swapped = true;
584 }
585
586 let focal_matrix = ts_from_line_to_line(
587 Point::new(f_focal_x as f64, 0.0),
588 Point::new(1.0, 0.0),
589 Point::new(0.0, 0.0),
590 Point::new(1.0, 0.0),
591 );
592 *matrix = focal_matrix * *matrix;
593
594 let fr1 = r1 / (1.0 - f_focal_x).abs();
595
596 let data = Self {
597 fr1,
598 f_focal_x,
599 f_is_swapped: swapped,
600 };
601
602 if data.is_focal_on_circle() {
603 *matrix = matrix.then_scale(0.5);
604 } else {
605 *matrix = matrix.then_scale_non_uniform(
606 (fr1 / (fr1 * fr1 - 1.0)) as f64,
607 1.0 / (fr1 * fr1 - 1.0).abs().sqrt() as f64,
608 );
609 }
610
611 *matrix = matrix.then_scale((1.0 - f_focal_x).abs() as f64);
612
613 data
614 }
615
616 pub fn is_focal_on_circle(&self) -> bool {
618 (1.0 - self.fr1).is_nearly_zero()
619 }
620
621 pub fn is_swapped(&self) -> bool {
623 self.f_is_swapped
624 }
625
626 pub fn is_well_behaved(&self) -> bool {
628 !self.is_focal_on_circle() && self.fr1 > 1.0
629 }
630
631 pub fn is_natively_focal(&self) -> bool {
633 self.f_focal_x.is_nearly_zero()
634 }
635}
636
637#[derive(Debug, PartialEq, Copy, Clone)]
639pub enum RadialKind {
640 Radial {
642 bias: f32,
647 scale: f32,
651 },
652 Strip {
654 scaled_r0_squared: f32,
656 },
657 Focal {
659 focal_data: FocalData,
661 fp0: f32,
663 fp1: f32,
665 },
666}
667
668impl RadialKind {
669 pub fn has_undefined(&self) -> bool {
671 match self {
672 Self::Radial { .. } => false,
673 Self::Strip { .. } => true,
674 Self::Focal { focal_data, .. } => !focal_data.is_well_behaved(),
675 }
676 }
677}
678
679#[derive(Debug)]
681pub struct SweepKind {
682 pub start_angle: f32,
684 pub inv_angle_delta: f32,
686}
687
688#[derive(Debug)]
690pub enum EncodedKind {
691 Linear(LinearKind),
693 Radial(RadialKind),
695 Sweep(SweepKind),
697}
698
699#[derive(Debug)]
701pub struct EncodedGradient {
702 pub kind: EncodedKind,
704 pub transform: Affine,
706 pub x_advance: Vec2,
708 pub y_advance: Vec2,
710 pub ranges: Vec<GradientRange>,
712 pub pad: bool,
714 pub has_opacities: bool,
716 u8_lut: OnceCell<GradientLut<u8>>,
717 f32_lut: OnceCell<GradientLut<f32>>,
718}
719
720impl EncodedGradient {
721 pub fn u8_lut<S: Simd>(&self, simd: S) -> &GradientLut<u8> {
723 self.u8_lut
724 .get_or_init(|| GradientLut::new(simd, &self.ranges))
725 }
726
727 pub fn f32_lut<S: Simd>(&self, simd: S) -> &GradientLut<f32> {
729 self.f32_lut
730 .get_or_init(|| GradientLut::new(simd, &self.ranges))
731 }
732}
733
734#[derive(Debug, Clone)]
736pub struct GradientRange {
737 pub x1: f32,
739 pub bias: [f32; 4],
742 pub scale: [f32; 4],
745}
746
747#[derive(Debug)]
749pub struct EncodedBlurredRoundedRectangle {
750 pub exponent: f32,
752 pub recip_exponent: f32,
754 pub scale: f32,
756 pub std_dev_inv: f32,
758 pub min_edge: f32,
760 pub w: f32,
762 pub h: f32,
764 pub width: f32,
766 pub height: f32,
768 pub r1: f32,
770 pub color: PremulColor,
772 pub transform: Affine,
774 pub x_advance: Vec2,
776 pub y_advance: Vec2,
778}
779
780impl private::Sealed for BlurredRoundedRectangle {}
781
782impl EncodeExt for BlurredRoundedRectangle {
783 fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint {
784 let rect = {
785 let mut rect = self.rect;
787
788 if self.rect.x0 > self.rect.x1 {
789 core::mem::swap(&mut rect.x0, &mut rect.x1);
790 }
791
792 if self.rect.y0 > self.rect.y1 {
793 core::mem::swap(&mut rect.y0, &mut rect.y1);
794 }
795
796 rect
797 };
798
799 let transform = Affine::translate((-rect.x0, -rect.y0)) * transform.inverse();
800
801 let (x_advance, y_advance) = x_y_advances(&transform);
802
803 let width = rect.width() as f32;
804 let height = rect.height() as f32;
805 let radius = self.radius.min(0.5 * width.min(height));
806
807 let std_dev = self.std_dev.max(1e-6);
809
810 let min_edge = width.min(height);
811 let rmax = 0.5 * min_edge;
812 let r0 = radius.hypot(std_dev * 1.15).min(rmax);
813 let r1 = radius.hypot(std_dev * 2.0).min(rmax);
814
815 let exponent = 2.0 * r1 / r0;
816
817 let std_dev_inv = std_dev.recip();
818
819 let delta = 1.25
821 * std_dev
822 * (exp(-(0.5 * std_dev_inv * width).powi(2))
823 - exp(-(0.5 * std_dev_inv * height).powi(2)));
824 let w = width + delta.min(0.0);
825 let h = height - delta.max(0.0);
826
827 let recip_exponent = exponent.recip();
828 let scale = 0.5 * compute_erf7(std_dev_inv * 0.5 * (w.max(h) - 0.5 * radius));
829
830 let encoded = EncodedBlurredRoundedRectangle {
831 exponent,
832 recip_exponent,
833 width,
834 height,
835 scale,
836 r1,
837 std_dev_inv,
838 min_edge,
839 color: PremulColor::from_alpha_color(self.color),
840 w,
841 h,
842 transform,
843 x_advance,
844 y_advance,
845 };
846
847 let idx = paints.len();
848 paints.push(encoded.into());
849
850 Paint::Indexed(IndexedPaint::new(idx))
851 }
852}
853
854fn ts_from_line_to_line(src1: Point, src2: Point, dst1: Point, dst2: Point) -> Affine {
862 let unit_to_line1 = unit_to_line(src1, src2);
863 let line1_to_unit = unit_to_line1.inverse();
865 let unit_to_line2 = unit_to_line(dst1, dst2);
867
868 unit_to_line2 * line1_to_unit
869}
870
871fn unit_to_line(p0: Point, p1: Point) -> Affine {
874 Affine::new([
875 p1.y - p0.y,
876 p0.x - p1.x,
877 p1.x - p0.x,
878 p1.y - p0.y,
879 p0.x,
880 p0.y,
881 ])
882}
883
884pub trait FromF32Color: Sized + Debug + Copy + Clone {
886 const ZERO: Self;
888 fn from_f32<S: Simd>(color: f32x4<S>) -> [Self; 4];
890}
891
892impl FromF32Color for f32 {
893 const ZERO: Self = 0.0;
894
895 fn from_f32<S: Simd>(color: f32x4<S>) -> [Self; 4] {
896 color.val
897 }
898}
899
900impl FromF32Color for u8 {
901 const ZERO: Self = 0;
902
903 fn from_f32<S: Simd>(mut color: f32x4<S>) -> [Self; 4] {
904 let simd = color.simd;
905 color = f32x4::splat(simd, 0.5).madd(color, f32x4::splat(simd, 255.0));
906
907 [
908 color[0] as Self,
909 color[1] as Self,
910 color[2] as Self,
911 color[3] as Self,
912 ]
913 }
914}
915
916#[derive(Debug)]
918pub struct GradientLut<T: FromF32Color> {
919 lut: Vec<[T; 4]>,
920 scale: f32,
921}
922
923impl<T: FromF32Color> GradientLut<T> {
924 fn new<S: Simd>(simd: S, ranges: &[GradientRange]) -> Self {
926 let lut_size = determine_lut_size(ranges);
927
928 let mut lut = vec![[T::ZERO, T::ZERO, T::ZERO, T::ZERO]; lut_size + 3];
931
932 let ramps = {
934 let mut ramps = Vec::with_capacity(ranges.len());
935 let mut prev_idx = 0;
936
937 for range in ranges {
938 let max_idx = (range.x1 * lut_size as f32) as usize;
939
940 ramps.push((prev_idx..max_idx, range));
941 prev_idx = max_idx;
942 }
943
944 ramps
945 };
946
947 let scale = lut_size as f32 - 1.0;
948
949 let inv_lut_scale = f32x4::splat(simd, 1.0 / scale);
950 let add_factor = f32x4::from_slice(simd, &[0.0, 1.0, 2.0, 3.0]) * inv_lut_scale;
951
952 for (ramp_range, range) in ramps {
953 let biases = f32x16::block_splat(f32x4::from_slice(simd, &range.bias));
954 let scales = f32x16::block_splat(f32x4::from_slice(simd, &range.scale));
955
956 ramp_range.step_by(4).for_each(|idx| {
957 let t_vals = add_factor.madd(f32x4::splat(simd, idx as f32), inv_lut_scale);
958
959 let t_vals = element_wise_splat(simd, t_vals);
960
961 let mut result = biases.madd(scales, t_vals);
962 let alphas = result.splat_4th();
963 result = result.min(1.0).min(alphas);
968 let (im1, im2) = simd.split_f32x16(result);
969 let (r1, r2) = simd.split_f32x8(im1);
970 let (r3, r4) = simd.split_f32x8(im2);
971
972 let lut = &mut lut[idx..][..4];
973 lut[0] = T::from_f32(r1);
974 lut[1] = T::from_f32(r2);
975 lut[2] = T::from_f32(r3);
976 lut[3] = T::from_f32(r4);
977 });
978 }
979
980 lut.truncate(lut_size);
982
983 Self { lut, scale }
984 }
985
986 #[inline(always)]
988 pub fn get(&self, idx: usize) -> [T; 4] {
989 self.lut[idx]
990 }
991
992 #[inline(always)]
994 pub fn lut(&self) -> &[[T; 4]] {
995 &self.lut
996 }
997
998 #[inline(always)]
1001 pub fn scale_factor(&self) -> f32 {
1002 self.scale
1003 }
1004}
1005
1006fn determine_lut_size(ranges: &[GradientRange]) -> usize {
1007 const MAX_LEN: usize = 4096;
1011
1012 let stop_len = match ranges.len() {
1018 1 => 256,
1019 2 => 512,
1020 _ => 1024,
1021 };
1022
1023 let mut last_x1 = 0.0;
1026 let mut min_size = 0;
1027
1028 for x1 in ranges.iter().map(|e| e.x1) {
1029 let res = ((1.0 / (x1 - last_x1)).ceil() as usize)
1032 .min(MAX_LEN)
1033 .next_power_of_two();
1034 min_size = min_size.max(res);
1035 last_x1 = x1;
1036 }
1037
1038 stop_len.max(min_size)
1040}
1041
1042mod private {
1043 #[expect(unnameable_types, reason = "Sealed trait pattern.")]
1044 pub trait Sealed {}
1045
1046 impl Sealed for super::Gradient {}
1047}
1048
1049#[cfg(test)]
1050mod tests {
1051 use super::{EncodeExt, Gradient};
1052 use crate::color::DynamicColor;
1053 use crate::color::palette::css::{BLACK, BLUE, GREEN};
1054 use crate::kurbo::{Affine, Point};
1055 use crate::peniko::{ColorStop, ColorStops, GradientKind};
1056 use alloc::vec;
1057 use smallvec::smallvec;
1058
1059 #[test]
1060 fn gradient_missing_stops() {
1061 let mut buf = vec![];
1062
1063 let gradient = Gradient {
1064 kind: GradientKind::Linear {
1065 start: Point::new(0.0, 0.0),
1066 end: Point::new(20.0, 0.0),
1067 },
1068 ..Default::default()
1069 };
1070
1071 assert_eq!(
1072 gradient.encode_into(&mut buf, Affine::IDENTITY),
1073 BLACK.into()
1074 );
1075 }
1076
1077 #[test]
1078 fn gradient_one_stop() {
1079 let mut buf = vec![];
1080
1081 let gradient = Gradient {
1082 kind: GradientKind::Linear {
1083 start: Point::new(0.0, 0.0),
1084 end: Point::new(20.0, 0.0),
1085 },
1086 stops: ColorStops(smallvec![ColorStop {
1087 offset: 0.0,
1088 color: DynamicColor::from_alpha_color(GREEN),
1089 }]),
1090 ..Default::default()
1091 };
1092
1093 assert_eq!(
1095 gradient.encode_into(&mut buf, Affine::IDENTITY),
1096 GREEN.into()
1097 );
1098 }
1099
1100 #[test]
1101 fn gradient_not_sorted_stops() {
1102 let mut buf = vec![];
1103
1104 let gradient = Gradient {
1105 kind: GradientKind::Linear {
1106 start: Point::new(0.0, 0.0),
1107 end: Point::new(20.0, 0.0),
1108 },
1109 stops: ColorStops(smallvec![
1110 ColorStop {
1111 offset: 1.0,
1112 color: DynamicColor::from_alpha_color(GREEN),
1113 },
1114 ColorStop {
1115 offset: 0.0,
1116 color: DynamicColor::from_alpha_color(BLUE),
1117 },
1118 ]),
1119 ..Default::default()
1120 };
1121
1122 assert_eq!(
1123 gradient.encode_into(&mut buf, Affine::IDENTITY),
1124 GREEN.into()
1125 );
1126 }
1127
1128 #[test]
1129 fn gradient_linear_degenerate() {
1130 let mut buf = vec![];
1131
1132 let gradient = Gradient {
1133 kind: GradientKind::Linear {
1134 start: Point::new(0.0, 0.0),
1135 end: Point::new(0.0, 0.0),
1136 },
1137 stops: ColorStops(smallvec![
1138 ColorStop {
1139 offset: 0.0,
1140 color: DynamicColor::from_alpha_color(GREEN),
1141 },
1142 ColorStop {
1143 offset: 1.0,
1144 color: DynamicColor::from_alpha_color(BLUE),
1145 },
1146 ]),
1147 ..Default::default()
1148 };
1149
1150 assert_eq!(
1151 gradient.encode_into(&mut buf, Affine::IDENTITY),
1152 GREEN.into()
1153 );
1154 }
1155
1156 #[test]
1157 fn gradient_radial_degenerate() {
1158 let mut buf = vec![];
1159
1160 let gradient = Gradient {
1161 kind: GradientKind::Radial {
1162 start_center: Point::new(0.0, 0.0),
1163 start_radius: 20.0,
1164 end_center: Point::new(0.0, 0.0),
1165 end_radius: 20.0,
1166 },
1167 stops: ColorStops(smallvec![
1168 ColorStop {
1169 offset: 0.0,
1170 color: DynamicColor::from_alpha_color(GREEN),
1171 },
1172 ColorStop {
1173 offset: 1.0,
1174 color: DynamicColor::from_alpha_color(BLUE),
1175 },
1176 ]),
1177 ..Default::default()
1178 };
1179
1180 assert_eq!(
1181 gradient.encode_into(&mut buf, Affine::IDENTITY),
1182 GREEN.into()
1183 );
1184 }
1185}