Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down Expand Up @@ -60,6 +71,8 @@ pub struct ImageData {
pub data: Blob<u8>,
/// 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.
Expand Down
79 changes: 77 additions & 2 deletions src/impl_bytemuck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down Expand Up @@ -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 {}

Expand Down Expand Up @@ -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;

Expand All @@ -166,6 +191,16 @@ mod tests {
assert_eq!(Ok(&Fill::EvenOdd), try_from_bytes::<Fill>(valid_one));
assert!(try_from_bytes::<Fill>(invalid).is_err());

assert_eq!(
Ok(&ImageAlphaType::Alpha),
try_from_bytes::<ImageAlphaType>(valid_zero)
);
assert_eq!(
Ok(&ImageAlphaType::AlphaPremultiplied),
try_from_bytes::<ImageAlphaType>(valid_one)
);
assert!(try_from_bytes::<ImageFormat>(invalid).is_err());

assert_eq!(
Ok(&ImageFormat::Rgba8),
try_from_bytes::<ImageFormat>(valid_zero)
Expand Down Expand Up @@ -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);
Expand All @@ -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);

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
Loading