Last active
March 20, 2023 17:15
-
-
Save frectonz/b508aeab09e1bd590ae2b8831bacc222 to your computer and use it in GitHub Desktop.
From cassido's March 13, 2023 Newsletter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::str::FromStr; | |
#[derive(Debug)] | |
struct Rgb { | |
r: u8, | |
g: u8, | |
b: u8, | |
} | |
impl FromStr for Rgb { | |
type Err = anyhow::Error; | |
fn from_str(s: &str) -> anyhow::Result<Self> { | |
if let Some(s) = s.strip_prefix('#') { | |
let r = u8::from_str_radix(&s[0..2], 16)?; | |
let g = u8::from_str_radix(&s[2..4], 16)?; | |
let b = u8::from_str_radix(&s[4..6], 16)?; | |
return Ok(Rgb { r, g, b }); | |
} | |
let s = s.trim().trim_start_matches('(').trim_end_matches(')'); | |
let mut s = s.split(','); | |
let r: u8 = s | |
.next() | |
.ok_or(anyhow::anyhow!("missing r"))? | |
.trim() | |
.parse()?; | |
let g: u8 = s | |
.next() | |
.ok_or(anyhow::anyhow!("missing g"))? | |
.trim() | |
.parse()?; | |
let b: u8 = s | |
.next() | |
.ok_or(anyhow::anyhow!("missing b"))? | |
.trim() | |
.parse()?; | |
Ok(Rgb { r, g, b }) | |
} | |
} | |
impl From<Hsl> for Rgb { | |
fn from(hsl: Hsl) -> Self { | |
let h = hsl.h / 360.0; | |
let s = hsl.s / 100.0; | |
let l = hsl.l / 100.0; | |
if s == 0.0 { | |
let l = (l * 255.0).round() as u8; | |
return Rgb { r: l, g: l, b: l }; | |
} | |
fn hue_to_rgb(p: f32, q: f32, t: f32) -> f32 { | |
let mut t = t; | |
if t < 0.0 { | |
t += 1.0; | |
} | |
if t > 1.0 { | |
t -= 1.0; | |
} | |
if t < 1.0 / 6.0 { | |
return p + (q - p) * 6.0 * t; | |
} | |
if t < 1.0 / 2.0 { | |
return q; | |
} | |
if t < 2.0 / 3.0 { | |
return p + (q - p) * (2.0 / 3.0 - t) * 6.0; | |
} | |
p | |
} | |
let q = if l < 0.5 { | |
l * (1.0 + s) | |
} else { | |
l + s - l * s | |
}; | |
let p = 2.0 * l - q; | |
let r = hue_to_rgb(p, q, h + 1.0 / 3.0); | |
let g = hue_to_rgb(p, q, h); | |
let b = hue_to_rgb(p, q, h - 1.0 / 3.0); | |
Rgb { | |
r: (r * 255.0).round() as u8, | |
g: (g * 255.0).round() as u8, | |
b: (b * 255.0).round() as u8, | |
} | |
} | |
} | |
#[derive(Debug)] | |
struct Hsl { | |
h: f32, | |
s: f32, | |
l: f32, | |
} | |
impl FromStr for Hsl { | |
type Err = anyhow::Error; | |
fn from_str(s: &str) -> anyhow::Result<Self> { | |
let str = s.trim().trim_start_matches('(').trim_end_matches(')'); | |
let mut str = str.split(','); | |
let h: f32 = str | |
.next() | |
.ok_or(anyhow::anyhow!("missing h"))? | |
.trim() | |
.parse()?; | |
let s: f32 = str | |
.next() | |
.ok_or(anyhow::anyhow!("missing s"))? | |
.trim() | |
.parse()?; | |
let l: f32 = str | |
.next() | |
.ok_or(anyhow::anyhow!("missing l"))? | |
.trim() | |
.parse()?; | |
Ok(Hsl { h, s, l }) | |
} | |
} | |
impl From<Rgb> for Hsl { | |
fn from(rgb: Rgb) -> Self { | |
let r = rgb.r as f32 / 255.0; | |
let g = rgb.g as f32 / 255.0; | |
let b = rgb.b as f32 / 255.0; | |
let max = r.max(g).max(b); | |
let min = r.min(g).min(b); | |
let l = (max + min) / 2.0; | |
if max == min { | |
return Hsl { h: 0.0, s: 0.0, l }; | |
} | |
let d = max - min; | |
let s = if l > 0.5 { | |
d / (2.0 - max - min) | |
} else { | |
d / (max + min) | |
}; | |
let h = if max == r { | |
(g - b) / d + if g < b { 6.0 } else { 0.0 } | |
} else if max == g { | |
((b - r) / d) + 2.0 | |
} else { | |
((r - g) / d) + 4.0 | |
}; | |
Hsl { h: h * 60.0, s, l } | |
} | |
} | |
#[derive(Debug, PartialEq)] | |
enum ColorType { | |
Rgb, | |
Hsl, | |
Hex, | |
} | |
fn convert_color(from: ColorType, to: ColorType, color: &str) -> anyhow::Result<String> { | |
match from { | |
ColorType::Rgb | ColorType::Hex => { | |
let color = color.parse::<Rgb>()?; | |
match to { | |
ColorType::Rgb => Ok(format!("RGB({},{},{})", color.r, color.g, color.b)), | |
ColorType::Hex => Ok(format!("#{:02x}{:02x}{:02x}", color.r, color.g, color.b)), | |
ColorType::Hsl => { | |
let color: Hsl = color.into(); | |
Ok(format!("HSL({},{},{})", color.h, color.s, color.l)) | |
} | |
} | |
} | |
ColorType::Hsl => { | |
let color = color.parse::<Hsl>()?; | |
match to { | |
ColorType::Rgb => { | |
let color: Rgb = color.into(); | |
Ok(format!("RGB({},{},{})", color.r, color.g, color.b)) | |
} | |
ColorType::Hex => { | |
let color: Rgb = color.into(); | |
Ok(format!("#{:02x}{:02x}{:02x}", color.r, color.g, color.b)) | |
} | |
ColorType::Hsl => Ok(format!("HSL({},{},{})", color.h, color.s, color.l)), | |
} | |
} | |
} | |
} | |
fn main() -> anyhow::Result<()> { | |
let out = convert_color(ColorType::Rgb, ColorType::Hex, "(255, 0, 0)")?; | |
assert_eq!(out, "#ff0000"); | |
let out = convert_color(ColorType::Hsl, ColorType::Rgb, "(65, 80, 80)")?; | |
assert_eq!(out, "RGB(238,245,163)"); | |
let out = convert_color(ColorType::Hsl, ColorType::Hex, "(65, 80, 80)")?; | |
assert_eq!(out, "#eef5a3"); | |
Ok(()) | |
} | |
#[cfg(test)] | |
mod tests { | |
#[test] | |
fn test_rgb() { | |
let rgb = "#ff0000".parse::<super::Rgb>().unwrap(); | |
assert_eq!(rgb.r, 255); | |
assert_eq!(rgb.g, 0); | |
assert_eq!(rgb.b, 0); | |
let rgb = "(255, 0, 0)".parse::<super::Rgb>().unwrap(); | |
assert_eq!(rgb.r, 255); | |
assert_eq!(rgb.g, 0); | |
assert_eq!(rgb.b, 0); | |
} | |
#[test] | |
fn test_hsl() { | |
let hsl = "(0, 100, 50)".parse::<super::Hsl>().unwrap(); | |
assert_eq!(hsl.h, 0.0); | |
assert_eq!(hsl.s, 100.0); | |
assert_eq!(hsl.l, 50.0); | |
} | |
#[test] | |
fn rgb_to_hsl() { | |
let rgb = "#ff0000".parse::<super::Rgb>().unwrap(); | |
let hsl = super::Hsl::from(rgb); | |
assert_eq!(hsl.h, 0.0); | |
assert_eq!(hsl.s, 1.0); | |
assert_eq!(hsl.l, 0.5); | |
let rgb = "(255, 0, 0)".parse::<super::Rgb>().unwrap(); | |
let hsl = super::Hsl::from(rgb); | |
assert_eq!(hsl.h, 0.0); | |
assert_eq!(hsl.s, 1.0); | |
assert_eq!(hsl.l, 0.5); | |
} | |
#[test] | |
fn hsl_to_rgb() { | |
let hsl = "(122, 33, 83)".parse::<super::Hsl>().unwrap(); | |
let rgb = super::Rgb::from(hsl); | |
assert_eq!(rgb.r, 197); | |
assert_eq!(rgb.g, 226); | |
assert_eq!(rgb.b, 198); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment