color/
impl_bytemuck.rs

1// Copyright 2024 the Color Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4#![allow(unsafe_code, reason = "unsafe is required for bytemuck unsafe impls")]
5
6use crate::{
7    cache_key::CacheKey, AlphaColor, ColorSpace, ColorSpaceTag, HueDirection, OpaqueColor,
8    PremulColor, PremulRgba8, Rgba8,
9};
10
11// Safety: The struct is `repr(transparent)` and the data member is bytemuck::Pod.
12unsafe impl<CS: ColorSpace> bytemuck::Pod for AlphaColor<CS> {}
13
14// Safety: The struct is `repr(transparent)`.
15unsafe impl<CS: ColorSpace> bytemuck::TransparentWrapper<[f32; 4]> for AlphaColor<CS> {}
16
17// Safety: The struct is `repr(transparent)` and the data member is bytemuck::Zeroable.
18unsafe impl<CS: ColorSpace> bytemuck::Zeroable for AlphaColor<CS> {}
19
20// Safety: The struct is `repr(transparent)` and the data member is bytemuck::Pod.
21unsafe impl<CS: ColorSpace> bytemuck::Pod for OpaqueColor<CS> {}
22
23// Safety: The struct is `repr(transparent)`.
24unsafe impl<CS: ColorSpace> bytemuck::TransparentWrapper<[f32; 3]> for OpaqueColor<CS> {}
25
26// Safety: The struct is `repr(transparent)` and the data member is bytemuck::Zeroable.
27unsafe impl<CS: ColorSpace> bytemuck::Zeroable for OpaqueColor<CS> {}
28
29// Safety: The struct is `repr(transparent)` and the data member is bytemuck::Pod.
30unsafe impl<CS: ColorSpace> bytemuck::Pod for PremulColor<CS> {}
31
32// Safety: The struct is `repr(transparent)`.
33unsafe impl<CS: ColorSpace> bytemuck::TransparentWrapper<[f32; 4]> for PremulColor<CS> {}
34
35// Safety: The struct is `repr(transparent)` and the data member is bytemuck::Zeroable.
36unsafe impl<CS: ColorSpace> bytemuck::Zeroable for PremulColor<CS> {}
37
38// Safety: The struct is `repr(C)` and all members are bytemuck::Pod.
39unsafe impl bytemuck::Pod for PremulRgba8 {}
40
41// Safety: The struct is `repr(C)` and all members are bytemuck::Zeroable.
42unsafe impl bytemuck::Zeroable for PremulRgba8 {}
43
44// Safety: The struct is `repr(C)` and all members are bytemuck::Pod.
45unsafe impl bytemuck::Pod for Rgba8 {}
46
47// Safety: The struct is `repr(C)` and all members are bytemuck::Zeroable.
48unsafe impl bytemuck::Zeroable for Rgba8 {}
49
50// Safety: The enum is `repr(u8)` and has only fieldless variants.
51unsafe impl bytemuck::NoUninit for ColorSpaceTag {}
52
53// Safety: The enum is `repr(u8)` and `0` is a valid value.
54unsafe impl bytemuck::Zeroable for ColorSpaceTag {}
55
56// Safety: The enum is `repr(u8)`.
57unsafe impl bytemuck::checked::CheckedBitPattern for ColorSpaceTag {
58    type Bits = u8;
59
60    fn is_valid_bit_pattern(bits: &u8) -> bool {
61        use bytemuck::Contiguous;
62        // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
63        *bits <= Self::MAX_VALUE
64    }
65}
66
67// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
68// the min and max values.
69unsafe impl bytemuck::Contiguous for ColorSpaceTag {
70    type Int = u8;
71    const MIN_VALUE: u8 = Self::Srgb as u8;
72    const MAX_VALUE: u8 = Self::Aces2065_1 as u8;
73}
74
75// Safety: The enum is `repr(u8)` and has only fieldless variants.
76unsafe impl bytemuck::NoUninit for HueDirection {}
77
78// Safety: The enum is `repr(u8)` and `0` is a valid value.
79unsafe impl bytemuck::Zeroable for HueDirection {}
80
81// Safety: The enum is `repr(u8)`.
82unsafe impl bytemuck::checked::CheckedBitPattern for HueDirection {
83    type Bits = u8;
84
85    fn is_valid_bit_pattern(bits: &u8) -> bool {
86        use bytemuck::Contiguous;
87        // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
88        *bits <= Self::MAX_VALUE
89    }
90}
91
92// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
93// the min and max values.
94unsafe impl bytemuck::Contiguous for HueDirection {
95    type Int = u8;
96    const MIN_VALUE: u8 = Self::Shorter as u8;
97    const MAX_VALUE: u8 = Self::Decreasing as u8;
98}
99
100// Safety: The struct is `repr(transparent)`.
101unsafe impl<T> bytemuck::TransparentWrapper<T> for CacheKey<T> {}
102
103#[cfg(test)]
104mod tests {
105    use crate::{
106        cache_key::CacheKey, AlphaColor, ColorSpaceTag, HueDirection, OpaqueColor, PremulColor,
107        PremulRgba8, Rgba8, Srgb,
108    };
109    use bytemuck::{checked::try_from_bytes, Contiguous, TransparentWrapper, Zeroable};
110    use core::{marker::PhantomData, ptr};
111
112    fn assert_is_pod(_pod: impl bytemuck::Pod) {}
113
114    #[test]
115    fn alphacolor_is_pod() {
116        let AlphaColor {
117            components,
118            cs: PhantomData,
119        } = AlphaColor::<Srgb>::new([1., 2., 3., 0.]);
120        assert_is_pod(components);
121    }
122
123    #[test]
124    fn opaquecolor_is_pod() {
125        let OpaqueColor {
126            components,
127            cs: PhantomData,
128        } = OpaqueColor::<Srgb>::new([1., 2., 3.]);
129        assert_is_pod(components);
130    }
131
132    #[test]
133    fn premulcolor_is_pod() {
134        let PremulColor {
135            components,
136            cs: PhantomData,
137        } = PremulColor::<Srgb>::new([1., 2., 3., 0.]);
138        assert_is_pod(components);
139    }
140
141    #[test]
142    fn premulrgba8_is_pod() {
143        let rgba8 = PremulRgba8 {
144            r: 0,
145            b: 0,
146            g: 0,
147            a: 0,
148        };
149        let PremulRgba8 { r, g, b, a } = rgba8;
150        assert_is_pod(r);
151        assert_is_pod(g);
152        assert_is_pod(b);
153        assert_is_pod(a);
154    }
155
156    #[test]
157    fn rgba8_is_pod() {
158        let rgba8 = Rgba8 {
159            r: 0,
160            b: 0,
161            g: 0,
162            a: 0,
163        };
164        let Rgba8 { r, g, b, a } = rgba8;
165        assert_is_pod(r);
166        assert_is_pod(g);
167        assert_is_pod(b);
168        assert_is_pod(a);
169    }
170
171    #[test]
172    fn checked_bit_pattern() {
173        let valid = bytemuck::bytes_of(&2_u8);
174        let invalid = bytemuck::bytes_of(&200_u8);
175
176        assert_eq!(
177            Ok(&ColorSpaceTag::Lab),
178            try_from_bytes::<ColorSpaceTag>(valid)
179        );
180
181        assert!(try_from_bytes::<ColorSpaceTag>(invalid).is_err());
182
183        assert_eq!(
184            Ok(&HueDirection::Increasing),
185            try_from_bytes::<HueDirection>(valid)
186        );
187
188        assert!(try_from_bytes::<HueDirection>(invalid).is_err());
189    }
190
191    #[test]
192    fn contiguous() {
193        let cst1 = ColorSpaceTag::LinearSrgb;
194        let cst2 = ColorSpaceTag::from_integer(cst1.into_integer());
195        assert_eq!(Some(cst1), cst2);
196
197        assert_eq!(None, ColorSpaceTag::from_integer(255));
198
199        let hd1 = HueDirection::Decreasing;
200        let hd2 = HueDirection::from_integer(hd1.into_integer());
201        assert_eq!(Some(hd1), hd2);
202
203        assert_eq!(None, HueDirection::from_integer(255));
204    }
205
206    // If the inner type is wrong in the unsafe impl above,
207    // that will result in failures here due to assertions
208    // within bytemuck.
209    #[test]
210    fn transparent_wrapper() {
211        let ac = AlphaColor::<Srgb>::new([1., 2., 3., 0.]);
212        let ai: [f32; 4] = AlphaColor::<Srgb>::peel(ac);
213        assert_eq!(ai, [1., 2., 3., 0.]);
214
215        let oc = OpaqueColor::<Srgb>::new([1., 2., 3.]);
216        let oi: [f32; 3] = OpaqueColor::<Srgb>::peel(oc);
217        assert_eq!(oi, [1., 2., 3.]);
218
219        let pc = PremulColor::<Srgb>::new([1., 2., 3., 0.]);
220        let pi: [f32; 4] = PremulColor::<Srgb>::peel(pc);
221        assert_eq!(pi, [1., 2., 3., 0.]);
222
223        let ck = CacheKey::<f32>::new(1.);
224        let ci: f32 = CacheKey::<f32>::peel(ck);
225        assert_eq!(ci, 1.);
226    }
227
228    #[test]
229    fn zeroable() {
230        let ac = AlphaColor::<Srgb>::zeroed();
231        assert_eq!(ac.components, [0., 0., 0., 0.]);
232
233        let oc = OpaqueColor::<Srgb>::zeroed();
234        assert_eq!(oc.components, [0., 0., 0.]);
235
236        let pc = PremulColor::<Srgb>::zeroed();
237        assert_eq!(pc.components, [0., 0., 0., 0.]);
238
239        let rgba8 = Rgba8::zeroed();
240        assert_eq!(
241            rgba8,
242            Rgba8 {
243                r: 0,
244                g: 0,
245                b: 0,
246                a: 0
247            }
248        );
249
250        let cst = ColorSpaceTag::zeroed();
251        assert_eq!(cst, ColorSpaceTag::Srgb);
252
253        let hd = HueDirection::zeroed();
254        assert_eq!(hd, HueDirection::Shorter);
255    }
256
257    /// Tests that the [`Contiguous`] impl for [`HueDirection`] is not trivially incorrect.
258    const _: () = {
259        let mut value = 0;
260        while value <= HueDirection::MAX_VALUE {
261            // Safety: In a const context, therefore if this makes an invalid HueDirection, that will be detected.
262            let it: HueDirection = unsafe { ptr::read((&raw const value).cast()) };
263            // Evaluate the enum value to ensure it actually has a valid tag
264            if it as u8 != value {
265                unreachable!();
266            }
267            value += 1;
268        }
269    };
270
271    /// Tests that the [`Contiguous`] impl for [`ColorSpaceTag`] is not trivially incorrect.
272    const _: () = {
273        let mut value = 0;
274        while value <= ColorSpaceTag::MAX_VALUE {
275            // Safety: In a const context, therefore if this makes an invalid ColorSpaceTag, that will be detected.
276            let it: ColorSpaceTag = unsafe { ptr::read((&raw const value).cast()) };
277            // Evaluate the enum value to ensure it actually has a valid tag
278            if it as u8 != value {
279                unreachable!();
280            }
281            value += 1;
282        }
283    };
284}
285
286#[cfg(doctest)]
287/// Doctests aren't collected under `cfg(test)`; we can use `cfg(doctest)` instead
288mod doctests {
289    /// Validates that any new variants in `HueDirection` has led to a change in the `Contiguous` impl.
290    /// Note that to test this robustly, we'd need 256 tests, which is impractical.
291    /// We make the assumption that all new variants will maintain contiguousness.
292    ///
293    /// ```compile_fail,E0080
294    /// use bytemuck::Contiguous;
295    /// use color::HueDirection;
296    /// const {
297    ///     let value = HueDirection::MAX_VALUE + 1;
298    ///     // Safety: In a const context, therefore if this makes an invalid HueDirection, that will be detected.
299    ///     // (Indeed, we rely upon that)
300    ///     let it: HueDirection = unsafe { core::ptr::read((&raw const value).cast()) };
301    ///     // Evaluate the enum value to ensure it actually has an invalid tag
302    ///     if it as u8 != value {
303    ///         unreachable!();
304    ///     }
305    /// }
306    /// ```
307    const _HUE_DIRECTION: () = {};
308
309    /// Validates that any new variants in `ColorSpaceTag` has led to a change in the `Contiguous` impl.
310    /// Note that to test this robustly, we'd need 256 tests, which is impractical.
311    /// We make the assumption that all new variants will maintain contiguousness.
312    ///
313    /// ```compile_fail,E0080
314    /// use bytemuck::Contiguous;
315    /// use color::ColorSpaceTag;
316    /// const {
317    ///     let value = ColorSpaceTag::MAX_VALUE + 1;
318    ///     let it: ColorSpaceTag = unsafe { core::ptr::read((&raw const value).cast()) };
319    ///     // Evaluate the enum value to ensure it actually has an invalid tag
320    ///     if it as u8 != value {
321    ///         unreachable!();
322    ///     }
323    /// }
324    /// ```
325    const _COLOR_SPACE_TAG: () = {};
326}