peniko/
impl_bytemuck.rs

1// Copyright 2024 the Peniko 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::{Compose, Extend, Fill, ImageFormat, ImageQuality, Mix};
7
8// Safety: The enum is `repr(u8)` and has only fieldless variants.
9unsafe impl bytemuck::NoUninit for Compose {}
10
11// Safety: The enum is `repr(u8)` and `0` is a valid value.
12unsafe impl bytemuck::Zeroable for Compose {}
13
14// Safety: The enum is `repr(u8)`.
15unsafe impl bytemuck::checked::CheckedBitPattern for Compose {
16    type Bits = u8;
17
18    fn is_valid_bit_pattern(bits: &u8) -> bool {
19        use bytemuck::Contiguous;
20        // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
21        *bits <= Self::MAX_VALUE
22    }
23}
24
25// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
26// the min and max values.
27unsafe impl bytemuck::Contiguous for Compose {
28    type Int = u8;
29    const MIN_VALUE: u8 = Self::Clear as u8;
30    const MAX_VALUE: u8 = Self::PlusLighter as u8;
31}
32
33// Safety: The enum is `repr(u8)` and has only fieldless variants.
34unsafe impl bytemuck::NoUninit for Extend {}
35
36// Safety: The enum is `repr(u8)` and `0` is a valid value.
37unsafe impl bytemuck::Zeroable for Extend {}
38
39// Safety: The enum is `repr(u8)`.
40unsafe impl bytemuck::checked::CheckedBitPattern for Extend {
41    type Bits = u8;
42
43    fn is_valid_bit_pattern(bits: &u8) -> bool {
44        use bytemuck::Contiguous;
45        // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
46        *bits <= Self::MAX_VALUE
47    }
48}
49
50// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
51// the min and max values.
52unsafe impl bytemuck::Contiguous for Extend {
53    type Int = u8;
54    const MIN_VALUE: u8 = Self::Pad as u8;
55    const MAX_VALUE: u8 = Self::Reflect as u8;
56}
57
58// Safety: The enum is `repr(u8)` and has only fieldless variants.
59unsafe impl bytemuck::NoUninit for Fill {}
60
61// Safety: The enum is `repr(u8)` and `0` is a valid value.
62unsafe impl bytemuck::Zeroable for Fill {}
63
64// Safety: The enum is `repr(u8)`.
65unsafe impl bytemuck::checked::CheckedBitPattern for Fill {
66    type Bits = u8;
67
68    fn is_valid_bit_pattern(bits: &u8) -> bool {
69        use bytemuck::Contiguous;
70        // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
71        *bits <= Self::MAX_VALUE
72    }
73}
74
75// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
76// the min and max values.
77unsafe impl bytemuck::Contiguous for Fill {
78    type Int = u8;
79    const MIN_VALUE: u8 = Self::NonZero as u8;
80    const MAX_VALUE: u8 = Self::EvenOdd as u8;
81}
82
83// Safety: The enum is `repr(u8)` and has only fieldless variants.
84unsafe impl bytemuck::NoUninit for ImageFormat {}
85
86// Safety: The enum is `repr(u8)` and `0` is a valid value.
87unsafe impl bytemuck::Zeroable for ImageFormat {}
88
89// Safety: The enum is `repr(u8)`.
90unsafe impl bytemuck::checked::CheckedBitPattern for ImageFormat {
91    type Bits = u8;
92
93    fn is_valid_bit_pattern(bits: &u8) -> bool {
94        #![expect(
95            clippy::absurd_extreme_comparisons,
96            reason = "There is only one value."
97        )]
98        use bytemuck::Contiguous;
99        // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
100        *bits <= Self::MAX_VALUE
101    }
102}
103
104// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
105// the min and max values.
106unsafe impl bytemuck::Contiguous for ImageFormat {
107    type Int = u8;
108    const MIN_VALUE: u8 = Self::Rgba8 as u8;
109    const MAX_VALUE: u8 = Self::Rgba8 as u8;
110}
111
112// Safety: The enum is `repr(u8)` and has only fieldless variants.
113unsafe impl bytemuck::NoUninit for ImageQuality {}
114
115// Safety: The enum is `repr(u8)` and `0` is a valid value.
116unsafe impl bytemuck::Zeroable for ImageQuality {}
117
118// Safety: The enum is `repr(u8)`.
119unsafe impl bytemuck::checked::CheckedBitPattern for ImageQuality {
120    type Bits = u8;
121
122    fn is_valid_bit_pattern(bits: &u8) -> bool {
123        use bytemuck::Contiguous;
124        // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
125        *bits <= Self::MAX_VALUE
126    }
127}
128
129// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
130// the min and max values.
131unsafe impl bytemuck::Contiguous for ImageQuality {
132    type Int = u8;
133    const MIN_VALUE: u8 = Self::Low as u8;
134    const MAX_VALUE: u8 = Self::High as u8;
135}
136
137// Safety: The enum is `repr(u8)` and has only fieldless variants.
138unsafe impl bytemuck::NoUninit for Mix {}
139
140// Safety: The enum is `repr(u8)` and `0` is a valid value.
141unsafe impl bytemuck::Zeroable for Mix {}
142
143// Safety: The enum is `repr(u8)`.
144unsafe impl bytemuck::checked::CheckedBitPattern for Mix {
145    type Bits = u8;
146
147    fn is_valid_bit_pattern(bits: &u8) -> bool {
148        *bits <= Self::Luminosity as u8 || *bits == Self::Clip as u8
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use crate::{Compose, Extend, Fill, ImageFormat, ImageQuality, Mix};
155    use bytemuck::{checked::try_from_bytes, Contiguous, Zeroable};
156    use core::ptr;
157
158    #[test]
159    fn checked_bit_pattern() {
160        let valid_zero = bytemuck::bytes_of(&0_u8);
161        let valid_one = bytemuck::bytes_of(&1_u8);
162        let invalid = bytemuck::bytes_of(&200_u8);
163
164        assert_eq!(Ok(&Compose::Copy), try_from_bytes::<Compose>(valid_one));
165        assert!(try_from_bytes::<Compose>(invalid).is_err());
166
167        assert_eq!(Ok(&Extend::Repeat), try_from_bytes::<Extend>(valid_one));
168        assert!(try_from_bytes::<Extend>(invalid).is_err());
169
170        assert_eq!(Ok(&Fill::EvenOdd), try_from_bytes::<Fill>(valid_one));
171        assert!(try_from_bytes::<Fill>(invalid).is_err());
172
173        assert_eq!(
174            Ok(&ImageFormat::Rgba8),
175            try_from_bytes::<ImageFormat>(valid_zero)
176        );
177        assert!(try_from_bytes::<ImageFormat>(invalid).is_err());
178
179        assert_eq!(
180            Ok(&ImageQuality::Medium),
181            try_from_bytes::<ImageQuality>(valid_one)
182        );
183        assert!(try_from_bytes::<ImageQuality>(invalid).is_err());
184
185        assert_eq!(Ok(&Mix::Multiply), try_from_bytes::<Mix>(valid_one));
186        assert!(try_from_bytes::<Mix>(invalid).is_err());
187    }
188
189    #[test]
190    fn contiguous() {
191        let compose1 = Compose::PlusLighter;
192        let compose2 = Compose::from_integer(compose1.into_integer());
193        assert_eq!(Some(compose1), compose2);
194
195        assert_eq!(None, Compose::from_integer(255));
196
197        let extend1 = Extend::Repeat;
198        let extend2 = Extend::from_integer(extend1.into_integer());
199        assert_eq!(Some(extend1), extend2);
200
201        assert_eq!(None, Extend::from_integer(255));
202
203        let fill1 = Fill::EvenOdd;
204        let fill2 = Fill::from_integer(fill1.into_integer());
205        assert_eq!(Some(fill1), fill2);
206
207        assert_eq!(None, Fill::from_integer(255));
208
209        let image_format_1 = ImageFormat::Rgba8;
210        let image_format_2 = ImageFormat::from_integer(image_format_1.into_integer());
211        assert_eq!(Some(image_format_1), image_format_2);
212
213        let image_quality_1 = ImageQuality::Low;
214        let image_quality_2 = ImageQuality::from_integer(image_quality_1.into_integer());
215        assert_eq!(Some(image_quality_1), image_quality_2);
216
217        assert_eq!(None, ImageQuality::from_integer(255));
218    }
219
220    #[test]
221    fn zeroable() {
222        let compose = Compose::zeroed();
223        assert_eq!(compose, Compose::Clear);
224
225        let extend = Extend::zeroed();
226        assert_eq!(extend, Extend::Pad);
227
228        let fill = Fill::zeroed();
229        assert_eq!(fill, Fill::NonZero);
230
231        let image_format = ImageFormat::zeroed();
232        assert_eq!(image_format, ImageFormat::Rgba8);
233
234        let image_quality = ImageQuality::zeroed();
235        assert_eq!(image_quality, ImageQuality::Low);
236
237        let mix = Mix::zeroed();
238        assert_eq!(mix, Mix::Normal);
239    }
240
241    /// Tests that the [`Contiguous`] impl for [`Compose`] is not trivially incorrect.
242    const _: () = {
243        let mut value = 0;
244        while value <= Compose::MAX_VALUE {
245            // Safety: In a const context, therefore if this makes an invalid Compose, that will be detected.
246            let it: Compose = unsafe { ptr::read((&raw const value).cast()) };
247            // Evaluate the enum value to ensure it actually has a valid tag
248            if it as u8 != value {
249                unreachable!();
250            }
251            value += 1;
252        }
253    };
254
255    /// Tests that the [`Contiguous`] impl for [`Extend`] is not trivially incorrect.
256    const _: () = {
257        let mut value = 0;
258        while value <= Extend::MAX_VALUE {
259            // Safety: In a const context, therefore if this makes an invalid Extend, that will be detected.
260            let it: Extend = unsafe { ptr::read((&raw const value).cast()) };
261            // Evaluate the enum value to ensure it actually has a valid tag
262            if it as u8 != value {
263                unreachable!();
264            }
265            value += 1;
266        }
267    };
268
269    /// Tests that the [`Contiguous`] impl for [`Fill`] is not trivially incorrect.
270    const _: () = {
271        let mut value = 0;
272        while value <= Fill::MAX_VALUE {
273            // Safety: In a const context, therefore if this makes an invalid Fill, that will be detected.
274            let it: Fill = unsafe { ptr::read((&raw const value).cast()) };
275            // Evaluate the enum value to ensure it actually has a valid tag
276            if it as u8 != value {
277                unreachable!();
278            }
279            value += 1;
280        }
281    };
282
283    /// Tests that the [`Contiguous`] impl for [`ImageFormat`] is not trivially incorrect.
284    const _: () = {
285        let mut value = 0;
286        #[expect(
287            clippy::absurd_extreme_comparisons,
288            reason = "There is only one value."
289        )]
290        while value <= ImageFormat::MAX_VALUE {
291            // Safety: In a const context, therefore if this makes an invalid ImageFormat, that will be detected.
292            let it: ImageFormat = unsafe { ptr::read((&raw const value).cast()) };
293            // Evaluate the enum value to ensure it actually has a valid tag
294            if it as u8 != value {
295                unreachable!();
296            }
297            value += 1;
298        }
299    };
300
301    /// Tests that the [`Contiguous`] impl for [`ImageQuality`] is not trivially incorrect.
302    const _: () = {
303        let mut value = 0;
304        while value <= ImageQuality::MAX_VALUE {
305            // Safety: In a const context, therefore if this makes an invalid ImageQuality, that will be detected.
306            let it: ImageQuality = unsafe { ptr::read((&raw const value).cast()) };
307            // Evaluate the enum value to ensure it actually has a valid tag
308            if it as u8 != value {
309                unreachable!();
310            }
311            value += 1;
312        }
313    };
314}
315
316#[cfg(doctest)]
317/// Doctests aren't collected under `cfg(test)`; we can use `cfg(doctest)` instead
318mod doctests {
319    /// Validates that any new variants in `Compose` has led to a change in the `Contiguous` impl.
320    /// Note that to test this robustly, we'd need 256 tests, which is impractical.
321    /// We make the assumption that all new variants will maintain contiguousness.
322    ///
323    /// ```compile_fail,E0080
324    /// use bytemuck::Contiguous;
325    /// use peniko::Compose;
326    /// const {
327    ///     let value = Compose::MAX_VALUE + 1;
328    ///     // Safety: In a const context, therefore if this makes an invalid Compose, that will be detected.
329    ///     // (Indeed, we rely upon that)
330    ///     let it: Compose = unsafe { core::ptr::read((&raw const value).cast()) };
331    ///     // Evaluate the enum value to ensure it actually has an invalid tag
332    ///     if it as u8 != value {
333    ///         unreachable!();
334    ///     }
335    /// }
336    /// ```
337    const _COMPOSE: () = {};
338
339    /// Validates that any new variants in `Extend` has led to a change in the `Contiguous` impl.
340    /// Note that to test this robustly, we'd need 256 tests, which is impractical.
341    /// We make the assumption that all new variants will maintain contiguousness.
342    ///
343    /// ```compile_fail,E0080
344    /// use bytemuck::Contiguous;
345    /// use peniko::Extend;
346    /// const {
347    ///     let value = Extend::MAX_VALUE + 1;
348    ///     let it: Extend = unsafe { core::ptr::read((&raw const value).cast()) };
349    ///     // Evaluate the enum value to ensure it actually has an invalid tag
350    ///     if it as u8 != value {
351    ///         unreachable!();
352    ///     }
353    /// }
354    /// ```
355    const _EXTEND: () = {};
356
357    /// Validates that any new variants in `Fill` has led to a change in the `Contiguous` impl.
358    /// Note that to test this robustly, we'd need 256 tests, which is impractical.
359    /// We make the assumption that all new variants will maintain contiguousness.
360    ///
361    /// ```compile_fail,E0080
362    /// use bytemuck::Contiguous;
363    /// use peniko::Fill;
364    /// const {
365    ///     let value = Fill::MAX_VALUE + 1;
366    ///     let it: Fill = unsafe { core::ptr::read((&raw const value).cast()) };
367    ///     // Evaluate the enum value to ensure it actually has an invalid tag
368    ///     if it as u8 != value {
369    ///         unreachable!();
370    ///     }
371    /// }
372    /// ```
373    const _FILL: () = {};
374
375    /// Validates that any new variants in `ImageFormat` has led to a change in the `Contiguous` impl.
376    /// Note that to test this robustly, we'd need 256 tests, which is impractical.
377    /// We make the assumption that all new variants will maintain contiguousness.
378    ///
379    /// ```compile_fail,E0080
380    /// use bytemuck::Contiguous;
381    /// use peniko::ImageFormat;
382    /// const {
383    ///     let value = ImageFormat::MAX_VALUE + 1;
384    ///     let it: ImageFormat = unsafe { core::ptr::read((&raw const value).cast()) };
385    ///     // Evaluate the enum value to ensure it actually has an invalid tag
386    ///     if it as u8 != value {
387    ///         unreachable!();
388    ///     }
389    /// }
390    /// ```
391    const _IMAGE_FORMAT: () = {};
392
393    /// Validates that any new variants in `ImageQuality` has led to a change in the `Contiguous` impl.
394    /// Note that to test this robustly, we'd need 256 tests, which is impractical.
395    /// We make the assumption that all new variants will maintain contiguousness.
396    ///
397    /// ```compile_fail,E0080
398    /// use bytemuck::Contiguous;
399    /// use peniko::ImageQuality;
400    /// const {
401    ///     let value = ImageQuality::MAX_VALUE + 1;
402    ///     let it: ImageQuality = unsafe { core::ptr::read((&raw const value).cast()) };
403    ///     // Evaluate the enum value to ensure it actually has an invalid tag
404    ///     if it as u8 != value {
405    ///         unreachable!();
406    ///     }
407    /// }
408    /// ```
409    const _IMAGE_QUALITY: () = {};
410}