use core::marker::PhantomData;
use crate::{
bool_mask::HasBoolMask,
convert::{ConvertOnce, FromColorUnclamped, Matrix3},
num::{Arithmetics, Zero},
stimulus::{FromStimulus, Stimulus, StimulusColor},
xyz::meta::HasXyzMeta,
Alpha, Xyz,
};
use super::matrix::{HasLmsMatrix, XyzToLms};
pub type Lmsa<M, T> = Alpha<Lms<M, T>, T>;
#[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)]
#[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))]
#[palette(palette_internal, component = "T", skip_derives(Lms, Xyz))]
#[repr(C)]
pub struct Lms<M, T> {
pub long: T,
pub medium: T,
pub short: T,
#[cfg_attr(feature = "serializing", serde(skip))]
#[palette(unsafe_zero_sized)]
pub meta: PhantomData<M>,
}
impl<M, T> Lms<M, T> {
pub const fn new(long: T, medium: T, short: T) -> Self {
Self {
long,
medium,
short,
meta: PhantomData,
}
}
pub fn into_format<U>(self) -> Lms<M, U>
where
U: FromStimulus<T>,
{
Lms {
long: U::from_stimulus(self.long),
medium: U::from_stimulus(self.medium),
short: U::from_stimulus(self.short),
meta: PhantomData,
}
}
pub fn from_format<U>(color: Lms<M, U>) -> Self
where
T: FromStimulus<U>,
{
color.into_format()
}
pub fn into_components(self) -> (T, T, T) {
(self.long, self.medium, self.short)
}
pub fn from_components((long, medium, short): (T, T, T)) -> Self {
Self::new(long, medium, short)
}
pub fn with_meta<NewM>(self) -> Lms<NewM, T> {
Lms {
long: self.long,
medium: self.medium,
short: self.short,
meta: PhantomData,
}
}
}
impl<M, T> Lms<M, T>
where
T: Zero,
{
pub fn min_short() -> T {
T::zero()
}
pub fn min_medium() -> T {
T::zero()
}
pub fn min_long() -> T {
T::zero()
}
}
impl<M, T> Lms<M, T> {
#[inline]
pub fn matrix_from_xyz() -> Matrix3<Xyz<M::XyzMeta, T>, Self>
where
M: HasXyzMeta + HasLmsMatrix,
M::LmsMatrix: XyzToLms<T>,
{
Matrix3::from_array(M::LmsMatrix::xyz_to_lms_matrix())
}
}
impl<S, T, A> Alpha<Lms<S, T>, A> {
pub const fn new(red: T, green: T, blue: T, alpha: A) -> Self {
Alpha {
color: Lms::new(red, green, blue),
alpha,
}
}
pub fn into_format<U, B>(self) -> Alpha<Lms<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<Lms<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.long,
self.color.medium,
self.color.short,
self.alpha,
)
}
pub fn from_components((long, medium, short, alpha): (T, T, T, A)) -> Self {
Self::new(long, medium, short, alpha)
}
pub fn with_meta<NewM>(self) -> Alpha<Lms<NewM, T>, A> {
Alpha {
color: self.color.with_meta(),
alpha: self.alpha,
}
}
}
impl<M, T> FromColorUnclamped<Lms<M, T>> for Lms<M, T> {
#[inline]
fn from_color_unclamped(val: Lms<M, T>) -> Self {
val
}
}
impl<M, T> FromColorUnclamped<Xyz<M::XyzMeta, T>> for Lms<M, T>
where
M: HasLmsMatrix + HasXyzMeta,
M::LmsMatrix: XyzToLms<T>,
T: Arithmetics,
{
#[inline]
fn from_color_unclamped(val: Xyz<M::XyzMeta, T>) -> Self {
Self::matrix_from_xyz().convert_once(val)
}
}
impl<M, T> StimulusColor for Lms<M, T> where T: Stimulus {}
impl<M, T> HasBoolMask for Lms<M, T>
where
T: HasBoolMask,
{
type Mask = T::Mask;
}
impl<M, T> Default for Lms<M, T>
where
T: Default,
{
fn default() -> Lms<M, T> {
Lms::new(T::default(), T::default(), T::default())
}
}
impl<M> From<Lms<M, f32>> for Lms<M, f64> {
#[inline]
fn from(color: Lms<M, f32>) -> Self {
color.into_format()
}
}
impl<M> From<Lmsa<M, f32>> for Lmsa<M, f64> {
#[inline]
fn from(color: Lmsa<M, f32>) -> Self {
color.into_format()
}
}
impl<M> From<Lms<M, f64>> for Lms<M, f32> {
#[inline]
fn from(color: Lms<M, f64>) -> Self {
color.into_format()
}
}
impl<M> From<Lmsa<M, f64>> for Lmsa<M, f32> {
#[inline]
fn from(color: Lmsa<M, f64>) -> Self {
color.into_format()
}
}
#[cfg(feature = "bytemuck")]
unsafe impl<M, T> bytemuck::Zeroable for Lms<M, T> where T: bytemuck::Zeroable {}
#[cfg(feature = "bytemuck")]
unsafe impl<M: 'static, T> bytemuck::Pod for Lms<M, T> where T: bytemuck::Pod {}
impl_reference_component_methods!(Lms<M>, [long, medium, short], meta);
impl_struct_of_arrays_methods!(Lms<M>, [long, medium, short], meta);
impl_is_within_bounds! {
Lms<M> {
long => [Self::min_long(), None],
medium => [Self::min_medium(), None],
short => [Self::min_short(), None]
}
where T: Stimulus
}
impl_clamp! {
Lms<M> {
long => [Self::min_long()],
medium => [Self::min_medium()],
short => [Self::min_short()]
}
other {meta}
where T: Stimulus
}
impl_mix!(Lms<M>);
impl_premultiply!(Lms<M> {long, medium, short} phantom: meta);
impl_euclidean_distance!(Lms<M> {long, medium, short});
impl_color_add!(Lms<M>, [long, medium, short], meta);
impl_color_sub!(Lms<M>, [long, medium, short], meta);
impl_color_mul!(Lms<M>, [long, medium, short], meta);
impl_color_div!(Lms<M>, [long, medium, short], meta);
impl_tuple_conversion!(Lms<M> as (T, T, T));
impl_array_casts!(Lms<M, T>, [T; 3]);
impl_simd_array_conversion!(Lms<M>, [long, medium, short], meta);
impl_struct_of_array_traits!(Lms<M>, [long, medium, short], meta);
impl_eq!(Lms<M>, [long, medium, short]);
impl_copy_clone!(Lms<M>, [long, medium, short], meta);
impl_rand_traits_cartesian!(UniformLms, Lms<M> {long, medium, short} phantom: meta: PhantomData<M>);
#[cfg(test)]
mod test {
use crate::{lms::VonKriesLms, white_point::D65};
#[cfg(feature = "alloc")]
use super::Lmsa;
#[cfg(feature = "random")]
use super::Lms;
#[cfg(feature = "approx")]
use crate::{convert::FromColorUnclamped, lms::BradfordLms, Xyz};
test_convert_into_from_xyz!(VonKriesLms<D65, f32>);
raw_pixel_conversion_tests!(VonKriesLms<D65>: long, medium, short);
raw_pixel_conversion_fail_tests!(VonKriesLms<D65>: long, medium, short);
#[cfg(feature = "approx")]
#[test]
fn von_kries_xyz_roundtrip() {
let xyz = Xyz::new(0.2f32, 0.4, 0.8);
let lms = VonKriesLms::<D65, _>::from_color_unclamped(xyz);
assert_relative_eq!(Xyz::from_color_unclamped(lms), xyz);
}
#[cfg(feature = "approx")]
#[test]
fn bradford_xyz_roundtrip() {
let xyz = Xyz::new(0.2f32, 0.4, 0.8);
let lms = BradfordLms::<D65, _>::from_color_unclamped(xyz);
assert_relative_eq!(Xyz::from_color_unclamped(lms), xyz);
}
#[cfg(feature = "serializing")]
#[test]
fn serialize() {
let serialized =
::serde_json::to_string(&VonKriesLms::<D65, f32>::new(0.3, 0.8, 0.1)).unwrap();
assert_eq!(serialized, r#"{"long":0.3,"medium":0.8,"short":0.1}"#);
}
#[cfg(feature = "serializing")]
#[test]
fn deserialize() {
let deserialized: VonKriesLms<D65, f32> =
::serde_json::from_str(r#"{"long":0.3,"medium":0.8,"short":0.1}"#).unwrap();
assert_eq!(deserialized, VonKriesLms::<D65, f32>::new(0.3, 0.8, 0.1));
}
struct_of_arrays_tests!(
VonKriesLms<D65>[long, medium, short] phantom: meta,
Lmsa::new(0.1f32, 0.2, 0.3, 0.4),
Lmsa::new(0.2, 0.3, 0.4, 0.5),
Lmsa::new(0.3, 0.4, 0.5, 0.6)
);
test_uniform_distribution! {
VonKriesLms<D65, f32> {
long: (0.0, 1.0),
medium: (0.0, 1.0),
short: (0.0, 1.0)
},
min: Lms::new(0.0f32, 0.0, 0.0),
max: Lms::new(1.0, 1.0, 1.0)
}
}