Palette 0.5.0

17 Nov 2019 — Permalink

Well, here it is. The (by some, at least) long awaited Palette 0.5.0 release. This one has been brewing for quite some time while waiting for the ecosystem to really support one of its main additions1. That addition is #[no_std] support.

First, what is Palette? It’s a Rust library for working with colors and color spaces. It uses the type system to prevent mistakes, like mixing incompatible colors or working with non-linear RGB. It encodes the color spaces and their meta data (such as RGB primaries and white point) into the types to help making color processing less error prone and hopefully more accessible to those who don’t want to dive into the rabbit hole that is colors in computing.

Palette has made an appearance in some really awesome projects, which is fantastic! I’ll take that as a sign of being on the right track, considering they fit the intended target audience.

Before finally going into what has changed in this release, I would just like to send out a big “Thank you!” to the contributors who have dedicated both time and energy to help get all this through. This one is more your release than mine! :heart:

#[no_std] Support

For those who are not familiar with #[no_std], the gist of it is that it allows Rust programs to be compiled without the std library. This is useful on some platforms, such as micro controllers, where some of the features in std can’t be used. Or sometimes just to slim the binary down a bit.

To fully opt out of using std, your dependencies and their dependencies will also have to opt out of using std, which leads us to one of the changes in this release. Palette has been changed to not necessarily rely on std for anything other than:

  • gradients (they have to be thoroughly redesigned first) and
  • dynamically looking up colors by name.

The #[no_std] support has been implemented in a way that makes std a feature flag that is enabled by default. Opting out of default features will also opt out of std, but something has to replace it for floating point math to still work. Currently libm is the only supported alternative.

This is how the Cargo.toml entry for Palette may look when using libm:

[dependencies.palette]
version = "0.5"
default-features = false
features = ["libm"]

and those of you who want to opt out of other features, but still use std, can write:

[dependencies.palette]
version = "0.5"
default-features = false
features = ["std"]

Thanks to a number of Palette contributors, and to the maintainers and contributors to both libm and num_traits (for integrating libm support), this could finally be implemented in a simple and easily maintainable way!

Removing The Color Enum

The Color enum was added as a way to have the computer pick the most suitable color representation for the situation. It would pick a hue based color for hue transformation and a lightness based color for lightness transform. It sounded like a fine idea at first, but it turned out to be both complex and maybe not as useful as it sounds. Especially when multiple RGB types came into the picture.

To mainly reduce the complexity of the library, this type has been removed. If anyone would ever feel the need for such a type, it’s much easier to define one’s own replacement, that solves a particular use case, instead of one that tries (and fails) to solve every use case. All the required traits and types for that is in the Palette crate, or should otherwise be added.

Rounding When Converting To Integers

This is a small, but possibly significant one. Color components were previously always rounded down when, for example, converting from f32 to u8, making it statistically almost impossible to hit pure white (among other subtle effects). Thankfully PeterHatch came along and pointed out my mistake2.

Color components are now always correctly rounded to their nearest integer value. This will affect the output in some situations and make some comparisons fail, but the visual difference should be minimal, if any at all.

Extended Conversion Trait

The way Palette converts between color spaces without clamping them (or even panicking) when the result is out of bounds has come as a bit of a surprise for some.

This default will change to clamping at some point, but until then there are the ConvertFrom and ConvertInto traits that will give better control over the result. They do already clamp by default:

use palette::ConvertInto;
use palette::Limited;
use palette::{Srgb, Lch};

// This one is always within bounds
let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).convert_into();
assert!(rgb.is_valid());

// This one may end up out of bounds
let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).convert_unclamped_into();
assert!(!rgb.is_valid());

// This one gives a `Result` that lets you handle both cases
let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).try_convert_into() {
    Ok(color) => color,
    Err(err) => {
        println!("Color is out of bounds");
        err.color()
    },
};

The future solution for this will be similar, with the same semantics, but most likely divided into three traits. One for each method above.

Other News

Other than what has been mentioned, a few dependencies have been updated, as usual, and assign operators have been implemented. You can now +=, -=, *= and /= your colors!

What’s Next?

Most of the items in the issue tracker are various quality of life improvements. Not surprising, considering color math at this level is pretty much stable and not particularly dramatic. No big projects, other than what is mentioned there, are planned at the moment.

There are also a couple of known problems that were not addressed in this release. Particularly #130, where I would greatly appreciate a hand from someone who knows the Serde best practices.

That’s all for this release. Palette can be found on GitHub (contributions are always appreciated), and on crates.io.

Thank you for reading, and have fun with the new features!

  1. And also a bit because I have been doing other things, but that’s not as interesting. 

  2. I have to admit it took more time than it should have before I finally realized where my thought process was wrong. It’s way too easy to get stuck in some idea…