Skip to content

Commit

Permalink
Merge #338
Browse files Browse the repository at this point in the history
338: Add traits for casting collections of colors to and from other data types r=Ogeon a=Ogeon

These traits are alternatives to the free functions in `cast`. The idea is to have less "type information" in the naming and only focus on the high level transform. In essence what `From` and `Into` also does. For example, with these changes:

```rust
let raw = &[0xFF7F0080u32, 0xFF60BBCC];

// Before
use palette::{rgb::PackedArgb, cast};
let colors = cast::from_uint_slice::<PackedArgb>(raw);

// After
use palette::{rgb::PackedArgb, cast::UintsInto};
let colors: &[PackedArgb] = raw.uints_into();
``` 

Casting from owned to slice is limited to a fixed set of owning types, since it's hard to cover everything that resembles a slice. Arrays and slices don't implement `Deref<Target=[T]>`, for example, while `AsRef<[T]>` doesn't work for the `Self` type...

In addition to that, I also upgraded some actions in the workflows and added Miri checks to CI. It's good to keep an eye on those transmutes and pointer casts and even better to do it automatically. I have only been running it manually so far.

Co-authored-by: Erik Hedvall <erikwhedvall@gmail.com>
  • Loading branch information
bors[bot] and Ogeon committed Jul 16, 2023
2 parents 4fe46b9 + 889578a commit 9aeb8aa
Show file tree
Hide file tree
Showing 20 changed files with 2,052 additions and 97 deletions.
15 changes: 7 additions & 8 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
on: [pull_request]
on:
pull_request:
paths:
- '**.rs'
name: Benchmark pull requests
jobs:
runBenchmark:
name: Run benchmark
runs-on: windows-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.4
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions-rs/toolchain@v1.0.7
with:
toolchain: stable
override: true
profile: minimal
- uses: dtolnay/rust-toolchain@stable
- uses: boa-dev/criterion-compare-action@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
44 changes: 20 additions & 24 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,42 +21,37 @@ jobs:
env:
RUSTFLAGS: -D warnings
steps:
- uses: actions/checkout@v2.3.4
- uses: actions-rs/toolchain@v1.0.7
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}
override: true
target: thumbv6m-none-eabi
profile: minimal
targets: thumbv6m-none-eabi
- name: Minimal check
uses: actions-rs/cargo@v1
with:
command: check
args: -v -p palette --no-default-features --features std
run: cargo check -v -p palette --no-default-features --features std
- name: find-crate check
uses: actions-rs/cargo@v1
with:
command: check
args: -v -p palette --no-default-features --features "std find-crate"
run: cargo check -v -p palette --no-default-features --features "std find-crate"
- name: Default check
uses: actions-rs/cargo@v1
with:
command: check
args: -v --workspace --exclude no_std_test
- uses: actions-rs/cargo@v1
with:
command: test
args: -v
run: cargo check -v --workspace --exclude no_std_test
- run: cargo test -v
- name: Test features
shell: bash
working-directory: palette
run: bash ../scripts/test_features.sh
- name: "Test #[no_std]"
if: ${{ runner.os == 'Linux' && matrix.toolchain == 'nightly' }}
uses: actions-rs/cargo@v1
run: cargo build -v --package no_std_test --features nightly --target thumbv6m-none-eabi
miri:
name: Miri tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
with:
command: build
args: -v --package no_std_test --features nightly --target thumbv6m-none-eabi
components: miri
- name: Unit tests
run: cargo miri test --lib --features "bytemuck" -- -Z unstable-options --report-time
- name: Documentation tests
run: cargo miri test --doc --features "bytemuck" -- -Z unstable-options --report-time

# Refs: https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149
#
Expand All @@ -67,6 +62,7 @@ jobs:
if: success()
needs:
- compile_and_test
- miri
runs-on: ubuntu-latest
steps:
- name: Mark the job as a success
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/prepare_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
name: Update version and release notes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.4
- uses: actions/checkout@v3
with:
fetch-depth: 0 # We want the full history and all tags
- name: Increment version for palette_derive
Expand Down
13 changes: 3 additions & 10 deletions .github/workflows/publish_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,10 @@ jobs:
name: Publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.4
- uses: actions-rs/toolchain@v1.0.7
with:
toolchain: stable
override: true
profile: minimal
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- name: Generate
uses: actions-rs/cargo@v1
with:
command: doc
args: --package palette --no-deps
run: cargo doc --package palette --no-deps
- name: Upload
uses: JamesIves/github-pages-deploy-action@4.1.4
with:
Expand Down
12 changes: 6 additions & 6 deletions palette/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,16 @@ A longer and more advanced example that shows how to implement the conversion tr

### Pixels And Buffers

When working with image or pixel buffers, or any color type that can be converted to a slice of components (ex. `&[u8]`), the `cast` module provides functions for turning them into slices of Palette colors without cloning the whole buffer:
When working with image or pixel buffers, or any color type that can be converted to a slice of components (ex. `&[u8]`), the `cast` module provides traits and functions for turning them into slices of Palette colors without cloning the whole buffer:

```rust
use palette::{cast, Srgb};
use palette::{cast::ComponentsInto, Srgb};

// The input to this function could be data from an image file or
// maybe a texture in a game.
fn swap_red_and_blue(my_rgb_image: &mut [u8]) {
// Convert `my_rgb_image` into `&mut [Srgb<u8>]` without copying.
let my_rgb_image: &mut [Srgb<u8>] = cast::from_component_slice_mut(my_rgb_image);
let my_rgb_image: &mut [Srgb<u8>] = my_rgb_image.components_into();

for color in my_rgb_image {
std::mem::swap(&mut color.red, &mut color.blue);
Expand Down Expand Up @@ -152,13 +152,13 @@ This image shows the transition from the color to `new_color` in HSL and HSV:
In addition to the operator traits, the SVG blend and composition functions have also been implemented.

```rust
use palette::{blend::Compose, cast, Srgb, WithAlpha};
use palette::{blend::Compose, cast::ComponentsInto, Srgb, WithAlpha};

// The input to this function could be data from image files.
fn alpha_blend_images(image1: &mut [u8], image2: &[u8]) {
// Convert the images into `&mut [Srgb<u8>]` and `&[Srgb<u8>]` without copying.
let image1: &mut [Srgb<u8>] = cast::from_component_slice_mut(image1);
let image2: &[Srgb<u8>] = cast::from_component_slice(image2);
let image1: &mut [Srgb<u8>] = image1.components_into();
let image2: &[Srgb<u8>] = image2.components_into();

for (color1, color2) in image1.iter_mut().zip(image2) {
// Convert the colors to linear floating point format and give them transparency values.
Expand Down
10 changes: 5 additions & 5 deletions palette/examples/readme_examples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ fn converting() {
}

fn pixels_and_buffers() {
use palette::{cast, Srgb};
use palette::{cast::ComponentsInto, Srgb};

// The input to this function could be data from an image file or
// maybe a texture in a game.
fn swap_red_and_blue(my_rgb_image: &mut [u8]) {
// Convert `my_rgb_image` into `&mut [Srgb<u8>]` without copying.
let my_rgb_image: &mut [Srgb<u8>] = cast::from_component_slice_mut(my_rgb_image);
let my_rgb_image: &mut [Srgb<u8>] = my_rgb_image.components_into();

for color in my_rgb_image {
std::mem::swap(&mut color.red, &mut color.blue);
Expand Down Expand Up @@ -91,13 +91,13 @@ fn color_operations_1() {
}

fn color_operations_2() {
use palette::{blend::Compose, cast, Srgb, WithAlpha};
use palette::{blend::Compose, cast::ComponentsInto, Srgb, WithAlpha};

// The input to this function could be data from image files.
fn alpha_blend_images(image1: &mut [u8], image2: &[u8]) {
// Convert the images into `&mut [Srgb<u8>]` and `&[Srgb<u8>]` without copying.
let image1: &mut [Srgb<u8>] = cast::from_component_slice_mut(image1);
let image2: &[Srgb<u8>] = cast::from_component_slice(image2);
let image1: &mut [Srgb<u8>] = image1.components_into();
let image2: &[Srgb<u8>] = image2.components_into();

for (color1, color2) in image1.iter_mut().zip(image2) {
// Convert the colors to linear floating point format and give them transparency values.
Expand Down
4 changes: 2 additions & 2 deletions palette/examples/struct_of_arrays.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use palette::{cast, color_difference::EuclideanDistance, IntoColor, Oklab, Srgb};
use palette::{cast::ComponentsInto, color_difference::EuclideanDistance, IntoColor, Oklab, Srgb};

fn main() {
let image = image::open("example-data/input/fruits.png")
.expect("could not open 'example-data/input/fruits.png'")
.to_rgb8();

let image = cast::from_component_slice::<Srgb<u8>>(image.as_raw());
let image: &[Srgb<u8>] = image.as_raw().components_into();

// Convert and collect the colors in a struct-of-arrays (SoA) format, where
// each component is a Vec of all the pixels' component values.
Expand Down
69 changes: 44 additions & 25 deletions palette/src/cast.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
//! Traits and functions for casting colors to and from other data types.
//!
//! The functions in this module casts without changing the underlying data. See
//! the [`convert`](crate::convert) module for how to convert between color
//! spaces.
//! The functions and traits in this module cast without changing the underlying
//! data. See the [`convert`](crate::convert) module for how to convert between
//! color spaces.
//!
//! # Traits or Functions?
//!
//! This module provides both a set of traits ([`FromComponents`],
//! [`IntoUints`], etc.) and a set of functions ([`from_component_slice`],
//! [`into_uint_array`], etc.) that effectively implement the same
//! functionality. The traits are all implemented using the functions, so they
//! can be seen as bits of more implicit syntax sugar for the more explicit
//! functions.
//!
//! A general recommendation is to use the traits, since they provide mostly
//! better ergonomics.
//!
//! # Arrays and Slices
//!
//! Types that implement [`ArrayCast`] can be cast to and from arrays and slices
//! with little to no overhead. This makes it easy to work with image buffers
//! and types from other crates without having to copy the data first.
//!
//! Casting can be either be done with the free functions in this module, or
//! using the helper traits.
//!
//! ## Casting Arrays
//!
//! Arrays can be type checked to have the correct size at compile time, making
Expand All @@ -18,12 +33,13 @@
//! same after casting.
//!
//! ```
//! use palette::{cast, Srgb, IntoColor};
//! use palette::{cast::{self, ArraysInto}, Srgb, IntoColor};
//!
//! let color = cast::from_array::<Srgb<u8>>([23u8, 198, 76]).into_linear();
//! let color = cast::from_array::<Srgb<u8>>([23, 198, 76]).into_linear();
//! // Note: `Srgb::<u8>::from([23, 198, 76])` works too.
//!
//! let buffer = &mut [[64u8, 139, 10], [93, 18, 214]];
//! let color_buffer = cast::from_array_slice_mut::<Srgb<u8>>(buffer);
//! let buffer = &mut [[64, 139, 10], [93, 18, 214]];
//! let color_buffer: &mut [Srgb<u8>] = buffer.arrays_into();
//!
//! for destination in color_buffer {
//! let linear_dst = destination.into_linear::<f32>();
Expand All @@ -36,7 +52,7 @@
//! ```compile_fail
//! use palette::{cast, Srgb};
//!
//! let color = cast::from_array::<Srgb<u8>>([23u8, 198]); // Too few components.
//! let color = cast::from_array::<Srgb<u8>>([23, 198]); // Too few components.
//! ```
//!
//! ## Casting Component Buffers
Expand All @@ -52,34 +68,35 @@
//! or multiplying the length.
//!
//! ```
//! use palette::{cast, Srgb};
//! use palette::{cast::{self, TryFromComponents}, Srgb};
//!
//! let correct_buffer = &[64u8, 139, 10, 93, 18, 214];
//! assert!(cast::try_from_component_slice::<Srgb<u8>>(correct_buffer).is_ok());
//! let correct_buffer = &[64, 139, 10, 93, 18, 214];
//! assert!(<&[Srgb<u8>]>::try_from_components(correct_buffer).is_ok());
//!
//! let incorrect_buffer = &[64u8, 139, 10, 93, 18, 214, 198, 76];
//! assert!(cast::try_from_component_slice::<Srgb<u8>>(incorrect_buffer).is_err());
//! let incorrect_buffer = &[64, 139, 10, 93, 18, 214, 198, 76];
//! assert!(<&[Srgb<u8>]>::try_from_components(incorrect_buffer).is_err());
//! ```
//!
//! An alternative, for when the length can be trusted to be correct, is to use
//! the `from_component_*` functions that panic on error.
//! the `ComponentsInto::components_into` and `FromComponents::from_components`
//! methods, or the `from_component_*` functions, that panic on error.
//!
//! This works:
//!
//! ```
//! use palette::{cast, Srgb};
//! use palette::{cast::ComponentsInto, Srgb};
//!
//! let correct_buffer = &[64u8, 139, 10, 93, 18, 214];
//! let color_buffer = cast::from_component_slice::<Srgb<u8>>(correct_buffer);
//! let correct_buffer = &[64, 139, 10, 93, 18, 214];
//! let color_buffer: &[Srgb<u8>] = correct_buffer.components_into();
//! ```
//!
//! But this panics:
//!
//! ```should_panic
//! use palette::{cast, Srgb};
//! use palette::{cast::ComponentsInto, Srgb};
//!
//! let incorrect_buffer = &[64u8, 139, 10, 93, 18, 214, 198, 76];
//! let color_buffer = cast::from_component_slice::<Srgb<u8>>(incorrect_buffer);
//! let incorrect_buffer = &[64, 139, 10, 93, 18, 214, 198, 76];
//! let color_buffer: &[Srgb<u8>] = incorrect_buffer.components_into();
//! ```
//!
//! ## Casting Single Colors
Expand Down Expand Up @@ -111,10 +128,10 @@
//!
//! ```
//! // `PackedArgb` is an alias for `Packed<rgb::channels::Argb, P = u32>`.
//! use palette::{rgb::PackedArgb, cast, Srgba};
//! use palette::{rgb::PackedArgb, cast::ComponentsInto, Srgba};
//!
//! let components = &[1.0f32, 0.8, 0.2, 0.3, 1.0, 0.5, 0.7, 0.6];
//! let colors = cast::from_component_slice::<PackedArgb<_>>(components);
//! let colors: &[PackedArgb<_>] = components.components_into();
//!
//! // Notice how the alpha values have moved from the beginning to the end:
//! assert_eq!(Srgba::from(colors[0]), Srgba::new(0.8, 0.2, 0.3, 1.0));
Expand All @@ -135,18 +152,20 @@
//!
//! ```
//! // `PackedArgb` is an alias for `Packed<rgb::channels::Argb, P = u32>`.
//! use palette::{rgb::PackedArgb, cast, Srgba};
//! use palette::{rgb::PackedArgb, cast::UintsInto, Srgba};
//!
//! let raw = &[0xFF7F0080u32, 0xFF60BBCC];
//! let colors = cast::from_uint_slice::<PackedArgb>(raw);
//! let colors: &[PackedArgb] = raw.uints_into();
//!
//! assert_eq!(colors.len(), 2);
//! assert_eq!(Srgba::from(colors[0]), Srgba::new(0x7F, 0x00, 0x80, 0xFF));
//! assert_eq!(Srgba::from(colors[1]), Srgba::new(0x60, 0xBB, 0xCC, 0xFF));
//! ```

mod array;
mod array_traits;
mod packed;
mod uint;
mod uint_traits;

pub use self::{array::*, packed::*, uint::*};
pub use self::{array::*, array_traits::*, packed::*, uint::*, uint_traits::*};
Loading

0 comments on commit 9aeb8aa

Please sign in to comment.