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