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}