Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add ProPhoto RGB standard #413

Merged
merged 1 commit into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions palette/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ pub use self::adobe::AdobeRgb;
pub use self::gamma::{F2p2, Gamma};
pub use self::linear::Linear;
pub use self::p3::{DciP3, DciP3Plus, DisplayP3};
pub use self::prophoto::ProPhotoRgb;
pub use self::rec_standards::{Rec2020, Rec709};
pub use self::srgb::Srgb;

pub mod adobe;
pub mod gamma;
pub mod linear;
pub mod p3;
pub mod prophoto;
pub mod rec_standards;
pub mod srgb;

Expand Down
150 changes: 150 additions & 0 deletions palette/src/encoding/prophoto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//! The ProPhoto RGB standard.

use crate::{
bool_mask::LazySelect,
encoding::{FromLinear, IntoLinear},
luma::LumaStandard,
num::{Arithmetics, PartialCmp, Powf, Real},
rgb::{Primaries, RgbSpace, RgbStandard},
white_point::{Any, D50},
Mat3, Yxy,
};

/// The ProPhoto RGB standard and color space with gamma 2.2 transfer function.
///
/// About 13% of the colors in this space are "[impossible colors](https://en.wikipedia.org/wiki/Impossible_color)"
/// meaning that they model cone responses that are, in practice, impossible to
/// achieve.
///
/// # As transfer function
///
/// `ProPhotoRgb` will not use any kind of approximation when converting from `T` to
/// `T`. This involves a call to `powf`, which may make it too slow for certain
/// applications.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ProPhotoRgb;

impl<T: Real> Primaries<T> for ProPhotoRgb {
fn red() -> Yxy<Any, T> {
Yxy::new(
T::from_f64(0.7347),
T::from_f64(0.2653),
T::from_f64(0.28804),
)
}
fn green() -> Yxy<Any, T> {
Yxy::new(
T::from_f64(0.1596),
T::from_f64(0.8404),
T::from_f64(0.71187),
)
}
fn blue() -> Yxy<Any, T> {
Yxy::new(
T::from_f64(0.0366),
T::from_f64(0.0001),
T::from_f64(0.000085663),
)
}
}

impl RgbSpace for ProPhotoRgb {
type Primaries = ProPhotoRgb;
type WhitePoint = D50;

#[rustfmt::skip]
#[inline(always)]
fn rgb_to_xyz_matrix() -> Option<Mat3<f64>> {
// Matrix from http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
Some([
0.7976749, 0.1351917, 0.0313534,
0.2880402, 0.7118741, 0.0000857,
0.0000000, 0.0000000, 0.8252100,
])
}

#[rustfmt::skip]
#[inline(always)]
fn xyz_to_rgb_matrix() -> Option<Mat3<f64>> {
// Matrix from http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
Some([
1.3459433, -0.2556075, -0.0511118,
-0.5445989, 1.5081673, 0.0205351,
0.0000000, 0.0000000, 1.2118128,
])
}
}

impl RgbStandard for ProPhotoRgb {
type Space = ProPhotoRgb;
type TransferFn = ProPhotoRgb;
}

impl LumaStandard for ProPhotoRgb {
type WhitePoint = D50;
type TransferFn = ProPhotoRgb;
}

impl<T> IntoLinear<T, T> for ProPhotoRgb
where
T: Real + Powf + Arithmetics + PartialCmp + Clone,
T::Mask: LazySelect<T>,
{
fn into_linear(encoded: T) -> T {
lazy_select! {
if encoded.lt(&T::from_f64(0.03125)) => T::from_f64(1.0 / 16.0) * &encoded,
else => encoded.clone().powf(T::from_f64(1.8)),
}
}
}

impl<T> FromLinear<T, T> for ProPhotoRgb
where
T: Real + Powf + Arithmetics + PartialCmp + Clone,
T::Mask: LazySelect<T>,
{
fn from_linear(linear: T) -> T {
lazy_select! {
if linear.lt(&T::from_f64(0.001953125)) => T::from_f64(16.0) * &linear,
else => linear.clone().powf(T::from_f64(1.0 / 1.8)),
}
}
}

#[cfg(test)]
mod test {
#[cfg(feature = "approx")]
mod conversion {
use crate::{
convert::IntoColorUnclamped,
encoding::prophoto::ProPhotoRgb,
matrix::{matrix_inverse, rgb_to_xyz_matrix},
rgb::{Primaries, RgbSpace},
white_point::{Any, WhitePoint, D50},
Xyz,
};

#[test]
fn rgb_to_xyz() {
let dynamic = rgb_to_xyz_matrix::<ProPhotoRgb, f64>();
let constant = ProPhotoRgb::rgb_to_xyz_matrix().unwrap();
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
}

#[test]
fn xyz_to_rgb() {
let dynamic = matrix_inverse(rgb_to_xyz_matrix::<ProPhotoRgb, f64>());
let constant = ProPhotoRgb::xyz_to_rgb_matrix().unwrap();
assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
}

#[test]
fn primaries_prophoto() {
let red: Xyz<Any, f64> = ProPhotoRgb::red().into_color_unclamped();
let green: Xyz<Any, f64> = ProPhotoRgb::green().into_color_unclamped();
let blue: Xyz<Any, f64> = ProPhotoRgb::blue().into_color_unclamped();
// Compare sum of primaries to white point.
assert_relative_eq!(red + green + blue, D50::get_xyz(), epsilon = 0.0001);
}
}
}
36 changes: 36 additions & 0 deletions palette/src/rgb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,42 @@ pub type LinAdobeRgb<T = f32> = Rgb<Linear<encoding::AdobeRgb>, T>;
/// create a value and use it.
pub type LinAdobeRgba<T = f32> = Rgba<Linear<encoding::AdobeRgb>, T>;

/// Non-linear ProPhoto RGB, a wide color gamut RGB format.
///
/// This is an RGB standard with a color gamut designed to include 100% of likely
/// occurring real-world colors.
///
/// See [`Rgb`] for more details on how to create a value and use it.
pub type ProPhotoRgb<T = f32> = Rgb<encoding::ProPhotoRgb, T>;

/// Non-linear ProPhoto RGB with an alpha component.
///
/// This is a transparent version of [`ProPhotoRgb`], which is commonly used as the
/// input or output format.
///
/// See [`Rgb`], [`Rgba`] and [`Alpha`](crate::Alpha) for more details on how to
/// create a value and use it.
pub type ProPhotoRgba<T = f32> = Rgba<encoding::ProPhotoRgb, T>;

/// Linear ProPhoto RGB.
///
/// You probably want [`ProPhotoRgb`] if you are looking for an input or output format.
/// This is the linear version of ProPhoto RGB, which is what you would usually convert
/// to before working with the color.
///
/// See [`Rgb`] for more details on how to create a value and use it.
pub type LinProPhotoRgb<T = f32> = Rgb<Linear<encoding::ProPhotoRgb>, T>;

/// Linear ProPhoto RGB with an alpha component.
///
/// You probably want [`ProPhotoRgba`] if you are looking for an input or output format.
/// This is the linear version of ProPhoto RGBA, which is what you would usually convert
/// to before working with the color.
///
/// See [`Rgb`], [`Rgba`] and [`Alpha`](crate::Alpha) for more details on how to
/// create a value and use it.
pub type LinProPhotoRgba<T = f32> = Rgba<Linear<encoding::ProPhotoRgb>, T>;

/// Rec. 709.
///
/// This standard has the same primaries as [`Srgb`], but uses the transfer
Expand Down
Loading