use core::{
any::TypeId,
fmt,
fmt::Debug,
marker::PhantomData,
num::ParseIntError,
ops::{Add, AddAssign, BitAnd, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
str::FromStr,
};
#[cfg(feature = "approx")]
use approx::{AbsDiffEq, RelativeEq, UlpsEq};
#[cfg(feature = "random")]
use rand::{
distributions::{
uniform::{SampleBorrow, SampleUniform, Uniform, UniformSampler},
Distribution, Standard,
},
Rng,
};
use crate::{
alpha::Alpha,
angle::{RealAngle, UnsignedAngle},
blend::{PreAlpha, Premultiply},
bool_mask::{BitOps, HasBoolMask, LazySelect},
cast::{ComponentOrder, Packed},
clamp, clamp_assign,
color_difference::Wcag21RelativeContrast,
convert::{FromColorUnclamped, IntoColorUnclamped},
encoding::{FromLinear, IntoLinear, Linear, Srgb},
luma::LumaStandard,
matrix::{matrix_inverse, matrix_map, multiply_xyz_to_rgb, rgb_to_xyz_matrix},
num::{
self, Abs, Arithmetics, FromScalar, FromScalarArray, IntoScalarArray, IsValidDivisor,
MinMax, One, PartialCmp, Real, Recip, Round, Trigonometry, Zero,
},
oklab::oklab_to_linear_srgb,
rgb::{RgbSpace, RgbStandard},
stimulus::{FromStimulus, Stimulus, StimulusColor},
white_point::{Any, WhitePoint, D65},
Clamp, ClampAssign, FromColor, GetHue, Hsl, Hsv, IntoColor, IsWithinBounds, Lighten,
LightenAssign, Luma, Mix, MixAssign, Oklab, RgbHue, Xyz, Yxy,
};
use super::Primaries;
pub type Rgba<S = Srgb, T = f32> = Alpha<Rgb<S, T>, T>;
#[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)]
#[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))]
#[palette(
palette_internal,
rgb_standard = "S",
component = "T",
skip_derives(Xyz, Hsv, Hsl, Luma, Rgb, Oklab)
)]
#[repr(C)]
pub struct Rgb<S = Srgb, T = f32> {
pub red: T,
pub green: T,
pub blue: T,
#[cfg_attr(feature = "serializing", serde(skip))]
#[palette(unsafe_zero_sized)]
pub standard: PhantomData<S>,
}
impl<S, T: Copy> Copy for Rgb<S, T> {}
impl<S, T: Clone> Clone for Rgb<S, T> {
fn clone(&self) -> Rgb<S, T> {
Rgb {
red: self.red.clone(),
green: self.green.clone(),
blue: self.blue.clone(),
standard: PhantomData,
}
}
}
impl<S, T> Rgb<S, T> {
pub const fn new(red: T, green: T, blue: T) -> Rgb<S, T> {
Rgb {
red,
green,
blue,
standard: PhantomData,
}
}
pub fn into_format<U>(self) -> Rgb<S, U>
where
U: FromStimulus<T>,
{
Rgb {
red: U::from_stimulus(self.red),
green: U::from_stimulus(self.green),
blue: U::from_stimulus(self.blue),
standard: PhantomData,
}
}
pub fn from_format<U>(color: Rgb<S, U>) -> Self
where
T: FromStimulus<U>,
{
color.into_format()
}
pub fn into_components(self) -> (T, T, T) {
(self.red, self.green, self.blue)
}
pub fn from_components((red, green, blue): (T, T, T)) -> Self {
Self::new(red, green, blue)
}
}
impl<S, T> Rgb<S, T>
where
T: Stimulus,
{
pub fn min_red() -> T {
T::zero()
}
pub fn max_red() -> T {
T::max_intensity()
}
pub fn min_green() -> T {
T::zero()
}
pub fn max_green() -> T {
T::max_intensity()
}
pub fn min_blue() -> T {
T::zero()
}
pub fn max_blue() -> T {
T::max_intensity()
}
}
impl<S> Rgb<S, u8> {
#[inline]
pub fn into_u32<O>(self) -> u32
where
O: ComponentOrder<Rgba<S, u8>, u32>,
{
O::pack(Rgba::from(self))
}
#[inline]
pub fn from_u32<O>(color: u32) -> Self
where
O: ComponentOrder<Rgba<S, u8>, u32>,
{
O::unpack(color).color
}
}
impl<S: RgbStandard, T> Rgb<S, T> {
#[inline(always)]
pub fn into_linear<U>(self) -> Rgb<Linear<S::Space>, U>
where
S::TransferFn: IntoLinear<U, T>,
{
Rgb::new(
S::TransferFn::into_linear(self.red),
S::TransferFn::into_linear(self.green),
S::TransferFn::into_linear(self.blue),
)
}
#[inline(always)]
pub fn from_linear<U>(color: Rgb<Linear<S::Space>, U>) -> Self
where
S::TransferFn: FromLinear<U, T>,
{
Rgb::new(
S::TransferFn::from_linear(color.red),
S::TransferFn::from_linear(color.green),
S::TransferFn::from_linear(color.blue),
)
}
}
impl<S: RgbSpace, T> Rgb<Linear<S>, T> {
pub fn into_encoding<U, St>(self) -> Rgb<St, U>
where
St: RgbStandard<Space = S>,
St::TransferFn: FromLinear<T, U>,
{
Rgb::<St, U>::from_linear(self)
}
pub fn from_encoding<U, St>(color: Rgb<St, U>) -> Self
where
St: RgbStandard<Space = S>,
St::TransferFn: IntoLinear<T, U>,
{
color.into_linear()
}
}
impl<S, T> Rgb<S, T>
where
S: RgbStandard,
{
#[inline]
pub(crate) fn reinterpret_as<St>(self) -> Rgb<St, T>
where
S::Space: RgbSpace<WhitePoint = <St::Space as RgbSpace>::WhitePoint>,
St: RgbStandard,
{
Rgb {
red: self.red,
green: self.green,
blue: self.blue,
standard: PhantomData,
}
}
}
impl<S, T, A> Alpha<Rgb<S, T>, A> {
pub const fn new(red: T, green: T, blue: T, alpha: A) -> Self {
Alpha {
color: Rgb::new(red, green, blue),
alpha,
}
}
pub fn into_format<U, B>(self) -> Alpha<Rgb<S, U>, B>
where
U: FromStimulus<T>,
B: FromStimulus<A>,
{
Alpha {
color: self.color.into_format(),
alpha: B::from_stimulus(self.alpha),
}
}
pub fn from_format<U, B>(color: Alpha<Rgb<S, U>, B>) -> Self
where
T: FromStimulus<U>,
A: FromStimulus<B>,
{
color.into_format()
}
pub fn into_components(self) -> (T, T, T, A) {
(
self.color.red,
self.color.green,
self.color.blue,
self.alpha,
)
}
pub fn from_components((red, green, blue, alpha): (T, T, T, A)) -> Self {
Self::new(red, green, blue, alpha)
}
}
impl<S> Rgba<S, u8> {
#[inline]
pub fn into_u32<O>(self) -> u32
where
O: ComponentOrder<Rgba<S, u8>, u32>,
{
O::pack(self)
}
#[inline]
pub fn from_u32<O>(color: u32) -> Self
where
O: ComponentOrder<Rgba<S, u8>, u32>,
{
O::unpack(color)
}
}
impl<S: RgbStandard, T, A> Alpha<Rgb<S, T>, A> {
pub fn into_linear<U, B>(self) -> Alpha<Rgb<Linear<S::Space>, U>, B>
where
S::TransferFn: IntoLinear<U, T>,
B: FromStimulus<A>,
{
Alpha {
color: self.color.into_linear(),
alpha: B::from_stimulus(self.alpha),
}
}
pub fn from_linear<U, B>(color: Alpha<Rgb<Linear<S::Space>, U>, B>) -> Self
where
S::TransferFn: FromLinear<U, T>,
A: FromStimulus<B>,
{
Alpha {
color: Rgb::from_linear(color.color),
alpha: A::from_stimulus(color.alpha),
}
}
}
impl<S: RgbSpace, T, A> Alpha<Rgb<Linear<S>, T>, A> {
pub fn into_encoding<U, B, St>(self) -> Alpha<Rgb<St, U>, B>
where
St: RgbStandard<Space = S>,
St::TransferFn: FromLinear<T, U>,
B: FromStimulus<A>,
{
Alpha::<Rgb<St, U>, B>::from_linear(self)
}
pub fn from_encoding<U, B, St>(color: Alpha<Rgb<St, U>, B>) -> Self
where
St: RgbStandard<Space = S>,
St::TransferFn: IntoLinear<T, U>,
A: FromStimulus<B>,
{
color.into_linear()
}
}
impl_reference_component_methods!(Rgb<S>, [red, green, blue], standard);
impl_struct_of_arrays_methods!(Rgb<S>, [red, green, blue], standard);
impl<S1, S2, T> FromColorUnclamped<Rgb<S2, T>> for Rgb<S1, T>
where
S1: RgbStandard + 'static,
S2: RgbStandard + 'static,
S1::TransferFn: FromLinear<T, T>,
S2::TransferFn: IntoLinear<T, T>,
S2::Space: RgbSpace<WhitePoint = <S1::Space as RgbSpace>::WhitePoint>,
Xyz<<S2::Space as RgbSpace>::WhitePoint, T>: FromColorUnclamped<Rgb<S2, T>>,
Rgb<S1, T>: FromColorUnclamped<Xyz<<S1::Space as RgbSpace>::WhitePoint, T>>,
{
fn from_color_unclamped(rgb: Rgb<S2, T>) -> Self {
let rgb_space1 = TypeId::of::<<S1::Space as RgbSpace>::Primaries>();
let rgb_space2 = TypeId::of::<<S2::Space as RgbSpace>::Primaries>();
if TypeId::of::<S1>() == TypeId::of::<S2>() {
rgb.reinterpret_as()
} else if rgb_space1 == rgb_space2 {
Self::from_linear(rgb.into_linear().reinterpret_as())
} else {
Self::from_color_unclamped(Xyz::from_color_unclamped(rgb))
}
}
}
impl<S, T> FromColorUnclamped<Xyz<<S::Space as RgbSpace>::WhitePoint, T>> for Rgb<S, T>
where
S: RgbStandard,
S::TransferFn: FromLinear<T, T>,
<S::Space as RgbSpace>::Primaries: Primaries<T::Scalar>,
<S::Space as RgbSpace>::WhitePoint: WhitePoint<T::Scalar>,
T: Arithmetics + FromScalar,
T::Scalar: Real
+ Recip
+ IsValidDivisor<Mask = bool>
+ Arithmetics
+ Clone
+ FromScalar<Scalar = T::Scalar>,
Yxy<Any, T::Scalar>: IntoColorUnclamped<Xyz<Any, T::Scalar>>,
{
fn from_color_unclamped(color: Xyz<<S::Space as RgbSpace>::WhitePoint, T>) -> Self {
let transform_matrix = S::Space::xyz_to_rgb_matrix().map_or_else(
|| matrix_inverse(rgb_to_xyz_matrix::<S::Space, T::Scalar>()),
|matrix| matrix_map(matrix, T::Scalar::from_f64),
);
Self::from_linear(multiply_xyz_to_rgb(transform_matrix, color))
}
}
impl<S, T> FromColorUnclamped<Hsl<S, T>> for Rgb<S, T>
where
T: Real
+ RealAngle
+ UnsignedAngle
+ Zero
+ One
+ Abs
+ Round
+ PartialCmp
+ Arithmetics
+ Clone,
T::Mask: LazySelect<T> + BitOps + Clone,
{
fn from_color_unclamped(hsl: Hsl<S, T>) -> Self {
let c = (T::one() - (hsl.lightness.clone() * T::from_f64(2.0) - T::one()).abs())
* hsl.saturation;
let h = hsl.hue.into_positive_degrees() / T::from_f64(60.0);
let h_mod_two = h.clone() - Round::floor(h.clone() * T::from_f64(0.5)) * T::from_f64(2.0);
let x = c.clone() * (T::one() - (h_mod_two - T::one()).abs());
let m = hsl.lightness - c.clone() * T::from_f64(0.5);
let is_zone0 = h.gt_eq(&T::zero()) & h.lt(&T::one());
let is_zone1 = h.gt_eq(&T::one()) & h.lt(&T::from_f64(2.0));
let is_zone2 = h.gt_eq(&T::from_f64(2.0)) & h.lt(&T::from_f64(3.0));
let is_zone3 = h.gt_eq(&T::from_f64(3.0)) & h.lt(&T::from_f64(4.0));
let is_zone4 = h.gt_eq(&T::from_f64(4.0)) & h.lt(&T::from_f64(5.0));
let red = lazy_select! {
if is_zone1.clone() | &is_zone4 => x.clone(),
if is_zone2.clone() | &is_zone3 => T::zero(),
else => c.clone(),
};
let green = lazy_select! {
if is_zone0.clone() | &is_zone3 => x.clone(),
if is_zone1.clone() | &is_zone2 => c.clone(),
else => T::zero(),
};
let blue = lazy_select! {
if is_zone0 | is_zone1 => T::zero(),
if is_zone3 | is_zone4 => c,
else => x,
};
Rgb {
red: red + m.clone(),
green: green + m.clone(),
blue: blue + m,
standard: PhantomData,
}
}
}
impl<S, T> FromColorUnclamped<Hsv<S, T>> for Rgb<S, T>
where
T: Real
+ RealAngle
+ UnsignedAngle
+ Round
+ Zero
+ One
+ Abs
+ PartialCmp
+ Arithmetics
+ Clone,
T::Mask: LazySelect<T> + BitOps + Clone,
{
fn from_color_unclamped(hsv: Hsv<S, T>) -> Self {
let c = hsv.value.clone() * hsv.saturation;
let h = hsv.hue.into_positive_degrees() / T::from_f64(60.0);
let h_mod_two = h.clone() - Round::floor(h.clone() * T::from_f64(0.5)) * T::from_f64(2.0);
let x = c.clone() * (T::one() - (h_mod_two - T::one()).abs());
let m = hsv.value - c.clone();
let is_zone0 = h.gt_eq(&T::zero()) & h.lt(&T::one());
let is_zone1 = h.gt_eq(&T::one()) & h.lt(&T::from_f64(2.0));
let is_zone2 = h.gt_eq(&T::from_f64(2.0)) & h.lt(&T::from_f64(3.0));
let is_zone3 = h.gt_eq(&T::from_f64(3.0)) & h.lt(&T::from_f64(4.0));
let is_zone4 = h.gt_eq(&T::from_f64(4.0)) & h.lt(&T::from_f64(5.0));
let red = lazy_select! {
if is_zone1.clone() | &is_zone4 => x.clone(),
if is_zone2.clone() | &is_zone3 => T::zero(),
else => c.clone(),
};
let green = lazy_select! {
if is_zone0.clone() | &is_zone3 => x.clone(),
if is_zone1.clone() | &is_zone2 => c.clone(),
else => T::zero(),
};
let blue = lazy_select! {
if is_zone0 | is_zone1 => T::zero(),
if is_zone3 | is_zone4 => c,
else => x,
};
Rgb {
red: red + m.clone(),
green: green + m.clone(),
blue: blue + m,
standard: PhantomData,
}
}
}
impl<S, St, T> FromColorUnclamped<Luma<St, T>> for Rgb<S, T>
where
S: RgbStandard + 'static,
St: LumaStandard<WhitePoint = <S::Space as RgbSpace>::WhitePoint> + 'static,
S::TransferFn: FromLinear<T, T>,
St::TransferFn: IntoLinear<T, T>,
T: Clone,
{
#[inline]
fn from_color_unclamped(color: Luma<St, T>) -> Self {
if TypeId::of::<S::TransferFn>() == TypeId::of::<St::TransferFn>() {
Rgb {
red: color.luma.clone(),
green: color.luma.clone(),
blue: color.luma,
standard: PhantomData,
}
} else {
let luma = color.into_linear();
Self::from_linear(Rgb {
red: luma.luma.clone(),
green: luma.luma.clone(),
blue: luma.luma,
standard: PhantomData,
})
}
}
}
impl<S, T> FromColorUnclamped<Oklab<T>> for Rgb<S, T>
where
T: Real + Arithmetics + Copy,
S: RgbStandard,
S::TransferFn: FromLinear<T, T>,
S::Space: RgbSpace<WhitePoint = D65> + 'static,
Rgb<Linear<Srgb>, T>: IntoColorUnclamped<Self>,
Xyz<D65, T>: FromColorUnclamped<Oklab<T>> + IntoColorUnclamped<Self>,
{
fn from_color_unclamped(oklab: Oklab<T>) -> Self {
if TypeId::of::<<S as RgbStandard>::Space>() == TypeId::of::<Srgb>() {
oklab_to_linear_srgb(oklab).into_color_unclamped()
} else {
Xyz::from_color_unclamped(oklab).into_color_unclamped()
}
}
}
impl_is_within_bounds! {
Rgb<S> {
red => [Self::min_red(), Self::max_red()],
green => [Self::min_green(), Self::max_green()],
blue => [Self::min_blue(), Self::max_blue()]
}
where T: Stimulus
}
impl<S, T> Clamp for Rgb<S, T>
where
T: Stimulus + num::Clamp,
{
#[inline]
fn clamp(self) -> Self {
Self::new(
clamp(self.red, Self::min_red(), Self::max_red()),
clamp(self.green, Self::min_green(), Self::max_green()),
clamp(self.blue, Self::min_blue(), Self::max_blue()),
)
}
}
impl<S, T> ClampAssign for Rgb<S, T>
where
T: Stimulus + num::ClampAssign,
{
#[inline]
fn clamp_assign(&mut self) {
clamp_assign(&mut self.red, Self::min_red(), Self::max_red());
clamp_assign(&mut self.green, Self::min_green(), Self::max_green());
clamp_assign(&mut self.blue, Self::min_blue(), Self::max_blue());
}
}
impl_mix!(Rgb<S>);
impl_lighten! {
Rgb<S>
increase {
red => [Self::min_red(), Self::max_red()],
green => [Self::min_green(), Self::max_green()],
blue => [Self::min_blue(), Self::max_blue()]
}
other {}
phantom: standard
where T: Stimulus,
}
impl<S, T> GetHue for Rgb<S, T>
where
T: Real + RealAngle + Trigonometry + Arithmetics + Clone,
{
type Hue = RgbHue<T>;
fn get_hue(&self) -> RgbHue<T> {
let sqrt_3: T = T::from_f64(1.73205081);
RgbHue::from_cartesian(
self.red.clone() * T::from_f64(2.0) - self.green.clone() - self.blue.clone(),
sqrt_3 * (self.green.clone() - self.blue.clone()),
)
}
}
impl_premultiply!(Rgb<S> {red, green, blue} phantom: standard);
impl_euclidean_distance!(Rgb<S> {red, green, blue});
impl<S, T> StimulusColor for Rgb<S, T> where T: Stimulus {}
impl<S, T> HasBoolMask for Rgb<S, T>
where
T: HasBoolMask,
{
type Mask = T::Mask;
}
impl<S, T> Default for Rgb<S, T>
where
T: Stimulus,
{
fn default() -> Rgb<S, T> {
Rgb::new(Self::min_red(), Self::min_green(), Self::min_blue())
}
}
impl_color_add!(Rgb<S, T>, [red, green, blue], standard);
impl_color_sub!(Rgb<S, T>, [red, green, blue], standard);
impl_color_mul!(Rgb<S, T>, [red, green, blue], standard);
impl_color_div!(Rgb<S, T>, [red, green, blue], standard);
impl<S, T> From<(T, T, T)> for Rgb<S, T> {
fn from(components: (T, T, T)) -> Self {
Self::from_components(components)
}
}
impl<S, T> From<Rgb<S, T>> for (T, T, T) {
fn from(color: Rgb<S, T>) -> (T, T, T) {
color.into_components()
}
}
impl<S, T, A> From<(T, T, T, A)> for Alpha<Rgb<S, T>, A> {
fn from(components: (T, T, T, A)) -> Self {
Self::from_components(components)
}
}
impl<S, T, A> From<Alpha<Rgb<S, T>, A>> for (T, T, T, A) {
fn from(color: Alpha<Rgb<S, T>, A>) -> (T, T, T, A) {
color.into_components()
}
}
impl_array_casts!(Rgb<S, T>, [T; 3]);
impl_simd_array_conversion!(Rgb<S>, [red, green, blue], standard);
impl_struct_of_array_traits!(Rgb<S>, [red, green, blue], standard);
impl_eq!(Rgb<S>, [red, green, blue]);
impl<S, T> fmt::LowerHex for Rgb<S, T>
where
T: fmt::LowerHex,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let size = f.width().unwrap_or(::core::mem::size_of::<T>() * 2);
write!(
f,
"{:0width$x}{:0width$x}{:0width$x}",
self.red,
self.green,
self.blue,
width = size
)
}
}
impl<S, T> fmt::UpperHex for Rgb<S, T>
where
T: fmt::UpperHex,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let size = f.width().unwrap_or(::core::mem::size_of::<T>() * 2);
write!(
f,
"{:0width$X}{:0width$X}{:0width$X}",
self.red,
self.green,
self.blue,
width = size
)
}
}
#[derive(Debug)]
pub enum FromHexError {
ParseIntError(ParseIntError),
HexFormatError(&'static str),
}
impl From<ParseIntError> for FromHexError {
fn from(err: ParseIntError) -> FromHexError {
FromHexError::ParseIntError(err)
}
}
impl From<&'static str> for FromHexError {
fn from(err: &'static str) -> FromHexError {
FromHexError::HexFormatError(err)
}
}
impl core::fmt::Display for FromHexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &*self {
FromHexError::ParseIntError(e) => write!(f, "{}", e),
FromHexError::HexFormatError(s) => write!(
f,
"{}, please use format '#fff', 'fff', '#ffffff' or 'ffffff'.",
s
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FromHexError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &*self {
FromHexError::HexFormatError(_s) => None,
FromHexError::ParseIntError(e) => Some(e),
}
}
}
impl<S> FromStr for Rgb<S, u8> {
type Err = FromHexError;
fn from_str(hex: &str) -> Result<Self, Self::Err> {
let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped);
match hex_code.len() {
3 => {
let red = u8::from_str_radix(&hex_code[..1], 16)?;
let green = u8::from_str_radix(&hex_code[1..2], 16)?;
let blue = u8::from_str_radix(&hex_code[2..3], 16)?;
let col: Rgb<S, u8> = Rgb::new(red * 17, green * 17, blue * 17);
Ok(col)
}
6 => {
let red = u8::from_str_radix(&hex_code[..2], 16)?;
let green = u8::from_str_radix(&hex_code[2..4], 16)?;
let blue = u8::from_str_radix(&hex_code[4..6], 16)?;
let col: Rgb<S, u8> = Rgb::new(red, green, blue);
Ok(col)
}
_ => Err("invalid hex code format".into()),
}
}
}
impl<S, T, P, O> From<Rgb<S, T>> for Packed<O, P>
where
O: ComponentOrder<Rgba<S, T>, P>,
Rgba<S, T>: From<Rgb<S, T>>,
{
#[inline]
fn from(color: Rgb<S, T>) -> Self {
Self::from(Rgba::from(color))
}
}
impl<S, T, O, P> From<Rgba<S, T>> for Packed<O, P>
where
O: ComponentOrder<Rgba<S, T>, P>,
{
#[inline]
fn from(color: Rgba<S, T>) -> Self {
Packed::pack(color)
}
}
impl<S, O, P> From<Packed<O, P>> for Rgb<S, u8>
where
O: ComponentOrder<Rgba<S, u8>, P>,
{
#[inline]
fn from(packed: Packed<O, P>) -> Self {
Rgba::from(packed).color
}
}
impl<S, T, O, P> From<Packed<O, P>> for Rgba<S, T>
where
O: ComponentOrder<Rgba<S, T>, P>,
{
#[inline]
fn from(packed: Packed<O, P>) -> Self {
packed.unpack()
}
}
impl<S> From<u32> for Rgb<S, u8> {
#[inline]
fn from(color: u32) -> Self {
Self::from_u32::<super::channels::Argb>(color)
}
}
impl<S> From<u32> for Rgba<S, u8> {
#[inline]
fn from(color: u32) -> Self {
Self::from_u32::<super::channels::Rgba>(color)
}
}
impl<S> From<Rgb<S, u8>> for u32 {
#[inline]
fn from(color: Rgb<S, u8>) -> Self {
Rgb::into_u32::<super::channels::Argb>(color)
}
}
impl<S> From<Rgba<S, u8>> for u32 {
#[inline]
fn from(color: Rgba<S, u8>) -> Self {
Rgba::into_u32::<super::channels::Rgba>(color)
}
}
#[allow(deprecated)]
impl<S, T> crate::RelativeContrast for Rgb<S, T>
where
T: Real + Arithmetics + PartialCmp,
T::Mask: LazySelect<T>,
S: RgbStandard,
Xyz<<<S as RgbStandard>::Space as RgbSpace>::WhitePoint, T>: FromColor<Self>,
{
type Scalar = T;
#[inline]
fn get_contrast_ratio(self, other: Self) -> T {
let xyz1 = Xyz::from_color(self);
let xyz2 = Xyz::from_color(other);
crate::contrast_ratio(xyz1.y, xyz2.y)
}
}
impl<S, T> Wcag21RelativeContrast for Rgb<S, T>
where
Self: IntoColor<Luma<Linear<D65>, T>>,
S: RgbStandard<Space = crate::encoding::srgb::Srgb>,
T: Real + Add<T, Output = T> + Div<T, Output = T> + PartialCmp + MinMax,
{
type Scalar = T;
fn relative_luminance(self) -> Luma<Linear<D65>, Self::Scalar> {
self.into_color()
}
}
#[cfg(feature = "random")]
impl<S, T> Distribution<Rgb<S, T>> for Standard
where
Standard: Distribution<T>,
{
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Rgb<S, T> {
Rgb {
red: rng.gen(),
green: rng.gen(),
blue: rng.gen(),
standard: PhantomData,
}
}
}
#[cfg(feature = "random")]
pub struct UniformRgb<S, T>
where
T: SampleUniform,
{
red: Uniform<T>,
green: Uniform<T>,
blue: Uniform<T>,
standard: PhantomData<S>,
}
#[cfg(feature = "random")]
impl<S, T> SampleUniform for Rgb<S, T>
where
T: SampleUniform + Clone,
{
type Sampler = UniformRgb<S, T>;
}
#[cfg(feature = "random")]
impl<S, T> UniformSampler for UniformRgb<S, T>
where
T: SampleUniform + Clone,
{
type X = Rgb<S, T>;
fn new<B1, B2>(low_b: B1, high_b: B2) -> Self
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + Sized,
{
let low = low_b.borrow();
let high = high_b.borrow();
UniformRgb {
red: Uniform::new::<_, T>(low.red.clone(), high.red.clone()),
green: Uniform::new::<_, T>(low.green.clone(), high.green.clone()),
blue: Uniform::new::<_, T>(low.blue.clone(), high.blue.clone()),
standard: PhantomData,
}
}
fn new_inclusive<B1, B2>(low_b: B1, high_b: B2) -> Self
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + Sized,
{
let low = low_b.borrow();
let high = high_b.borrow();
UniformRgb {
red: Uniform::new_inclusive::<_, T>(low.red.clone(), high.red.clone()),
green: Uniform::new_inclusive::<_, T>(low.green.clone(), high.green.clone()),
blue: Uniform::new_inclusive::<_, T>(low.blue.clone(), high.blue.clone()),
standard: PhantomData,
}
}
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Rgb<S, T> {
Rgb {
red: self.red.sample(rng),
green: self.green.sample(rng),
blue: self.blue.sample(rng),
standard: PhantomData,
}
}
}
#[cfg(feature = "bytemuck")]
unsafe impl<S, T> bytemuck::Zeroable for Rgb<S, T> where T: bytemuck::Zeroable {}
#[cfg(feature = "bytemuck")]
unsafe impl<S: 'static, T> bytemuck::Pod for Rgb<S, T> where T: bytemuck::Pod {}
#[cfg(test)]
mod test {
use core::str::FromStr;
use crate::encoding::Srgb;
use crate::rgb::channels;
use super::{Rgb, Rgba};
test_convert_into_from_xyz!(Rgb);
#[test]
fn ranges() {
assert_ranges! {
Rgb<Srgb, f64>;
clamped {
red: 0.0 => 1.0,
green: 0.0 => 1.0,
blue: 0.0 => 1.0
}
clamped_min {}
unclamped {}
}
}
raw_pixel_conversion_tests!(Rgb<Srgb>: red, green, blue);
raw_pixel_conversion_fail_tests!(Rgb<Srgb>: red, green, blue);
#[test]
fn lower_hex() {
assert_eq!(
format!("{:x}", Rgb::<Srgb, u8>::new(171, 193, 35)),
"abc123"
);
}
#[test]
fn lower_hex_small_numbers() {
assert_eq!(format!("{:x}", Rgb::<Srgb, u8>::new(1, 2, 3)), "010203");
assert_eq!(
format!("{:x}", Rgb::<Srgb, u16>::new(1, 2, 3)),
"000100020003"
);
assert_eq!(
format!("{:x}", Rgb::<Srgb, u32>::new(1, 2, 3)),
"000000010000000200000003"
);
assert_eq!(
format!("{:x}", Rgb::<Srgb, u64>::new(1, 2, 3)),
"000000000000000100000000000000020000000000000003"
);
}
#[test]
fn lower_hex_custom_width() {
assert_eq!(
format!("{:03x}", Rgb::<Srgb, u8>::new(1, 2, 3)),
"001002003"
);
assert_eq!(
format!("{:03x}", Rgb::<Srgb, u16>::new(1, 2, 3)),
"001002003"
);
assert_eq!(
format!("{:03x}", Rgb::<Srgb, u32>::new(1, 2, 3)),
"001002003"
);
assert_eq!(
format!("{:03x}", Rgb::<Srgb, u64>::new(1, 2, 3)),
"001002003"
);
}
#[test]
fn upper_hex() {
assert_eq!(
format!("{:X}", Rgb::<Srgb, u8>::new(171, 193, 35)),
"ABC123"
);
}
#[test]
fn upper_hex_small_numbers() {
assert_eq!(format!("{:X}", Rgb::<Srgb, u8>::new(1, 2, 3)), "010203");
assert_eq!(
format!("{:X}", Rgb::<Srgb, u16>::new(1, 2, 3)),
"000100020003"
);
assert_eq!(
format!("{:X}", Rgb::<Srgb, u32>::new(1, 2, 3)),
"000000010000000200000003"
);
assert_eq!(
format!("{:X}", Rgb::<Srgb, u64>::new(1, 2, 3)),
"000000000000000100000000000000020000000000000003"
);
}
#[test]
fn upper_hex_custom_width() {
assert_eq!(
format!("{:03X}", Rgb::<Srgb, u8>::new(1, 2, 3)),
"001002003"
);
assert_eq!(
format!("{:03X}", Rgb::<Srgb, u16>::new(1, 2, 3)),
"001002003"
);
assert_eq!(
format!("{:03X}", Rgb::<Srgb, u32>::new(1, 2, 3)),
"001002003"
);
assert_eq!(
format!("{:03X}", Rgb::<Srgb, u64>::new(1, 2, 3)),
"001002003"
);
}
#[test]
fn rgb_hex_into_from() {
let c1 = Rgb::<Srgb, u8>::from_u32::<channels::Argb>(0x1100_7FFF);
let c2 = Rgb::<Srgb, u8>::new(0u8, 127, 255);
assert_eq!(c1, c2);
assert_eq!(Rgb::<Srgb, u8>::into_u32::<channels::Argb>(c1), 0xFF00_7FFF);
let c1 = Rgba::<Srgb, u8>::from_u32::<channels::Rgba>(0x007F_FF80);
let c2 = Rgba::<Srgb, u8>::new(0u8, 127, 255, 128);
assert_eq!(c1, c2);
assert_eq!(
Rgba::<Srgb, u8>::into_u32::<channels::Rgba>(c1),
0x007F_FF80
);
assert_eq!(
Rgb::<Srgb, u8>::from(0x7FFF_FF80),
Rgb::from((255u8, 255, 128))
);
assert_eq!(
Rgba::<Srgb, u8>::from(0x7FFF_FF80),
Rgba::from((127u8, 255, 255, 128))
);
}
#[cfg(feature = "serializing")]
#[test]
fn serialize() {
let serialized = ::serde_json::to_string(&Rgb::<Srgb>::new(0.3, 0.8, 0.1)).unwrap();
assert_eq!(serialized, r#"{"red":0.3,"green":0.8,"blue":0.1}"#);
}
#[cfg(feature = "serializing")]
#[test]
fn deserialize() {
let deserialized: Rgb<Srgb> =
::serde_json::from_str(r#"{"red":0.3,"green":0.8,"blue":0.1}"#).unwrap();
assert_eq!(deserialized, Rgb::<Srgb>::new(0.3, 0.8, 0.1));
}
#[test]
fn from_str() {
let c = Rgb::<Srgb, u8>::from_str("#ffffff");
assert!(c.is_ok());
assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(255, 255, 255));
let c = Rgb::<Srgb, u8>::from_str("#gggggg");
assert!(c.is_err());
let c = Rgb::<Srgb, u8>::from_str("#fff");
assert!(c.is_ok());
assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(255, 255, 255));
let c = Rgb::<Srgb, u8>::from_str("#000000");
assert!(c.is_ok());
assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(0, 0, 0));
let c = Rgb::<Srgb, u8>::from_str("");
assert!(c.is_err());
let c = Rgb::<Srgb, u8>::from_str("#123456");
assert!(c.is_ok());
assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(18, 52, 86));
let c = Rgb::<Srgb, u8>::from_str("#iii");
assert!(c.is_err());
assert_eq!(
format!("{}", c.err().unwrap()),
"invalid digit found in string"
);
let c = Rgb::<Srgb, u8>::from_str("#08f");
assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(0, 136, 255));
let c = Rgb::<Srgb, u8>::from_str("08f");
assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(0, 136, 255));
let c = Rgb::<Srgb, u8>::from_str("ffffff");
assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(255, 255, 255));
let c = Rgb::<Srgb, u8>::from_str("#12");
assert!(c.is_err());
assert_eq!(
format!("{}", c.err().unwrap()),
"invalid hex code format, \
please use format \'#fff\', \'fff\', \'#ffffff\' or \'ffffff\'."
);
let c = Rgb::<Srgb, u8>::from_str("da0bce");
assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(218, 11, 206));
let c = Rgb::<Srgb, u8>::from_str("f034e6");
assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(240, 52, 230));
let c = Rgb::<Srgb, u8>::from_str("abc");
assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(170, 187, 204));
}
#[test]
fn check_min_max_components() {
assert_relative_eq!(Rgb::<Srgb, f32>::min_red(), 0.0);
assert_relative_eq!(Rgb::<Srgb, f32>::min_green(), 0.0);
assert_relative_eq!(Rgb::<Srgb, f32>::min_blue(), 0.0);
assert_relative_eq!(Rgb::<Srgb, f32>::max_red(), 1.0);
assert_relative_eq!(Rgb::<Srgb, f32>::max_green(), 1.0);
assert_relative_eq!(Rgb::<Srgb, f32>::max_blue(), 1.0);
}
struct_of_arrays_tests!(
Rgb<Srgb>,
Rgb::new(0.1f32, 0.2, 0.3),
Rgb::new(0.2, 0.3, 0.4),
Rgb::new(0.3, 0.4, 0.5)
);
mod alpha {
use crate::{encoding::Srgb, rgb::Rgba};
struct_of_arrays_tests!(
Rgba<Srgb>,
Rgba::new(0.1f32, 0.2, 0.3, 0.4),
Rgba::new(0.2, 0.3, 0.4, 0.5),
Rgba::new(0.3, 0.4, 0.5, 0.6)
);
}
#[cfg(feature = "random")]
test_uniform_distribution! {
Rgb<Srgb, f32> {
red: (0.0, 1.0),
green: (0.0, 1.0),
blue: (0.0, 1.0)
},
min: Rgb::new(0.0f32, 0.0, 0.0),
max: Rgb::new(1.0, 1.0, 1.0)
}
}