Skip to main content

color/
lib.rs

1// Copyright 2024 the Color Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Color is a Rust crate which implements color space conversions, targeting at least
5//! [CSS Color Level 4].
6//!
7//! ## Main types
8//!
9//! The crate has two approaches to representing color in the Rust type system: a set of
10//! types with static color space as part of the types, and [`DynamicColor`]
11//! in which the color space is represented at runtime.
12//!
13//! The static color types come in three variants: [`OpaqueColor`] without an
14//! alpha channel, [`AlphaColor`] with a separate alpha channel, and [`PremulColor`] with
15//! premultiplied alpha. The last type is particularly useful for making interpolation and
16//! compositing more efficient. These have a marker type parameter, indicating which
17//! [`ColorSpace`] they are in. Conversion to another color space uses the `convert` method
18//! on each of these types. The static types are open-ended, as it's possible to implement
19//! this trait for new color spaces.
20//!
21//! ## Scope and goals
22//!
23//! Color in its entirety is an extremely deep and complex topic. It is completely impractical
24//! for a single crate to meet all color needs. The goal of this one is to strike a balance,
25//! providing color capabilities while also keeping things simple and efficient.
26//!
27//! The main purpose of this crate is to provide a good set of types for representing colors,
28//! along with conversions between them and basic manipulations, especially interpolation. A
29//! major inspiration is the [CSS Color Level 4] draft spec; we implement most of the operations
30//! and strive for correctness.
31//!
32//! A primary use case is rendering, including color conversions and methods for preparing
33//! gradients. The crate should also be suitable for document authoring and editing, as it
34//! contains methods for parsing and serializing colors with CSS Color 4 compatible syntax.
35//!
36//! Simplifications include:
37//!   * Always using `f32` to represent component values.
38//!   * Only handling 3-component color spaces (plus optional alpha).
39//!   * Choosing a fixed, curated set of color spaces for dynamic color types.
40//!   * Choosing linear sRGB as the central color space.
41//!   * Keeping white point implicit in the general conversion operations.
42//!
43//! A number of other tasks are out of scope for this crate:
44//!   * Print color spaces (CMYK).
45//!   * Spectral colors.
46//!   * Color spaces with more than 3 components generally.
47//!   * [ICC] color profiles.
48//!   * [ACES] color transforms.
49//!   * Appearance models and other color science not needed for rendering.
50//!   * Quantizing and packing to lower bit depths.
51//!
52//! The [`Rgba8`] and [`PremulRgba8`] types are a partial exception to this last item, as
53//! those representation are ubiquitous and requires special logic for serializing to
54//! maximize compatibility.
55//!
56//! Some of these capabilities may be added as other crates within the `color` repository,
57//! and we will also facilitate interoperability with other color crates in the Rust
58//! ecosystem as needed.
59//!
60//! ## Features
61//!
62//! - `std` (enabled by default): Get floating point functions from the standard library
63//!   (likely using your target's libc).
64//! - `libm`: Use floating point implementations from [libm][].
65//! - `bytemuck`: Implement traits from `bytemuck` on [`AlphaColor`], [`ColorSpaceTag`],
66//!   [`HueDirection`], [`OpaqueColor`], [`PremulColor`], [`PremulRgba8`], and [`Rgba8`].
67//! - `serde`: Implement `serde::Deserialize` and `serde::Serialize` on [`AlphaColor`],
68//!   [`DynamicColor`], [`OpaqueColor`], [`PremulColor`], [`PremulRgba8`], and [`Rgba8`].
69//!
70//! At least one of `std` and `libm` is required; `std` overrides `libm`.
71//!
72//! [CSS Color Level 4]: https://www.w3.org/TR/css-color-4/
73//! [ICC]: https://color.org/
74//! [ACES]: https://acescentral.com/
75#![cfg_attr(feature = "libm", doc = "[libm]: libm")]
76#![cfg_attr(not(feature = "libm"), doc = "[libm]: https://crates.io/crates/libm")]
77// LINEBENDER LINT SET - lib.rs - v4
78// See https://linebender.org/wiki/canonical-lints/
79// These lints shouldn't apply to examples or tests.
80#![cfg_attr(not(test), warn(unused_crate_dependencies))]
81// These lints shouldn't apply to examples.
82#![warn(clippy::print_stdout, clippy::print_stderr)]
83// Targeting e.g. 32-bit means structs containing usize can give false positives for 64-bit.
84#![cfg_attr(target_pointer_width = "64", warn(clippy::trivially_copy_pass_by_ref))]
85// END LINEBENDER LINT SET
86#![cfg_attr(docsrs, feature(doc_cfg))]
87#![no_std]
88
89pub mod cache_key;
90mod chromaticity;
91mod color;
92mod colorspace;
93mod dynamic;
94mod flags;
95mod gradient;
96pub mod palette;
97mod rgba8;
98mod serialize;
99mod tag;
100mod x11_colors;
101
102// Note: this may become feature-gated; we'll decide this soon
103// (This line is isolated so that the comment binds to it with import ordering)
104mod parse;
105
106#[cfg(feature = "bytemuck")]
107mod impl_bytemuck;
108
109#[cfg(all(not(feature = "std"), not(test)))]
110mod floatfuncs;
111
112pub use chromaticity::Chromaticity;
113pub use color::{AlphaColor, HueDirection, OpaqueColor, PremulColor};
114pub use colorspace::{
115    A98Rgb, Aces2065_1, AcesCg, ColorSpace, ColorSpaceLayout, DisplayP3, Hsl, Hwb, Lab, Lch,
116    LinearSrgb, Oklab, Oklch, ProphotoRgb, Rec2020, Srgb, XyzD50, XyzD65,
117};
118pub use dynamic::{DynamicColor, Interpolator, UnpremultipliedInterpolator};
119pub use flags::{Flags, Missing};
120pub use gradient::{gradient, gradient_unpremultiplied, GradientIter, UnpremultipliedGradientIter};
121pub use parse::{parse_color, parse_color_prefix, ParseError};
122pub use rgba8::{PremulRgba8, Rgba8};
123pub use tag::ColorSpaceTag;
124
125const fn u8_to_f32(x: u8) -> f32 {
126    x as f32 * (1.0 / 255.0)
127}
128
129/// Multiplication `m * x` of a 3x3-matrix `m` and a 3-vector `x`.
130const fn matvecmul(m: &[[f32; 3]; 3], x: [f32; 3]) -> [f32; 3] {
131    [
132        m[0][0] * x[0] + m[0][1] * x[1] + m[0][2] * x[2],
133        m[1][0] * x[0] + m[1][1] * x[1] + m[1][2] * x[2],
134        m[2][0] * x[0] + m[2][1] * x[1] + m[2][2] * x[2],
135    ]
136}
137
138/// Multiplication `ma * mb` of two 3x3-matrices `ma` and `mb`.
139const fn matmatmul(ma: &[[f32; 3]; 3], mb: &[[f32; 3]; 3]) -> [[f32; 3]; 3] {
140    [
141        [
142            ma[0][0] * mb[0][0] + ma[0][1] * mb[1][0] + ma[0][2] * mb[2][0],
143            ma[0][0] * mb[0][1] + ma[0][1] * mb[1][1] + ma[0][2] * mb[2][1],
144            ma[0][0] * mb[0][2] + ma[0][1] * mb[1][2] + ma[0][2] * mb[2][2],
145        ],
146        [
147            ma[1][0] * mb[0][0] + ma[1][1] * mb[1][0] + ma[1][2] * mb[2][0],
148            ma[1][0] * mb[0][1] + ma[1][1] * mb[1][1] + ma[1][2] * mb[2][1],
149            ma[1][0] * mb[0][2] + ma[1][1] * mb[1][2] + ma[1][2] * mb[2][2],
150        ],
151        [
152            ma[2][0] * mb[0][0] + ma[2][1] * mb[1][0] + ma[2][2] * mb[2][0],
153            ma[2][0] * mb[0][1] + ma[2][1] * mb[1][1] + ma[2][2] * mb[2][1],
154            ma[2][0] * mb[0][2] + ma[2][1] * mb[1][2] + ma[2][2] * mb[2][2],
155        ],
156    ]
157}
158
159/// Multiplication `ma * mb` of a 3x3-matrix `ma` by a 3x3-diagonal matrix `mb`.
160///
161/// Diagonal matrix `mb` is given by
162///
163/// ```text
164/// [ mb[0] 0     0     ]
165/// [ 0     mb[1] 0     ]
166/// [ 0     0     mb[2] ]
167/// ```
168const fn matdiagmatmul(ma: &[[f32; 3]; 3], mb: [f32; 3]) -> [[f32; 3]; 3] {
169    [
170        [ma[0][0] * mb[0], ma[0][1] * mb[1], ma[0][2] * mb[2]],
171        [ma[1][0] * mb[0], ma[1][1] * mb[1], ma[1][2] * mb[2]],
172        [ma[2][0] * mb[0], ma[2][1] * mb[1], ma[2][2] * mb[2]],
173    ]
174}
175
176impl AlphaColor<Srgb> {
177    /// Create a color from 8-bit rgba values.
178    ///
179    /// Note: for conversion from the [`Rgba8`] type, just use the `From` trait.
180    pub const fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
181        let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), u8_to_f32(a)];
182        Self::new(components)
183    }
184
185    /// Create a color from 8-bit rgb values with an opaque alpha.
186    ///
187    /// Note: for conversion from the [`Rgba8`] type, just use the `From` trait.
188    pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Self {
189        let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), 1.];
190        Self::new(components)
191    }
192}
193
194impl OpaqueColor<Srgb> {
195    /// Create a color from 8-bit rgb values.
196    pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Self {
197        let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b)];
198        Self::new(components)
199    }
200}
201
202impl PremulColor<Srgb> {
203    /// Create a color from pre-multiplied 8-bit rgba values.
204    ///
205    /// Note: for conversion from the [`PremulRgba8`] type, just use the `From` trait.
206    pub const fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
207        let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), u8_to_f32(a)];
208        Self::new(components)
209    }
210
211    /// Create a color from 8-bit rgb values with an opaque alpha.
212    ///
213    /// Note: for conversion from the [`Rgba8`] type, just use the `From` trait.
214    pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Self {
215        let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), 1.];
216        Self::new(components)
217    }
218}
219
220// Keep clippy from complaining about unused libm in nostd test case.
221#[cfg(feature = "libm")]
222#[expect(unused, reason = "keep clippy happy")]
223fn ensure_libm_dependency_used() -> f32 {
224    libm::sqrtf(4_f32)
225}