From 3fe3039ef908314922a70f366f2bb27e099aa0b7 Mon Sep 17 00:00:00 2001 From: Erik Hedvall Date: Sat, 28 Mar 2020 18:28:50 +0100 Subject: [PATCH] Rewrite the conversion traits to work more like From and Into --- README.md | 23 +- palette/examples/color_scheme.rs | 8 +- palette/examples/gradient.rs | 18 +- palette/examples/hue.rs | 14 +- palette/examples/readme_examples.rs | 32 +- palette/examples/saturate.rs | 18 +- palette/examples/shade.rs | 14 +- palette/src/{ => alpha}/alpha.rs | 34 + palette/src/alpha/mod.rs | 163 ++ palette/src/chromatic_adaptation.rs | 12 +- palette/src/convert.rs | 1447 ++++++++--------- palette/src/encoding/gamma.rs | 2 +- palette/src/encoding/mod.rs | 2 +- palette/src/encoding/pixel/mod.rs | 7 +- palette/src/hsl.rs | 181 ++- palette/src/hsv.rs | 173 +- palette/src/hwb.rs | 100 +- palette/src/lab.rs | 53 +- palette/src/lch.rs | 51 +- palette/src/lib.rs | 9 +- palette/src/luma/luma.rs | 82 +- palette/src/luma/mod.rs | 2 +- palette/src/matrix.rs | 8 +- palette/src/rgb/mod.rs | 8 +- palette/src/rgb/packed.rs | 4 +- palette/src/rgb/rgb.rs | 333 ++-- palette/src/white_point.rs | 2 +- palette/src/xyz.rs | 51 +- palette/src/yxy.rs | 97 +- palette/tests/color_checker_data/babel.rs | 9 +- .../tests/color_checker_data/color_checker.rs | 9 +- palette/tests/convert/data_cie_15_2004.rs | 7 +- palette/tests/convert/data_ciede_2000.rs | 5 +- palette/tests/convert/data_color_mine.rs | 17 +- palette/tests/convert/lab_lch.rs | 15 +- palette/tests/pointer_dataset/pointer_data.rs | 7 +- palette_derive/Cargo.toml | 1 + palette_derive/src/alpha/mod.rs | 3 + palette_derive/src/alpha/with_alpha.rs | 124 ++ palette_derive/src/convert/from_color.rs | 424 ----- .../src/convert/from_color_unclamped.rs | 305 ++++ palette_derive/src/convert/into_color.rs | 328 ---- palette_derive/src/convert/mod.rs | 8 +- palette_derive/src/convert/shared.rs | 352 ---- palette_derive/src/convert/util.rs | 225 +++ palette_derive/src/encoding/pixel.rs | 155 +- palette_derive/src/lib.rs | 86 +- palette_derive/src/meta.rs | 227 --- palette_derive/src/meta/field_attributes.rs | 54 + palette_derive/src/meta/mod.rs | 267 +++ .../src/meta/type_item_attributes.rs | 154 ++ palette_derive/src/util.rs | 65 +- 52 files changed, 2926 insertions(+), 2869 deletions(-) rename palette/src/{ => alpha}/alpha.rs (95%) create mode 100644 palette/src/alpha/mod.rs create mode 100644 palette_derive/src/alpha/mod.rs create mode 100644 palette_derive/src/alpha/with_alpha.rs delete mode 100644 palette_derive/src/convert/from_color.rs create mode 100644 palette_derive/src/convert/from_color_unclamped.rs delete mode 100644 palette_derive/src/convert/into_color.rs delete mode 100644 palette_derive/src/convert/shared.rs create mode 100644 palette_derive/src/convert/util.rs delete mode 100644 palette_derive/src/meta.rs create mode 100644 palette_derive/src/meta/field_attributes.rs create mode 100644 palette_derive/src/meta/mod.rs create mode 100644 palette_derive/src/meta/type_item_attributes.rs diff --git a/README.md b/README.md index 72630feec..84b1229a8 100644 --- a/README.md +++ b/README.md @@ -59,15 +59,15 @@ Palette provides tools for both color manipulation and conversion between color RGB is probably the most widely known color space, but it's not the only one. You have probably used a color picker with a rainbow wheel and a brightness slider. That may have been an HSV or an HSL color picker, where the color is encoded as hue, saturation and brightness/lightness. There's also a group of color spaces that are designed to be perceptually uniform, meaning that the perceptual change is equal to the numerical change. -Selecting the proper color space can have a big impact on how the resulting image looks (as illustrated by some of the programs in `examples`), and Palette makes the conversion between them as easy as a call to `from` or `into`. +Selecting the proper color space can have a big impact on how the resulting image looks (as illustrated by some of the programs in `examples`), and Palette makes the conversion between them as easy as a call to `from_color` or `into_color`. This example takes an sRGB color, converts it to CIE L\*C\*h°, shifts its hue by 180° and converts it back to RGB: ```Rust -use palette::{Srgb, LinSrgb, Lch, Hue}; +use palette::{FromColor, Hue, IntoColor, Lch, Srgb}; -let lch_color: Lch = Srgb::new(0.8, 0.2, 0.1).into(); -let new_color = LinSrgb::from(lch_color.shift_hue(180.0)); +let lch_color: Lch = Srgb::new(0.8, 0.2, 0.1).into_color(); +let new_color = Srgb::from_color(lch_color.shift_hue(180.0)); ``` This results in the following two colors: @@ -78,16 +78,14 @@ This results in the following two colors: Palette comes with a number of color manipulation tools, that are implemented as traits. These includes lighten/darken, saturate/desaturate and hue shift. These traits are only implemented on types where they are meaningful, which means that you can't shift the hue of an RGB color without converting it to a color space where it makes sense. -This may seem limiting, but the point is to avoid inconsistent behavior due to arbitrary defaults, such as saturating a gray color to red when there is no available hue information. The abstract `Color` type does still support every operation, for when this is less important. - -The following example shows how the `Color` type is used to make a lighter and a desaturated version of the original. +The following example shows how to make a lighter and a desaturated version of the original. ```Rust -use palette::{Saturate, Shade, Srgb, Lch}; +use palette::{FromColor, Saturate, Shade, Srgb, Lch}; let color = Srgb::new(0.8, 0.2, 0.1).into_linear(); let lighter = color.lighten(0.1); -let desaturated = Lch::from(color).desaturate(0.5); +let desaturated = Lch::from_color(color).desaturate(0.5); ``` This results in the following three colors: @@ -101,7 +99,7 @@ There is also a linear gradient type which makes it easy to interpolate between The following example shows three gradients between the same two endpoints, but the top is in RGB space while the middle and bottom are in HSV space. The bottom gradient is an example of using the color sequence iterator. ```Rust -use palette::{LinSrgb, Hsv, Gradient}; +use palette::{FromColor, LinSrgb, Hsv, Gradient}; let grad1 = Gradient::new(vec![ LinSrgb::new(1.0, 0.1, 0.1), @@ -109,8 +107,8 @@ let grad1 = Gradient::new(vec![ ]); let grad2 = Gradient::new(vec![ - Hsv::from(LinSrgb::new(1.0, 0.1, 0.1)), - Hsv::from(LinSrgb::new(0.1, 1.0, 1.0)) + Hsv::from_color(LinSrgb::new(1.0, 0.1, 0.1)), + Hsv::from_color(LinSrgb::new(0.1, 1.0, 1.0)) ]); ``` @@ -125,6 +123,7 @@ Palette supports converting from a raw buffer of data into a color type using th Oftentimes, pixel data is stored in a raw buffer such as a `[u8; 3]`. `from_raw` can be used to convert into a Palette color, `into_format` converts from `Srgb` to `Srgb`, and finally `into_raw` to convert from a Palette color back to a `[u8;3]`. Here's an example of turning a buffer of `[u8; 3]` into a Palette `Srgb` color and back to a raw buffer. + ```rust use approx::assert_relative_eq; use palette::{Srgb, Pixel}; diff --git a/palette/examples/color_scheme.rs b/palette/examples/color_scheme.rs index 33d5ad44b..070b5a8be 100644 --- a/palette/examples/color_scheme.rs +++ b/palette/examples/color_scheme.rs @@ -1,4 +1,4 @@ -use palette::{Hue, Lch, LinSrgb, Pixel, Shade, Srgb}; +use palette::{Hue, IntoColor, Lch, LinSrgb, Pixel, Shade, Srgb}; use image::{GenericImage, GenericImageView, RgbImage, SubImage}; @@ -92,7 +92,7 @@ fn main() { let primary: Lch = Srgb::new(red, green, blue) .into_format::() .into_linear() - .into(); + .into_color(); //Generate the secondary colors, depending on the input arguments let secondary = match matches.subcommand() { @@ -144,14 +144,14 @@ fn main() { //Draw the primary swatches blit_shades( - primary.into(), + primary.into_color(), image.sub_image(0, 0, SWATCH_SIZE, SWATCH_SIZE), ); //Draw the secondary swatches for (n, color) in secondary.into_iter().enumerate() { blit_shades( - color.into(), + color.into_color(), image.sub_image((n as u32 + 1) * SWATCH_SIZE, 0, SWATCH_SIZE, SWATCH_SIZE), ); } diff --git a/palette/examples/gradient.rs b/palette/examples/gradient.rs index 8cfb5d82c..93d6d8479 100644 --- a/palette/examples/gradient.rs +++ b/palette/examples/gradient.rs @@ -5,7 +5,7 @@ fn main() { #[cfg(feature = "std")] fn main() { - use palette::{Gradient, Lch, LinSrgb, Pixel, Srgb}; + use palette::{FromColor, Gradient, IntoColor, Lch, LinSrgb, Pixel, Srgb}; use image::{GenericImage, GenericImageView, RgbImage}; @@ -26,17 +26,17 @@ fn main() { //The same colors and offsets as in grad1, but in a color space where the hue // is a component let grad3 = Gradient::new(vec![ - Lch::from(LinSrgb::new(1.0, 0.1, 0.1)), - Lch::from(LinSrgb::new(0.1, 0.1, 1.0)), - Lch::from(LinSrgb::new(0.1, 1.0, 0.1)), + Lch::from_color(LinSrgb::new(1.0, 0.1, 0.1)), + Lch::from_color(LinSrgb::new(0.1, 0.1, 1.0)), + Lch::from_color(LinSrgb::new(0.1, 1.0, 0.1)), ]); //The same colors and and color space as in grad3, but with the blue point // shifted down let grad4 = Gradient::with_domain(vec![ - (0.0, Lch::from(LinSrgb::new(1.0, 0.1, 0.1))), - (0.25, Lch::from(LinSrgb::new(0.1, 0.1, 1.0))), - (1.0, Lch::from(LinSrgb::new(0.1, 1.0, 0.1))), + (0.0, Lch::from_color(LinSrgb::new(1.0, 0.1, 0.1))), + (0.25, Lch::from_color(LinSrgb::new(0.1, 0.1, 1.0))), + (1.0, Lch::from_color(LinSrgb::new(0.1, 1.0, 0.1))), ]); let mut image = RgbImage::new(256, 128); @@ -49,8 +49,8 @@ fn main() { { let c1 = Srgb::from_linear(c1).into_format().into_raw(); let c2 = Srgb::from_linear(c2).into_format().into_raw(); - let c3 = Srgb::from_linear(c3.into()).into_format().into_raw(); - let c4 = Srgb::from_linear(c4.into()).into_format().into_raw(); + let c3 = Srgb::from_linear(c3.into_color()).into_format().into_raw(); + let c4 = Srgb::from_linear(c4.into_color()).into_format().into_raw(); { let mut sub_image = image.sub_image(i as u32, 0, 1, 31); diff --git a/palette/examples/hue.rs b/palette/examples/hue.rs index a0ab0d2a0..74ef9195c 100644 --- a/palette/examples/hue.rs +++ b/palette/examples/hue.rs @@ -1,4 +1,4 @@ -use palette::{Hsl, Hue, Lch, Pixel, Srgb}; +use palette::{FromColor, Hsl, Hue, IntoColor, Lch, Pixel, Srgb}; fn main() { let mut image = image::open("res/fruits.png") @@ -12,11 +12,15 @@ fn main() { let color = Srgb::from_raw(&pixel.0).into_format(); pixel.0 = if x < y { - let saturated = Hsl::from(color).shift_hue(180.0); - Srgb::from_linear(saturated.into()).into_format().into_raw() + let saturated = Hsl::from_color(color).shift_hue(180.0); + Srgb::from_linear(saturated.into_color()) + .into_format() + .into_raw() } else { - let saturated = Lch::from(color).shift_hue(180.0); - Srgb::from_linear(saturated.into()).into_format().into_raw() + let saturated = Lch::from_color(color).shift_hue(180.0); + Srgb::from_linear(saturated.into_color()) + .into_format() + .into_raw() }; } diff --git a/palette/examples/readme_examples.rs b/palette/examples/readme_examples.rs index 50966832a..d5d8fa470 100644 --- a/palette/examples/readme_examples.rs +++ b/palette/examples/readme_examples.rs @@ -1,22 +1,22 @@ use image::{GenericImage, GenericImageView, RgbImage}; #[cfg(feature = "std")] -use palette::{Gradient, LinSrgb, Mix}; +use palette::{FromColor, Gradient, IntoColor, LinSrgb, Mix}; use palette::{Pixel, Srgb}; mod color_spaces { use crate::display_colors; - use palette::{Hue, Lch, LinSrgb, Srgb}; + use palette::{FromColor, Hue, IntoColor, Lch, Srgb}; pub fn run() { - let lch_color: Lch = Srgb::new(0.8, 0.2, 0.1).into(); - let new_color = LinSrgb::from(lch_color.shift_hue(180.0)); + let lch_color: Lch = Srgb::new(0.8, 0.2, 0.1).into_color(); + let new_color = Srgb::from_color(lch_color.shift_hue(180.0)); display_colors( "examples/readme_color_spaces.png", &[ ::palette::Srgb::new(0.8, 0.2, 0.1).into_format(), - Srgb::from_linear(new_color.into()).into_format(), + new_color.into_format(), ], ); } @@ -24,19 +24,19 @@ mod color_spaces { mod manipulation { use crate::display_colors; - use palette::{Lch, Saturate, Shade, Srgb}; + use palette::{FromColor, IntoColor, Lch, Saturate, Shade, Srgb}; pub fn run() { let color = Srgb::new(0.8, 0.2, 0.1).into_linear(); let lighter = color.lighten(0.1); - let desaturated = Lch::from(color).desaturate(0.5); + let desaturated = Lch::from_color(color).desaturate(0.5); display_colors( "examples/readme_manipulation.png", &[ Srgb::from_linear(color.into()).into_format(), Srgb::from_linear(lighter.into()).into_format(), - Srgb::from_linear(desaturated.into()).into_format(), + Srgb::from_linear(desaturated.into_color()).into_format(), ], ); } @@ -45,7 +45,7 @@ mod manipulation { #[cfg(feature = "std")] mod gradients { use crate::display_gradients; - use palette::{Gradient, Hsv, LinSrgb}; + use palette::{FromColor, Gradient, Hsv, LinSrgb}; pub fn run() { let grad1 = Gradient::new(vec![ @@ -54,8 +54,8 @@ mod gradients { ]); let grad2 = Gradient::new(vec![ - Hsv::from(LinSrgb::new(1.0, 0.1, 0.1)), - Hsv::from(LinSrgb::new(0.1, 1.0, 1.0)), + Hsv::from_color(LinSrgb::new(1.0, 0.1, 0.1)), + Hsv::from_color(LinSrgb::new(0.1, 1.0, 1.0)), ]); display_gradients("examples/readme_gradients.png", grad1, grad2); @@ -86,8 +86,8 @@ fn display_gradients + Clone, B: Mix + Clone> grad1: Gradient, grad2: Gradient, ) where - LinSrgb: From, - LinSrgb: From, + LinSrgb: FromColor, + LinSrgb: FromColor, { let mut image = RgbImage::new(256, 96); { @@ -99,7 +99,7 @@ fn display_gradients + Clone, B: Mix + Clone> x, y, image::Rgb( - Srgb::from_linear(grad1.get(x as f32 / 255.0).into()) + Srgb::from_linear(grad1.get(x as f32 / 255.0).into_color()) .into_format() .into_raw(), ), @@ -117,7 +117,7 @@ fn display_gradients + Clone, B: Mix + Clone> x, y, image::Rgb( - Srgb::from_linear(grad2.get(x as f32 / 255.0).into()) + Srgb::from_linear(grad2.get(x as f32 / 255.0).into_color()) .into_format() .into_raw(), ), @@ -131,7 +131,7 @@ fn display_gradients + Clone, B: Mix + Clone> let swatch_size = 32; let mut v1 = Vec::new(); for color in grad2.take(8) { - let pix: [u8; 3] = Srgb::from_linear(LinSrgb::from(color)) + let pix: [u8; 3] = Srgb::from_linear(LinSrgb::from_color(color)) .into_format() .into_raw(); v1.push(pix); diff --git a/palette/examples/saturate.rs b/palette/examples/saturate.rs index a4e5e3dc3..0d52833c4 100644 --- a/palette/examples/saturate.rs +++ b/palette/examples/saturate.rs @@ -1,4 +1,4 @@ -use palette::{Hsl, Lch, Pixel, Saturate, Srgb}; +use palette::{Hsl, IntoColor, Lch, Pixel, Saturate, Srgb}; use image::{GenericImage, GenericImageView}; @@ -20,13 +20,17 @@ fn main() { let color: Hsl = Srgb::from_raw(&sub_image.get_pixel(x, y).0) .into_format() .into_linear() - .into(); + .into_color(); let saturated = color.saturate(0.8); sub_image.put_pixel( x, y, - image::Rgb(Srgb::from_linear(saturated.into()).into_format().into_raw()), + image::Rgb( + Srgb::from_linear(saturated.into_color()) + .into_format() + .into_raw(), + ), ); } } @@ -40,13 +44,17 @@ fn main() { let color: Lch = Srgb::from_raw(&sub_image.get_pixel(x, y).0) .into_format() .into_linear() - .into(); + .into_color(); let saturated = color.saturate(0.8); sub_image.put_pixel( x, y, - image::Rgb(Srgb::from_linear(saturated.into()).into_format().into_raw()), + image::Rgb( + Srgb::from_linear(saturated.into_color()) + .into_format() + .into_raw(), + ), ); } } diff --git a/palette/examples/shade.rs b/palette/examples/shade.rs index cc2b50e81..f2e17131b 100644 --- a/palette/examples/shade.rs +++ b/palette/examples/shade.rs @@ -1,12 +1,12 @@ -use palette::{Hsv, Lab, LinSrgb, Pixel, Shade, Srgb}; +use palette::{FromColor, Hsv, IntoColor, Lab, LinSrgb, Pixel, Shade, Srgb}; use image::{GenericImage, GenericImageView, RgbImage}; fn main() { //The same color in linear RGB, CIE L*a*b*, and HSV let rgb = LinSrgb::new(0.5, 0.0, 0.0); - let lab = Lab::from(rgb); - let hsv = Hsv::from(rgb); + let lab = Lab::from_color(rgb); + let hsv = Hsv::from_color(rgb); let mut image = RgbImage::new(220, 193); @@ -38,10 +38,10 @@ fn main() { } } - let lab1 = Srgb::from_linear(lab.darken(0.05 * i as f32).into()) + let lab1 = Srgb::from_linear(lab.darken(0.05 * i as f32).into_color()) .into_format() .into_raw(); - let lab2 = Srgb::from_linear(lab.lighten(0.05 * i as f32).into()) + let lab2 = Srgb::from_linear(lab.lighten(0.05 * i as f32).into_color()) .into_format() .into_raw(); @@ -65,10 +65,10 @@ fn main() { } } - let hsv1 = Srgb::from_linear(hsv.darken(0.05 * i as f32).into()) + let hsv1 = Srgb::from_linear(hsv.darken(0.05 * i as f32).into_color()) .into_format() .into_raw(); - let hsv2 = Srgb::from_linear(hsv.lighten(0.05 * i as f32).into()) + let hsv2 = Srgb::from_linear(hsv.lighten(0.05 * i as f32).into_color()) .into_format() .into_raw(); diff --git a/palette/src/alpha.rs b/palette/src/alpha/alpha.rs similarity index 95% rename from palette/src/alpha.rs rename to palette/src/alpha/alpha.rs index cae4ea449..006e38a0f 100644 --- a/palette/src/alpha.rs +++ b/palette/src/alpha/alpha.rs @@ -10,10 +10,12 @@ use rand::distributions::{Distribution, Standard}; use rand::Rng; use crate::blend::PreAlpha; +use crate::convert::{FromColorUnclamped, IntoColorUnclamped}; use crate::encoding::pixel::RawPixel; use crate::float::Float; use crate::{ clamp, Blend, Component, ComponentWise, GetHue, Hue, Limited, Mix, Pixel, Saturate, Shade, + WithAlpha, }; /// An alpha component wrapper for colors. @@ -42,6 +44,38 @@ impl Alpha { } } +impl, C2, T: Component> FromColorUnclamped for Alpha +where + C1::Color: IntoColorUnclamped, +{ + fn from_color_unclamped(other: C1) -> Self { + let (color, alpha) = other.split(); + + Alpha { + color: color.into_color_unclamped(), + alpha, + } + } +} + +impl WithAlpha for Alpha { + type Color = C; + type WithAlpha = Self; + + fn with_alpha(mut self, alpha: A) -> Self::WithAlpha { + self.alpha = alpha; + self + } + + fn without_alpha(self) -> Self::Color { + self.color + } + + fn split(self) -> (Self::Color, A) { + (self.color, self.alpha) + } +} + impl Deref for Alpha { type Target = C; diff --git a/palette/src/alpha/mod.rs b/palette/src/alpha/mod.rs new file mode 100644 index 000000000..c65bf345a --- /dev/null +++ b/palette/src/alpha/mod.rs @@ -0,0 +1,163 @@ +use crate::Component; + +#[doc(hidden)] +pub use palette_derive::WithAlpha; + +pub use self::alpha::*; + +mod alpha; + +/// A trait for color types that can have or be given transparency (alpha channel). +/// +/// `WithAlpha` is an interface for adding, removing and setting the alpha +/// channel of a color type. The color type itself doesn't need to store the +/// transparency value as it can be transformed into or wrapped in a type that +/// has a representation of transparency. This would typically be done by +/// wrapping it in an [`Alpha`](struct.Alpha.html) instance. +/// +/// # Deriving +/// The trait is trivial enough to be automatically derived. If the color type +/// has a field for transparency (an alpha channel), it has to be marked with +/// `#[palette(alpha)]` to be taken into account. +/// +/// Derived without an internal alpha channel: +/// +/// ``` +/// use palette::WithAlpha; +/// +/// #[derive(WithAlpha)] +/// struct CustomColor { +/// redness: f32, +/// glow: f32, +/// glitter: f32, +/// } +/// +/// let color = CustomColor { +/// redness: 0.8, +/// glow: 2.5, +/// glitter: 1000.0 +/// }; +/// let transparent = color.with_alpha(0.3); +/// +/// assert_eq!(transparent.alpha, 0.3); +/// ``` +/// +/// Derived with an internal alpha channel: +/// +/// ``` +/// use palette::WithAlpha; +/// +/// #[derive(WithAlpha)] +/// struct CustomColor { +/// redness: f32, +/// glow: f32, +/// glitter: f32, +/// +/// #[palette(alpha)] +/// alpha: u8, +/// } +/// +/// let color = CustomColor { +/// redness: 0.8, +/// glow: 2.5, +/// glitter: 1000.0, +/// alpha: 255 +/// }; +/// let transparent = color.with_alpha(10); +/// +/// assert_eq!(transparent.alpha, 10); +/// ``` +pub trait WithAlpha: Sized { + /// The opaque color type, without any transparency. + /// + /// This is typically `Self`. + type Color; + + /// The color type with transparency applied. + /// + /// This is typically `Alpha`. + type WithAlpha: WithAlpha; + + /// Transforms the color into a transparent color with the provided + /// alpha value. If `Self` already has a transparency, it is + /// overwritten. + /// + /// ``` + /// use palette::{Srgb, WithAlpha}; + /// + /// let color = Srgb::new(255u8, 0, 255); + /// + /// // This results in an `Alpha, f32>` + /// let transparent = color.with_alpha(0.3f32); + /// assert_eq!(transparent.alpha, 0.3); + /// + /// // This changes the transparency to 0.8 + /// let transparent = transparent.with_alpha(0.8f32); + /// assert_eq!(transparent.alpha, 0.8); + /// ``` + fn with_alpha(self, alpha: A) -> Self::WithAlpha; + + /// Removes the transparency from the color. If `Self::Color` has + /// an internal transparency field, that field will be set to + /// `A::max_intensity()` to make it opaque. + /// + /// ``` + /// use palette::{Srgba, Srgb, WithAlpha}; + /// + /// let transparent = Srgba::new(255u8, 0, 255, 10); + /// + /// // This unwraps the color information from the `Alpha` wrapper + /// let color = transparent.without_alpha(); + /// assert_eq!(transparent.color, color); + /// ``` + fn without_alpha(self) -> Self::Color; + + /// Splits the color into separate color and transparency values. + /// + /// A color without any transparency field will return + /// `A::max_intensity()` instead. If `Self::Color` has an internal + /// transparency field, that field will be set to + /// `A::max_intensity()` to make it opaque. + /// + /// ``` + /// use palette::{Srgba, Srgb, WithAlpha}; + /// + /// let transparent = Srgba::new(255u8, 0, 255, 10); + /// + /// // This unwraps both the color and alpha from the `Alpha` wrapper + /// let (color, alpha) = transparent.split(); + /// assert_eq!(transparent.color, color); + /// assert_eq!(transparent.alpha, alpha); + /// ``` + fn split(self) -> (Self::Color, A); + + /// Transforms the color into a fully opaque color with a transparency + /// field. If `Self` already has a transparency, it is overwritten. + /// + /// ``` + /// use palette::{Srgb, Srgba, WithAlpha}; + /// + /// let color = Srgb::new(255u8, 0, 255); + /// + /// let opaque: Srgba = color.opaque(); + /// assert_eq!(opaque.alpha, 255); + /// ``` + fn opaque(self) -> Self::WithAlpha { + self.with_alpha(A::max_intensity()) + } + + /// Transforms the color into a fully transparent color. If `Self` + /// already has a transparency, it is overwritten. + /// + /// ``` + /// use palette::{Srgb, Srgba, WithAlpha}; + /// + /// let color = Srgb::new(255u8, 0, 255); + /// + /// let transparent: Srgba = color.transparent(); + /// assert_eq!(transparent.alpha, 0); + /// ``` + fn transparent(self) -> Self::WithAlpha { + self.with_alpha(A::zero()) + } +} diff --git a/palette/src/chromatic_adaptation.rs b/palette/src/chromatic_adaptation.rs index 5eb4e9032..e372fd4d8 100644 --- a/palette/src/chromatic_adaptation.rs +++ b/palette/src/chromatic_adaptation.rs @@ -23,11 +23,12 @@ //! //Should print {x: 0.257963, y: 0.139776,z: 0.058825} //! println!("{:?}", c) //! ``` +use crate::convert::{FromColorUnclamped, IntoColorUnclamped}; use crate::float::Float; use crate::from_f64; use crate::matrix::{multiply_3x3, multiply_xyz, Mat3}; use crate::white_point::WhitePoint; -use crate::{FloatComponent, FromColor, IntoColor, Xyz}; +use crate::{FloatComponent, Xyz}; /// Chromatic adaptation methods implemented in the library pub enum Method { @@ -165,14 +166,14 @@ where T: FloatComponent, Swp: WhitePoint, Dwp: WhitePoint, - S: IntoColor, - D: FromColor, + S: IntoColorUnclamped>, + D: FromColorUnclamped>, { fn adapt_from_using>(color: S, method: M) -> D { - let src_xyz: Xyz = color.into_xyz(); + let src_xyz: Xyz = color.into_color_unclamped(); let transform_matrix = method.generate_transform_matrix(); let dst_xyz: Xyz = multiply_xyz(&transform_matrix, &src_xyz); - D::from_xyz(dst_xyz) + D::from_color_unclamped(dst_xyz) } } @@ -210,7 +211,6 @@ where #[cfg(test)] mod test { - use super::{AdaptFrom, AdaptInto, Method, TransformMatrix}; use crate::white_point::{A, C, D50, D65}; use crate::Xyz; diff --git a/palette/src/convert.rs b/palette/src/convert.rs index 7a18e8e9c..a4ac79880 100644 --- a/palette/src/convert.rs +++ b/palette/src/convert.rs @@ -1,488 +1,277 @@ -use core::fmt::{self, Display, Formatter}; - -use crate::encoding::Linear; -use crate::luma::Luma; -use crate::rgb::{Rgb, RgbSpace}; -use crate::white_point::{WhitePoint, D65}; -use crate::{FloatComponent, Hsl, Hsv, Hwb, Lab, Lch, Limited, Xyz, Yxy}; +//! Traits for converting between color spaces. +//! +//! # Deriving +//! +//! `FromColorUnclamped` can be derived in a mostly automatic way. +//! The default minimum requirement is to implement `FromColorUnclamped`, but it can +//! also be customized to make use of generics and have other manual implementations. +//! +//! It is also recommended to derive or implement [`WithAlpha`](../trait.WithAlpha.html), +//! to be able to convert between all `Alpha` wrapped color types. +//! +//! ## Configuration Attributes +//! +//! The derives can be configured using one or more `#[palette(...)]` attributes. +//! They can be attached to either the item itself, or to the fields. +//! +//! ``` +//! # use palette::rgb::RgbSpace; +//! # use palette::convert::FromColorUnclamped; +//! # use palette::{Xyz, Component, FloatComponent}; +//! # +//! #[palette( +//! component = "T", +//! rgb_space = "S", +//! white_point = "S::WhitePoint", +//! )] +//! #[derive(FromColorUnclamped)] +//! #[repr(C)] +//! struct ExampleType { +//! // ... +//! #[palette(alpha)] +//! alpha: T, +//! space: std::marker::PhantomData, +//! } +//! +//! # impl FromColorUnclamped> for ExampleType { +//! # fn from_color_unclamped(color: Xyz) -> Self { +//! # ExampleType {alpha: T::max_intensity(), space: std::marker::PhantomData} +//! # } +//! # } +//! # +//! # impl FromColorUnclamped> for Xyz { +//! # fn from_color_unclamped(color: ExampleType) -> Self { +//! # Xyz::default() +//! # } +//! # } +//! ``` +//! +//! ### Item Attributes +//! +//! * `skip_derives(Luma, Rgb)`: No conversion derives will be implemented for these colors. +//! They are instead to be implemented manually, and serve as the basis for the automatic +//! implementations. +//! +//! * `white_point = "some::white_point::Type"`: Sets the white +//! point type that should be used when deriving. The default is `D65`, but it +//! may be any other type, including type parameters. +//! +//! * `component = "some::component::Type"`: Sets the color +//! component type that should be used when deriving. The default is `f32`, but +//! it may be any other type, including type parameters. +//! +//! * `rgb_space = "some::rgb_space::Type"`: Sets the RGB space +//! type that should be used when deriving. The default is to either use `Srgb` +//! or a best effort to convert between spaces, so sometimes it has to be set +//! to a specific type. This also accepts type parameters. +//! +//! ## Field Attributes +//! +//! * `alpha`: Specifies field as the color's transparency value. +//! +//! ## Examples +//! +//! Minimum requirements implementation: +//! +//! ```rust +//! use palette::convert::FromColorUnclamped; +//! use palette::{Srgb, Xyz, IntoColor}; +//! +//! /// A custom version of Xyz that stores integer values from 0 to 100. +//! #[derive(PartialEq, Debug, FromColorUnclamped)] +//! struct Xyz100 { +//! x: u8, +//! y: u8, +//! z: u8, +//! } +//! +//! // We have to implement at least one "manual" conversion. The default +//! // is to and from `Xyz`, but it can be customized with `skip_derives(...)`. +//! impl FromColorUnclamped for Xyz100 { +//! fn from_color_unclamped(color: Xyz) -> Xyz100 { +//! Xyz100 { +//! x: (color.x * 100.0) as u8, +//! y: (color.y * 100.0) as u8, +//! z: (color.z * 100.0) as u8, +//! } +//! } +//! } +//! +//! impl FromColorUnclamped for Xyz { +//! fn from_color_unclamped(color: Xyz100) -> Xyz { +//! Xyz::new( +//! color.x as f32 / 100.0, +//! color.y as f32 / 100.0, +//! color.z as f32 / 100.0, +//! ) +//! } +//! } +//! +//! fn main() { +//! // Start with an Xyz100 color. +//! let xyz = Xyz100 { +//! x: 59, +//! y: 75, +//! z: 42, +//! }; +//! +//! // Convert the color to sRGB. +//! let rgb: Srgb = xyz.into_color(); +//! +//! assert_eq!(rgb.into_format(), Srgb::new(196u8, 238, 154)); +//! } +//! ``` +//! +//! With generic components: +//! +//! ```rust +//! #[macro_use] +//! extern crate approx; +//! +//! use palette::rgb::{Rgb, RgbSpace, RgbStandard}; +//! use palette::encoding::Linear; +//! use palette::white_point::D65; +//! use palette::convert::{FromColorUnclamped, IntoColorUnclamped}; +//! use palette::{FloatComponent, Hsv, Pixel, Srgb, IntoColor}; +//! +//! /// sRGB, but with a reversed memory layout. +//! #[palette( +//! skip_derives(Rgb), +//! component = "T", +//! rgb_space = "palette::encoding::Srgb" +//! )] +//! #[derive(Copy, Clone, Pixel, FromColorUnclamped)] +//! #[repr(C)] // Makes sure the memory layout is as we want it. +//! struct Bgr { +//! blue: T, +//! green: T, +//! red: T, +//! } +//! +//! // It converts from and into any linear Rgb type that has the +//! // D65 white point, which is the default if we don't specify +//! // anything else with the `white_point` attribute argument. +//! impl FromColorUnclamped> for Rgb +//! where +//! T: FloatComponent, +//! S: RgbStandard, +//! S::Space: RgbSpace +//! { +//! fn from_color_unclamped(color: Bgr) -> Rgb { +//! Srgb::new(color.red, color.green, color.blue) +//! .into_color_unclamped() +//! } +//! } +//! +//! impl FromColorUnclamped> for Bgr +//! where +//! T: FloatComponent, +//! S: RgbStandard, +//! S::Space: RgbSpace +//! { +//! fn from_color_unclamped(color: Rgb) -> Bgr { +//! let color = Srgb::from_color_unclamped(color); +//! Bgr { +//! blue: color.blue, +//! green: color.green, +//! red: color.red, +//! } +//! } +//! } +//! +//! fn main() { +//! let buffer = vec![ +//! 0.0f64, +//! 0.0, +//! 0.0, +//! 0.0, +//! 0.7353569830524495, +//! 0.5370987304831942, +//! ]; +//! let hsv: Hsv<_, f64> = Bgr::from_raw_slice(&buffer)[1].into_color(); +//! +//! assert_relative_eq!(hsv, Hsv::new(90.0, 1.0, 0.5)); +//! } +//! ``` +//! +//! With alpha component: +//! +//! ```rust +//! #[macro_use] +//! extern crate approx; +//! +//! use palette::{LinSrgba, Srgb, IntoColor, WithAlpha}; +//! use palette::rgb::{Rgb, RgbSpace, RgbStandard}; +//! use palette::encoding::Linear; +//! use palette::white_point::D65; +//! use palette::convert::{FromColorUnclamped, IntoColorUnclamped}; +//! +//! /// CSS style sRGB. +//! #[palette( +//! skip_derives(Rgb), +//! rgb_space = "palette::encoding::Srgb" +//! )] +//! #[derive(PartialEq, Debug, FromColorUnclamped, WithAlpha)] +//! struct CssRgb { +//! red: u8, +//! green: u8, +//! blue: u8, +//! #[palette(alpha)] +//! alpha: f32, +//! } +//! +//! // We will write a conversion function for opaque RGB and +//! // `impl_default_conversions` will take care of preserving +//! // the transparency for us. +//! impl FromColorUnclamped> for CssRgb +//! where +//! S: RgbStandard, +//! S::Space: RgbSpace, +//! { +//! fn from_color_unclamped(color: Rgb) -> CssRgb{ +//! let srgb = Srgb::from_color_unclamped(color) +//! .into_format(); +//! +//! CssRgb { +//! red: srgb.red, +//! green: srgb.green, +//! blue: srgb.blue, +//! alpha: 1.0 +//! } +//! } +//! } +//! +//! impl FromColorUnclamped for Rgb +//! where +//! S: RgbStandard, +//! S::Space: RgbSpace, +//! { +//! fn from_color_unclamped(color: CssRgb) -> Rgb{ +//! Srgb::new(color.red, color.green, color.blue) +//! .into_format() +//! .into_color_unclamped() +//! } +//! } +//! +//! fn main() { +//! let css_color = CssRgb { +//! red: 187, +//! green: 0, +//! blue: 255, +//! alpha: 0.3, +//! }; +//! let color: LinSrgba = css_color.into_color(); +//! +//! assert_relative_eq!(color, LinSrgba::new(0.496933, 0.0, 1.0, 0.3)); +//! } +//! ``` -/// FromColor provides conversion from the colors. -/// -/// It requires from_xyz, when implemented manually, and derives conversion to -/// other colors as a default from this. These defaults must be overridden when -/// direct conversion exists between colors. For example, Luma has direct -/// conversion to LinRgb. So from_rgb conversion for Luma and from_luma for -/// LinRgb is implemented directly. The from for the same color must override -/// the default. For example, from_rgb for LinRgb will convert via Xyz which -/// needs to be overridden with self to avoid the unnecessary conversion. -/// -/// # Deriving -/// -/// `FromColor` can be derived in a mostly automatic way. The strength of -/// deriving it is that it will also derive `From` implementations for all of -/// the `palette` color types. The minimum requirement is to implement -/// `From`, but it can also be customized to make use of generics -/// and have other manual implementations. -/// -/// ## Item Attributes -/// -/// * `#[palette_manual_from(Luma, Rgb = "from_rgb_internal")]`: Specifies the -/// color types that -/// the the custom color type already has `From` implementations for. Adding `= -/// "function_name"` tells it to use that function instead of a `From` -/// implementation. The default, when omitted, is to require `From` to be -/// implemented. -/// -/// * `#[palette_white_point = "some::white_point::Type"]`: Sets the white -/// point type that should -/// be used when deriving. The default is `D65`, but it may be any other type, -/// including type parameters. -/// -/// * `#[palette_component = "some::component::Type"]`: Sets the color -/// component type that should -/// be used when deriving. The default is `f32`, but it may be any other type, -/// including type parameters. -/// -/// * `#[palette_rgb_space = "some::rgb_space::Type"]`: Sets the RGB space type -/// that should -/// be used when deriving. The default is to either use `Srgb` or a best effort -/// to convert between spaces, so sometimes it has to be set to a specific type. -/// This does also accept type parameters. -/// -/// ## Field Attributes -/// -/// * `#[palette_alpha]`: Specifies that the field is the color's transparency -/// value. -/// -/// ## Examples -/// -/// Minimum requirements implementation: -/// -/// ```rust -/// use palette::{FromColor, Srgb, Xyz}; -/// -/// /// A custom version of Xyz that stores integer values from 0 to 100. -/// #[derive(PartialEq, Debug, FromColor)] -/// struct Xyz100 { -/// x: u8, -/// y: u8, -/// z: u8, -/// } -/// -/// // We have to at least implement conversion from Xyz if we don't -/// // specify anything else, using the `palette_manual_from` attribute. -/// impl From for Xyz100 { -/// fn from(color: Xyz) -> Self { -/// let scaled = color * 100.0; -/// Xyz100 { -/// x: scaled.x.max(0.0).min(100.0) as u8, -/// y: scaled.y.max(0.0).min(100.0) as u8, -/// z: scaled.z.max(0.0).min(100.0) as u8, -/// } -/// } -/// } -/// -/// // Start with an sRGB color and convert it from u8 to f32, -/// // which is the default component type. -/// let rgb = Srgb::new(196u8, 238, 155).into_format(); -/// -/// // Convert the rgb color to our own format. -/// let xyz = Xyz100::from(rgb); -/// -/// assert_eq!( -/// xyz, -/// Xyz100 { -/// x: 59, -/// y: 75, -/// z: 42, -/// } -/// ); -/// ``` -/// -/// With generic components: -/// -/// ```rust -/// use approx::assert_relative_eq; -/// -/// use palette::{FloatComponent, FromColor, Hsv, Pixel, Srgb}; -/// use palette::rgb::{Rgb, RgbSpace}; -/// use palette::encoding::Linear; -/// use palette::white_point::D65; -/// -/// /// sRGB, but with a reversed memory layout. -/// #[derive(PartialEq, Debug, FromColor, Pixel)] -/// #[palette_manual_from(Rgb = "from_rgb_internal")] -/// #[palette_component = "T"] -/// #[repr(C)] // Makes sure the memory layout is as we want it. -/// struct Bgr { -/// blue: T, -/// green: T, -/// red: T, -/// } -/// -/// // Rgb is a bit more complex than other colors, so we are -/// // implementing a private conversion function and letting it -/// // derive `From` automatically. It will take a round trip -/// // through linear format, but that's fine in this case. -/// impl Bgr { -/// // It converts from any linear Rgb type that has the D65 -/// // white point, which is the default if we don't specify -/// // anything else with the `palette_white_point` attribute. -/// fn from_rgb_internal(color: Rgb, T>) -> Self -/// where -/// S: RgbSpace, -/// { -/// let srgb = Srgb::from_rgb(color); -/// -/// Bgr { -/// blue: srgb.blue, -/// green: srgb.green, -/// red: srgb.red, -/// } -/// } -/// } -/// -/// let mut buffer = vec![0.0f64, 0.0, 0.0, 0.0, 0.0, 0.0]; -/// { -/// let bgr_buffer = Bgr::from_raw_slice_mut(&mut buffer); -/// bgr_buffer[1] = Hsv::new(90.0, 1.0, 0.5).into(); -/// } -/// -/// assert_relative_eq!(buffer[3], 0.0); -/// assert_relative_eq!(buffer[4], 0.7353569830524495); -/// assert_relative_eq!(buffer[5], 0.5370987304831942); -/// ``` -/// -/// With alpha component: -/// -/// ```rust -/// use palette::{FromColor, LinSrgba, Srgb}; -/// use palette::rgb::{Rgb, RgbSpace}; -/// use palette::encoding::Linear; -/// use palette::white_point::D65; -/// -/// /// CSS style sRGB. -/// #[derive(PartialEq, Debug, FromColor)] -/// #[palette_manual_from(Rgb = "from_rgb_internal")] -/// struct CssRgb { -/// red: u8, -/// green: u8, -/// blue: u8, -/// #[palette_alpha] -/// alpha: f32, -/// } -/// -/// // We will write a conversion function for opaque RGB and derive -/// // will take care of preserving the transparency for us. -/// impl CssRgb { -/// fn from_rgb_internal(color: Rgb, f32>) -> Self -/// where -/// S: RgbSpace, -/// { -/// // Convert to u8 sRGB -/// let srgb = Srgb::from_rgb(color).into_format(); -/// -/// CssRgb { -/// red: srgb.red, -/// green: srgb.green, -/// blue: srgb.blue, -/// alpha: 1.0, -/// } -/// } -/// } -/// -/// let color = LinSrgba::new(0.5, 0.0, 1.0, 0.3); -/// let css_color = CssRgb::from(color); -/// -/// assert_eq!( -/// css_color, -/// CssRgb { -/// red: 188, -/// green: 0, -/// blue: 255, -/// alpha: 0.3, -/// } -/// ); -/// ``` -pub trait FromColor: Sized -where - T: FloatComponent, - Wp: WhitePoint, -{ - /// Convert from XYZ color space - fn from_xyz(inp: Xyz) -> Self; - - /// Convert from Yxy color space - fn from_yxy(inp: Yxy) -> Self { - Self::from_xyz(inp.into_xyz()) - } - - /// Convert from L\*a\*b\* color space - fn from_lab(inp: Lab) -> Self { - Self::from_xyz(inp.into_xyz()) - } - - /// Convert from L\*C\*h° color space - fn from_lch(inp: Lch) -> Self { - Self::from_lab(inp.into_lab()) - } - - /// Convert from RGB color space - fn from_rgb>(inp: Rgb, T>) -> Self { - Self::from_xyz(inp.into_xyz()) - } - - /// Convert from HSL color space - fn from_hsl>(inp: Hsl) -> Self { - Self::from_rgb(Rgb::, T>::from_hsl(inp)) - } - - /// Convert from HSV color space - fn from_hsv>(inp: Hsv) -> Self { - Self::from_rgb(Rgb::, T>::from_hsv(inp)) - } - - /// Convert from HWB color space - fn from_hwb>(inp: Hwb) -> Self { - Self::from_hsv(Hsv::::from_hwb(inp)) - } - - /// Convert from Luma - fn from_luma(inp: Luma, T>) -> Self { - Self::from_xyz(inp.into_xyz()) - } -} - -/// IntoColor provides conversion to the colors. -/// -/// It requires into_xyz, when implemented manually, and derives conversion to -/// other colors as a default from this. These defaults must be overridden when -/// direct conversion exists between colors. -/// -/// # Deriving -/// -/// `IntoColor` can be derived in a mostly automatic way. The strength of -/// deriving it is that it will also derive `Into` implementations for all of -/// the `palette` color types. The minimum requirement is to implement -/// `Into`, but it can also be customized to make use of generics -/// and have other manual implementations. -/// -/// ## Item Attributes -/// -/// * `#[palette_manual_into(Luma, Rgb = "into_rgb_internal")]`: Specifies the -/// color types that -/// the the custom color type already has `Into` implementations for. Adding `= -/// "function_name"` tells it to use that function instead of an `Into` -/// implementation. The default, when omitted, is to require `Into` to be -/// implemented. -/// -/// * `#[palette_white_point = "some::white_point::Type"]`: Sets the white -/// point type that should -/// be used when deriving. The default is `D65`, but it may be any other type, -/// including type parameters. -/// -/// * `#[palette_component = "some::component::Type"]`: Sets the color -/// component type that should -/// be used when deriving. The default is `f32`, but it may be any other type, -/// including type parameters. -/// -/// * `#[palette_rgb_space = "some::rgb_space::Type"]`: Sets the RGB space type -/// that should -/// be used when deriving. The default is to either use `Srgb` or a best effort -/// to convert between spaces, so sometimes it has to be set to a specific type. -/// This does also accept type parameters. -/// -/// ## Field Attributes -/// -/// * `#[palette_alpha]`: Specifies that the field is the color's transparency -/// value. -/// -/// ## Examples -/// -/// Minimum requirements implementation: -/// -/// ```rust -/// use palette::{IntoColor, Srgb, Xyz}; -/// -/// /// A custom version of Xyz that stores integer values from 0 to 100. -/// #[derive(PartialEq, Debug, IntoColor)] -/// struct Xyz100 { -/// x: u8, -/// y: u8, -/// z: u8, -/// } -/// -/// // We have to at least implement conversion into Xyz if we don't -/// // specify anything else, using the `palette_manual_into` attribute. -/// impl Into for Xyz100 { -/// fn into(self) -> Xyz { -/// Xyz::new( -/// self.x as f32 / 100.0, -/// self.y as f32 / 100.0, -/// self.z as f32 / 100.0, -/// ) -/// } -/// } -/// -/// // Start with an Xyz100 color. -/// let xyz = Xyz100 { -/// x: 59, -/// y: 75, -/// z: 42, -/// }; -/// -/// // Convert the color to sRGB. -/// let rgb: Srgb = xyz.into(); -/// -/// assert_eq!(rgb.into_format(), Srgb::new(196u8, 238, 154)); -/// ``` -/// -/// With generic components: -/// -/// ```rust -/// use approx::assert_relative_eq; -/// -/// use palette::{FloatComponent, Hsv, IntoColor, Pixel, Srgb}; -/// use palette::rgb::{Rgb, RgbSpace}; -/// use palette::encoding::{Linear, self}; -/// use palette::white_point::D65; -/// -/// type Hsv64 = Hsv; -/// -/// /// sRGB, but with a reversed memory layout. -/// #[derive(Copy, Clone, IntoColor, Pixel)] -/// #[palette_manual_into(Rgb = "into_rgb_internal")] -/// #[palette_component = "T"] -/// #[repr(C)] // Makes sure the memory layout is as we want it. -/// struct Bgr { -/// blue: T, -/// green: T, -/// red: T, -/// } -/// -/// // Rgb is a bit more complex than other colors, so we are -/// // implementing a private conversion function and letting it -/// // derive `Into` automatically. -/// impl Bgr { -/// // It converts from any linear Rgb type that has the D65 -/// // white point, which is the default if we don't specify -/// // anything else with the `palette_white_point` attribute. -/// fn into_rgb_internal(self) -> Rgb, T> -/// where -/// S: RgbSpace, -/// { -/// Srgb::new(self.red, self.green, self.blue).into_rgb() -/// } -/// } -/// -/// let buffer = vec![ -/// 0.0f64, -/// 0.0, -/// 0.0, -/// 0.0, -/// 0.7353569830524495, -/// 0.5370987304831942, -/// ]; -/// let hsv: Hsv64 = Bgr::from_raw_slice(&buffer)[1].into(); -/// -/// assert_relative_eq!(hsv, Hsv::new(90.0, 1.0, 0.5)); -/// ``` -/// -/// With alpha component: -/// -/// ```rust -/// use approx::assert_relative_eq; -/// -/// use palette::{IntoColor, LinSrgba, Srgb}; -/// use palette::rgb::{Rgb, RgbSpace}; -/// use palette::encoding::Linear; -/// use palette::white_point::D65; -/// -/// /// CSS style sRGB. -/// #[derive(PartialEq, Debug, IntoColor)] -/// #[palette_manual_into(Rgb = "into_rgb_internal")] -/// struct CssRgb { -/// red: u8, -/// green: u8, -/// blue: u8, -/// #[palette_alpha] -/// alpha: f32, -/// } -/// -/// // We will write a conversion function for opaque RGB and derive -/// // will take care of preserving the transparency for us. -/// impl CssRgb { -/// fn into_rgb_internal(self) -> Rgb, f32> -/// where -/// S: RgbSpace, -/// { -/// Srgb::new(self.red, self.green, self.blue) -/// .into_format() -/// .into_rgb() -/// } -/// } -/// -/// let css_color = CssRgb { -/// red: 187, -/// green: 0, -/// blue: 255, -/// alpha: 0.3, -/// }; -/// let color: LinSrgba = css_color.into(); -/// -/// assert_relative_eq!(color, LinSrgba::new(0.496933, 0.0, 1.0, 0.3)); -/// ``` -pub trait IntoColor: Sized -where - T: FloatComponent, - Wp: WhitePoint, -{ - /// Convert into XYZ space - fn into_xyz(self) -> Xyz; - - /// Convert into Yxy color space - fn into_yxy(self) -> Yxy { - Yxy::from_xyz(self.into_xyz()) - } - - /// Convert into L\*a\*b\* color space - fn into_lab(self) -> Lab { - Lab::from_xyz(self.into_xyz()) - } - - /// Convert into L\*C\*h° color space - fn into_lch(self) -> Lch { - Lch::from_lab(self.into_lab()) - } - - /// Convert into RGB color space. - fn into_rgb>(self) -> Rgb, T> { - Rgb::from_xyz(self.into_xyz()) - } - - /// Convert into HSL color space - fn into_hsl>(self) -> Hsl { - let rgb: Rgb, T> = self.into_rgb(); - Hsl::from_rgb(rgb) - } - - /// Convert into HSV color space - fn into_hsv>(self) -> Hsv { - let rgb: Rgb, T> = self.into_rgb(); - Hsv::from_rgb(rgb) - } +use core::fmt::{self, Display, Formatter}; - /// Convert into HWB color space - fn into_hwb>(self) -> Hwb { - let hsv: Hsv = self.into_hsv(); - Hwb::from_hsv(hsv) - } +#[doc(hidden)] +pub use palette_derive::FromColorUnclamped; - /// Convert into Luma - fn into_luma(self) -> Luma, T> { - Luma::from_xyz(self.into_xyz()) - } -} +use crate::Limited; /// The error type for a color conversion that converted a color into a color /// with invalid values. @@ -508,127 +297,184 @@ impl OutOfBounds { #[cfg(feature = "std")] impl ::std::error::Error for OutOfBounds { fn description(&self) -> &str { - "Color conversion is out of bounds" + "color conversion is out of bounds" } } impl Display for OutOfBounds { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "Color conversion is out of bounds") + write!(fmt, "color conversion is out of bounds") } } -/// A trait for converting a color into another. -pub trait ConvertInto: Into { +/// A trait for converting a color into another, in a possibly lossy way. +/// +/// `U: IntoColor` is implemented for every type `T: FromColor`. +/// +/// See [`FromColor`](trait.FromColor.html) for more details. +pub trait IntoColor: Sized { /// Convert into T with values clamped to the color defined bounds /// /// ``` - /// use palette::ConvertInto; - /// use palette::Limited; - /// use palette::{Srgb, Lch}; + /// use palette::{Lch, Srgb, Limited, IntoColor}; /// - /// - /// let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).convert_into(); + /// let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).into_color(); /// assert!(rgb.is_valid()); /// ``` - fn convert_into(self) -> T; + fn into_color(self) -> T; +} +/// A trait for unchecked conversion of a color into another. +/// +/// `U: IntoColorUnclamped` is implemented for every type `T: FromColorUnclamped`. +/// +/// See [`FromColorUnclamped`](trait.FromColorUnclamped.html) for more details. +pub trait IntoColorUnclamped: Sized { /// Convert into T. The resulting color might be invalid in its color space /// /// ``` - /// use palette::ConvertInto; - /// use palette::Limited; - /// use palette::{Srgb, Lch}; + /// use palette::convert::IntoColorUnclamped; + /// use palette::{Lch, Srgb, Limited}; /// - /// let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).convert_unclamped_into(); - /// assert!(!rgb.is_valid()); - /// ``` - fn convert_unclamped_into(self) -> T; + ///let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).into_color_unclamped(); + ///assert!(!rgb.is_valid()); + ///``` + fn into_color_unclamped(self) -> T; +} +/// A trait for fallible conversion of a color into another. +/// +/// `U: TryIntoColor` is implemented for every type `T: TryFromColor`. +/// +/// See [`TryFromColor`](trait.TryFromColor.html) for more details. +pub trait TryIntoColor: Sized { /// 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::ConvertInto; - /// use palette::{Srgb, Hsl}; + ///``` + /// use palette::convert::TryIntoColor; + /// use palette::{Hsl, Srgb}; /// - /// 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"); - /// err.color() - /// }, + /// let rgb: Srgb = match Hsl::new(150.0, 1.0, 1.1).try_into_color() { + /// Ok(color) => color, + /// Err(err) => { + /// println!("Color is out of bounds"); + /// err.color() + /// } /// }; /// ``` - fn try_convert_into(self) -> Result>; + fn try_into_color(self) -> Result>; } -/// A trait for converting one color from another. +///A trait for converting one color from another, in a possibly lossy way. /// -/// `convert_unclamped` currently wraps the underlying `From` implementation. -pub trait ConvertFrom: From { - /// Convert from T with values clamped to the color defined bounds +/// `U: FromColor` is implemented for every type `U: FromColorUnclamped + Limited`. +/// +/// See [`FromColorUnclamped`](trait.FromColorUnclamped.html) for a lossless version of this trait. +/// See [`TryFromColor`](trait.TryFromColor.html) for a trait that gives an error when the result +/// is out of bounds. +/// +/// # The Difference Between FromColor and From +/// +/// The conversion traits, including `FromColor`, were added to gain even more flexibility +/// than what `From` and the other standard library traits can give. There are a few subtle, +/// but important, differences in their semantics: +/// +/// * `FromColor` and `IntoColor` are allowed to be lossy, meaning converting `A -> B -> A` +/// may result in a different value than the original. This applies to `A -> A` as well. +/// * `From` and `Into` are blanket implemented, while `FromColor` and +/// `IntoColor` have to be manually implemented. This allows additional flexibility, +/// such as allowing implementing `FromColor> for Rgb`. +/// * Implementing `FromColorUnclamped` and `Limited` is enough to get all the other conversion +/// traits, while `From` and `Into` would not be possible to blanket implement in the same way. +/// This also reduces the work that needs to be done by macros. +/// +/// See the [`convert`](index.html) module for how to implement `FromColorUnclamped` for +/// custom colors. +pub trait FromColor: Sized { + /// Convert from T with values clamped to the color defined bounds. /// /// ``` - /// use palette::ConvertFrom; - /// use palette::Limited; - /// use palette::{Srgb, Lch}; - /// + /// use palette::{Lch, Srgb, Limited, FromColor}; /// - /// let rgb = Srgb::convert_from(Lch::new(50.0, 100.0, -175.0)); + /// let rgb = Srgb::from_color(Lch::new(50.0, 100.0, -175.0)); /// assert!(rgb.is_valid()); /// ``` - fn convert_from(_: T) -> Self; + fn from_color(t: T) -> Self; +} - /// Convert from T. The resulting color might be invalid in its color space +/// A trait for unchecked conversion of one color from another. +/// +/// See [`FromColor`](trait.FromColor.html) for a lossy version of this trait. +/// See [`TryFromColor`](trait.TryFromColor.html) for a trait that gives an error when the result +/// is out of bounds. +/// +/// See the [`convert`](index.html) module for how to implement `FromColorUnclamped` for +/// custom colors. +pub trait FromColorUnclamped: Sized { + /// Convert from T. The resulting color might be invalid in its color space. /// /// ``` - /// use palette::ConvertFrom; - /// use palette::Limited; - /// use palette::{Srgb, Lch}; + /// use palette::convert::FromColorUnclamped; + /// use palette::{Lch, Srgb, Limited}; /// - /// let rgb = Srgb::convert_unclamped_from(Lch::new(50.0, 100.0, -175.0)); + /// let rgb = Srgb::from_color_unclamped(Lch::new(50.0, 100.0, -175.0)); /// assert!(!rgb.is_valid()); /// ``` - #[inline] - fn convert_unclamped_from(val: T) -> Self { - Self::from(val) - } + fn from_color_unclamped(val: T) -> Self; +} +/// A trait for fallible conversion of one color from another. +/// +/// `U: TryFromColor` is implemented for every type `U: FromColorUnclamped + Limited`. +/// +/// See [`FromColor`](trait.FromColor.html) for a lossy version of this trait. +/// See [`FromColorUnclamped`](trait.FromColorUnclamped.html) for a lossless version. +/// +/// See the [`convert`](index.html) module for how to implement `FromColorUnclamped` for +/// custom colors. +pub trait TryFromColor: Sized { /// 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}; + ///``` + /// use palette::convert::TryFromColor; + /// use palette::{Hsl, Srgb}; /// - /// 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() - /// }, + /// let rgb = match Srgb::try_from_color(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>; + fn try_from_color(t: T) -> Result>; } -impl ConvertFrom for U +impl FromColor for U where - U: From + Limited, + U: FromColorUnclamped + Limited, { - fn convert_from(t: T) -> U { - let mut this = U::from(t); + #[inline] + fn from_color(t: T) -> Self { + let mut this = Self::from_color_unclamped(t); if !this.is_valid() { this.clamp_self(); } this } +} - fn try_convert_from(t: T) -> Result> { - let this = U::from(t); +impl TryFromColor for U +where + U: FromColorUnclamped + Limited, +{ + #[inline] + fn try_from_color(t: T) -> Result> { + let this = Self::from_color_unclamped(t); if this.is_valid() { Ok(this) } else { @@ -637,280 +483,421 @@ where } } -// ConvertFrom implies ConvertInto -impl ConvertInto for T +impl IntoColor for T where - U: ConvertFrom, + U: FromColor, { #[inline] - fn convert_into(self) -> U { - U::convert_from(self) + fn into_color(self) -> U { + U::from_color(self) } +} +impl IntoColorUnclamped for T +where + U: FromColorUnclamped, +{ #[inline] - fn convert_unclamped_into(self) -> U { - U::convert_unclamped_from(self) + fn into_color_unclamped(self) -> U { + U::from_color_unclamped(self) } +} +impl TryIntoColor for T +where + U: TryFromColor, +{ #[inline] - fn try_convert_into(self) -> Result> { - U::try_convert_from(self) + fn try_into_color(self) -> Result> { + U::try_from_color(self) } } -macro_rules! impl_into_color { - ($self_ty: ident, $from_fn: ident) => { - impl IntoColor for $self_ty - where - T: FloatComponent, - Wp: WhitePoint, - { - fn into_xyz(self) -> Xyz { - Xyz::$from_fn(self) - } - - fn into_yxy(self) -> Yxy { - Yxy::$from_fn(self) - } - - fn into_lab(self) -> Lab { - Lab::$from_fn(self) - } - - fn into_lch(self) -> Lch { - Lch::$from_fn(self) - } - - fn into_rgb>(self) -> Rgb, T> { - Rgb::$from_fn(self) - } - - fn into_hsl>(self) -> Hsl { - Hsl::$from_fn(self) - } - - fn into_hsv>(self) -> Hsv { - Hsv::$from_fn(self) - } - - fn into_luma(self) -> Luma, T> { - Luma::$from_fn(self) - } +#[cfg(test)] +mod tests { + use core::marker::PhantomData; + + use super::{FromColor, FromColorUnclamped, IntoColor}; + use crate::encoding::linear::Linear; + use crate::luma::{Luma, LumaStandard}; + use crate::rgb::{Rgb, RgbSpace}; + use crate::{Alpha, Hsl, Hsv, Hwb, Lab, Lch, Xyz, Yxy}; + use crate::{FloatComponent, Limited}; + + #[palette( + skip_derives(Xyz, Luma), + white_point = "S::WhitePoint", + component = "f64", + rgb_space = "S", + palette_internal, + palette_internal_not_base_type + )] + #[derive(FromColorUnclamped, WithAlpha)] + struct WithXyz(PhantomData); + + impl Clone for WithXyz { + fn clone(&self) -> Self { + *self } - }; -} + } + + impl Copy for WithXyz {} -macro_rules! impl_into_color_rgb { - ($self_ty: ident, $from_fn: ident) => { - impl IntoColor for $self_ty - where - T: FloatComponent, - Wp: WhitePoint, - S: RgbSpace, - { - fn into_xyz(self) -> Xyz { - Xyz::$from_fn(self) - } - - fn into_yxy(self) -> Yxy { - Yxy::$from_fn(self) - } - - fn into_lab(self) -> Lab { - Lab::$from_fn(self) - } - - fn into_lch(self) -> Lch { - Lch::$from_fn(self) - } - - fn into_rgb>(self) -> Rgb, T> { - Rgb::$from_fn(self) - } - - fn into_hsl>(self) -> Hsl { - Hsl::$from_fn(self) - } - - fn into_hsv>(self) -> Hsv { - Hsv::$from_fn(self) - } - - fn into_luma(self) -> Luma, T> { - Luma::$from_fn(self) - } + impl Limited for WithXyz { + fn is_valid(&self) -> bool { + true } - }; -} -impl_into_color!(Xyz, from_xyz); -impl_into_color!(Yxy, from_yxy); -impl_into_color!(Lab, from_lab); -impl_into_color!(Lch, from_lch); -impl_into_color_rgb!(Hsl, from_hsl); -impl_into_color_rgb!(Hsv, from_hsv); -impl_into_color_rgb!(Hwb, from_hwb); + fn clamp(&self) -> Self { + *self + } -#[cfg(test)] -mod tests { - use crate::encoding; - use crate::encoding::linear::Linear; - use crate::luma::Luma; - use crate::rgb::{Rgb, RgbSpace}; - use crate::white_point; - use crate::FloatComponent; - use crate::{Hsl, Hsv, Hwb, Lab, Lch, Xyz, Yxy}; - use core::marker::PhantomData; + fn clamp_self(&mut self) {} + } - #[derive(Copy, Clone, FromColor, IntoColor)] - #[palette_manual_from(Xyz, Luma = "from_luma_internal")] - #[palette_manual_into(Xyz, Luma = "into_luma_internal")] - #[palette_white_point = "S::WhitePoint"] - #[palette_component = "f64"] - #[palette_rgb_space = "S"] - #[palette_internal] - struct WithXyz(PhantomData); + impl FromColorUnclamped> for WithXyz + where + S1: RgbSpace, + S2: RgbSpace, + { + fn from_color_unclamped(_color: WithXyz) -> Self { + WithXyz(PhantomData) + } + } - impl WithXyz { - fn from_luma_internal(_color: Luma, f64>) -> Self { + impl FromColorUnclamped> for WithXyz { + fn from_color_unclamped(_color: Xyz) -> Self { WithXyz(PhantomData) } + } - fn into_luma_internal(self) -> Luma, f64> { - Luma::new(1.0) + impl FromColorUnclamped> for Xyz { + fn from_color_unclamped(_color: WithXyz) -> Xyz { + Xyz::with_wp(0.0, 1.0, 0.0) } } - impl From> for WithXyz { - fn from(_color: Xyz) -> Self { + impl> + FromColorUnclamped> for WithXyz + { + fn from_color_unclamped(_color: Luma) -> Self { WithXyz(PhantomData) } } - impl Into> for WithXyz { - fn into(self) -> Xyz { - Xyz::with_wp(0.0, 1.0, 0.0) + impl> + FromColorUnclamped> for Luma + { + fn from_color_unclamped(_color: WithXyz) -> Self { + Luma::new(1.0) } } - #[derive(Copy, Clone, FromColor, IntoColor)] - #[palette_manual_from(Lch, Luma = "from_luma_internal")] - #[palette_manual_into(Lch, Luma = "into_luma_internal")] - #[palette_white_point = "crate::white_point::E"] - #[palette_component = "T"] - #[palette_rgb_space = "(crate::encoding::Srgb, crate::white_point::E)"] - #[palette_internal] + #[palette( + skip_derives(Lch, Luma), + white_point = "crate::white_point::E", + component = "T", + rgb_space = "(crate::encoding::Srgb, crate::white_point::E)", + palette_internal, + palette_internal_not_base_type + )] + #[derive(Copy, Clone, FromColorUnclamped, WithAlpha)] struct WithoutXyz(PhantomData); - impl WithoutXyz { - fn from_luma_internal(_color: Luma, T>) -> Self { - WithoutXyz(PhantomData) + impl Limited for WithoutXyz { + fn is_valid(&self) -> bool { + true } - fn into_luma_internal(self) -> Luma, T> { - Luma::new(T::one()) + fn clamp(&self) -> Self { + *self + } + + fn clamp_self(&mut self) {} + } + + impl FromColorUnclamped> for WithoutXyz { + fn from_color_unclamped(color: WithoutXyz) -> Self { + color } } - impl From> for WithoutXyz { - fn from(_color: Lch) -> Self { + impl FromColorUnclamped> for WithoutXyz { + fn from_color_unclamped(_color: Lch) -> Self { WithoutXyz(PhantomData) } } - impl Into> for WithoutXyz { - fn into(self) -> Lch { + impl FromColorUnclamped> for Lch { + fn from_color_unclamped(_color: WithoutXyz) -> Lch { Lch::with_wp(T::one(), T::zero(), T::zero()) } } + impl FromColorUnclamped, T>> + for WithoutXyz + { + fn from_color_unclamped(_color: Luma, T>) -> Self { + WithoutXyz(PhantomData) + } + } + + impl FromColorUnclamped> + for Luma, T> + { + fn from_color_unclamped(_color: WithoutXyz) -> Luma, T> { + Luma::new(T::one()) + } + } + #[test] fn from_with_xyz() { + let color: WithXyz = WithXyz(Default::default()); + WithXyz::::from_color(color); + + let xyz: Xyz<_, f64> = Default::default(); + WithXyz::::from_color(xyz); + + let yxy: Yxy<_, f64> = Default::default(); + WithXyz::::from_color(yxy); + + let lab: Lab<_, f64> = Default::default(); + WithXyz::::from_color(lab); + + let lch: Lch<_, f64> = Default::default(); + WithXyz::::from_color(lch); + + let rgb: Rgb = Default::default(); + WithXyz::::from_color(rgb); + + let hsl: Hsl<_, f64> = Default::default(); + WithXyz::::from_color(hsl); + + let hsv: Hsv<_, f64> = Default::default(); + WithXyz::::from_color(hsv); + + let hwb: Hwb<_, f64> = Default::default(); + WithXyz::::from_color(hwb); + + let luma: Luma = Default::default(); + WithXyz::::from_color(luma); + } + + #[test] + fn from_with_xyz_alpha() { + let color: Alpha, u8> = + Alpha::from(WithXyz(Default::default())); + WithXyz::::from_color(color); + + let xyz: Alpha, u8> = Alpha::from(Xyz::default()); + WithXyz::::from_color(xyz); + + let yxy: Alpha, u8> = Alpha::from(Yxy::default()); + WithXyz::::from_color(yxy); + + let lab: Alpha, u8> = Alpha::from(Lab::default()); + WithXyz::::from_color(lab); + + let lch: Alpha, u8> = Alpha::from(Lch::default()); + WithXyz::::from_color(lch); + + let rgb: Alpha, u8> = + Alpha::from(Rgb::::default()); + WithXyz::::from_color(rgb); + + let hsl: Alpha, u8> = Alpha::from(Hsl::default()); + WithXyz::::from_color(hsl); + + let hsv: Alpha, u8> = Alpha::from(Hsv::default()); + WithXyz::::from_color(hsv); + + let hwb: Alpha, u8> = Alpha::from(Hwb::default()); + WithXyz::::from_color(hwb); + + let luma: Alpha, u8> = + Alpha::from(Luma::::default()); + WithXyz::::from_color(luma); + } + + #[test] + fn from_with_xyz_into_alpha() { + let color: WithXyz = WithXyz(Default::default()); + Alpha::, u8>::from_color(color); + let xyz: Xyz<_, f64> = Default::default(); - WithXyz::::from(xyz); + Alpha::, u8>::from_color(xyz); let yxy: Yxy<_, f64> = Default::default(); - WithXyz::::from(yxy); + Alpha::, u8>::from_color(yxy); let lab: Lab<_, f64> = Default::default(); - WithXyz::::from(lab); + Alpha::, u8>::from_color(lab); let lch: Lch<_, f64> = Default::default(); - WithXyz::::from(lch); + Alpha::, u8>::from_color(lch); - let rgb: Rgb = Default::default(); - WithXyz::::from(rgb); + let rgb: Rgb = Default::default(); + Alpha::, u8>::from_color(rgb); let hsl: Hsl<_, f64> = Default::default(); - WithXyz::::from(hsl); + Alpha::, u8>::from_color(hsl); let hsv: Hsv<_, f64> = Default::default(); - WithXyz::::from(hsv); + Alpha::, u8>::from_color(hsv); let hwb: Hwb<_, f64> = Default::default(); - WithXyz::::from(hwb); + Alpha::, u8>::from_color(hwb); - let luma: Luma = Default::default(); - WithXyz::::from(luma); + let luma: Luma = Default::default(); + Alpha::, u8>::from_color(luma); } #[test] - fn into_with_xyz() { - let color = WithXyz::(PhantomData); + fn from_with_xyz_alpha_into_alpha() { + let color: Alpha, u8> = + Alpha::from(WithXyz(Default::default())); + Alpha::, u8>::from_color(color); + + let xyz: Xyz<_, f64> = Default::default(); + Alpha::, u8>::from_color(xyz); + + let yxy: Yxy<_, f64> = Default::default(); + Alpha::, u8>::from_color(yxy); + + let lab: Lab<_, f64> = Default::default(); + Alpha::, u8>::from_color(lab); + + let lch: Lch<_, f64> = Default::default(); + Alpha::, u8>::from_color(lch); + + let rgb: Rgb = Default::default(); + Alpha::, u8>::from_color(rgb); + + let hsl: Hsl<_, f64> = Default::default(); + Alpha::, u8>::from_color(hsl); + + let hsv: Hsv<_, f64> = Default::default(); + Alpha::, u8>::from_color(hsv); + + let hwb: Hwb<_, f64> = Default::default(); + Alpha::, u8>::from_color(hwb); - let _xyz: Xyz<_, f64> = color.into(); - let _yxy: Yxy<_, f64> = color.into(); - let _lab: Lab<_, f64> = color.into(); - let _lch: Lch<_, f64> = color.into(); - let _rgb: Rgb = color.into(); - let _hsl: Hsl<_, f64> = color.into(); - let _hsv: Hsv<_, f64> = color.into(); - let _hwb: Hwb<_, f64> = color.into(); - let _luma: Luma = color.into(); + let luma: Luma = Default::default(); + Alpha::, u8>::from_color(luma); + } + + #[test] + fn into_from_with_xyz() { + let color = WithXyz::(PhantomData); + + let _self: WithXyz = color.into_color(); + let _xyz: Xyz<_, f64> = color.into_color(); + let _yxy: Yxy<_, f64> = color.into_color(); + let _lab: Lab<_, f64> = color.into_color(); + let _lch: Lch<_, f64> = color.into_color(); + let _rgb: Rgb = color.into_color(); + let _hsl: Hsl<_, f64> = color.into_color(); + let _hsv: Hsv<_, f64> = color.into_color(); + let _hwb: Hwb<_, f64> = color.into_color(); + let _luma: Luma = color.into_color(); + } + + #[test] + fn into_from_with_xyz_alpha() { + let color: Alpha, u8> = + Alpha::from(WithXyz::(PhantomData)); + + let _self: WithXyz = color.into_color(); + let _xyz: Xyz<_, f64> = color.into_color(); + let _yxy: Yxy<_, f64> = color.into_color(); + let _lab: Lab<_, f64> = color.into_color(); + let _lch: Lch<_, f64> = color.into_color(); + let _rgb: Rgb = color.into_color(); + let _hsl: Hsl<_, f64> = color.into_color(); + let _hsv: Hsv<_, f64> = color.into_color(); + let _hwb: Hwb<_, f64> = color.into_color(); + let _luma: Luma = color.into_color(); + } + + #[test] + fn into_alpha_from_with_xyz() { + let color = WithXyz::(PhantomData); + + let _self: Alpha, u8> = color.into_color(); + let _xyz: Alpha, u8> = color.into_color(); + let _yxy: Alpha, u8> = color.into_color(); + let _lab: Alpha, u8> = color.into_color(); + let _lch: Alpha, u8> = color.into_color(); + let _rgb: Alpha, u8> = color.into_color(); + let _hsl: Alpha, u8> = color.into_color(); + let _hsv: Alpha, u8> = color.into_color(); + let _hwb: Alpha, u8> = color.into_color(); + let _luma: Alpha, u8> = color.into_color(); + } + + #[test] + fn into_alpha_from_with_xyz_alpha() { + let color: Alpha, u8> = + Alpha::from(WithXyz::(PhantomData)); + + let _self: Alpha, u8> = color.into_color(); + let _xyz: Alpha, u8> = color.into_color(); + let _yxy: Alpha, u8> = color.into_color(); + let _lab: Alpha, u8> = color.into_color(); + let _lch: Alpha, u8> = color.into_color(); + let _rgb: Alpha, u8> = color.into_color(); + let _hsl: Alpha, u8> = color.into_color(); + let _hsv: Alpha, u8> = color.into_color(); + let _hwb: Alpha, u8> = color.into_color(); + let _luma: Alpha, u8> = color.into_color(); } #[test] fn from_without_xyz() { - let xyz: Xyz = Default::default(); - WithoutXyz::::from(xyz); + let color: WithoutXyz = WithoutXyz(Default::default()); + WithoutXyz::::from_color(color); + + let xyz: Xyz = Default::default(); + WithoutXyz::::from_color(xyz); - let yxy: Yxy = Default::default(); - WithoutXyz::::from(yxy); + let yxy: Yxy = Default::default(); + WithoutXyz::::from_color(yxy); - let lab: Lab = Default::default(); - WithoutXyz::::from(lab); + let lab: Lab = Default::default(); + WithoutXyz::::from_color(lab); - let lch: Lch = Default::default(); - WithoutXyz::::from(lch); + let lch: Lch = Default::default(); + WithoutXyz::::from_color(lch); - let rgb: Rgb<(_, encoding::Srgb), f64> = Default::default(); - WithoutXyz::::from(rgb); + let rgb: Rgb<(_, crate::encoding::Srgb), f64> = Default::default(); + WithoutXyz::::from_color(rgb); let hsl: Hsl<_, f64> = Default::default(); - WithoutXyz::::from(hsl); + WithoutXyz::::from_color(hsl); let hsv: Hsv<_, f64> = Default::default(); - WithoutXyz::::from(hsv); + WithoutXyz::::from_color(hsv); let hwb: Hwb<_, f64> = Default::default(); - WithoutXyz::::from(hwb); + WithoutXyz::::from_color(hwb); - let luma: Luma, f64> = Default::default(); - WithoutXyz::::from(luma); + let luma: Luma, f64> = Default::default(); + WithoutXyz::::from_color(luma); } #[test] fn into_without_xyz() { let color = WithoutXyz::(PhantomData); - let _xyz: Xyz = color.into(); - let _yxy: Yxy = color.into(); - let _lab: Lab = color.into(); - let _lch: Lch = color.into(); - let _rgb: Rgb<(_, encoding::Srgb), f64> = color.into(); - let _hsl: Hsl<_, f64> = color.into(); - let _hsv: Hsv<_, f64> = color.into(); - let _hwb: Hwb<_, f64> = color.into(); - let _luma: Luma, f64> = color.into(); + let _self: WithoutXyz = color.into_color(); + let _xyz: Xyz = color.into_color(); + let _yxy: Yxy = color.into_color(); + let _lab: Lab = color.into_color(); + let _lch: Lch = color.into_color(); + let _rgb: Rgb<(_, crate::encoding::Srgb), f64> = color.into_color(); + let _hsl: Hsl<_, f64> = color.into_color(); + let _hsv: Hsv<_, f64> = color.into_color(); + let _hwb: Hwb<_, f64> = color.into_color(); + let _luma: Luma, f64> = color.into_color(); } } diff --git a/palette/src/encoding/gamma.rs b/palette/src/encoding/gamma.rs index 3d43ae9ae..0e59aec5b 100644 --- a/palette/src/encoding/gamma.rs +++ b/palette/src/encoding/gamma.rs @@ -51,7 +51,7 @@ impl TransferFn for GammaFn { } /// A type level float constant. -pub trait Number { +pub trait Number: 'static { /// The represented number. const VALUE: f64; } diff --git a/palette/src/encoding/mod.rs b/palette/src/encoding/mod.rs index 58260a8a6..931fff3d8 100644 --- a/palette/src/encoding/mod.rs +++ b/palette/src/encoding/mod.rs @@ -13,7 +13,7 @@ pub mod pixel; pub mod srgb; /// A transfer function to and from linear space. -pub trait TransferFn { +pub trait TransferFn: 'static { /// Convert the color component `x` from linear space. fn from_linear(x: T) -> T; diff --git a/palette/src/encoding/pixel/mod.rs b/palette/src/encoding/pixel/mod.rs index 4f360bb31..3e9378076 100644 --- a/palette/src/encoding/pixel/mod.rs +++ b/palette/src/encoding/pixel/mod.rs @@ -1,5 +1,8 @@ //! Pixel encodings and pixel format conversion. +#[doc(hidden)] +pub use palette_derive::Pixel; + pub use self::raw::*; mod raw; @@ -77,11 +80,11 @@ mod raw; /// #[derive(PartialEq, Debug, Pixel)] /// #[repr(C)] /// struct MyCoolColor { -/// #[palette_unsafe_zero_sized] +/// #[palette(unsafe_zero_sized)] /// standard: PhantomData, /// // RgbHue is a wrapper with `#[repr(C)]`, so it can safely /// // be converted straight from `f32`. -/// #[palette_unsafe_same_layout_as = "f32"] +/// #[palette(unsafe_same_layout_as = "f32")] /// hue: RgbHue, /// lumen: f32, /// chroma: f32, diff --git a/palette/src/hsl.rs b/palette/src/hsl.rs index de20d08e0..a28fcdf5c 100644 --- a/palette/src/hsl.rs +++ b/palette/src/hsl.rs @@ -10,13 +10,14 @@ use rand::distributions::{Distribution, Standard}; #[cfg(feature = "random")] use rand::Rng; +use crate::convert::{FromColorUnclamped, IntoColorUnclamped}; use crate::encoding::pixel::RawPixel; use crate::encoding::{Linear, Srgb}; use crate::float::Float; -use crate::rgb::{Rgb, RgbSpace}; +use crate::rgb::{Rgb, RgbSpace, RgbStandard}; use crate::{ - clamp, contrast_ratio, from_f64, Alpha, Component, FloatComponent, FromColor, FromF64, GetHue, - Hsv, Hue, IntoColor, Limited, Mix, Pixel, RelativeContrast, RgbHue, Saturate, Shade, Xyz, + clamp, contrast_ratio, from_f64, Alpha, Component, FloatComponent, FromF64, GetHue, Hsv, Hue, + Limited, Mix, Pixel, RelativeContrast, RgbHue, Saturate, Shade, Xyz, }; /// Linear HSL with an alpha component. See the [`Hsla` implementation in @@ -34,13 +35,15 @@ pub type Hsla = Alpha, T>; /// /// See [HSV](struct.Hsv.html) for a very similar color space, with brightness /// instead of lightness. -#[derive(Debug, PartialEq, FromColor, Pixel)] +#[derive(Debug, PartialEq, Pixel, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] -#[palette_internal] -#[palette_rgb_space = "S"] -#[palette_white_point = "S::WhitePoint"] -#[palette_component = "T"] -#[palette_manual_from(Xyz, Rgb = "from_rgb_internal", Hsv, Hsl = "from_hsl_internal")] +#[palette( + palette_internal, + rgb_space = "S", + white_point = "S::WhitePoint", + component = "T", + skip_derives(Xyz, Rgb, Hsv, Hsl) +)] #[repr(C)] pub struct Hsl where @@ -49,7 +52,7 @@ where { /// The hue of the color, in degrees. Decides if it's red, blue, purple, /// etc. - #[palette_unsafe_same_layout_as = "T"] + #[palette(unsafe_same_layout_as = "T")] pub hue: RgbHue, /// The colorfulness of the color. 0.0 gives gray scale colors and 1.0 will @@ -63,7 +66,7 @@ where /// The white point and RGB primaries this color is adapted to. The default /// is the sRGB standard. #[cfg_attr(feature = "serializing", serde(skip))] - #[palette_unsafe_zero_sized] + #[palette(unsafe_zero_sized)] pub space: PhantomData, } @@ -124,56 +127,6 @@ where Self::with_wp(hue, saturation, lightness) } - fn from_hsl_internal>(hsl: Hsl) -> Self { - if TypeId::of::() == TypeId::of::() { - hsl.reinterpret_as() - } else { - Self::from_rgb(Rgb::, T>::from_hsl(hsl)) - } - } - - fn from_rgb_internal>( - color: Rgb, T>, - ) -> Self { - let rgb = Rgb::, T>::from_rgb(color); - - let (max, min, sep, coeff) = { - let (max, min, sep, coeff) = if rgb.red > rgb.green { - (rgb.red, rgb.green, rgb.green - rgb.blue, T::zero()) - } else { - (rgb.green, rgb.red, rgb.blue - rgb.red, from_f64(2.0)) - }; - if rgb.blue > max { - (rgb.blue, min, rgb.red - rgb.green, from_f64(4.0)) - } else { - let min_val = if rgb.blue < min { rgb.blue } else { min }; - (max, min_val, sep, coeff) - } - }; - - let mut h = T::zero(); - let mut s = T::zero(); - - let sum = max + min; - let l = sum / from_f64(2.0); - if max != min { - let d = max - min; - s = if sum > T::one() { - d / (from_f64::(2.0) - sum) - } else { - d / sum - }; - h = ((sep / d) + coeff) * from_f64(60.0); - }; - - Hsl { - hue: h.into(), - saturation: s, - lightness: l, - space: PhantomData, - } - } - #[inline] fn reinterpret_as(self) -> Hsl { Hsl { @@ -248,26 +201,86 @@ where } } -impl From> for Hsl +impl FromColorUnclamped> for Hsl where T: FloatComponent, S: RgbSpace, + Sp: RgbSpace, +{ + fn from_color_unclamped(hsl: Hsl) -> Self { + if TypeId::of::() == TypeId::of::() { + hsl.reinterpret_as() + } else { + let rgb = Rgb::, T>::from_color_unclamped(hsl); + let converted_rgb = Rgb::, T>::from_color_unclamped(rgb); + Self::from_color_unclamped(converted_rgb) + } + } +} + +impl FromColorUnclamped> for Hsl +where + T: FloatComponent, + S: RgbStandard, { - fn from(color: Xyz) -> Self { - let rgb: Rgb, T> = color.into_rgb(); - Self::from_rgb(rgb) + fn from_color_unclamped(color: Rgb) -> Self { + let rgb = color.into_linear(); + + let (max, min, sep, coeff) = { + let (max, min, sep, coeff) = if rgb.red > rgb.green { + (rgb.red, rgb.green, rgb.green - rgb.blue, T::zero()) + } else { + (rgb.green, rgb.red, rgb.blue - rgb.red, from_f64(2.0)) + }; + if rgb.blue > max { + (rgb.blue, min, rgb.red - rgb.green, from_f64(4.0)) + } else { + let min_val = if rgb.blue < min { rgb.blue } else { min }; + (max, min_val, sep, coeff) + } + }; + + let mut h = T::zero(); + let mut s = T::zero(); + + let sum = max + min; + let l = sum / from_f64(2.0); + if max != min { + let d = max - min; + s = if sum > T::one() { + d / (from_f64::(2.0) - sum) + } else { + d / sum + }; + h = ((sep / d) + coeff) * from_f64(60.0); + }; + + Hsl { + hue: h.into(), + saturation: s, + lightness: l, + space: PhantomData, + } } } -impl From> for Hsl +impl FromColorUnclamped> for Hsl where T: FloatComponent, S: RgbSpace, - Sp: RgbSpace, { - fn from(color: Hsv) -> Self { - let hsv = Hsv::::from_hsv(color); + fn from_color_unclamped(color: Xyz) -> Self { + let rgb: Rgb, T> = color.into_color_unclamped(); + Self::from_color_unclamped(rgb) + } +} +impl FromColorUnclamped> for Hsl +where + T: FloatComponent, + S: RgbSpace, +{ + fn from_color_unclamped(hsv: Hsv) -> Self { let x = (from_f64::(2.0) - hsv.saturation) * hsv.value; let saturation = if !hsv.value.is_normal() { T::zero() @@ -656,10 +669,12 @@ where type Scalar = T; fn get_contrast_ratio(&self, other: &Self) -> T { - let luma1 = self.into_luma(); - let luma2 = other.into_luma(); + use crate::FromColor; + + let xyz1 = Xyz::from_color(*self); + let xyz2 = Xyz::from_color(*other); - contrast_ratio(luma1.luma, luma2.luma) + contrast_ratio(xyz1.y, xyz2.y) } } @@ -759,13 +774,13 @@ where mod test { use super::Hsl; use crate::encoding::Srgb; - use crate::{Hsv, LinSrgb}; + use crate::{FromColor, Hsv, LinSrgb}; #[test] fn red() { - let a = Hsl::from(LinSrgb::new(1.0, 0.0, 0.0)); + let a = Hsl::from_color(LinSrgb::new(1.0, 0.0, 0.0)); let b = Hsl::new(0.0, 1.0, 0.5); - let c = Hsl::from(Hsv::new(0.0, 1.0, 1.0)); + let c = Hsl::from_color(Hsv::new(0.0, 1.0, 1.0)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); @@ -773,9 +788,9 @@ mod test { #[test] fn orange() { - let a = Hsl::from(LinSrgb::new(1.0, 0.5, 0.0)); + let a = Hsl::from_color(LinSrgb::new(1.0, 0.5, 0.0)); let b = Hsl::new(30.0, 1.0, 0.5); - let c = Hsl::from(Hsv::new(30.0, 1.0, 1.0)); + let c = Hsl::from_color(Hsv::new(30.0, 1.0, 1.0)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); @@ -783,9 +798,9 @@ mod test { #[test] fn green() { - let a = Hsl::from(LinSrgb::new(0.0, 1.0, 0.0)); + let a = Hsl::from_color(LinSrgb::new(0.0, 1.0, 0.0)); let b = Hsl::new(120.0, 1.0, 0.5); - let c = Hsl::from(Hsv::new(120.0, 1.0, 1.0)); + let c = Hsl::from_color(Hsv::new(120.0, 1.0, 1.0)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); @@ -793,9 +808,9 @@ mod test { #[test] fn blue() { - let a = Hsl::from(LinSrgb::new(0.0, 0.0, 1.0)); + let a = Hsl::from_color(LinSrgb::new(0.0, 0.0, 1.0)); let b = Hsl::new(240.0, 1.0, 0.5); - let c = Hsl::from(Hsv::new(240.0, 1.0, 1.0)); + let c = Hsl::from_color(Hsv::new(240.0, 1.0, 1.0)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); @@ -803,9 +818,9 @@ mod test { #[test] fn purple() { - let a = Hsl::from(LinSrgb::new(0.5, 0.0, 1.0)); + let a = Hsl::from_color(LinSrgb::new(0.5, 0.0, 1.0)); let b = Hsl::new(270.0, 1.0, 0.5); - let c = Hsl::from(Hsv::new(270.0, 1.0, 1.0)); + let c = Hsl::from_color(Hsv::new(270.0, 1.0, 1.0)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); diff --git a/palette/src/hsv.rs b/palette/src/hsv.rs index 476cd7589..6ef9253d0 100644 --- a/palette/src/hsv.rs +++ b/palette/src/hsv.rs @@ -10,13 +10,14 @@ use rand::distributions::{Distribution, Standard}; #[cfg(feature = "random")] use rand::Rng; +use crate::convert::FromColorUnclamped; use crate::encoding::pixel::RawPixel; use crate::encoding::{Linear, Srgb}; use crate::float::Float; -use crate::rgb::{Rgb, RgbSpace}; +use crate::rgb::{Rgb, RgbSpace, RgbStandard}; use crate::{ clamp, contrast_ratio, from_f64, Alpha, Component, FloatComponent, FromColor, FromF64, GetHue, - Hsl, Hue, Hwb, IntoColor, Limited, Mix, Pixel, RelativeContrast, RgbHue, Saturate, Shade, Xyz, + Hsl, Hue, Hwb, Limited, Mix, Pixel, RelativeContrast, RgbHue, Saturate, Shade, Xyz, }; /// Linear HSV with an alpha component. See the [`Hsva` implementation in @@ -31,13 +32,15 @@ pub type Hsva = Alpha, T>; /// _lightness_. The difference is that, for example, red (100% R, 0% G, 0% B) /// and white (100% R, 100% G, 100% B) has the same brightness (or value), but /// not the same lightness. -#[derive(Debug, PartialEq, FromColor, Pixel)] +#[derive(Debug, PartialEq, Pixel, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] -#[palette_internal] -#[palette_white_point = "S::WhitePoint"] -#[palette_rgb_space = "S"] -#[palette_component = "T"] -#[palette_manual_from(Xyz, Rgb = "from_rgb_internal", Hsl, Hwb, Hsv = "from_hsv_internal")] +#[palette( + palette_internal, + white_point = "S::WhitePoint", + rgb_space = "S", + component = "T", + skip_derives(Xyz, Rgb, Hsl, Hwb, Hsv) +)] #[repr(C)] pub struct Hsv where @@ -46,7 +49,7 @@ where { /// The hue of the color, in degrees. Decides if it's red, blue, purple, /// etc. - #[palette_unsafe_same_layout_as = "T"] + #[palette(unsafe_same_layout_as = "T")] pub hue: RgbHue, /// The colorfulness of the color. 0.0 gives gray scale colors and 1.0 will @@ -61,7 +64,7 @@ where /// The white point and RGB primaries this color is adapted to. The default /// is the sRGB standard. #[cfg_attr(feature = "serializing", serde(skip))] - #[palette_unsafe_zero_sized] + #[palette(unsafe_zero_sized)] pub space: PhantomData, } @@ -122,51 +125,6 @@ where Self::with_wp(hue, saturation, value) } - fn from_hsv_internal>(hsv: Hsv) -> Self { - if TypeId::of::() == TypeId::of::() { - hsv.reinterpret_as() - } else { - Self::from_rgb(Rgb::, T>::from_hsv(hsv)) - } - } - - fn from_rgb_internal>( - color: Rgb, T>, - ) -> Self { - let rgb = Rgb::, T>::from_rgb(color); - - let (max, min, sep, coeff) = { - let (max, min, sep, coeff) = if rgb.red > rgb.green { - (rgb.red, rgb.green, rgb.green - rgb.blue, T::zero()) - } else { - (rgb.green, rgb.red, rgb.blue - rgb.red, from_f64(2.0)) - }; - if rgb.blue > max { - (rgb.blue, min, rgb.red - rgb.green, from_f64(4.0)) - } else { - let min_val = if rgb.blue < min { rgb.blue } else { min }; - (max, min_val, sep, coeff) - } - }; - - let mut h = T::zero(); - let mut s = T::zero(); - let v = max; - - if max != min { - let d = max - min; - s = d / max; - h = ((sep / d) + coeff) * from_f64(60.0); - }; - - Hsv { - hue: h.into(), - saturation: s, - value: v, - space: PhantomData, - } - } - #[inline] fn reinterpret_as(self) -> Hsv { Hsv { @@ -241,26 +199,81 @@ where } } -impl From> for Hsv +impl FromColorUnclamped> for Hsv where T: FloatComponent, S: RgbSpace, + Sp: RgbSpace, { - fn from(color: Xyz) -> Self { - let rgb: Rgb, T> = Rgb::from_xyz(color); - Self::from_rgb(rgb) + fn from_color_unclamped(hsv: Hsv) -> Self { + if TypeId::of::() == TypeId::of::() { + hsv.reinterpret_as() + } else { + let rgb = Rgb::, T>::from_color_unclamped(hsv); + let converted_rgb = Rgb::, T>::from_color_unclamped(rgb); + Self::from_color_unclamped(converted_rgb) + } } } -impl From> for Hsv +impl FromColorUnclamped> for Hsv +where + T: FloatComponent, + S: RgbStandard, +{ + fn from_color_unclamped(color: Rgb) -> Self { + let rgb = color.into_linear(); + + let (max, min, sep, coeff) = { + let (max, min, sep, coeff) = if rgb.red > rgb.green { + (rgb.red, rgb.green, rgb.green - rgb.blue, T::zero()) + } else { + (rgb.green, rgb.red, rgb.blue - rgb.red, from_f64(2.0)) + }; + if rgb.blue > max { + (rgb.blue, min, rgb.red - rgb.green, from_f64(4.0)) + } else { + let min_val = if rgb.blue < min { rgb.blue } else { min }; + (max, min_val, sep, coeff) + } + }; + + let mut h = T::zero(); + let mut s = T::zero(); + let v = max; + + if max != min { + let d = max - min; + s = d / max; + h = ((sep / d) + coeff) * from_f64(60.0); + }; + + Hsv { + hue: h.into(), + saturation: s, + value: v, + space: PhantomData, + } + } +} + +impl FromColorUnclamped> for Hsv where T: FloatComponent, S: RgbSpace, - Sp: RgbSpace, { - fn from(color: Hsl) -> Self { - let hsl = Hsl::::from_hsl(color); + fn from_color_unclamped(color: Xyz) -> Self { + let rgb: Rgb, T> = Rgb::from_color_unclamped(color); + Self::from_color_unclamped(rgb) + } +} +impl FromColorUnclamped> for Hsv +where + T: FloatComponent, + S: RgbSpace, +{ + fn from_color_unclamped(hsl: Hsl) -> Self { let x = hsl.saturation * if hsl.lightness < from_f64(0.5) { hsl.lightness @@ -283,14 +296,14 @@ where } } -impl From> for Hsv +impl FromColorUnclamped> for Hsv where T: FloatComponent, S: RgbSpace, Sp: RgbSpace, { - fn from(color: Hwb) -> Self { - let hwb = Hwb::::from_hwb(color); + fn from_color_unclamped(color: Hwb) -> Self { + let hwb = Hwb::::from_color_unclamped(color); let inv = T::one() - hwb.blackness; // avoid divide by zero @@ -669,10 +682,10 @@ where type Scalar = T; fn get_contrast_ratio(&self, other: &Self) -> T { - let luma1 = self.into_luma(); - let luma2 = other.into_luma(); + let xyz1 = Xyz::from_color(*self); + let xyz2 = Xyz::from_color(*other); - contrast_ratio(luma1.luma, luma2.luma) + contrast_ratio(xyz1.y, xyz2.y) } } @@ -780,13 +793,13 @@ where mod test { use super::Hsv; use crate::encoding::Srgb; - use crate::{Hsl, LinSrgb}; + use crate::{FromColor, Hsl, LinSrgb}; #[test] fn red() { - let a = Hsv::from(LinSrgb::new(1.0, 0.0, 0.0)); + let a = Hsv::from_color(LinSrgb::new(1.0, 0.0, 0.0)); let b = Hsv::new(0.0, 1.0, 1.0); - let c = Hsv::from(Hsl::new(0.0, 1.0, 0.5)); + let c = Hsv::from_color(Hsl::new(0.0, 1.0, 0.5)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); @@ -794,9 +807,9 @@ mod test { #[test] fn orange() { - let a = Hsv::from(LinSrgb::new(1.0, 0.5, 0.0)); + let a = Hsv::from_color(LinSrgb::new(1.0, 0.5, 0.0)); let b = Hsv::new(30.0, 1.0, 1.0); - let c = Hsv::from(Hsl::new(30.0, 1.0, 0.5)); + let c = Hsv::from_color(Hsl::new(30.0, 1.0, 0.5)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); @@ -804,9 +817,9 @@ mod test { #[test] fn green() { - let a = Hsv::from(LinSrgb::new(0.0, 1.0, 0.0)); + let a = Hsv::from_color(LinSrgb::new(0.0, 1.0, 0.0)); let b = Hsv::new(120.0, 1.0, 1.0); - let c = Hsv::from(Hsl::new(120.0, 1.0, 0.5)); + let c = Hsv::from_color(Hsl::new(120.0, 1.0, 0.5)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); @@ -814,9 +827,9 @@ mod test { #[test] fn blue() { - let a = Hsv::from(LinSrgb::new(0.0, 0.0, 1.0)); + let a = Hsv::from_color(LinSrgb::new(0.0, 0.0, 1.0)); let b = Hsv::new(240.0, 1.0, 1.0); - let c = Hsv::from(Hsl::new(240.0, 1.0, 0.5)); + let c = Hsv::from_color(Hsl::new(240.0, 1.0, 0.5)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); @@ -824,9 +837,9 @@ mod test { #[test] fn purple() { - let a = Hsv::from(LinSrgb::new(0.5, 0.0, 1.0)); + let a = Hsv::from_color(LinSrgb::new(0.5, 0.0, 1.0)); let b = Hsv::new(270.0, 1.0, 1.0); - let c = Hsv::from(Hsl::new(270.0, 1.0, 0.5)); + let c = Hsv::from_color(Hsl::new(270.0, 1.0, 0.5)); assert_relative_eq!(a, b); assert_relative_eq!(a, c); diff --git a/palette/src/hwb.rs b/palette/src/hwb.rs index 7e6eace23..a89dc421a 100644 --- a/palette/src/hwb.rs +++ b/palette/src/hwb.rs @@ -10,13 +10,14 @@ use rand::distributions::{Distribution, Standard}; #[cfg(feature = "random")] use rand::Rng; +use crate::convert::{FromColorUnclamped, IntoColorUnclamped}; use crate::encoding::pixel::RawPixel; use crate::encoding::Srgb; use crate::float::Float; use crate::rgb::RgbSpace; use crate::{ - clamp, contrast_ratio, Alpha, Component, FloatComponent, FromColor, FromF64, GetHue, Hsv, Hue, - IntoColor, Limited, Mix, Pixel, RelativeContrast, RgbHue, Shade, Xyz, + clamp, contrast_ratio, Alpha, Component, FloatComponent, FromF64, GetHue, Hsv, Hue, Limited, + Mix, Pixel, RelativeContrast, RgbHue, Shade, Xyz, }; /// Linear HWB with an alpha component. See the [`Hwba` implementation in @@ -32,13 +33,15 @@ pub type Hwba = Alpha, T>; /// /// It is very intuitive for humans to use and many color-pickers are based on /// the HWB color system -#[derive(Debug, PartialEq, FromColor, Pixel)] +#[derive(Debug, PartialEq, Pixel, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] -#[palette_internal] -#[palette_rgb_space = "S"] -#[palette_white_point = "S::WhitePoint"] -#[palette_component = "T"] -#[palette_manual_from(Xyz, Hsv, Hwb = "from_hwb_internal")] +#[palette( + palette_internal, + rgb_space = "S", + white_point = "S::WhitePoint", + component = "T", + skip_derives(Xyz, Hsv, Hwb) +)] #[repr(C)] pub struct Hwb where @@ -47,7 +50,7 @@ where { /// The hue of the color, in degrees. Decides if it's red, blue, purple, /// etc. Same as the hue for HSL and HSV. - #[palette_unsafe_same_layout_as = "T"] + #[palette(unsafe_same_layout_as = "T")] pub hue: RgbHue, /// The whiteness of the color. It specifies the amount white to mix into @@ -66,7 +69,7 @@ where /// The white point and RGB primaries this color is adapted to. The default /// is the sRGB standard. #[cfg_attr(feature = "serializing", serde(skip))] - #[palette_unsafe_zero_sized] + #[palette(unsafe_zero_sized)] pub space: PhantomData, } @@ -127,14 +130,6 @@ where Self::with_wp(hue, whiteness, blackness) } - fn from_hwb_internal>(color: Hwb) -> Self { - if TypeId::of::() == TypeId::of::() { - color.reinterpret_as() - } else { - Self::from_hsv(Hsv::::from_hwb(color)) - } - } - #[inline] fn reinterpret_as(self) -> Hwb { Hwb { @@ -209,26 +204,40 @@ where } } -impl From> for Hwb +impl FromColorUnclamped> for Hwb where - T: FloatComponent, S: RgbSpace, + Sp: RgbSpace, + T: FloatComponent, { - fn from(color: Xyz) -> Self { - let hsv: Hsv = color.into_hsv(); - Self::from_hsv(hsv) + fn from_color_unclamped(color: Hwb) -> Self { + if TypeId::of::() == TypeId::of::() { + color.reinterpret_as() + } else { + let hsv = Hsv::::from_color_unclamped(color); + let converted_hsv = Hsv::::from_color_unclamped(hsv); + Self::from_color_unclamped(converted_hsv) + } } } -impl From> for Hwb +impl FromColorUnclamped> for Hwb where - T: FloatComponent, S: RgbSpace, - Sp: RgbSpace, + T: FloatComponent, { - fn from(color: Hsv) -> Self { - let color = Hsv::::from_hsv(color); + fn from_color_unclamped(color: Xyz) -> Self { + let hsv: Hsv = color.into_color_unclamped(); + Self::from_color_unclamped(hsv) + } +} +impl FromColorUnclamped> for Hwb +where + S: RgbSpace, + T: FloatComponent, +{ + fn from_color_unclamped(color: Hsv) -> Self { Hwb { hue: color.hue, whiteness: (T::one() - color.saturation) * color.value, @@ -617,10 +626,12 @@ where type Scalar = T; fn get_contrast_ratio(&self, other: &Self) -> T { - let luma1 = self.into_luma(); - let luma2 = other.into_luma(); + use crate::FromColor; + + let xyz1 = Xyz::from_color(*self); + let xyz2 = Xyz::from_color(*other); - contrast_ratio(luma1.luma, luma2.luma) + contrast_ratio(xyz1.y, xyz2.y) } } @@ -632,7 +643,7 @@ where Standard: Distribution, { fn sample(&self, rng: &mut R) -> Hwb { - Hwb::from(rng.gen::>()) + Hwb::from_color_unclamped(rng.gen::>()) } } @@ -670,7 +681,10 @@ where { let low = *low_b.borrow(); let high = *high_b.borrow(); - let sampler = crate::hsv::UniformHsv::::new(Hsv::from(low), Hsv::from(high)); + let sampler = crate::hsv::UniformHsv::::new( + Hsv::from_color_unclamped(low), + Hsv::from_color_unclamped(high), + ); UniformHwb { sampler, @@ -685,8 +699,10 @@ where { let low = *low_b.borrow(); let high = *high_b.borrow(); - let sampler = - crate::hsv::UniformHsv::::new_inclusive(Hsv::from(low), Hsv::from(high)); + let sampler = crate::hsv::UniformHsv::::new_inclusive( + Hsv::from_color_unclamped(low), + Hsv::from_color_unclamped(high), + ); UniformHwb { sampler, @@ -695,7 +711,7 @@ where } fn sample(&self, rng: &mut R) -> Hwb { - Hwb::from(self.sampler.sample(rng)) + Hwb::from_color_unclamped(self.sampler.sample(rng)) } } @@ -703,39 +719,39 @@ where mod test { use super::Hwb; use crate::encoding::Srgb; - use crate::{Limited, LinSrgb}; + use crate::{FromColor, Limited, LinSrgb}; #[test] fn red() { - let a = Hwb::from(LinSrgb::new(1.0, 0.0, 0.0)); + let a = Hwb::from_color(LinSrgb::new(1.0, 0.0, 0.0)); let b = Hwb::new(0.0, 0.0, 0.0); assert_relative_eq!(a, b, epsilon = 0.000001); } #[test] fn orange() { - let a = Hwb::from(LinSrgb::new(1.0, 0.5, 0.0)); + let a = Hwb::from_color(LinSrgb::new(1.0, 0.5, 0.0)); let b = Hwb::new(30.0, 0.0, 0.0); assert_relative_eq!(a, b, epsilon = 0.000001); } #[test] fn green() { - let a = Hwb::from(LinSrgb::new(0.0, 1.0, 0.0)); + let a = Hwb::from_color(LinSrgb::new(0.0, 1.0, 0.0)); let b = Hwb::new(120.0, 0.0, 0.0); assert_relative_eq!(a, b); } #[test] fn blue() { - let a = Hwb::from(LinSrgb::new(0.0, 0.0, 1.0)); + let a = Hwb::from_color(LinSrgb::new(0.0, 0.0, 1.0)); let b = Hwb::new(240.0, 0.0, 0.0); assert_relative_eq!(a, b); } #[test] fn purple() { - let a = Hwb::from(LinSrgb::new(0.5, 0.0, 1.0)); + let a = Hwb::from_color(LinSrgb::new(0.5, 0.0, 1.0)); let b = Hwb::new(270.0, 0.0, 0.0); assert_relative_eq!(a, b, epsilon = 0.000001); } diff --git a/palette/src/lab.rs b/palette/src/lab.rs index f53da8154..81a30b1e8 100644 --- a/palette/src/lab.rs +++ b/palette/src/lab.rs @@ -10,11 +10,12 @@ use rand::Rng; use crate::color_difference::ColorDifference; use crate::color_difference::{get_ciede_difference, LabColorDiff}; +use crate::convert::FromColorUnclamped; use crate::encoding::pixel::RawPixel; use crate::white_point::{WhitePoint, D65}; use crate::{ clamp, contrast_ratio, from_f64, Alpha, Component, ComponentWise, FloatComponent, GetHue, - IntoColor, LabHue, Lch, Limited, Mix, Pixel, RelativeContrast, Shade, Xyz, + LabHue, Lch, Limited, Mix, Pixel, RelativeContrast, Shade, Xyz, }; /// CIE L\*a\*b\* (CIELAB) with an alpha component. See the [`Laba` @@ -32,12 +33,14 @@ pub type Laba = Alpha, T>; /// /// The parameters of L\*a\*b\* are quite different, compared to many other /// color spaces, so manipulating them manually may be unintuitive. -#[derive(Debug, PartialEq, FromColor, Pixel)] +#[derive(Debug, PartialEq, Pixel, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] -#[palette_internal] -#[palette_white_point = "Wp"] -#[palette_component = "T"] -#[palette_manual_from(Xyz, Lab, Lch)] +#[palette( + palette_internal, + white_point = "Wp", + component = "T", + skip_derives(Xyz, Lab, Lch) +)] #[repr(C)] pub struct Lab where @@ -57,7 +60,7 @@ where /// The white point associated with the color's illuminant and observer. /// D65 for 2 degree observer is used by default. #[cfg_attr(feature = "serializing", serde(skip))] - #[palette_unsafe_zero_sized] + #[palette(unsafe_zero_sized)] pub white_point: PhantomData, } @@ -190,12 +193,22 @@ where } } -impl From> for Lab +impl FromColorUnclamped> for Lab where + Wp: WhitePoint, T: FloatComponent, +{ + fn from_color_unclamped(color: Lab) -> Self { + color + } +} + +impl FromColorUnclamped> for Lab +where Wp: WhitePoint, + T: FloatComponent, { - fn from(color: Xyz) -> Self { + fn from_color_unclamped(color: Xyz) -> Self { let Xyz { mut x, mut y, @@ -227,12 +240,12 @@ where } } -impl From> for Lab +impl FromColorUnclamped> for Lab where - T: FloatComponent, Wp: WhitePoint, + T: FloatComponent, { - fn from(color: Lch) -> Self { + fn from_color_unclamped(color: Lch) -> Self { Lab { l: color.l, a: color.chroma.max(T::zero()) * color.hue.to_radians().cos(), @@ -669,10 +682,12 @@ where type Scalar = T; fn get_contrast_ratio(&self, other: &Self) -> T { - let luma1 = self.into_luma(); - let luma2 = other.into_luma(); + use crate::FromColor; + + let xyz1 = Xyz::from_color(*self); + let xyz2 = Xyz::from_color(*other); - contrast_ratio(luma1.luma, luma2.luma) + contrast_ratio(xyz1.y, xyz2.y) } } @@ -769,25 +784,25 @@ where mod test { use super::Lab; use crate::white_point::D65; - use crate::LinSrgb; + use crate::{FromColor, LinSrgb}; #[test] fn red() { - let a = Lab::from(LinSrgb::new(1.0, 0.0, 0.0)); + let a = Lab::from_color(LinSrgb::new(1.0, 0.0, 0.0)); let b = Lab::new(53.23288, 80.09246, 67.2031); assert_relative_eq!(a, b, epsilon = 0.01); } #[test] fn green() { - let a = Lab::from(LinSrgb::new(0.0, 1.0, 0.0)); + let a = Lab::from_color(LinSrgb::new(0.0, 1.0, 0.0)); let b = Lab::new(87.73704, -86.184654, 83.18117); assert_relative_eq!(a, b, epsilon = 0.01); } #[test] fn blue() { - let a = Lab::from(LinSrgb::new(0.0, 0.0, 1.0)); + let a = Lab::from_color(LinSrgb::new(0.0, 0.0, 1.0)); let b = Lab::new(32.302586, 79.19668, -107.863686); assert_relative_eq!(a, b, epsilon = 0.01); } diff --git a/palette/src/lch.rs b/palette/src/lch.rs index 980934fef..98ee584bc 100644 --- a/palette/src/lch.rs +++ b/palette/src/lch.rs @@ -10,11 +10,12 @@ use rand::Rng; use crate::color_difference::ColorDifference; use crate::color_difference::{get_ciede_difference, LabColorDiff}; +use crate::convert::{FromColorUnclamped, IntoColorUnclamped}; use crate::encoding::pixel::RawPixel; use crate::white_point::{WhitePoint, D65}; use crate::{ - clamp, contrast_ratio, from_f64, Alpha, Component, FloatComponent, FromColor, GetHue, Hue, - IntoColor, Lab, LabHue, Limited, Mix, Pixel, RelativeContrast, Saturate, Shade, Xyz, + clamp, contrast_ratio, from_f64, Alpha, Component, FloatComponent, FromColor, GetHue, Hue, Lab, + LabHue, Limited, Mix, Pixel, RelativeContrast, Saturate, Shade, Xyz, }; /// CIE L\*C\*h° with an alpha component. See the [`Lcha` implementation in @@ -27,12 +28,14 @@ pub type Lcha = Alpha, T>; /// it's a cylindrical color space, like [HSL](struct.Hsl.html) and /// [HSV](struct.Hsv.html). This gives it the same ability to directly change /// the hue and colorfulness of a color, while preserving other visual aspects. -#[derive(Debug, PartialEq, FromColor, Pixel)] +#[derive(Debug, PartialEq, Pixel, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] -#[palette_internal] -#[palette_white_point = "Wp"] -#[palette_component = "T"] -#[palette_manual_from(Xyz, Lab, Lch)] +#[palette( + palette_internal, + white_point = "Wp", + component = "T", + skip_derives(Xyz, Lab, Lch) +)] #[repr(C)] pub struct Lch where @@ -51,13 +54,13 @@ where /// The hue of the color, in degrees. Decides if it's red, blue, purple, /// etc. - #[palette_unsafe_same_layout_as = "T"] + #[palette(unsafe_same_layout_as = "T")] pub hue: LabHue, /// The white point associated with the color's illuminant and observer. /// D65 for 2 degree observer is used by default. #[cfg_attr(feature = "serializing", serde(skip))] - #[palette_unsafe_zero_sized] + #[palette(unsafe_zero_sized)] pub white_point: PhantomData, } @@ -189,23 +192,33 @@ where } } -impl From> for Lch +impl FromColorUnclamped> for Lch where - T: FloatComponent, Wp: WhitePoint, + T: FloatComponent, { - fn from(color: Xyz) -> Self { - let lab: Lab = color.into_lab(); - Self::from_lab(lab) + fn from_color_unclamped(color: Lch) -> Self { + color } } -impl From> for Lch +impl FromColorUnclamped> for Lch where + Wp: WhitePoint, T: FloatComponent, +{ + fn from_color_unclamped(color: Xyz) -> Self { + let lab: Lab = color.into_color_unclamped(); + Self::from_color_unclamped(lab) + } +} + +impl FromColorUnclamped> for Lch +where Wp: WhitePoint, + T: FloatComponent, { - fn from(color: Lab) -> Self { + fn from_color_unclamped(color: Lab) -> Self { Lch { l: color.l, chroma: (color.a * color.a + color.b * color.b).sqrt(), @@ -560,10 +573,10 @@ where type Scalar = T; fn get_contrast_ratio(&self, other: &Self) -> T { - let luma1 = self.into_luma(); - let luma2 = other.into_luma(); + let xyz1 = Xyz::from_color(*self); + let xyz2 = Xyz::from_color(*other); - contrast_ratio(luma1.luma, luma2.luma) + contrast_ratio(xyz1.y, xyz2.y) } } diff --git a/palette/src/lib.rs b/palette/src/lib.rs index c2fe50bcd..48754ec25 100644 --- a/palette/src/lib.rs +++ b/palette/src/lib.rs @@ -185,10 +185,7 @@ use float::Float; use luma::Luma; -#[doc(hidden)] -pub use palette_derive::*; - -pub use alpha::Alpha; +pub use alpha::{Alpha, WithAlpha}; pub use blend::Blend; #[cfg(feature = "std")] pub use gradient::Gradient; @@ -205,7 +202,7 @@ pub use yxy::{Yxy, Yxya}; pub use color_difference::ColorDifference; pub use component::*; -pub use convert::{ConvertFrom, ConvertInto, FromColor, IntoColor, OutOfBounds}; +pub use convert::{FromColor, IntoColor}; pub use encoding::pixel::Pixel; pub use hues::{LabHue, RgbHue}; pub use matrix::Mat3; @@ -391,7 +388,7 @@ mod hues; pub mod chromatic_adaptation; mod color_difference; mod component; -mod convert; +pub mod convert; pub mod encoding; mod equality; mod matrix; diff --git a/palette/src/luma/luma.rs b/palette/src/luma/luma.rs index 14c0cdb0d..e7c77421e 100644 --- a/palette/src/luma/luma.rs +++ b/palette/src/luma/luma.rs @@ -1,3 +1,4 @@ +use core::any::TypeId; use core::fmt; use core::marker::PhantomData; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; @@ -11,14 +12,14 @@ use rand::distributions::{Distribution, Standard}; use rand::Rng; use crate::blend::PreAlpha; +use crate::convert::FromColorUnclamped; use crate::encoding::linear::LinearFn; use crate::encoding::pixel::RawPixel; use crate::encoding::{Linear, Srgb, TransferFn}; use crate::luma::LumaStandard; -use crate::white_point::WhitePoint; use crate::{ - clamp, contrast_ratio, Alpha, Blend, Component, ComponentWise, FloatComponent, FromColor, - FromComponent, IntoColor, Limited, Mix, Pixel, RelativeContrast, Shade, Xyz, Yxy, + clamp, contrast_ratio, Alpha, Blend, Component, ComponentWise, FloatComponent, FromComponent, + Limited, Mix, Pixel, RelativeContrast, Shade, Xyz, Yxy, }; /// Luminance with an alpha component. See the [`Lumaa` implementation @@ -32,12 +33,14 @@ pub type Lumaa = Alpha, T>; /// perceived to be. It's basically the `Y` component of [CIE /// XYZ](struct.Xyz.html). The lack of any form of hue representation limits /// the set of operations that can be performed on it. -#[derive(Debug, PartialEq, FromColor, Pixel)] +#[derive(Debug, PartialEq, Pixel, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] -#[palette_internal] -#[palette_white_point = "S::WhitePoint"] -#[palette_component = "T"] -#[palette_manual_from(Xyz, Yxy, Luma = "from_linear")] +#[palette( + palette_internal, + white_point = "S::WhitePoint", + component = "T", + skip_derives(Xyz, Yxy, Luma) +)] #[repr(C)] pub struct Luma where @@ -49,7 +52,7 @@ where /// The kind of RGB standard. sRGB is the default. #[cfg_attr(feature = "serializing", serde(skip))] - #[palette_unsafe_zero_sized] + #[palette(unsafe_zero_sized)] pub standard: PhantomData, } @@ -122,6 +125,13 @@ where pub fn max_luma() -> T { T::max_intensity() } + + fn reinterpret_as>(&self) -> Luma { + Luma { + luma: self.luma, + standard: PhantomData, + } + } } impl Luma @@ -243,12 +253,27 @@ where } } -impl From> for Luma +impl FromColorUnclamped> for Luma +where + S1: LumaStandard, + S2: LumaStandard, + T: FloatComponent, +{ + fn from_color_unclamped(color: Luma) -> Self { + if TypeId::of::() == TypeId::of::() { + color.reinterpret_as() + } else { + Self::from_linear(color.into_linear().reinterpret_as()) + } + } +} + +impl FromColorUnclamped> for Luma where S: LumaStandard, T: FloatComponent, { - fn from(color: Xyz) -> Self { + fn from_color_unclamped(color: Xyz) -> Self { Self::from_linear(Luma { luma: color.y, standard: PhantomData, @@ -256,12 +281,12 @@ where } } -impl From> for Luma +impl FromColorUnclamped> for Luma where S: LumaStandard, T: FloatComponent, { - fn from(color: Yxy) -> Self { + fn from_color_unclamped(color: Yxy) -> Self { Self::from_linear(Luma { luma: color.luma, standard: PhantomData, @@ -293,25 +318,6 @@ impl Into<(T, A)> for Alpha IntoColor for Luma -where - S: LumaStandard, - T: FloatComponent, - Wp: WhitePoint, -{ - fn into_xyz(self) -> Xyz { - Xyz::from_luma(self.into_linear()) - } - - fn into_yxy(self) -> Yxy { - Yxy::from_luma(self.into_linear()) - } - - fn into_luma(self) -> Luma, T> { - self.into_linear() - } -} - impl Limited for Luma where T: Component, @@ -372,11 +378,15 @@ where type Color = Luma; fn into_premultiplied(self) -> PreAlpha, T> { - Lumaa::from(self).into() + Lumaa { + color: self, + alpha: T::one(), + } + .into_premultiplied() } fn from_premultiplied(color: PreAlpha, T>) -> Self { - Lumaa::from(color).into() + Lumaa::from_premultiplied(color).color } } @@ -747,8 +757,8 @@ where type Scalar = T; fn get_contrast_ratio(&self, other: &Self) -> T { - let luma1 = self.into_luma(); - let luma2 = other.into_luma(); + let luma1 = self.into_linear(); + let luma2 = other.into_linear(); contrast_ratio(luma1.luma, luma2.luma) } diff --git a/palette/src/luma/mod.rs b/palette/src/luma/mod.rs index ecc9d768e..334ed48ca 100644 --- a/palette/src/luma/mod.rs +++ b/palette/src/luma/mod.rs @@ -23,7 +23,7 @@ pub type GammaLuma = Luma, T>; pub type GammaLumaa = Lumaa, T>; /// A white point and a transfer function. -pub trait LumaStandard { +pub trait LumaStandard: 'static { /// The white point of the color space. type WhitePoint: WhitePoint; diff --git a/palette/src/matrix.rs b/palette/src/matrix.rs index 3296dac3f..da17f9ee3 100644 --- a/palette/src/matrix.rs +++ b/palette/src/matrix.rs @@ -3,7 +3,7 @@ use core::marker::PhantomData; -use crate::convert::IntoColor; +use crate::convert::IntoColorUnclamped; use crate::encoding::Linear; use crate::float::Float; use crate::rgb::{Primaries, Rgb, RgbSpace}; @@ -99,9 +99,9 @@ pub fn matrix_inverse(a: &Mat3) -> Mat3 { /// Geneartes to Srgb to Xyz transformation matrix for the given white point pub fn rgb_to_xyz_matrix() -> Mat3 { - let r: Xyz = S::Primaries::red().into_xyz(); - let g: Xyz = S::Primaries::green().into_xyz(); - let b: Xyz = S::Primaries::blue().into_xyz(); + let r: Xyz = S::Primaries::red().into_color_unclamped(); + let g: Xyz = S::Primaries::green().into_color_unclamped(); + let b: Xyz = S::Primaries::blue().into_color_unclamped(); let mut transform_matrix = mat3_from_primaries(r, g, b); diff --git a/palette/src/rgb/mod.rs b/palette/src/rgb/mod.rs index 08e69ae22..c30f31a46 100644 --- a/palette/src/rgb/mod.rs +++ b/palette/src/rgb/mod.rs @@ -1,7 +1,5 @@ //! RGB types, spaces and standards. -use core::any::Any; - use crate::encoding::{self, Gamma, Linear, TransferFn}; use crate::white_point::WhitePoint; use crate::{Component, FloatComponent, FromComponent, Yxy}; @@ -28,7 +26,7 @@ pub type GammaSrgb = Rgb, T>; pub type GammaSrgba = Rgba, T>; /// An RGB space and a transfer function. -pub trait RgbStandard { +pub trait RgbStandard: 'static { /// The RGB color space. type Space: RgbSpace; @@ -47,7 +45,7 @@ impl RgbStandard for (P, W, T) { } /// A set of primaries and a white point. -pub trait RgbSpace { +pub trait RgbSpace: 'static { /// The primaries of the RGB color space. type Primaries: Primaries; @@ -61,7 +59,7 @@ impl RgbSpace for (P, W) { } /// Represents the red, green and blue primaries of an RGB space. -pub trait Primaries: Any { +pub trait Primaries: 'static { /// Primary red. fn red() -> Yxy; /// Primary green. diff --git a/palette/src/rgb/packed.rs b/palette/src/rgb/packed.rs index 101c2c97c..dd6f878a0 100644 --- a/palette/src/rgb/packed.rs +++ b/palette/src/rgb/packed.rs @@ -65,7 +65,7 @@ use crate::Pixel; /// assert_eq!(colors[1].color, 0x60BBCC); /// ``` #[derive(Copy, Clone, Debug, PartialEq, Eq, Pixel)] -#[palette_internal] +#[palette(palette_internal)] #[repr(C)] pub struct Packed { /// The sRGB color packed into a `u32`. @@ -74,7 +74,7 @@ pub struct Packed { /// The channel ordering for red, green, blue, and alpha components in the /// packed integer; can be `Abgr`, `Argb`, `Bgra`, or `Rgba`. See /// [RgbChannels](trait.RgbChannels.html). - #[palette_unsafe_zero_sized] + #[palette(unsafe_zero_sized)] pub channel_order: PhantomData, } diff --git a/palette/src/rgb/rgb.rs b/palette/src/rgb/rgb.rs index d05a2e3f7..e809e00e4 100644 --- a/palette/src/rgb/rgb.rs +++ b/palette/src/rgb/rgb.rs @@ -15,19 +15,18 @@ use rand::Rng; use crate::alpha::Alpha; use crate::blend::PreAlpha; -use crate::convert::{FromColor, IntoColor}; +use crate::convert::FromColorUnclamped; use crate::encoding::linear::LinearFn; use crate::encoding::pixel::RawPixel; use crate::encoding::{Linear, Srgb}; use crate::luma::LumaStandard; use crate::matrix::{matrix_inverse, multiply_xyz_to_rgb, rgb_to_xyz_matrix}; use crate::rgb::{Packed, RgbChannels, RgbSpace, RgbStandard, TransferFn}; -use crate::white_point::WhitePoint; use crate::{ clamp, contrast_ratio, from_f64, Blend, Component, ComponentWise, FloatComponent, FromComponent, GetHue, Limited, Mix, Pixel, RelativeContrast, Shade, }; -use crate::{Hsl, Hsv, Hwb, Lab, Lch, Luma, RgbHue, Xyz, Yxy}; +use crate::{Hsl, Hsv, Luma, RgbHue, Xyz}; /// Generic RGB with an alpha component. See the [`Rgba` implementation in /// `Alpha`](../struct.Alpha.html#Rgba). @@ -44,13 +43,15 @@ pub type Rgba = Alpha, T>; /// linear, meaning that gamma correction is required when converting to and /// from a displayable RGB, such as sRGB. See the [`pixel`](pixel/index.html) /// module for encoding formats. -#[derive(Debug, PartialEq, FromColor, Pixel)] +#[derive(Debug, PartialEq, Pixel, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] -#[palette_internal] -#[palette_rgb_space = "S::Space"] -#[palette_white_point = "::WhitePoint"] -#[palette_component = "T"] -#[palette_manual_from(Xyz, Hsv, Hsl, Luma, Rgb = "from_rgb_internal")] +#[palette( + palette_internal, + rgb_space = "S::Space", + white_point = "::WhitePoint", + component = "T", + skip_derives(Xyz, Hsv, Hsl, Luma, Rgb) +)] #[repr(C)] pub struct Rgb { /// The amount of red light, where 0.0 is no red light and 1.0f (or 255u8) @@ -67,7 +68,7 @@ pub struct Rgb { /// The kind of RGB standard. sRGB is the default. #[cfg_attr(feature = "serializing", serde(skip))] - #[palette_unsafe_zero_sized] + #[palette(unsafe_zero_sized)] pub standard: PhantomData, } @@ -218,22 +219,11 @@ impl Rgb { S::TransferFn::from_linear(St::TransferFn::into_linear(color.blue)), ) } - - fn from_rgb_internal(rgb: Rgb, T>) -> Self - where - Sp: RgbSpace::WhitePoint>, - { - if TypeId::of::() == TypeId::of::<::Primaries>() { - Self::from_linear(rgb.reinterpret_as()) - } else { - Self::from_xyz(Xyz::from_rgb(rgb)) - } - } } -impl, T: Component> Rgb { +impl Rgb { #[inline] - fn reinterpret_as>(self) -> Rgb + fn reinterpret_as(self) -> Rgb where S::Space: RgbSpace::WhitePoint>, { @@ -364,6 +354,123 @@ impl Alpha, A> { } } +impl FromColorUnclamped> for Rgb +where + S1: RgbStandard, + S2: RgbStandard, + S2::Space: RgbSpace::WhitePoint>, + T: FloatComponent, +{ + fn from_color_unclamped(rgb: Rgb) -> Self { + if TypeId::of::() == TypeId::of::() { + rgb.reinterpret_as() + } else if TypeId::of::<::Primaries>() + == TypeId::of::<::Primaries>() + { + Self::from_linear(rgb.into_linear().reinterpret_as()) + } else { + Self::from_color_unclamped(Xyz::from_color_unclamped(rgb)) + } + } +} + +impl FromColorUnclamped::WhitePoint, T>> for Rgb +where + S: RgbStandard, + T: FloatComponent, +{ + fn from_color_unclamped(color: Xyz<::WhitePoint, T>) -> Self { + let transform_matrix = matrix_inverse(&rgb_to_xyz_matrix::()); + Self::from_linear(multiply_xyz_to_rgb(&transform_matrix, &color)) + } +} + +impl FromColorUnclamped> for Rgb +where + S: RgbStandard, + T: FloatComponent, +{ + fn from_color_unclamped(hsl: Hsl) -> Self { + let c = (T::one() - (hsl.lightness * from_f64(2.0) - T::one()).abs()) * hsl.saturation; + let h = hsl.hue.to_positive_degrees() / from_f64(60.0); + let x = c * (T::one() - (h % from_f64(2.0) - T::one()).abs()); + let m = hsl.lightness - c * from_f64(0.5); + + let (red, green, blue) = if h >= T::zero() && h < T::one() { + (c, x, T::zero()) + } else if h >= T::one() && h < from_f64(2.0) { + (x, c, T::zero()) + } else if h >= from_f64(2.0) && h < from_f64(3.0) { + (T::zero(), c, x) + } else if h >= from_f64(3.0) && h < from_f64(4.0) { + (T::zero(), x, c) + } else if h >= from_f64(4.0) && h < from_f64(5.0) { + (x, T::zero(), c) + } else { + (c, T::zero(), x) + }; + + Self::from_linear(Rgb { + red: red + m, + green: green + m, + blue: blue + m, + standard: PhantomData, + }) + } +} + +impl FromColorUnclamped> for Rgb +where + S: RgbStandard, + T: FloatComponent, +{ + fn from_color_unclamped(hsv: Hsv) -> Self { + let c = hsv.value * hsv.saturation; + let h = hsv.hue.to_positive_degrees() / from_f64(60.0); + let x = c * (T::one() - (h % from_f64(2.0) - T::one()).abs()); + let m = hsv.value - c; + + let (red, green, blue) = if h >= T::zero() && h < T::one() { + (c, x, T::zero()) + } else if h >= T::one() && h < from_f64(2.0) { + (x, c, T::zero()) + } else if h >= from_f64(2.0) && h < from_f64(3.0) { + (T::zero(), c, x) + } else if h >= from_f64(3.0) && h < from_f64(4.0) { + (T::zero(), x, c) + } else if h >= from_f64(4.0) && h < from_f64(5.0) { + (x, T::zero(), c) + } else { + (c, T::zero(), x) + }; + + Self::from_linear(Rgb { + red: red + m, + green: green + m, + blue: blue + m, + standard: PhantomData, + }) + } +} + +impl FromColorUnclamped> for Rgb +where + S: RgbStandard, + St: LumaStandard::WhitePoint>, + T: FloatComponent, +{ + fn from_color_unclamped(color: Luma) -> Self { + let luma = color.into_linear(); + + Self::from_linear(Rgb { + red: luma.luma, + green: luma.luma, + blue: luma.luma, + standard: PhantomData, + }) + } +} + impl Limited for Rgb where S: RgbStandard, @@ -454,11 +561,15 @@ where type Color = Rgb; fn into_premultiplied(self) -> PreAlpha, T> { - Rgba::from(self).into() + Rgba { + color: self, + alpha: T::one(), + } + .into_premultiplied() } fn from_premultiplied(color: PreAlpha, T>) -> Self { - Rgba::from(color).into() + Rgba::from_premultiplied(color).color } } @@ -738,117 +849,6 @@ where } } -impl From> for Rgb -where - S: RgbStandard, - T: FloatComponent, - Wp: WhitePoint, - S::Space: RgbSpace, -{ - fn from(color: Xyz) -> Self { - let transform_matrix = matrix_inverse(&rgb_to_xyz_matrix::()); - Self::from_linear(multiply_xyz_to_rgb(&transform_matrix, &color)) - } -} - -impl From> for Rgb -where - S: RgbStandard, - T: FloatComponent, - Wp: WhitePoint, - S::Space: RgbSpace, - Sp: RgbSpace, -{ - fn from(color: Hsl) -> Self { - let hsl = Hsl::::from_hsl(color); - - let c = (T::one() - (hsl.lightness * from_f64(2.0) - T::one()).abs()) * hsl.saturation; - let h = hsl.hue.to_positive_degrees() / from_f64(60.0); - let x = c * (T::one() - (h % from_f64(2.0) - T::one()).abs()); - let m = hsl.lightness - c * from_f64(0.5); - - let (red, green, blue) = if h >= T::zero() && h < T::one() { - (c, x, T::zero()) - } else if h >= T::one() && h < from_f64(2.0) { - (x, c, T::zero()) - } else if h >= from_f64(2.0) && h < from_f64(3.0) { - (T::zero(), c, x) - } else if h >= from_f64(3.0) && h < from_f64(4.0) { - (T::zero(), x, c) - } else if h >= from_f64(4.0) && h < from_f64(5.0) { - (x, T::zero(), c) - } else { - (c, T::zero(), x) - }; - - Self::from_linear(Rgb { - red: red + m, - green: green + m, - blue: blue + m, - standard: PhantomData, - }) - } -} - -impl From> for Rgb -where - S: RgbStandard, - T: FloatComponent, - Wp: WhitePoint, - S::Space: RgbSpace, - Sp: RgbSpace, -{ - fn from(color: Hsv) -> Self { - let hsv = Hsv::::from_hsv(color); - - let c = hsv.value * hsv.saturation; - let h = hsv.hue.to_positive_degrees() / from_f64(60.0); - let x = c * (T::one() - (h % from_f64(2.0) - T::one()).abs()); - let m = hsv.value - c; - - let (red, green, blue) = if h >= T::zero() && h < T::one() { - (c, x, T::zero()) - } else if h >= T::one() && h < from_f64(2.0) { - (x, c, T::zero()) - } else if h >= from_f64(2.0) && h < from_f64(3.0) { - (T::zero(), c, x) - } else if h >= from_f64(3.0) && h < from_f64(4.0) { - (T::zero(), x, c) - } else if h >= from_f64(4.0) && h < from_f64(5.0) { - (x, T::zero(), c) - } else { - (c, T::zero(), x) - }; - - Self::from_linear(Rgb { - red: red + m, - green: green + m, - blue: blue + m, - standard: PhantomData, - }) - } -} - -impl From> for Rgb -where - S: RgbStandard, - T: FloatComponent, - Wp: WhitePoint, - S::Space: RgbSpace, - St: LumaStandard, -{ - fn from(color: Luma) -> Self { - let luma = color.into_linear(); - - Self::from_linear(Rgb { - red: luma.luma, - green: luma.luma, - blue: luma.luma, - standard: PhantomData, - }) - } -} - impl From<(T, T, T)> for Rgb { fn from(components: (T, T, T)) -> Self { Self::from_components(components) @@ -873,59 +873,6 @@ impl Into<(T, T, T, A)> for Alpha IntoColor for Rgb -where - S: RgbStandard, - T: FloatComponent, - Wp: WhitePoint, - S::Space: RgbSpace, -{ - #[inline(always)] - fn into_xyz(self) -> Xyz { - Xyz::from_rgb(self.into_linear()) - } - - #[inline(always)] - fn into_yxy(self) -> Yxy { - Yxy::from_rgb(self.into_linear()) - } - - #[inline(always)] - fn into_lab(self) -> Lab { - Lab::from_rgb(self.into_linear()) - } - - #[inline(always)] - fn into_lch(self) -> Lch { - Lch::from_rgb(self.into_linear()) - } - - #[inline(always)] - fn into_rgb>(self) -> Rgb, T> { - Rgb::from_rgb(self.into_linear()) - } - - #[inline(always)] - fn into_hsl>(self) -> Hsl { - Hsl::from_rgb(self.into_linear()) - } - - #[inline(always)] - fn into_hsv>(self) -> Hsv { - Hsv::from_rgb(self.into_linear()) - } - - #[inline(always)] - fn into_hwb>(self) -> Hwb { - Hwb::from_rgb(self.into_linear()) - } - - #[inline(always)] - fn into_luma(self) -> Luma, T> { - Luma::from_rgb(self.into_linear()) - } -} - impl AbsDiffEq for Rgb where T: Component + AbsDiffEq, @@ -1142,10 +1089,12 @@ where type Scalar = T; fn get_contrast_ratio(&self, other: &Self) -> T { - let luma1 = self.into_luma(); - let luma2 = other.into_luma(); + use crate::FromColor; + + let xyz1 = Xyz::from_color(*self); + let xyz2 = Xyz::from_color(*other); - contrast_ratio(luma1.luma, luma2.luma) + contrast_ratio(xyz1.y, xyz2.y) } } diff --git a/palette/src/white_point.rs b/palette/src/white_point.rs index 8ab7d9779..2937fdfb2 100644 --- a/palette/src/white_point.rs +++ b/palette/src/white_point.rs @@ -18,7 +18,7 @@ use crate::{from_f64, FloatComponent, Xyz}; /// Custom white points can be easily defined on an empty struct with the /// tristimulus values and can be used in place of the ones defined in this /// library. -pub trait WhitePoint { +pub trait WhitePoint: 'static { /// Get the Xyz chromacity co-ordinates for the white point. fn get_xyz() -> Xyz; } diff --git a/palette/src/xyz.rs b/palette/src/xyz.rs index 88a00cde3..6e62e224e 100644 --- a/palette/src/xyz.rs +++ b/palette/src/xyz.rs @@ -8,6 +8,7 @@ use rand::distributions::{Distribution, Standard}; #[cfg(feature = "random")] use rand::Rng; +use crate::convert::FromColorUnclamped; use crate::encoding::pixel::RawPixel; use crate::luma::LumaStandard; use crate::matrix::{multiply_rgb_to_xyz, rgb_to_xyz_matrix}; @@ -31,12 +32,14 @@ pub type Xyza = Alpha, T>; /// /// Conversions and operations on this color space depend on the defined white /// point -#[derive(Debug, PartialEq, FromColor, Pixel)] +#[derive(Debug, PartialEq, Pixel, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] -#[palette_internal] -#[palette_white_point = "Wp"] -#[palette_component = "T"] -#[palette_manual_from(Xyz, Rgb, Lab, Yxy, Luma)] +#[palette( + palette_internal, + white_point = "Wp", + component = "T", + skip_derives(Xyz, Yxy, Rgb, Lab, Luma) +)] #[repr(C)] pub struct Xyz where @@ -59,7 +62,7 @@ where /// The white point associated with the color's illuminant and observer. /// D65 for 2 degree observer is used by default. #[cfg_attr(feature = "serializing", serde(skip))] - #[palette_unsafe_zero_sized] + #[palette(unsafe_zero_sized)] pub white_point: PhantomData, } @@ -195,25 +198,35 @@ where } } -impl From> for Xyz +impl FromColorUnclamped> for Xyz +where + Wp: WhitePoint, + T: FloatComponent, +{ + fn from_color_unclamped(color: Xyz) -> Self { + color + } +} + +impl FromColorUnclamped> for Xyz where T: FloatComponent, Wp: WhitePoint, S: RgbStandard, S::Space: RgbSpace, { - fn from(color: Rgb) -> Self { + fn from_color_unclamped(color: Rgb) -> Self { let transform_matrix = rgb_to_xyz_matrix::(); multiply_rgb_to_xyz(&transform_matrix, &color.into_linear()) } } -impl From> for Xyz +impl FromColorUnclamped> for Xyz where T: FloatComponent, Wp: WhitePoint, { - fn from(color: Yxy) -> Self { + fn from_color_unclamped(color: Yxy) -> Self { let mut xyz = Xyz { y: color.luma, ..Default::default() @@ -227,12 +240,12 @@ where } } -impl From> for Xyz +impl FromColorUnclamped> for Xyz where T: FloatComponent, Wp: WhitePoint, { - fn from(color: Lab) -> Self { + fn from_color_unclamped(color: Lab) -> Self { let y = (color.l + from_f64(16.0)) / from_f64(116.0); let x = y + (color.a / from_f64(500.0)); let z = y - (color.b / from_f64(200.0)); @@ -253,13 +266,13 @@ where } } -impl From> for Xyz +impl FromColorUnclamped> for Xyz where T: FloatComponent, Wp: WhitePoint, S: LumaStandard, { - fn from(color: Luma) -> Self { + fn from_color_unclamped(color: Luma) -> Self { Wp::get_xyz() * color.luma } } @@ -746,35 +759,35 @@ where mod test { use super::Xyz; use crate::white_point::D65; - use crate::{LinLuma, LinSrgb}; + use crate::{FromColor, LinLuma, LinSrgb}; const X_N: f64 = 0.95047; const Y_N: f64 = 1.0; const Z_N: f64 = 1.08883; #[test] fn luma() { - let a = Xyz::from(LinLuma::new(0.5)); + let a = Xyz::from_color(LinLuma::new(0.5)); let b = Xyz::new(0.475235, 0.5, 0.544415); assert_relative_eq!(a, b, epsilon = 0.0001); } #[test] fn red() { - let a = Xyz::from(LinSrgb::new(1.0, 0.0, 0.0)); + let a = Xyz::from_color(LinSrgb::new(1.0, 0.0, 0.0)); let b = Xyz::new(0.41240, 0.21260, 0.01930); assert_relative_eq!(a, b, epsilon = 0.0001); } #[test] fn green() { - let a = Xyz::from(LinSrgb::new(0.0, 1.0, 0.0)); + let a = Xyz::from_color(LinSrgb::new(0.0, 1.0, 0.0)); let b = Xyz::new(0.35760, 0.71520, 0.11920); assert_relative_eq!(a, b, epsilon = 0.0001); } #[test] fn blue() { - let a = Xyz::from(LinSrgb::new(0.0, 0.0, 1.0)); + let a = Xyz::from_color(LinSrgb::new(0.0, 0.0, 1.0)); let b = Xyz::new(0.18050, 0.07220, 0.95030); assert_relative_eq!(a, b, epsilon = 0.0001); } diff --git a/palette/src/yxy.rs b/palette/src/yxy.rs index 5fb8a97c7..51998bb2e 100644 --- a/palette/src/yxy.rs +++ b/palette/src/yxy.rs @@ -8,12 +8,13 @@ use rand::distributions::{Distribution, Standard}; #[cfg(feature = "random")] use rand::Rng; +use crate::convert::{FromColorUnclamped, IntoColorUnclamped}; use crate::encoding::pixel::RawPixel; use crate::luma::LumaStandard; use crate::white_point::{WhitePoint, D65}; use crate::{ - clamp, contrast_ratio, Alpha, Component, ComponentWise, FloatComponent, IntoColor, Limited, - Luma, Mix, Pixel, RelativeContrast, Shade, Xyz, + clamp, contrast_ratio, Alpha, Component, ComponentWise, FloatComponent, Limited, Luma, Mix, + Pixel, RelativeContrast, Shade, Xyz, }; /// CIE 1931 Yxy (xyY) with an alpha component. See the [`Yxya` implementation @@ -27,12 +28,14 @@ pub type Yxya = Alpha, T>; /// for the color spaces are a plot of this color space's x and y coordiantes. /// /// Conversions and operations on this color space depend on the white point. -#[derive(Debug, PartialEq, FromColor, Pixel)] +#[derive(Debug, PartialEq, Pixel, FromColorUnclamped, WithAlpha)] #[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))] -#[palette_internal] -#[palette_white_point = "Wp"] -#[palette_component = "T"] -#[palette_manual_from(Xyz, Yxy, Luma)] +#[palette( + palette_internal, + white_point = "Wp", + component = "T", + skip_derives(Xyz, Yxy, Luma) +)] #[repr(C)] pub struct Yxy where @@ -55,7 +58,7 @@ where /// The white point associated with the color's illuminant and observer. /// D65 for 2 degree observer is used by default. #[cfg_attr(feature = "serializing", serde(skip))] - #[palette_unsafe_zero_sized] + #[palette(unsafe_zero_sized)] pub white_point: PhantomData, } @@ -187,12 +190,46 @@ where } } -impl From> for Yxy +impl From<(T, T, T)> for Yxy { + fn from(components: (T, T, T)) -> Self { + Self::from_components(components) + } +} + +impl Into<(T, T, T)> for Yxy { + fn into(self) -> (T, T, T) { + self.into_components() + } +} + +impl From<(T, T, T, A)> for Alpha, A> { + fn from(components: (T, T, T, A)) -> Self { + Self::from_components(components) + } +} + +impl Into<(T, T, T, A)> for Alpha, A> { + fn into(self) -> (T, T, T, A) { + self.into_components() + } +} + +impl FromColorUnclamped> for Yxy where + Wp: WhitePoint, T: FloatComponent, +{ + fn from_color_unclamped(color: Yxy) -> Self { + color + } +} + +impl FromColorUnclamped> for Yxy +where Wp: WhitePoint, + T: FloatComponent, { - fn from(xyz: Xyz) -> Self { + fn from_color_unclamped(xyz: Xyz) -> Self { let mut yxy = Yxy { x: T::zero(), y: T::zero(), @@ -209,12 +246,12 @@ where } } -impl From> for Yxy +impl FromColorUnclamped> for Yxy where T: FloatComponent, S: LumaStandard, { - fn from(luma: Luma) -> Self { + fn from_color_unclamped(luma: Luma) -> Self { Yxy { luma: luma.into_linear().luma, ..Default::default() @@ -222,30 +259,6 @@ where } } -impl From<(T, T, T)> for Yxy { - fn from(components: (T, T, T)) -> Self { - Self::from_components(components) - } -} - -impl Into<(T, T, T)> for Yxy { - fn into(self) -> (T, T, T) { - self.into_components() - } -} - -impl From<(T, T, T, A)> for Alpha, A> { - fn from(components: (T, T, T, A)) -> Self { - Self::from_components(components) - } -} - -impl Into<(T, T, T, A)> for Alpha, A> { - fn into(self) -> (T, T, T, A) { - self.into_components() - } -} - impl Limited for Yxy where T: FloatComponent, @@ -345,7 +358,7 @@ where // outside the usual color gamut and might cause scaling issues. Yxy { luma: T::zero(), - ..Wp::get_xyz().into_yxy() + ..Wp::get_xyz().into_color_unclamped() } } } @@ -708,32 +721,32 @@ where mod test { use super::Yxy; use crate::white_point::D65; - use crate::{LinLuma, LinSrgb}; + use crate::{FromColor, LinLuma, LinSrgb}; #[test] fn luma() { - let a = Yxy::from(LinLuma::new(0.5)); + let a = Yxy::from_color(LinLuma::new(0.5)); let b = Yxy::new(0.312727, 0.329023, 0.5); assert_relative_eq!(a, b, epsilon = 0.000001); } #[test] fn red() { - let a = Yxy::from(LinSrgb::new(1.0, 0.0, 0.0)); + let a = Yxy::from_color(LinSrgb::new(1.0, 0.0, 0.0)); let b = Yxy::new(0.64, 0.33, 0.212673); assert_relative_eq!(a, b, epsilon = 0.000001); } #[test] fn green() { - let a = Yxy::from(LinSrgb::new(0.0, 1.0, 0.0)); + let a = Yxy::from_color(LinSrgb::new(0.0, 1.0, 0.0)); let b = Yxy::new(0.3, 0.6, 0.715152); assert_relative_eq!(a, b, epsilon = 0.000001); } #[test] fn blue() { - let a = Yxy::from(LinSrgb::new(0.0, 0.0, 1.0)); + let a = Yxy::from_color(LinSrgb::new(0.0, 0.0, 1.0)); let b = Yxy::new(0.15, 0.06, 0.072175); assert_relative_eq!(a, b, epsilon = 0.000001); } diff --git a/palette/tests/color_checker_data/babel.rs b/palette/tests/color_checker_data/babel.rs index 4d6582eaf..81457bae8 100644 --- a/palette/tests/color_checker_data/babel.rs +++ b/palette/tests/color_checker_data/babel.rs @@ -9,8 +9,9 @@ The Rgb colors in this data appear to be adapted to the D50 white_point from the use approx::assert_relative_eq; use lazy_static::lazy_static; +use palette::convert::IntoColorUnclamped; use palette::white_point::D50; -use palette::{IntoColor, Lab, Xyz, Yxy}; +use palette::{Lab, Xyz, Yxy}; use super::load_data::{load_babel, ColorCheckerRaw}; use super::MAX_ERROR; @@ -37,9 +38,9 @@ macro_rules! impl_from_color { impl From<$self_ty> for BabelData { fn from(color: $self_ty) -> BabelData { BabelData { - yxy: color.into_yxy(), - xyz: color.into_xyz(), - lab: color.into_lab(), + yxy: color.into_color_unclamped(), + xyz: color.into_color_unclamped(), + lab: color.into_color_unclamped(), } } } diff --git a/palette/tests/color_checker_data/color_checker.rs b/palette/tests/color_checker_data/color_checker.rs index 9a6a4235f..b667cfb58 100644 --- a/palette/tests/color_checker_data/color_checker.rs +++ b/palette/tests/color_checker_data/color_checker.rs @@ -9,8 +9,9 @@ The Rgb colors in this data appear to be adapted to the reference white point fo use approx::assert_relative_eq; use lazy_static::lazy_static; +use palette::convert::IntoColorUnclamped; use palette::white_point::D50; -use palette::{IntoColor, Lab, Xyz, Yxy}; +use palette::{Lab, Xyz, Yxy}; use super::load_data::{load_color_checker, ColorCheckerRaw}; use super::MAX_ERROR; @@ -37,9 +38,9 @@ macro_rules! impl_from_color { impl From<$self_ty> for ColorCheckerData { fn from(color: $self_ty) -> ColorCheckerData { ColorCheckerData { - yxy: color.into_yxy(), - xyz: color.into_xyz(), - lab: color.into_lab(), + yxy: color.into_color_unclamped(), + xyz: color.into_color_unclamped(), + lab: color.into_color_unclamped(), } } } diff --git a/palette/tests/convert/data_cie_15_2004.rs b/palette/tests/convert/data_cie_15_2004.rs index 69debc6df..5cb62440c 100644 --- a/palette/tests/convert/data_cie_15_2004.rs +++ b/palette/tests/convert/data_cie_15_2004.rs @@ -10,8 +10,9 @@ use approx::assert_relative_eq; use csv; use serde_derive::Deserialize; +use palette::convert::IntoColorUnclamped; use palette::white_point::D65; -use palette::{IntoColor, Xyz, Yxy}; +use palette::{Xyz, Yxy}; #[derive(Deserialize, PartialEq)] struct Cie2004Raw { @@ -43,8 +44,8 @@ macro_rules! impl_from_color_pointer { impl From<$self_ty> for Cie2004 { fn from(color: $self_ty) -> Cie2004 { Cie2004 { - xyz: color.into_xyz(), - yxy: color.into_yxy(), + xyz: color.into_color_unclamped(), + yxy: color.into_color_unclamped(), } } } diff --git a/palette/tests/convert/data_ciede_2000.rs b/palette/tests/convert/data_ciede_2000.rs index 92afeb72b..5db3cdcae 100644 --- a/palette/tests/convert/data_ciede_2000.rs +++ b/palette/tests/convert/data_ciede_2000.rs @@ -14,6 +14,7 @@ use csv; use approx::assert_relative_eq; use serde_derive::Deserialize; +use palette::convert::FromColorUnclamped; use palette::white_point::D65; use palette::{ColorDifference, Lab, Lch}; @@ -73,8 +74,8 @@ pub fn run_tests() { let result_lab = expected.c1.get_color_difference(&expected.c2); check_equal_lab(result_lab, expected.delta_e); - let lch1: Lch<_, f64> = Lch::from(expected.c1); - let lch2: Lch<_, f64> = Lch::from(expected.c2); + let lch1: Lch<_, f64> = Lch::from_color_unclamped(expected.c1); + let lch2: Lch<_, f64> = Lch::from_color_unclamped(expected.c2); let result_lch = lch1.get_color_difference(&lch2); check_equal_lch(result_lch, expected.delta_e); } diff --git a/palette/tests/convert/data_color_mine.rs b/palette/tests/convert/data_color_mine.rs index cc4ced752..9d903fb38 100644 --- a/palette/tests/convert/data_color_mine.rs +++ b/palette/tests/convert/data_color_mine.rs @@ -7,8 +7,9 @@ use approx::assert_relative_eq; use lazy_static::lazy_static; use serde_derive::Deserialize; +use palette::convert::IntoColorUnclamped; use palette::white_point::D65; -use palette::{Hsl, Hsv, Hwb, IntoColor, Lab, Lch, LinSrgb, Srgb, Xyz, Yxy}; +use palette::{Hsl, Hsv, Hwb, Lab, Lch, LinSrgb, Srgb, Xyz, Yxy}; #[derive(Deserialize, PartialEq)] pub struct ColorMineRaw { @@ -89,13 +90,13 @@ macro_rules! impl_from_color { impl From<$self_ty> for ColorMine { fn from(color: $self_ty) -> ColorMine { ColorMine { - xyz: color.into_xyz(), - yxy: color.into_yxy(), - linear_rgb: color.into_rgb(), - rgb: color.into_rgb(), - hsl: color.into_hsl(), - hsv: color.into_hsv(), - hwb: color.into_hwb(), + xyz: color.into_color_unclamped(), + yxy: color.into_color_unclamped(), + linear_rgb: color.into_color_unclamped(), + rgb: color.into_color_unclamped(), + hsl: color.into_color_unclamped(), + hsv: color.into_color_unclamped(), + hwb: color.into_color_unclamped(), } } } diff --git a/palette/tests/convert/lab_lch.rs b/palette/tests/convert/lab_lch.rs index 6b1a7b761..ee82e0120 100644 --- a/palette/tests/convert/lab_lch.rs +++ b/palette/tests/convert/lab_lch.rs @@ -1,13 +1,14 @@ use approx::assert_relative_eq; -use palette::{IntoColor, Lab, Lch}; +use palette::convert::IntoColorUnclamped; +use palette::{Lab, Lch}; #[test] fn lab_lch_green() { let lab = Lab::new(46.23, -66.176, 63.872); let lch = Lch::new(46.23, 91.972, 136.015); - let expect_lab = lch.into_lab(); - let expect_lch = lab.into_lch(); + let expect_lab = lch.into_color_unclamped(); + let expect_lch = lab.into_color_unclamped(); assert_relative_eq!(lab, expect_lab, epsilon = 0.001); assert_relative_eq!(lch, expect_lch, epsilon = 0.001); @@ -18,8 +19,8 @@ fn lab_lch_magenta() { let lab = Lab::new(60.320, 98.254, -60.843); let lch = Lch::new(60.320, 115.567, 328.233); - let expect_lab = lch.into_lab(); - let expect_lch = lab.into_lch(); + let expect_lab = lch.into_color_unclamped(); + let expect_lch = lab.into_color_unclamped(); assert_relative_eq!(lab, expect_lab, epsilon = 0.001); assert_relative_eq!(lch, expect_lch, epsilon = 0.001); @@ -30,8 +31,8 @@ fn lab_lch_blue() { let lab = Lab::new(32.303, 79.197, -107.864); let lch = Lch::new(32.303, 133.816, 306.287); - let expect_lab = lch.into_lab(); - let expect_lch = lab.into_lch(); + let expect_lab = lch.into_color_unclamped(); + let expect_lch = lab.into_color_unclamped(); assert_relative_eq!(lab, expect_lab, epsilon = 0.001); assert_relative_eq!(lch, expect_lch, epsilon = 0.001); diff --git a/palette/tests/pointer_dataset/pointer_data.rs b/palette/tests/pointer_dataset/pointer_data.rs index acdb9c455..1f41a3e94 100644 --- a/palette/tests/pointer_dataset/pointer_data.rs +++ b/palette/tests/pointer_dataset/pointer_data.rs @@ -17,9 +17,10 @@ use lazy_static::lazy_static; use num_traits::{NumCast, ToPrimitive}; use serde_derive::Deserialize; +use palette::convert::IntoColorUnclamped; use palette::float::Float; use palette::white_point::WhitePoint; -use palette::{FloatComponent, IntoColor, Lab, Lch, Xyz}; +use palette::{FloatComponent, Lab, Lch, Xyz}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct PointerWP; @@ -67,8 +68,8 @@ macro_rules! impl_from_color_pointer { impl From<$self_ty> for PointerData { fn from(color: $self_ty) -> PointerData { PointerData { - lch: color.into_lch(), - lab: color.into_lab(), + lch: color.into_color_unclamped(), + lab: color.into_color_unclamped(), } } } diff --git a/palette_derive/Cargo.toml b/palette_derive/Cargo.toml index c87125edf..93a671748 100644 --- a/palette_derive/Cargo.toml +++ b/palette_derive/Cargo.toml @@ -18,5 +18,6 @@ proc-macro = true syn = { version = "^1.0", features = ["extra-traits"] } quote = "^1.0" proc-macro2 = "^1.0" +find-crate = "0.5" [features] diff --git a/palette_derive/src/alpha/mod.rs b/palette_derive/src/alpha/mod.rs new file mode 100644 index 000000000..3d190aa0a --- /dev/null +++ b/palette_derive/src/alpha/mod.rs @@ -0,0 +1,3 @@ +pub use self::with_alpha::derive as derive_with_alpha; + +mod with_alpha; diff --git a/palette_derive/src/alpha/with_alpha.rs b/palette_derive/src/alpha/with_alpha.rs new file mode 100644 index 000000000..cf587bff6 --- /dev/null +++ b/palette_derive/src/alpha/with_alpha.rs @@ -0,0 +1,124 @@ +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; + +use quote::quote; +use syn::{parse_quote, DeriveInput, Generics, Ident, Type}; + +use crate::{ + meta::{ + parse_field_attributes, parse_namespaced_attributes, FieldAttributes, IdentOrIndex, + TypeItemAttributes, + }, + util, +}; + +pub fn derive(item: TokenStream) -> ::std::result::Result> { + let DeriveInput { + ident, + generics: original_generics, + data, + attrs, + .. + } = syn::parse(item).map_err(|error| vec![error])?; + let generics = original_generics.clone(); + + let item_meta: TypeItemAttributes = parse_namespaced_attributes(attrs)?; + + let fields_meta: FieldAttributes = if let syn::Data::Struct(struct_data) = data { + parse_field_attributes(struct_data.fields)? + } else { + return Err(vec![syn::Error::new( + Span::call_site(), + "only structs are supported", + )]); + }; + + let implementation = if let Some((alpha_property, alpha_type)) = fields_meta.alpha_property { + implement_for_internal_alpha(&ident, &generics, &alpha_property, &alpha_type, &item_meta) + } else { + implement_for_external_alpha(&ident, &generics, &item_meta) + }; + + Ok(implementation.into()) +} + +fn implement_for_internal_alpha( + ident: &Ident, + generics: &Generics, + alpha_property: &IdentOrIndex, + alpha_type: &Type, + item_meta: &TypeItemAttributes, +) -> TokenStream2 { + let with_alpha_trait_path = util::path(["WithAlpha"], item_meta.internal); + let component_trait_path = util::path(["Component"], item_meta.internal); + + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_generics #with_alpha_trait_path<#alpha_type> for #ident #type_generics #where_clause { + type Color = Self; + type WithAlpha = Self; + + fn with_alpha(mut self, alpha: #alpha_type) -> Self::WithAlpha { + self.#alpha_property = alpha; + self + } + + fn without_alpha(mut self) -> Self::Color { + self.#alpha_property = #component_trait_path::max_intensity(); + self + } + + fn split(mut self) -> (Self::Color, #alpha_type) { + let opaque_alpha = #component_trait_path::max_intensity(); + let alpha = core::mem::replace(&mut self.#alpha_property, opaque_alpha); + (self, alpha) + } + } + } +} + +fn implement_for_external_alpha( + ident: &Ident, + generics: &Generics, + item_meta: &TypeItemAttributes, +) -> TokenStream2 { + let with_alpha_trait_path = util::path(["WithAlpha"], item_meta.internal); + let component_trait_path = util::path(["Component"], item_meta.internal); + let alpha_path = util::path(["Alpha"], item_meta.internal); + + let (_, type_generics, _) = generics.split_for_impl(); + + let alpha_type: Type = parse_quote!(_A); + let mut impl_generics = generics.clone(); + impl_generics.params.push(parse_quote!(_A)); + impl_generics + .make_where_clause() + .predicates + .push(parse_quote!(_A: #component_trait_path)); + let (impl_generics, _, where_clause) = impl_generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_generics #with_alpha_trait_path<#alpha_type> for #ident #type_generics #where_clause { + type Color = Self; + type WithAlpha = #alpha_path; + + fn with_alpha(self, alpha: #alpha_type) -> Self::WithAlpha { + #alpha_path { + color: self, + alpha + } + } + + fn without_alpha(self) -> Self::Color { + self + } + + fn split(self) -> (Self::Color, #alpha_type) { + (self, #component_trait_path::max_intensity()) + } + } + } +} diff --git a/palette_derive/src/convert/from_color.rs b/palette_derive/src/convert/from_color.rs deleted file mode 100644 index 0dc2d6591..000000000 --- a/palette_derive/src/convert/from_color.rs +++ /dev/null @@ -1,424 +0,0 @@ -use proc_macro::TokenStream; -use proc_macro2::{Span, TokenStream as TokenStream2}; -use syn::{parse_macro_input, DeriveInput, Generics, Ident, Type}; -use quote::quote; - -use crate::meta::{self, DataMetaParser, IdentOrIndex, KeyValuePair, MetaParser}; -use crate::util; - -use super::shared::{self, ConvertDirection}; - -use crate::COLOR_TYPES; - -pub fn derive(tokens: TokenStream) -> TokenStream { - let DeriveInput { - ident, - attrs, - generics: original_generics, - data, - .. - } = parse_macro_input!(tokens); - let mut generics = original_generics.clone(); - - let mut meta: FromColorMeta = meta::parse_attributes(attrs); - let item_meta: FromColorItemMeta = meta::parse_data_attributes(data); - - let (generic_component, generic_white_point) = shared::find_in_generics( - meta.component.as_ref(), - meta.white_point.as_ref(), - &original_generics, - ); - - let white_point = shared::white_point_type(meta.white_point.clone(), meta.internal); - let component = shared::component_type(meta.component.clone()); - - let (alpha_property, alpha_type) = item_meta - .alpha_property - .map(|(property, ty)| (Some(property), ty)) - .unwrap_or_else(|| (None, component.clone())); - - if generic_component { - shared::add_component_where_clause(&component, &mut generics, meta.internal); - } - - if generic_white_point { - shared::add_white_point_where_clause(&white_point, &mut generics, meta.internal); - } - - let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); - - // Assume conversion from Xyz by default - if meta.manual_implementations.is_empty() { - meta.manual_implementations.push(KeyValuePair { - key: Ident::new("Xyz", Span::call_site()), - value: None, - }); - } - - let methods = shared::generate_methods( - &ident, - ConvertDirection::From, - &meta.manual_implementations, - &component, - &white_point, - &shared::rgb_space_type(meta.rgb_space.clone(), &white_point, meta.internal), - &type_generics.as_turbofish(), - meta.internal, - ); - - let from_impls: Vec<_> = COLOR_TYPES - .into_iter() - .map(|&color| { - let skip_regular_from = (meta.internal && ident == color) - || meta - .manual_implementations - .iter() - .any(|color_impl| color_impl.key == color && color_impl.value.is_none()); - - let regular_from = if skip_regular_from { - None - } else { - Some(impl_from( - &ident, - color, - &meta, - &original_generics, - generic_component, - )) - }; - - let self_alpha = if meta.internal && ident != color { - Some(impl_from_no_alpha_to_alpha( - &ident, - color, - &meta, - &original_generics, - generic_component, - )) - } else { - None - }; - - let other_alpha = impl_from_alpha_to_no_alpha( - &ident, - color, - &meta, - &original_generics, - generic_component, - alpha_property.as_ref(), - &alpha_type, - ); - - let both_alpha = if meta.internal && ident != color { - Some(impl_from_alpha_to_alpha( - &ident, - color, - &meta, - &original_generics, - generic_component, - )) - } else { - None - }; - - quote! { - #regular_from - #self_alpha - #other_alpha - #both_alpha - } - }) - .collect(); - - let trait_path = util::path(&["FromColor"], meta.internal); - let from_color_impl = quote! { - #[automatically_derived] - impl #impl_generics #trait_path<#white_point, #component> for #ident #type_generics #where_clause { - #(#methods)* - } - }; - - let result = util::bundle_impl( - "FromColor", - ident, - meta.internal, - quote! { - #from_color_impl - #(#from_impls)* - }, - ); - - result.into() -} - -fn impl_from( - ident: &Ident, - color: &str, - meta: &FromColorMeta, - generics: &Generics, - generic_component: bool, -) -> TokenStream2 { - let (_, type_generics, _) = generics.split_for_impl(); - - let FromImplParameters { - generics, - trait_path, - color_ty, - method_call, - .. - } = prepare_from_impl(ident, color, meta, generics, generic_component); - - let (impl_generics, _, where_clause) = generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl #impl_generics From<#color_ty> for #ident #type_generics #where_clause { - fn from(color: #color_ty) -> Self { - use #trait_path; - #method_call - } - } - } -} - -fn impl_from_alpha_to_alpha( - ident: &Ident, - color: &str, - meta: &FromColorMeta, - generics: &Generics, - generic_component: bool, -) -> TokenStream2 { - let (_, type_generics, _) = generics.split_for_impl(); - - let FromImplParameters { - generics, - alpha_path, - trait_path, - color_ty, - component, - method_call, - } = prepare_from_impl(ident, color, meta, generics, generic_component); - - let (impl_generics, _, where_clause) = generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl #impl_generics From<#alpha_path<#color_ty, #component>> for #alpha_path<#ident #type_generics, #component> #where_clause { - fn from(color: #alpha_path<#color_ty, #component>) -> Self { - use #trait_path; - let #alpha_path {color, alpha} = color; - #alpha_path { - color: #method_call, - alpha - } - } - } - } -} - -fn impl_from_no_alpha_to_alpha( - ident: &Ident, - color: &str, - meta: &FromColorMeta, - generics: &Generics, - generic_component: bool, -) -> TokenStream2 { - let (_, type_generics, _) = generics.split_for_impl(); - - let FromImplParameters { - generics, - alpha_path, - trait_path, - color_ty, - method_call, - component, - } = prepare_from_impl(ident, color, meta, generics, generic_component); - - let (impl_generics, _, where_clause) = generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl #impl_generics From<#color_ty> for #alpha_path<#ident #type_generics, #component> #where_clause { - fn from(color: #color_ty) -> Self { - use #trait_path; - #method_call.into() - } - } - } -} - -fn impl_from_alpha_to_no_alpha( - ident: &Ident, - color: &str, - meta: &FromColorMeta, - generics: &Generics, - generic_component: bool, - alpha_property: Option<&IdentOrIndex>, - alpha_type: &Type, -) -> TokenStream2 { - let (_, type_generics, _) = generics.split_for_impl(); - - let FromImplParameters { - generics, - alpha_path, - trait_path, - color_ty, - method_call, - .. - } = prepare_from_impl(ident, color, meta, generics, generic_component); - - let (impl_generics, _, where_clause) = generics.split_for_impl(); - - if let Some(alpha_property) = alpha_property { - quote! { - #[automatically_derived] - impl #impl_generics From<#alpha_path<#color_ty, #alpha_type>> for #ident #type_generics #where_clause { - fn from(color: #alpha_path<#color_ty, #alpha_type>) -> Self { - use #trait_path; - let #alpha_path { color, alpha } = color; - let mut result = #method_call; - result.#alpha_property = alpha; - result - } - } - } - } else { - quote! { - #[automatically_derived] - impl #impl_generics From<#alpha_path<#color_ty, #alpha_type>> for #ident #type_generics #where_clause { - fn from(color: #alpha_path<#color_ty, #alpha_type>) -> Self { - use #trait_path; - let color = color.color; - #method_call - } - } - } - } -} - -fn prepare_from_impl( - ident: &Ident, - color: &str, - meta: &FromColorMeta, - generics: &Generics, - generic_component: bool, -) -> FromImplParameters { - let (_, type_generics, _) = generics.split_for_impl(); - let turbofish_generics = type_generics.as_turbofish(); - let mut generics = generics.clone(); - - let method_name = Ident::new(&format!("from_{}", color.to_lowercase()), Span::call_site()); - - let trait_path = util::path(&["FromColor"], meta.internal); - let alpha_path = util::path(&["Alpha"], meta.internal); - - let white_point = shared::white_point_type(meta.white_point.clone(), meta.internal); - let component = shared::component_type(meta.component.clone()); - - if generic_component { - shared::add_component_where_clause(&component, &mut generics, meta.internal) - } - - let color_ty = shared::get_convert_color_type( - color, - &white_point, - &component, - meta.rgb_space.as_ref(), - &mut generics, - meta.internal, - ); - - let method_call = match color { - "Rgb" | "Luma" => quote! { - #ident #turbofish_generics::#method_name(color.into_linear()) - }, - _ => quote! { - #ident #turbofish_generics::#method_name(color) - }, - }; - - return FromImplParameters { - generics, - alpha_path, - trait_path, - color_ty, - component, - method_call, - }; -} - -struct FromImplParameters { - generics: Generics, - alpha_path: TokenStream2, - trait_path: TokenStream2, - color_ty: Type, - component: Type, - method_call: TokenStream2, -} - -#[derive(Default)] -struct FromColorMeta { - manual_implementations: Vec, - internal: bool, - component: Option, - white_point: Option, - rgb_space: Option, -} - -impl MetaParser for FromColorMeta { - fn internal(&mut self) { - self.internal = true; - } - - fn parse_attribute(&mut self, attribute_name: Ident, attribute_tts: TokenStream2) { - match &*attribute_name.to_string() { - "palette_manual_from" => { - let impls = - meta::parse_tuple_attribute::(&attribute_name, attribute_tts); - self.manual_implementations.extend(impls) - } - "palette_component" => { - if self.component.is_none() { - let component = meta::parse_equal_attribute(&attribute_name, attribute_tts); - self.component = Some(component); - } - } - "palette_white_point" => { - if self.white_point.is_none() { - let white_point = meta::parse_equal_attribute(&attribute_name, attribute_tts); - self.white_point = Some(white_point); - } - } - "palette_rgb_space" => { - if self.rgb_space.is_none() { - let rgb_space = meta::parse_equal_attribute(&attribute_name, attribute_tts); - self.rgb_space = Some(rgb_space); - } - } - _ => {} - } - } -} - -#[derive(Default)] -struct FromColorItemMeta { - alpha_property: Option<(IdentOrIndex, Type)>, -} - -impl DataMetaParser for FromColorItemMeta { - fn parse_struct_field_attribute( - &mut self, - field_name: IdentOrIndex, - ty: Type, - attribute_name: Ident, - attribute_tts: TokenStream2, - ) { - match &*attribute_name.to_string() { - "palette_alpha" => { - meta::assert_empty_attribute(&attribute_name, attribute_tts); - self.alpha_property = Some((field_name, ty)); - } - _ => {} - } - } -} diff --git a/palette_derive/src/convert/from_color_unclamped.rs b/palette_derive/src/convert/from_color_unclamped.rs new file mode 100644 index 000000000..4892e9c92 --- /dev/null +++ b/palette_derive/src/convert/from_color_unclamped.rs @@ -0,0 +1,305 @@ +use std::collections::HashSet; + +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{parse_quote, DeriveInput, Generics, Ident, Result, Type}; + +use crate::meta::{ + parse_field_attributes, parse_namespaced_attributes, FieldAttributes, IdentOrIndex, + TypeItemAttributes, +}; +use crate::util; + +use crate::COLOR_TYPES; + +use super::util::{ + add_float_component_where_clause, add_white_point_where_clause, component_type, + find_in_generics, find_nearest_color, get_convert_color_type, white_point_type, +}; + +pub fn derive(item: TokenStream) -> ::std::result::Result> { + let DeriveInput { + ident, + generics: original_generics, + data, + attrs, + .. + } = syn::parse(item).map_err(|error| vec![error])?; + let mut generics = original_generics.clone(); + + let mut item_meta: TypeItemAttributes = parse_namespaced_attributes(attrs)?; + + let fields_meta: FieldAttributes = if let syn::Data::Struct(struct_data) = data { + parse_field_attributes(struct_data.fields)? + } else { + return Err(vec![syn::Error::new( + Span::call_site(), + "only structs are supported", + )]); + }; + + let (generic_component, generic_white_point) = find_in_generics( + item_meta.component.as_ref(), + item_meta.white_point.as_ref(), + &original_generics, + ); + + let white_point = white_point_type(item_meta.white_point.clone(), item_meta.internal); + let component = component_type(item_meta.component.clone()); + + let alpha_field = fields_meta.alpha_property; + + if generic_component { + add_float_component_where_clause(&component, &mut generics, item_meta.internal); + } + + if generic_white_point { + add_white_point_where_clause(&white_point, &mut generics, item_meta.internal); + } + + // Assume conversion from Xyz by default + if item_meta.skip_derives.is_empty() { + item_meta.skip_derives.insert("Xyz".into()); + } + + let all_from_impl_params = prepare_from_impl( + &item_meta.skip_derives, + &component, + &white_point, + item_meta.rgb_space.as_ref(), + &item_meta, + &generics, + generic_component, + ) + .map_err(|error| vec![error])?; + + let mut implementations = + generate_from_implementations(&ident, &generics, &item_meta, &all_from_impl_params); + + if let Some((alpha_property, alpha_type)) = alpha_field { + implementations.push(generate_from_alpha_implementation_with_internal( + &ident, + &generics, + &item_meta, + &alpha_property, + &alpha_type, + )); + } else { + implementations.push(generate_from_alpha_implementation( + &ident, &generics, &item_meta, + )); + } + + Ok(TokenStream::from(quote! { + #(#implementations)* + })) +} + +fn prepare_from_impl( + skip: &HashSet, + component: &Type, + white_point: &Type, + rgb_space: Option<&Type>, + meta: &TypeItemAttributes, + generics: &Generics, + generic_component: bool, +) -> Result> { + let included_colors = COLOR_TYPES + .into_iter() + .filter(|&&color| !skip.contains(color)); + let linear_path = util::path(&["encoding", "Linear"], meta.internal); + + let mut parameters = Vec::new(); + + for &color_name in included_colors { + let nearest_color_name = find_nearest_color(color_name, skip)?; + + let mut generics = generics.clone(); + if generic_component { + add_float_component_where_clause(&component, &mut generics, meta.internal) + } + + let color_ty = get_convert_color_type( + color_name, + white_point, + component, + rgb_space, + &mut generics, + meta.internal, + ); + + let nearest_color_path = util::color_path(nearest_color_name, meta.internal); + let target_color_rgb_space = match color_name { + "Rgb" => Some(parse_quote!(_S::Space)), + "Hsl" | "Hsv" | "Hwb" => Some(parse_quote!(_S)), + _ => None, + }; + + let rgb_space = rgb_space.cloned().or(target_color_rgb_space); + + let nearest_color_ty: Type = match nearest_color_name { + "Rgb" | "Hsl" | "Hsv" | "Hwb" => { + let rgb_space = rgb_space + .ok_or_else(|| { + syn::parse::Error::new( + Span::call_site(), + format!( + "could not determine which RGB space to use when converting to and from `{}` via `{}`", + color_name, + nearest_color_name + ), + ) + })?; + + if nearest_color_name == "Rgb" { + parse_quote!(#nearest_color_path::<#linear_path<#rgb_space>, #component>) + } else { + parse_quote!(#nearest_color_path::<#rgb_space, #component>) + } + } + "Luma" => parse_quote!(#nearest_color_path::<#linear_path<#white_point>, #component>), + _ => parse_quote!(#nearest_color_path::<#white_point, #component>), + }; + + parameters.push(FromImplParameters { + generics, + color_ty, + nearest_color_ty, + }); + } + + Ok(parameters) +} + +struct FromImplParameters { + generics: Generics, + color_ty: Type, + nearest_color_ty: Type, +} + +fn generate_from_implementations( + ident: &Ident, + generics: &Generics, + meta: &TypeItemAttributes, + all_parameters: &[FromImplParameters], +) -> Vec { + let from_trait_path = util::path(&["convert", "FromColorUnclamped"], meta.internal); + let into_trait_path = util::path(&["convert", "IntoColorUnclamped"], meta.internal); + + let (_, type_generics, _) = generics.split_for_impl(); + + let mut implementations = Vec::with_capacity(all_parameters.len()); + + for parameters in all_parameters { + let FromImplParameters { + color_ty, + generics, + nearest_color_ty, + } = parameters; + + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + implementations.push(quote! { + #[automatically_derived] + impl #impl_generics #from_trait_path<#color_ty> for #ident #type_generics #where_clause { + fn from_color_unclamped(color: #color_ty) -> Self { + use #from_trait_path; + use #into_trait_path; + #nearest_color_ty::from_color_unclamped(color).into_color_unclamped() + } + } + }); + + if !meta.internal || meta.internal_not_base_type { + implementations.push(quote! { + #[automatically_derived] + impl #impl_generics #from_trait_path<#ident #type_generics> for #color_ty #where_clause { + fn from_color_unclamped(color: #ident #type_generics) -> Self { + use #from_trait_path; + use #into_trait_path; + #nearest_color_ty::from_color_unclamped(color).into_color_unclamped() + } + } + }); + } + } + + implementations +} + +fn generate_from_alpha_implementation( + ident: &Ident, + generics: &Generics, + meta: &TypeItemAttributes, +) -> TokenStream2 { + let from_trait_path = util::path(&["convert", "FromColorUnclamped"], meta.internal); + let into_trait_path = util::path(&["convert", "IntoColorUnclamped"], meta.internal); + let component_trait_path = util::path(&["Component"], meta.internal); + let alpha_path = util::path(&["Alpha"], meta.internal); + + let mut impl_generics = generics.clone(); + impl_generics.params.push(parse_quote!(_C)); + impl_generics.params.push(parse_quote!(_A)); + { + let where_clause = impl_generics.make_where_clause(); + where_clause + .predicates + .push(parse_quote!(_C: #into_trait_path)); + where_clause + .predicates + .push(parse_quote!(_A: #component_trait_path)); + } + + let (_, type_generics, _) = generics.split_for_impl(); + let (self_impl_generics, _, self_where_clause) = impl_generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #self_impl_generics #from_trait_path<#alpha_path<_C, _A>> for #ident #type_generics #self_where_clause { + fn from_color_unclamped(color: #alpha_path<_C, _A>) -> Self { + color.color.into_color_unclamped() + } + } + } +} + +fn generate_from_alpha_implementation_with_internal( + ident: &Ident, + generics: &Generics, + meta: &TypeItemAttributes, + alpha_property: &IdentOrIndex, + alpha_type: &Type, +) -> TokenStream2 { + let from_trait_path = util::path(&["convert", "FromColorUnclamped"], meta.internal); + let into_trait_path = util::path(&["convert", "IntoColorUnclamped"], meta.internal); + let alpha_path = util::path(&["Alpha"], meta.internal); + + let (_, type_generics, _) = generics.split_for_impl(); + let mut impl_generics = generics.clone(); + impl_generics.params.push(parse_quote!(_C)); + { + let where_clause = impl_generics.make_where_clause(); + where_clause + .predicates + .push(parse_quote!(_C: #into_trait_path)); + } + let (impl_generics, _, where_clause) = impl_generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_generics #from_trait_path<#alpha_path<_C, #alpha_type>> for #ident #type_generics #where_clause { + fn from_color_unclamped(color: #alpha_path<_C, #alpha_type>) -> Self { + use #from_trait_path; + use #into_trait_path; + + let #alpha_path { color, alpha } = color; + + let mut result: Self = color.into_color_unclamped(); + result.#alpha_property = alpha; + + result + } + } + } +} diff --git a/palette_derive/src/convert/into_color.rs b/palette_derive/src/convert/into_color.rs deleted file mode 100644 index 3e1f7600a..000000000 --- a/palette_derive/src/convert/into_color.rs +++ /dev/null @@ -1,328 +0,0 @@ -use proc_macro::TokenStream; -use proc_macro2::{Span, TokenStream as TokenStream2}; -use syn::{parse_macro_input, DeriveInput, Generics, Ident, Type}; -use quote::quote; - -use crate::meta::{self, DataMetaParser, IdentOrIndex, KeyValuePair, MetaParser}; -use crate::util; -use crate::COLOR_TYPES; - -use super::shared::{self, ConvertDirection}; - -pub fn derive(tokens: TokenStream) -> TokenStream { - let DeriveInput { - ident, - attrs, - generics: original_generics, - data, - .. - } = parse_macro_input!(tokens); - let mut generics = original_generics.clone(); - - let mut meta: IntoColorMeta = meta::parse_attributes(attrs); - let item_meta: IntoColorItemMeta = meta::parse_data_attributes(data); - - let (generic_component, generic_white_point) = shared::find_in_generics( - meta.component.as_ref(), - meta.white_point.as_ref(), - &original_generics, - ); - - let white_point = shared::white_point_type(meta.white_point.clone(), meta.internal); - let component = shared::component_type(meta.component.clone()); - - let (alpha_property, alpha_type) = item_meta - .alpha_property - .map(|(property, ty)| (Some(property), ty)) - .unwrap_or_else(|| (None, component.clone())); - - if generic_component { - shared::add_component_where_clause(&component, &mut generics, meta.internal); - } - - if generic_white_point { - shared::add_white_point_where_clause(&white_point, &mut generics, meta.internal); - } - - let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); - - // Assume conversion into Xyz by default - if meta.manual_implementations.is_empty() { - meta.manual_implementations.push(KeyValuePair { - key: Ident::new("Xyz", Span::call_site()), - value: None, - }); - } - - let methods = shared::generate_methods( - &ident, - ConvertDirection::Into, - &meta.manual_implementations, - &component, - &white_point, - &shared::rgb_space_type(meta.rgb_space.clone(), &white_point, meta.internal), - &type_generics.as_turbofish(), - meta.internal, - ); - - let trait_path = util::path(&["IntoColor"], meta.internal); - let into_color_impl = quote! { - #[automatically_derived] - impl #impl_generics #trait_path<#white_point, #component> for #ident #type_generics #where_clause { - #(#methods)* - } - }; - - let into_impls = COLOR_TYPES.into_iter().map(|&color| { - let skip_regular_into = (meta.internal && ident == color) - || meta - .manual_implementations - .iter() - .any(|color_impl| color_impl.key == color && color_impl.value.is_none()); - - let regular_into = if skip_regular_into { - None - } else { - Some(impl_into( - &ident, - color, - &meta, - &original_generics, - generic_component, - )) - }; - - let with_alpha = impl_into_alpha( - &ident, - color, - &meta, - &original_generics, - generic_component, - alpha_property.as_ref(), - &alpha_type, - ); - - quote! { - #regular_into - #with_alpha - } - }); - - let result = util::bundle_impl( - "IntoColor", - ident.clone(), - meta.internal, - quote! { - #into_color_impl - #(#into_impls)* - }, - ); - - result.into() -} - -fn impl_into( - ident: &Ident, - color: &str, - meta: &IntoColorMeta, - generics: &Generics, - generic_component: bool, -) -> TokenStream2 { - let (_, type_generics, _) = generics.split_for_impl(); - - let IntoImplParameters { - generics, - trait_path, - color_ty, - method_call, - .. - } = prepare_into_impl(ident, color, meta, generics, generic_component); - - let (impl_generics, _, where_clause) = generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl #impl_generics Into<#color_ty> for #ident #type_generics #where_clause { - fn into(self) -> #color_ty { - use #trait_path; - let color = self; - #method_call - } - } - } -} - -fn impl_into_alpha( - ident: &Ident, - color: &str, - meta: &IntoColorMeta, - generics: &Generics, - generic_component: bool, - alpha_property: Option<&IdentOrIndex>, - alpha_type: &Type, -) -> TokenStream2 { - let (_, type_generics, _) = generics.split_for_impl(); - - let IntoImplParameters { - generics, - alpha_path, - trait_path, - color_ty, - method_call, - .. - } = prepare_into_impl(ident, color, meta, generics, generic_component); - - let (impl_generics, _, where_clause) = generics.split_for_impl(); - - if let Some(alpha_property) = alpha_property { - quote! { - #[automatically_derived] - impl #impl_generics Into<#alpha_path<#color_ty, #alpha_type>> for #ident #type_generics #where_clause { - fn into(self) -> #alpha_path<#color_ty, #alpha_type> { - use #trait_path; - let color = self; - #alpha_path { - alpha: color.#alpha_property.clone(), - color: #method_call, - } - } - } - } - } else { - quote! { - #[automatically_derived] - impl #impl_generics Into<#alpha_path<#color_ty, #alpha_type>> for #ident #type_generics #where_clause { - fn into(self) -> #alpha_path<#color_ty, #alpha_type> { - use #trait_path; - let color = self; - #method_call.into() - } - } - } - } -} - -fn prepare_into_impl( - ident: &Ident, - color: &str, - meta: &IntoColorMeta, - generics: &Generics, - generic_component: bool, -) -> IntoImplParameters { - let (_, type_generics, _) = generics.split_for_impl(); - let turbofish_generics = type_generics.as_turbofish(); - let mut generics = generics.clone(); - - let method_name = Ident::new(&format!("into_{}", color.to_lowercase()), Span::call_site()); - - let trait_path = util::path(&["IntoColor"], meta.internal); - let alpha_path = util::path(&["Alpha"], meta.internal); - - let white_point = shared::white_point_type(meta.white_point.clone(), meta.internal); - let component = shared::component_type(meta.component.clone()); - - if generic_component { - shared::add_component_where_clause(&component, &mut generics, meta.internal) - } - - let color_ty = shared::get_convert_color_type( - color, - &white_point, - &component, - meta.rgb_space.as_ref(), - &mut generics, - meta.internal, - ); - - let method_call = match color { - "Rgb" | "Luma" => quote! { - #ident #turbofish_generics::#method_name(color).into_encoding() - }, - _ => quote! { - #ident #turbofish_generics::#method_name(color) - }, - }; - - IntoImplParameters { - generics, - alpha_path, - trait_path, - color_ty, - method_call, - } -} - -struct IntoImplParameters { - generics: Generics, - alpha_path: TokenStream2, - trait_path: TokenStream2, - color_ty: Type, - method_call: TokenStream2, -} - -#[derive(Default)] -struct IntoColorMeta { - manual_implementations: Vec, - internal: bool, - component: Option, - white_point: Option, - rgb_space: Option, -} - -impl MetaParser for IntoColorMeta { - fn internal(&mut self) { - self.internal = true; - } - - fn parse_attribute(&mut self, attribute_name: Ident, attribute_tts: TokenStream2) { - match &*attribute_name.to_string() { - "palette_manual_into" => { - let impls = - meta::parse_tuple_attribute::(&attribute_name, attribute_tts); - self.manual_implementations.extend(impls) - } - "palette_component" => { - if self.component.is_none() { - let component = meta::parse_equal_attribute(&attribute_name, attribute_tts); - self.component = Some(component); - } - } - "palette_white_point" => { - if self.white_point.is_none() { - let white_point = meta::parse_equal_attribute(&attribute_name, attribute_tts); - self.white_point = Some(white_point); - } - } - "palette_rgb_space" => { - if self.rgb_space.is_none() { - let rgb_space = meta::parse_equal_attribute(&attribute_name, attribute_tts); - self.rgb_space = Some(rgb_space); - } - } - _ => {} - } - } -} - -#[derive(Default)] -struct IntoColorItemMeta { - alpha_property: Option<(IdentOrIndex, Type)>, -} - -impl DataMetaParser for IntoColorItemMeta { - fn parse_struct_field_attribute( - &mut self, - field_name: IdentOrIndex, - ty: Type, - attribute_name: Ident, - attribute_tts: TokenStream2, - ) { - match &*attribute_name.to_string() { - "palette_alpha" => { - meta::assert_empty_attribute(&attribute_name, attribute_tts); - self.alpha_property = Some((field_name, ty)); - } - _ => {} - } - } -} diff --git a/palette_derive/src/convert/mod.rs b/palette_derive/src/convert/mod.rs index 236ec48ce..de6c01cb4 100644 --- a/palette_derive/src/convert/mod.rs +++ b/palette_derive/src/convert/mod.rs @@ -1,6 +1,4 @@ -pub use self::from_color::derive as derive_from_color; -pub use self::into_color::derive as derive_into_color; +pub use self::from_color_unclamped::derive as derive_from_color_unclamped; -mod from_color; -mod into_color; -mod shared; +mod from_color_unclamped; +mod util; diff --git a/palette_derive/src/convert/shared.rs b/palette_derive/src/convert/shared.rs deleted file mode 100644 index 9dcc394ef..000000000 --- a/palette_derive/src/convert/shared.rs +++ /dev/null @@ -1,352 +0,0 @@ -use std::fmt; - -use proc_macro2::{Span, TokenStream}; -use syn::{parse_quote, GenericParam, Generics, Ident, Path, Turbofish, Type, TypePath}; -use quote::quote; - -use crate::meta::KeyValuePair; -use crate::util; - -pub fn find_in_generics( - component: Option<&Type>, - white_point: Option<&Type>, - generics: &Generics, -) -> (bool, bool) { - let mut generic_component = false; - let mut generic_white_point = false; - - for param in &generics.params { - if let GenericParam::Type(ref param) = *param { - if let Some(&Type::Path(TypePath { - qself: None, - path: - Path { - segments: ref component, - leading_colon, - }, - })) = component - { - let first = component.first(); - let is_ident_path = leading_colon.is_none() - && component.len() == 1 - && first.unwrap().arguments.is_empty() - && first.unwrap().ident == param.ident; - - if is_ident_path { - generic_component = true; - } - } - - if let Some(&Type::Path(TypePath { - qself: None, - path: - Path { - segments: ref white_point, - leading_colon, - }, - })) = white_point - { - let first = white_point.first(); - let is_ident_path = leading_colon.is_none() - && white_point.len() == 1 - && first.unwrap().arguments.is_empty() - && first.unwrap().ident == param.ident; - - if is_ident_path { - generic_white_point = true; - } - } - } - } - - (generic_component, generic_white_point) -} - -pub fn white_point_type(white_point: Option, internal: bool) -> Type { - white_point.unwrap_or_else(|| util::path_type(&["white_point", "D65"], internal)) -} - -pub fn component_type(component: Option) -> Type { - component.unwrap_or_else(|| parse_quote!(f32)) -} - -pub fn rgb_space_type(rgb_space: Option, white_point: &Type, internal: bool) -> Type { - rgb_space.unwrap_or_else(|| { - let srgb_path = util::path_type(&["encoding", "srgb", "Srgb"], internal); - parse_quote!((#srgb_path, #white_point)) - }) -} - -pub fn add_component_where_clause(component: &Type, generics: &mut Generics, internal: bool) { - let component_trait_path = util::path(&["FloatComponent"], internal); - - generics - .make_where_clause() - .predicates - .push(parse_quote!(#component: #component_trait_path + _FloatTrait)); -} - -pub fn add_white_point_where_clause(white_point: &Type, generics: &mut Generics, internal: bool) { - let white_point_trait_path = util::path(&["white_point", "WhitePoint"], internal); - - generics - .make_where_clause() - .predicates - .push(parse_quote!(#white_point: #white_point_trait_path)); -} - -pub fn generate_methods( - ident: &Ident, - convert_direction: ConvertDirection, - implementations: &[KeyValuePair], - component: &Type, - white_point: &Type, - rgb_space: &Type, - turbofish_generics: &Turbofish, - internal: bool, -) -> Vec { - let mut xyz_convert = Some(XyzConvert::Luma); - let mut methods = vec![]; - - for color in implementations { - if color.key == "Xyz" { - xyz_convert = None; - } - - xyz_convert = xyz_convert.map(|current| { - current.get_best(match &*color.key.to_string() { - "Rgb" | "Hsl" | "Hsv" | "Hwb" => XyzConvert::Rgb, - "Lab" => XyzConvert::Lab, - "Lch" => XyzConvert::Lch, - "Yxy" => XyzConvert::Yxy, - "Luma" => XyzConvert::Luma, - color => panic!("unexpected color type: {}", color), - }) - }); - - let color_name = color.key.to_string(); - - let method_name = Ident::new( - &format!("{}_{}", convert_direction, color_name.to_lowercase()), - Span::call_site(), - ); - let color_path = util::color_path(&*color_name, internal); - let convert_function = color - .value - .clone() - .unwrap_or_else(|| Ident::new(convert_direction.as_ref(), Span::call_site())); - - let method = match &*color_name { - "Rgb" | "Hsl" | "Hsv" | "Hwb" => { - let rgb_space_path = util::path(&["rgb", "RgbSpace"], internal); - quote!(#method_name<_S: #rgb_space_path>) - } - _ => quote!(#method_name), - }; - - let color_ty = match &*color_name { - "Rgb" => { - let linear_path = util::path(&["encoding", "Linear"], internal); - - quote!(#color_path<#linear_path<_S>, #component>) - } - "Luma" => { - let linear_path = util::path(&["encoding", "Linear"], internal); - - quote!(#color_path<#linear_path<#white_point>, #component>) - } - "Hsl" | "Hsv" | "Hwb" => quote!(#color_path<_S, #component>), - _ => quote!(#color_path<#white_point, #component>), - }; - - methods.push(match convert_direction { - ConvertDirection::From => quote! { - fn #method (color: #color_ty) -> Self { - #ident #turbofish_generics::#convert_function(color) - } - }, - ConvertDirection::Into => quote! { - fn #method (self) -> #color_ty { - self.#convert_function() - } - }, - }); - } - - if let Some(xyz_convert) = xyz_convert { - let color_path = util::path(&["Xyz"], internal); - let method_name = Ident::new(&format!("{}_xyz", convert_direction), Span::call_site()); - let into_temporary_name = Ident::new(&format!("into_{}", xyz_convert), Span::call_site()); - let into_color_trait_path = util::path(&["IntoColor"], internal); - let convert_function = Ident::new( - &format!("{}_{}", convert_direction, xyz_convert), - Span::call_site(), - ); - - let method = match convert_direction { - ConvertDirection::From if xyz_convert == XyzConvert::Rgb => quote! { - fn #method_name(color: #color_path<#white_point, #component>) -> Self { - use #into_color_trait_path; - #ident #turbofish_generics::#convert_function(color.#into_temporary_name::<#rgb_space>()) - } - }, - ConvertDirection::From => quote! { - fn #method_name(color: #color_path<#white_point, #component>) -> Self { - use #into_color_trait_path; - #ident #turbofish_generics::#convert_function(color.#into_temporary_name()) - } - }, - ConvertDirection::Into if xyz_convert == XyzConvert::Rgb => quote! { - fn #method_name(self) -> #color_path<#white_point, #component> { - self.#convert_function::<#rgb_space>().into_xyz() - } - }, - ConvertDirection::Into => quote! { - fn #method_name(self) -> #color_path<#white_point, #component> { - self.#convert_function().into_xyz() - } - }, - }; - - methods.push(method); - } - - methods -} - -pub fn get_convert_color_type( - color: &str, - white_point: &Type, - component: &Type, - rgb_space: Option<&Type>, - generics: &mut Generics, - internal: bool, -) -> Type { - let color_path = util::color_path(color, internal); - - match color { - "Rgb" => { - let rgb_standard_path = util::path(&["rgb", "RgbStandard"], internal); - let rgb_space_path = util::path(&["rgb", "RgbSpace"], internal); - generics.params.push(GenericParam::Type( - Ident::new("_S", Span::call_site()).into(), - )); - - let where_clause = generics.make_where_clause(); - if let Some(ref rgb_space) = rgb_space { - where_clause - .predicates - .push(parse_quote!(_S: #rgb_standard_path)); - } else { - where_clause - .predicates - .push(parse_quote!(_S: #rgb_standard_path)); - where_clause - .predicates - .push(parse_quote!(_S::Space: #rgb_space_path)); - } - - parse_quote!(#color_path<_S, #component>) - } - "Luma" => { - let luma_standard_path = util::path(&["luma", "LumaStandard"], internal); - generics.params.push(GenericParam::Type( - Ident::new("_S", Span::call_site()).into(), - )); - - generics - .make_where_clause() - .predicates - .push(parse_quote!(_S: #luma_standard_path)); - parse_quote!(#color_path<_S, #component>) - } - "Hsl" | "Hsv" | "Hwb" => { - let rgb_space_path = util::path(&["rgb", "RgbSpace"], internal); - - if let Some(ref rgb_space) = rgb_space { - parse_quote!(#color_path<#rgb_space, #component>) - } else { - generics.params.push(GenericParam::Type( - Ident::new("_S", Span::call_site()).into(), - )); - - generics - .make_where_clause() - .predicates - .push(parse_quote!(_S: #rgb_space_path)); - - parse_quote!(#color_path<_S, #component>) - } - } - _ => parse_quote!(#color_path<#white_point, #component>), - } -} - -#[derive(Clone, Copy)] -pub enum ConvertDirection { - From, - Into, -} - -impl fmt::Display for ConvertDirection { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.as_ref().fmt(f) - } -} - -impl AsRef for ConvertDirection { - fn as_ref(&self) -> &str { - match *self { - ConvertDirection::From => "from", - ConvertDirection::Into => "into", - } - } -} - -#[derive(PartialEq, Clone, Copy)] -enum XyzConvert { - Luma, - Hwb, - Hsl, - Hsv, - Rgb, - Lab, - Lch, - Yxy, -} - -impl XyzConvert { - fn get_best(&self, other: XyzConvert) -> XyzConvert { - match (*self, other) { - (XyzConvert::Yxy, _) | (_, XyzConvert::Yxy) => XyzConvert::Yxy, - (XyzConvert::Lab, _) | (_, XyzConvert::Lab) => XyzConvert::Lab, - (XyzConvert::Lch, _) | (_, XyzConvert::Lch) => XyzConvert::Lch, - (XyzConvert::Rgb, _) | (_, XyzConvert::Rgb) => XyzConvert::Rgb, - (XyzConvert::Hsl, _) | (_, XyzConvert::Hsl) => XyzConvert::Hsl, - (XyzConvert::Hsv, _) | (_, XyzConvert::Hsv) => XyzConvert::Hsv, - (XyzConvert::Hwb, _) | (_, XyzConvert::Hwb) => XyzConvert::Hwb, - (XyzConvert::Luma, XyzConvert::Luma) => XyzConvert::Luma, - } - } -} - -impl AsRef for XyzConvert { - fn as_ref(&self) -> &str { - match *self { - XyzConvert::Luma => "luma", - XyzConvert::Hwb => "hwb", - XyzConvert::Hsl => "hsl", - XyzConvert::Hsv => "hsv", - XyzConvert::Rgb => "rgb", - XyzConvert::Lab => "lab", - XyzConvert::Lch => "lch", - XyzConvert::Yxy => "yxy", - } - } -} - -impl fmt::Display for XyzConvert { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.as_ref().fmt(f) - } -} diff --git a/palette_derive/src/convert/util.rs b/palette_derive/src/convert/util.rs new file mode 100644 index 000000000..c64be4839 --- /dev/null +++ b/palette_derive/src/convert/util.rs @@ -0,0 +1,225 @@ +use std::collections::{HashMap, HashSet}; + +use proc_macro2::Span; +use syn::spanned::Spanned; +use syn::{parse_quote, GenericParam, Generics, Ident, Path, Result, Type, TypePath}; + +use crate::util; +use crate::{COLOR_TYPES, PREFERRED_CONVERSION_SOURCE}; + +pub fn find_in_generics( + component: Option<&Type>, + white_point: Option<&Type>, + generics: &Generics, +) -> (bool, bool) { + let mut generic_component = false; + let mut generic_white_point = false; + + for param in &generics.params { + if let GenericParam::Type(ref param) = *param { + if let Some(&Type::Path(TypePath { + qself: None, + path: + Path { + segments: ref component, + leading_colon, + }, + })) = component + { + let first = component.first(); + let is_ident_path = leading_colon.is_none() + && component.len() == 1 + && first.unwrap().arguments.is_empty() + && first.unwrap().ident == param.ident; + + if is_ident_path { + generic_component = true; + } + } + + if let Some(&Type::Path(TypePath { + qself: None, + path: + Path { + segments: ref white_point, + leading_colon, + }, + })) = white_point + { + let first = white_point.first(); + let is_ident_path = leading_colon.is_none() + && white_point.len() == 1 + && first.unwrap().arguments.is_empty() + && first.unwrap().ident == param.ident; + + if is_ident_path { + generic_white_point = true; + } + } + } + } + + (generic_component, generic_white_point) +} + +pub fn white_point_type(white_point: Option, internal: bool) -> Type { + white_point.unwrap_or_else(|| util::path_type(&["white_point", "D65"], internal)) +} + +pub fn component_type(component: Option) -> Type { + component.unwrap_or_else(|| parse_quote!(f32)) +} + +pub fn add_float_component_where_clause(component: &Type, generics: &mut Generics, internal: bool) { + let component_trait_path = util::path(&["FloatComponent"], internal); + + generics + .make_where_clause() + .predicates + .push(parse_quote!(#component: #component_trait_path)); +} + +pub fn add_white_point_where_clause(white_point: &Type, generics: &mut Generics, internal: bool) { + let white_point_trait_path = util::path(&["white_point", "WhitePoint"], internal); + + generics + .make_where_clause() + .predicates + .push(parse_quote!(#white_point: #white_point_trait_path)); +} + +pub fn get_convert_color_type( + color: &str, + white_point: &Type, + component: &Type, + rgb_space: Option<&Type>, + generics: &mut Generics, + internal: bool, +) -> Type { + let color_path = util::color_path(color, internal); + + match color { + "Rgb" => { + let rgb_standard_path = util::path(&["rgb", "RgbStandard"], internal); + let rgb_space_path = util::path(&["rgb", "RgbSpace"], internal); + generics.params.push(GenericParam::Type( + Ident::new("_S", Span::call_site()).into(), + )); + + let where_clause = generics.make_where_clause(); + if let Some(ref rgb_space) = rgb_space { + where_clause + .predicates + .push(parse_quote!(_S: #rgb_standard_path)); + } else { + where_clause + .predicates + .push(parse_quote!(_S: #rgb_standard_path)); + where_clause + .predicates + .push(parse_quote!(_S::Space: #rgb_space_path)); + } + + parse_quote!(#color_path<_S, #component>) + } + "Luma" => { + let luma_standard_path = util::path(&["luma", "LumaStandard"], internal); + generics.params.push(GenericParam::Type( + Ident::new("_S", Span::call_site()).into(), + )); + + generics + .make_where_clause() + .predicates + .push(parse_quote!(_S: #luma_standard_path)); + parse_quote!(#color_path<_S, #component>) + } + "Hsl" | "Hsv" | "Hwb" => { + let rgb_space_path = util::path(&["rgb", "RgbSpace"], internal); + + if let Some(ref rgb_space) = rgb_space { + parse_quote!(#color_path<#rgb_space, #component>) + } else { + generics.params.push(GenericParam::Type( + Ident::new("_S", Span::call_site()).into(), + )); + + generics + .make_where_clause() + .predicates + .push(parse_quote!(_S: #rgb_space_path)); + + parse_quote!(#color_path<_S, #component>) + } + } + _ => parse_quote!(#color_path<#white_point, #component>), + } +} + +pub fn find_nearest_color<'a>(color: &'a str, skip: &HashSet) -> Result<&'a str> { + let mut stack = vec![(color, 0)]; + let mut found = None; + let mut visited = HashMap::new(); + + // Make sure there is at least one valid color in the skip list + assert!(!skip.is_empty()); + for skipped_color in skip { + if !COLOR_TYPES + .iter() + .any(|valid_color| skipped_color == valid_color) + { + return Err(::syn::parse::Error::new( + color.span(), + format!("`{}` is not a valid color type", skipped_color), + )); + } + } + + while let Some((color, distance)) = stack.pop() { + if skip.contains(color) { + if let Some((_, found_distance)) = found { + if distance < found_distance { + found = Some((color, distance)); + continue; + } + } else { + found = Some((color, distance)); + continue; + } + } + + if let Some(&previous_distance) = visited.get(color) { + if previous_distance <= distance { + continue; + } + } + + visited.insert(color, distance); + + // Start by pushing the plan B routes... + for &(destination, source) in PREFERRED_CONVERSION_SOURCE { + if color == source { + stack.push((destination, distance + 1)); + } + } + + // ...then push the preferred routes. They will be popped first. + for &(destination, source) in PREFERRED_CONVERSION_SOURCE { + if color == destination { + stack.push((source, distance + 1)); + } + } + } + + if let Some((color, _)) = found { + Ok(color) + } else { + Err(::syn::parse::Error::new( + color.span(), + format!( + "none of the skipped colors can be used for converting from {}", + color + ), + )) + } +} diff --git a/palette_derive/src/encoding/pixel.rs b/palette_derive/src/encoding/pixel.rs index a5e8e3312..2918b9a87 100644 --- a/palette_derive/src/encoding/pixel.rs +++ b/palette_derive/src/encoding/pixel.rs @@ -1,36 +1,51 @@ -use std::collections::{HashMap, HashSet}; - use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::Span; + use quote::{quote, ToTokens}; -use syn::{parse_macro_input, Data, DeriveInput, Fields, Ident, Type}; +use syn::{Attribute, Data, DeriveInput, Fields, Ident, Type}; -use crate::meta::{self, DataMetaParser, IdentOrIndex, MetaParser}; +use crate::meta::{self, FieldAttributes, IdentOrIndex, TypeItemAttributes}; use crate::util; -pub fn derive(tokens: TokenStream) -> TokenStream { +pub fn derive(tokens: TokenStream) -> std::result::Result> { let DeriveInput { ident, attrs, generics, data, .. - } = parse_macro_input!(tokens); + } = syn::parse(tokens).map_err(|error| vec![error])?; - let meta: PixelMeta = meta::parse_attributes(attrs); - let item_meta: PixelItemMeta = meta::parse_data_attributes(data.clone()); + let repr_c = is_repr_c(&attrs)?; + let item_meta: TypeItemAttributes = meta::parse_namespaced_attributes(attrs)?; let mut number_of_channels = 0usize; let mut field_type: Option = None; - let all_fields = match data { - Data::Struct(struct_item) => match struct_item.fields { - Fields::Named(fields) => fields.named, - Fields::Unnamed(fields) => fields.unnamed, - Fields::Unit => Default::default(), - }, - Data::Enum(_) => panic!("`Pixel` cannot be derived for enums, because of the discriminant"), - Data::Union(_) => panic!("`Pixel` cannot be derived for unions"), + let (all_fields, fields_meta) = match data { + Data::Struct(struct_item) => { + let fields_meta: FieldAttributes = + meta::parse_field_attributes(struct_item.fields.clone())?; + let all_fields = match struct_item.fields { + Fields::Named(fields) => fields.named, + Fields::Unnamed(fields) => fields.unnamed, + Fields::Unit => Default::default(), + }; + + (all_fields, fields_meta) + } + Data::Enum(_) => { + return Err(vec![syn::Error::new( + Span::call_site(), + "`Pixel` cannot be derived for enums, because of the discriminant", + )]); + } + Data::Union(_) => { + return Err(vec![syn::Error::new( + Span::call_site(), + "`Pixel` cannot be derived for unions", + )]); + } }; let fields = all_fields @@ -45,10 +60,12 @@ pub fn derive(tokens: TokenStream) -> TokenStream { field.ty, ) }) - .filter(|&(ref field, _)| !item_meta.zero_size_fields.contains(field)); + .filter(|&(ref field, _)| !fields_meta.zero_size_fields.contains(field)); + + let mut errors = Vec::new(); for (field, ty) in fields { - let ty = item_meta + let ty = fields_meta .type_substitutes .get(&field) .cloned() @@ -57,28 +74,32 @@ pub fn derive(tokens: TokenStream) -> TokenStream { if let Some(field_type) = field_type.clone() { if field_type != ty { - panic!( - "expected fields to be of type `{}`, but `{}` is of type `{}`", - field_type.into_token_stream(), - field.into_token_stream(), - ty.into_token_stream() - ); + errors.push(syn::Error::new_spanned( + &field, + format!( + "expected fields to have type `{}`", + field_type.into_token_stream() + ), + )); } } else { field_type = Some(ty); } } - if !meta.repr_c { - panic!( - "a `#[repr(C)]` attribute is required to give `{}` a fixed memory layout", - ident - ); + if !repr_c { + errors.push(syn::Error::new( + Span::call_site(), + format!( + "a `#[repr(C)]` attribute is required to give `{}` a fixed memory layout", + ident + ), + )); } - let pixel_trait_path = util::path(&["Pixel"], meta.internal); + let pixel_trait_path = util::path(&["Pixel"], item_meta.internal); - let implementation = if let Some(field_type) = field_type { + let mut implementation = if let Some(field_type) = field_type { let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); quote! { @@ -88,63 +109,47 @@ pub fn derive(tokens: TokenStream) -> TokenStream { } } } else { - panic!("`Pixel` can only be derived for structs with one or more fields"); + errors.push(syn::Error::new( + Span::call_site(), + format!("`Pixel` can only be derived for structs with one or more fields"), + )); + + return Err(errors); }; - let result = util::bundle_impl("Pixel", ident, meta.internal, implementation); - result.into() + implementation.extend(errors.iter().map(syn::Error::to_compile_error)); + Ok(implementation.into()) } -#[derive(Default)] -struct PixelMeta { - internal: bool, - repr_c: bool, -} +fn is_repr_c(attributes: &[Attribute]) -> std::result::Result> { + let mut errors = Vec::new(); -impl MetaParser for PixelMeta { - fn internal(&mut self) { - self.internal = true; - } + for attribute in attributes { + let attribute_name = attribute.path.get_ident().map(ToString::to_string); + + match attribute_name.as_deref() { + Some("repr") => { + let items = match meta::parse_tuple_attribute(attribute.tokens.clone()) { + Ok(items) => items, + Err(error) => { + errors.push(error); + continue; + } + }; - fn parse_attribute(&mut self, attribute_name: Ident, attribute_tts: TokenStream2) { - match &*attribute_name.to_string() { - "repr" => { - let items = meta::parse_tuple_attribute(&attribute_name, attribute_tts); let contains_c = items.into_iter().find(|item: &Ident| item == "C").is_some(); if contains_c { - self.repr_c = true; + return Ok(true); } } _ => {} } } -} -#[derive(Default)] -struct PixelItemMeta { - zero_size_fields: HashSet, - type_substitutes: HashMap, -} - -impl DataMetaParser for PixelItemMeta { - fn parse_struct_field_attribute( - &mut self, - field_name: IdentOrIndex, - _ty: Type, - attribute_name: Ident, - attribute_tts: TokenStream2, - ) { - match &*attribute_name.to_string() { - "palette_unsafe_same_layout_as" => { - let substitute = meta::parse_equal_attribute(&attribute_name, attribute_tts); - self.type_substitutes.insert(field_name, substitute); - } - "palette_unsafe_zero_sized" => { - meta::assert_empty_attribute(&attribute_name, attribute_tts); - self.zero_size_fields.insert(field_name); - } - _ => {} - } + if errors.is_empty() { + Ok(false) + } else { + Err(errors) } } diff --git a/palette_derive/src/lib.rs b/palette_derive/src/lib.rs index 81e358301..ac37edaaf 100644 --- a/palette_derive/src/lib.rs +++ b/palette_derive/src/lib.rs @@ -1,9 +1,37 @@ //! Derives traits from the [palette](https://crates.io/crates/palette) crate. -#![recursion_limit = "128"] - use proc_macro::TokenStream; +macro_rules! syn_try { + ($e:expr) => { + match $e { + Ok(value) => value, + Err(errors) => { + trait IntoErrors { + fn into_errors(self) -> Vec<::syn::parse::Error>; + } + impl IntoErrors for Vec<::syn::parse::Error> { + fn into_errors(self) -> Vec<::syn::parse::Error> { + self + } + } + impl IntoErrors for ::syn::parse::Error { + fn into_errors(self) -> Vec<::syn::parse::Error> { + vec![self] + } + } + + let errors: ::proc_macro2::TokenStream = IntoErrors::into_errors(errors) + .iter() + .map(::syn::parse::Error::to_compile_error) + .collect(); + return ::proc_macro::TokenStream::from(errors); + } + } + }; +} + +mod alpha; mod convert; mod encoding; mod meta; @@ -13,44 +41,28 @@ const COLOR_TYPES: &[&str] = &[ "Rgb", "Luma", "Hsl", "Hsv", "Hwb", "Lab", "Lch", "Xyz", "Yxy", ]; -#[proc_macro_derive( - FromColor, - attributes( - palette_internal, - palette_white_point, - palette_component, - palette_manual_from, - palette_rgb_space, - palette_alpha - ) -)] -pub fn derive_from_color(tokens: TokenStream) -> TokenStream { - convert::derive_from_color(tokens) +const PREFERRED_CONVERSION_SOURCE: &[(&str, &str)] = &[ + ("Rgb", "Xyz"), + ("Luma", "Xyz"), + ("Hsl", "Rgb"), + ("Hsv", "Rgb"), + ("Hwb", "Hsv"), + ("Lab", "Xyz"), + ("Lch", "Lab"), + ("Yxy", "Xyz"), +]; + +#[proc_macro_derive(WithAlpha, attributes(palette))] +pub fn derive_with_alpha(tokens: TokenStream) -> TokenStream { + syn_try!(alpha::derive_with_alpha(tokens)) } -#[proc_macro_derive( - IntoColor, - attributes( - palette_internal, - palette_white_point, - palette_component, - palette_manual_into, - palette_rgb_space, - palette_alpha - ) -)] -pub fn derive_into_color(tokens: TokenStream) -> TokenStream { - convert::derive_into_color(tokens) +#[proc_macro_derive(FromColorUnclamped, attributes(palette))] +pub fn derive_from_color_unclamped(tokens: TokenStream) -> TokenStream { + syn_try!(convert::derive_from_color_unclamped(tokens)) } -#[proc_macro_derive( - Pixel, - attributes( - palette_internal, - palette_unsafe_same_layout_as, - palette_unsafe_zero_sized - ) -)] +#[proc_macro_derive(Pixel, attributes(palette))] pub fn derive_pixel(tokens: TokenStream) -> TokenStream { - encoding::derive_pixel(tokens) + syn_try!(encoding::derive_pixel(tokens)) } diff --git a/palette_derive/src/meta.rs b/palette_derive/src/meta.rs deleted file mode 100644 index 38d7d18eb..000000000 --- a/palette_derive/src/meta.rs +++ /dev/null @@ -1,227 +0,0 @@ -use proc_macro2::TokenStream; -use quote::ToTokens; -use syn::parse::{Parse, ParseStream, Parser, Result}; -use syn::punctuated::Punctuated; -use syn::{parenthesized, Attribute, Data, Field, Fields, Ident, Index, LitStr, Token, Type}; - -pub fn parse_attributes(attributes: Vec) -> T { - let mut result = T::default(); - - for attribute in attributes { - let attribute_name = attribute.path.segments.first().unwrap().ident.clone(); - let is_palette_attribute = attribute_name.to_string().starts_with("palette_"); - - if attribute.path.segments.len() > 1 { - if is_palette_attribute { - panic!( - "expected `{}`, but found `{}`", - attribute_name, - attribute.path.into_token_stream() - ); - } else { - continue; - } - } - - if attribute_name == "palette_internal" { - assert_empty_attribute(&attribute_name, attribute.tokens); - result.internal(); - } else { - result.parse_attribute(attribute_name, attribute.tokens); - } - } - - result -} - -pub fn parse_data_attributes(data: Data) -> T { - let mut result = T::default(); - - match data { - Data::Struct(struct_item) => { - let fields = match struct_item.fields { - Fields::Named(fields) => fields.named, - Fields::Unnamed(fields) => fields.unnamed, - Fields::Unit => Default::default(), - }; - - parse_struct_field_attributes(&mut result, fields) - } - Data::Enum(_) => {} - Data::Union(_) => {} - } - - result -} - -pub fn parse_struct_field_attributes( - parser: &mut T, - fields: Punctuated, -) { - for (index, field) in fields.into_iter().enumerate() { - let identifier = field - .ident - .map(IdentOrIndex::Ident) - .unwrap_or_else(|| IdentOrIndex::Index(index.into())); - - for attribute in field.attrs { - let attribute_name = attribute.path.segments.first().unwrap().ident.clone(); - if !attribute_name.to_string().starts_with("palette_") { - continue; - } - - if attribute.path.segments.len() > 1 { - panic!( - "expected `{}`, but found `{}`", - attribute_name, - attribute.path.into_token_stream() - ); - } - - parser.parse_struct_field_attribute( - identifier.clone(), - field.ty.clone(), - attribute_name, - attribute.tokens, - ); - } - } -} - -pub fn assert_empty_attribute(attribute_name: &Ident, tts: TokenStream) { - if !tts.is_empty() { - panic!( - "expected the attribute to be on the form `#[{name}]`, but found `#[{name}{tts}]`", - name = attribute_name, - tts = tts - ); - } -} - -pub fn parse_tuple_attribute(attribute_name: &Ident, tts: TokenStream) -> Vec { - fn parse_generic_tuple(input: ParseStream) -> Result> { - let content; - parenthesized!(content in input); - - let mut tuple = Vec::new(); - loop { - tuple.push(content.parse()?); - if content.is_empty() { - break; - } - content.parse::()?; - if content.is_empty() { - break; - } - } - Ok(tuple) - } - - match parse_generic_tuple.parse2(tts.clone()) { - Ok(elements) => elements, - Err(_) => panic!( - "expected the attribute to be on the form `#[{name}(A, B, ...)]`, but found #[{name}{tts}]", - name = attribute_name, - tts = tts - ), - } -} - -pub fn parse_equal_attribute(attribute_name: &Ident, tts: TokenStream) -> T { - fn parse_paren(input: ParseStream) -> Result { - input.parse::()?; - if input.peek(LitStr) { - input.parse::()?.parse() - } else { - input.parse() - } - } - - match parse_paren::.parse2(tts.clone()) { - Ok(assign) => assign, - Err(_) => panic!( - "expected the attribute to be on the form `#[{name} = A]` or `#[{name} = \"A\"]`, but found #[{name}{tts}]", - name = attribute_name, - tts = tts - ), - } -} - -#[derive(PartialEq)] -pub struct KeyValuePair { - pub key: Ident, - pub value: Option, -} - -impl Parse for KeyValuePair { - fn parse(input: ParseStream) -> Result { - let key: Ident = input.parse()?; - let option_eq: Option = input.parse()?; - let value = match option_eq { - None => None, - Some(_) => Some(input.parse::()?.parse::()?), - }; - Ok(KeyValuePair { key, value }) - } -} - -impl PartialEq for KeyValuePair { - fn eq(&self, other: &str) -> bool { - self.key == other - } -} - -#[derive(Clone)] -pub enum IdentOrIndex { - Index(Index), - Ident(Ident), -} - -impl PartialEq for IdentOrIndex { - fn eq(&self, other: &IdentOrIndex) -> bool { - match (self, other) { - (&IdentOrIndex::Index(ref this), &IdentOrIndex::Index(ref other)) => { - this.index == other.index - } - (&IdentOrIndex::Ident(ref this), &IdentOrIndex::Ident(ref other)) => this == other, - _ => false, - } - } -} - -impl Eq for IdentOrIndex {} - -impl ::std::hash::Hash for IdentOrIndex { - fn hash(&self, hasher: &mut H) { - ::std::mem::discriminant(self).hash(hasher); - - match *self { - IdentOrIndex::Index(ref index) => index.index.hash(hasher), - IdentOrIndex::Ident(ref ident) => ident.hash(hasher), - } - } -} - -impl ::quote::ToTokens for IdentOrIndex { - fn to_tokens(&self, tokens: &mut TokenStream) { - match *self { - IdentOrIndex::Index(ref index) => index.to_tokens(tokens), - IdentOrIndex::Ident(ref ident) => ident.to_tokens(tokens), - } - } -} - -pub trait MetaParser: Default { - fn internal(&mut self); - fn parse_attribute(&mut self, attribute_name: Ident, attribute_tts: TokenStream); -} - -pub trait DataMetaParser: Default { - fn parse_struct_field_attribute( - &mut self, - field_name: IdentOrIndex, - ty: Type, - attribute_name: Ident, - attribute_tts: TokenStream, - ); -} diff --git a/palette_derive/src/meta/field_attributes.rs b/palette_derive/src/meta/field_attributes.rs new file mode 100644 index 000000000..f8bd5d977 --- /dev/null +++ b/palette_derive/src/meta/field_attributes.rs @@ -0,0 +1,54 @@ +use std::collections::{HashMap, HashSet}; + +use syn::spanned::Spanned; +use syn::{Lit, Meta, MetaNameValue, Result, Type}; + +use super::{assert_path_meta, FieldAttributeArgumentParser, IdentOrIndex}; + +#[derive(Default)] +pub struct FieldAttributes { + pub alpha_property: Option<(IdentOrIndex, Type)>, + pub zero_size_fields: HashSet, + pub type_substitutes: HashMap, +} + +impl FieldAttributeArgumentParser for FieldAttributes { + fn argument(&mut self, field_name: &IdentOrIndex, ty: &Type, argument: Meta) -> Result<()> { + let argument_name = argument.path().get_ident().map(ToString::to_string); + + match argument_name.as_deref() { + Some("alpha") => { + assert_path_meta(&argument)?; + self.alpha_property = Some((field_name.clone(), ty.clone())); + } + Some("unsafe_same_layout_as") => { + let substitute = if let Meta::NameValue(MetaNameValue { + lit: Lit::Str(string), + .. + }) = argument + { + string.parse()? + } else { + return Err(::syn::parse::Error::new( + argument.span(), + "expected `unsafe_same_layout_as = \"SomeType\"`", + )); + }; + + self.type_substitutes.insert(field_name.clone(), substitute); + } + Some("unsafe_zero_sized") => { + assert_path_meta(&argument)?; + self.zero_size_fields.insert(field_name.clone()); + } + _ => { + return Err(::syn::parse::Error::new( + argument.span(), + "unknown field attribute", + )); + } + } + + Ok(()) + } +} diff --git a/palette_derive/src/meta/mod.rs b/palette_derive/src/meta/mod.rs new file mode 100644 index 000000000..bb34ca31c --- /dev/null +++ b/palette_derive/src/meta/mod.rs @@ -0,0 +1,267 @@ +use proc_macro2::TokenStream; +use syn::parse::{Parse, ParseBuffer, ParseStream, Parser, Result}; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::{ + parenthesized, Attribute, Fields, Ident, Index, Lit, LitStr, Meta, NestedMeta, Token, Type, +}; + +pub use self::field_attributes::*; +pub use self::type_item_attributes::*; + +mod field_attributes; +mod type_item_attributes; + +pub fn parse_namespaced_attributes( + attributes: Vec, +) -> ::std::result::Result> { + let mut result = T::default(); + let mut errors = Vec::new(); + + for attribute in attributes { + let is_palette_attribute = attribute + .path + .get_ident() + .map(|name| name == "palette") + .unwrap_or(false); + + if !is_palette_attribute { + continue; + } + + if attribute.tokens.is_empty() { + errors.push(::syn::parse::Error::new( + attribute.path.span(), + "expected `palette(...)`", + )); + + continue; + } + + let parse_result = parse_meta_list.parse2(attribute.tokens); + match parse_result { + Ok(meta) => { + for argument in meta { + let argument_result = match argument { + NestedMeta::Meta(argument) => result.argument(argument), + NestedMeta::Lit(literal) => result.literal(literal), + }; + + if let Err(error) = argument_result { + errors.push(error); + } + } + } + Err(error) => errors.push(error), + } + } + + if errors.is_empty() { + Ok(result) + } else { + Err(errors) + } +} + +pub fn parse_field_attributes( + fields: Fields, +) -> ::std::result::Result> { + let mut result = T::default(); + let mut errors = Vec::new(); + + let attributes = fields.into_iter().enumerate().flat_map(|(index, field)| { + let field_name = field + .ident + .map(IdentOrIndex::Ident) + .unwrap_or_else(|| IdentOrIndex::Index(index.into())); + let ty = field.ty; + + field + .attrs + .into_iter() + .map(move |attribute| (field_name.clone(), ty.clone(), attribute)) + }); + + for (field_name, ty, attribute) in attributes { + let is_palette_attribute = attribute + .path + .get_ident() + .map(|name| name == "palette") + .unwrap_or(false); + + if !is_palette_attribute { + continue; + } + + if attribute.tokens.is_empty() { + errors.push(::syn::parse::Error::new( + attribute.path.span(), + "expected `palette(...)`", + )); + + continue; + } + + let parse_result = parse_meta_list.parse2(attribute.tokens); + match parse_result { + Ok(meta) => { + for argument in meta { + let argument_result = match argument { + NestedMeta::Meta(argument) => result.argument(&field_name, &ty, argument), + NestedMeta::Lit(literal) => result.literal(&field_name, &ty, literal), + }; + + if let Err(error) = argument_result { + errors.push(error); + } + } + } + Err(error) => errors.push(error), + } + } + + if errors.is_empty() { + Ok(result) + } else { + Err(errors) + } +} + +pub fn assert_path_meta(meta: &Meta) -> Result<()> { + if !matches!(meta, Meta::Path(_)) { + return Err(::syn::parse::Error::new( + meta.span(), + "expected the attribute to be just an identifier or a path", + )); + } + + Ok(()) +} + +pub fn parse_tuple_attribute(tts: TokenStream) -> Result> { + fn parse_generic_tuple(input: ParseStream) -> Result> { + let content; + parenthesized!(content in input); + + let mut tuple = Vec::new(); + loop { + tuple.push(content.parse()?); + if content.is_empty() { + break; + } + content.parse::()?; + if content.is_empty() { + break; + } + } + Ok(tuple) + } + + parse_generic_tuple.parse2(tts.clone()) +} + +fn parse_meta_list(buffer: &ParseBuffer) -> syn::Result> { + let inner; + parenthesized!(inner in buffer); + syn::punctuated::Punctuated::parse_terminated(&inner) +} + +#[derive(PartialEq)] +pub struct KeyValuePair { + pub key: Ident, + pub value: Ident, +} + +impl Parse for KeyValuePair { + fn parse(input: ParseStream) -> Result { + let key: Ident = input.parse()?; + input.parse::()?; + let value = input.parse::()?.parse::()?; + Ok(KeyValuePair { key, value }) + } +} + +impl PartialEq for KeyValuePair { + fn eq(&self, other: &str) -> bool { + self.key == other + } +} + +#[derive(Clone)] +pub enum IdentOrIndex { + Index(Index), + Ident(Ident), +} + +impl PartialEq for IdentOrIndex { + fn eq(&self, other: &IdentOrIndex) -> bool { + match (self, other) { + (&IdentOrIndex::Index(ref this), &IdentOrIndex::Index(ref other)) => { + this.index == other.index + } + (&IdentOrIndex::Ident(ref this), &IdentOrIndex::Ident(ref other)) => this == other, + _ => false, + } + } +} + +impl Eq for IdentOrIndex {} + +impl ::std::hash::Hash for IdentOrIndex { + fn hash(&self, hasher: &mut H) { + ::std::mem::discriminant(self).hash(hasher); + + match *self { + IdentOrIndex::Index(ref index) => index.index.hash(hasher), + IdentOrIndex::Ident(ref ident) => ident.hash(hasher), + } + } +} + +impl ::quote::ToTokens for IdentOrIndex { + fn to_tokens(&self, tokens: &mut TokenStream) { + match *self { + IdentOrIndex::Index(ref index) => index.to_tokens(tokens), + IdentOrIndex::Ident(ref ident) => ident.to_tokens(tokens), + } + } +} + +pub trait MetaParser: Default { + fn internal(&mut self); + fn parse_attribute(&mut self, attribute_name: Ident, attribute_tts: TokenStream) -> Result<()>; +} + +pub trait DataMetaParser: Default { + fn parse_struct_field_attribute( + &mut self, + field_name: IdentOrIndex, + ty: Type, + attribute_name: Ident, + attribute_tts: TokenStream, + ) -> Result<()>; +} + +pub trait AttributeArgumentParser: Default { + fn argument(&mut self, argument: Meta) -> Result<()>; + + fn literal(&mut self, literal: Lit) -> Result<()> { + Err(::syn::parse::Error::new( + literal.span(), + "unexpected literal", + )) + } +} + +pub trait FieldAttributeArgumentParser: Default { + fn argument(&mut self, field_name: &IdentOrIndex, ty: &Type, argument: Meta) -> Result<()>; + + fn literal(&mut self, field_name: &IdentOrIndex, ty: &Type, literal: Lit) -> Result<()> { + let (_, _) = (field_name, ty); + + Err(::syn::parse::Error::new( + literal.span(), + "unexpected literal", + )) + } +} diff --git a/palette_derive/src/meta/type_item_attributes.rs b/palette_derive/src/meta/type_item_attributes.rs new file mode 100644 index 000000000..5adcec55d --- /dev/null +++ b/palette_derive/src/meta/type_item_attributes.rs @@ -0,0 +1,154 @@ +use std::collections::HashSet; + +use syn::spanned::Spanned; +use syn::{Ident, Lit, Meta, MetaNameValue, NestedMeta, Result, Type}; + +use super::AttributeArgumentParser; + +#[derive(Default)] +pub struct TypeItemAttributes { + pub skip_derives: HashSet, + pub internal: bool, + pub internal_not_base_type: bool, + pub component: Option, + pub white_point: Option, + pub rgb_space: Option, +} + +impl AttributeArgumentParser for TypeItemAttributes { + fn argument(&mut self, argument: Meta) -> Result<()> { + let argument_name = argument.path().get_ident().map(ToString::to_string); + + match argument_name.as_deref() { + Some("skip_derives") => { + let result = if let Meta::List(list) = argument { + let skipped: ::std::result::Result<_, _> = list + .nested + .into_iter() + .map(|value| match value { + NestedMeta::Meta(Meta::Path(path)) => { + if let Some(name) = path.get_ident() { + Ok(name.clone()) + } else { + Err(path.span()) + } + } + value => Err(value.span()), + }) + .collect(); + + skipped.map(|values: Vec| { + self.skip_derives + .extend(values.into_iter().map(|ident| ident.to_string())); + }) + } else { + Err(argument.span()) + }; + + if let Err(span) = result { + return Err(::syn::parse::Error::new( + span, + "expected `skip` to have a list of color type names, like `skip(Xyz, Luma, Rgb)`", + )); + } + } + Some("component") => { + if self.component.is_none() { + let result = if let Meta::NameValue(MetaNameValue { + lit: Lit::Str(ty), .. + }) = argument + { + self.component = Some(ty.parse()?); + Ok(()) + } else { + Err(argument.span()) + }; + + if let Err(span) = result { + let message = "expected `component` to be a type or type parameter in a string, like `component = \"T\"`"; + return Err(::syn::parse::Error::new(span, message)); + } + } else { + return Err(::syn::parse::Error::new( + argument.span(), + "`component` appears more than once", + )); + } + } + Some("white_point") => { + if self.white_point.is_none() { + let result = if let Meta::NameValue(MetaNameValue { + lit: Lit::Str(ty), .. + }) = argument + { + self.white_point = Some(ty.parse()?); + Ok(()) + } else { + Err(argument.span()) + }; + + if let Err(span) = result { + let message = "expected `white_point` to be a type or type parameter in a string, like `white_point = \"T\"`"; + return Err(::syn::parse::Error::new(span, message)); + } + } else { + return Err(::syn::parse::Error::new( + argument.span(), + "`white_point` appears more than once", + )); + } + } + Some("rgb_space") => { + if self.rgb_space.is_none() { + let result = if let Meta::NameValue(MetaNameValue { + lit: Lit::Str(ty), .. + }) = argument + { + self.rgb_space = Some(ty.parse()?); + Ok(()) + } else { + Err(argument.span()) + }; + + if let Err(span) = result { + let message = "expected `rgb_space` to be a type or type parameter in a string, like `rgb_space = \"T\"`"; + return Err(::syn::parse::Error::new(span, message)); + } + } else { + return Err(::syn::parse::Error::new( + argument.span(), + "`rgb_space` appears more than once", + )); + } + } + Some("palette_internal") => { + if let Meta::Path(_) = argument { + self.internal = true; + } else { + return Err(::syn::parse::Error::new( + argument.span(), + "expected `palette_internal` to a literal without value", + )); + } + } + Some("palette_internal_not_base_type") => { + if let Meta::Path(_) = argument { + self.internal_not_base_type = true; + } else { + return Err(::syn::parse::Error::new( + argument.span(), + "expected `palette_internal` to a literal without value", + )); + } + } + _ => { + return Err(::syn::parse::Error::new( + argument.span(), + "unknown type item attribute", + )); + } + } + + Ok(()) + } +} diff --git a/palette_derive/src/util.rs b/palette_derive/src/util.rs index 19c8d5b17..4ef228226 100644 --- a/palette_derive/src/util.rs +++ b/palette_derive/src/util.rs @@ -1,49 +1,18 @@ use proc_macro2::{Span, TokenStream}; -use syn::{parse_quote, Ident, Type}; use quote::quote; +use syn::{parse_quote, Ident, Type}; -pub fn bundle_impl( - trait_name: &str, - type_name: Ident, - internal: bool, - block: TokenStream, -) -> TokenStream { - let const_name = Ident::new( - &format!("_palette_derive_{}_for_{}", trait_name, type_name), - Span::call_site(), - ); - - if internal { - quote! { - #[allow(non_snake_case, unused_attributes, unused_qualifications, unused_imports)] - mod #const_name { - use crate::float::Float as _FloatTrait; - use super::*; - #block - } - } - } else { - quote! { - #[allow(non_snake_case, unused_attributes, unused_qualifications, unused_imports)] - mod #const_name { - extern crate palette as _palette; - use self::_palette::float::Float as _FloatTrait; - use super::*; - #block - } - } - } -} - -pub fn path(path: &[&str], internal: bool) -> TokenStream { +pub fn path<'a, P: AsRef<[&'a str]>>(path: P, internal: bool) -> TokenStream { let path = path + .as_ref() .into_iter() .map(|&ident| Ident::new(ident, Span::call_site())); if internal { quote! {crate::#(#path)::*} } else { - quote! {self::_palette::#(#path)::*} + let crate_name = find_crate_name(); + quote! {#crate_name::#(#path)::*} } } @@ -53,16 +22,30 @@ pub fn path_type(path: &[&str], internal: bool) -> Type { .map(|&ident| Ident::new(ident, Span::call_site())); if internal { - parse_quote! {::#(#path)::*} + parse_quote! {crate::#(#path)::*} } else { - parse_quote! {self::_palette::#(#path)::*} + let crate_name = find_crate_name(); + parse_quote! {#crate_name::#(#path)::*} } } pub fn color_path(color: &str, internal: bool) -> TokenStream { match color { - "Luma" => path(&["luma", "Luma"], internal), - "Rgb" => path(&["rgb", "Rgb"], internal), - _ => path(&[color], internal), + "Luma" => path(["luma", "Luma"], internal), + "Rgb" => path(["rgb", "Rgb"], internal), + _ => path([color], internal), + } +} + +fn find_crate_name() -> Ident { + use find_crate::Error; + + match find_crate::find_crate(|name| name == "palette") { + Ok(package) => Ident::new(&package.name, Span::call_site()), + Err(Error::NotFound) => Ident::new("palette", Span::call_site()), + Err(error) => panic!( + "error when trying to find the name of the `palette` crate: {}", + error + ), } }