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}