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 - v1
78// See https://linebender.org/wiki/canonical-lints/
79// These lints aren't included in Cargo.toml because they
80// shouldn't apply to examples and tests
81#![warn(unused_crate_dependencies)]
82#![warn(clippy::print_stdout, clippy::print_stderr)]
83// END LINEBENDER LINT SET
84#![cfg_attr(docsrs, feature(doc_auto_cfg))]
85#![no_std]
86
87pub mod cache_key;
88mod chromaticity;
89mod color;
90mod colorspace;
91mod dynamic;
92mod flags;
93mod gradient;
94pub mod palette;
95mod rgba8;
96mod serialize;
97mod tag;
98mod x11_colors;
99
100// Note: this may become feature-gated; we'll decide this soon
101// (This line is isolated so that the comment binds to it with import ordering)
102mod parse;
103
104#[cfg(feature = "bytemuck")]
105mod impl_bytemuck;
106
107#[cfg(all(not(feature = "std"), not(test)))]
108mod floatfuncs;
109
110pub use chromaticity::Chromaticity;
111pub use color::{AlphaColor, HueDirection, OpaqueColor, PremulColor};
112pub use colorspace::{
113    A98Rgb, Aces2065_1, AcesCg, ColorSpace, ColorSpaceLayout, DisplayP3, Hsl, Hwb, Lab, Lch,
114    LinearSrgb, Oklab, Oklch, ProphotoRgb, Rec2020, Srgb, XyzD50, XyzD65,
115};
116pub use dynamic::{DynamicColor, Interpolator, UnpremultipliedInterpolator};
117pub use flags::{Flags, Missing};
118pub use gradient::{gradient, gradient_unpremultiplied, GradientIter, UnpremultipliedGradientIter};
119pub use parse::{parse_color, parse_color_prefix, ParseError};
120pub use rgba8::{PremulRgba8, Rgba8};
121pub use tag::ColorSpaceTag;
122
123const fn u8_to_f32(x: u8) -> f32 {
124    x as f32 * (1.0 / 255.0)
125}
126
127/// Multiplication `m * x` of a 3x3-matrix `m` and a 3-vector `x`.
128const fn matvecmul(m: &[[f32; 3]; 3], x: [f32; 3]) -> [f32; 3] {
129    [
130        m[0][0] * x[0] + m[0][1] * x[1] + m[0][2] * x[2],
131        m[1][0] * x[0] + m[1][1] * x[1] + m[1][2] * x[2],
132        m[2][0] * x[0] + m[2][1] * x[1] + m[2][2] * x[2],
133    ]
134}
135
136/// Multiplication `ma * mb` of two 3x3-matrices `ma` and `mb`.
137const fn matmatmul(ma: &[[f32; 3]; 3], mb: &[[f32; 3]; 3]) -> [[f32; 3]; 3] {
138    [
139        [
140            ma[0][0] * mb[0][0] + ma[0][1] * mb[1][0] + ma[0][2] * mb[2][0],
141            ma[0][0] * mb[0][1] + ma[0][1] * mb[1][1] + ma[0][2] * mb[2][1],
142            ma[0][0] * mb[0][2] + ma[0][1] * mb[1][2] + ma[0][2] * mb[2][2],
143        ],
144        [
145            ma[1][0] * mb[0][0] + ma[1][1] * mb[1][0] + ma[1][2] * mb[2][0],
146            ma[1][0] * mb[0][1] + ma[1][1] * mb[1][1] + ma[1][2] * mb[2][1],
147            ma[1][0] * mb[0][2] + ma[1][1] * mb[1][2] + ma[1][2] * mb[2][2],
148        ],
149        [
150            ma[2][0] * mb[0][0] + ma[2][1] * mb[1][0] + ma[2][2] * mb[2][0],
151            ma[2][0] * mb[0][1] + ma[2][1] * mb[1][1] + ma[2][2] * mb[2][1],
152            ma[2][0] * mb[0][2] + ma[2][1] * mb[1][2] + ma[2][2] * mb[2][2],
153        ],
154    ]
155}
156
157/// Multiplication `ma * mb` of a 3x3-matrix `ma` by a 3x3-diagonal matrix `mb`.
158///
159/// Diagonal matrix `mb` is given by
160///
161/// ```text
162/// [ mb[0] 0     0     ]
163/// [ 0     mb[1] 0     ]
164/// [ 0     0     mb[2] ]
165/// ```
166const fn matdiagmatmul(ma: &[[f32; 3]; 3], mb: [f32; 3]) -> [[f32; 3]; 3] {
167    [
168        [ma[0][0] * mb[0], ma[0][1] * mb[1], ma[0][2] * mb[2]],
169        [ma[1][0] * mb[0], ma[1][1] * mb[1], ma[1][2] * mb[2]],
170        [ma[2][0] * mb[0], ma[2][1] * mb[1], ma[2][2] * mb[2]],
171    ]
172}
173
174impl AlphaColor<Srgb> {
175    /// Create a color from 8-bit rgba values.
176    ///
177    /// Note: for conversion from the [`Rgba8`] type, just use the `From` trait.
178    pub const fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
179        let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), u8_to_f32(a)];
180        Self::new(components)
181    }
182
183    /// Create a color from 8-bit rgb values with an opaque alpha.
184    ///
185    /// Note: for conversion from the [`Rgba8`] type, just use the `From` trait.
186    pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Self {
187        let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), 1.];
188        Self::new(components)
189    }
190}
191
192impl OpaqueColor<Srgb> {
193    /// Create a color from 8-bit rgb values.
194    pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Self {
195        let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b)];
196        Self::new(components)
197    }
198}
199
200impl PremulColor<Srgb> {
201    /// Create a color from pre-multiplied 8-bit rgba values.
202    ///
203    /// Note: for conversion from the [`PremulRgba8`] type, just use the `From` trait.
204    pub const fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
205        let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), u8_to_f32(a)];
206        Self::new(components)
207    }
208
209    /// Create a color from 8-bit rgb values with an opaque alpha.
210    ///
211    /// Note: for conversion from the [`Rgba8`] type, just use the `From` trait.
212    pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Self {
213        let components = [u8_to_f32(r), u8_to_f32(g), u8_to_f32(b), 1.];
214        Self::new(components)
215    }
216}
217
218// Keep clippy from complaining about unused libm in nostd test case.
219#[cfg(feature = "libm")]
220#[expect(unused, reason = "keep clippy happy")]
221fn ensure_libm_dependency_used() -> f32 {
222    libm::sqrtf(4_f32)
223}