Skip to main content

chromashift/
srgb.rs

1use crate::{Alpha, Gamut, LinearRgb, Number, Srgb};
2use core::fmt;
3
4impl Srgb {
5	/// Creates a colour from 8-bit channels, as written in `rgb(255, 0, 0)` or `#ff0000`.
6	pub fn from_u8(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
7		Self::new(red as f64 / 255.0, green as f64 / 255.0, blue as f64 / 255.0, alpha)
8	}
9
10	/// Returns the channels as 8-bit values, clamping out-of-gamut channels.
11	pub fn to_u8(&self) -> [u8; 3] {
12		[self.red, self.green, self.blue].map(|channel| (channel * 255.0).round().clamp(0.0, 255.0) as u8)
13	}
14
15	pub(crate) fn hue(&self) -> f64 {
16		let Self { red, green, blue, .. } = *self;
17		let max = red.max(green).max(blue);
18		let delta = max - red.min(green).min(blue);
19		if delta <= 1e-9 {
20			return 0.0;
21		}
22		let sextant = if max == red {
23			(green - blue) / delta + if green < blue { 6.0 } else { 0.0 }
24		} else if max == green {
25			(blue - red) / delta + 2.0
26		} else {
27			(red - green) / delta + 4.0
28		};
29		sextant * 60.0
30	}
31
32	fn legacy_alpha(&self) -> f64 {
33		let byte = self.alpha as f64 * 2.55;
34		let rounded = byte.round();
35		if (byte - rounded).abs() >= 1e-3 {
36			return self.alpha as f64 / 100.0;
37		}
38		let byte = rounded as u32;
39		let percent = (byte * 100 + 127) / 255;
40		if (percent * 255 + 50) / 100 == byte {
41			percent as f64 / 100.0
42		} else {
43			((byte * 1000 + 127) / 255) as f64 / 1000.0
44		}
45	}
46}
47
48impl fmt::Display for Srgb {
49	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50		if !self.in_gamut() {
51			let Self { red, green, blue, alpha } = *self;
52			return write!(f, "color(srgb {} {} {}{})", Number(red), Number(green), Number(blue), Alpha(alpha));
53		}
54		let [red, green, blue] = self.to_u8();
55		if self.alpha < 100.0 {
56			write!(f, "rgba({red}, {green}, {blue}, {})", Number(self.legacy_alpha()))
57		} else {
58			write!(f, "rgb({red}, {green}, {blue})")
59		}
60	}
61}
62
63impl From<Srgb> for LinearRgb {
64	fn from(value: Srgb) -> Self {
65		let Srgb { red, green, blue, alpha } = value;
66		let [red, green, blue] = Srgb::SPACE.transfer.decode_all([red, green, blue]);
67		LinearRgb::new(red, green, blue, alpha)
68	}
69}
70
71impl From<LinearRgb> for Srgb {
72	fn from(value: LinearRgb) -> Self {
73		let LinearRgb { red, green, blue, alpha } = value;
74		let [red, green, blue] = Srgb::SPACE.transfer.encode_all([red, green, blue]);
75		Srgb::new(red, green, blue, alpha)
76	}
77}
78
79#[cfg(feature = "anstyle")]
80impl From<Srgb> for anstyle::RgbColor {
81	fn from(value: Srgb) -> Self {
82		let [red, green, blue] = value.to_u8();
83		anstyle::RgbColor(red, green, blue)
84	}
85}
86
87#[cfg(feature = "anstyle")]
88impl From<anstyle::RgbColor> for Srgb {
89	fn from(value: anstyle::RgbColor) -> Self {
90		Srgb::from_u8(value.0, value.1, value.2, 100.0)
91	}
92}
93
94#[cfg(feature = "owo-colors")]
95impl From<Srgb> for owo_colors::Rgb {
96	fn from(value: Srgb) -> Self {
97		let [red, green, blue] = value.to_u8();
98		owo_colors::Rgb(red, green, blue)
99	}
100}
101
102#[cfg(feature = "owo-colors")]
103impl From<owo_colors::Rgb> for Srgb {
104	fn from(value: owo_colors::Rgb) -> Self {
105		Srgb::from_u8(value.0, value.1, value.2, 100.0)
106	}
107}