1use crate::properties::InterpolatedPropertyValue;
9#[cfg(not(feature = "std"))]
10#[allow(unused_imports)]
11use num_traits::Float;
12
13#[derive(Copy, Clone, PartialEq, Debug, Default)]
19pub struct RgbaColor<T> {
20 pub alpha: T,
22 pub red: T,
24 pub green: T,
26 pub blue: T,
28}
29
30#[cfg(feature = "32-bit-color")]
31type Channel = f32;
32#[cfg(not(feature = "32-bit-color"))]
33type Channel = u8;
34
35#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Default)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53#[repr(C)]
54pub struct Color {
55 red: Channel,
56 green: Channel,
57 blue: Channel,
58 alpha: Channel,
59}
60
61const fn round(mut value: f32) -> u8 {
63 if value % 1.0 > 0.5 {
64 value += 0.5;
65 }
66
67 value as _
68}
69
70const fn quantize(value: f32) -> u8 {
71 round(value * 255.0)
72}
73
74const fn unquantize(value: u8) -> f32 {
75 (value as f32) / 255.0
76}
77
78#[test]
79fn unquantize_roundtrip() {
80 for v in 0..=255 {
81 assert_eq!(v, quantize(unquantize(v)));
82 }
83}
84
85impl From<RgbaColor<u8>> for RgbaColor<f32> {
86 #[inline]
87 fn from(col: RgbaColor<u8>) -> Self {
88 Self {
89 red: unquantize(col.red),
90 green: unquantize(col.green),
91 blue: unquantize(col.blue),
92 alpha: unquantize(col.alpha),
93 }
94 }
95}
96
97impl From<RgbaColor<f32>> for RgbaColor<u8> {
98 #[inline]
99 fn from(col: RgbaColor<f32>) -> Self {
100 Self {
101 red: quantize(col.red),
102 green: quantize(col.green),
103 blue: quantize(col.blue),
104 alpha: quantize(col.alpha),
105 }
106 }
107}
108
109impl From<Color> for RgbaColor<f32> {
110 #[inline]
111 fn from(col: Color) -> Self {
112 #[cfg(feature = "32-bit-color")]
113 {
114 Self { red: col.red, green: col.green, blue: col.blue, alpha: col.alpha }
115 }
116 #[cfg(not(feature = "32-bit-color"))]
117 {
118 let col: RgbaColor<u8> = col.into();
119 col.into()
120 }
121 }
122}
123
124impl From<RgbaColor<f32>> for Color {
125 #[inline]
126 fn from(col: RgbaColor<f32>) -> Self {
127 #[cfg(feature = "32-bit-color")]
128 {
129 Self { red: col.red, green: col.green, blue: col.blue, alpha: col.alpha }
130 }
131 #[cfg(not(feature = "32-bit-color"))]
132 {
133 let col: RgbaColor<u8> = col.into();
134 col.into()
135 }
136 }
137}
138
139impl From<RgbaColor<u8>> for Color {
140 #[inline]
141 fn from(col: RgbaColor<u8>) -> Self {
142 #[cfg(feature = "32-bit-color")]
143 {
144 let col: RgbaColor<f32> = col.into();
145 col.into()
146 }
147 #[cfg(not(feature = "32-bit-color"))]
148 {
149 Self { red: col.red, green: col.green, blue: col.blue, alpha: col.alpha }
150 }
151 }
152}
153
154impl From<Color> for RgbaColor<u8> {
155 #[inline]
156 fn from(col: Color) -> Self {
157 #[cfg(feature = "32-bit-color")]
158 {
159 let col: RgbaColor<f32> = col.into();
160 col.into()
161 }
162 #[cfg(not(feature = "32-bit-color"))]
163 {
164 Self { red: col.red, green: col.green, blue: col.blue, alpha: col.alpha }
165 }
166 }
167}
168
169impl Color {
170 pub const fn from_argb_encoded(encoded: u32) -> Color {
172 Self::from_argb_u8(
173 (encoded >> 24) as u8,
174 (encoded >> 16) as u8,
175 (encoded >> 8) as u8,
176 encoded as u8,
177 )
178 }
179
180 pub fn as_argb_encoded(&self) -> u32 {
182 let col: RgbaColor<u8> = (*self).into();
183 ((col.red as u32) << 16)
184 | ((col.green as u32) << 8)
185 | (col.blue as u32)
186 | ((col.alpha as u32) << 24)
187 }
188
189 pub const fn from_argb_u8(alpha: u8, red: u8, green: u8, blue: u8) -> Self {
191 #[cfg(feature = "32-bit-color")]
192 {
193 Self {
194 red: unquantize(red),
195 green: unquantize(green),
196 blue: unquantize(blue),
197 alpha: unquantize(alpha),
198 }
199 }
200 #[cfg(not(feature = "32-bit-color"))]
201 {
202 Self { red, green, blue, alpha }
203 }
204 }
205
206 pub const fn from_rgb_u8(red: u8, green: u8, blue: u8) -> Self {
209 Self::from_argb_u8(255, red, green, blue)
210 }
211
212 pub fn from_argb_f32(alpha: f32, red: f32, green: f32, blue: f32) -> Self {
214 RgbaColor { alpha, red, green, blue }.into()
215 }
216
217 pub fn from_rgb_f32(red: f32, green: f32, blue: f32) -> Self {
220 Self::from_argb_f32(1.0, red, green, blue)
221 }
222
223 pub fn to_argb_u8(&self) -> RgbaColor<u8> {
225 RgbaColor::from(*self)
226 }
227
228 pub fn to_argb_f32(&self) -> RgbaColor<f32> {
230 RgbaColor::from(*self)
231 }
232
233 pub fn to_hsva(&self) -> HsvaColor {
235 let rgba: RgbaColor<f32> = (*self).into();
236 rgba.into()
237 }
238
239 pub fn from_hsva(hue: f32, saturation: f32, value: f32, alpha: f32) -> Self {
243 let hsva = HsvaColor { hue, saturation, value, alpha };
244 <RgbaColor<f32>>::from(hsva).into()
245 }
246
247 pub fn to_oklch(&self) -> OklchColor {
254 let rgba: RgbaColor<f32> = (*self).into();
255 rgba.into()
256 }
257
258 pub fn from_oklch(lightness: f32, chroma: f32, hue: f32, alpha: f32) -> Self {
265 let oklch = OklchColor { lightness, chroma, hue, alpha };
266 <RgbaColor<f32>>::from(oklch).into()
267 }
268
269 #[inline(always)]
271 pub fn red(self) -> u8 {
272 RgbaColor::<u8>::from(self).red
273 }
274
275 #[inline(always)]
277 pub fn green(self) -> u8 {
278 RgbaColor::<u8>::from(self).green
279 }
280
281 #[inline(always)]
283 pub fn blue(self) -> u8 {
284 RgbaColor::<u8>::from(self).blue
285 }
286
287 #[inline(always)]
289 pub fn alpha(self) -> u8 {
290 RgbaColor::<u8>::from(self).alpha
291 }
292
293 #[must_use]
300 pub fn brighter(&self, factor: f32) -> Self {
301 let rgba: RgbaColor<f32> = (*self).into();
302 let mut hsva: HsvaColor = rgba.into();
303 hsva.value *= 1. + factor;
304 let rgba: RgbaColor<f32> = hsva.into();
305 rgba.into()
306 }
307
308 #[must_use]
314 pub fn darker(&self, factor: f32) -> Self {
315 let rgba: RgbaColor<f32> = (*self).into();
316 let mut hsva: HsvaColor = rgba.into();
317 hsva.value /= 1. + factor;
318 let rgba: RgbaColor<f32> = hsva.into();
319 rgba.into()
320 }
321
322 #[must_use]
349 pub fn transparentize(&self, factor: f32) -> Self {
350 let mut col: RgbaColor<f32> = (*self).into();
351 col.alpha = (col.alpha * (1.0 - factor)).clamp(0.0, 1.0);
352 col.into()
353 }
354
355 #[must_use]
376 pub fn mix(&self, other: &Self, factor: f32) -> Self {
377 fn lerp(v1: f32, v2: f32, f: f32) -> f32 {
384 (v1 * f + v2 * (1.0 - f)).clamp(0.0, 1.0)
385 }
386
387 let original_factor = factor.clamp(0.0, 1.0);
388
389 let col = RgbaColor::<f32>::from(*self);
390 let other = RgbaColor::<f32>::from(*other);
391
392 let normal_weight = 2.0 * original_factor - 1.0;
393 let alpha_distance = col.alpha - other.alpha;
394 let weight_by_distance = normal_weight * alpha_distance;
395
396 let combined_weight = if weight_by_distance == -1.0 {
398 normal_weight
399 } else {
400 (normal_weight + alpha_distance) / (1.0 + weight_by_distance)
401 };
402
403 let channels_factor = (combined_weight + 1.0) / 2.0;
404
405 let red = lerp(col.red, other.red, channels_factor);
406 let green = lerp(col.green, other.green, channels_factor);
407 let blue = lerp(col.blue, other.blue, channels_factor);
408
409 let alpha = lerp(col.alpha, other.alpha, original_factor);
410
411 RgbaColor { red, green, blue, alpha }.into()
412 }
413
414 #[must_use]
416 pub fn with_alpha(&self, alpha: f32) -> Self {
417 let mut rgba: RgbaColor<f32> = (*self).into();
418 rgba.alpha = alpha.clamp(0.0, 1.0);
419 rgba.into()
420 }
421}
422
423impl InterpolatedPropertyValue for Color {
424 fn interpolate(&self, target_value: &Self, t: f32) -> Self {
425 target_value.mix(self, t)
426 }
427}
428
429impl core::fmt::Display for Color {
430 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
431 write!(f, "argb({}, {}, {}, {})", self.alpha(), self.red(), self.green(), self.blue())
432 }
433}
434
435#[derive(Copy, Clone, PartialOrd, Debug, Default)]
439#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
440pub struct HsvaColor {
441 pub hue: f32,
443 pub saturation: f32,
445 pub value: f32,
447 pub alpha: f32,
449}
450
451impl PartialEq for HsvaColor {
452 fn eq(&self, other: &Self) -> bool {
453 (self.hue - other.hue).abs() < 0.00001
454 && (self.saturation - other.saturation).abs() < 0.00001
455 && (self.value - other.value).abs() < 0.00001
456 && (self.alpha - other.alpha).abs() < 0.00001
457 }
458}
459
460impl From<RgbaColor<f32>> for HsvaColor {
461 fn from(col: RgbaColor<f32>) -> Self {
462 let red = col.red;
465 let green = col.green;
466 let blue = col.blue;
467
468 let min = red.min(green).min(blue);
469 let max = red.max(green).max(blue);
470 let chroma = max - min;
471
472 #[allow(clippy::float_cmp)] let hue = num_traits::Euclid::rem_euclid(
474 &(60.
475 * if chroma == 0.0 {
476 0.0
477 } else if max == red {
478 ((green - blue) / chroma) % 6.0
479 } else if max == green {
480 2. + (blue - red) / chroma
481 } else {
482 4. + (red - green) / chroma
483 }),
484 &360.0,
485 );
486 let saturation = if max == 0. { 0. } else { chroma / max };
487
488 Self { hue, saturation, value: max, alpha: col.alpha }
489 }
490}
491
492impl From<HsvaColor> for RgbaColor<f32> {
493 fn from(col: HsvaColor) -> Self {
494 let chroma = col.saturation * col.value;
497
498 let hue = num_traits::Euclid::rem_euclid(&col.hue, &360.0);
499
500 let x = chroma * (1. - ((hue / 60.) % 2. - 1.).abs());
501
502 let (red, green, blue) = match (hue / 60.0) as usize {
503 0 => (chroma, x, 0.),
504 1 => (x, chroma, 0.),
505 2 => (0., chroma, x),
506 3 => (0., x, chroma),
507 4 => (x, 0., chroma),
508 5 => (chroma, 0., x),
509 _ => (0., 0., 0.),
510 };
511
512 let m = col.value - chroma;
513
514 Self { red: red + m, green: green + m, blue: blue + m, alpha: col.alpha }
515 }
516}
517
518impl From<HsvaColor> for Color {
519 fn from(value: HsvaColor) -> Self {
520 RgbaColor::from(value).into()
521 }
522}
523
524impl From<Color> for HsvaColor {
525 fn from(value: Color) -> Self {
526 value.to_hsva()
527 }
528}
529
530#[derive(Copy, Clone, PartialOrd, Debug, Default)]
537#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
538pub struct OklchColor {
539 pub lightness: f32,
541 pub chroma: f32,
543 pub hue: f32,
545 pub alpha: f32,
547}
548
549impl PartialEq for OklchColor {
550 fn eq(&self, other: &Self) -> bool {
551 (self.lightness - other.lightness).abs() < 0.00001
552 && (self.chroma - other.chroma).abs() < 0.00001
553 && (self.hue - other.hue).abs() < 0.00001
554 && (self.alpha - other.alpha).abs() < 0.00001
555 }
556}
557
558#[derive(Copy, Clone, Debug)]
560struct OklabColor {
561 l: f32,
562 a: f32,
563 b: f32,
564 alpha: f32,
565}
566
567impl From<OklchColor> for OklabColor {
568 fn from(oklch: OklchColor) -> Self {
569 let hue_rad = oklch.hue * core::f32::consts::PI / 180.0;
570 Self {
571 l: oklch.lightness,
572 a: oklch.chroma * hue_rad.cos(),
573 b: oklch.chroma * hue_rad.sin(),
574 alpha: oklch.alpha,
575 }
576 }
577}
578
579impl From<OklabColor> for OklchColor {
580 fn from(oklab: OklabColor) -> Self {
581 let chroma = (oklab.a * oklab.a + oklab.b * oklab.b).sqrt();
582 let hue = if chroma < 0.00001 {
583 0.0
584 } else {
585 let hue_rad = oklab.b.atan2(oklab.a);
586 num_traits::Euclid::rem_euclid(&(hue_rad * 180.0 / core::f32::consts::PI), &360.0)
587 };
588 Self { lightness: oklab.l, chroma, hue, alpha: oklab.alpha }
589 }
590}
591
592fn srgb_to_linear(c: f32) -> f32 {
594 if c <= 0.04045 { c / 12.92 } else { ((c + 0.055) / 1.055).powf(2.4) }
595}
596
597fn linear_to_srgb(c: f32) -> f32 {
599 if c <= 0.0031308 { c * 12.92 } else { 1.055 * c.powf(1.0 / 2.4) - 0.055 }
600}
601
602impl From<RgbaColor<f32>> for OklabColor {
603 fn from(col: RgbaColor<f32>) -> Self {
604 let r = srgb_to_linear(col.red);
606 let g = srgb_to_linear(col.green);
607 let b = srgb_to_linear(col.blue);
608
609 let l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
611 let m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
612 let s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
613
614 let l_ = l.cbrt();
616 let m_ = m.cbrt();
617 let s_ = s.cbrt();
618
619 Self {
621 l: 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
622 a: 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
623 b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
624 alpha: col.alpha,
625 }
626 }
627}
628
629impl From<OklabColor> for RgbaColor<f32> {
630 fn from(oklab: OklabColor) -> Self {
631 let l_ = oklab.l + 0.3963377774 * oklab.a + 0.2158037573 * oklab.b;
633 let m_ = oklab.l - 0.1055613458 * oklab.a - 0.0638541728 * oklab.b;
634 let s_ = oklab.l - 0.0894841775 * oklab.a - 1.2914855480 * oklab.b;
635
636 let l = l_ * l_ * l_;
638 let m = m_ * m_ * m_;
639 let s = s_ * s_ * s_;
640
641 let r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
643 let g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
644 let b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
645
646 Self {
648 red: linear_to_srgb(r).clamp(0.0, 1.0),
649 green: linear_to_srgb(g).clamp(0.0, 1.0),
650 blue: linear_to_srgb(b).clamp(0.0, 1.0),
651 alpha: oklab.alpha,
652 }
653 }
654}
655
656impl From<OklchColor> for RgbaColor<f32> {
657 fn from(oklch: OklchColor) -> Self {
658 let oklab = OklabColor::from(oklch);
659 RgbaColor::from(oklab)
660 }
661}
662
663impl From<RgbaColor<f32>> for OklchColor {
664 fn from(col: RgbaColor<f32>) -> Self {
665 let oklab = OklabColor::from(col);
666 OklchColor::from(oklab)
667 }
668}
669
670impl From<OklchColor> for Color {
671 fn from(value: OklchColor) -> Self {
672 RgbaColor::from(value).into()
673 }
674}
675
676impl From<Color> for OklchColor {
677 fn from(value: Color) -> Self {
678 value.to_oklch()
679 }
680}
681
682#[test]
683fn test_rgb_to_hsv() {
684 assert_eq!(
686 HsvaColor::from(RgbaColor::<f32> { red: 1., green: 1., blue: 1., alpha: 0.5 }),
687 HsvaColor { hue: 0., saturation: 0., value: 1., alpha: 0.5 }
688 );
689 assert_eq!(
690 RgbaColor::<f32>::from(HsvaColor { hue: 0., saturation: 0., value: 1., alpha: 0.3 }),
691 RgbaColor::<f32> { red: 1., green: 1., blue: 1., alpha: 0.3 }
692 );
693
694 assert_eq!(
696 HsvaColor::from(Color::from_argb_u8(0xff, 0x8a, 0xc, 0x77,).to_argb_f32()),
697 HsvaColor { hue: 309.0476, saturation: 0.9130435, value: 0.5411765, alpha: 1.0 }
698 );
699
700 let received = RgbaColor::<f32>::from(HsvaColor {
701 hue: 309.0476,
702 saturation: 0.9130435,
703 value: 0.5411765,
704 alpha: 1.0,
705 });
706 let expected = Color::from_argb_u8(0xff, 0x8a, 0xc, 0x77).to_argb_f32();
707
708 assert!(
709 (received.alpha - expected.alpha).abs() < 0.00001
710 && (received.red - expected.red).abs() < 0.00001
711 && (received.green - expected.green).abs() < 0.00001
712 && (received.blue - expected.blue).abs() < 0.00001
713 );
714
715 assert_eq!(
717 HsvaColor::from(RgbaColor::<f32> { red: 0., green: 0.9, blue: 0., alpha: 1.0 }),
718 HsvaColor { hue: 120., saturation: 1., value: 0.9, alpha: 1.0 }
719 );
720 assert_eq!(
721 RgbaColor::<f32>::from(HsvaColor { hue: 120., saturation: 1., value: 0.9, alpha: 1.0 }),
722 RgbaColor::<f32> { red: 0., green: 0.9, blue: 0., alpha: 1.0 }
723 );
724
725 assert_eq!(
727 RgbaColor::<f32> { red: 0., green: 0.9, blue: 0., alpha: 1.0 },
728 RgbaColor::<f32>::from(HsvaColor { hue: 480., saturation: 1., value: 0.9, alpha: 1.0 }),
729 );
730 assert_eq!(
731 RgbaColor::<f32> { red: 0., green: 0.9, blue: 0., alpha: 1.0 },
732 RgbaColor::<f32>::from(HsvaColor { hue: -240., saturation: 1., value: 0.9, alpha: 1.0 }),
733 );
734}
735
736#[test]
737fn test_brighter_darker() {
738 let blue = Color::from_rgb_u8(0, 0, 128);
739 assert_eq!(blue.brighter(0.5), Color::from_rgb_f32(0.0, 0.0, 0.75294125));
740 assert_eq!(blue.darker(0.5), Color::from_rgb_f32(0.0, 0.0, 0.33464053));
741}
742
743#[test]
744fn test_transparent_transition() {
745 let color = Color::from_argb_f32(0.0, 0.0, 0.0, 0.0);
746 let interpolated = color.interpolate(&Color::from_rgb_f32(0.8, 0.8, 0.8), 0.25);
747 assert_eq!(interpolated, Color::from_argb_f32(0.25, 0.8, 0.8, 0.8));
748 let interpolated = color.interpolate(&Color::from_rgb_f32(0.8, 0.8, 0.8), 0.5);
749 assert_eq!(interpolated, Color::from_argb_f32(0.5, 0.8, 0.8, 0.8));
750 let interpolated = color.interpolate(&Color::from_rgb_f32(0.8, 0.8, 0.8), 0.75);
751 assert_eq!(interpolated, Color::from_argb_f32(0.75, 0.8, 0.8, 0.8));
752}
753
754#[test]
755fn test_oklch_roundtrip() {
756 let test_colors = [
759 OklchColor { lightness: 0.5, chroma: 0.08, hue: 30.0, alpha: 1.0 },
760 OklchColor { lightness: 0.6, chroma: 0.1, hue: 120.0, alpha: 0.8 },
761 OklchColor { lightness: 0.4, chroma: 0.08, hue: 240.0, alpha: 1.0 },
762 OklchColor { lightness: 0.8, chroma: 0.05, hue: 0.0, alpha: 1.0 },
763 OklchColor { lightness: 0.5, chroma: 0.0, hue: 0.0, alpha: 1.0 },
765 ];
766
767 for oklch in test_colors {
768 let rgba = RgbaColor::<f32>::from(oklch);
769 let roundtrip = OklchColor::from(rgba);
770 assert!(
772 (oklch.lightness - roundtrip.lightness).abs() < 0.01,
773 "Lightness mismatch: {:?} vs {:?}",
774 oklch,
775 roundtrip
776 );
777 if oklch.chroma > 0.001 {
779 assert!(
780 (oklch.chroma - roundtrip.chroma).abs() < 0.02,
781 "Chroma mismatch: {:?} vs {:?}",
782 oklch,
783 roundtrip
784 );
785 let hue_diff = (oklch.hue - roundtrip.hue).abs();
787 let hue_diff = hue_diff.min(360.0 - hue_diff);
788 assert!(hue_diff < 2.0, "Hue mismatch: {:?} vs {:?}", oklch, roundtrip);
789 }
790 }
791}
792
793#[test]
794fn test_oklch_known_values() {
795 let red_oklch = OklchColor { lightness: 0.63, chroma: 0.26, hue: 29.0, alpha: 1.0 };
798 let red_rgba = RgbaColor::<f32>::from(red_oklch);
799 assert!(red_rgba.red > 0.8, "Red component should be high: {}", red_rgba.red);
801 assert!(red_rgba.green < 0.3, "Green component should be low: {}", red_rgba.green);
802 assert!(red_rgba.blue < 0.3, "Blue component should be low: {}", red_rgba.blue);
803}
804
805#[test]
806fn test_rgb_to_oklch() {
807 let white = OklchColor::from(RgbaColor::<f32> { red: 1., green: 1., blue: 1., alpha: 0.5 });
809 assert!((white.lightness - 1.0).abs() < 0.01, "White lightness should be ~1.0");
810 assert!(white.chroma < 0.001, "White chroma should be ~0");
811 assert!((white.alpha - 0.5).abs() < 0.001, "Alpha should be preserved");
812
813 let black = OklchColor::from(RgbaColor::<f32> { red: 0., green: 0., blue: 0., alpha: 1.0 });
815 assert!(black.lightness < 0.01, "Black lightness should be ~0");
816 assert!(black.chroma < 0.001, "Black chroma should be ~0");
817
818 let red = OklchColor::from(RgbaColor::<f32> { red: 1., green: 0., blue: 0., alpha: 1.0 });
820 assert!(red.lightness > 0.5 && red.lightness < 0.7, "Red lightness should be ~0.63");
821 assert!(red.chroma > 0.2, "Red should have significant chroma");
822 assert!(red.hue > 20.0 && red.hue < 40.0, "Red hue should be around 29 degrees");
823
824 let blue = OklchColor::from(RgbaColor::<f32> { red: 0., green: 0., blue: 1., alpha: 1.0 });
826 assert!(blue.lightness > 0.4 && blue.lightness < 0.5, "Blue lightness should be ~0.45");
827 assert!(blue.chroma > 0.2, "Blue should have significant chroma");
828 assert!(blue.hue > 250.0 && blue.hue < 280.0, "Blue hue should be around 264 degrees");
829}
830
831#[cfg(feature = "ffi")]
832pub(crate) mod ffi {
833 #![allow(unsafe_code)]
834 use super::*;
835
836 #[unsafe(no_mangle)]
837 pub unsafe extern "C" fn slint_color_brighter(col: &Color, factor: f32, out: *mut Color) {
838 unsafe { core::ptr::write(out, col.brighter(factor)) }
839 }
840
841 #[unsafe(no_mangle)]
842 pub unsafe extern "C" fn slint_color_darker(col: &Color, factor: f32, out: *mut Color) {
843 unsafe { core::ptr::write(out, col.darker(factor)) }
844 }
845
846 #[unsafe(no_mangle)]
847 pub unsafe extern "C" fn slint_color_transparentize(col: &Color, factor: f32, out: *mut Color) {
848 unsafe { core::ptr::write(out, col.transparentize(factor)) }
849 }
850
851 #[unsafe(no_mangle)]
852 pub unsafe extern "C" fn slint_color_mix(
853 col1: &Color,
854 col2: &Color,
855 factor: f32,
856 out: *mut Color,
857 ) {
858 unsafe { core::ptr::write(out, col1.mix(col2, factor)) }
859 }
860
861 #[unsafe(no_mangle)]
862 pub unsafe extern "C" fn slint_color_with_alpha(col: &Color, alpha: f32, out: *mut Color) {
863 unsafe { core::ptr::write(out, col.with_alpha(alpha)) }
864 }
865
866 #[unsafe(no_mangle)]
867 pub extern "C" fn slint_color_to_hsva(
868 col: &Color,
869 h: &mut f32,
870 s: &mut f32,
871 v: &mut f32,
872 a: &mut f32,
873 ) {
874 let hsv = col.to_hsva();
875 *h = hsv.hue;
876 *s = hsv.saturation;
877 *v = hsv.value;
878 *a = hsv.alpha;
879 }
880
881 #[unsafe(no_mangle)]
882 pub extern "C" fn slint_color_from_hsva(h: f32, s: f32, v: f32, a: f32) -> Color {
883 Color::from_hsva(h, s, v, a)
884 }
885
886 #[unsafe(no_mangle)]
887 pub extern "C" fn slint_color_from_oklch(l: f32, c: f32, h: f32, a: f32) -> Color {
888 Color::from_oklch(l, c, h, a)
889 }
890
891 #[unsafe(no_mangle)]
892 pub extern "C" fn slint_color_to_oklch(
893 col: &Color,
894 l: &mut f32,
895 c: &mut f32,
896 h: &mut f32,
897 a: &mut f32,
898 ) {
899 let oklch = col.to_oklch();
900 *l = oklch.lightness;
901 *c = oklch.chroma;
902 *h = oklch.hue;
903 *a = oklch.alpha;
904 }
905}