@@ -108,6 +108,53 @@ impl Color {
108108 }
109109 }
110110
111+ /// Parses a [`Color`] from a hex string.
112+ ///
113+ /// Supported formats are `#rrggbb`, `#rrggbbaa`, `#rgb`, and `#rgba`.
114+ /// The starting "#" is optional. Both uppercase and lowercase are supported.
115+ ///
116+ /// If you have a static color string, using the [`color!`] macro should be preferred
117+ /// since it leverages hexadecimal literal notation and arithmetic directly.
118+ ///
119+ /// [`color!`]: crate::color!
120+ pub fn parse ( s : & str ) -> Option < Color > {
121+ let hex = s. strip_prefix ( '#' ) . unwrap_or ( s) ;
122+
123+ let parse_channel = |from : usize , to : usize | {
124+ let num =
125+ usize:: from_str_radix ( & hex[ from..=to] , 16 ) . ok ( ) ? as f32 / 255.0 ;
126+
127+ // If we only got half a byte (one letter), expand it into a full byte (two letters)
128+ Some ( if from == to { num + num * 16.0 } else { num } )
129+ } ;
130+
131+ Some ( match hex. len ( ) {
132+ 3 => Color :: from_rgb (
133+ parse_channel ( 0 , 0 ) ?,
134+ parse_channel ( 1 , 1 ) ?,
135+ parse_channel ( 2 , 2 ) ?,
136+ ) ,
137+ 4 => Color :: from_rgba (
138+ parse_channel ( 0 , 0 ) ?,
139+ parse_channel ( 1 , 1 ) ?,
140+ parse_channel ( 2 , 2 ) ?,
141+ parse_channel ( 3 , 3 ) ?,
142+ ) ,
143+ 6 => Color :: from_rgb (
144+ parse_channel ( 0 , 1 ) ?,
145+ parse_channel ( 2 , 3 ) ?,
146+ parse_channel ( 4 , 5 ) ?,
147+ ) ,
148+ 8 => Color :: from_rgba (
149+ parse_channel ( 0 , 1 ) ?,
150+ parse_channel ( 2 , 3 ) ?,
151+ parse_channel ( 4 , 5 ) ?,
152+ parse_channel ( 6 , 7 ) ?,
153+ ) ,
154+ _ => None ?,
155+ } )
156+ }
157+
111158 /// Converts the [`Color`] into its RGBA8 equivalent.
112159 #[ must_use]
113160 pub fn into_rgba8 ( self ) -> [ u8 ; 4 ] {
@@ -178,34 +225,65 @@ impl From<[f32; 4]> for Color {
178225///
179226/// ```
180227/// # use iced_core::{Color, color};
181- /// assert_eq!(color!(0, 0, 0), Color::from_rgb(0., 0., 0.));
182- /// assert_eq!(color!(0, 0, 0, 0.), Color::from_rgba(0., 0., 0., 0.));
183- /// assert_eq!(color!(0xffffff), Color::from_rgb(1., 1., 1.));
184- /// assert_eq!(color!(0xffffff, 0.), Color::from_rgba(1., 1., 1., 0.));
228+ /// assert_eq!(color!(0, 0, 0), Color::BLACK);
229+ /// assert_eq!(color!(0, 0, 0, 0.0), Color::TRANSPARENT);
230+ /// assert_eq!(color!(0xffffff), Color::from_rgb(1.0, 1.0, 1.0));
231+ /// assert_eq!(color!(0xffffff, 0.), Color::from_rgba(1.0, 1.0, 1.0, 0.0));
232+ /// assert_eq!(color!(0x123), Color::from_rgba8(0x11, 0x22, 0x33, 1.0));
233+ /// assert_eq!(color!(0x123), color!(0x112233));
185234/// ```
186235#[ macro_export]
187236macro_rules! color {
188237 ( $r: expr, $g: expr, $b: expr) => {
189238 color!( $r, $g, $b, 1.0 )
190239 } ;
191- ( $r: expr, $g: expr, $b: expr, $a: expr) => {
192- $crate:: Color {
193- r: $r as f32 / 255.0 ,
194- g: $g as f32 / 255.0 ,
195- b: $b as f32 / 255.0 ,
196- a: $a,
240+ ( $r: expr, $g: expr, $b: expr, $a: expr) => { {
241+ let r = $r as f32 / 255.0 ;
242+ let g = $g as f32 / 255.0 ;
243+ let b = $b as f32 / 255.0 ;
244+
245+ #[ allow( clippy:: manual_range_contains) ]
246+ {
247+ debug_assert!(
248+ r >= 0.0 && r <= 1.0 ,
249+ "R channel must be in [0, 255] range."
250+ ) ;
251+ debug_assert!(
252+ g >= 0.0 && g <= 1.0 ,
253+ "G channel must be in [0, 255] range."
254+ ) ;
255+ debug_assert!(
256+ b >= 0.0 && b <= 1.0 ,
257+ "B channel must be in [0, 255] range."
258+ ) ;
197259 }
198- } ;
260+
261+ $crate:: Color { r, g, b, a: $a }
262+ } } ;
199263 ( $hex: expr) => { {
200264 color!( $hex, 1.0 )
201265 } } ;
202266 ( $hex: expr, $a: expr) => { {
203267 let hex = $hex as u32 ;
204- let r = ( hex & 0xff0000 ) >> 16 ;
205- let g = ( hex & 0xff00 ) >> 8 ;
206- let b = ( hex & 0xff ) ;
207268
208- color!( r, g, b, $a)
269+ if hex <= 0xfff {
270+ let r = ( hex & 0xf00 ) >> 8 ;
271+ let g = ( hex & 0x0f0 ) >> 4 ;
272+ let b = ( hex & 0x00f ) ;
273+
274+ color!( ( r << 4 | r) , ( g << 4 | g) , ( b << 4 | b) , $a)
275+ } else {
276+ debug_assert!(
277+ hex <= 0xffffff ,
278+ "color! value must not exceed 0xffffff"
279+ ) ;
280+
281+ let r = ( hex & 0xff0000 ) >> 16 ;
282+ let g = ( hex & 0xff00 ) >> 8 ;
283+ let b = ( hex & 0xff ) ;
284+
285+ color!( r, g, b, $a)
286+ }
209287 } } ;
210288}
211289
@@ -273,4 +351,23 @@ mod tests {
273351 assert_relative_eq ! ( result. b, 0.3 ) ;
274352 assert_relative_eq ! ( result. a, 1.0 ) ;
275353 }
354+
355+ #[ test]
356+ fn parse ( ) {
357+ let tests = [
358+ ( "#ff0000" , [ 255 , 0 , 0 , 255 ] ) ,
359+ ( "00ff0080" , [ 0 , 255 , 0 , 128 ] ) ,
360+ ( "#F80" , [ 255 , 136 , 0 , 255 ] ) ,
361+ ( "#00f1" , [ 0 , 0 , 255 , 17 ] ) ,
362+ ] ;
363+
364+ for ( arg, expected) in tests {
365+ assert_eq ! (
366+ Color :: parse( arg) . expect( "color must parse" ) . into_rgba8( ) ,
367+ expected
368+ ) ;
369+ }
370+
371+ assert ! ( Color :: parse( "invalid" ) . is_none( ) ) ;
372+ }
276373}
0 commit comments