1use std::sync::Arc;
22
23use super::AtmospherePaint;
24use crate::{Color, MeshPaint, Point, Rect, Transform, ValidationError};
25
26#[derive(Clone, Debug)]
28pub struct ImagePattern {
29 pub image: PatternImage,
31 pub transform: Transform,
33 pub extend: GradientExtend,
35 pub sampling: crate::Sampling,
37 pub decal: bool,
39}
40
41#[derive(Clone, Debug)]
43pub struct PatternImage(Arc<dyn crate::pixels::ImagePixels>);
44
45impl PatternImage {
46 pub fn width(&self) -> u32 {
48 self.0.width()
49 }
50
51 pub fn height(&self) -> u32 {
53 self.0.height()
54 }
55
56 pub fn levels(&self) -> usize {
58 self.0.level_count()
59 }
60
61 pub fn size(&self, level: usize) -> (u32, u32) {
63 self.0.level_size(level)
64 }
65
66 pub fn texel(&self, level: usize, column: usize, row: usize) -> [f32; 4] {
68 self.0.texel(level, column, row)
69 }
70
71 pub fn extend_level(&self, level: usize, out: &mut Vec<f32>) {
73 self.0.extend_level(level, out);
74 }
75
76 pub fn identity(&self) -> usize {
78 Arc::as_ptr(&self.0).cast::<()>() as usize
79 }
80
81 pub fn downgrade(&self) -> WeakPatternImage {
86 WeakPatternImage(Arc::downgrade(&self.0))
87 }
88
89 fn same(&self, other: &Self) -> bool {
90 Arc::ptr_eq(&self.0, &other.0) || self.0.content_eq(other.0.as_ref())
91 }
92}
93
94#[derive(Clone, Debug)]
96pub struct WeakPatternImage(std::sync::Weak<dyn crate::pixels::ImagePixels>);
97
98impl WeakPatternImage {
99 pub fn refers_to(&self, image: &PatternImage) -> bool {
101 std::sync::Weak::ptr_eq(&self.0, &Arc::downgrade(&image.0))
102 }
103}
104
105impl PartialEq for ImagePattern {
106 fn eq(&self, other: &Self) -> bool {
107 self.transform == other.transform
108 && self.extend == other.extend
109 && self.sampling == other.sampling
110 && self.decal == other.decal
111 && self.image.same(&other.image)
112 }
113}
114
115impl ImagePattern {
116 pub fn from_pixels(image: Arc<dyn crate::ImagePixels>) -> Self {
118 Self {
119 image: PatternImage(image),
120 transform: Transform::IDENTITY,
121 extend: GradientExtend::Repeat,
122 sampling: crate::Sampling::Trilinear,
123 decal: false,
124 }
125 }
126
127 pub fn from_asset<Image: crate::pixels::ImagePixels>(image: Arc<Image>) -> Self {
129 Self {
130 image: PatternImage(image),
131 transform: Transform::IDENTITY,
132 extend: GradientExtend::Repeat,
133 sampling: crate::Sampling::Trilinear,
134 decal: false,
135 }
136 }
137
138 pub fn sampling(mut self, sampling: crate::Sampling) -> Self {
140 self.sampling = sampling;
141 self
142 }
143
144 pub fn transform(mut self, transform: Transform) -> Self {
146 self.transform = transform;
147 self
148 }
149
150 pub fn extend(mut self, extend: GradientExtend) -> Self {
152 self.extend = extend;
153 self
154 }
155
156 pub fn validate(&self) -> Result<(), ValidationError> {
158 if !self.transform.is_finite() || self.transform.inverse().is_none() {
159 return Err(invalid(
160 "Image pattern transform must be finite and invertible",
161 ));
162 }
163 Ok(())
164 }
165}
166
167impl From<ImagePattern> for super::Paint {
168 fn from(value: ImagePattern) -> Self {
169 Self::Pattern(value)
170 }
171}
172
173#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
175#[cfg_attr(
176 feature = "serde",
177 derive(serde::Serialize, serde::Deserialize),
178 serde(rename_all = "snake_case")
179)]
180#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
181pub enum GradientSpace {
182 #[default]
184 Oklab,
185 LinearRgb,
187 Srgb,
189}
190
191#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
193#[cfg_attr(
194 feature = "serde",
195 derive(serde::Serialize, serde::Deserialize),
196 serde(rename_all = "snake_case")
197)]
198#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
199pub enum GradientUnits {
200 #[default]
202 ObjectBoundingBox,
203 UserSpace,
205}
206
207#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
209#[cfg_attr(
210 feature = "serde",
211 derive(serde::Serialize, serde::Deserialize),
212 serde(rename_all = "snake_case")
213)]
214#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
215pub enum GradientExtend {
216 #[default]
218 Pad,
219 Repeat,
221 Reflect,
223}
224
225impl GradientExtend {
226 pub fn apply(self, position: f64) -> f64 {
228 match self {
229 Self::Pad => position.clamp(0.0, 1.0),
230 Self::Repeat => position.rem_euclid(1.0),
231 Self::Reflect => {
232 let value = position.rem_euclid(2.0);
233 if value > 1.0 { 2.0 - value } else { value }
234 }
235 }
236 }
237}
238
239#[derive(Clone, Debug, PartialEq)]
240pub enum Geometry {
242 Linear(Point, Point),
244 Radial(Point, f64),
246 RadialFocal(Point, f64, Point),
248 Sweep(Point, f64, f64),
250}
251
252#[derive(Clone, Debug, PartialEq)]
253pub struct Gradient {
255 pub geometry: Geometry,
257 pub stops: Arc<[(f64, Color)]>,
259 pub space: GradientSpace,
261 pub units: GradientUnits,
263 pub extend: GradientExtend,
265}
266
267fn invalid(message: &str) -> ValidationError {
268 ValidationError::invalid("paint.gradient", "parameters", message)
269}
270
271impl Gradient {
272 fn new(geometry: Geometry) -> Self {
273 Self {
274 geometry,
275 stops: Arc::from([]),
276 space: GradientSpace::default(),
277 units: GradientUnits::default(),
278 extend: GradientExtend::default(),
279 }
280 }
281
282 fn stop(mut self, position: f64, color: Color) -> Self {
283 if self.stops.len() < 65 {
284 let mut stops = self.stops.to_vec();
285 stops.push((position, color));
286 self.stops = stops.into();
287 }
288 self
289 }
290
291 pub fn validate(&self) -> Result<(), ValidationError> {
293 if !(2..=64).contains(&self.stops.len())
294 || self.stops.first().map(|stop| stop.0) != Some(0.0)
295 || self.stops.last().map(|stop| stop.0) != Some(1.0)
296 || self
297 .stops
298 .iter()
299 .any(|(position, _)| !position.is_finite() || !(0.0..=1.0).contains(position))
300 || self.stops.windows(2).any(|pair| pair[0].0 > pair[1].0)
301 {
302 return Err(invalid(
303 "Require 2..=64 ordered stops including zero and one",
304 ));
305 }
306 match self.geometry {
307 Geometry::Linear(first, second)
308 if [first.x, first.y, second.x, second.y]
309 .iter()
310 .all(|value| value.is_finite())
311 && first != second
312 && first.distance(second).is_finite() =>
313 {
314 Ok(())
315 }
316 Geometry::Radial(center, radius)
317 if center.x.is_finite()
318 && center.y.is_finite()
319 && radius.is_finite()
320 && radius > 0.0 =>
321 {
322 Ok(())
323 }
324 Geometry::RadialFocal(center, radius, focus)
325 if center.x.is_finite()
326 && center.y.is_finite()
327 && focus.x.is_finite()
328 && focus.y.is_finite()
329 && radius.is_finite()
330 && radius > 0.0
331 && center.distance(focus) < radius =>
332 {
333 Ok(())
334 }
335 Geometry::Sweep(center, start, end)
336 if center.x.is_finite()
337 && center.y.is_finite()
338 && start.is_finite()
339 && end.is_finite()
340 && end > start
341 && end - start <= std::f64::consts::TAU =>
342 {
343 Ok(())
344 }
345 _ => Err(invalid(
346 "Gradient geometry must be finite and nondegenerate",
347 )),
348 }
349 }
350
351 pub fn position(&self, point: Point, bounds: Rect) -> Result<f64, ValidationError> {
354 let point = match self.units {
355 GradientUnits::UserSpace => point,
356 GradientUnits::ObjectBoundingBox => {
357 if !bounds.width().is_finite()
358 || !bounds.height().is_finite()
359 || bounds.width() <= 0.0
360 || bounds.height() <= 0.0
361 {
362 return Err(invalid(
363 "Object bounding-box gradient requires finite nonzero shape bounds",
364 ));
365 }
366 Point::new(
367 (point.x - bounds.left) / bounds.width(),
368 (point.y - bounds.top) / bounds.height(),
369 )
370 }
371 };
372 let position = match self.geometry {
373 Geometry::Linear(start, end) => {
374 let direction = end - start;
375 let length = direction.length();
376 (point - start).dot(direction / length) / length
377 }
378 Geometry::Radial(center, radius) => point.distance(center) / radius,
379 Geometry::RadialFocal(center, radius, focus) => {
380 let offset = point - focus;
381 let displacement = focus - center;
382 let square = offset.dot(offset);
383 if square == 0.0 {
384 0.0
385 } else {
386 let projection = offset.dot(displacement);
387 square
388 / (-projection
389 + libm::sqrt(
390 projection * projection
391 + square * (radius * radius - displacement.dot(displacement)),
392 ))
393 }
394 }
395 Geometry::Sweep(center, start, end) => {
396 let angle = libm::atan2(point.y - center.y, point.x - center.x);
397 (angle - start).rem_euclid(std::f64::consts::TAU) / (end - start)
398 }
399 };
400 if !position.is_finite() {
401 return Err(invalid("Gradient sample position must remain finite"));
402 }
403 Ok(self.extend.apply(position))
404 }
405
406 pub fn color(&self, position: f64) -> [f64; 4] {
409 let upper = self
410 .stops
411 .partition_point(|(offset, _)| *offset <= position);
412 if upper == self.stops.len() {
413 return self.stops[upper - 1].1.to_linear_premultiplied();
414 }
415 let (start, first) = self.stops[upper.saturating_sub(1)];
416 let (end, second) = self.stops[upper];
417 let progress = ((position - start) / (end - start)).clamp(0.0, 1.0);
418 if progress == 0.0 {
419 return first.to_linear_premultiplied();
420 }
421 if progress == 1.0 {
422 return second.to_linear_premultiplied();
423 }
424 match self.space {
425 GradientSpace::Oklab => {
426 let first = first.to_rgba_float();
427 let second = second.to_rgba_float();
428 let alpha = first[3] * (1.0 - progress) + second[3] * progress;
429 if alpha == 0.0 {
430 return [0.0; 4];
431 }
432 let left = crate::color::srgb_to_oklab([first[0], first[1], first[2]]);
433 let right = crate::color::srgb_to_oklab([second[0], second[1], second[2]]);
434 let rgb = crate::color::oklab_to_srgb(std::array::from_fn(|index| {
435 left[index] * (1.0 - progress) + right[index] * progress
436 }))
437 .map(crate::color::srgb_to_linear);
438 [rgb[0] * alpha, rgb[1] * alpha, rgb[2] * alpha, alpha]
439 }
440 GradientSpace::LinearRgb => {
441 let first = first.to_rgba_float();
442 let second = second.to_rgba_float();
443 let alpha = first[3] * (1.0 - progress) + second[3] * progress;
444 std::array::from_fn(|channel| {
445 if channel == 3 {
446 alpha
447 } else {
448 (crate::color::srgb_to_linear(first[channel]) * (1.0 - progress)
449 + crate::color::srgb_to_linear(second[channel]) * progress)
450 * alpha
451 }
452 })
453 }
454 GradientSpace::Srgb => {
455 let first = first.to_rgba_float();
456 let second = second.to_rgba_float();
457 let alpha = first[3] * (1.0 - progress) + second[3] * progress;
458 let decode = crate::color::srgb_to_linear;
459 [
460 decode(first[0] * (1.0 - progress) + second[0] * progress) * alpha,
461 decode(first[1] * (1.0 - progress) + second[1] * progress) * alpha,
462 decode(first[2] * (1.0 - progress) + second[2] * progress) * alpha,
463 alpha,
464 ]
465 }
466 }
467 }
468}
469
470macro_rules! gradient {
471 ($name:ident, $variant:ident, $description:literal) => {
472 #[doc = $description]
473 #[derive(Clone, Debug, PartialEq)]
474 pub struct $name(pub Gradient);
475 impl $name {
476 pub fn stop(self, position: f64, color: Color) -> Self {
478 Self(self.0.stop(position, color))
479 }
480 pub fn space(mut self, space: GradientSpace) -> Self {
482 self.0.space = space;
483 self
484 }
485 pub fn units(mut self, units: GradientUnits) -> Self {
487 self.0.units = units;
488 self
489 }
490 pub fn extend(mut self, extend: GradientExtend) -> Self {
492 self.0.extend = extend;
493 self
494 }
495 pub fn sample(&self, point: Point, bounds: Rect) -> Result<[f64; 4], ValidationError> {
497 self.0.validate()?;
498 Ok(self.0.color(self.0.position(point, bounds)?))
499 }
500 }
501 impl From<$name> for super::Paint {
502 fn from(value: $name) -> Self {
503 Self::$variant(value)
504 }
505 }
506 };
507}
508
509gradient!(
510 LinearGradient,
511 Linear,
512 "A linear gradient in f64 authoring coordinates."
513);
514
515impl LinearGradient {
516 pub fn new(start: Point, end: Point) -> Self {
518 Self(Gradient::new(Geometry::Linear(start, end)))
519 }
520}
521
522gradient!(
523 RadialGradient,
524 Radial,
525 "A circular radial gradient; object bounds can stretch it elliptically."
526);
527
528impl RadialGradient {
529 pub fn focal(mut self, focus: Point) -> Self {
532 if let Geometry::Radial(center, radius) | Geometry::RadialFocal(center, radius, _) =
533 self.0.geometry
534 {
535 self.0.geometry = Geometry::RadialFocal(center, radius, focus);
536 }
537 self
538 }
539
540 pub fn new(center: Point, radius: f64) -> Self {
542 Self(Gradient::new(Geometry::Radial(center, radius)))
543 }
544}
545
546gradient!(
547 SweepGradient,
548 Sweep,
549 "A clockwise angular gradient in radians."
550);
551
552impl SweepGradient {
553 pub fn new(center: Point, start_angle: f64, end_angle: f64) -> Self {
555 Self(Gradient::new(Geometry::Sweep(
556 center,
557 start_angle,
558 end_angle,
559 )))
560 }
561}
562
563#[derive(Clone, Debug, PartialEq)]
565#[non_exhaustive]
566pub enum Paint {
567 Solid(Color),
569 Linear(LinearGradient),
571 Radial(RadialGradient),
573 Sweep(SweepGradient),
575 Pattern(ImagePattern),
577 Mesh(MeshPaint),
579 Atmosphere(AtmospherePaint),
581}
582
583impl Paint {
584 pub fn gradient(&self) -> Option<&Gradient> {
586 match self {
587 Self::Linear(value) => Some(&value.0),
588 Self::Radial(value) => Some(&value.0),
589 Self::Sweep(value) => Some(&value.0),
590 _ => None,
591 }
592 }
593
594 pub fn validate(&self) -> Result<(), ValidationError> {
596 match self {
597 Self::Solid(_) => Ok(()),
598 Self::Linear(value) => value.0.validate(),
599 Self::Radial(value) => value.0.validate(),
600 Self::Sweep(value) => value.0.validate(),
601 Self::Pattern(value) => value.validate(),
602 Self::Mesh(value) => value.validate(),
603 Self::Atmosphere(value) => value.validate(),
604 }
605 }
606}
607
608impl From<Color> for Paint {
609 fn from(color: Color) -> Self {
610 Self::Solid(color)
611 }
612}
613
614#[cfg(test)]
615mod tests {
616 #[test]
617 fn focal_radial_uses_ray_circle_intersection_without_changing_end_colors() {
618 use super::*;
619 let gradient = RadialGradient::new(Point::new(0.5, 0.5), 0.5)
620 .focal(Point::new(0.25, 0.5))
621 .stop(0.0, Color::BLACK)
622 .stop(1.0, Color::WHITE)
623 .space(GradientSpace::LinearRgb);
624 let bounds = Rect::new(0.0, 0.0, 1.0, 1.0);
625 assert_eq!(
626 gradient.sample(Point::new(0.25, 0.5), bounds).unwrap(),
627 [0.0, 0.0, 0.0, 1.0]
628 );
629 assert_eq!(
630 gradient.sample(Point::new(1.0, 0.5), bounds).unwrap(),
631 [1.0; 4]
632 );
633 let center = gradient.sample(Point::new(0.5, 0.5), bounds).unwrap();
634 assert!((center[0] - 1.0 / 3.0).abs() < 1e-12);
635 }
636
637 use super::*;
638
639 #[test]
640 fn gradients_preserve_spaces_units_extension_and_hard_stops() {
641 let bounds = Rect::new(10.0, 20.0, 30.0, 40.0);
642 let linear = LinearGradient::new(Point::ORIGIN, Point::new(1.0, 0.0))
643 .stop(0.0, Color::BLACK)
644 .stop(1.0, Color::WHITE)
645 .space(GradientSpace::LinearRgb);
646 assert_eq!(
647 linear.sample(Point::new(20.0, 30.0), bounds).unwrap(),
648 [0.5, 0.5, 0.5, 1.0]
649 );
650 assert_eq!(
651 linear
652 .clone()
653 .extend(GradientExtend::Repeat)
654 .sample(Point::new(40.0, 30.0), bounds)
655 .unwrap(),
656 [0.5, 0.5, 0.5, 1.0]
657 );
658 assert_eq!(
659 linear
660 .clone()
661 .extend(GradientExtend::Reflect)
662 .sample(Point::new(40.0, 30.0), bounds)
663 .unwrap(),
664 [0.5, 0.5, 0.5, 1.0]
665 );
666 let radial = RadialGradient::new(Point::new(0.5, 0.5), 0.5)
667 .stop(0.0, Color::BLACK)
668 .stop(1.0, Color::WHITE)
669 .space(GradientSpace::LinearRgb);
670 assert_eq!(
671 radial.sample(Point::new(20.0, 30.0), bounds).unwrap(),
672 [0.0, 0.0, 0.0, 1.0]
673 );
674 assert_eq!(
675 radial.sample(Point::new(30.0, 30.0), bounds).unwrap(),
676 [1.0; 4]
677 );
678 let sweep = SweepGradient::new(Point::ORIGIN, 0.0, std::f64::consts::TAU)
679 .stop(0.0, Color::BLACK)
680 .stop(1.0, Color::WHITE)
681 .space(GradientSpace::LinearRgb)
682 .units(GradientUnits::UserSpace);
683 assert_eq!(
684 sweep.sample(Point::new(0.0, 1.0), bounds).unwrap(),
685 [0.25, 0.25, 0.25, 1.0]
686 );
687 assert!(
688 LinearGradient::new(Point::ORIGIN, Point::ORIGIN)
689 .stop(0.0, Color::WHITE)
690 .stop(1.0, Color::BLACK)
691 .sample(Point::ORIGIN, bounds)
692 .is_err()
693 );
694 let hard = LinearGradient::new(Point::ORIGIN, Point::new(1.0, 0.0))
695 .stop(0.0, Color::BLACK)
696 .stop(0.5, Color::BLACK)
697 .stop(0.5, Color::WHITE)
698 .stop(1.0, Color::WHITE);
699 assert_eq!(
700 hard.sample(Point::new(20.0, 20.0), bounds).unwrap(),
701 [1.0; 4]
702 );
703 let encoded = linear
704 .clone()
705 .space(GradientSpace::Srgb)
706 .sample(Point::new(20.0, 30.0), bounds)
707 .unwrap();
708 assert!((encoded[0] - 0.21404114048223255).abs() < 1e-12);
709 let perceptual = linear
710 .clone()
711 .space(GradientSpace::Oklab)
712 .sample(Point::new(20.0, 30.0), bounds)
713 .unwrap();
714 for channel in &perceptual[..3] {
715 assert!((*channel - 0.125).abs() < 1e-6);
716 }
717 let alpha = LinearGradient::new(Point::ORIGIN, Point::new(1.0, 0.0))
718 .stop(0.0, Color::rgb(255, 0, 0))
719 .stop(1.0, Color::rgb(0, 255, 0).with_alpha(0.0))
720 .space(GradientSpace::LinearRgb);
721 assert_eq!(
722 alpha.sample(Point::new(20.0, 30.0), bounds).unwrap(),
723 [0.25, 0.25, 0.0, 0.5]
724 );
725 }
726}