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