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 ImageAlphaType {}
85
86// Safety: The enum is `repr(u8)` and `0` is a valid value.
87unsafe impl bytemuck::Zeroable for ImageAlphaType {}
88
89// Safety: The enum is `repr(u8)`.
90unsafe impl bytemuck::checked::CheckedBitPattern for ImageAlphaType {
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 ImageAlphaType {
103 type Int = u8;
104 const MIN_VALUE: u8 = Self::Alpha as u8;
105 const MAX_VALUE: u8 = Self::AlphaPremultiplied as u8;
106}
107
108// Safety: The enum is `repr(u8)` and has only fieldless variants.
109unsafe impl bytemuck::NoUninit for ImageFormat {}
110
111// Safety: The enum is `repr(u8)` and `0` is a valid value.
112unsafe impl bytemuck::Zeroable for ImageFormat {}
113
114// Safety: The enum is `repr(u8)`.
115unsafe impl bytemuck::checked::CheckedBitPattern for ImageFormat {
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 ImageFormat {
128 type Int = u8;
129 const MIN_VALUE: u8 = Self::Rgba8 as u8;
130 const MAX_VALUE: u8 = Self::Bgra8 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 fn is_valid_bit_pattern(bits: &u8) -> bool {
169 use bytemuck::Contiguous;
170 // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
171 *bits <= Self::MAX_VALUE
172 }
173}
174
175// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
176// the min and max values.
177unsafe impl bytemuck::Contiguous for Mix {
178 type Int = u8;
179 const MIN_VALUE: u8 = Self::Normal as u8;
180 const MAX_VALUE: u8 = Self::Luminosity as u8;
181}
182
183#[cfg(test)]
184mod tests {
185 use crate::{Compose, Extend, Fill, ImageAlphaType, ImageFormat, ImageQuality, Mix};
186 use bytemuck::{Contiguous, Zeroable, checked::try_from_bytes};
187 use core::ptr;
188
189 #[test]
190 fn checked_bit_pattern() {
191 let valid_zero = bytemuck::bytes_of(&0_u8);
192 let valid_one = bytemuck::bytes_of(&1_u8);
193 let invalid = bytemuck::bytes_of(&200_u8);
194
195 assert_eq!(Ok(&Compose::Copy), try_from_bytes::<Compose>(valid_one));
196 assert!(try_from_bytes::<Compose>(invalid).is_err());
197
198 assert_eq!(Ok(&Extend::Repeat), try_from_bytes::<Extend>(valid_one));
199 assert!(try_from_bytes::<Extend>(invalid).is_err());
200
201 assert_eq!(Ok(&Fill::EvenOdd), try_from_bytes::<Fill>(valid_one));
202 assert!(try_from_bytes::<Fill>(invalid).is_err());
203
204 assert_eq!(
205 Ok(&ImageAlphaType::Alpha),
206 try_from_bytes::<ImageAlphaType>(valid_zero)
207 );
208 assert_eq!(
209 Ok(&ImageAlphaType::AlphaPremultiplied),
210 try_from_bytes::<ImageAlphaType>(valid_one)
211 );
212 assert!(try_from_bytes::<ImageAlphaType>(invalid).is_err());
213
214 assert_eq!(
215 Ok(&ImageFormat::Rgba8),
216 try_from_bytes::<ImageFormat>(valid_zero)
217 );
218 assert_eq!(
219 Ok(&ImageFormat::Bgra8),
220 try_from_bytes::<ImageFormat>(valid_one)
221 );
222 assert!(try_from_bytes::<ImageFormat>(invalid).is_err());
223
224 assert_eq!(
225 Ok(&ImageQuality::Medium),
226 try_from_bytes::<ImageQuality>(valid_one)
227 );
228 assert!(try_from_bytes::<ImageQuality>(invalid).is_err());
229
230 assert_eq!(Ok(&Mix::Multiply), try_from_bytes::<Mix>(valid_one));
231 assert!(try_from_bytes::<Mix>(invalid).is_err());
232 }
233
234 #[test]
235 fn contiguous() {
236 let compose1 = Compose::PlusLighter;
237 let compose2 = Compose::from_integer(compose1.into_integer());
238 assert_eq!(Some(compose1), compose2);
239
240 assert_eq!(None, Compose::from_integer(255));
241
242 let extend1 = Extend::Repeat;
243 let extend2 = Extend::from_integer(extend1.into_integer());
244 assert_eq!(Some(extend1), extend2);
245
246 assert_eq!(None, Extend::from_integer(255));
247
248 let fill1 = Fill::EvenOdd;
249 let fill2 = Fill::from_integer(fill1.into_integer());
250 assert_eq!(Some(fill1), fill2);
251
252 assert_eq!(None, Fill::from_integer(255));
253
254 let image_alpha_type_1 = ImageAlphaType::Alpha;
255 let image_alpha_type_2 = ImageAlphaType::from_integer(image_alpha_type_1.into_integer());
256 assert_eq!(Some(image_alpha_type_1), image_alpha_type_2);
257
258 assert_eq!(None, ImageAlphaType::from_integer(255));
259
260 let image_format_1 = ImageFormat::Rgba8;
261 let image_format_2 = ImageFormat::from_integer(image_format_1.into_integer());
262 assert_eq!(Some(image_format_1), image_format_2);
263
264 assert_eq!(None, ImageFormat::from_integer(255));
265
266 let image_quality_1 = ImageQuality::Low;
267 let image_quality_2 = ImageQuality::from_integer(image_quality_1.into_integer());
268 assert_eq!(Some(image_quality_1), image_quality_2);
269
270 assert_eq!(None, ImageQuality::from_integer(255));
271
272 let mix_1 = Mix::Multiply;
273 let mix_2 = Mix::from_integer(mix_1.into_integer());
274 assert_eq!(Some(mix_1), mix_2);
275
276 assert_eq!(None, Mix::from_integer(255));
277 }
278
279 #[test]
280 fn zeroable() {
281 let compose = Compose::zeroed();
282 assert_eq!(compose, Compose::Clear);
283
284 let extend = Extend::zeroed();
285 assert_eq!(extend, Extend::Pad);
286
287 let fill = Fill::zeroed();
288 assert_eq!(fill, Fill::NonZero);
289
290 let image_alpha_type = ImageAlphaType::zeroed();
291 assert_eq!(image_alpha_type, ImageAlphaType::Alpha);
292
293 let image_format = ImageFormat::zeroed();
294 assert_eq!(image_format, ImageFormat::Rgba8);
295
296 let image_quality = ImageQuality::zeroed();
297 assert_eq!(image_quality, ImageQuality::Low);
298
299 let mix = Mix::zeroed();
300 assert_eq!(mix, Mix::Normal);
301 }
302
303 /// Tests that the [`Contiguous`] impl for [`Compose`] is not trivially incorrect.
304 const _: () = {
305 let mut value = 0;
306 while value <= Compose::MAX_VALUE {
307 // Safety: In a const context, therefore if this makes an invalid Compose, that will be detected.
308 let it: Compose = unsafe { ptr::read((&raw const value).cast()) };
309 // Evaluate the enum value to ensure it actually has a valid tag
310 if it as u8 != value {
311 unreachable!();
312 }
313 value += 1;
314 }
315 };
316
317 /// Tests that the [`Contiguous`] impl for [`Extend`] is not trivially incorrect.
318 const _: () = {
319 let mut value = 0;
320 while value <= Extend::MAX_VALUE {
321 // Safety: In a const context, therefore if this makes an invalid Extend, that will be detected.
322 let it: Extend = unsafe { ptr::read((&raw const value).cast()) };
323 // Evaluate the enum value to ensure it actually has a valid tag
324 if it as u8 != value {
325 unreachable!();
326 }
327 value += 1;
328 }
329 };
330
331 /// Tests that the [`Contiguous`] impl for [`Fill`] is not trivially incorrect.
332 const _: () = {
333 let mut value = 0;
334 while value <= Fill::MAX_VALUE {
335 // Safety: In a const context, therefore if this makes an invalid Fill, that will be detected.
336 let it: Fill = unsafe { ptr::read((&raw const value).cast()) };
337 // Evaluate the enum value to ensure it actually has a valid tag
338 if it as u8 != value {
339 unreachable!();
340 }
341 value += 1;
342 }
343 };
344
345 /// Tests that the [`Contiguous`] impl for [`ImageAlphaType`] is not trivially incorrect.
346 const _: () = {
347 let mut value = 0;
348 while value <= ImageAlphaType::MAX_VALUE {
349 // Safety: In a const context, therefore if this makes an invalid ImageFormat, that will be detected.
350 let it: ImageAlphaType = unsafe { ptr::read((&raw const value).cast()) };
351 // Evaluate the enum value to ensure it actually has a valid tag
352 if it as u8 != value {
353 unreachable!();
354 }
355 value += 1;
356 }
357 };
358
359 /// Tests that the [`Contiguous`] impl for [`ImageFormat`] is not trivially incorrect.
360 const _: () = {
361 let mut value = 0;
362 while value <= ImageFormat::MAX_VALUE {
363 // Safety: In a const context, therefore if this makes an invalid ImageFormat, that will be detected.
364 let it: ImageFormat = unsafe { ptr::read((&raw const value).cast()) };
365 // Evaluate the enum value to ensure it actually has a valid tag
366 if it as u8 != value {
367 unreachable!();
368 }
369 value += 1;
370 }
371 };
372
373 /// Tests that the [`Contiguous`] impl for [`ImageQuality`] is not trivially incorrect.
374 const _: () = {
375 let mut value = 0;
376 while value <= ImageQuality::MAX_VALUE {
377 // Safety: In a const context, therefore if this makes an invalid ImageQuality, that will be detected.
378 let it: ImageQuality = unsafe { ptr::read((&raw const value).cast()) };
379 // Evaluate the enum value to ensure it actually has a valid tag
380 if it as u8 != value {
381 unreachable!();
382 }
383 value += 1;
384 }
385 };
386
387 /// Tests that the [`Contiguous`] impl for [`Mix`] is not trivially incorrect.
388 const _: () = {
389 let mut value = 0;
390 while value <= ImageQuality::MAX_VALUE {
391 // Safety: In a const context, therefore if this makes an invalid ImageQuality, that will be detected.
392 let it: ImageQuality = unsafe { ptr::read((&raw const value).cast()) };
393 // Evaluate the enum value to ensure it actually has a valid tag
394 if it as u8 != value {
395 unreachable!();
396 }
397 value += 1;
398 }
399 };
400}
401
402#[cfg(doctest)]
403/// Doctests aren't collected under `cfg(test)`; we can use `cfg(doctest)` instead
404mod doctests {
405 /// Validates that any new variants in `Compose` has led to a change in the `Contiguous` impl.
406 /// Note that to test this robustly, we'd need 256 tests, which is impractical.
407 /// We make the assumption that all new variants will maintain contiguousness.
408 ///
409 /// ```compile_fail,E0080
410 /// use bytemuck::Contiguous;
411 /// use peniko::Compose;
412 /// const {
413 /// let value = Compose::MAX_VALUE + 1;
414 /// // Safety: In a const context, therefore if this makes an invalid Compose, that will be detected.
415 /// // (Indeed, we rely upon that)
416 /// let it: Compose = unsafe { core::ptr::read((&raw const value).cast()) };
417 /// // Evaluate the enum value to ensure it actually has an invalid tag
418 /// if it as u8 != value {
419 /// unreachable!();
420 /// }
421 /// }
422 /// ```
423 const _COMPOSE: () = {};
424
425 /// Validates that any new variants in `Extend` has led to a change in the `Contiguous` impl.
426 /// Note that to test this robustly, we'd need 256 tests, which is impractical.
427 /// We make the assumption that all new variants will maintain contiguousness.
428 ///
429 /// ```compile_fail,E0080
430 /// use bytemuck::Contiguous;
431 /// use peniko::Extend;
432 /// const {
433 /// let value = Extend::MAX_VALUE + 1;
434 /// let it: Extend = unsafe { core::ptr::read((&raw const value).cast()) };
435 /// // Evaluate the enum value to ensure it actually has an invalid tag
436 /// if it as u8 != value {
437 /// unreachable!();
438 /// }
439 /// }
440 /// ```
441 const _EXTEND: () = {};
442
443 /// Validates that any new variants in `Fill` has led to a change in the `Contiguous` impl.
444 /// Note that to test this robustly, we'd need 256 tests, which is impractical.
445 /// We make the assumption that all new variants will maintain contiguousness.
446 ///
447 /// ```compile_fail,E0080
448 /// use bytemuck::Contiguous;
449 /// use peniko::Fill;
450 /// const {
451 /// let value = Fill::MAX_VALUE + 1;
452 /// let it: Fill = unsafe { core::ptr::read((&raw const value).cast()) };
453 /// // Evaluate the enum value to ensure it actually has an invalid tag
454 /// if it as u8 != value {
455 /// unreachable!();
456 /// }
457 /// }
458 /// ```
459 const _FILL: () = {};
460
461 /// Validates that any new variants in `ImageAlphaType` has led to a change in the `Contiguous` impl.
462 /// Note that to test this robustly, we'd need 256 tests, which is impractical.
463 /// We make the assumption that all new variants will maintain contiguousness.
464 ///
465 /// ```compile_fail,E0080
466 /// use bytemuck::Contiguous;
467 /// use peniko::ImageAlphaType;
468 /// const {
469 /// let value = ImageAlphaType::MAX_VALUE + 1;
470 /// let it: ImageAlphaType = unsafe { core::ptr::read((&raw const value).cast()) };
471 /// // Evaluate the enum value to ensure it actually has an invalid tag
472 /// if it as u8 != value {
473 /// unreachable!();
474 /// }
475 /// }
476 /// ```
477 const _IMAGE_ALPHA_TYPE: () = {};
478
479 /// Validates that any new variants in `ImageFormat` has led to a change in the `Contiguous` impl.
480 /// Note that to test this robustly, we'd need 256 tests, which is impractical.
481 /// We make the assumption that all new variants will maintain contiguousness.
482 ///
483 /// ```compile_fail,E0080
484 /// use bytemuck::Contiguous;
485 /// use peniko::ImageFormat;
486 /// const {
487 /// let value = ImageFormat::MAX_VALUE + 1;
488 /// let it: ImageFormat = unsafe { core::ptr::read((&raw const value).cast()) };
489 /// // Evaluate the enum value to ensure it actually has an invalid tag
490 /// if it as u8 != value {
491 /// unreachable!();
492 /// }
493 /// }
494 /// ```
495 const _IMAGE_FORMAT: () = {};
496
497 /// Validates that any new variants in `ImageQuality` has led to a change in the `Contiguous` impl.
498 /// Note that to test this robustly, we'd need 256 tests, which is impractical.
499 /// We make the assumption that all new variants will maintain contiguousness.
500 ///
501 /// ```compile_fail,E0080
502 /// use bytemuck::Contiguous;
503 /// use peniko::ImageQuality;
504 /// const {
505 /// let value = ImageQuality::MAX_VALUE + 1;
506 /// let it: ImageQuality = unsafe { core::ptr::read((&raw const value).cast()) };
507 /// // Evaluate the enum value to ensure it actually has an invalid tag
508 /// if it as u8 != value {
509 /// unreachable!();
510 /// }
511 /// }
512 /// ```
513 const _IMAGE_QUALITY: () = {};
514
515 /// Validates that any new variants in `Mix` has led to a change in the `Contiguous` impl.
516 /// Note that to test this robustly, we'd need 256 tests, which is impractical.
517 /// We make the assumption that all new variants will maintain contiguousness.
518 ///
519 /// ```compile_fail,E0080
520 /// use bytemuck::Contiguous;
521 /// use peniko::Mix;
522 /// const {
523 /// let value = Mix::MAX_VALUE + 1;
524 /// let it: Mix = unsafe { core::ptr::read((&raw const value).cast()) };
525 /// // Evaluate the enum value to ensure it actually has an invalid tag
526 /// if it as u8 != value {
527 /// unreachable!();
528 /// }
529 /// }
530 /// ```
531 const _MIX: () = {};
532}