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 may_have_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 may_have_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 may_have_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) => EncodedImage {
502 source: ImageSource::Pixmap(pixmap.clone()),
503 sampler,
504 may_have_opacities: pixmap.may_have_opacities(),
505 transform,
506 x_advance,
507 y_advance,
508 },
509 ImageSource::OpaqueId(image) => EncodedImage {
510 source: ImageSource::OpaqueId(*image),
511 sampler,
512 may_have_opacities: true,
515 transform,
516 x_advance,
517 y_advance,
518 },
519 };
520
521 paints.push(EncodedPaint::Image(encoded));
522
523 Paint::Indexed(IndexedPaint::new(idx))
524 }
525}
526
527#[derive(Debug)]
529pub enum EncodedPaint {
530 Gradient(EncodedGradient),
532 Image(EncodedImage),
534 BlurredRoundedRect(EncodedBlurredRoundedRectangle),
536}
537
538impl From<EncodedGradient> for EncodedPaint {
539 fn from(value: EncodedGradient) -> Self {
540 Self::Gradient(value)
541 }
542}
543
544impl From<EncodedBlurredRoundedRectangle> for EncodedPaint {
545 fn from(value: EncodedBlurredRoundedRectangle) -> Self {
546 Self::BlurredRoundedRect(value)
547 }
548}
549
550#[derive(Debug)]
552pub struct EncodedImage {
553 pub source: ImageSource,
555 pub sampler: ImageSampler,
557 pub may_have_opacities: bool,
559 pub transform: Affine,
561 pub x_advance: Vec2,
563 pub y_advance: Vec2,
565}
566
567#[derive(Debug, Copy, Clone)]
569pub struct LinearKind;
570
571#[derive(Debug, PartialEq, Copy, Clone)]
573pub struct FocalData {
574 pub fr1: f32,
576 pub f_focal_x: f32,
578 pub f_is_swapped: bool,
580}
581
582impl FocalData {
583 pub fn create(mut r0: f32, mut r1: f32, matrix: &mut Affine) -> Self {
585 let mut swapped = false;
586 let mut f_focal_x = r0 / (r0 - r1);
587
588 if (f_focal_x - 1.0).is_nearly_zero() {
589 *matrix = matrix.then_translate(Vec2::new(-1.0, 0.0));
590 *matrix = matrix.then_scale_non_uniform(-1.0, 1.0);
591 core::mem::swap(&mut r0, &mut r1);
592 f_focal_x = 0.0;
593 swapped = true;
594 }
595
596 let focal_matrix = ts_from_line_to_line(
597 Point::new(f_focal_x as f64, 0.0),
598 Point::new(1.0, 0.0),
599 Point::new(0.0, 0.0),
600 Point::new(1.0, 0.0),
601 );
602 *matrix = focal_matrix * *matrix;
603
604 let fr1 = r1 / (1.0 - f_focal_x).abs();
605
606 let data = Self {
607 fr1,
608 f_focal_x,
609 f_is_swapped: swapped,
610 };
611
612 if data.is_focal_on_circle() {
613 *matrix = matrix.then_scale(0.5);
614 } else {
615 *matrix = matrix.then_scale_non_uniform(
616 (fr1 / (fr1 * fr1 - 1.0)) as f64,
617 1.0 / (fr1 * fr1 - 1.0).abs().sqrt() as f64,
618 );
619 }
620
621 *matrix = matrix.then_scale((1.0 - f_focal_x).abs() as f64);
622
623 data
624 }
625
626 pub fn is_focal_on_circle(&self) -> bool {
628 (1.0 - self.fr1).is_nearly_zero()
629 }
630
631 pub fn is_swapped(&self) -> bool {
633 self.f_is_swapped
634 }
635
636 pub fn is_well_behaved(&self) -> bool {
638 !self.is_focal_on_circle() && self.fr1 > 1.0
639 }
640
641 pub fn is_natively_focal(&self) -> bool {
643 self.f_focal_x.is_nearly_zero()
644 }
645}
646
647#[derive(Debug, PartialEq, Copy, Clone)]
649pub enum RadialKind {
650 Radial {
652 bias: f32,
657 scale: f32,
661 },
662 Strip {
664 scaled_r0_squared: f32,
666 },
667 Focal {
669 focal_data: FocalData,
671 fp0: f32,
673 fp1: f32,
675 },
676}
677
678impl RadialKind {
679 pub fn has_undefined(&self) -> bool {
681 match self {
682 Self::Radial { .. } => false,
683 Self::Strip { .. } => true,
684 Self::Focal { focal_data, .. } => !focal_data.is_well_behaved(),
685 }
686 }
687}
688
689#[derive(Debug)]
691pub struct SweepKind {
692 pub start_angle: f32,
694 pub inv_angle_delta: f32,
696}
697
698#[derive(Debug)]
700pub enum EncodedKind {
701 Linear(LinearKind),
703 Radial(RadialKind),
705 Sweep(SweepKind),
707}
708
709impl EncodedKind {
710 fn has_undefined(&self) -> bool {
712 match self {
713 Self::Radial(radial_kind) => radial_kind.has_undefined(),
714 _ => false,
715 }
716 }
717}
718
719#[derive(Debug)]
721pub struct EncodedGradient {
722 pub cache_key: CacheKey<GradientCacheKey>,
724 pub kind: EncodedKind,
726 pub transform: Affine,
728 pub x_advance: Vec2,
730 pub y_advance: Vec2,
732 pub ranges: Vec<GradientRange>,
734 pub extend: Extend,
736 pub may_have_opacities: bool,
738 u8_lut: OnceCell<GradientLut<u8>>,
739 f32_lut: OnceCell<GradientLut<f32>>,
740}
741
742impl EncodedGradient {
743 pub fn u8_lut<S: Simd>(&self, simd: S) -> &GradientLut<u8> {
745 self.u8_lut
746 .get_or_init(|| GradientLut::new(simd, &self.ranges, self.kind.has_undefined()))
747 }
748
749 pub fn f32_lut<S: Simd>(&self, simd: S) -> &GradientLut<f32> {
751 self.f32_lut
752 .get_or_init(|| GradientLut::new(simd, &self.ranges, self.kind.has_undefined()))
753 }
754}
755
756#[derive(Debug, Clone)]
758pub struct GradientCacheKey {
759 pub stops: ColorStops,
761 pub interpolation_cs: ColorSpaceTag,
763 pub hue_direction: HueDirection,
765}
766
767impl BitHash for GradientCacheKey {
768 fn bit_hash<H: Hasher>(&self, state: &mut H) {
769 self.stops.bit_hash(state);
770 core::mem::discriminant(&self.interpolation_cs).hash(state);
771 core::mem::discriminant(&self.hue_direction).hash(state);
772 }
773}
774
775impl BitEq for GradientCacheKey {
776 fn bit_eq(&self, other: &Self) -> bool {
777 self.stops.bit_eq(&other.stops)
778 && self.interpolation_cs == other.interpolation_cs
779 && self.hue_direction == other.hue_direction
780 }
781}
782
783#[derive(Debug, Clone)]
785pub struct GradientRange {
786 pub x1: f32,
788 pub bias: [f32; 4],
791 pub scale: [f32; 4],
794 pub interpolation_alpha_space: InterpolationAlphaSpace,
796}
797
798#[derive(Debug)]
800pub struct EncodedBlurredRoundedRectangle {
801 pub exponent: f32,
803 pub recip_exponent: f32,
805 pub scale: f32,
807 pub std_dev_inv: f32,
809 pub min_edge: f32,
811 pub w: f32,
813 pub h: f32,
815 pub width: f32,
817 pub height: f32,
819 pub r1: f32,
821 pub color: PremulColor,
823 pub transform: Affine,
825 pub x_advance: Vec2,
827 pub y_advance: Vec2,
829}
830
831impl private::Sealed for BlurredRoundedRectangle {}
832
833impl EncodeExt for BlurredRoundedRectangle {
834 fn encode_into(&self, paints: &mut Vec<EncodedPaint>, transform: Affine) -> Paint {
835 let rect = {
836 let mut rect = self.rect;
838
839 if self.rect.x0 > self.rect.x1 {
840 core::mem::swap(&mut rect.x0, &mut rect.x1);
841 }
842
843 if self.rect.y0 > self.rect.y1 {
844 core::mem::swap(&mut rect.y0, &mut rect.y1);
845 }
846
847 rect
848 };
849
850 let transform = Affine::translate((-rect.x0, -rect.y0)) * transform.inverse();
851
852 let (x_advance, y_advance) = x_y_advances(&transform);
853
854 let width = rect.width() as f32;
855 let height = rect.height() as f32;
856 let radius = self.radius.min(0.5 * width.min(height));
857
858 let std_dev = self.std_dev.max(1e-6);
860
861 let min_edge = width.min(height);
862 let rmax = 0.5 * min_edge;
863 let r0 = radius.hypot(std_dev * 1.15).min(rmax);
864 let r1 = radius.hypot(std_dev * 2.0).min(rmax);
865
866 let exponent = 2.0 * r1 / r0;
867
868 let std_dev_inv = std_dev.recip();
869
870 let delta = 1.25
872 * std_dev
873 * (exp(-(0.5 * std_dev_inv * width).powi(2))
874 - exp(-(0.5 * std_dev_inv * height).powi(2)));
875 let w = width + delta.min(0.0);
876 let h = height - delta.max(0.0);
877
878 let recip_exponent = exponent.recip();
879 let scale = 0.5 * compute_erf7(std_dev_inv * 0.5 * (w.max(h) - 0.5 * radius));
880
881 let encoded = EncodedBlurredRoundedRectangle {
882 exponent,
883 recip_exponent,
884 width,
885 height,
886 scale,
887 r1,
888 std_dev_inv,
889 min_edge,
890 color: PremulColor::from_alpha_color(self.color),
891 w,
892 h,
893 transform,
894 x_advance,
895 y_advance,
896 };
897
898 let idx = paints.len();
899 paints.push(encoded.into());
900
901 Paint::Indexed(IndexedPaint::new(idx))
902 }
903}
904
905fn ts_from_line_to_line(src1: Point, src2: Point, dst1: Point, dst2: Point) -> Affine {
913 let unit_to_line1 = unit_to_line(src1, src2);
914 let line1_to_unit = unit_to_line1.inverse();
916 let unit_to_line2 = unit_to_line(dst1, dst2);
918
919 unit_to_line2 * line1_to_unit
920}
921
922fn unit_to_line(p0: Point, p1: Point) -> Affine {
925 Affine::new([
926 p1.y - p0.y,
927 p0.x - p1.x,
928 p1.x - p0.x,
929 p1.y - p0.y,
930 p0.x,
931 p0.y,
932 ])
933}
934
935pub trait FromF32Color: Sized + Debug + Copy + Clone {
937 const ZERO: Self;
939 fn from_f32<S: Simd>(color: f32x4<S>) -> [Self; 4];
941}
942
943impl FromF32Color for f32 {
944 const ZERO: Self = 0.0;
945
946 fn from_f32<S: Simd>(color: f32x4<S>) -> [Self; 4] {
947 color.into()
948 }
949}
950
951impl FromF32Color for u8 {
952 const ZERO: Self = 0;
953
954 fn from_f32<S: Simd>(mut color: f32x4<S>) -> [Self; 4] {
955 let simd = color.simd;
956 color = color.madd(f32x4::splat(simd, 255.0), f32x4::splat(simd, 0.5));
957
958 [
959 color[0] as Self,
960 color[1] as Self,
961 color[2] as Self,
962 color[3] as Self,
963 ]
964 }
965}
966
967#[derive(Debug)]
969pub struct GradientLut<T: FromF32Color> {
970 lut: Vec<[T; 4]>,
971 scale: f32,
972 has_undefined: bool,
973}
974
975impl<T: FromF32Color> GradientLut<T> {
976 fn new<S: Simd>(simd: S, ranges: &[GradientRange], has_undefined: bool) -> Self {
978 let lut_size = determine_lut_size(ranges);
979
980 let padded_lut_size = lut_size + has_undefined as usize;
984 let mut lut = vec![[T::ZERO; 4]; padded_lut_size];
985
986 let ramps = {
988 let mut ramps = Vec::with_capacity(ranges.len());
989 let mut prev_idx = 0;
990
991 for range in ranges {
992 let max_idx = (range.x1 * lut_size as f32) as usize;
993
994 ramps.push((prev_idx..max_idx, range));
995 prev_idx = max_idx;
996 }
997
998 ramps
999 };
1000
1001 let scale = lut_size as f32 - 1.0;
1002
1003 let inv_lut_scale = f32x4::splat(simd, 1.0 / scale);
1004 let add_factor = f32x4::from_slice(simd, &[0.0, 1.0, 2.0, 3.0]) * inv_lut_scale;
1005
1006 for (ramp_range, range) in ramps {
1007 let biases = f32x16::block_splat(f32x4::from_slice(simd, &range.bias));
1008 let scales = f32x16::block_splat(f32x4::from_slice(simd, &range.scale));
1009
1010 ramp_range.clone().step_by(4).for_each(|idx| {
1011 let t_vals = f32x4::splat(simd, idx as f32).madd(inv_lut_scale, add_factor);
1012
1013 let t_vals = element_wise_splat(simd, t_vals);
1014
1015 let mut result = scales.madd(t_vals, biases);
1016 let alphas = result.splat_4th();
1017 if range.interpolation_alpha_space == InterpolationAlphaSpace::Unpremultiplied {
1019 result = {
1020 let mask =
1021 mask32x16::block_splat(mask32x4::from_slice(simd, &[-1, -1, -1, 0]));
1022 simd.select_f32x16(mask, result * alphas, alphas)
1023 };
1024 }
1025
1026 result = result.min(1.0).min(alphas);
1031 let (im1, im2) = simd.split_f32x16(result);
1032 let (r1, r2) = simd.split_f32x8(im1);
1033 let (r3, r4) = simd.split_f32x8(im2);
1034 let rs = [r1, r2, r3, r4].map(T::from_f32);
1035
1036 let lut = &mut lut[idx..(idx + 4).min(lut_size)];
1039 lut.copy_from_slice(&rs[..lut.len()]);
1040 });
1041 }
1042
1043 Self {
1044 lut,
1045 scale,
1046 has_undefined,
1047 }
1048 }
1049
1050 #[inline(always)]
1052 pub fn get(&self, idx: usize) -> [T; 4] {
1053 self.lut[idx]
1054 }
1055
1056 #[inline(always)]
1058 pub fn lut(&self) -> &[[T; 4]] {
1059 &self.lut
1060 }
1061
1062 #[inline(always)]
1066 pub fn transparent_index(&self) -> Option<usize> {
1067 self.has_undefined.then(|| self.lut.len() - 1)
1068 }
1069
1070 #[inline(always)]
1074 pub fn width(&self) -> usize {
1075 if self.has_undefined {
1076 self.lut.len() - 1
1077 } else {
1078 self.lut.len()
1079 }
1080 }
1081
1082 #[inline(always)]
1085 pub fn scale_factor(&self) -> f32 {
1086 self.scale
1087 }
1088}
1089
1090pub const MAX_GRADIENT_LUT_SIZE: usize = 4096;
1095
1096fn determine_lut_size(ranges: &[GradientRange]) -> usize {
1097 let stop_len = match ranges.len() {
1103 1 => 256,
1104 2 => 512,
1105 _ => 1024,
1106 };
1107
1108 let mut last_x1 = 0.0;
1111 let mut min_size = 0;
1112
1113 for x1 in ranges.iter().map(|e| e.x1) {
1114 let res = ((1.0 / (x1 - last_x1)).ceil() as usize)
1117 .min(MAX_GRADIENT_LUT_SIZE)
1118 .next_power_of_two();
1119 min_size = min_size.max(res);
1120 last_x1 = x1;
1121 }
1122
1123 stop_len.max(min_size)
1125}
1126
1127mod private {
1128 #[expect(unnameable_types, reason = "Sealed trait pattern.")]
1129 pub trait Sealed {}
1130
1131 impl Sealed for super::Gradient {}
1132}
1133
1134#[cfg(test)]
1135mod tests {
1136 use super::{EncodeExt, Gradient};
1137 use crate::color::DynamicColor;
1138 use crate::color::palette::css::{BLACK, BLUE, GREEN};
1139 use crate::kurbo::{Affine, Point};
1140 use crate::peniko::{ColorStop, ColorStops};
1141 use alloc::vec;
1142 use peniko::{LinearGradientPosition, RadialGradientPosition};
1143 use smallvec::smallvec;
1144
1145 #[test]
1146 fn gradient_missing_stops() {
1147 let mut buf = vec![];
1148
1149 let gradient = Gradient {
1150 kind: LinearGradientPosition {
1151 start: Point::new(0.0, 0.0),
1152 end: Point::new(20.0, 0.0),
1153 }
1154 .into(),
1155 ..Default::default()
1156 };
1157
1158 assert_eq!(
1159 gradient.encode_into(&mut buf, Affine::IDENTITY),
1160 BLACK.into()
1161 );
1162 }
1163
1164 #[test]
1165 fn gradient_one_stop() {
1166 let mut buf = vec![];
1167
1168 let gradient = Gradient {
1169 kind: LinearGradientPosition {
1170 start: Point::new(0.0, 0.0),
1171 end: Point::new(20.0, 0.0),
1172 }
1173 .into(),
1174 stops: ColorStops(smallvec![ColorStop {
1175 offset: 0.0,
1176 color: DynamicColor::from_alpha_color(GREEN),
1177 }]),
1178 ..Default::default()
1179 };
1180
1181 assert_eq!(
1183 gradient.encode_into(&mut buf, Affine::IDENTITY),
1184 GREEN.into()
1185 );
1186 }
1187
1188 #[test]
1189 fn gradient_not_sorted_stops() {
1190 let mut buf = vec![];
1191
1192 let gradient = Gradient {
1193 kind: LinearGradientPosition {
1194 start: Point::new(0.0, 0.0),
1195 end: Point::new(20.0, 0.0),
1196 }
1197 .into(),
1198 stops: ColorStops(smallvec![
1199 ColorStop {
1200 offset: 1.0,
1201 color: DynamicColor::from_alpha_color(GREEN),
1202 },
1203 ColorStop {
1204 offset: 0.0,
1205 color: DynamicColor::from_alpha_color(BLUE),
1206 },
1207 ]),
1208 ..Default::default()
1209 };
1210
1211 assert_eq!(
1212 gradient.encode_into(&mut buf, Affine::IDENTITY),
1213 GREEN.into()
1214 );
1215 }
1216
1217 #[test]
1218 fn gradient_linear_degenerate() {
1219 let mut buf = vec![];
1220
1221 let gradient = Gradient {
1222 kind: LinearGradientPosition {
1223 start: Point::new(0.0, 0.0),
1224 end: Point::new(0.0, 0.0),
1225 }
1226 .into(),
1227 stops: ColorStops(smallvec![
1228 ColorStop {
1229 offset: 0.0,
1230 color: DynamicColor::from_alpha_color(GREEN),
1231 },
1232 ColorStop {
1233 offset: 1.0,
1234 color: DynamicColor::from_alpha_color(BLUE),
1235 },
1236 ]),
1237 ..Default::default()
1238 };
1239
1240 assert_eq!(
1241 gradient.encode_into(&mut buf, Affine::IDENTITY),
1242 GREEN.into()
1243 );
1244 }
1245
1246 #[test]
1247 fn gradient_radial_degenerate() {
1248 let mut buf = vec![];
1249
1250 let gradient = Gradient {
1251 kind: RadialGradientPosition {
1252 start_center: Point::new(0.0, 0.0),
1253 start_radius: 20.0,
1254 end_center: Point::new(0.0, 0.0),
1255 end_radius: 20.0,
1256 }
1257 .into(),
1258 stops: ColorStops(smallvec![
1259 ColorStop {
1260 offset: 0.0,
1261 color: DynamicColor::from_alpha_color(GREEN),
1262 },
1263 ColorStop {
1264 offset: 1.0,
1265 color: DynamicColor::from_alpha_color(BLUE),
1266 },
1267 ]),
1268 ..Default::default()
1269 };
1270
1271 assert_eq!(
1272 gradient.encode_into(&mut buf, Affine::IDENTITY),
1273 GREEN.into()
1274 );
1275 }
1276}