Palette 0.7.2

21 May 2023 — Permalink

Here’s what’s new in Palette since the release of 0.7.0.

For those who aren’t familiar with it, Palette is a Rust library for working with colors, including color conversion and color management. Its features range from common tasks, like color blending or converting HSL to RGB, to more advanced topics. It leverages Rust’s type system, by encoding color space information as type parameters, to be able to prevent mistakes and to keep it open to extension and customization.

Color Difference Metrics

Palette has had the ColorDifference and RelativeContrast traits for quite some time now. The former is an implementation of CIEDE2000 while the latter is the WCAG 2.1 relative contrast metric, for measuring color difference and luminance contrast. To make this more clear, and as part of an effort to improve the color difference traits, the ColorDifference and RelativeContrast traits have been deprecated in favor of the Ciede2000 and Wcag21RelativeContrast traits, respectively.

There are also other ways to calculate color differences that may be more suitable for some applications. Two of them have been added to Palette in 0.7.2:

  • EuclideanDistance: One of the fastest metrics to calculate, but not the most accurate. It treats the colors as points in a Euclidean space and simply calculates the distance between them.
  • HyAb: Another metric that’s similarly fast to compute. It’s a hybrid between Euclidean and Manhattan/Taxidriver distance, that separates luminance from chroma in a way that’s more representative of how they are perceived.

Both of these metrics will give better results in color spaces that are more perceptually uniform, such as Lab, Oklab and Luv, than for example Rgb.

Saturating Addition And Subtraction

Colors with integer components have gotten saturating_add and saturating_sub methods, via the SaturatingAdd and SaturatingSub traits. These prevent overflows by clamping the result of addition and subtraction to the minimum and maximum values of each component.

use palette::{num::SaturatingAdd, Srgb};

fn main() {
    let color1 = Srgb::<u8>::new(200, 20, 20);
    let color2 = Srgb::<u8>::new(100, 10, 10);

    let color_sum = color1.saturating_add(color2);

    // Srgb{255,30,30}
    println!("{:?}", color_sum);
}

Struct of Arrays (SoA)

Some applications need to work with color components/channels separated from each other, rather than interleaved in the same buffer. For example, if you would like to reduce the color and luminance noise in an image, you may get a performance boost from processing the components of Lab in separate buffers. Palette 0.7.2 adds some utilities that help with transforming an array of color structs into a color struct of component arrays (and back). There are also iterators and some Vec methods, such as push and pop.

Here’s an example that shows the idea, but not necessarily in the best performing way:

use palette::{FromColor, IntoColor, Lab, LinSrgb};

fn example_denoise(
    luminance_blur: usize,
    chroma_blur: usize,
    width: usize,
    height: usize,
    pixels: &mut [LinSrgb<f32>],
) {
    // This makes `lab_pixels` have `Vec<f32>`s as `l`, `a` and `b` values.
    let mut lab_pixels: Lab<_, Vec<f32>> = pixels
        .iter()
        .map(|&rgb| Lab::from_color(rgb))
        .collect();

    // Process the `l`, `a` and `b` values separately.
    blur(luminance_blur, width, height, &mut lab_pixels.l);
    blur(chroma_blur, width, height, &mut lab_pixels.a);
    blur(chroma_blur, width, height, &mut lab_pixels.b);

    // Write the new colors back into the `pixels` buffer.
    for (rgb, lab) in pixels.iter_mut().zip(lab_pixels) {
        *rgb = lab.into_color();
    }
}

fn blur(amount: usize, width: usize, height: usize, values: &mut [f32]) {
    // ...
}

As with anything performance related, make sure to benchmark the options for your specific application.

Fixes Since 0.7.0

0.7.1

  • There were implementations of Add, Sub, Mul and Div that allowed PreAlpha to be used on the right hand side with f32 or f64 on the left hand side. This would unfortunately cause sporadic infinite recursion in rustc and has been removed for now.
  • An old issue with how Alpha and PreAlpha was serialized has been resolved. Users of some formats that make a difference between maps and structs were not able to parse the serialized value. The generated (de)serialization code has been replaces with a custom implementation that’s more fitting and able to read its own handwriting.

0.7.2

  • The conversion from Okhsv to Oklab has been fixed to not give NaN values when value is 0 saturation is > 0.

Wrapping Up

I want to give a big thank you to everyone who contributed or took part in discussions. Your help is always appreciated!

Palette can be found on GitHub, and on crates.io.

Thank you for reading!