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, ImageAlphaType, 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        use bytemuck::Contiguous;
95        // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
96        *bits <= Self::MAX_VALUE
97    }
98}
99
100// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
101// the min and max values.
102unsafe impl bytemuck::Contiguous for ImageFormat {
103    type Int = u8;
104    const MIN_VALUE: u8 = Self::Rgba8 as u8;
105    const MAX_VALUE: u8 = Self::Bgra8 as u8;
106}
107
108// Safety: The enum is `repr(u8)` and has only fieldless variants.
109unsafe impl bytemuck::NoUninit for ImageAlphaType {}
110
111// Safety: The enum is `repr(u8)` and `0` is a valid value.
112unsafe impl bytemuck::Zeroable for ImageAlphaType {}
113
114// Safety: The enum is `repr(u8)`.
115unsafe impl bytemuck::checked::CheckedBitPattern for ImageAlphaType {
116    type Bits = u8;
117
118    fn is_valid_bit_pattern(bits: &u8) -> bool {
119        use bytemuck::Contiguous;
120        // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
121        *bits <= Self::MAX_VALUE
122    }
123}
124
125// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
126// the min and max values.
127unsafe impl bytemuck::Contiguous for ImageAlphaType {
128    type Int = u8;
129    const MIN_VALUE: u8 = Self::Alpha as u8;
130    const MAX_VALUE: u8 = Self::AlphaPremultiplied as u8;
131}
132
133// Safety: The enum is `repr(u8)` and has only fieldless variants.
134unsafe impl bytemuck::NoUninit for ImageQuality {}
135
136// Safety: The enum is `repr(u8)` and `0` is a valid value.
137unsafe impl bytemuck::Zeroable for ImageQuality {}
138
139// Safety: The enum is `repr(u8)`.
140unsafe impl bytemuck::checked::CheckedBitPattern for ImageQuality {
141    type Bits = u8;
142
143    fn is_valid_bit_pattern(bits: &u8) -> bool {
144        use bytemuck::Contiguous;
145        // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
146        *bits <= Self::MAX_VALUE
147    }
148}
149
150// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
151// the min and max values.
152unsafe impl bytemuck::Contiguous for ImageQuality {
153    type Int = u8;
154    const MIN_VALUE: u8 = Self::Low as u8;
155    const MAX_VALUE: u8 = Self::High as u8;
156}
157
158// Safety: The enum is `repr(u8)` and has only fieldless variants.
159unsafe impl bytemuck::NoUninit for Mix {}
160
161// Safety: The enum is `repr(u8)` and `0` is a valid value.
162unsafe impl bytemuck::Zeroable for Mix {}
163
164// Safety: The enum is `repr(u8)`.
165unsafe impl bytemuck::checked::CheckedBitPattern for Mix {
166    type Bits = u8;
167
168    #[expect(deprecated, reason = "Mix::Clip is still a valid bit pattern for now.")]
169    fn is_valid_bit_pattern(bits: &u8) -> bool {
170        *bits <= Self::Luminosity as u8 || *bits == Self::Clip as u8
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use crate::{Compose, Extend, Fill, ImageAlphaType, ImageFormat, ImageQuality, Mix};
177    use bytemuck::{checked::try_from_bytes, Contiguous, Zeroable};
178    use core::ptr;
179
180    #[test]
181    fn checked_bit_pattern() {
182        let valid_zero = bytemuck::bytes_of(&0_u8);
183        let valid_one = bytemuck::bytes_of(&1_u8);
184        let invalid = bytemuck::bytes_of(&200_u8);
185
186        assert_eq!(Ok(&Compose::Copy), try_from_bytes::<Compose>(valid_one));
187        assert!(try_from_bytes::<Compose>(invalid).is_err());
188
189        assert_eq!(Ok(&Extend::Repeat), try_from_bytes::<Extend>(valid_one));
190        assert!(try_from_bytes::<Extend>(invalid).is_err());
191
192        assert_eq!(Ok(&Fill::EvenOdd), try_from_bytes::<Fill>(valid_one));
193        assert!(try_from_bytes::<Fill>(invalid).is_err());
194
195        assert_eq!(
196            Ok(&ImageAlphaType::Alpha),
197            try_from_bytes::<ImageAlphaType>(valid_zero)
198        );
199        assert_eq!(
200            Ok(&ImageAlphaType::AlphaPremultiplied),
201            try_from_bytes::<ImageAlphaType>(valid_one)
202        );
203        assert!(try_from_bytes::<ImageFormat>(invalid).is_err());
204
205        assert_eq!(
206            Ok(&ImageFormat::Rgba8),
207            try_from_bytes::<ImageFormat>(valid_zero)
208        );
209        assert_eq!(
210            Ok(&ImageFormat::Bgra8),
211            try_from_bytes::<ImageFormat>(valid_one)
212        );
213        assert!(try_from_bytes::<ImageFormat>(invalid).is_err());
214
215        assert_eq!(
216            Ok(&ImageQuality::Medium),
217            try_from_bytes::<ImageQuality>(valid_one)
218        );
219        assert!(try_from_bytes::<ImageQuality>(invalid).is_err());
220
221        assert_eq!(Ok(&Mix::Multiply), try_from_bytes::<Mix>(valid_one));
222        assert!(try_from_bytes::<Mix>(invalid).is_err());
223    }
224
225    #[test]
226    fn contiguous() {
227        let compose1 = Compose::PlusLighter;
228        let compose2 = Compose::from_integer(compose1.into_integer());
229        assert_eq!(Some(compose1), compose2);
230
231        assert_eq!(None, Compose::from_integer(255));
232
233        let extend1 = Extend::Repeat;
234        let extend2 = Extend::from_integer(extend1.into_integer());
235        assert_eq!(Some(extend1), extend2);
236
237        assert_eq!(None, Extend::from_integer(255));
238
239        let fill1 = Fill::EvenOdd;
240        let fill2 = Fill::from_integer(fill1.into_integer());
241        assert_eq!(Some(fill1), fill2);
242
243        assert_eq!(None, Fill::from_integer(255));
244
245        let image_format_1 = ImageFormat::Rgba8;
246        let image_format_2 = ImageFormat::from_integer(image_format_1.into_integer());
247        assert_eq!(Some(image_format_1), image_format_2);
248
249        let image_alpha_type_1 = ImageAlphaType::Alpha;
250        let image_alpha_type_2 = ImageAlphaType::from_integer(image_alpha_type_1.into_integer());
251        assert_eq!(Some(image_alpha_type_1), image_alpha_type_2);
252        assert_eq!(None, ImageAlphaType::from_integer(255));
253
254        let image_quality_1 = ImageQuality::Low;
255        let image_quality_2 = ImageQuality::from_integer(image_quality_1.into_integer());
256        assert_eq!(Some(image_quality_1), image_quality_2);
257
258        assert_eq!(None, ImageQuality::from_integer(255));
259    }
260
261    #[test]
262    fn zeroable() {
263        let compose = Compose::zeroed();
264        assert_eq!(compose, Compose::Clear);
265
266        let extend = Extend::zeroed();
267        assert_eq!(extend, Extend::Pad);
268
269        let fill = Fill::zeroed();
270        assert_eq!(fill, Fill::NonZero);
271
272        let image_format = ImageFormat::zeroed();
273        assert_eq!(image_format, ImageFormat::Rgba8);
274
275        let image_alpha_type = ImageAlphaType::zeroed();
276        assert_eq!(image_alpha_type, ImageAlphaType::Alpha);
277
278        let image_quality = ImageQuality::zeroed();
279        assert_eq!(image_quality, ImageQuality::Low);
280
281        let mix = Mix::zeroed();
282        assert_eq!(mix, Mix::Normal);
283    }
284
285    /// Tests that the [`Contiguous`] impl for [`Compose`] is not trivially incorrect.
286    const _: () = {
287        let mut value = 0;
288        while value <= Compose::MAX_VALUE {
289            // Safety: In a const context, therefore if this makes an invalid Compose, that will be detected.
290            let it: Compose = unsafe { ptr::read((&raw const value).cast()) };
291            // Evaluate the enum value to ensure it actually has a valid tag
292            if it as u8 != value {
293                unreachable!();
294            }
295            value += 1;
296        }
297    };
298
299    /// Tests that the [`Contiguous`] impl for [`Extend`] is not trivially incorrect.
300    const _: () = {
301        let mut value = 0;
302        while value <= Extend::MAX_VALUE {
303            // Safety: In a const context, therefore if this makes an invalid Extend, that will be detected.
304            let it: Extend = unsafe { ptr::read((&raw const value).cast()) };
305            // Evaluate the enum value to ensure it actually has a valid tag
306            if it as u8 != value {
307                unreachable!();
308            }
309            value += 1;
310        }
311    };
312
313    /// Tests that the [`Contiguous`] impl for [`Fill`] is not trivially incorrect.
314    const _: () = {
315        let mut value = 0;
316        while value <= Fill::MAX_VALUE {
317            // Safety: In a const context, therefore if this makes an invalid Fill, that will be detected.
318            let it: Fill = unsafe { ptr::read((&raw const value).cast()) };
319            // Evaluate the enum value to ensure it actually has a valid tag
320            if it as u8 != value {
321                unreachable!();
322            }
323            value += 1;
324        }
325    };
326
327    /// Tests that the [`Contiguous`] impl for [`ImageFormat`] is not trivially incorrect.
328    const _: () = {
329        let mut value = 0;
330        while value <= ImageFormat::MAX_VALUE {
331            // Safety: In a const context, therefore if this makes an invalid ImageFormat, that will be detected.
332            let it: ImageFormat = unsafe { ptr::read((&raw const value).cast()) };
333            // Evaluate the enum value to ensure it actually has a valid tag
334            if it as u8 != value {
335                unreachable!();
336            }
337            value += 1;
338        }
339    };
340
341    /// Tests that the [`Contiguous`] impl for [`ImageAlphaType`] is not trivially incorrect.
342    const _: () = {
343        let mut value = 0;
344        while value <= ImageAlphaType::MAX_VALUE {
345            // Safety: In a const context, therefore if this makes an invalid ImageFormat, that will be detected.
346            let it: ImageAlphaType = unsafe { ptr::read((&raw const value).cast()) };
347            // Evaluate the enum value to ensure it actually has a valid tag
348            if it as u8 != value {
349                unreachable!();
350            }
351            value += 1;
352        }
353    };
354
355    /// Tests that the [`Contiguous`] impl for [`ImageQuality`] is not trivially incorrect.
356    const _: () = {
357        let mut value = 0;
358        while value <= ImageQuality::MAX_VALUE {
359            // Safety: In a const context, therefore if this makes an invalid ImageQuality, that will be detected.
360            let it: ImageQuality = unsafe { ptr::read((&raw const value).cast()) };
361            // Evaluate the enum value to ensure it actually has a valid tag
362            if it as u8 != value {
363                unreachable!();
364            }
365            value += 1;
366        }
367    };
368}
369
370#[cfg(doctest)]
371/// Doctests aren't collected under `cfg(test)`; we can use `cfg(doctest)` instead
372mod doctests {
373    /// Validates that any new variants in `Compose` has led to a change in the `Contiguous` impl.
374    /// Note that to test this robustly, we'd need 256 tests, which is impractical.
375    /// We make the assumption that all new variants will maintain contiguousness.
376    ///
377    /// ```compile_fail,E0080
378    /// use bytemuck::Contiguous;
379    /// use peniko::Compose;
380    /// const {
381    ///     let value = Compose::MAX_VALUE + 1;
382    ///     // Safety: In a const context, therefore if this makes an invalid Compose, that will be detected.
383    ///     // (Indeed, we rely upon that)
384    ///     let it: Compose = 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 _COMPOSE: () = {};
392
393    /// Validates that any new variants in `Extend` 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::Extend;
400    /// const {
401    ///     let value = Extend::MAX_VALUE + 1;
402    ///     let it: Extend = 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 _EXTEND: () = {};
410
411    /// Validates that any new variants in `Fill` has led to a change in the `Contiguous` impl.
412    /// Note that to test this robustly, we'd need 256 tests, which is impractical.
413    /// We make the assumption that all new variants will maintain contiguousness.
414    ///
415    /// ```compile_fail,E0080
416    /// use bytemuck::Contiguous;
417    /// use peniko::Fill;
418    /// const {
419    ///     let value = Fill::MAX_VALUE + 1;
420    ///     let it: Fill = unsafe { core::ptr::read((&raw const value).cast()) };
421    ///     // Evaluate the enum value to ensure it actually has an invalid tag
422    ///     if it as u8 != value {
423    ///         unreachable!();
424    ///     }
425    /// }
426    /// ```
427    const _FILL: () = {};
428
429    /// Validates that any new variants in `ImageFormat` has led to a change in the `Contiguous` impl.
430    /// Note that to test this robustly, we'd need 256 tests, which is impractical.
431    /// We make the assumption that all new variants will maintain contiguousness.
432    ///
433    /// ```compile_fail,E0080
434    /// use bytemuck::Contiguous;
435    /// use peniko::ImageFormat;
436    /// const {
437    ///     let value = ImageFormat::MAX_VALUE + 1;
438    ///     let it: ImageFormat = unsafe { core::ptr::read((&raw const value).cast()) };
439    ///     // Evaluate the enum value to ensure it actually has an invalid tag
440    ///     if it as u8 != value {
441    ///         unreachable!();
442    ///     }
443    /// }
444    /// ```
445    const _IMAGE_FORMAT: () = {};
446
447    /// Validates that any new variants in `ImageAlphaType` has led to a change in the `Contiguous` impl.
448    /// Note that to test this robustly, we'd need 256 tests, which is impractical.
449    /// We make the assumption that all new variants will maintain contiguousness.
450    ///
451    /// ```compile_fail,E0080
452    /// use bytemuck::Contiguous;
453    /// use peniko::ImageAlphaType;
454    /// const {
455    ///     let value = ImageAlphaType::MAX_VALUE + 1;
456    ///     let it: ImageAlphaType = unsafe { core::ptr::read((&raw const value).cast()) };
457    ///     // Evaluate the enum value to ensure it actually has an invalid tag
458    ///     if it as u8 != value {
459    ///         unreachable!();
460    ///     }
461    /// }
462    /// ```
463    const _IMAGE_ALPHA_TYPE: () = {};
464
465    /// Validates that any new variants in `ImageQuality` has led to a change in the `Contiguous` impl.
466    /// Note that to test this robustly, we'd need 256 tests, which is impractical.
467    /// We make the assumption that all new variants will maintain contiguousness.
468    ///
469    /// ```compile_fail,E0080
470    /// use bytemuck::Contiguous;
471    /// use peniko::ImageQuality;
472    /// const {
473    ///     let value = ImageQuality::MAX_VALUE + 1;
474    ///     let it: ImageQuality = unsafe { core::ptr::read((&raw const value).cast()) };
475    ///     // Evaluate the enum value to ensure it actually has an invalid tag
476    ///     if it as u8 != value {
477    ///         unreachable!();
478    ///     }
479    /// }
480    /// ```
481    const _IMAGE_QUALITY: () = {};
482}