Skip to content

Instantly share code, notes, and snippets.

@frectonz
Last active March 20, 2023 17:15
Show Gist options
  • Save frectonz/b508aeab09e1bd590ae2b8831bacc222 to your computer and use it in GitHub Desktop.
Save frectonz/b508aeab09e1bd590ae2b8831bacc222 to your computer and use it in GitHub Desktop.
From cassido's March 13, 2023 Newsletter
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