From f8f364cffedb34e01de9c6a9a2cff859bc4369c6 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 3 Jun 2018 17:21:22 +0200 Subject: [PATCH 1/5] Add conversion trait --- palette/src/convert.rs | 58 +++++++++++++++++++++++++++++++++++++++++- palette/src/lib.rs | 2 +- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/palette/src/convert.rs b/palette/src/convert.rs index 7fb401c10..d2620fc69 100644 --- a/palette/src/convert.rs +++ b/palette/src/convert.rs @@ -1,9 +1,11 @@ use num_traits::Float; -use {Component, Hsl, Hsv, Hwb, Lab, Lch, Xyz, Yxy}; +use Pixel; +use {Component, Limited, Hsl, Hsv, Hwb, Lab, Lch, Xyz, Yxy}; use white_point::{D65, WhitePoint}; use rgb::{Rgb, RgbSpace}; use luma::Luma; +use Alpha; use encoding::Linear; /// FromColor provides conversion from the colors. @@ -499,6 +501,60 @@ where } } +///A trait for converting from one color to another. +/// +///This trait wraps the underlying `From` implementation. +pub trait Convert: Sized { + ///Lossless, same as calling T::from(self) + #[inline] + fn convert>(self) -> T { + T::from(self) + } + + ///Convert into T with values clamped to the color defined bounds + fn convert_clamped + Limited>(self) -> T { + let mut this: T = self.convert(); + if !this.is_valid() { + this.clamp_self(); + } + this + } + + ///Convert into Option, returning None if the resulting Color is outside of its defined range + fn try_convert + Limited>(self) -> Option { + let this: T = self.convert(); + if this.is_valid() { + Some(this) + } else { + None + } + } +} + +impl Convert for Rgb {} +impl Convert for Luma {} +impl Convert for Hsl {} +impl Convert for Hsv {} +impl Convert for Hwb {} +impl Convert for Lab {} +impl Convert for Lch {} +impl Convert for Xyz {} +impl Convert for Yxy {} +impl Convert for Alpha where C: Convert, T: Component {} + +///A trait for converting from one color to an arbitrary type by handing a closure the color's raw components. +/// +///This trait makes use of the `Pixel` trait which can be unsafe depending on the implementator. +pub trait ConvertWith: Pixel where T: Component +{ + ///Convert `self` to `T` by applying a function over its components + fn convert_with(self, f: F) -> Out where F: FnOnce(&[T]) -> Out, Out: Sized { + f(Pixel::into_raw_slice(&[self])) + } +} + +impl ConvertWith for U where U: Pixel, T: Component {} + macro_rules! impl_into_color { ($self_ty: ident, $from_fn: ident) => { impl IntoColor for $self_ty diff --git a/palette/src/lib.rs b/palette/src/lib.rs index ae67fccfc..10956fc22 100644 --- a/palette/src/lib.rs +++ b/palette/src/lib.rs @@ -182,7 +182,7 @@ pub use rgb::{GammaSrgb, GammaSrgba, LinSrgb, LinSrgba, Srgb, Srgba}; pub use xyz::{Xyz, Xyza}; pub use yxy::{Yxy, Yxya}; -pub use convert::{FromColor, IntoColor}; +pub use convert::{Convert, ConvertWith, FromColor, IntoColor}; pub use encoding::pixel::Pixel; pub use hues::{LabHue, RgbHue}; pub use matrix::Mat3; From 9eeced6e9f8c612aedae4e85be0ed481f7d0296f Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 8 Jun 2018 01:14:01 +0200 Subject: [PATCH 2/5] removed Limited trait bounds --- palette/src/convert.rs | 198 ++++++++++++++++++++++++++++++++--------- palette/src/lib.rs | 2 +- 2 files changed, 155 insertions(+), 45 deletions(-) diff --git a/palette/src/convert.rs b/palette/src/convert.rs index d2620fc69..567b7e41d 100644 --- a/palette/src/convert.rs +++ b/palette/src/convert.rs @@ -1,10 +1,11 @@ use num_traits::Float; -use Pixel; +use std::error; +use std::fmt::{self, Debug, Display, Formatter}; use {Component, Limited, Hsl, Hsv, Hwb, Lab, Lch, Xyz, Yxy}; use white_point::{D65, WhitePoint}; -use rgb::{Rgb, RgbSpace}; -use luma::Luma; +use rgb::{Rgb, RgbSpace, RgbStandard}; +use luma::{Luma, LumaStandard}; use Alpha; use encoding::Linear; @@ -501,59 +502,168 @@ where } } +///The error type for a color conversion that converted a color into a color with invalid values. +#[derive(Debug)] +pub struct OutOfBounds { + color: T, +} + +impl OutOfBounds { + ///Create a new error wrapping a color + fn new(color: T) -> Self { + OutOfBounds { color } + } + + ///Consume this error and return the wrapped color + pub fn color(self) -> T { + self.color + } +} + +impl error::Error for OutOfBounds {} + +impl Display for OutOfBounds { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "Color conversion is out of bounds") + } +} + +///An extension for `Result` that offers a method to take out the color of `try_convert` result. +pub trait ResultExt { + ///Take the color out of this `Result`. + fn take(self) -> T; +} + +impl ResultExt for Result> { + #[inline] + fn take(self) -> T { + match self { + Ok(color) => color, + Err(color) => color.color(), + } + } +} + ///A trait for converting from one color to another. /// -///This trait wraps the underlying `From` implementation. -pub trait Convert: Sized { - ///Lossless, same as calling T::from(self) +///`convert_unclamped` currently wraps the underlying `Into` implementation. +pub trait Convert: Sized + Into { + + ///Convert into T with values clamped to the color defined bounds + /// + ///``` + ///use palette::Convert; + ///use palette::Limited; + ///use palette::{Srgb, Lch}; + /// + /// + ///let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).convert(); + ///assert!(rgb.is_valid()); + ///``` + fn convert(self) -> T; + + ///Convert into T, the resulting color might be invalid in it's color space + /// + ///``` + ///use palette::Convert; + ///use palette::Limited; + ///use palette::{Srgb, Lch}; + /// + ///let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).convert_unclamped(); + ///assert!(!rgb.is_valid()); + ///``` #[inline] - fn convert>(self) -> T { - T::from(self) + fn convert_unclamped(self) -> T { + self.into() } - ///Convert into T with values clamped to the color defined bounds - fn convert_clamped + Limited>(self) -> T { - let mut this: T = self.convert(); - if !this.is_valid() { - this.clamp_self(); + ///Convert into T, returning ok if the color is inside of its defined range, + ///otherwise an `OutOfBounds` error is returned which contains the unclamped color. + /// + ///``` + ///use palette::Convert; + ///use palette::{Srgb, Hsl}; + /// + ///let rgb: Srgb = match Hsl::new(150.0, 1.0, 1.1).try_convert() { + /// Ok(color) => color, + /// Err(err) => { + /// println!("Color is out of bounds"); + /// err.color() + /// }, + ///}; + ///``` + fn try_convert(self) -> Result>; +} + +macro_rules! expand_impl_convert { + (Alpha<$typ:ident<$gp:ident>>) => { + fn convert(self) -> Alpha<$typ<$gp, C>, C> { + let mut this = self.into(); + if !this.is_valid() { + this.clamp_self(); + } + this } - this - } - ///Convert into Option, returning None if the resulting Color is outside of its defined range - fn try_convert + Limited>(self) -> Option { - let this: T = self.convert(); - if this.is_valid() { - Some(this) - } else { - None + fn try_convert(self) -> Result, C>, OutOfBounds, C>>> { + let this = self.into(); + if this.is_valid() { + Ok(this) + } else { + Err(OutOfBounds::new(this)) + } } - } + }; + ($typ:ident<$gp:ident>) => { + fn convert(self) -> $typ<$gp, C> { + let mut this = self.into(); + if !this.is_valid() { + this.clamp_self(); + } + this + } + + fn try_convert(self) -> Result<$typ<$gp, C>, OutOfBounds<$typ<$gp, C>>> { + let this = self.into(); + if this.is_valid() { + Ok(this) + } else { + Err(OutOfBounds::new(this)) + } + } + }; } -impl Convert for Rgb {} -impl Convert for Luma {} -impl Convert for Hsl {} -impl Convert for Hsv {} -impl Convert for Hwb {} -impl Convert for Lab {} -impl Convert for Lch {} -impl Convert for Xyz {} -impl Convert for Yxy {} -impl Convert for Alpha where C: Convert, T: Component {} - -///A trait for converting from one color to an arbitrary type by handing a closure the color's raw components. -/// -///This trait makes use of the `Pixel` trait which can be unsafe depending on the implementator. -pub trait ConvertWith: Pixel where T: Component -{ - ///Convert `self` to `T` by applying a function over its components - fn convert_with(self, f: F) -> Out where F: FnOnce(&[T]) -> Out, Out: Sized { - f(Pixel::into_raw_slice(&[self])) - } +macro_rules! impl_convert { + (impl<$gp:ident: $gt:ident> $typ:ident<$gt2:ident>) => { + impl<$gp: $gt, C: Component, T> Convert<$typ<$gp, C>> for T where T: Into<$typ<$gp, C>> { + expand_impl_convert!($typ<$gp>); + } + + impl<$gp: $gt, C: Component, T> Convert, C>> for T where T: Into, C>> { + expand_impl_convert!(Alpha<$typ<$gp>>); + } + }; + (float_component impl<$gp:ident: $gt:ident> $typ:ident<$gt2:ident>) => { + impl<$gp: $gt, C: Float + Component, T> Convert<$typ<$gp, C>> for T where T: Into<$typ<$gp, C>> { + expand_impl_convert!($typ<$gp>); + } + + impl<$gp: $gt, C: Float + Component, T> Convert, C>> for T where T: Into, C>> { + expand_impl_convert!(Alpha<$typ<$gp>>); + } + }; } -impl ConvertWith for U where U: Pixel, T: Component {} +impl_convert!(impl Rgb); +impl_convert!(impl Luma); +impl_convert!(float_component impl Hsl); +impl_convert!(float_component impl Hsv); +impl_convert!(float_component impl Hwb); +impl_convert!(float_component impl Lab); +impl_convert!(float_component impl Lch); +impl_convert!(float_component impl Xyz); +impl_convert!(float_component impl Yxy); macro_rules! impl_into_color { ($self_ty: ident, $from_fn: ident) => { diff --git a/palette/src/lib.rs b/palette/src/lib.rs index 10956fc22..918261d2a 100644 --- a/palette/src/lib.rs +++ b/palette/src/lib.rs @@ -182,7 +182,7 @@ pub use rgb::{GammaSrgb, GammaSrgba, LinSrgb, LinSrgba, Srgb, Srgba}; pub use xyz::{Xyz, Xyza}; pub use yxy::{Yxy, Yxya}; -pub use convert::{Convert, ConvertWith, FromColor, IntoColor}; +pub use convert::{Convert, ResultExt, OutOfBounds, FromColor, IntoColor}; pub use encoding::pixel::Pixel; pub use hues::{LabHue, RgbHue}; pub use matrix::Mat3; From 277263d4dfeb96d21e24b1d0c867a0b6219c1069 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 8 Jun 2018 10:06:37 +0200 Subject: [PATCH 3/5] Add missing error description --- palette/src/convert.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/palette/src/convert.rs b/palette/src/convert.rs index 567b7e41d..da566ac3f 100644 --- a/palette/src/convert.rs +++ b/palette/src/convert.rs @@ -520,7 +520,11 @@ impl OutOfBounds { } } -impl error::Error for OutOfBounds {} +impl error::Error for OutOfBounds { + fn description(&self) -> &str { + "Color conversion is out of bounds" + } +} impl Display for OutOfBounds { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { From 1a6e53f38abc98fe869be3ebd6c8f3090ed069de Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 9 Jun 2018 22:30:22 +0200 Subject: [PATCH 4/5] Changed convert to ConvertFrom/ConvertInto pair --- palette/src/convert.rs | 176 +++++++++++++++++++++-------------------- palette/src/lib.rs | 2 +- 2 files changed, 93 insertions(+), 85 deletions(-) diff --git a/palette/src/convert.rs b/palette/src/convert.rs index da566ac3f..5e8bf927b 100644 --- a/palette/src/convert.rs +++ b/palette/src/convert.rs @@ -1,12 +1,11 @@ use num_traits::Float; -use std::error; +use std::error::Error; use std::fmt::{self, Debug, Display, Formatter}; use {Component, Limited, Hsl, Hsv, Hwb, Lab, Lch, Xyz, Yxy}; use white_point::{D65, WhitePoint}; -use rgb::{Rgb, RgbSpace, RgbStandard}; -use luma::{Luma, LumaStandard}; -use Alpha; +use rgb::{Rgb, RgbSpace}; +use luma::Luma; use encoding::Linear; /// FromColor provides conversion from the colors. @@ -510,17 +509,19 @@ pub struct OutOfBounds { impl OutOfBounds { ///Create a new error wrapping a color + #[inline] fn new(color: T) -> Self { OutOfBounds { color } } ///Consume this error and return the wrapped color + #[inline] pub fn color(self) -> T { self.color } } -impl error::Error for OutOfBounds { +impl Error for OutOfBounds { fn description(&self) -> &str { "Color conversion is out of bounds" } @@ -532,52 +533,35 @@ impl Display for OutOfBounds { } } -///An extension for `Result` that offers a method to take out the color of `try_convert` result. -pub trait ResultExt { - ///Take the color out of this `Result`. - fn take(self) -> T; -} - -impl ResultExt for Result> { - #[inline] - fn take(self) -> T { - match self { - Ok(color) => color, - Err(color) => color.color(), - } - } -} - -///A trait for converting from one color to another. +///A trait for converting a color into another. /// ///`convert_unclamped` currently wraps the underlying `Into` implementation. -pub trait Convert: Sized + Into { - +pub trait ConvertInto: Into { ///Convert into T with values clamped to the color defined bounds /// ///``` - ///use palette::Convert; + ///use palette::ConvertInto; ///use palette::Limited; ///use palette::{Srgb, Lch}; /// /// - ///let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).convert(); + ///let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).convert_into(); ///assert!(rgb.is_valid()); ///``` - fn convert(self) -> T; + fn convert_into(self) -> T; - ///Convert into T, the resulting color might be invalid in it's color space + ///Convert into T. The resulting color might be invalid in its color space /// ///``` - ///use palette::Convert; + ///use palette::ConvertInto; ///use palette::Limited; ///use palette::{Srgb, Lch}; /// - ///let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).convert_unclamped(); + ///let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).convert_unclamped_into(); ///assert!(!rgb.is_valid()); ///``` #[inline] - fn convert_unclamped(self) -> T { + fn convert_unclamped_into(self) -> T { self.into() } @@ -585,10 +569,10 @@ pub trait Convert: Sized + Into { ///otherwise an `OutOfBounds` error is returned which contains the unclamped color. /// ///``` - ///use palette::Convert; + ///use palette::ConvertInto; ///use palette::{Srgb, Hsl}; /// - ///let rgb: Srgb = match Hsl::new(150.0, 1.0, 1.1).try_convert() { + ///let rgb: Srgb = match Hsl::new(150.0, 1.0, 1.1).try_convert_into() { /// Ok(color) => color, /// Err(err) => { /// println!("Color is out of bounds"); @@ -596,69 +580,92 @@ pub trait Convert: Sized + Into { /// }, ///}; ///``` - fn try_convert(self) -> Result>; + fn try_convert_into(self) -> Result>; } -macro_rules! expand_impl_convert { - (Alpha<$typ:ident<$gp:ident>>) => { - fn convert(self) -> Alpha<$typ<$gp, C>, C> { - let mut this = self.into(); - if !this.is_valid() { - this.clamp_self(); - } - this - } +///A trait for converting one color from another. +/// +///`convert_unclamped` currently wraps the underlying `Into` implementation. +pub trait ConvertFrom: From { + ///Convert from T with values clamped to the color defined bounds + /// + ///``` + ///use palette::ConvertFrom; + ///use palette::Limited; + ///use palette::{Srgb, Lch}; + /// + /// + ///let rgb = Srgb::convert_from(Lch::new(50.0, 100.0, -175.0)); + ///assert!(rgb.is_valid()); + ///``` + fn convert_from(_: T) -> Self; - fn try_convert(self) -> Result, C>, OutOfBounds, C>>> { - let this = self.into(); - if this.is_valid() { - Ok(this) - } else { - Err(OutOfBounds::new(this)) - } - } - }; - ($typ:ident<$gp:ident>) => { - fn convert(self) -> $typ<$gp, C> { - let mut this = self.into(); - if !this.is_valid() { - this.clamp_self(); - } - this - } + ///Convert from T. The resulting color might be invalid in its color space + /// + ///``` + ///use palette::ConvertFrom; + ///use palette::Limited; + ///use palette::{Srgb, Lch}; + /// + ///let rgb = Srgb::convert_unclamped_from(Lch::new(50.0, 100.0, -175.0)); + ///assert!(!rgb.is_valid()); + ///``` + #[inline] + fn convert_unclamped_from(val: T) -> Self { + Self::from(val) + } - fn try_convert(self) -> Result<$typ<$gp, C>, OutOfBounds<$typ<$gp, C>>> { - let this = self.into(); - if this.is_valid() { - Ok(this) - } else { - Err(OutOfBounds::new(this)) - } - } - }; + ///Convert from T, returning ok if the color is inside of its defined range, + ///otherwise an `OutOfBounds` error is returned which contains the unclamped color. + /// + ///``` + ///use palette::ConvertFrom; + ///use palette::{Srgb, Hsl}; + /// + ///let rgb = match Srgb::try_convert_from(Hsl::new(150.0, 1.0, 1.1)) { + /// Ok(color) => color, + /// Err(err) => { + /// println!("Color is out of bounds"); + /// err.color() + /// }, + ///}; + ///``` + fn try_convert_from(_: T) -> Result>; } -macro_rules! impl_convert { - (impl<$gp:ident: $gt:ident> $typ:ident<$gt2:ident>) => { - impl<$gp: $gt, C: Component, T> Convert<$typ<$gp, C>> for T where T: Into<$typ<$gp, C>> { - expand_impl_convert!($typ<$gp>); +impl ConvertFrom for U where U: From + Limited { + fn convert_from(t: T) -> U { + let mut this = U::from(t); + if !this.is_valid() { + this.clamp_self(); } + this + } - impl<$gp: $gt, C: Component, T> Convert, C>> for T where T: Into, C>> { - expand_impl_convert!(Alpha<$typ<$gp>>); - } - }; - (float_component impl<$gp:ident: $gt:ident> $typ:ident<$gt2:ident>) => { - impl<$gp: $gt, C: Float + Component, T> Convert<$typ<$gp, C>> for T where T: Into<$typ<$gp, C>> { - expand_impl_convert!($typ<$gp>); + fn try_convert_from(t: T) -> Result> { + let this = U::from(t); + if this.is_valid() { + Ok(this) + } else { + Err(OutOfBounds::new(this)) } + } +} - impl<$gp: $gt, C: Float + Component, T> Convert, C>> for T where T: Into, C>> { - expand_impl_convert!(Alpha<$typ<$gp>>); - } - }; +// ConvertFrom implies ConvertInto +impl ConvertInto for T where U: ConvertFrom { + #[inline] + fn convert_into(self) -> U { + U::convert_from(self) + } + + #[inline] + fn try_convert_into(self) -> Result> { + U::try_convert_from(self) + } } +/* impl_convert!(impl Rgb); impl_convert!(impl Luma); impl_convert!(float_component impl Hsl); @@ -668,6 +675,7 @@ impl_convert!(float_component impl Lab); impl_convert!(float_component impl Lch); impl_convert!(float_component impl Xyz); impl_convert!(float_component impl Yxy); +*/ macro_rules! impl_into_color { ($self_ty: ident, $from_fn: ident) => { diff --git a/palette/src/lib.rs b/palette/src/lib.rs index 918261d2a..fa1a9431e 100644 --- a/palette/src/lib.rs +++ b/palette/src/lib.rs @@ -182,7 +182,7 @@ pub use rgb::{GammaSrgb, GammaSrgba, LinSrgb, LinSrgba, Srgb, Srgba}; pub use xyz::{Xyz, Xyza}; pub use yxy::{Yxy, Yxya}; -pub use convert::{Convert, ResultExt, OutOfBounds, FromColor, IntoColor}; +pub use convert::{ConvertFrom, ConvertInto, OutOfBounds, FromColor, IntoColor}; pub use encoding::pixel::Pixel; pub use hues::{LabHue, RgbHue}; pub use matrix::Mat3; From 79c848c590a71702342dcc18300f00718461c854 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 10 Jun 2018 16:04:47 +0200 Subject: [PATCH 5/5] Make ConvertInto wrap ConvertFrom for convert_unclamped --- palette/src/convert.rs | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/palette/src/convert.rs b/palette/src/convert.rs index 5e8bf927b..29b055ae0 100644 --- a/palette/src/convert.rs +++ b/palette/src/convert.rs @@ -534,8 +534,6 @@ impl Display for OutOfBounds { } ///A trait for converting a color into another. -/// -///`convert_unclamped` currently wraps the underlying `Into` implementation. pub trait ConvertInto: Into { ///Convert into T with values clamped to the color defined bounds /// @@ -561,9 +559,7 @@ pub trait ConvertInto: Into { ///assert!(!rgb.is_valid()); ///``` #[inline] - fn convert_unclamped_into(self) -> T { - self.into() - } + fn convert_unclamped_into(self) -> T; ///Convert into T, returning ok if the color is inside of its defined range, ///otherwise an `OutOfBounds` error is returned which contains the unclamped color. @@ -585,7 +581,7 @@ pub trait ConvertInto: Into { ///A trait for converting one color from another. /// -///`convert_unclamped` currently wraps the underlying `Into` implementation. +///`convert_unclamped` currently wraps the underlying `From` implementation. pub trait ConvertFrom: From { ///Convert from T with values clamped to the color defined bounds /// @@ -659,24 +655,17 @@ impl ConvertInto for T where U: ConvertFrom { U::convert_from(self) } + #[inline] + fn convert_unclamped_into(self) -> U { + U::convert_unclamped_from(self) + } + #[inline] fn try_convert_into(self) -> Result> { U::try_convert_from(self) } } -/* -impl_convert!(impl Rgb); -impl_convert!(impl Luma); -impl_convert!(float_component impl Hsl); -impl_convert!(float_component impl Hsv); -impl_convert!(float_component impl Hwb); -impl_convert!(float_component impl Lab); -impl_convert!(float_component impl Lch); -impl_convert!(float_component impl Xyz); -impl_convert!(float_component impl Yxy); -*/ - macro_rules! impl_into_color { ($self_ty: ident, $from_fn: ident) => { impl IntoColor for $self_ty