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, ColorStops, 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 core::hash::{Hash, Hasher};
20use fearless_simd::{Simd, SimdBase, SimdFloat, f32x4, f32x16, mask32x4, mask32x16};
21use peniko::color::cache_key::{BitEq, BitHash, CacheKey};
22use peniko::color::gradient_unpremultiplied;
23use peniko::{
24 ImageSampler, InterpolationAlphaSpace, LinearGradientPosition, RadialGradientPosition,
25 SweepGradientPosition,
26};
27use smallvec::ToSmallVec;
28#[cfg(feature = "multithreading")]
30use std::sync::OnceLock as OnceCell;
31
32use crate::simd::{Splat4thExt, element_wise_splat};
33#[cfg(not(feature = "std"))]
34use peniko::kurbo::common::FloatFuncs as _;
35
36const DEGENERATE_THRESHOLD: f32 = 1.0e-6;
37const NUDGE_VAL: f32 = 1.0e-7;
38const PIXEL_CENTER_OFFSET: f64 = 0.5;
39
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(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint;
58}
59
60impl EncodeExt for Gradient {
61 fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint {
63 if let Err(paint) = validate(self) {
65 return paint;
66 }
67
68 let mut has_opacities = self.stops.iter().any(|s| s.color.components[3] != 1.0);
69
70 let mut base_transform;
71
72 let mut stops = Cow::Borrowed(&self.stops.0);
73
74 let first_stop = &stops[0];
75 let last_stop = &stops[stops.len() - 1];
76
77 if first_stop.offset != 0.0 || last_stop.offset != 1.0 {
78 let mut vec = stops.to_smallvec();
79
80 if first_stop.offset != 0.0 {
81 let mut first_stop = *first_stop;
82 first_stop.offset = 0.0;
83 vec.insert(0, first_stop);
84 }
85
86 if last_stop.offset != 1.0 {
87 let mut last_stop = *last_stop;
88 last_stop.offset = 1.0;
89 vec.push(last_stop);
90 }
91
92 stops = Cow::Owned(vec);
93 }
94
95 let kind = match self.kind {
96 GradientKind::Linear(LinearGradientPosition { start: p0, end: p1 }) => {
97 base_transform = ts_from_line_to_line(p0, p1, Point::ZERO, Point::new(1.0, 0.0));
101
102 EncodedKind::Linear(LinearKind)
103 }
104 GradientKind::Radial(RadialGradientPosition {
105 start_center: c0,
106 start_radius: r0,
107 end_center: c1,
108 end_radius: r1,
109 }) => {
110 let d_radius = r1 - r0;
116
117 let radial_kind = if ((c1 - c0).length() as f32).is_nearly_zero() {
119 base_transform = Affine::translate((-c1.x, -c1.y));
120 base_transform = base_transform.then_scale(1.0 / r0.max(r1) as f64);
121
122 let scale = r1.max(r0) / d_radius;
123 let bias = -r0 / d_radius;
124
125 RadialKind::Radial { bias, scale }
126 } else {
127 base_transform =
128 ts_from_line_to_line(c0, c1, Point::ZERO, Point::new(1.0, 0.0));
129
130 if (r1 - r0).is_nearly_zero() {
131 let scaled_r0 = r1 / (c1 - c0).length() as f32;
132 RadialKind::Strip {
133 scaled_r0_squared: scaled_r0 * scaled_r0,
134 }
135 } else {
136 let d_center = (c0 - c1).length() as f32;
137
138 let focal_data =
139 FocalData::create(r0 / d_center, r1 / d_center, &mut base_transform);
140
141 let fp0 = 1.0 / focal_data.fr1;
142 let fp1 = focal_data.f_focal_x;
143
144 RadialKind::Focal {
145 focal_data,
146 fp0,
147 fp1,
148 }
149 }
150 };
151
152 has_opacities |= radial_kind.has_undefined();
157
158 EncodedKind::Radial(radial_kind)
159 }
160 GradientKind::Sweep(SweepGradientPosition {
161 center,
162 start_angle,
163 end_angle,
164 }) => {
165 let x_offset = -center.x as f32;
168 let y_offset = -center.y as f32;
169 base_transform = Affine::translate((x_offset as f64, y_offset as f64));
170
171 EncodedKind::Sweep(SweepKind {
172 start_angle,
173 inv_angle_delta: 1.0 / (end_angle - start_angle),
175 })
176 }
177 };
178
179 let ranges = encode_stops(
180 &stops,
181 self.interpolation_cs,
182 self.hue_direction,
183 self.interpolation_alpha_space,
184 );
185
186 let transform = base_transform
194 * transform.inverse()
195 * Affine::translate((PIXEL_CENTER_OFFSET, PIXEL_CENTER_OFFSET));
196
197 let (x_advance, y_advance) = x_y_advances(&transform);
210
211 let cache_key = CacheKey(GradientCacheKey {
212 stops: self.stops.clone(),
213 interpolation_cs: self.interpolation_cs,
214 hue_direction: self.hue_direction,
215 });
216
217 let encoded = EncodedGradient {
218 cache_key,
219 kind,
220 transform,
221 x_advance,
222 y_advance,
223 ranges,
224 extend: self.extend,
225 has_opacities,
226 u8_lut: OnceCell::new(),
227 f32_lut: OnceCell::new(),
228 };
229
230 let idx = paints.len();
231 paints.push(encoded.into());
232
233 Paint::Indexed(IndexedPaint::new(idx))
234 }
235}
236
237fn validate(gradient: &Gradient) -> Result<(), Paint> {
241 let black = Err(BLACK.into());
242
243 if gradient.stops.is_empty() {
245 return black;
246 }
247
248 let first = Err(gradient.stops[0].color.to_alpha_color::<Srgb>().into());
249
250 if gradient.stops.len() == 1 {
251 return first;
252 }
253
254 for stops in gradient.stops.windows(2) {
255 let f = stops[0];
256 let n = stops[1];
257
258 if f.offset > 1.0 || f.offset < 0.0 {
260 return first;
261 }
262
263 if f.offset > n.offset {
265 return first;
266 }
267 }
268
269 let degenerate_point = |p1: &Point, p2: &Point| {
270 (p1.x - p2.x).abs() as f32 <= DEGENERATE_THRESHOLD
271 && (p1.y - p2.y).abs() as f32 <= DEGENERATE_THRESHOLD
272 };
273
274 let degenerate_val = |v1: f32, v2: f32| (v2 - v1).abs() <= DEGENERATE_THRESHOLD;
275
276 match &gradient.kind {
277 GradientKind::Linear(LinearGradientPosition { start, end }) => {
278 if degenerate_point(start, end) {
280 return first;
281 }
282 }
283 GradientKind::Radial(RadialGradientPosition {
284 start_center,
285 start_radius,
286 end_center,
287 end_radius,
288 }) => {
289 if *start_radius < 0.0 || *end_radius < 0.0 {
291 return first;
292 }
293
294 if degenerate_point(start_center, end_center)
296 && degenerate_val(*start_radius, *end_radius)
297 {
298 return first;
299 }
300 }
301 GradientKind::Sweep(SweepGradientPosition {
302 start_angle,
303 end_angle,
304 ..
305 }) => {
306 if degenerate_val(*start_angle, *end_angle) {
308 return first;
309 }
310
311 if end_angle <= start_angle {
312 return first;
313 }
314 }
315 }
316
317 Ok(())
318}
319
320fn encode_stops(
322 stops: &[ColorStop],
323 cs: ColorSpaceTag,
324 hue_dir: HueDirection,
325 interpolation_alpha_space: InterpolationAlphaSpace,
326) -> Vec<GradientRange> {
327 #[derive(Debug)]
328 struct EncodedColorStop {
329 offset: f32,
330 color: crate::color::AlphaColor<Srgb>,
331 }
332
333 let create_range = |left_stop: &EncodedColorStop, right_stop: &EncodedColorStop| {
334 let clamp = |mut color: [f32; 4]| {
335 for c in &mut color {
338 *c = c.clamp(0.0, 1.0);
339 }
340
341 color
342 };
343
344 let x0 = left_stop.offset;
345 let x1 = right_stop.offset;
346 let c0 = if interpolation_alpha_space == InterpolationAlphaSpace::Unpremultiplied {
347 clamp(left_stop.color.components)
348 } else {
349 clamp(left_stop.color.premultiply().components)
350 };
351 let c1 = if interpolation_alpha_space == InterpolationAlphaSpace::Unpremultiplied {
352 clamp(right_stop.color.components)
353 } else {
354 clamp(right_stop.color.premultiply().components)
355 };
356
357 let x1_minus_x0 = (x1 - x0).max(NUDGE_VAL);
363 let mut scale = [0.0; 4];
364 let mut bias = c0;
365
366 for i in 0..4 {
367 scale[i] = (c1[i] - c0[i]) / x1_minus_x0;
368 bias[i] = c0[i] - x0 * scale[i];
369 }
370
371 GradientRange {
372 x1,
373 bias,
374 scale,
375 interpolation_alpha_space,
376 }
377 };
378
379 if cs != ColorSpaceTag::Srgb {
382 let interpolated_stops = if interpolation_alpha_space
383 == InterpolationAlphaSpace::Premultiplied
384 {
385 stops
386 .windows(2)
387 .flat_map(|s| {
388 let left_stop = &s[0];
389 let right_stop = &s[1];
390
391 let interpolated =
392 gradient::<Srgb>(left_stop.color, right_stop.color, cs, hue_dir, 0.01);
393
394 interpolated.map(|st| EncodedColorStop {
395 offset: left_stop.offset + (right_stop.offset - left_stop.offset) * st.0,
396 color: st.1.un_premultiply(),
397 })
398 })
399 .collect::<Vec<_>>()
400 } else {
401 stops
402 .windows(2)
403 .flat_map(|s| {
404 let left_stop = &s[0];
405 let right_stop = &s[1];
406
407 let interpolated = gradient_unpremultiplied::<Srgb>(
408 left_stop.color,
409 right_stop.color,
410 cs,
411 hue_dir,
412 0.01,
413 );
414
415 interpolated.map(|st| EncodedColorStop {
416 offset: left_stop.offset + (right_stop.offset - left_stop.offset) * st.0,
417 color: st.1,
418 })
419 })
420 .collect::<Vec<_>>()
421 };
422
423 interpolated_stops
424 .windows(2)
425 .map(|s| {
426 let left_stop = &s[0];
427 let right_stop = &s[1];
428
429 create_range(left_stop, right_stop)
430 })
431 .collect()
432 } else {
433 stops
434 .windows(2)
435 .map(|c| {
436 let c0 = EncodedColorStop {
437 offset: c[0].offset,
438 color: c[0].color.to_alpha_color::<Srgb>(),
439 };
440
441 let c1 = EncodedColorStop {
442 offset: c[1].offset,
443 color: c[1].color.to_alpha_color::<Srgb>(),
444 };
445
446 create_range(&c0, &c1)
447 })
448 .collect()
449 }
450}
451
452pub(crate) fn x_y_advances(transform: &Affine) -> (Vec2, Vec2) {
453 let scale_skew_transform = {
454 let c = transform.as_coeffs();
455 Affine::new([c[0], c[1], c[2], c[3], 0.0, 0.0])
456 };
457
458 let x_advance = scale_skew_transform * Point::new(1.0, 0.0);
459 let y_advance = scale_skew_transform * Point::new(0.0, 1.0);
460
461 (
462 Vec2::new(x_advance.x, x_advance.y),
463 Vec2::new(y_advance.x, y_advance.y),
464 )
465}
466
467impl private::Sealed for Image {}
468
469impl EncodeExt for Image {
470 fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint {
471 let idx = paints.len();
472
473 let mut sampler = self.sampler;
474
475 if sampler.alpha != 1.0 {
476 unimplemented!("Applying opacity to image commands");
478 }
479
480 let c = transform.as_coeffs();
481
482 if (c[0] as f32 - 1.0).is_nearly_zero()
484 && (c[1] as f32).is_nearly_zero()
485 && (c[2] as f32).is_nearly_zero()
486 && (c[3] as f32 - 1.0).is_nearly_zero()
487 && ((c[4] - c[4].floor()) as f32).is_nearly_zero()
488 && ((c[5] - c[5].floor()) as f32).is_nearly_zero()
489 && sampler.quality == ImageQuality::Medium
490 {
491 sampler.quality = ImageQuality::Low;
492 }
493
494 let transform = transform.inverse() * Affine::translate((0.5, 0.5));
497
498 let (x_advance, y_advance) = x_y_advances(&transform);
499
500 let encoded = match &self.image {
501 ImageSource::Pixmap(pixmap) => {
502 EncodedImage {
503 source: ImageSource::Pixmap(pixmap.clone()),
504 sampler,
505 has_opacities: true,
507 transform,
508 x_advance,
509 y_advance,
510 }
511 }
512 ImageSource::OpaqueId(image) => EncodedImage {
513 source: ImageSource::OpaqueId(*image),
514 sampler,
515 has_opacities: true,
516 transform,
517 x_advance,
518 y_advance,
519 },
520 };
521
522 paints.push(EncodedPaint::Image(encoded));
523
524 Paint::Indexed(IndexedPaint::new(idx))
525 }
526}
527
528#[derive(Debug)]
530pub enum EncodedPaint {
531 Gradient(EncodedGradient),
533 Image(EncodedImage),
535 BlurredRoundedRect(EncodedBlurredRoundedRectangle),
537}
538
539impl From<EncodedGradient> for EncodedPaint {
540 fn from(value: EncodedGradient) -> Self {
541 Self::Gradient(value)
542 }
543}
544
545impl From<EncodedBlurredRoundedRectangle> for EncodedPaint {
546 fn from(value: EncodedBlurredRoundedRectangle) -> Self {
547 Self::BlurredRoundedRect(value)
548 }
549}
550
551#[derive(Debug)]
553pub struct EncodedImage {
554 pub source: ImageSource,
556 pub sampler: ImageSampler,
558 pub has_opacities: bool,
560 pub transform: Affine,
562 pub x_advance: Vec2,
564 pub y_advance: Vec2,
566}
567
568#[derive(Debug, Copy, Clone)]
570pub struct LinearKind;
571
572#[derive(Debug, PartialEq, Copy, Clone)]
574pub struct FocalData {
575 pub fr1: f32,
577 pub f_focal_x: f32,
579 pub f_is_swapped: bool,
581}
582
583impl FocalData {
584 pub fn create(mut r0: f32, mut r1: f32, matrix: &mut Affine) -> Self {
586 let mut swapped = false;
587 let mut f_focal_x = r0 / (r0 - r1);
588
589 if (f_focal_x - 1.0).is_nearly_zero() {
590 *matrix = matrix.then_translate(Vec2::new(-1.0, 0.0));
591 *matrix = matrix.then_scale_non_uniform(-1.0, 1.0);
592 core::mem::swap(&mut r0, &mut r1);
593 f_focal_x = 0.0;
594 swapped = true;
595 }
596
597 let focal_matrix = ts_from_line_to_line(
598 Point::new(f_focal_x as f64, 0.0),
599 Point::new(1.0, 0.0),
600 Point::new(0.0, 0.0),
601 Point::new(1.0, 0.0),
602 );
603 *matrix = focal_matrix * *matrix;
604
605 let fr1 = r1 / (1.0 - f_focal_x).abs();
606
607 let data = Self {
608 fr1,
609 f_focal_x,
610 f_is_swapped: swapped,
611 };
612
613 if data.is_focal_on_circle() {
614 *matrix = matrix.then_scale(0.5);
615 } else {
616 *matrix = matrix.then_scale_non_uniform(
617 (fr1 / (fr1 * fr1 - 1.0)) as f64,
618 1.0 / (fr1 * fr1 - 1.0).abs().sqrt() as f64,
619 );
620 }
621
622 *matrix = matrix.then_scale((1.0 - f_focal_x).abs() as f64);
623
624 data
625 }
626
627 pub fn is_focal_on_circle(&self) -> bool {
629 (1.0 - self.fr1).is_nearly_zero()
630 }
631
632 pub fn is_swapped(&self) -> bool {
634 self.f_is_swapped
635 }
636
637 pub fn is_well_behaved(&self) -> bool {
639 !self.is_focal_on_circle() && self.fr1 > 1.0
640 }
641
642 pub fn is_natively_focal(&self) -> bool {
644 self.f_focal_x.is_nearly_zero()
645 }
646}
647
648#[derive(Debug, PartialEq, Copy, Clone)]
650pub enum RadialKind {
651 Radial {
653 bias: f32,
658 scale: f32,
662 },
663 Strip {
665 scaled_r0_squared: f32,
667 },
668 Focal {
670 focal_data: FocalData,
672 fp0: f32,
674 fp1: f32,
676 },
677}
678
679impl RadialKind {
680 pub fn has_undefined(&self) -> bool {
682 match self {
683 Self::Radial { .. } => false,
684 Self::Strip { .. } => true,
685 Self::Focal { focal_data, .. } => !focal_data.is_well_behaved(),
686 }
687 }
688}
689
690#[derive(Debug)]
692pub struct SweepKind {
693 pub start_angle: f32,
695 pub inv_angle_delta: f32,
697}
698
699#[derive(Debug)]
701pub enum EncodedKind {
702 Linear(LinearKind),
704 Radial(RadialKind),
706 Sweep(SweepKind),
708}
709
710#[derive(Debug)]
712pub struct EncodedGradient {
713 pub cache_key: CacheKey<GradientCacheKey>,
715 pub kind: EncodedKind,
717 pub transform: Affine,
719 pub x_advance: Vec2,
721 pub y_advance: Vec2,
723 pub ranges: Vec<GradientRange>,
725 pub extend: Extend,
727 pub has_opacities: bool,
729 u8_lut: OnceCell<GradientLut<u8>>,
730 f32_lut: OnceCell<GradientLut<f32>>,
731}
732
733impl EncodedGradient {
734 pub fn u8_lut<S: Simd>(&self, simd: S) -> &GradientLut<u8> {
736 self.u8_lut
737 .get_or_init(|| GradientLut::new(simd, &self.ranges))
738 }
739
740 pub fn f32_lut<S: Simd>(&self, simd: S) -> &GradientLut<f32> {
742 self.f32_lut
743 .get_or_init(|| GradientLut::new(simd, &self.ranges))
744 }
745}
746
747#[derive(Debug, Clone)]
749pub struct GradientCacheKey {
750 pub stops: ColorStops,
752 pub interpolation_cs: ColorSpaceTag,
754 pub hue_direction: HueDirection,
756}
757
758impl BitHash for GradientCacheKey {
759 fn bit_hash<H: Hasher>(&self, state: &mut H) {
760 self.stops.bit_hash(state);
761 core::mem::discriminant(&self.interpolation_cs).hash(state);
762 core::mem::discriminant(&self.hue_direction).hash(state);
763 }
764}
765
766impl BitEq for GradientCacheKey {
767 fn bit_eq(&self, other: &Self) -> bool {
768 self.stops.bit_eq(&other.stops)
769 && self.interpolation_cs == other.interpolation_cs
770 && self.hue_direction == other.hue_direction
771 }
772}
773
774#[derive(Debug, Clone)]
776pub struct GradientRange {
777 pub x1: f32,
779 pub bias: [f32; 4],
782 pub scale: [f32; 4],
785 pub interpolation_alpha_space: InterpolationAlphaSpace,
787}
788
789#[derive(Debug)]
791pub struct EncodedBlurredRoundedRectangle {
792 pub exponent: f32,
794 pub recip_exponent: f32,
796 pub scale: f32,
798 pub std_dev_inv: f32,
800 pub min_edge: f32,
802 pub w: f32,
804 pub h: f32,
806 pub width: f32,
808 pub height: f32,
810 pub r1: f32,
812 pub color: PremulColor,
814 pub transform: Affine,
816 pub x_advance: Vec2,
818 pub y_advance: Vec2,
820}
821
822impl private::Sealed for BlurredRoundedRectangle {}
823
824impl EncodeExt for BlurredRoundedRectangle {
825 fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint {
826 let rect = {
827 let mut rect = self.rect;
829
830 if self.rect.x0 > self.rect.x1 {
831 core::mem::swap(&mut rect.x0, &mut rect.x1);
832 }
833
834 if self.rect.y0 > self.rect.y1 {
835 core::mem::swap(&mut rect.y0, &mut rect.y1);
836 }
837
838 rect
839 };
840
841 let transform = Affine::translate((-rect.x0, -rect.y0)) * transform.inverse();
842
843 let (x_advance, y_advance) = x_y_advances(&transform);
844
845 let width = rect.width() as f32;
846 let height = rect.height() as f32;
847 let radius = self.radius.min(0.5 * width.min(height));
848
849 let std_dev = self.std_dev.max(1e-6);
851
852 let min_edge = width.min(height);
853 let rmax = 0.5 * min_edge;
854 let r0 = radius.hypot(std_dev * 1.15).min(rmax);
855 let r1 = radius.hypot(std_dev * 2.0).min(rmax);
856
857 let exponent = 2.0 * r1 / r0;
858
859 let std_dev_inv = std_dev.recip();
860
861 let delta = 1.25
863 * std_dev
864 * (exp(-(0.5 * std_dev_inv * width).powi(2))
865 - exp(-(0.5 * std_dev_inv * height).powi(2)));
866 let w = width + delta.min(0.0);
867 let h = height - delta.max(0.0);
868
869 let recip_exponent = exponent.recip();
870 let scale = 0.5 * compute_erf7(std_dev_inv * 0.5 * (w.max(h) - 0.5 * radius));
871
872 let encoded = EncodedBlurredRoundedRectangle {
873 exponent,
874 recip_exponent,
875 width,
876 height,
877 scale,
878 r1,
879 std_dev_inv,
880 min_edge,
881 color: PremulColor::from_alpha_color(self.color),
882 w,
883 h,
884 transform,
885 x_advance,
886 y_advance,
887 };
888
889 let idx = paints.len();
890 paints.push(encoded.into());
891
892 Paint::Indexed(IndexedPaint::new(idx))
893 }
894}
895
896fn ts_from_line_to_line(src1: Point, src2: Point, dst1: Point, dst2: Point) -> Affine {
904 let unit_to_line1 = unit_to_line(src1, src2);
905 let line1_to_unit = unit_to_line1.inverse();
907 let unit_to_line2 = unit_to_line(dst1, dst2);
909
910 unit_to_line2 * line1_to_unit
911}
912
913fn unit_to_line(p0: Point, p1: Point) -> Affine {
916 Affine::new([
917 p1.y - p0.y,
918 p0.x - p1.x,
919 p1.x - p0.x,
920 p1.y - p0.y,
921 p0.x,
922 p0.y,
923 ])
924}
925
926pub trait FromF32Color: Sized + Debug + Copy + Clone {
928 const ZERO: Self;
930 fn from_f32<S: Simd>(color: f32x4<S>) -> [Self; 4];
932}
933
934impl FromF32Color for f32 {
935 const ZERO: Self = 0.0;
936
937 fn from_f32<S: Simd>(color: f32x4<S>) -> [Self; 4] {
938 color.val
939 }
940}
941
942impl FromF32Color for u8 {
943 const ZERO: Self = 0;
944
945 fn from_f32<S: Simd>(mut color: f32x4<S>) -> [Self; 4] {
946 let simd = color.simd;
947 color = color.madd(f32x4::splat(simd, 255.0), f32x4::splat(simd, 0.5));
948
949 [
950 color[0] as Self,
951 color[1] as Self,
952 color[2] as Self,
953 color[3] as Self,
954 ]
955 }
956}
957
958#[derive(Debug)]
960pub struct GradientLut<T: FromF32Color> {
961 lut: Vec<[T; 4]>,
962 scale: f32,
963}
964
965impl<T: FromF32Color> GradientLut<T> {
966 fn new<S: Simd>(simd: S, ranges: &[GradientRange]) -> Self {
968 let lut_size = determine_lut_size(ranges);
969
970 let mut lut = vec![[T::ZERO, T::ZERO, T::ZERO, T::ZERO]; lut_size + 3];
973
974 let ramps = {
976 let mut ramps = Vec::with_capacity(ranges.len());
977 let mut prev_idx = 0;
978
979 for range in ranges {
980 let max_idx = (range.x1 * lut_size as f32) as usize;
981
982 ramps.push((prev_idx..max_idx, range));
983 prev_idx = max_idx;
984 }
985
986 ramps
987 };
988
989 let scale = lut_size as f32 - 1.0;
990
991 let inv_lut_scale = f32x4::splat(simd, 1.0 / scale);
992 let add_factor = f32x4::from_slice(simd, &[0.0, 1.0, 2.0, 3.0]) * inv_lut_scale;
993
994 for (ramp_range, range) in ramps {
995 let biases = f32x16::block_splat(f32x4::from_slice(simd, &range.bias));
996 let scales = f32x16::block_splat(f32x4::from_slice(simd, &range.scale));
997
998 ramp_range.step_by(4).for_each(|idx| {
999 let t_vals = f32x4::splat(simd, idx as f32).madd(inv_lut_scale, add_factor);
1000
1001 let t_vals = element_wise_splat(simd, t_vals);
1002
1003 let mut result = scales.madd(t_vals, biases);
1004 let alphas = result.splat_4th();
1005 if range.interpolation_alpha_space == InterpolationAlphaSpace::Unpremultiplied {
1007 result = {
1008 let mask =
1009 mask32x16::block_splat(mask32x4::from_slice(simd, &[-1, -1, -1, 0]));
1010 simd.select_f32x16(mask, result * alphas, alphas)
1011 };
1012 }
1013
1014 result = result.min(1.0).min(alphas);
1019 let (im1, im2) = simd.split_f32x16(result);
1020 let (r1, r2) = simd.split_f32x8(im1);
1021 let (r3, r4) = simd.split_f32x8(im2);
1022
1023 let lut = &mut lut[idx..][..4];
1024 lut[0] = T::from_f32(r1);
1025 lut[1] = T::from_f32(r2);
1026 lut[2] = T::from_f32(r3);
1027 lut[3] = T::from_f32(r4);
1028 });
1029 }
1030
1031 lut.truncate(lut_size);
1033
1034 Self { lut, scale }
1035 }
1036
1037 #[inline(always)]
1039 pub fn get(&self, idx: usize) -> [T; 4] {
1040 self.lut[idx]
1041 }
1042
1043 #[inline(always)]
1045 pub fn lut(&self) -> &[[T; 4]] {
1046 &self.lut
1047 }
1048
1049 #[inline(always)]
1052 pub fn scale_factor(&self) -> f32 {
1053 self.scale
1054 }
1055}
1056
1057pub const MAX_GRADIENT_LUT_SIZE: usize = 4096;
1062
1063fn determine_lut_size(ranges: &[GradientRange]) -> usize {
1064 let stop_len = match ranges.len() {
1070 1 => 256,
1071 2 => 512,
1072 _ => 1024,
1073 };
1074
1075 let mut last_x1 = 0.0;
1078 let mut min_size = 0;
1079
1080 for x1 in ranges.iter().map(|e| e.x1) {
1081 let res = ((1.0 / (x1 - last_x1)).ceil() as usize)
1084 .min(MAX_GRADIENT_LUT_SIZE)
1085 .next_power_of_two();
1086 min_size = min_size.max(res);
1087 last_x1 = x1;
1088 }
1089
1090 stop_len.max(min_size)
1092}
1093
1094mod private {
1095 #[expect(unnameable_types, reason = "Sealed trait pattern.")]
1096 pub trait Sealed {}
1097
1098 impl Sealed for super::Gradient {}
1099}
1100
1101#[cfg(test)]
1102mod tests {
1103 use super::{EncodeExt, Gradient};
1104 use crate::color::DynamicColor;
1105 use crate::color::palette::css::{BLACK, BLUE, GREEN};
1106 use crate::kurbo::{Affine, Point};
1107 use crate::peniko::{ColorStop, ColorStops};
1108 use alloc::vec;
1109 use peniko::{LinearGradientPosition, RadialGradientPosition};
1110 use smallvec::smallvec;
1111
1112 #[test]
1113 fn gradient_missing_stops() {
1114 let mut buf = vec![];
1115
1116 let gradient = Gradient {
1117 kind: LinearGradientPosition {
1118 start: Point::new(0.0, 0.0),
1119 end: Point::new(20.0, 0.0),
1120 }
1121 .into(),
1122 ..Default::default()
1123 };
1124
1125 assert_eq!(
1126 gradient.encode_into(&mut buf, Affine::IDENTITY),
1127 BLACK.into()
1128 );
1129 }
1130
1131 #[test]
1132 fn gradient_one_stop() {
1133 let mut buf = vec![];
1134
1135 let gradient = Gradient {
1136 kind: LinearGradientPosition {
1137 start: Point::new(0.0, 0.0),
1138 end: Point::new(20.0, 0.0),
1139 }
1140 .into(),
1141 stops: ColorStops(smallvec![ColorStop {
1142 offset: 0.0,
1143 color: DynamicColor::from_alpha_color(GREEN),
1144 }]),
1145 ..Default::default()
1146 };
1147
1148 assert_eq!(
1150 gradient.encode_into(&mut buf, Affine::IDENTITY),
1151 GREEN.into()
1152 );
1153 }
1154
1155 #[test]
1156 fn gradient_not_sorted_stops() {
1157 let mut buf = vec![];
1158
1159 let gradient = Gradient {
1160 kind: LinearGradientPosition {
1161 start: Point::new(0.0, 0.0),
1162 end: Point::new(20.0, 0.0),
1163 }
1164 .into(),
1165 stops: ColorStops(smallvec![
1166 ColorStop {
1167 offset: 1.0,
1168 color: DynamicColor::from_alpha_color(GREEN),
1169 },
1170 ColorStop {
1171 offset: 0.0,
1172 color: DynamicColor::from_alpha_color(BLUE),
1173 },
1174 ]),
1175 ..Default::default()
1176 };
1177
1178 assert_eq!(
1179 gradient.encode_into(&mut buf, Affine::IDENTITY),
1180 GREEN.into()
1181 );
1182 }
1183
1184 #[test]
1185 fn gradient_linear_degenerate() {
1186 let mut buf = vec![];
1187
1188 let gradient = Gradient {
1189 kind: LinearGradientPosition {
1190 start: Point::new(0.0, 0.0),
1191 end: Point::new(0.0, 0.0),
1192 }
1193 .into(),
1194 stops: ColorStops(smallvec![
1195 ColorStop {
1196 offset: 0.0,
1197 color: DynamicColor::from_alpha_color(GREEN),
1198 },
1199 ColorStop {
1200 offset: 1.0,
1201 color: DynamicColor::from_alpha_color(BLUE),
1202 },
1203 ]),
1204 ..Default::default()
1205 };
1206
1207 assert_eq!(
1208 gradient.encode_into(&mut buf, Affine::IDENTITY),
1209 GREEN.into()
1210 );
1211 }
1212
1213 #[test]
1214 fn gradient_radial_degenerate() {
1215 let mut buf = vec![];
1216
1217 let gradient = Gradient {
1218 kind: RadialGradientPosition {
1219 start_center: Point::new(0.0, 0.0),
1220 start_radius: 20.0,
1221 end_center: Point::new(0.0, 0.0),
1222 end_radius: 20.0,
1223 }
1224 .into(),
1225 stops: ColorStops(smallvec![
1226 ColorStop {
1227 offset: 0.0,
1228 color: DynamicColor::from_alpha_color(GREEN),
1229 },
1230 ColorStop {
1231 offset: 1.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),
1240 GREEN.into()
1241 );
1242 }
1243}