Skip to content

Commit

Permalink
Add white point to colors and implement chromatic adapatation methods
Browse files Browse the repository at this point in the history
  • Loading branch information
sidred committed Mar 4, 2016
1 parent ab3c57b commit a8e6273
Show file tree
Hide file tree
Showing 27 changed files with 2,621 additions and 687 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ version = "0.7"
optional = true

[dev-dependencies]
image = "0.4"
image = "0.7"
clap = "1"
csv = "0.14"
rustc-serialize = "0.3"
Expand Down
5 changes: 3 additions & 2 deletions examples/readme_examples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use image::{RgbImage, GenericImage};

use palette::{Rgba, Gradient, Mix};
use palette::pixel::Srgb;
use palette::white_point::D65;

mod color_spaces {
use palette::{Rgb, Lch, Hue};
Expand Down Expand Up @@ -75,8 +76,8 @@ fn display_colors(filename: &str, colors: &[[u8; 3]]) {
}

fn display_gradients<A: Mix<Scalar=f64> + Clone, B: Mix<Scalar=f64> + Clone>(filename: &str, grad1: Gradient<A>, grad2: Gradient<B>) where
Rgba<f64>: From<A>,
Rgba<f64>: From<B>,
Rgba<D65, f64>: From<A>,
Rgba<D65, f64>: From<B>,
{
let mut image = RgbImage::new(256, 64);

Expand Down
7 changes: 4 additions & 3 deletions src/blend/blend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ pub trait Blend: Sized {
///```
///use palette::{Rgb, Rgba, Blend};
///use palette::blend::PreAlpha;
///use palette::white_point::D65;
///
///type PreRgba = PreAlpha<Rgb<f32>, f32>;
///type PreRgba = PreAlpha<Rgb<D65, f32>, f32>;
///
///fn blend_mode(a: PreRgba, b: PreRgba) -> PreRgba {
/// PreAlpha {
Expand Down Expand Up @@ -326,7 +327,7 @@ pub trait Blend: Sized {
} else if b * flt(4.0) <= dst.alpha {
let m2 = m * m;
let m3 = m2 * m;

dst.alpha * (two * a - src.alpha) * (m3 * flt(16.0) - m2 * flt(12.0) - m * flt(3.0)) + a - a * dst.alpha + b
} else {
dst.alpha * (two * a - src.alpha) * (m.sqrt() - m) + a - a * dst.alpha + b
Expand Down Expand Up @@ -374,4 +375,4 @@ pub trait Blend: Sized {

Self::from_premultiplied(result)
}
}
}
261 changes: 261 additions & 0 deletions src/chromatic_adaptation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
//!Convert colors from one reference white point to another
//!
//!Chromatic adaptation is the human visual system’s ability to adjust to changes
//!in illumination in order to preserve the appearance of object colors. It is responsible
//!for the stable appearance of object colours despite the wide variation of light which
//!might be reflected from an object and observed by our eyes.
//!
//!This library provides three methods for chromatic adaptation Bradford (which is the default),
//!VonKries and XyzScaling
//!
//!```
//!use palette::Xyz;
//!use palette::white_point::{A, C};
//!use palette::chromatic_adaptation::AdaptInto;
//!
//!
//!let a = Xyz::<A, f32>::with_wp(0.315756, 0.162732, 0.015905);
//!
//!//Will convert Xyz<A, f32> to Xyz<C, f32> using Bradford chromatic adaptation
//!let c: Xyz<C, f32> = a.adapt_into();
//!
//!//Should print {x: 0.257963, y: 0.139776,z: 0.058825}
//!println!("{:?}", c)
//!```
use num::Float;

use {Xyz, FromColor, IntoColor, flt};
use white_point::WhitePoint;
use matrix::{Mat3, multiply_xyz, multiply_3x3};


///Chromatic adaptation methods implemented in the library
pub enum Method {
///Bradford chromatic adaptation method
Bradford,
///VonKries chromatic adaptation method
VonKries,
///XyzScaling chromatic adaptation method
XyzScaling,
}

///Holds the matrix coeffecients for the chromatic adaptation methods
pub struct ConeResponseMatrices<T: Float> {
///3x3 matrix for the cone response domains
pub ma: Mat3<T>,
///3x3 matrix for the inverse of the cone response domains
pub inv_ma: Mat3<T>,
}

///Generates a conversion matrix to convert the Xyz trisitmilus values from
///one illuminant to another (Swp -> Dwp)
pub trait TransformMatrix<Swp, Dwp, T>
where T: Float,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
{
///Get the cone response functions for the chromatic adaptation method
fn get_cone_response(&self) -> ConeResponseMatrices<T>;

///Generates a 3x3 transformation matrix to convert color from one reference white point
///to another with the given cone_response
fn generate_transform_matrix(&self) -> Mat3<T> {
let s_wp = Swp::get_xyz();
let t_wp = Dwp::get_xyz();
let adapt = self.get_cone_response();

let resp_src: Xyz<Swp,_> = multiply_xyz(&adapt.ma, &s_wp);
let resp_dst: Xyz<Dwp,_> = multiply_xyz(&adapt.ma, &t_wp);
let z = T::zero();
let resp = [resp_dst.x/resp_src.x, z , z , z, resp_dst.y/resp_src.y, z, z, z, resp_dst.z/ resp_src.z];

let tmp = multiply_3x3(&resp, &adapt.ma);
multiply_3x3(&adapt.inv_ma, &tmp)
}
}


impl<Swp, Dwp, T> TransformMatrix<Swp, Dwp, T> for Method
where T: Float,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
{
fn get_cone_response(&self) -> ConeResponseMatrices<T> {
match *self {
Method::Bradford => {
ConeResponseMatrices::<T> {
ma: [flt(0.8951000), flt(0.2664000), flt(-0.1614000),
flt(-0.7502000), flt(1.7135000), flt(0.0367000),
flt(0.0389000), flt(-0.0685000), flt(1.0296000)
],
inv_ma: [flt(0.9869929), flt(-0.1470543), flt(0.1599627),
flt(0.4323053), flt(0.5183603), flt(0.0492912),
flt(-0.0085287), flt(0.0400428), flt(0.9684867)
],
}
}
Method::VonKries => {
ConeResponseMatrices::<T> {
ma: [flt(0.4002400), flt(0.7076000), flt(-0.0808100),
flt(-0.2263000), flt(1.1653200), flt(0.0457000),
flt(0.0000000), flt(0.0000000), flt(0.9182200)
],
inv_ma: [flt(1.8599364), flt(-1.1293816), flt(0.2198974),
flt(0.3611914), flt(0.6388125), flt(-0.0000064),
flt(0.0000000), flt(0.0000000), flt(1.0890636)
],
}
}
Method::XyzScaling => {
ConeResponseMatrices::<T> {
ma: [flt(1.0000000), flt(0.0000000), flt(0.0000000),
flt(0.0000000), flt(1.0000000), flt(0.0000000),
flt(0.0000000), flt(0.0000000), flt(1.0000000)
],
inv_ma: [flt(1.0000000), flt(0.0000000), flt(0.0000000),
flt(0.0000000), flt(1.0000000), flt(0.0000000),
flt(0.0000000), flt(0.0000000), flt(1.0000000)
],
}
}
}
}
}


///Trait to convert color from one reference white point to another
///
///Converts a color from the source white point (Swp) to the destination white point (Dwp).
///Uses the bradford method for conversion by default.
pub trait AdaptFrom<S, Swp, Dwp, T>: Sized
where T: Float,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
{
///Convert the source color to the destination color using the bradford method by default
fn adapt_from(color: S) -> Self {
Self::adapt_from_using(color, Method::Bradford)
}
///Convert the source color to the destination color using the specified method
fn adapt_from_using<M: TransformMatrix<Swp, Dwp, T>>(color: S, method: M) -> Self;
}

impl<S, D, Swp, Dwp, T> AdaptFrom<S, Swp, Dwp, T> for D
where T: Float,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
S: IntoColor<Swp, T>,
D: FromColor<Dwp, T>,

{
fn adapt_from_using<M: TransformMatrix<Swp, Dwp, T>>(color:S, method: M) -> D {
let src_xyz: Xyz<Swp, T> = color.into_xyz();
let transform_matrix = method.generate_transform_matrix();
let dst_xyz: Xyz<Dwp, T> = multiply_xyz(&transform_matrix, &src_xyz);
D::from_xyz(dst_xyz)
}
}

///Trait to convert color with one reference white point into another
///
///Converts a color with the source white point (Swp) into the destination white point (Dwp).
///Uses the bradford method for conversion by default.
pub trait AdaptInto<D, Swp, Dwp, T>: Sized
where T: Float,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
{
///Convert the source color to the destination color using the bradford method by default
fn adapt_into(self) -> D {
self.adapt_into_using(Method::Bradford)
}
///Convert the source color to the destination color using the specified method
fn adapt_into_using<M: TransformMatrix<Swp, Dwp, T>>(self, method: M) -> D;
}

impl<S, D, Swp, Dwp, T> AdaptInto<D, Swp, Dwp, T> for S
where T: Float,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
D: AdaptFrom<S, Swp, Dwp, T>

{
fn adapt_into_using<M: TransformMatrix<Swp, Dwp, T>>(self, method: M) -> D {
D::adapt_from_using(self, method)
}
}




#[cfg(test)]
mod test {

use {Xyz};
use white_point::{D65, D50, A, C};
use super::{Method, TransformMatrix, AdaptInto, AdaptFrom};

#[test]
fn d65_to_d50_matrix_xyz_scaling() {
let expected = [1.0144665, 0.0000000, 0.0000000, 0.0000000, 1.0000000, 0.0000000, 0.0000000, 0.0000000, 0.7578869];
let xyz_scaling = Method::XyzScaling;
let computed = <TransformMatrix<D65, D50, _>>::generate_transform_matrix(&xyz_scaling);
for (e, c) in expected.iter().zip(computed.iter()) {
assert_relative_eq!(e, c, epsilon = 0.0001)
}
}
#[test]
fn d65_to_d50_matrix_von_kries() {
let expected = [1.0160803, 0.0552297, -0.0521326, 0.0060666, 0.9955661, -0.0012235, 0.0000000, 0.0000000, 0.7578869];
let von_kries = Method::VonKries;
let computed = <TransformMatrix<D65, D50, _>>::generate_transform_matrix(&von_kries);
for (e, c) in expected.iter().zip(computed.iter()) {
assert_relative_eq!(e, c, epsilon = 0.0001)
}
}
#[test]
fn d65_to_d50_matrix_bradford() {
let expected = [1.0478112, 0.0228866, -0.0501270, 0.0295424, 0.9904844, -0.0170491, -0.0092345, 0.0150436, 0.7521316];
let bradford = Method::Bradford;
let computed = <TransformMatrix<D65, D50, _>>::generate_transform_matrix(&bradford);
for (e, c) in expected.iter().zip(computed.iter()) {
assert_relative_eq!(e, c, epsilon = 0.0001)
}
}

#[test]
fn chromatic_adaptation_from_a_to_c() {
let input_a = Xyz::<A, f32>::with_wp(0.315756, 0.162732, 0.015905);

let expected_bradford = Xyz::<C, f32>::with_wp(0.257963, 0.139776, 0.058825);
let expected_vonkries = Xyz::<C, f32>::with_wp(0.268446, 0.159139, 0.052843);
let expected_xyz_scaling = Xyz::<C, f32>::with_wp(0.281868, 0.162732, 0.052844);

let computed_bradford: Xyz<C, f32> = Xyz::adapt_from(input_a);
assert_relative_eq!(expected_bradford, computed_bradford, epsilon = 0.0001);

let computed_vonkries: Xyz<C, f32> = Xyz::adapt_from_using(input_a, Method::VonKries);
assert_relative_eq!(expected_vonkries, computed_vonkries, epsilon = 0.0001);

let computed_xyz_scaling: Xyz<C,_> = Xyz::adapt_from_using(input_a, Method::XyzScaling);
assert_relative_eq!(expected_xyz_scaling, computed_xyz_scaling, epsilon = 0.0001);
}

#[test]
fn chromatic_adaptation_into_a_to_c() {
let input_a = Xyz::<A, f32>::with_wp(0.315756, 0.162732, 0.015905);

let expected_bradford = Xyz::<C, f32>::with_wp(0.257963, 0.139776, 0.058825);
let expected_vonkries = Xyz::<C, f32>::with_wp(0.268446, 0.159139, 0.052843);
let expected_xyz_scaling = Xyz::<C, f32>::with_wp(0.281868, 0.162732, 0.052844);

let computed_bradford: Xyz<C, f32> = input_a.adapt_into();
assert_relative_eq!(expected_bradford, computed_bradford, epsilon = 0.0001);

let computed_vonkries: Xyz<C, f32> = input_a.adapt_into_using(Method::VonKries);
assert_relative_eq!(expected_vonkries, computed_vonkries, epsilon = 0.0001);

let computed_xyz_scaling: Xyz<C,_> = input_a.adapt_into_using(Method::XyzScaling);
assert_relative_eq!(expected_xyz_scaling, computed_xyz_scaling, epsilon = 0.0001);
}
}
Loading

0 comments on commit a8e6273

Please sign in to comment.