1use anyhow::{Context as _, bail};
2use schemars::{JsonSchema, json_schema};
3use serde::{
4 Deserialize, Deserializer, Serialize, Serializer,
5 de::{self, Visitor},
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 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 pub fn alpha(&self, a: f32) -> Self {
96 Rgba {
97 r: self.r,
98 g: self.g,
99 b: self.b,
100 a: a.clamp(0., 1.),
101 }
102 }
103
104 pub fn opacity(&self, factor: f32) -> Self {
127 Rgba {
128 r: self.r,
129 g: self.g,
130 b: self.b,
131 a: self.a * factor.clamp(0., 1.),
132 }
133 }
134}
135
136impl From<Rgba> for u32 {
137 fn from(rgba: Rgba) -> Self {
138 let r = (rgba.r * 255.0) as u32;
139 let g = (rgba.g * 255.0) as u32;
140 let b = (rgba.b * 255.0) as u32;
141 let a = (rgba.a * 255.0) as u32;
142 (r << 24) | (g << 16) | (b << 8) | a
143 }
144}
145
146struct RgbaVisitor;
147
148impl Visitor<'_> for RgbaVisitor {
149 type Value = Rgba;
150
151 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
152 formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
153 }
154
155 fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
156 Rgba::try_from(value).map_err(E::custom)
157 }
158}
159
160impl JsonSchema for Rgba {
161 fn schema_name() -> Cow<'static, str> {
162 "Rgba".into()
163 }
164
165 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
166 json_schema!({
167 "type": "string",
168 "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
169 })
170 }
171}
172
173impl<'de> Deserialize<'de> for Rgba {
174 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
175 deserializer.deserialize_str(RgbaVisitor)
176 }
177}
178
179impl Serialize for Rgba {
180 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
181 where
182 S: Serializer,
183 {
184 let r = (self.r * 255.0).round() as u8;
185 let g = (self.g * 255.0).round() as u8;
186 let b = (self.b * 255.0).round() as u8;
187 let a = (self.a * 255.0).round() as u8;
188
189 let s = format!("#{r:02x}{g:02x}{b:02x}{a:02x}");
190 serializer.serialize_str(&s)
191 }
192}
193
194impl From<Hsla> for Rgba {
195 fn from(color: Hsla) -> Self {
196 let h = color.h;
197 let s = color.s;
198 let l = color.l;
199
200 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
201 let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
202 let m = l - c / 2.0;
203 let cm = c + m;
204 let xm = x + m;
205
206 let (r, g, b) = match (h * 6.0).floor() as i32 {
207 0 | 6 => (cm, xm, m),
208 1 => (xm, cm, m),
209 2 => (m, cm, xm),
210 3 => (m, xm, cm),
211 4 => (xm, m, cm),
212 _ => (cm, m, xm),
213 };
214
215 Rgba {
216 r: r.clamp(0., 1.),
217 g: g.clamp(0., 1.),
218 b: b.clamp(0., 1.),
219 a: color.a,
220 }
221 }
222}
223
224impl TryFrom<&'_ str> for Rgba {
225 type Error = anyhow::Error;
226
227 fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
228 const RGB: usize = "rgb".len();
229 const RGBA: usize = "rgba".len();
230 const RRGGBB: usize = "rrggbb".len();
231 const RRGGBBAA: usize = "rrggbbaa".len();
232
233 const EXPECTED_FORMATS: &str = "Expected #rgb, #rgba, #rrggbb, or #rrggbbaa";
234 const INVALID_UNICODE: &str = "invalid unicode characters in color";
235
236 let Some(("", hex)) = value.trim().split_once('#') else {
237 bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}");
238 };
239
240 let (r, g, b, a) = match hex.len() {
241 RGB | RGBA => {
242 let r = u8::from_str_radix(
243 hex.get(0..1).with_context(|| {
244 format!("{INVALID_UNICODE}: r component of #rgb/#rgba for value: '{value}'")
245 })?,
246 16,
247 )?;
248 let g = u8::from_str_radix(
249 hex.get(1..2).with_context(|| {
250 format!("{INVALID_UNICODE}: g component of #rgb/#rgba for value: '{value}'")
251 })?,
252 16,
253 )?;
254 let b = u8::from_str_radix(
255 hex.get(2..3).with_context(|| {
256 format!("{INVALID_UNICODE}: b component of #rgb/#rgba for value: '{value}'")
257 })?,
258 16,
259 )?;
260 let a = if hex.len() == RGBA {
261 u8::from_str_radix(
262 hex.get(3..4).with_context(|| {
263 format!("{INVALID_UNICODE}: a component of #rgba for value: '{value}'")
264 })?,
265 16,
266 )?
267 } else {
268 0xf
269 };
270
271 const fn duplicate(value: u8) -> u8 {
274 (value << 4) | value
275 }
276
277 (duplicate(r), duplicate(g), duplicate(b), duplicate(a))
278 }
279 RRGGBB | RRGGBBAA => {
280 let r = u8::from_str_radix(
281 hex.get(0..2).with_context(|| {
282 format!(
283 "{}: r component of #rrggbb/#rrggbbaa for value: '{}'",
284 INVALID_UNICODE, value
285 )
286 })?,
287 16,
288 )?;
289 let g = u8::from_str_radix(
290 hex.get(2..4).with_context(|| {
291 format!(
292 "{INVALID_UNICODE}: g component of #rrggbb/#rrggbbaa for value: '{value}'"
293 )
294 })?,
295 16,
296 )?;
297 let b = u8::from_str_radix(
298 hex.get(4..6).with_context(|| {
299 format!(
300 "{INVALID_UNICODE}: b component of #rrggbb/#rrggbbaa for value: '{value}'"
301 )
302 })?,
303 16,
304 )?;
305 let a = if hex.len() == RRGGBBAA {
306 u8::from_str_radix(
307 hex.get(6..8).with_context(|| {
308 format!(
309 "{INVALID_UNICODE}: a component of #rrggbbaa for value: '{value}'"
310 )
311 })?,
312 16,
313 )?
314 } else {
315 0xff
316 };
317 (r, g, b, a)
318 }
319 _ => bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}"),
320 };
321
322 Ok(Rgba {
323 r: r as f32 / 255.,
324 g: g as f32 / 255.,
325 b: b as f32 / 255.,
326 a: a as f32 / 255.,
327 })
328 }
329}
330
331#[derive(Default, Copy, Clone, Debug)]
333#[repr(C)]
334pub struct Hsla {
335 pub h: f32,
337
338 pub s: f32,
340
341 pub l: f32,
343
344 pub a: f32,
346}
347
348#[cfg(feature = "proptest")]
349mod property {
350 use super::Hsla;
351 use proptest::prelude::*;
352
353 impl Hsla {
354 pub fn opaque_strategy() -> impl Strategy<Value = Self> {
358 (0.0f32..=1.0, 0.0f32..=1.0, 0.0f32..=1.0).prop_map(|(h, s, l)| Hsla { h, s, l, a: 1. })
359 }
360 }
361
362 impl Arbitrary for Hsla {
363 type Strategy = BoxedStrategy<Self>;
364 type Parameters = ();
365
366 fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
367 (0.0f32..=1.0, 0.0f32..=1.0, 0.0f32..=1.0, 0.0f32..=1.0)
368 .prop_map(|(h, s, l, a)| Hsla { h, s, l, a })
369 .boxed()
370 }
371 }
372}
373
374impl PartialEq for Hsla {
375 fn eq(&self, other: &Self) -> bool {
376 self.h
377 .total_cmp(&other.h)
378 .then(self.s.total_cmp(&other.s))
379 .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
380 .is_eq()
381 }
382}
383
384impl PartialOrd for Hsla {
385 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
386 Some(self.cmp(other))
387 }
388}
389
390impl Ord for Hsla {
391 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
392 self.h
393 .total_cmp(&other.h)
394 .then(self.s.total_cmp(&other.s))
395 .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
396 }
397}
398
399impl Eq for Hsla {}
400
401impl Hash for Hsla {
402 fn hash<H: Hasher>(&self, state: &mut H) {
403 state.write_u32(u32::from_be_bytes(self.h.to_be_bytes()));
404 state.write_u32(u32::from_be_bytes(self.s.to_be_bytes()));
405 state.write_u32(u32::from_be_bytes(self.l.to_be_bytes()));
406 state.write_u32(u32::from_be_bytes(self.a.to_be_bytes()));
407 }
408}
409
410impl Display for Hsla {
411 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
412 write!(
413 f,
414 "hsla({:.2}, {:.2}%, {:.2}%, {:.2})",
415 self.h * 360.,
416 self.s * 100.,
417 self.l * 100.,
418 self.a
419 )
420 }
421}
422
423pub const fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
425 Hsla {
426 h: h.clamp(0., 1.),
427 s: s.clamp(0., 1.),
428 l: l.clamp(0., 1.),
429 a: a.clamp(0., 1.),
430 }
431}
432
433pub const fn black() -> Hsla {
435 Hsla {
436 h: 0.,
437 s: 0.,
438 l: 0.,
439 a: 1.,
440 }
441}
442
443pub const fn transparent_black() -> Hsla {
445 Hsla {
446 h: 0.,
447 s: 0.,
448 l: 0.,
449 a: 0.,
450 }
451}
452
453pub const fn transparent_white() -> Hsla {
455 Hsla {
456 h: 0.,
457 s: 0.,
458 l: 1.,
459 a: 0.,
460 }
461}
462
463pub const fn opaque_grey(lightness: f32, opacity: f32) -> Hsla {
465 Hsla {
466 h: 0.,
467 s: 0.,
468 l: lightness.clamp(0., 1.),
469 a: opacity.clamp(0., 1.),
470 }
471}
472
473pub const fn white() -> Hsla {
475 Hsla {
476 h: 0.,
477 s: 0.,
478 l: 1.,
479 a: 1.,
480 }
481}
482
483pub const fn red() -> Hsla {
485 Hsla {
486 h: 0.,
487 s: 1.,
488 l: 0.5,
489 a: 1.,
490 }
491}
492
493pub const fn blue() -> Hsla {
495 Hsla {
496 h: 0.6666666667,
497 s: 1.,
498 l: 0.5,
499 a: 1.,
500 }
501}
502
503pub const fn green() -> Hsla {
505 Hsla {
506 h: 0.3333333333,
507 s: 1.,
508 l: 0.25,
509 a: 1.,
510 }
511}
512
513pub const fn yellow() -> Hsla {
515 Hsla {
516 h: 0.1666666667,
517 s: 1.,
518 l: 0.5,
519 a: 1.,
520 }
521}
522
523impl Hsla {
524 pub fn to_rgb(self) -> Rgba {
526 self.into()
527 }
528
529 pub const fn red() -> Self {
531 red()
532 }
533
534 pub const fn green() -> Self {
536 green()
537 }
538
539 pub const fn blue() -> Self {
541 blue()
542 }
543
544 pub const fn black() -> Self {
546 black()
547 }
548
549 pub const fn white() -> Self {
551 white()
552 }
553
554 pub const fn transparent_black() -> Self {
556 transparent_black()
557 }
558
559 pub fn is_transparent(&self) -> bool {
561 self.a == 0.0
562 }
563
564 pub fn is_opaque(&self) -> bool {
566 self.a == 1.0
567 }
568
569 pub fn blend(self, other: Hsla) -> Hsla {
581 let alpha = other.a;
582
583 if alpha >= 1.0 {
584 other
585 } else if alpha <= 0.0 {
586 self
587 } else {
588 let converted_self = Rgba::from(self);
589 let converted_other = Rgba::from(other);
590 let blended_rgb = converted_self.blend(converted_other);
591 Hsla::from(blended_rgb)
592 }
593 }
594
595 pub fn grayscale(&self) -> Self {
597 Hsla {
598 h: self.h,
599 s: 0.,
600 l: self.l,
601 a: self.a,
602 }
603 }
604
605 pub fn fade_out(&mut self, factor: f32) {
608 self.a *= 1.0 - factor.clamp(0., 1.);
609 }
610
611 pub fn opacity(&self, factor: f32) -> Self {
638 Hsla {
639 h: self.h,
640 s: self.s,
641 l: self.l,
642 a: self.a * factor.clamp(0., 1.),
643 }
644 }
645
646 pub fn alpha(&self, a: f32) -> Self {
668 Hsla {
669 h: self.h,
670 s: self.s,
671 l: self.l,
672 a: a.clamp(0., 1.),
673 }
674 }
675}
676
677impl From<Rgba> for Hsla {
678 fn from(color: Rgba) -> Self {
679 let r = color.r;
680 let g = color.g;
681 let b = color.b;
682
683 let max = r.max(g.max(b));
684 let min = r.min(g.min(b));
685 let delta = max - min;
686
687 let l = (max + min) / 2.0;
688 let s = if l == 0.0 || l == 1.0 {
689 0.0
690 } else if l < 0.5 {
691 delta / (2.0 * l)
692 } else {
693 delta / (2.0 - 2.0 * l)
694 };
695
696 let h = if delta == 0.0 {
697 0.0
698 } else if max == r {
699 ((g - b) / delta).rem_euclid(6.0) / 6.0
700 } else if max == g {
701 ((b - r) / delta + 2.0) / 6.0
702 } else {
703 ((r - g) / delta + 4.0) / 6.0
704 };
705
706 Hsla {
707 h,
708 s,
709 l,
710 a: color.a,
711 }
712 }
713}
714
715impl JsonSchema for Hsla {
716 fn schema_name() -> Cow<'static, str> {
717 Rgba::schema_name()
718 }
719
720 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
721 Rgba::json_schema(generator)
722 }
723}
724
725impl Serialize for Hsla {
726 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
727 where
728 S: Serializer,
729 {
730 Rgba::from(*self).serialize(serializer)
731 }
732}
733
734impl<'de> Deserialize<'de> for Hsla {
735 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
736 where
737 D: Deserializer<'de>,
738 {
739 Ok(Rgba::deserialize(deserializer)?.into())
740 }
741}
742
743#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
744#[repr(C)]
745pub(crate) enum BackgroundTag {
746 Solid = 0,
747 LinearGradient = 1,
748 PatternSlash = 2,
749 Checkerboard = 3,
750}
751
752#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
758#[repr(C)]
759pub enum ColorSpace {
760 #[default]
761 Srgb = 0,
763 Oklab = 1,
765}
766
767impl Display for ColorSpace {
768 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
769 match self {
770 ColorSpace::Srgb => write!(f, "sRGB"),
771 ColorSpace::Oklab => write!(f, "Oklab"),
772 }
773 }
774}
775
776#[derive(Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
778#[repr(C)]
779pub struct Background {
780 pub(crate) tag: BackgroundTag,
781 pub(crate) color_space: ColorSpace,
782 pub(crate) solid: Hsla,
783 pub(crate) gradient_angle_or_pattern_height: f32,
784 pub(crate) colors: [LinearColorStop; 2],
785 pad: u32,
787}
788
789impl std::fmt::Debug for Background {
790 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
791 match self.tag {
792 BackgroundTag::Solid => write!(f, "Solid({:?})", self.solid),
793 BackgroundTag::LinearGradient => write!(
794 f,
795 "LinearGradient({}, {:?}, {:?})",
796 self.gradient_angle_or_pattern_height, self.colors[0], self.colors[1]
797 ),
798 BackgroundTag::PatternSlash => write!(
799 f,
800 "PatternSlash({:?}, {})",
801 self.solid, self.gradient_angle_or_pattern_height
802 ),
803 BackgroundTag::Checkerboard => write!(
804 f,
805 "Checkerboard({:?}, {})",
806 self.solid, self.gradient_angle_or_pattern_height
807 ),
808 }
809 }
810}
811
812impl Eq for Background {}
813impl Default for Background {
814 fn default() -> Self {
815 Self {
816 tag: BackgroundTag::Solid,
817 solid: Hsla::default(),
818 color_space: ColorSpace::default(),
819 gradient_angle_or_pattern_height: 0.0,
820 colors: [LinearColorStop::default(), LinearColorStop::default()],
821 pad: 0,
822 }
823 }
824}
825
826pub fn pattern_slash(color: impl Into<Hsla>, width: f32, interval: f32) -> Background {
828 let width_scaled = (width * 255.0) as u32;
829 let interval_scaled = (interval * 255.0) as u32;
830 let height = ((width_scaled * 0xFFFF) + interval_scaled) as f32;
831
832 Background {
833 tag: BackgroundTag::PatternSlash,
834 solid: color.into(),
835 gradient_angle_or_pattern_height: height,
836 ..Default::default()
837 }
838}
839
840pub fn checkerboard(color: impl Into<Hsla>, size: f32) -> Background {
842 Background {
843 tag: BackgroundTag::Checkerboard,
844 solid: color.into(),
845 gradient_angle_or_pattern_height: size,
846 ..Default::default()
847 }
848}
849
850pub fn solid_background(color: impl Into<Hsla>) -> Background {
852 Background {
853 solid: color.into(),
854 ..Default::default()
855 }
856}
857
858pub fn linear_gradient(
866 angle: f32,
867 from: impl Into<LinearColorStop>,
868 to: impl Into<LinearColorStop>,
869) -> Background {
870 Background {
871 tag: BackgroundTag::LinearGradient,
872 gradient_angle_or_pattern_height: angle,
873 colors: [from.into(), to.into()],
874 ..Default::default()
875 }
876}
877
878#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
882#[repr(C)]
883pub struct LinearColorStop {
884 pub color: Hsla,
886 pub percentage: f32,
888}
889
890pub fn linear_color_stop(color: impl Into<Hsla>, percentage: f32) -> LinearColorStop {
894 LinearColorStop {
895 color: color.into(),
896 percentage,
897 }
898}
899
900impl LinearColorStop {
901 pub fn opacity(&self, factor: f32) -> Self {
903 Self {
904 percentage: self.percentage,
905 color: self.color.opacity(factor),
906 }
907 }
908}
909
910impl Background {
911 pub fn as_solid(&self) -> Option<Hsla> {
913 if self.tag == BackgroundTag::Solid {
914 Some(self.solid)
915 } else {
916 None
917 }
918 }
919
920 pub fn color_space(mut self, color_space: ColorSpace) -> Self {
924 self.color_space = color_space;
925 self
926 }
927
928 pub fn opacity(&self, factor: f32) -> Self {
930 let mut background = *self;
931 background.solid = background.solid.opacity(factor);
932 background.colors = [
933 self.colors[0].opacity(factor),
934 self.colors[1].opacity(factor),
935 ];
936 background
937 }
938
939 pub fn is_transparent(&self) -> bool {
941 match self.tag {
942 BackgroundTag::Solid => self.solid.is_transparent(),
943 BackgroundTag::LinearGradient => self.colors.iter().all(|c| c.color.is_transparent()),
944 BackgroundTag::PatternSlash => self.solid.is_transparent(),
945 BackgroundTag::Checkerboard => self.solid.is_transparent(),
946 }
947 }
948}
949
950impl From<Hsla> for Background {
951 fn from(value: Hsla) -> Self {
952 Background {
953 tag: BackgroundTag::Solid,
954 solid: value,
955 ..Default::default()
956 }
957 }
958}
959
960impl From<Rgba> for Background {
961 fn from(value: Rgba) -> Self {
962 Background {
963 tag: BackgroundTag::Solid,
964 solid: Hsla::from(value),
965 ..Default::default()
966 }
967 }
968}
969
970#[cfg(test)]
971mod tests {
972 use serde_json::json;
973
974 use super::*;
975
976 #[test]
977 fn test_deserialize_three_value_hex_to_rgba() {
978 let actual: Rgba = serde_json::from_value(json!("#f09")).unwrap();
979
980 assert_eq!(actual, rgba(0xff0099ff))
981 }
982
983 #[test]
984 fn test_deserialize_four_value_hex_to_rgba() {
985 let actual: Rgba = serde_json::from_value(json!("#f09f")).unwrap();
986
987 assert_eq!(actual, rgba(0xff0099ff))
988 }
989
990 #[test]
991 fn test_deserialize_six_value_hex_to_rgba() {
992 let actual: Rgba = serde_json::from_value(json!("#ff0099")).unwrap();
993
994 assert_eq!(actual, rgba(0xff0099ff))
995 }
996
997 #[test]
998 fn test_deserialize_eight_value_hex_to_rgba() {
999 let actual: Rgba = serde_json::from_value(json!("#ff0099ff")).unwrap();
1000
1001 assert_eq!(actual, rgba(0xff0099ff))
1002 }
1003
1004 #[test]
1005 fn test_deserialize_eight_value_hex_with_padding_to_rgba() {
1006 let actual: Rgba = serde_json::from_value(json!(" #f5f5f5ff ")).unwrap();
1007
1008 assert_eq!(actual, rgba(0xf5f5f5ff))
1009 }
1010
1011 #[test]
1012 fn test_deserialize_eight_value_hex_with_mixed_case_to_rgba() {
1013 let actual: Rgba = serde_json::from_value(json!("#DeAdbEeF")).unwrap();
1014
1015 assert_eq!(actual, rgba(0xdeadbeef))
1016 }
1017
1018 #[test]
1019 fn test_background_solid() {
1020 let color = Hsla::from(rgba(0xff0099ff));
1021 let mut background = Background::from(color);
1022 assert_eq!(background.tag, BackgroundTag::Solid);
1023 assert_eq!(background.solid, color);
1024
1025 assert_eq!(background.opacity(0.5).solid, color.opacity(0.5));
1026 assert!(!background.is_transparent());
1027 background.solid = hsla(0.0, 0.0, 0.0, 0.0);
1028 assert!(background.is_transparent());
1029 }
1030
1031 #[test]
1032 fn test_background_linear_gradient() {
1033 let from = linear_color_stop(rgba(0xff0099ff), 0.0);
1034 let to = linear_color_stop(rgba(0x00ff99ff), 1.0);
1035 let background = linear_gradient(90.0, from, to);
1036 assert_eq!(background.tag, BackgroundTag::LinearGradient);
1037 assert_eq!(background.colors[0], from);
1038 assert_eq!(background.colors[1], to);
1039
1040 assert_eq!(background.opacity(0.5).colors[0], from.opacity(0.5));
1041 assert_eq!(background.opacity(0.5).colors[1], to.opacity(0.5));
1042 assert!(!background.is_transparent());
1043 assert!(background.opacity(0.0).is_transparent());
1044 }
1045
1046 #[test]
1047 fn gradient_dither_is_stable_at_screen_pixels() {
1048 fn lowbias32(mut value: u32) -> u32 {
1049 value ^= value >> 16;
1050 value = value.wrapping_mul(0x7feb_352d);
1051 value ^= value >> 15;
1052 value = value.wrapping_mul(0x846c_a68b);
1053 value ^= value >> 16;
1054 value
1055 }
1056
1057 let cases: [((u32, u32), u32, u32, u32, i64); 4] = [
1058 ((0, 0), 0x0000_0000, 0x86a2_4fa0, 0x7cc4_acaf, 646_563),
1059 ((1, 0), 0x9e37_79b9, 0x6441_0d95, 0x0bec_fece, 5_788_687),
1060 ((0, 1), 0x85eb_ca6b, 0xed53_cbbe, 0x48c1_24af, 10_785_447),
1061 (
1062 (919, 999),
1063 0x6207_1092,
1064 0x229b_548c,
1065 0xb4f8_4bec,
1066 -9_592_055,
1067 ),
1068 ];
1069
1070 for ((x, y), expected_seed, expected_h0, expected_h1, expected_difference) in cases {
1071 let seed = x.wrapping_mul(0x9e37_79b9) ^ y.wrapping_mul(0x85eb_ca6b);
1072 let h0 = lowbias32(seed ^ 0xa511_e9b3);
1073 let h1 = lowbias32(seed ^ 0x63d8_3595);
1074 let difference = i64::from(h0 >> 8) - i64::from(h1 >> 8);
1075
1076 assert_eq!(seed, expected_seed);
1077 assert_eq!(h0, expected_h0);
1078 assert_eq!(h1, expected_h1);
1079 assert_eq!(difference, expected_difference);
1080 }
1081 }
1082
1083 #[test]
1084 fn test_rgba_alpha() {
1085 let color = Rgba {
1086 r: 0.2,
1087 g: 0.6,
1088 b: 1.0,
1089 a: 0.8,
1090 };
1091
1092 assert_eq!(color.alpha(0.25).a, 0.25);
1093 assert_eq!(color.alpha(1.5).a, 1.0);
1094 }
1095
1096 #[test]
1097 fn test_rgba_opacity() {
1098 let color = Rgba {
1099 r: 0.2,
1100 g: 0.6,
1101 b: 1.0,
1102 a: 0.8,
1103 };
1104 assert!((color.opacity(0.5).a - 0.4).abs() < 1e-6);
1105 assert_eq!(color.opacity(2.0).a, 0.8);
1106 }
1107}