#[cfg(feature = "pyffi")]
use pyo3::prelude::*;
use crate::core::{convert, normalize, ColorSpace};
use crate::Float;
#[allow(non_snake_case)]
pub(crate) fn delta_e_ok(coordinates1: &[Float; 3], coordinates2: &[Float; 3]) -> Float {
let [ref L1, ref a1, ref b1] = *coordinates1;
let [ref L2, ref a2, ref b2] = *coordinates2;
let ΔL = L1 - L2;
let Δa = a1 - a2;
let Δb = b1 - b2;
ΔL.mul_add(ΔL, Δa.mul_add(Δa, Δb * Δb)).sqrt()
}
pub(crate) fn find_closest<'c, C, F>(
origin: &[f64; 3],
candidates: C,
mut compute_distance: F,
) -> Option<usize>
where
C: IntoIterator<Item = &'c [f64; 3]>,
F: FnMut(&[f64; 3], &[f64; 3]) -> f64,
{
let mut min_distance = f64::INFINITY;
let mut min_index = None;
for (index, candidate) in candidates.into_iter().enumerate() {
let distance = compute_distance(origin, candidate);
if distance < min_distance {
min_distance = distance;
min_index = Some(index);
}
}
min_index
}
fn carry_forward(from_space: ColorSpace, to_space: ColorSpace, index: usize) -> Option<usize> {
use ColorSpace::*;
if !(0..=2).contains(&index) {
panic!("0..=2.contains({}) does not hold!", index)
}
match (from_space, to_space, index) {
(
Srgb | LinearSrgb | DisplayP3 | LinearDisplayP3 | Rec2020 | LinearRec2020 | Xyz,
Srgb | LinearSrgb | DisplayP3 | LinearDisplayP3 | Rec2020 | LinearRec2020 | Xyz,
_,
) => Some(index),
(Oklab | Oklch, Oklab | Oklch, 0) => Some(0),
(Oklrab | Oklrch, Oklrab | Oklrch, 0) => Some(0),
(Oklab | Oklrab, Oklab | Oklrab, 1 | 2) => Some(index),
(Oklch | Oklrch, Oklch | Oklrch, 1 | 2) => Some(index),
_ => None,
}
}
fn prepare_coordinate_interpolation(
from_space: ColorSpace,
to_space: ColorSpace,
coordinates: &[Float; 3],
) -> [Float; 3] {
let mut intermediate = convert(from_space, to_space, &normalize(from_space, coordinates));
for (index, coordinate) in coordinates.iter().enumerate() {
if coordinate.is_nan() {
if let Some(index) = carry_forward(from_space, to_space, index) {
intermediate[index] = Float::NAN;
}
}
}
intermediate
}
#[cfg_attr(
feature = "pyffi",
pyclass(eq, eq_int, frozen, hash, module = "prettypretty.color")
)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum HueInterpolation {
Shorter,
Longer,
Increasing,
Decreasing,
}
fn prepare_hue_interpolation(strategy: HueInterpolation, h1: Float, h2: Float) -> [Float; 2] {
match strategy {
HueInterpolation::Shorter => {
if 180.0 < h2 - h1 {
return [h1 + 360.0, h2];
} else if h2 - h1 < -180.0 {
return [h1, h2 + 360.0];
}
}
HueInterpolation::Longer => {
if (0.0..=180.0).contains(&(h2 - h1)) {
return [h1 + 360.0, h2];
} else if (-180.0..=0.0).contains(&(h2 - h1)) {
return [h1, h2 + 360.0];
}
}
HueInterpolation::Increasing => {
if h2 < h1 {
return [h1, h2 + 360.0];
}
}
HueInterpolation::Decreasing => {
if h1 < h2 {
return [h1 + 360.0, h2];
}
}
}
[h1, h2]
}
#[must_use = "function returns new color coordinates and does not mutate original values"]
pub(crate) fn prepare_to_interpolate(
space1: ColorSpace,
coordinates1: &[Float; 3],
space2: ColorSpace,
coordinates2: &[Float; 3],
interpolation_space: ColorSpace,
strategy: HueInterpolation,
) -> ([Float; 3], [Float; 3]) {
let mut coordinates1 =
prepare_coordinate_interpolation(space1, interpolation_space, coordinates1);
let mut coordinates2 =
prepare_coordinate_interpolation(space2, interpolation_space, coordinates2);
for index in 0..=2 {
if coordinates1[index].is_nan() {
coordinates1[index] = coordinates2[index];
} else if coordinates2[index].is_nan() {
coordinates2[index] = coordinates1[index];
}
}
if interpolation_space.is_polar() {
[coordinates1[2], coordinates2[2]] =
prepare_hue_interpolation(strategy, coordinates1[2], coordinates2[2])
}
(coordinates1, coordinates2)
}
#[must_use = "function returns new color coordinates and does not mutate original values"]
pub(crate) fn interpolate(
fraction: Float,
coordinates1: &[Float; 3],
coordinates2: &[Float; 3],
) -> [Float; 3] {
[
fraction.mul_add(coordinates2[0] - coordinates1[0], coordinates1[0]),
fraction.mul_add(coordinates2[1] - coordinates1[1], coordinates1[1]),
fraction.mul_add(coordinates2[2] - coordinates1[2], coordinates1[2]),
]
}