1use std::{fmt::Display, str::FromStr};
7
8use crate::Color32;
9
10#[repr(C)]
11#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
12#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
13pub enum HexColor {
17 Hex3(Color32),
19
20 Hex4(Color32),
22
23 Hex6(Color32),
25
26 Hex8(Color32),
28}
29
30#[derive(Clone, Debug, Eq, PartialEq)]
31pub enum ParseHexColorError {
32 MissingHash,
33 InvalidLength,
34 InvalidInt(std::num::ParseIntError),
35}
36
37impl FromStr for HexColor {
38 type Err = ParseHexColorError;
39
40 fn from_str(s: &str) -> Result<Self, Self::Err> {
41 s.strip_prefix('#')
42 .ok_or(ParseHexColorError::MissingHash)
43 .and_then(Self::from_str_without_hash)
44 }
45}
46
47impl Display for HexColor {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 match self {
50 Self::Hex3(color) => {
51 let [r, g, b, _] = color.to_srgba_unmultiplied().map(|u| u >> 4);
52 f.write_fmt(format_args!("#{r:x}{g:x}{b:x}"))
53 }
54 Self::Hex4(color) => {
55 let [r, g, b, a] = color.to_srgba_unmultiplied().map(|u| u >> 4);
56 f.write_fmt(format_args!("#{r:x}{g:x}{b:x}{a:x}"))
57 }
58 Self::Hex6(color) => {
59 let [r, g, b, _] = color.to_srgba_unmultiplied();
60 let u = u32::from_be_bytes([0, r, g, b]);
61 f.write_fmt(format_args!("#{u:06x}"))
62 }
63 Self::Hex8(color) => {
64 let [r, g, b, a] = color.to_srgba_unmultiplied();
65 let u = u32::from_be_bytes([r, g, b, a]);
66 f.write_fmt(format_args!("#{u:08x}"))
67 }
68 }
69 }
70}
71
72impl HexColor {
73 #[inline]
75 pub fn color(&self) -> Color32 {
76 match self {
77 Self::Hex3(color) | Self::Hex4(color) | Self::Hex6(color) | Self::Hex8(color) => *color,
78 }
79 }
80
81 #[inline]
87 pub fn from_str_without_hash(s: &str) -> Result<Self, ParseHexColorError> {
88 match s.len() {
89 3 => {
90 let [r, gb] = u16::from_str_radix(s, 16)
91 .map_err(ParseHexColorError::InvalidInt)?
92 .to_be_bytes();
93 let [r, g, b] = [r, gb >> 4, gb & 0x0f].map(|u| (u << 4) | u);
94 Ok(Self::Hex3(Color32::from_rgb(r, g, b)))
95 }
96 4 => {
97 let [r_g, b_a] = u16::from_str_radix(s, 16)
98 .map_err(ParseHexColorError::InvalidInt)?
99 .to_be_bytes();
100 let [r, g, b, a] =
101 [r_g >> 4, r_g & 0x0f, b_a >> 4, b_a & 0x0f].map(|u| (u << 4) | u);
102 Ok(Self::Hex4(Color32::from_rgba_unmultiplied(r, g, b, a)))
103 }
104 6 => {
105 let [_, r, g, b] = u32::from_str_radix(s, 16)
106 .map_err(ParseHexColorError::InvalidInt)?
107 .to_be_bytes();
108 Ok(Self::Hex6(Color32::from_rgb(r, g, b)))
109 }
110 8 => {
111 let [r, g, b, a] = u32::from_str_radix(s, 16)
112 .map_err(ParseHexColorError::InvalidInt)?
113 .to_be_bytes();
114 Ok(Self::Hex8(Color32::from_rgba_unmultiplied(r, g, b, a)))
115 }
116 _ => Err(ParseHexColorError::InvalidLength)?,
117 }
118 }
119}
120
121impl Color32 {
122 pub fn from_hex(hex: &str) -> Result<Self, ParseHexColorError> {
144 HexColor::from_str(hex).map(|h| h.color())
145 }
146
147 #[inline]
162 pub fn to_hex(&self) -> String {
163 HexColor::Hex8(*self).to_string()
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn hex_string_formats() {
173 use Color32 as C;
174 use HexColor as H;
175 let cases = [
176 (H::Hex3(C::RED), "#f00"),
177 (H::Hex4(C::RED), "#f00f"),
178 (H::Hex6(C::RED), "#ff0000"),
179 (H::Hex8(C::RED), "#ff0000ff"),
180 (H::Hex3(C::GREEN), "#0f0"),
181 (H::Hex4(C::GREEN), "#0f0f"),
182 (H::Hex6(C::GREEN), "#00ff00"),
183 (H::Hex8(C::GREEN), "#00ff00ff"),
184 (H::Hex3(C::BLUE), "#00f"),
185 (H::Hex4(C::BLUE), "#00ff"),
186 (H::Hex6(C::BLUE), "#0000ff"),
187 (H::Hex8(C::BLUE), "#0000ffff"),
188 (H::Hex3(C::WHITE), "#fff"),
189 (H::Hex4(C::WHITE), "#ffff"),
190 (H::Hex6(C::WHITE), "#ffffff"),
191 (H::Hex8(C::WHITE), "#ffffffff"),
192 (H::Hex3(C::BLACK), "#000"),
193 (H::Hex4(C::BLACK), "#000f"),
194 (H::Hex6(C::BLACK), "#000000"),
195 (H::Hex8(C::BLACK), "#000000ff"),
196 (H::Hex4(C::TRANSPARENT), "#0000"),
197 (H::Hex8(C::TRANSPARENT), "#00000000"),
198 ];
199 for (color, string) in cases {
200 assert_eq!(color.to_string(), string, "{color:?} <=> {string}");
201 assert_eq!(
202 H::from_str(string).unwrap(),
203 color,
204 "{color:?} <=> {string}"
205 );
206 }
207 }
208
209 #[test]
210 fn hex_string_round_trip() {
211 let cases = [
212 [0, 20, 30, 0],
213 [10, 0, 30, 40],
214 [10, 100, 200, 0],
215 [10, 100, 200, 100],
216 [10, 100, 200, 200],
217 [10, 100, 200, 255],
218 [10, 100, 200, 40],
219 [10, 20, 0, 255],
220 [10, 20, 30, 0],
221 [10, 20, 30, 255],
222 [10, 20, 30, 40],
223 ];
224 for [r, g, b, a] in cases {
225 let color = Color32::from_rgba_unmultiplied(r, g, b, a);
226 assert_eq!(Color32::from_hex(color.to_hex().as_str()), Ok(color));
227 }
228 }
229}