Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add ACEScg color space #54

Merged
merged 15 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
# source control.
Raph Levien
Tom Churchman
Jordan Johnson
2 changes: 1 addition & 1 deletion color/.clippy.toml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
doc-valid-idents = ["ProPhoto", ".."]
doc-valid-idents = ["ACEScg", "ACEScc", "ACEScct", "ProPhoto", ".."]
54 changes: 54 additions & 0 deletions color/src/colorspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,60 @@ impl ColorSpace for Rec2020 {
}
}

/// 🌌 The ACEScg color space.
///
/// This color space is defined by the Academy Color Encoding System [specification][acescg].
///
/// The ACEScg color space is a linear color space. The wide gamut makes this color space useful as
/// a working space for computer graphics.
///
/// Other ACES color spaces include ACES2065-1, ACEScc, and ACEScct. This crate currently does not
/// support these color spaces.
///
/// The ACEScg components are `[R, G, B]`. The components are bounded to `[-65504.0, 65504.0]`,
/// though it is unusual to clip in this color space.
///
/// The conversion between D65 and the ACES whitepoint is done with the standard Bradford linear
/// chromatic adaptation transform.
///
/// [acescg]: https://docs.acescentral.com/specifications/acescg/
#[derive(Clone, Copy, Debug)]
pub struct AcesCg;

impl ColorSpace for AcesCg {
const IS_LINEAR: bool = true;

const TAG: Option<ColorSpaceTag> = Some(ColorSpaceTag::AcesCg);

fn to_linear_srgb(src: [f32; 3]) -> [f32; 3] {
// XYZ_to_lin_sRGB * ACESwp_to_D65 * ACEScg_to_XYZ
const ACESCG_TO_LINEAR_SRGB: [[f32; 3]; 3] = [
[1.705_051, -0.621_792_12, -0.083_258_87],
[-0.130_256_42, 1.140_804_8, -0.010_548_32],
[-0.024_003_36, -0.128_968_98, 1.152_972_3],
];
matmul(&ACESCG_TO_LINEAR_SRGB, src)
}

fn from_linear_srgb(src: [f32; 3]) -> [f32; 3] {
// XYZ_to_ACEScg * D65_to_ACESwp * lin_sRGB_to_XYZ
const LINEAR_SRGB_TO_ACESCG: [[f32; 3]; 3] = [
[0.613_097_4, 0.339_523_15, 0.047379_45],
[0.070_193_72, 0.916_353_9, 0.013_452_4],
[0.020_615_59, 0.109_569_77, 0.869814_63],
];
matmul(&LINEAR_SRGB_TO_ACESCG, src)
}

fn clip([r, g, b]: [f32; 3]) -> [f32; 3] {
[
r.clamp(-65504., 65504.),
g.clamp(-65504., 65504.),
b.clamp(-65504., 65504.),
]
}
}

/// 🌌 The CIE XYZ color space with a 2° observer and a reference white of D50.
///
/// Its components are `[X, Y, Z]`. The components are unbounded, but are usually positive.
Expand Down
4 changes: 2 additions & 2 deletions color/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ mod floatfuncs;

pub use color::{AlphaColor, HueDirection, OpaqueColor, PremulColor};
pub use colorspace::{
A98Rgb, ColorSpace, ColorSpaceLayout, DisplayP3, Hsl, Hwb, Lab, Lch, LinearSrgb, Oklab, Oklch,
ProphotoRgb, Rec2020, Srgb, XyzD50, XyzD65,
A98Rgb, AcesCg, ColorSpace, ColorSpaceLayout, DisplayP3, Hsl, Hwb, Lab, Lch, LinearSrgb, Oklab,
Oklch, ProphotoRgb, Rec2020, Srgb, XyzD50, XyzD65,
};
pub use dynamic::{DynamicColor, Interpolator};
pub use gradient::{gradient, GradientIter};
Expand Down
1 change: 1 addition & 0 deletions color/src/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ impl core::fmt::Display for DynamicColor {
ColorSpaceTag::A98Rgb => write_color_function(self, "a98-rgb", f),
ColorSpaceTag::ProphotoRgb => write_color_function(self, "prophoto-rgb", f),
ColorSpaceTag::Rec2020 => write_color_function(self, "rec2020", f),
ColorSpaceTag::AcesCg => write_color_function(self, "acescg", f),
ColorSpaceTag::Hsl => write_legacy_function(self, "hsl", 1.0, f),
ColorSpaceTag::Hwb => write_modern_function(self, "hwb", f),
ColorSpaceTag::XyzD50 => write_color_function(self, "xyz-d50", f),
Expand Down
27 changes: 23 additions & 4 deletions color/src/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
//! The color space tag enum.

use crate::{
A98Rgb, ColorSpace, ColorSpaceLayout, DisplayP3, Hsl, Hwb, Lab, Lch, LinearSrgb, Missing,
Oklab, Oklch, ProphotoRgb, Rec2020, Srgb, XyzD50, XyzD65,
A98Rgb, AcesCg, ColorSpace, ColorSpaceLayout, DisplayP3, Hsl, Hwb, Lab, Lch, LinearSrgb,
Missing, Oklab, Oklch, ProphotoRgb, Rec2020, Srgb, XyzD50, XyzD65,
};

/// The color space tag for dynamic colors.
Expand Down Expand Up @@ -44,6 +44,8 @@ pub enum ColorSpaceTag {
ProphotoRgb,
/// The [`Rec2020`] color space.
Rec2020,
/// The [`AcesCg`] color space.
AcesCg,
/// The [`XyzD50`] color space.
XyzD50,
/// The [`XyzD65`] color space.
Expand All @@ -67,8 +69,22 @@ impl ColorSpaceTag {
matches!(
(self, other),
(
Srgb | LinearSrgb | DisplayP3 | A98Rgb | ProphotoRgb | Rec2020 | XyzD50 | XyzD65,
Srgb | LinearSrgb | DisplayP3 | A98Rgb | ProphotoRgb | Rec2020 | XyzD50 | XyzD65
Srgb | LinearSrgb
| DisplayP3
| A98Rgb
| ProphotoRgb
| Rec2020
| AcesCg
| XyzD50
| XyzD65,
Srgb | LinearSrgb
| DisplayP3
| A98Rgb
| ProphotoRgb
| Rec2020
| AcesCg
| XyzD50
| XyzD65
) | (Lab | Oklab, Lab | Oklab)
| (Lch | Oklch, Lch | Oklch)
)
Expand Down Expand Up @@ -145,6 +161,7 @@ impl ColorSpaceTag {
Self::A98Rgb => A98Rgb::from_linear_srgb(rgb),
Self::ProphotoRgb => ProphotoRgb::from_linear_srgb(rgb),
Self::Rec2020 => Rec2020::from_linear_srgb(rgb),
Self::AcesCg => AcesCg::from_linear_srgb(rgb),
Self::XyzD50 => XyzD50::from_linear_srgb(rgb),
Self::XyzD65 => XyzD65::from_linear_srgb(rgb),
Self::Hsl => Hsl::from_linear_srgb(rgb),
Expand All @@ -167,6 +184,7 @@ impl ColorSpaceTag {
Self::A98Rgb => A98Rgb::to_linear_srgb(src),
Self::ProphotoRgb => ProphotoRgb::to_linear_srgb(src),
Self::Rec2020 => Rec2020::to_linear_srgb(src),
Self::AcesCg => AcesCg::to_linear_srgb(src),
Self::XyzD50 => XyzD50::to_linear_srgb(src),
Self::XyzD65 => XyzD65::to_linear_srgb(src),
Self::Hsl => Hsl::to_linear_srgb(src),
Expand Down Expand Up @@ -223,6 +241,7 @@ impl ColorSpaceTag {
Self::A98Rgb => A98Rgb::clip(src),
Self::ProphotoRgb => ProphotoRgb::clip(src),
Self::Rec2020 => Rec2020::clip(src),
Self::AcesCg => AcesCg::clip(src),
Self::XyzD50 => XyzD50::clip(src),
Self::XyzD65 => XyzD65::clip(src),
Self::Hsl => Hsl::clip(src),
Expand Down