diff --git a/palette/src/convert.rs b/palette/src/convert.rs index 74d9ba007..644257eb8 100644 --- a/palette/src/convert.rs +++ b/palette/src/convert.rs @@ -113,7 +113,7 @@ use encoding::Linear; /// use num_traits::Float; /// /// /// sRGB, but with a reversed memory layout. -/// #[derive(PartialEq, Debug, FromColor)] +/// #[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. @@ -123,11 +123,6 @@ use encoding::Linear; /// red: T, /// } /// -/// // Careful with this one! It requires `#[repr(C)]`. -/// unsafe impl Pixel for Bgr { -/// const CHANNELS: usize = 3; -/// } -/// /// // 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 @@ -368,7 +363,7 @@ where /// use num_traits::Float; /// /// /// sRGB, but with a reversed memory layout. -/// #[derive(Copy, Clone, IntoColor)] +/// #[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. @@ -378,11 +373,6 @@ where /// red: T, /// } /// -/// // Careful with this one! It requires `#[repr(C)]`. -/// unsafe impl Pixel for Bgr { -/// const CHANNELS: usize = 3; -/// } -/// /// // Rgb is a bit more complex than other colors, so we are /// // implementing a private conversion function and letting it /// // derive `Into` automatically. diff --git a/palette/src/encoding/pixel/mod.rs b/palette/src/encoding/pixel/mod.rs index bd9fd3a29..0166be30f 100644 --- a/palette/src/encoding/pixel/mod.rs +++ b/palette/src/encoding/pixel/mod.rs @@ -6,7 +6,106 @@ mod raw; /// Represents colors that can be serialized and deserialized from raw color components. /// /// This uses bit by bit conversion, so make sure that anything that implements it can be -/// represented as a contiguous sequence of a single type `T`. +/// represented as a contiguous sequence of a single type `T`. This is most safely done using +/// `#[derive(Pixel)]`. +/// +/// # Deriving +/// +/// `Pixel` can be automatically derived. The only requirements are that the type is a `struct`, +/// that it has a `#[repr(C)]` attribute, and that all of its fields have the same types. It stays +/// on the conservative side and will show an error if any of those requirements are not fulfilled. +/// If some fields have different types, but the same memory layout, or are zero-sized, they can be +/// marked with attributes to show that their types are safe to use. +/// +/// ## Field Attributes +/// +/// * `#[palette_unsafe_same_layout_as = "SomeType"]`: Mark the field as having the same memory +/// layout as `SomeType`. +/// +/// **Unsafety:** corrupt data and undefined behavior may occur if this is not true! +/// +/// * `#[palette_unsafe_zero_sized]`: Mark the field as being zero-sized, and thus not taking up +/// any memory space. This means that it can be ignored. +/// +/// **Unsafety:** corrupt data and undefined behavior may occur if this is not true! +/// +/// ## Examples +/// +/// Basic use: +/// +/// ```rust +/// #[macro_use] +/// extern crate palette_derive; +/// extern crate palette; +/// +/// use palette::Pixel; +/// +/// #[derive(PartialEq, Debug, Pixel)] +/// #[repr(C)] +/// struct MyCmyk { +/// cyan: f32, +/// magenta: f32, +/// yellow: f32, +/// key: f32, +/// } +/// +/// fn main() { +/// let buffer = [0.1, 0.2, 0.3, 0.4]; +/// let color = MyCmyk::from_raw(&buffer); +/// +/// assert_eq!( +/// color, +/// &MyCmyk { +/// cyan: 0.1, +/// magenta: 0.2, +/// yellow: 0.3, +/// key: 0.4, +/// } +/// ); +/// } +/// ``` +/// +/// Heterogenous field types: +/// +/// ```rust +/// #[macro_use] +/// extern crate palette_derive; +/// extern crate palette; +/// +/// use std::marker::PhantomData; +/// +/// use palette::{Pixel, RgbHue}; +/// use palette::rgb::RgbStandard; +/// use palette::encoding::Srgb; +/// +/// #[derive(PartialEq, Debug, Pixel)] +/// #[repr(C)] +/// struct MyCoolColor { +/// #[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"] +/// hue: RgbHue, +/// lumen: f32, +/// chroma: f32, +/// } +/// +/// fn main() { +/// let buffer = [172.0, 100.0, 0.3]; +/// let color = MyCoolColor::::from_raw(&buffer); +/// +/// assert_eq!( +/// color, +/// &MyCoolColor { +/// hue: 172.0.into(), +/// lumen: 100.0, +/// chroma: 0.3, +/// standard: PhantomData, +/// } +/// ); +/// } +/// ``` pub unsafe trait Pixel: Sized { /// The number of color channels. const CHANNELS: usize; @@ -58,7 +157,7 @@ pub unsafe trait Pixel: Sized { /// Cast a slice of raw color components to a slice of colors. /// /// ```rust - /// use palette::{Srgb, Pixel}; + /// use palette::{Pixel, Srgb}; /// /// let raw = &[255u8, 128, 64, 10, 20, 30]; /// let colors = Srgb::from_raw_slice(raw); @@ -77,7 +176,7 @@ pub unsafe trait Pixel: Sized { /// Cast a mutable slice of raw color components to a mutable slice of colors. /// /// ```rust - /// use palette::{Srgb, Pixel}; + /// use palette::{Pixel, Srgb}; /// /// let raw = &mut [255u8, 128, 64, 10, 20, 30]; /// { @@ -102,7 +201,7 @@ pub unsafe trait Pixel: Sized { /// Cast a slice of colors to a slice of raw color components. /// /// ```rust - /// use palette::{Srgb, Pixel}; + /// use palette::{Pixel, Srgb}; /// /// let colors = &[Srgb::new(255u8, 128, 64), Srgb::new(10, 20, 30)]; /// let raw = Srgb::into_raw_slice(colors); @@ -119,7 +218,7 @@ pub unsafe trait Pixel: Sized { /// Cast a mutable slice of colors to a mutable slice of raw color components. /// /// ```rust - /// use palette::{Srgb, Pixel}; + /// use palette::{Pixel, Srgb}; /// /// let colors = &mut [Srgb::new(255u8, 128, 64), Srgb::new(10, 20, 30)]; /// { @@ -131,7 +230,6 @@ pub unsafe trait Pixel: Sized { /// raw[3] = 200; /// } /// - /// /// assert_eq!(colors[0].blue, 100); /// assert_eq!(colors[1].red, 200); /// ``` diff --git a/palette/src/hsl.rs b/palette/src/hsl.rs index 4db65e513..7744ffb3e 100644 --- a/palette/src/hsl.rs +++ b/palette/src/hsl.rs @@ -25,7 +25,7 @@ pub type Hsla = Alpha, T>; ///more gray, or making it darker. /// ///See [HSV](struct.Hsv.html) for a very similar color space, with brightness instead of lightness. -#[derive(Debug, PartialEq, FromColor)] +#[derive(Debug, PartialEq, FromColor, Pixel)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[palette_internal] #[palette_rgb_space = "S"] @@ -40,6 +40,7 @@ where { ///The hue of the color, in degrees. Decides if it's red, blue, purple, ///etc. + #[palette_unsafe_same_layout_as = "T"] pub hue: RgbHue, ///The colorfulness of the color. 0.0 gives gray scale colors and 1.0 will @@ -53,6 +54,7 @@ where ///The white point and RGB primaries this color is adapted to. The default ///is the sRGB standard. #[cfg_attr(feature = "serde", serde(skip))] + #[palette_unsafe_zero_sized] pub space: PhantomData, } @@ -73,10 +75,6 @@ where } } -unsafe impl Pixel for Hsl { - const CHANNELS: usize = 3; -} - impl Hsl where T: Component + Float, @@ -586,13 +584,17 @@ mod test { fn serialize() { let serialized = ::serde_json::to_string(&Hsl::new(0.3, 0.8, 0.1)).unwrap(); - assert_eq!(serialized, r#"{"hue":0.3,"saturation":0.8,"lightness":0.1}"#); + assert_eq!( + serialized, + r#"{"hue":0.3,"saturation":0.8,"lightness":0.1}"# + ); } #[cfg(feature = "serde")] #[test] fn deserialize() { - let deserialized: Hsl = ::serde_json::from_str(r#"{"hue":0.3,"saturation":0.8,"lightness":0.1}"#).unwrap(); + let deserialized: Hsl = + ::serde_json::from_str(r#"{"hue":0.3,"saturation":0.8,"lightness":0.1}"#).unwrap(); assert_eq!(deserialized, Hsl::new(0.3, 0.8, 0.1)); } diff --git a/palette/src/hsv.rs b/palette/src/hsv.rs index 3678a21b3..e81411358 100644 --- a/palette/src/hsv.rs +++ b/palette/src/hsv.rs @@ -24,7 +24,7 @@ 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)] +#[derive(Debug, PartialEq, FromColor, Pixel)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[palette_internal] #[palette_white_point = "S::WhitePoint"] @@ -39,6 +39,7 @@ where { ///The hue of the color, in degrees. Decides if it's red, blue, purple, ///etc. + #[palette_unsafe_same_layout_as = "T"] pub hue: RgbHue, ///The colorfulness of the color. 0.0 gives gray scale colors and 1.0 will @@ -53,6 +54,7 @@ where ///The white point and RGB primaries this color is adapted to. The default ///is the sRGB standard. #[cfg_attr(feature = "serde", serde(skip))] + #[palette_unsafe_zero_sized] pub space: PhantomData, } @@ -73,10 +75,6 @@ where } } -unsafe impl Pixel for Hsv { - const CHANNELS: usize = 3; -} - impl Hsv where T: Component + Float, diff --git a/palette/src/hwb.rs b/palette/src/hwb.rs index a8f14b06e..30f96ff7a 100644 --- a/palette/src/hwb.rs +++ b/palette/src/hwb.rs @@ -22,7 +22,7 @@ pub type Hwba = Alpha, T>; ///then a degree of whiteness and blackness to mix into that base hue. /// ///It is very intuitive for humans to use and many color-pickers are based on the HWB color system -#[derive(Debug, PartialEq, FromColor)] +#[derive(Debug, PartialEq, FromColor, Pixel)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[palette_internal] #[palette_rgb_space = "S"] @@ -37,6 +37,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"] pub hue: RgbHue, ///The whiteness of the color. It specifies the amount white to mix into the hue. @@ -54,6 +55,7 @@ where ///The white point and RGB primaries this color is adapted to. The default ///is the sRGB standard. #[cfg_attr(feature = "serde", serde(skip))] + #[palette_unsafe_zero_sized] pub space: PhantomData, } @@ -74,10 +76,6 @@ where } } -unsafe impl Pixel for Hwb { - const CHANNELS: usize = 3; -} - impl Hwb where T: Component + Float, diff --git a/palette/src/lab.rs b/palette/src/lab.rs index 3012c41e2..850377c0e 100644 --- a/palette/src/lab.rs +++ b/palette/src/lab.rs @@ -24,7 +24,7 @@ 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)] +#[derive(Debug, PartialEq, FromColor, Pixel)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[palette_internal] #[palette_white_point = "Wp"] @@ -49,6 +49,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 = "serde", serde(skip))] + #[palette_unsafe_zero_sized] pub white_point: PhantomData, } @@ -69,10 +70,6 @@ where } } -unsafe impl Pixel for Lab { - const CHANNELS: usize = 3; -} - impl Lab where T: Component + Float, diff --git a/palette/src/lch.rs b/palette/src/lch.rs index d54e623dc..f9366e50d 100644 --- a/palette/src/lch.rs +++ b/palette/src/lch.rs @@ -19,7 +19,7 @@ pub type Lcha = Alpha, T>; ///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)] +#[derive(Debug, PartialEq, FromColor, Pixel)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[palette_internal] #[palette_white_point = "Wp"] @@ -43,11 +43,13 @@ where ///The hue of the color, in degrees. Decides if it's red, blue, purple, ///etc. + #[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 = "serde", serde(skip))] + #[palette_unsafe_zero_sized] pub white_point: PhantomData, } @@ -68,10 +70,6 @@ where } } -unsafe impl Pixel for Lch { - const CHANNELS: usize = 3; -} - impl Lch where T: Component + Float, diff --git a/palette/src/luma/luma.rs b/palette/src/luma/luma.rs index e43ac50be..74274d4d3 100644 --- a/palette/src/luma/luma.rs +++ b/palette/src/luma/luma.rs @@ -26,7 +26,7 @@ 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)] +#[derive(Debug, PartialEq, FromColor, Pixel)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[palette_internal] #[palette_white_point = "S::WhitePoint"] @@ -43,6 +43,7 @@ where /// The kind of RGB standard. sRGB is the default. #[cfg_attr(feature = "serde", serde(skip))] + #[palette_unsafe_zero_sized] pub standard: PhantomData, } @@ -63,10 +64,6 @@ where } } -unsafe impl Pixel for Luma { - const CHANNELS: usize = 1; -} - impl Luma where T: Component, diff --git a/palette/src/rgb/rgb.rs b/palette/src/rgb/rgb.rs index 153dddb77..7a61cb12a 100644 --- a/palette/src/rgb/rgb.rs +++ b/palette/src/rgb/rgb.rs @@ -34,7 +34,7 @@ pub type Rgba = Alpha, T>; /// 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)] +#[derive(Debug, PartialEq, FromColor, Pixel)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[palette_internal] #[palette_rgb_space = "S::Space"] @@ -57,6 +57,7 @@ pub struct Rgb { /// The kind of RGB standard. sRGB is the default. #[cfg_attr(feature = "serde", serde(skip))] + #[palette_unsafe_zero_sized] pub standard: PhantomData, } @@ -95,10 +96,6 @@ impl Rgb { } } -unsafe impl Pixel for Rgb { - const CHANNELS: usize = 3; -} - impl Rgb { /// Convert the color to linear RGB. pub fn into_linear(self) -> Rgb, T> { diff --git a/palette/src/xyz.rs b/palette/src/xyz.rs index 442674928..1b2862264 100644 --- a/palette/src/xyz.rs +++ b/palette/src/xyz.rs @@ -24,7 +24,7 @@ pub type Xyza = Alpha, T>; ///illuminant and a standard observer to be defined. /// ///Conversions and operations on this color space depend on the defined white point -#[derive(Debug, PartialEq, FromColor)] +#[derive(Debug, PartialEq, FromColor, Pixel)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[palette_internal] #[palette_white_point = "Wp"] @@ -51,6 +51,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 = "serde", serde(skip))] + #[palette_unsafe_zero_sized] pub white_point: PhantomData, } @@ -71,10 +72,6 @@ where } } -unsafe impl Pixel for Xyz { - const CHANNELS: usize = 3; -} - impl Xyz where T: Component + Float, diff --git a/palette/src/yxy.rs b/palette/src/yxy.rs index 8e52d7d9b..3f21d9e50 100644 --- a/palette/src/yxy.rs +++ b/palette/src/yxy.rs @@ -21,7 +21,7 @@ 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)] +#[derive(Debug, PartialEq, FromColor, Pixel)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[palette_internal] #[palette_white_point = "Wp"] @@ -49,6 +49,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 = "serde", serde(skip))] + #[palette_unsafe_zero_sized] pub white_point: PhantomData, } @@ -69,10 +70,6 @@ where } } -unsafe impl Pixel for Yxy { - const CHANNELS: usize = 3; -} - impl Yxy where T: Component + Float, diff --git a/palette_derive/src/convert/from_color.rs b/palette_derive/src/convert/from_color.rs index 815a1fa30..4556a2171 100644 --- a/palette_derive/src/convert/from_color.rs +++ b/palette_derive/src/convert/from_color.rs @@ -617,10 +617,8 @@ impl MetaParser for FromColorMeta { fn parse_attribute(&mut self, attribute_name: Ident, attribute_tts: TokenStream2) { match attribute_name.as_ref() { "palette_manual_from" => { - let impls = meta::parse_type_tuple_attribute::( - &attribute_name, - attribute_tts, - ); + let impls = + meta::parse_tuple_attribute::(&attribute_name, attribute_tts); self.manual_implementations.extend(impls) } "palette_component" => { diff --git a/palette_derive/src/convert/into_color.rs b/palette_derive/src/convert/into_color.rs index fa2de8c7a..f6e6760b7 100644 --- a/palette_derive/src/convert/into_color.rs +++ b/palette_derive/src/convert/into_color.rs @@ -407,10 +407,8 @@ impl MetaParser for IntoColorMeta { fn parse_attribute(&mut self, attribute_name: Ident, attribute_tts: TokenStream2) { match attribute_name.as_ref() { "palette_manual_into" => { - let impls = meta::parse_type_tuple_attribute::( - &attribute_name, - attribute_tts, - ); + let impls = + meta::parse_tuple_attribute::(&attribute_name, attribute_tts); self.manual_implementations.extend(impls) } "palette_component" => { diff --git a/palette_derive/src/encoding/mod.rs b/palette_derive/src/encoding/mod.rs new file mode 100644 index 000000000..19146d0da --- /dev/null +++ b/palette_derive/src/encoding/mod.rs @@ -0,0 +1,3 @@ +pub use self::pixel::derive as derive_pixel; + +mod pixel; diff --git a/palette_derive/src/encoding/pixel.rs b/palette_derive/src/encoding/pixel.rs new file mode 100644 index 000000000..117a2d59d --- /dev/null +++ b/palette_derive/src/encoding/pixel.rs @@ -0,0 +1,156 @@ +use std::collections::{HashMap, HashSet}; + +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use syn::{self, Data, DeriveInput, Fields, Ident, Type}; +use quote::ToTokens; + +use meta::{self, DataMetaParser, IdentOrIndex, MetaParser}; +use util; + +pub fn derive(tokens: TokenStream) -> TokenStream { + let DeriveInput { + ident, + attrs, + generics, + data, + .. + } = syn::parse(tokens).expect("could not parse tokens"); + + let meta: PixelMeta = meta::parse_attributes(attrs); + let item_meta: PixelItemMeta = meta::parse_data_attributes(data.clone()); + + 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 fields = all_fields + .into_iter() + .enumerate() + .map(|(index, field)| { + ( + field + .ident + .map(IdentOrIndex::Ident) + .unwrap_or_else(|| IdentOrIndex::Index(index.into())), + field.ty, + ) + }) + .filter(|&(ref field, _)| !item_meta.zero_size_fields.contains(field)); + + for (field, ty) in fields { + let ty = item_meta + .type_substitutes + .get(&field) + .cloned() + .unwrap_or(ty); + number_of_channels += 1; + + if let Some(field_type) = field_type.clone() { + let ty = ty.into_tokens(); + let field_type = field_type.into_tokens(); + + if field_type != ty { + panic!( + "expected fields to be of type `{}`, but `{}` is of type `{}`", + field_type, + field.into_tokens(), + ty + ); + } + } else { + field_type = Some(ty); + } + } + + if !meta.repr_c { + panic!( + "a `#[repr(C)]` attribute is required to give `{}` a fixed memory layout", + ident + ); + } + + let pixel_trait_path = util::path(&["Pixel"], meta.internal); + + let implementation = if let Some(field_type) = field_type { + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + unsafe impl #impl_generics #pixel_trait_path<#field_type> for #ident #type_generics #where_clause { + const CHANNELS: usize = #number_of_channels; + } + } + } else { + panic!("`Pixel` can only be derived for structs with one or more fields"); + }; + + let result = util::bundle_impl("Pixel", ident, meta.internal, implementation); + result.into() +} + +#[derive(Default)] +struct PixelMeta { + internal: bool, + repr_c: bool, +} + +impl MetaParser for PixelMeta { + fn internal(&mut self) { + self.internal = true; + } + + fn parse_attribute(&mut self, attribute_name: Ident, attribute_tts: TokenStream2) { + match attribute_name.as_ref() { + "repr" => { + let items = meta::parse_tuple_attribute(&attribute_name, attribute_tts); + let contains_c = items + .into_iter() + .find(|item: &Ident| item.as_ref() == "C") + .is_some(); + + if contains_c { + self.repr_c = 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.as_ref() { + "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); + } + _ => {} + } + } +} diff --git a/palette_derive/src/lib.rs b/palette_derive/src/lib.rs index a8b1c464a..921e47d5c 100644 --- a/palette_derive/src/lib.rs +++ b/palette_derive/src/lib.rs @@ -15,6 +15,7 @@ use proc_macro::TokenStream; mod util; mod meta; mod convert; +mod encoding; const COLOR_TYPES: &[&str] = &[ "Rgb", "Luma", "Hsl", "Hsv", "Hwb", "Lab", "Lch", "Xyz", "Yxy" @@ -33,3 +34,10 @@ pub fn derive_from_color(tokens: TokenStream) -> TokenStream { pub fn derive_into_color(tokens: TokenStream) -> TokenStream { convert::derive_into_color(tokens) } + +#[proc_macro_derive(Pixel, + attributes(palette_internal, palette_unsafe_same_layout_as, + palette_unsafe_zero_sized))] +pub fn derive_pixel(tokens: TokenStream) -> TokenStream { + encoding::derive_pixel(tokens) +} diff --git a/palette_derive/src/meta.rs b/palette_derive/src/meta.rs index 0b0fd8ccb..284e962a3 100644 --- a/palette_derive/src/meta.rs +++ b/palette_derive/src/meta.rs @@ -10,16 +10,18 @@ pub fn parse_attributes(attributes: Vec) -> T { for attribute in attributes { let attribute_name = attribute.path.segments.first().unwrap().into_value().ident; - if !attribute_name.as_ref().starts_with("palette_") { - continue; - } + let is_palette_attribute = attribute_name.as_ref().starts_with("palette_"); if attribute.path.segments.len() > 1 { - panic!( - "expected `{}`, but found `{}`", - attribute_name, - attribute.path.into_tokens() - ); + if is_palette_attribute { + panic!( + "expected `{}`, but found `{}`", + attribute_name, + attribute.path.into_tokens() + ); + } else { + continue; + } } if attribute_name == "palette_internal" { @@ -97,7 +99,7 @@ pub fn assert_empty_attribute(attribute_name: &Ident, tts: TokenStream) { } } -pub fn parse_type_tuple_attribute( +pub fn parse_tuple_attribute( attribute_name: &Ident, tts: TokenStream, ) -> Punctuated { @@ -197,6 +199,31 @@ pub enum IdentOrIndex { 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 ::std::cmp::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 ::quote::Tokens) { match *self {