1
1
#![ allow( dead_code) ]
2
2
3
+ use anyhow:: bail;
3
4
use serde:: de:: { self , Deserialize , Deserializer , Visitor } ;
4
5
use std:: fmt;
5
- use std:: num:: ParseIntError ;
6
6
7
7
pub fn rgb < C : From < Rgba > > ( hex : u32 ) -> C {
8
8
let r = ( ( hex >> 16 ) & 0xFF ) as f32 / 255.0 ;
@@ -19,7 +19,7 @@ pub fn rgba(hex: u32) -> Rgba {
19
19
Rgba { r, g, b, a }
20
20
}
21
21
22
- #[ derive( Clone , Copy , Default ) ]
22
+ #[ derive( PartialEq , Clone , Copy , Default ) ]
23
23
pub struct Rgba {
24
24
pub r : f32 ,
25
25
pub g : f32 ,
@@ -70,21 +70,7 @@ impl<'de> Visitor<'de> for RgbaVisitor {
70
70
}
71
71
72
72
fn visit_str < E : de:: Error > ( self , value : & str ) -> Result < Rgba , E > {
73
- if value. len ( ) == 7 || value. len ( ) == 9 {
74
- let r = u8:: from_str_radix ( & value[ 1 ..3 ] , 16 ) . unwrap ( ) as f32 / 255.0 ;
75
- let g = u8:: from_str_radix ( & value[ 3 ..5 ] , 16 ) . unwrap ( ) as f32 / 255.0 ;
76
- let b = u8:: from_str_radix ( & value[ 5 ..7 ] , 16 ) . unwrap ( ) as f32 / 255.0 ;
77
- let a = if value. len ( ) == 9 {
78
- u8:: from_str_radix ( & value[ 7 ..9 ] , 16 ) . unwrap ( ) as f32 / 255.0
79
- } else {
80
- 1.0
81
- } ;
82
- Ok ( Rgba { r, g, b, a } )
83
- } else {
84
- Err ( E :: custom (
85
- "Bad format for RGBA. Expected #rrggbb or #rrggbbaa." ,
86
- ) )
87
- }
73
+ Rgba :: try_from ( value) . map_err ( E :: custom)
88
74
}
89
75
}
90
76
@@ -125,19 +111,59 @@ impl From<Hsla> for Rgba {
125
111
}
126
112
127
113
impl TryFrom < & ' _ str > for Rgba {
128
- type Error = ParseIntError ;
114
+ type Error = anyhow :: Error ;
129
115
130
116
fn try_from ( value : & ' _ str ) -> Result < Self , Self :: Error > {
131
- let r = u8:: from_str_radix ( & value[ 1 ..3 ] , 16 ) ? as f32 / 255.0 ;
132
- let g = u8:: from_str_radix ( & value[ 3 ..5 ] , 16 ) ? as f32 / 255.0 ;
133
- let b = u8:: from_str_radix ( & value[ 5 ..7 ] , 16 ) ? as f32 / 255.0 ;
134
- let a = if value. len ( ) > 7 {
135
- u8:: from_str_radix ( & value[ 7 ..9 ] , 16 ) ? as f32 / 255.0
136
- } else {
137
- 1.0
117
+ const RGB : usize = "rgb" . len ( ) ;
118
+ const RGBA : usize = "rgba" . len ( ) ;
119
+ const RRGGBB : usize = "rrggbb" . len ( ) ;
120
+ const RRGGBBAA : usize = "rrggbbaa" . len ( ) ;
121
+
122
+ const EXPECTED_FORMATS : & ' static str = "Expected #rgb, #rgba, #rrggbb, or #rrggbbaa" ;
123
+
124
+ let Some ( ( "" , hex) ) = value. trim ( ) . split_once ( '#' ) else {
125
+ bail ! ( "invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}" ) ;
126
+ } ;
127
+
128
+ let ( r, g, b, a) = match hex. len ( ) {
129
+ RGB | RGBA => {
130
+ let r = u8:: from_str_radix ( & hex[ 0 ..1 ] , 16 ) ?;
131
+ let g = u8:: from_str_radix ( & hex[ 1 ..2 ] , 16 ) ?;
132
+ let b = u8:: from_str_radix ( & hex[ 2 ..3 ] , 16 ) ?;
133
+ let a = if hex. len ( ) == RGBA {
134
+ u8:: from_str_radix ( & hex[ 3 ..4 ] , 16 ) ?
135
+ } else {
136
+ 0xf
137
+ } ;
138
+
139
+ /// Duplicates a given hex digit.
140
+ /// E.g., `0xf` -> `0xff`.
141
+ const fn duplicate ( value : u8 ) -> u8 {
142
+ value << 4 | value
143
+ }
144
+
145
+ ( duplicate ( r) , duplicate ( g) , duplicate ( b) , duplicate ( a) )
146
+ }
147
+ RRGGBB | RRGGBBAA => {
148
+ let r = u8:: from_str_radix ( & hex[ 0 ..2 ] , 16 ) ?;
149
+ let g = u8:: from_str_radix ( & hex[ 2 ..4 ] , 16 ) ?;
150
+ let b = u8:: from_str_radix ( & hex[ 4 ..6 ] , 16 ) ?;
151
+ let a = if hex. len ( ) == RRGGBBAA {
152
+ u8:: from_str_radix ( & hex[ 6 ..8 ] , 16 ) ?
153
+ } else {
154
+ 0xff
155
+ } ;
156
+ ( r, g, b, a)
157
+ }
158
+ _ => bail ! ( "invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}" ) ,
138
159
} ;
139
160
140
- Ok ( Rgba { r, g, b, a } )
161
+ Ok ( Rgba {
162
+ r : r as f32 / 255. ,
163
+ g : g as f32 / 255. ,
164
+ b : b as f32 / 255. ,
165
+ a : a as f32 / 255. ,
166
+ } )
141
167
}
142
168
}
143
169
@@ -311,3 +337,52 @@ impl<'de> Deserialize<'de> for Hsla {
311
337
Ok ( Hsla :: from ( rgba) )
312
338
}
313
339
}
340
+
341
+ #[ cfg( test) ]
342
+ mod tests {
343
+ use serde_json:: json;
344
+
345
+ use super :: * ;
346
+
347
+ #[ test]
348
+ fn test_deserialize_three_value_hex_to_rgba ( ) {
349
+ let actual: Rgba = serde_json:: from_value ( json ! ( "#f09" ) ) . unwrap ( ) ;
350
+
351
+ assert_eq ! ( actual, rgba( 0xff0099ff ) )
352
+ }
353
+
354
+ #[ test]
355
+ fn test_deserialize_four_value_hex_to_rgba ( ) {
356
+ let actual: Rgba = serde_json:: from_value ( json ! ( "#f09f" ) ) . unwrap ( ) ;
357
+
358
+ assert_eq ! ( actual, rgba( 0xff0099ff ) )
359
+ }
360
+
361
+ #[ test]
362
+ fn test_deserialize_six_value_hex_to_rgba ( ) {
363
+ let actual: Rgba = serde_json:: from_value ( json ! ( "#ff0099" ) ) . unwrap ( ) ;
364
+
365
+ assert_eq ! ( actual, rgba( 0xff0099ff ) )
366
+ }
367
+
368
+ #[ test]
369
+ fn test_deserialize_eight_value_hex_to_rgba ( ) {
370
+ let actual: Rgba = serde_json:: from_value ( json ! ( "#ff0099ff" ) ) . unwrap ( ) ;
371
+
372
+ assert_eq ! ( actual, rgba( 0xff0099ff ) )
373
+ }
374
+
375
+ #[ test]
376
+ fn test_deserialize_eight_value_hex_with_padding_to_rgba ( ) {
377
+ let actual: Rgba = serde_json:: from_value ( json ! ( " #f5f5f5ff " ) ) . unwrap ( ) ;
378
+
379
+ assert_eq ! ( actual, rgba( 0xf5f5f5ff ) )
380
+ }
381
+
382
+ #[ test]
383
+ fn test_deserialize_eight_value_hex_with_mixed_case_to_rgba ( ) {
384
+ let actual: Rgba = serde_json:: from_value ( json ! ( "#DeAdbEeF" ) ) . unwrap ( ) ;
385
+
386
+ assert_eq ! ( actual, rgba( 0xdeadbeef ) )
387
+ }
388
+ }
0 commit comments