diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c3847a..31cd5b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This release has an [MSRV] of 1.82. - `Style` now impl `PartialEq`. ([#114][] by [@liferooter][]) - Add `Bgra8` variant to `ImageFormat`. ([#120][] by [@sagudev][]) +- Provide `ImageAlphaType` with `ImageData`. ([#121][] by [@sagudev][]) ## [0.4.0][] (2025-04-30) @@ -137,6 +138,7 @@ This release has an [MSRV] of 1.70. [#104]: https://github.com/linebender/peniko/pull/104 [#114]: https://github.com/linebender/peniko/pull/114 [#120]: https://github.com/linebender/peniko/pull/120 +[#121]: https://github.com/linebender/peniko/pull/121 [@dfrg]: https://github.com/dfrg [@DJMcNab]: https://github.com/DJMcNab diff --git a/src/image.rs b/src/image.rs index 0e237a3..70ed799 100644 --- a/src/image.rs +++ b/src/image.rs @@ -31,6 +31,17 @@ impl ImageFormat { } } +/// Handling of alpha channel. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[repr(u8)] +pub enum ImageAlphaType { + /// Image has separate alpha channel (also called straight/unpremultiplied alpha). + Alpha = 0, + /// Image has colors with premultiplied alpha. + AlphaPremultiplied = 1, +} + /// Defines the desired quality for sampling an image. #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -60,6 +71,8 @@ pub struct ImageData { pub data: Blob, /// Pixel format of the image. pub format: ImageFormat, + /// Encoding of alpha in the image pixels. + pub alpha_type: ImageAlphaType, /// Width of the image. pub width: u32, /// Height of the image. diff --git a/src/impl_bytemuck.rs b/src/impl_bytemuck.rs index 00852ef..e914669 100644 --- a/src/impl_bytemuck.rs +++ b/src/impl_bytemuck.rs @@ -3,7 +3,7 @@ #![allow(unsafe_code, reason = "unsafe is required for bytemuck unsafe impls")] -use crate::{Compose, Extend, Fill, ImageFormat, ImageQuality, Mix}; +use crate::{Compose, Extend, Fill, ImageAlphaType, ImageFormat, ImageQuality, Mix}; // Safety: The enum is `repr(u8)` and has only fieldless variants. unsafe impl bytemuck::NoUninit for Compose {} @@ -105,6 +105,31 @@ unsafe impl bytemuck::Contiguous for ImageFormat { const MAX_VALUE: u8 = Self::Bgra8 as u8; } +// Safety: The enum is `repr(u8)` and has only fieldless variants. +unsafe impl bytemuck::NoUninit for ImageAlphaType {} + +// Safety: The enum is `repr(u8)` and `0` is a valid value. +unsafe impl bytemuck::Zeroable for ImageAlphaType {} + +// Safety: The enum is `repr(u8)`. +unsafe impl bytemuck::checked::CheckedBitPattern for ImageAlphaType { + type Bits = u8; + + fn is_valid_bit_pattern(bits: &u8) -> bool { + use bytemuck::Contiguous; + // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE. + *bits <= Self::MAX_VALUE + } +} + +// Safety: The enum is `repr(u8)`. All values are `u8` and fall within +// the min and max values. +unsafe impl bytemuck::Contiguous for ImageAlphaType { + type Int = u8; + const MIN_VALUE: u8 = Self::Alpha as u8; + const MAX_VALUE: u8 = Self::AlphaPremultiplied as u8; +} + // Safety: The enum is `repr(u8)` and has only fieldless variants. unsafe impl bytemuck::NoUninit for ImageQuality {} @@ -147,7 +172,7 @@ unsafe impl bytemuck::checked::CheckedBitPattern for Mix { #[cfg(test)] mod tests { - use crate::{Compose, Extend, Fill, ImageFormat, ImageQuality, Mix}; + use crate::{Compose, Extend, Fill, ImageAlphaType, ImageFormat, ImageQuality, Mix}; use bytemuck::{checked::try_from_bytes, Contiguous, Zeroable}; use core::ptr; @@ -166,6 +191,16 @@ mod tests { assert_eq!(Ok(&Fill::EvenOdd), try_from_bytes::(valid_one)); assert!(try_from_bytes::(invalid).is_err()); + assert_eq!( + Ok(&ImageAlphaType::Alpha), + try_from_bytes::(valid_zero) + ); + assert_eq!( + Ok(&ImageAlphaType::AlphaPremultiplied), + try_from_bytes::(valid_one) + ); + assert!(try_from_bytes::(invalid).is_err()); + assert_eq!( Ok(&ImageFormat::Rgba8), try_from_bytes::(valid_zero) @@ -210,6 +245,11 @@ mod tests { let image_format_2 = ImageFormat::from_integer(image_format_1.into_integer()); assert_eq!(Some(image_format_1), image_format_2); + let image_alpha_type_1 = ImageAlphaType::Alpha; + let image_alpha_type_2 = ImageAlphaType::from_integer(image_alpha_type_1.into_integer()); + assert_eq!(Some(image_alpha_type_1), image_alpha_type_2); + assert_eq!(None, ImageAlphaType::from_integer(255)); + let image_quality_1 = ImageQuality::Low; let image_quality_2 = ImageQuality::from_integer(image_quality_1.into_integer()); assert_eq!(Some(image_quality_1), image_quality_2); @@ -231,6 +271,9 @@ mod tests { let image_format = ImageFormat::zeroed(); assert_eq!(image_format, ImageFormat::Rgba8); + let image_alpha_type = ImageAlphaType::zeroed(); + assert_eq!(image_alpha_type, ImageAlphaType::Alpha); + let image_quality = ImageQuality::zeroed(); assert_eq!(image_quality, ImageQuality::Low); @@ -294,6 +337,20 @@ mod tests { } }; + /// Tests that the [`Contiguous`] impl for [`ImageAlphaType`] is not trivially incorrect. + const _: () = { + let mut value = 0; + while value <= ImageAlphaType::MAX_VALUE { + // Safety: In a const context, therefore if this makes an invalid ImageFormat, that will be detected. + let it: ImageAlphaType = unsafe { ptr::read((&raw const value).cast()) }; + // Evaluate the enum value to ensure it actually has a valid tag + if it as u8 != value { + unreachable!(); + } + value += 1; + } + }; + /// Tests that the [`Contiguous`] impl for [`ImageQuality`] is not trivially incorrect. const _: () = { let mut value = 0; @@ -386,6 +443,24 @@ mod doctests { /// ``` const _IMAGE_FORMAT: () = {}; + /// Validates that any new variants in `ImageAlphaType` has led to a change in the `Contiguous` impl. + /// Note that to test this robustly, we'd need 256 tests, which is impractical. + /// We make the assumption that all new variants will maintain contiguousness. + /// + /// ```compile_fail,E0080 + /// use bytemuck::Contiguous; + /// use peniko::ImageAlphaType; + /// const { + /// let value = ImageAlphaType::MAX_VALUE + 1; + /// let it: ImageAlphaType = unsafe { core::ptr::read((&raw const value).cast()) }; + /// // Evaluate the enum value to ensure it actually has an invalid tag + /// if it as u8 != value { + /// unreachable!(); + /// } + /// } + /// ``` + const _IMAGE_ALPHA_TYPE: () = {}; + /// Validates that any new variants in `ImageQuality` has led to a change in the `Contiguous` impl. /// Note that to test this robustly, we'd need 256 tests, which is impractical. /// We make the assumption that all new variants will maintain contiguousness. diff --git a/src/lib.rs b/src/lib.rs index 7df16db..e0b24e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,7 +52,8 @@ pub use gradient::{ RadialGradientPosition, SweepGradientPosition, }; pub use image::{ - ImageBrush, ImageBrushRef, ImageData, ImageFormat, ImageQuality, ImageRenderParams, + ImageAlphaType, ImageBrush, ImageBrushRef, ImageData, ImageFormat, ImageQuality, + ImageRenderParams, }; pub use style::{Fill, Style, StyleRef};