1use crate::properties::InterpolatedPropertyValue;
9
10#[cfg(not(feature = "std"))]
11use num_traits::float::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#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Default)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48#[repr(C)]
49pub struct Color {
50 red: u8,
51 green: u8,
52 blue: u8,
53 alpha: u8,
54}
55
56impl From<RgbaColor<u8>> for Color {
57 fn from(col: RgbaColor<u8>) -> Self {
58 Self { red: col.red, green: col.green, blue: col.blue, alpha: col.alpha }
59 }
60}
61
62impl From<Color> for RgbaColor<u8> {
63 fn from(col: Color) -> Self {
64 RgbaColor { red: col.red, green: col.green, blue: col.blue, alpha: col.alpha }
65 }
66}
67
68impl From<RgbaColor<u8>> for RgbaColor<f32> {
69 fn from(col: RgbaColor<u8>) -> Self {
70 Self {
71 red: (col.red as f32) / 255.0,
72 green: (col.green as f32) / 255.0,
73 blue: (col.blue as f32) / 255.0,
74 alpha: (col.alpha as f32) / 255.0,
75 }
76 }
77}
78
79impl From<Color> for RgbaColor<f32> {
80 fn from(col: Color) -> Self {
81 let u8col: RgbaColor<u8> = col.into();
82 u8col.into()
83 }
84}
85
86impl From<RgbaColor<f32>> for Color {
87 fn from(col: RgbaColor<f32>) -> Self {
88 Self {
89 red: (col.red * 255.).round() as u8,
90 green: (col.green * 255.).round() as u8,
91 blue: (col.blue * 255.).round() as u8,
92 alpha: (col.alpha * 255.).round() as u8,
93 }
94 }
95}
96
97impl Color {
98 pub const fn from_argb_encoded(encoded: u32) -> Color {
100 Self {
101 red: (encoded >> 16) as u8,
102 green: (encoded >> 8) as u8,
103 blue: encoded as u8,
104 alpha: (encoded >> 24) as u8,
105 }
106 }
107
108 pub fn as_argb_encoded(&self) -> u32 {
110 ((self.red as u32) << 16)
111 | ((self.green as u32) << 8)
112 | (self.blue as u32)
113 | ((self.alpha as u32) << 24)
114 }
115
116 pub const fn from_argb_u8(alpha: u8, red: u8, green: u8, blue: u8) -> Self {
118 Self { red, green, blue, alpha }
119 }
120
121 pub const fn from_rgb_u8(red: u8, green: u8, blue: u8) -> Self {
124 Self::from_argb_u8(255, red, green, blue)
125 }
126
127 pub fn from_argb_f32(alpha: f32, red: f32, green: f32, blue: f32) -> Self {
129 RgbaColor { alpha, red, green, blue }.into()
130 }
131
132 pub fn from_rgb_f32(red: f32, green: f32, blue: f32) -> Self {
135 Self::from_argb_f32(1.0, red, green, blue)
136 }
137
138 pub fn to_argb_u8(&self) -> RgbaColor<u8> {
140 RgbaColor::from(*self)
141 }
142
143 pub fn to_argb_f32(&self) -> RgbaColor<f32> {
145 RgbaColor::from(*self)
146 }
147
148 pub fn to_hsva(&self) -> HsvaColor {
150 let rgba: RgbaColor<f32> = (*self).into();
151 rgba.into()
152 }
153
154 pub fn from_hsva(hue: f32, saturation: f32, value: f32, alpha: f32) -> Self {
158 let hsva = HsvaColor { hue, saturation, value, alpha };
159 <RgbaColor<f32>>::from(hsva).into()
160 }
161
162 #[inline(always)]
164 pub fn red(self) -> u8 {
165 self.red
166 }
167
168 #[inline(always)]
170 pub fn green(self) -> u8 {
171 self.green
172 }
173
174 #[inline(always)]
176 pub fn blue(self) -> u8 {
177 self.blue
178 }
179
180 #[inline(always)]
182 pub fn alpha(self) -> u8 {
183 self.alpha
184 }
185
186 #[must_use]
193 pub fn brighter(&self, factor: f32) -> Self {
194 let rgba: RgbaColor<f32> = (*self).into();
195 let mut hsva: HsvaColor = rgba.into();
196 hsva.value *= 1. + factor;
197 let rgba: RgbaColor<f32> = hsva.into();
198 rgba.into()
199 }
200
201 #[must_use]
207 pub fn darker(&self, factor: f32) -> Self {
208 let rgba: RgbaColor<f32> = (*self).into();
209 let mut hsva: HsvaColor = rgba.into();
210 hsva.value /= 1. + factor;
211 let rgba: RgbaColor<f32> = hsva.into();
212 rgba.into()
213 }
214
215 #[must_use]
242 pub fn transparentize(&self, factor: f32) -> Self {
243 let mut color = *self;
244 color.alpha = ((self.alpha as f32) * (1.0 - factor))
245 .round()
246 .clamp(u8::MIN as f32, u8::MAX as f32) as u8;
247 color
248 }
249
250 #[must_use]
271 pub fn mix(&self, other: &Self, factor: f32) -> Self {
272 fn lerp(v1: u8, v2: u8, f: f32) -> u8 {
279 (v1 as f32 * f + v2 as f32 * (1.0 - f)).clamp(u8::MIN as f32, u8::MAX as f32).round()
280 as u8
281 }
282
283 let original_factor = factor.clamp(0.0, 1.0);
284
285 let self_opacity = RgbaColor::<f32>::from(*self).alpha;
286 let other_opacity = RgbaColor::<f32>::from(*other).alpha;
287
288 let normal_weight = 2.0 * original_factor - 1.0;
289 let alpha_distance = self_opacity - other_opacity;
290 let weight_by_distance = normal_weight * alpha_distance;
291
292 let combined_weight = if weight_by_distance == -1.0 {
294 normal_weight
295 } else {
296 (normal_weight + alpha_distance) / (1.0 + weight_by_distance)
297 };
298
299 let channels_factor = (combined_weight + 1.0) / 2.0;
300
301 let red = lerp(self.red, other.red, channels_factor);
302 let green = lerp(self.green, other.green, channels_factor);
303 let blue = lerp(self.blue, other.blue, channels_factor);
304
305 let alpha = lerp(self.alpha, other.alpha, original_factor);
306
307 Self { red, green, blue, alpha }
308 }
309
310 #[must_use]
312 pub fn with_alpha(&self, alpha: f32) -> Self {
313 let mut rgba: RgbaColor<f32> = (*self).into();
314 rgba.alpha = alpha.clamp(0.0, 1.0);
315 rgba.into()
316 }
317}
318
319impl InterpolatedPropertyValue for Color {
320 fn interpolate(&self, target_value: &Self, t: f32) -> Self {
321 target_value.mix(self, t)
322 }
323}
324
325impl core::fmt::Display for Color {
326 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
327 write!(f, "argb({}, {}, {}, {})", self.alpha, self.red, self.green, self.blue)
328 }
329}
330
331#[derive(Copy, Clone, PartialOrd, Debug, Default)]
335#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
336pub struct HsvaColor {
337 pub hue: f32,
339 pub saturation: f32,
341 pub value: f32,
343 pub alpha: f32,
345}
346
347impl PartialEq for HsvaColor {
348 fn eq(&self, other: &Self) -> bool {
349 (self.hue - other.hue).abs() < 0.00001
350 && (self.saturation - other.saturation).abs() < 0.00001
351 && (self.value - other.value).abs() < 0.00001
352 && (self.alpha - other.alpha).abs() < 0.00001
353 }
354}
355
356impl From<RgbaColor<f32>> for HsvaColor {
357 fn from(col: RgbaColor<f32>) -> Self {
358 let red = col.red;
361 let green = col.green;
362 let blue = col.blue;
363
364 let min = red.min(green).min(blue);
365 let max = red.max(green).max(blue);
366 let chroma = max - min;
367
368 #[allow(clippy::float_cmp)] let hue = num_traits::Euclid::rem_euclid(
370 &(60.
371 * if chroma == 0.0 {
372 0.0
373 } else if max == red {
374 ((green - blue) / chroma) % 6.0
375 } else if max == green {
376 2. + (blue - red) / chroma
377 } else {
378 4. + (red - green) / chroma
379 }),
380 &360.0,
381 );
382 let saturation = if max == 0. { 0. } else { chroma / max };
383
384 Self { hue, saturation, value: max, alpha: col.alpha }
385 }
386}
387
388impl From<HsvaColor> for RgbaColor<f32> {
389 fn from(col: HsvaColor) -> Self {
390 let chroma = col.saturation * col.value;
393
394 let hue = num_traits::Euclid::rem_euclid(&col.hue, &360.0);
395
396 let x = chroma * (1. - ((hue / 60.) % 2. - 1.).abs());
397
398 let (red, green, blue) = match (hue / 60.0) as usize {
399 0 => (chroma, x, 0.),
400 1 => (x, chroma, 0.),
401 2 => (0., chroma, x),
402 3 => (0., x, chroma),
403 4 => (x, 0., chroma),
404 5 => (chroma, 0., x),
405 _ => (0., 0., 0.),
406 };
407
408 let m = col.value - chroma;
409
410 Self { red: red + m, green: green + m, blue: blue + m, alpha: col.alpha }
411 }
412}
413
414impl From<HsvaColor> for Color {
415 fn from(value: HsvaColor) -> Self {
416 RgbaColor::from(value).into()
417 }
418}
419
420impl From<Color> for HsvaColor {
421 fn from(value: Color) -> Self {
422 value.to_hsva()
423 }
424}
425
426#[test]
427fn test_rgb_to_hsv() {
428 assert_eq!(
430 HsvaColor::from(RgbaColor::<f32> { red: 1., green: 1., blue: 1., alpha: 0.5 }),
431 HsvaColor { hue: 0., saturation: 0., value: 1., alpha: 0.5 }
432 );
433 assert_eq!(
434 RgbaColor::<f32>::from(HsvaColor { hue: 0., saturation: 0., value: 1., alpha: 0.3 }),
435 RgbaColor::<f32> { red: 1., green: 1., blue: 1., alpha: 0.3 }
436 );
437
438 assert_eq!(
440 HsvaColor::from(Color::from_argb_u8(0xff, 0x8a, 0xc, 0x77,).to_argb_f32()),
441 HsvaColor { hue: 309.0476, saturation: 0.9130435, value: 0.5411765, alpha: 1.0 }
442 );
443
444 let received = RgbaColor::<f32>::from(HsvaColor {
445 hue: 309.0476,
446 saturation: 0.9130435,
447 value: 0.5411765,
448 alpha: 1.0,
449 });
450 let expected = Color::from_argb_u8(0xff, 0x8a, 0xc, 0x77).to_argb_f32();
451
452 assert!(
453 (received.alpha - expected.alpha).abs() < 0.00001
454 && (received.red - expected.red).abs() < 0.00001
455 && (received.green - expected.green).abs() < 0.00001
456 && (received.blue - expected.blue).abs() < 0.00001
457 );
458
459 assert_eq!(
461 HsvaColor::from(RgbaColor::<f32> { red: 0., green: 0.9, blue: 0., alpha: 1.0 }),
462 HsvaColor { hue: 120., saturation: 1., value: 0.9, alpha: 1.0 }
463 );
464 assert_eq!(
465 RgbaColor::<f32>::from(HsvaColor { hue: 120., saturation: 1., value: 0.9, alpha: 1.0 }),
466 RgbaColor::<f32> { red: 0., green: 0.9, blue: 0., alpha: 1.0 }
467 );
468
469 assert_eq!(
471 RgbaColor::<f32> { red: 0., green: 0.9, blue: 0., alpha: 1.0 },
472 RgbaColor::<f32>::from(HsvaColor { hue: 480., saturation: 1., value: 0.9, alpha: 1.0 }),
473 );
474 assert_eq!(
475 RgbaColor::<f32> { red: 0., green: 0.9, blue: 0., alpha: 1.0 },
476 RgbaColor::<f32>::from(HsvaColor { hue: -240., saturation: 1., value: 0.9, alpha: 1.0 }),
477 );
478}
479
480#[test]
481fn test_brighter_darker() {
482 let blue = Color::from_rgb_u8(0, 0, 128);
483 assert_eq!(blue.brighter(0.5), Color::from_rgb_u8(0, 0, 192));
484 assert_eq!(blue.darker(0.5), Color::from_rgb_u8(0, 0, 85));
485}
486
487#[test]
488fn test_transparent_transition() {
489 let color = Color::from_argb_u8(0, 0, 0, 0);
490 let interpolated = color.interpolate(&Color::from_rgb_u8(211, 211, 211), 0.25);
491 assert_eq!(interpolated, Color::from_argb_u8(64, 211, 211, 211));
492 let interpolated = color.interpolate(&Color::from_rgb_u8(211, 211, 211), 0.5);
493 assert_eq!(interpolated, Color::from_argb_u8(128, 211, 211, 211));
494 let interpolated = color.interpolate(&Color::from_rgb_u8(211, 211, 211), 0.75);
495 assert_eq!(interpolated, Color::from_argb_u8(191, 211, 211, 211));
496}
497
498#[cfg(feature = "ffi")]
499pub(crate) mod ffi {
500 #![allow(unsafe_code)]
501 use super::*;
502
503 #[unsafe(no_mangle)]
504 pub unsafe extern "C" fn slint_color_brighter(col: &Color, factor: f32, out: *mut Color) {
505 core::ptr::write(out, col.brighter(factor))
506 }
507
508 #[unsafe(no_mangle)]
509 pub unsafe extern "C" fn slint_color_darker(col: &Color, factor: f32, out: *mut Color) {
510 core::ptr::write(out, col.darker(factor))
511 }
512
513 #[unsafe(no_mangle)]
514 pub unsafe extern "C" fn slint_color_transparentize(col: &Color, factor: f32, out: *mut Color) {
515 core::ptr::write(out, col.transparentize(factor))
516 }
517
518 #[unsafe(no_mangle)]
519 pub unsafe extern "C" fn slint_color_mix(
520 col1: &Color,
521 col2: &Color,
522 factor: f32,
523 out: *mut Color,
524 ) {
525 core::ptr::write(out, col1.mix(col2, factor))
526 }
527
528 #[unsafe(no_mangle)]
529 pub unsafe extern "C" fn slint_color_with_alpha(col: &Color, alpha: f32, out: *mut Color) {
530 core::ptr::write(out, col.with_alpha(alpha))
531 }
532
533 #[unsafe(no_mangle)]
534 pub extern "C" fn slint_color_to_hsva(
535 col: &Color,
536 h: &mut f32,
537 s: &mut f32,
538 v: &mut f32,
539 a: &mut f32,
540 ) {
541 let hsv = col.to_hsva();
542 *h = hsv.hue;
543 *s = hsv.saturation;
544 *v = hsv.value;
545 *a = hsv.alpha;
546 }
547
548 #[unsafe(no_mangle)]
549 pub extern "C" fn slint_color_from_hsva(h: f32, s: f32, v: f32, a: f32) -> Color {
550 Color::from_hsva(h, s, v, a)
551 }
552}