1use anyhow::{bail, Context as _};
2use schemars::{json_schema, JsonSchema};
3use serde::{
4 de::{self, Visitor},
5 Deserialize, Deserializer, Serialize, Serializer,
6};
7use std::borrow::Cow;
8use std::{
9 fmt::{self, Display, Formatter},
10 hash::{Hash, Hasher},
11};
12
13pub fn rgb(hex: u32) -> Rgba {
15 let [_, r, g, b] = hex.to_be_bytes().map(|b| (b as f32) / 255.0);
16 Rgba { r, g, b, a: 1.0 }
17}
18
19pub fn rgba(hex: u32) -> Rgba {
21 let [r, g, b, a] = hex.to_be_bytes().map(|b| (b as f32) / 255.0);
22 Rgba { r, g, b, a }
23}
24
25pub(crate) fn swap_rgba_pa_to_bgra(color: &mut [u8]) {
27 color.swap(0, 2);
28 if color[3] > 0 {
29 let a = color[3] as f32 / 255.;
30 color[0] = (color[0] as f32 / a) as u8;
31 color[1] = (color[1] as f32 / a) as u8;
32 color[2] = (color[2] as f32 / a) as u8;
33 }
34}
35
36#[derive(PartialEq, Clone, Copy, Default)]
38#[repr(C)]
39pub struct Rgba {
40 pub r: f32,
42 pub g: f32,
44 pub b: f32,
46 pub a: f32,
48}
49
50impl fmt::Debug for Rgba {
51 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
52 write!(f, "rgba({:#010x})", u32::from(*self))
53 }
54}
55
56impl Rgba {
57 pub fn blend(&self, other: Rgba) -> Self {
59 if other.a >= 1.0 {
60 other
61 } else if other.a <= 0.0 {
62 *self
63 } else {
64 Rgba {
65 r: (self.r * (1.0 - other.a)) + (other.r * other.a),
66 g: (self.g * (1.0 - other.a)) + (other.g * other.a),
67 b: (self.b * (1.0 - other.a)) + (other.b * other.a),
68 a: self.a,
69 }
70 }
71 }
72}
73
74impl From<Rgba> for u32 {
75 fn from(rgba: Rgba) -> Self {
76 let r = (rgba.r * 255.0) as u32;
77 let g = (rgba.g * 255.0) as u32;
78 let b = (rgba.b * 255.0) as u32;
79 let a = (rgba.a * 255.0) as u32;
80 (r << 24) | (g << 16) | (b << 8) | a
81 }
82}
83
84struct RgbaVisitor;
85
86impl Visitor<'_> for RgbaVisitor {
87 type Value = Rgba;
88
89 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
90 formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
91 }
92
93 fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
94 Rgba::try_from(value).map_err(E::custom)
95 }
96}
97
98impl JsonSchema for Rgba {
99 fn schema_name() -> Cow<'static, str> {
100 "Rgba".into()
101 }
102
103 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
104 json_schema!({
105 "type": "string",
106 "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
107 })
108 }
109}
110
111impl<'de> Deserialize<'de> for Rgba {
112 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
113 deserializer.deserialize_str(RgbaVisitor)
114 }
115}
116
117impl Serialize for Rgba {
118 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
119 where
120 S: Serializer,
121 {
122 let r = (self.r * 255.0).round() as u8;
123 let g = (self.g * 255.0).round() as u8;
124 let b = (self.b * 255.0).round() as u8;
125 let a = (self.a * 255.0).round() as u8;
126
127 let s = format!("#{r:02x}{g:02x}{b:02x}{a:02x}");
128 serializer.serialize_str(&s)
129 }
130}
131
132impl From<Hsla> for Rgba {
133 fn from(color: Hsla) -> Self {
134 let h = color.h;
135 let s = color.s;
136 let l = color.l;
137
138 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
139 let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
140 let m = l - c / 2.0;
141 let cm = c + m;
142 let xm = x + m;
143
144 let (r, g, b) = match (h * 6.0).floor() as i32 {
145 0 | 6 => (cm, xm, m),
146 1 => (xm, cm, m),
147 2 => (m, cm, xm),
148 3 => (m, xm, cm),
149 4 => (xm, m, cm),
150 _ => (cm, m, xm),
151 };
152
153 Rgba {
154 r: r.clamp(0., 1.),
155 g: g.clamp(0., 1.),
156 b: b.clamp(0., 1.),
157 a: color.a,
158 }
159 }
160}
161
162impl TryFrom<&'_ str> for Rgba {
163 type Error = anyhow::Error;
164
165 fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
166 const RGB: usize = "rgb".len();
167 const RGBA: usize = "rgba".len();
168 const RRGGBB: usize = "rrggbb".len();
169 const RRGGBBAA: usize = "rrggbbaa".len();
170
171 const EXPECTED_FORMATS: &str = "Expected #rgb, #rgba, #rrggbb, or #rrggbbaa";
172 const INVALID_UNICODE: &str = "invalid unicode characters in color";
173
174 let Some(("", hex)) = value.trim().split_once('#') else {
175 bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}");
176 };
177
178 let (r, g, b, a) = match hex.len() {
179 RGB | RGBA => {
180 let r = u8::from_str_radix(
181 hex.get(0..1).with_context(|| {
182 format!("{INVALID_UNICODE}: r component of #rgb/#rgba for value: '{value}'")
183 })?,
184 16,
185 )?;
186 let g = u8::from_str_radix(
187 hex.get(1..2).with_context(|| {
188 format!("{INVALID_UNICODE}: g component of #rgb/#rgba for value: '{value}'")
189 })?,
190 16,
191 )?;
192 let b = u8::from_str_radix(
193 hex.get(2..3).with_context(|| {
194 format!("{INVALID_UNICODE}: b component of #rgb/#rgba for value: '{value}'")
195 })?,
196 16,
197 )?;
198 let a = if hex.len() == RGBA {
199 u8::from_str_radix(
200 hex.get(3..4).with_context(|| {
201 format!("{INVALID_UNICODE}: a component of #rgba for value: '{value}'")
202 })?,
203 16,
204 )?
205 } else {
206 0xf
207 };
208
209 const fn duplicate(value: u8) -> u8 {
212 (value << 4) | value
213 }
214
215 (duplicate(r), duplicate(g), duplicate(b), duplicate(a))
216 }
217 RRGGBB | RRGGBBAA => {
218 let r = u8::from_str_radix(
219 hex.get(0..2).with_context(|| {
220 format!(
221 "{}: r component of #rrggbb/#rrggbbaa for value: '{}'",
222 INVALID_UNICODE, value
223 )
224 })?,
225 16,
226 )?;
227 let g = u8::from_str_radix(
228 hex.get(2..4).with_context(|| {
229 format!(
230 "{INVALID_UNICODE}: g component of #rrggbb/#rrggbbaa for value: '{value}'"
231 )
232 })?,
233 16,
234 )?;
235 let b = u8::from_str_radix(
236 hex.get(4..6).with_context(|| {
237 format!(
238 "{INVALID_UNICODE}: b component of #rrggbb/#rrggbbaa for value: '{value}'"
239 )
240 })?,
241 16,
242 )?;
243 let a = if hex.len() == RRGGBBAA {
244 u8::from_str_radix(
245 hex.get(6..8).with_context(|| {
246 format!(
247 "{INVALID_UNICODE}: a component of #rrggbbaa for value: '{value}'"
248 )
249 })?,
250 16,
251 )?
252 } else {
253 0xff
254 };
255 (r, g, b, a)
256 }
257 _ => bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}"),
258 };
259
260 Ok(Rgba {
261 r: r as f32 / 255.,
262 g: g as f32 / 255.,
263 b: b as f32 / 255.,
264 a: a as f32 / 255.,
265 })
266 }
267}
268
269#[derive(Default, Copy, Clone, Debug)]
271#[repr(C)]
272pub struct Hsla {
273 pub h: f32,
275
276 pub s: f32,
278
279 pub l: f32,
281
282 pub a: f32,
284}
285
286impl PartialEq for Hsla {
287 fn eq(&self, other: &Self) -> bool {
288 self.h
289 .total_cmp(&other.h)
290 .then(self.s.total_cmp(&other.s))
291 .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
292 .is_eq()
293 }
294}
295
296impl PartialOrd for Hsla {
297 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
298 Some(self.cmp(other))
299 }
300}
301
302impl Ord for Hsla {
303 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
304 self.h
305 .total_cmp(&other.h)
306 .then(self.s.total_cmp(&other.s))
307 .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
308 }
309}
310
311impl Eq for Hsla {}
312
313impl Hash for Hsla {
314 fn hash<H: Hasher>(&self, state: &mut H) {
315 state.write_u32(u32::from_be_bytes(self.h.to_be_bytes()));
316 state.write_u32(u32::from_be_bytes(self.s.to_be_bytes()));
317 state.write_u32(u32::from_be_bytes(self.l.to_be_bytes()));
318 state.write_u32(u32::from_be_bytes(self.a.to_be_bytes()));
319 }
320}
321
322impl Display for Hsla {
323 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
324 write!(
325 f,
326 "hsla({:.2}, {:.2}%, {:.2}%, {:.2})",
327 self.h * 360.,
328 self.s * 100.,
329 self.l * 100.,
330 self.a
331 )
332 }
333}
334
335pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
337 Hsla {
338 h: h.clamp(0., 1.),
339 s: s.clamp(0., 1.),
340 l: l.clamp(0., 1.),
341 a: a.clamp(0., 1.),
342 }
343}
344
345pub const fn black() -> Hsla {
347 Hsla {
348 h: 0.,
349 s: 0.,
350 l: 0.,
351 a: 1.,
352 }
353}
354
355pub const fn transparent_black() -> Hsla {
357 Hsla {
358 h: 0.,
359 s: 0.,
360 l: 0.,
361 a: 0.,
362 }
363}
364
365pub const fn transparent_white() -> Hsla {
367 Hsla {
368 h: 0.,
369 s: 0.,
370 l: 1.,
371 a: 0.,
372 }
373}
374
375pub fn opaque_grey(lightness: f32, opacity: f32) -> Hsla {
377 Hsla {
378 h: 0.,
379 s: 0.,
380 l: lightness.clamp(0., 1.),
381 a: opacity.clamp(0., 1.),
382 }
383}
384
385pub const fn white() -> Hsla {
387 Hsla {
388 h: 0.,
389 s: 0.,
390 l: 1.,
391 a: 1.,
392 }
393}
394
395pub const fn red() -> Hsla {
397 Hsla {
398 h: 0.,
399 s: 1.,
400 l: 0.5,
401 a: 1.,
402 }
403}
404
405pub const fn blue() -> Hsla {
407 Hsla {
408 h: 0.6666666667,
409 s: 1.,
410 l: 0.5,
411 a: 1.,
412 }
413}
414
415pub const fn green() -> Hsla {
417 Hsla {
418 h: 0.3333333333,
419 s: 1.,
420 l: 0.25,
421 a: 1.,
422 }
423}
424
425pub const fn yellow() -> Hsla {
427 Hsla {
428 h: 0.1666666667,
429 s: 1.,
430 l: 0.5,
431 a: 1.,
432 }
433}
434
435impl Hsla {
436 pub fn to_rgb(self) -> Rgba {
438 self.into()
439 }
440
441 pub const fn red() -> Self {
443 red()
444 }
445
446 pub const fn green() -> Self {
448 green()
449 }
450
451 pub const fn blue() -> Self {
453 blue()
454 }
455
456 pub const fn black() -> Self {
458 black()
459 }
460
461 pub const fn white() -> Self {
463 white()
464 }
465
466 pub const fn transparent_black() -> Self {
468 transparent_black()
469 }
470
471 pub fn is_transparent(&self) -> bool {
473 self.a == 0.0
474 }
475
476 pub fn is_opaque(&self) -> bool {
478 self.a == 1.0
479 }
480
481 pub fn blend(self, other: Hsla) -> Hsla {
493 let alpha = other.a;
494
495 if alpha >= 1.0 {
496 other
497 } else if alpha <= 0.0 {
498 self
499 } else {
500 let converted_self = Rgba::from(self);
501 let converted_other = Rgba::from(other);
502 let blended_rgb = converted_self.blend(converted_other);
503 Hsla::from(blended_rgb)
504 }
505 }
506
507 pub fn grayscale(&self) -> Self {
509 Hsla {
510 h: self.h,
511 s: 0.,
512 l: self.l,
513 a: self.a,
514 }
515 }
516
517 pub fn fade_out(&mut self, factor: f32) {
520 self.a *= 1.0 - factor.clamp(0., 1.);
521 }
522
523 pub fn opacity(&self, factor: f32) -> Self {
550 Hsla {
551 h: self.h,
552 s: self.s,
553 l: self.l,
554 a: self.a * factor.clamp(0., 1.),
555 }
556 }
557
558 pub fn alpha(&self, a: f32) -> Self {
580 Hsla {
581 h: self.h,
582 s: self.s,
583 l: self.l,
584 a: a.clamp(0., 1.),
585 }
586 }
587}
588
589impl From<Rgba> for Hsla {
590 fn from(color: Rgba) -> Self {
591 let r = color.r;
592 let g = color.g;
593 let b = color.b;
594
595 let max = r.max(g.max(b));
596 let min = r.min(g.min(b));
597 let delta = max - min;
598
599 let l = (max + min) / 2.0;
600 let s = if l == 0.0 || l == 1.0 {
601 0.0
602 } else if l < 0.5 {
603 delta / (2.0 * l)
604 } else {
605 delta / (2.0 - 2.0 * l)
606 };
607
608 let h = if delta == 0.0 {
609 0.0
610 } else if max == r {
611 ((g - b) / delta).rem_euclid(6.0) / 6.0
612 } else if max == g {
613 ((b - r) / delta + 2.0) / 6.0
614 } else {
615 ((r - g) / delta + 4.0) / 6.0
616 };
617
618 Hsla {
619 h,
620 s,
621 l,
622 a: color.a,
623 }
624 }
625}
626
627impl JsonSchema for Hsla {
628 fn schema_name() -> Cow<'static, str> {
629 Rgba::schema_name()
630 }
631
632 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
633 Rgba::json_schema(generator)
634 }
635}
636
637impl Serialize for Hsla {
638 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
639 where
640 S: Serializer,
641 {
642 Rgba::from(*self).serialize(serializer)
643 }
644}
645
646impl<'de> Deserialize<'de> for Hsla {
647 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
648 where
649 D: Deserializer<'de>,
650 {
651 Ok(Rgba::deserialize(deserializer)?.into())
652 }
653}
654
655#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
656#[repr(C)]
657pub(crate) enum BackgroundTag {
658 Solid = 0,
659 LinearGradient = 1,
660 PatternSlash = 2,
661 RadialGradient = 3,
662 ConicGradient = 4,
663}
664
665#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
671#[repr(C)]
672pub enum ColorSpace {
673 #[default]
674 Srgb = 0,
676 Oklab = 1,
678}
679
680impl Display for ColorSpace {
681 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
682 match self {
683 ColorSpace::Srgb => write!(f, "sRGB"),
684 ColorSpace::Oklab => write!(f, "Oklab"),
685 }
686 }
687}
688
689#[derive(Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
691#[repr(C)]
692pub struct Background {
693 pub(crate) tag: BackgroundTag,
694 pub(crate) color_space: ColorSpace,
695 pub(crate) solid: Hsla,
696 pub(crate) gradient_angle_or_pattern_height: f32,
697 pub(crate) colors: [LinearColorStop; 4],
698 pub(crate) stop_count: u32,
699 pub(crate) center: [f32; 2],
700 pub(crate) radius: [f32; 2],
701}
702
703impl std::fmt::Debug for Background {
704 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
705 match self.tag {
706 BackgroundTag::Solid => write!(f, "Solid({:?})", self.solid),
707 BackgroundTag::LinearGradient => {
708 let count = if self.stop_count == 0 {
709 2
710 } else {
711 self.stop_count as usize
712 };
713 write!(
714 f,
715 "LinearGradient({}, {:?})",
716 self.gradient_angle_or_pattern_height,
717 &self.colors[..count]
718 )
719 }
720 BackgroundTag::PatternSlash => {
721 write!(
722 f,
723 "PatternSlash({:?}, {})",
724 self.solid, self.gradient_angle_or_pattern_height
725 )
726 }
727 BackgroundTag::RadialGradient => {
728 let count = if self.stop_count == 0 {
729 2
730 } else {
731 self.stop_count as usize
732 };
733 write!(
734 f,
735 "RadialGradient(center={:?}, radius={:?}, {:?})",
736 self.center,
737 self.radius,
738 &self.colors[..count]
739 )
740 }
741 BackgroundTag::ConicGradient => {
742 let count = if self.stop_count == 0 {
743 2
744 } else {
745 self.stop_count as usize
746 };
747 write!(
748 f,
749 "ConicGradient(center={:?}, angle={}, {:?})",
750 self.center,
751 self.gradient_angle_or_pattern_height,
752 &self.colors[..count]
753 )
754 }
755 }
756 }
757}
758
759impl Eq for Background {}
760impl Default for Background {
761 fn default() -> Self {
762 Self {
763 tag: BackgroundTag::Solid,
764 solid: Hsla::default(),
765 color_space: ColorSpace::default(),
766 gradient_angle_or_pattern_height: 0.0,
767 colors: [LinearColorStop::default(); 4],
768 stop_count: 0,
769 center: [0.5, 0.5],
770 radius: [0.5, 0.5],
771 }
772 }
773}
774
775pub fn pattern_slash(color: Hsla, width: f32, interval: f32) -> Background {
777 let width_scaled = (width * 255.0) as u32;
778 let interval_scaled = (interval * 255.0) as u32;
779 let height = ((width_scaled * 0xFFFF) + interval_scaled) as f32;
780
781 Background {
782 tag: BackgroundTag::PatternSlash,
783 solid: color,
784 gradient_angle_or_pattern_height: height,
785 ..Default::default()
786 }
787}
788
789pub fn solid_background(color: impl Into<Hsla>) -> Background {
791 Background {
792 solid: color.into(),
793 ..Default::default()
794 }
795}
796
797pub fn linear_gradient(
805 angle: f32,
806 from: impl Into<LinearColorStop>,
807 to: impl Into<LinearColorStop>,
808) -> Background {
809 let mut colors = [LinearColorStop::default(); 4];
810 colors[0] = from.into();
811 colors[1] = to.into();
812 Background {
813 tag: BackgroundTag::LinearGradient,
814 gradient_angle_or_pattern_height: angle,
815 colors,
816 stop_count: 2,
817 ..Default::default()
818 }
819}
820
821pub fn multi_stop_linear_gradient(angle: f32, stops: &[LinearColorStop]) -> Background {
823 let mut colors = [LinearColorStop::default(); 4];
824 let count = stops.len().min(4);
825 colors[..count].copy_from_slice(&stops[..count]);
826 Background {
827 tag: BackgroundTag::LinearGradient,
828 gradient_angle_or_pattern_height: angle,
829 colors,
830 stop_count: count as u32,
831 ..Default::default()
832 }
833}
834
835pub fn radial_gradient(
841 center_x: f32,
842 center_y: f32,
843 radius: f32,
844 stops: &[LinearColorStop],
845) -> Background {
846 let mut colors = [LinearColorStop::default(); 4];
847 let count = stops.len().min(4);
848 colors[..count].copy_from_slice(&stops[..count]);
849 Background {
850 tag: BackgroundTag::RadialGradient,
851 colors,
852 stop_count: count as u32,
853 center: [center_x, center_y],
854 radius: [radius, radius],
855 ..Default::default()
856 }
857}
858
859pub fn conic_gradient(
865 center_x: f32,
866 center_y: f32,
867 angle_offset: f32,
868 stops: &[LinearColorStop],
869) -> Background {
870 let mut colors = [LinearColorStop::default(); 4];
871 let count = stops.len().min(4);
872 colors[..count].copy_from_slice(&stops[..count]);
873 Background {
874 tag: BackgroundTag::ConicGradient,
875 gradient_angle_or_pattern_height: angle_offset,
876 colors,
877 stop_count: count as u32,
878 center: [center_x, center_y],
879 ..Default::default()
880 }
881}
882
883#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
887#[repr(C)]
888pub struct LinearColorStop {
889 pub color: Hsla,
891 pub percentage: f32,
893}
894
895pub fn linear_color_stop(color: impl Into<Hsla>, percentage: f32) -> LinearColorStop {
899 LinearColorStop {
900 color: color.into(),
901 percentage,
902 }
903}
904
905impl LinearColorStop {
906 pub fn opacity(&self, factor: f32) -> Self {
908 Self {
909 percentage: self.percentage,
910 color: self.color.opacity(factor),
911 }
912 }
913}
914
915impl Background {
916 pub fn color_space(mut self, color_space: ColorSpace) -> Self {
920 self.color_space = color_space;
921 self
922 }
923
924 pub fn opacity(&self, factor: f32) -> Self {
926 let mut background = *self;
927 background.solid = background.solid.opacity(factor);
928 background.colors = [
929 self.colors[0].opacity(factor),
930 self.colors[1].opacity(factor),
931 self.colors[2].opacity(factor),
932 self.colors[3].opacity(factor),
933 ];
934 background
935 }
936
937 pub fn is_transparent(&self) -> bool {
939 match self.tag {
940 BackgroundTag::Solid => self.solid.is_transparent(),
941 BackgroundTag::LinearGradient
942 | BackgroundTag::RadialGradient
943 | BackgroundTag::ConicGradient => {
944 let count = if self.stop_count == 0 {
945 2
946 } else {
947 self.stop_count as usize
948 };
949 self.colors[..count]
950 .iter()
951 .all(|c| c.color.is_transparent())
952 }
953 BackgroundTag::PatternSlash => self.solid.is_transparent(),
954 }
955 }
956}
957
958impl From<Hsla> for Background {
959 fn from(value: Hsla) -> Self {
960 Background {
961 tag: BackgroundTag::Solid,
962 solid: value,
963 ..Default::default()
964 }
965 }
966}
967impl From<Rgba> for Background {
968 fn from(value: Rgba) -> Self {
969 Background {
970 tag: BackgroundTag::Solid,
971 solid: Hsla::from(value),
972 ..Default::default()
973 }
974 }
975}
976
977#[cfg(test)]
978mod tests {
979 use serde_json::json;
980
981 use super::*;
982
983 #[test]
984 fn test_deserialize_three_value_hex_to_rgba() {
985 let actual: Rgba = serde_json::from_value(json!("#f09")).unwrap();
986
987 assert_eq!(actual, rgba(0xff0099ff))
988 }
989
990 #[test]
991 fn test_deserialize_four_value_hex_to_rgba() {
992 let actual: Rgba = serde_json::from_value(json!("#f09f")).unwrap();
993
994 assert_eq!(actual, rgba(0xff0099ff))
995 }
996
997 #[test]
998 fn test_deserialize_six_value_hex_to_rgba() {
999 let actual: Rgba = serde_json::from_value(json!("#ff0099")).unwrap();
1000
1001 assert_eq!(actual, rgba(0xff0099ff))
1002 }
1003
1004 #[test]
1005 fn test_deserialize_eight_value_hex_to_rgba() {
1006 let actual: Rgba = serde_json::from_value(json!("#ff0099ff")).unwrap();
1007
1008 assert_eq!(actual, rgba(0xff0099ff))
1009 }
1010
1011 #[test]
1012 fn test_deserialize_eight_value_hex_with_padding_to_rgba() {
1013 let actual: Rgba = serde_json::from_value(json!(" #f5f5f5ff ")).unwrap();
1014
1015 assert_eq!(actual, rgba(0xf5f5f5ff))
1016 }
1017
1018 #[test]
1019 fn test_deserialize_eight_value_hex_with_mixed_case_to_rgba() {
1020 let actual: Rgba = serde_json::from_value(json!("#DeAdbEeF")).unwrap();
1021
1022 assert_eq!(actual, rgba(0xdeadbeef))
1023 }
1024
1025 #[test]
1026 fn test_background_solid() {
1027 let color = Hsla::from(rgba(0xff0099ff));
1028 let mut background = Background::from(color);
1029 assert_eq!(background.tag, BackgroundTag::Solid);
1030 assert_eq!(background.solid, color);
1031
1032 assert_eq!(background.opacity(0.5).solid, color.opacity(0.5));
1033 assert!(!background.is_transparent());
1034 background.solid = hsla(0.0, 0.0, 0.0, 0.0);
1035 assert!(background.is_transparent());
1036 }
1037
1038 #[test]
1039 fn test_background_linear_gradient() {
1040 let from = linear_color_stop(rgba(0xff0099ff), 0.0);
1041 let to = linear_color_stop(rgba(0x00ff99ff), 1.0);
1042 let background = linear_gradient(90.0, from, to);
1043 assert_eq!(background.tag, BackgroundTag::LinearGradient);
1044 assert_eq!(background.colors[0], from);
1045 assert_eq!(background.colors[1], to);
1046
1047 assert_eq!(background.opacity(0.5).colors[0], from.opacity(0.5));
1048 assert_eq!(background.opacity(0.5).colors[1], to.opacity(0.5));
1049 assert!(!background.is_transparent());
1050 assert!(background.opacity(0.0).is_transparent());
1051 }
1052}