Skip to content

Commit

Permalink
Add PremulRgba8, Rgba8::to_u32 (#66)
Browse files Browse the repository at this point in the history
Packing a `PremulColor` to 8 bit channels is a common operation and is
done in Vello, so we now have support for a `PremulRgb8a` and some
`to_u32` operations as well.
  • Loading branch information
waywardmonkeys authored Nov 24, 2024
1 parent cb11a56 commit 1c9b06f
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 20 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
# To see the full list of contributors, see the revision history in
# source control.
Raph Levien
Bruce Mitchener, Jr.
Tom Churchman
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ This release has an [MSRV][] of 1.82.

* Add `BLACK`, `WHITE`, and `TRANSPARENT` constants to the color types. ([#64][] by [@waywardmonkeys][])
* The `serde` feature enables using `serde` with `AlphaColor`, `OpaqueColor`, `PremulColor`, and `Rgba8`. ([#61][] by [@waywardmonkeys][])
* Conversion of a `Rgba8` to a `u32` is now provided. ([#66][] by [@waywardmonkeys][])
* A new `PremulRgba8` type mirrors `Rgba8`, but for `PremulColor`. ([#66][] by [@waywardmonkeys][])

### Changed

Expand All @@ -35,6 +37,7 @@ This is the initial release.
[#61]: https://github.com/linebender/color/pull/61
[#64]: https://github.com/linebender/color/pull/64
[#65]: https://github.com/linebender/color/pull/65
[#66]: https://github.com/linebender/color/pull/66

[Unreleased]: https://github.com/linebender/color/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/linebender/color/releases/tag/v0.1.0
Expand Down
9 changes: 5 additions & 4 deletions color/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ A number of other tasks are out of scope for this crate:
* Appearance models and other color science not needed for rendering.
* Quantizing and packing to lower bit depths.

The [`Rgba8`] type is a partial exception to this last item, as that representation
is ubiquitous and requires special logic for serializing to maximize compatibility.
The [`Rgba8`] and [`PremulRgba8`] types are a partial exception to this last item, as
those representation are ubiquitous and requires special logic for serializing to
maximize compatibility.

Some of these capabilities may be added as other crates within the `color` repository,
and we will also facilitate interoperability with other color crates in the Rust
Expand All @@ -91,9 +92,9 @@ this trait for new color spaces.
(likely using your target's libc).
- `libm`: Use floating point implementations from [libm][].
- `bytemuck`: Implement traits from `bytemuck` on [`AlphaColor`], [`OpaqueColor`],
[`PremulColor`], and [`Rgba8`].
[`PremulColor`], [`PremulRgba8`], and [`Rgba8`].
- `serde`: Implement `serde::Deserialize` and `serde::Serialize` on [`AlphaColor`],
[`OpaqueColor`], [`PremulColor`], and [`Rgba8`].
[`OpaqueColor`], [`PremulColor`], [`PremulRgba8`], and [`Rgba8`].

At least one of `std` and `libm` is required; `std` overrides `libm`.

Expand Down
13 changes: 12 additions & 1 deletion color/src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use core::any::TypeId;
use core::marker::PhantomData;

use crate::{ColorSpace, ColorSpaceLayout, ColorSpaceTag, Oklab, Oklch, Rgba8, Srgb};
use crate::{ColorSpace, ColorSpaceLayout, ColorSpaceTag, Oklab, Oklch, PremulRgba8, Rgba8, Srgb};

#[cfg(all(not(feature = "std"), not(test)))]
use crate::floatfuncs::FloatFuncs;
Expand Down Expand Up @@ -569,6 +569,17 @@ impl<CS: ColorSpace> PremulColor<CS> {
let d = (self - other).components;
(d[0] * d[0] + d[1] * d[1] + d[2] * d[2] + d[3] * d[3]).sqrt()
}

/// Pack into 8 bit per component encoding.
#[must_use]
pub fn to_rgba8(self) -> PremulRgba8 {
#[expect(clippy::cast_possible_truncation, reason = "deliberate quantization")]
let [r, g, b, a] = self
.convert::<Srgb>()
.components
.map(|x| (x.clamp(0., 1.) * 255.0).round() as u8);
PremulRgba8 { r, g, b, a }
}
}

// Lossless conversion traits.
Expand Down
25 changes: 23 additions & 2 deletions color/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::{AlphaColor, ColorSpace, OpaqueColor, PremulColor, Rgba8};
use crate::{AlphaColor, ColorSpace, OpaqueColor, PremulColor, PremulRgba8, Rgba8};

// Safety: The struct is `repr(transparent)` and the data member is bytemuck::Pod.
unsafe impl<CS: ColorSpace> bytemuck::Pod for AlphaColor<CS> {}
Expand Down Expand Up @@ -32,6 +32,12 @@ unsafe impl<CS: ColorSpace> bytemuck::TransparentWrapper<[f32; 4]> for PremulCol
// Safety: The struct is `repr(transparent)` and the data member is bytemuck::Zeroable.
unsafe impl<CS: ColorSpace> bytemuck::Zeroable for PremulColor<CS> {}

// Safety: The struct is `repr(C)` and all members are bytemuck::Pod.
unsafe impl bytemuck::Pod for PremulRgba8 {}

// Safety: The struct is `repr(C)` and all members are bytemuck::Zeroable.
unsafe impl bytemuck::Zeroable for PremulRgba8 {}

// Safety: The struct is `repr(C)` and all members are bytemuck::Pod.
unsafe impl bytemuck::Pod for Rgba8 {}

Expand All @@ -40,7 +46,7 @@ unsafe impl bytemuck::Zeroable for Rgba8 {}

#[cfg(test)]
mod tests {
use crate::{AlphaColor, OpaqueColor, PremulColor, Rgba8, Srgb};
use crate::{AlphaColor, OpaqueColor, PremulColor, PremulRgba8, Rgba8, Srgb};
use bytemuck::{TransparentWrapper, Zeroable};
use core::marker::PhantomData;

Expand Down Expand Up @@ -73,6 +79,21 @@ mod tests {
assert_is_pod(components);
}

#[test]
fn premulrgba8_is_pod() {
let rgba8 = PremulRgba8 {
r: 0,
b: 0,
g: 0,
a: 0,
};
let PremulRgba8 { r, g, b, a } = rgba8;
assert_is_pod(r);
assert_is_pod(g);
assert_is_pod(b);
assert_is_pod(a);
}

#[test]
fn rgba8_is_pod() {
let rgba8 = Rgba8 {
Expand Down
30 changes: 18 additions & 12 deletions color/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@
//! * Appearance models and other color science not needed for rendering.
//! * Quantizing and packing to lower bit depths.
//!
//! The [`Rgba8`] type is a partial exception to this last item, as that representation
//! is ubiquitous and requires special logic for serializing to maximize compatibility.
//! The [`Rgba8`] and [`PremulRgba8`] types are a partial exception to this last item, as
//! those representation are ubiquitous and requires special logic for serializing to
//! maximize compatibility.
//!
//! Some of these capabilities may be added as other crates within the `color` repository,
//! and we will also facilitate interoperability with other color crates in the Rust
Expand All @@ -62,9 +63,9 @@
//! (likely using your target's libc).
//! - `libm`: Use floating point implementations from [libm][].
//! - `bytemuck`: Implement traits from `bytemuck` on [`AlphaColor`], [`OpaqueColor`],
//! [`PremulColor`], and [`Rgba8`].
//! [`PremulColor`], [`PremulRgba8`], and [`Rgba8`].
//! - `serde`: Implement `serde::Deserialize` and `serde::Serialize` on [`AlphaColor`],
//! [`OpaqueColor`], [`PremulColor`], and [`Rgba8`].
//! [`OpaqueColor`], [`PremulColor`], [`PremulRgba8`], and [`Rgba8`].
//!
//! At least one of `std` and `libm` is required; `std` overrides `libm`.
//!
Expand Down Expand Up @@ -111,10 +112,10 @@ pub use dynamic::{DynamicColor, Interpolator};
pub use gradient::{gradient, GradientIter};
pub use missing::Missing;
pub use parse::{parse_color, ParseError};
pub use rgba8::Rgba8;
pub use rgba8::{PremulRgba8, Rgba8};
pub use tag::ColorSpaceTag;

const fn u8_to_f32(x: u32) -> f32 {
const fn u8_to_f32(x: u8) -> f32 {
x as f32 * (1.0 / 255.0)
}

Expand All @@ -131,12 +132,17 @@ impl AlphaColor<Srgb> {
///
/// Note: for conversion from the [`Rgba8`] type, just use the `From` trait.
pub const fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
let components = [
u8_to_f32(r as u32),
u8_to_f32(g as u32),
u8_to_f32(b as u32),
u8_to_f32(a as u32),
];
let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), u8_to_f32(a)];
Self::new(components)
}
}

impl PremulColor<Srgb> {
/// Create a color from pre-multiplied 8-bit rgba values.
///
/// Note: for conversion from the [`PremulRgba8`] type, just use the `From` trait.
pub const fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), u8_to_f32(a)];
Self::new(components)
}
}
Expand Down
45 changes: 44 additions & 1 deletion color/src/rgba8.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2024 the Color Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::{AlphaColor, Srgb};
use crate::{AlphaColor, PremulColor, Srgb};

/// A packed representation of sRGB colors.
///
Expand All @@ -24,8 +24,51 @@ pub struct Rgba8 {
pub a: u8,
}

impl Rgba8 {
/// Returns the color as a packed value, with `r` as the most significant byte and
/// `a` the least.
#[must_use]
pub const fn to_u32(self) -> u32 {
((self.r as u32) << 24) | ((self.g as u32) << 16) | ((self.b as u32) << 8) | self.a as u32
}
}

impl From<Rgba8> for AlphaColor<Srgb> {
fn from(value: Rgba8) -> Self {
Self::from_rgba8(value.r, value.g, value.b, value.a)
}
}

/// A packed representation of pre-multiplied sRGB colors.
///
/// Encoding sRGB with 8 bits per component is extremely common, as
/// it is efficient and convenient, even if limited in accuracy and
/// gamut.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[repr(C)]
pub struct PremulRgba8 {
/// Red component.
pub r: u8,
/// Green component.
pub g: u8,
/// Blue component.
pub b: u8,
/// Alpha component.
pub a: u8,
}

impl PremulRgba8 {
/// Returns the color as a packed value, with `r` as the most significant byte and
/// `a` the least.
#[must_use]
pub const fn to_u32(self) -> u32 {
((self.r as u32) << 24) | ((self.g as u32) << 16) | ((self.b as u32) << 8) | self.a as u32
}
}

impl From<Rgba8> for PremulColor<Srgb> {
fn from(value: Rgba8) -> Self {
Self::from_rgba8(value.r, value.g, value.b, value.a)
}
}

0 comments on commit 1c9b06f

Please # to comment.