1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//! Contains the supported color spaces and utility functions for converting between them.

use crate::wu::{Binner3, UIntBinner};

#[cfg(all(feature = "kmeans", feature = "colorspaces"))]
use crate::{kmeans::Centroids, KmeansOptions};
#[cfg(feature = "colorspaces")]
use crate::{wu::FloatBinner, ColorSlice, QuantizeMethod};

#[cfg(feature = "colorspaces")]
use std::marker::PhantomData;

#[cfg(feature = "colorspaces")]
use ::palette::{IntoColor, LinSrgb, Srgb};
#[cfg(all(feature = "threads", feature = "colorspaces"))]
use rayon::prelude::*;

/// The set of supported color spaces that can be used when performing color quantization.
///
/// If the `colorspaces` feature is enabled, then this will add support for
/// the CIELAB and Oklab color spaces. Otherwise, only sRGB is supported.
///
/// See the descriptions on each enum variant for more information.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorSpace {
    /// The sRGB color space.
    ///
    /// Using this color space avoids color space conversion and gives the quickest results.
    /// However, the sRGB color space is not perceptually uniform,
    /// so it can give potentially lackluster results.
    Srgb,
    /// The CIELAB color space.
    ///
    /// This color space is mostly perceptually uniform and should give better results than sRGB.
    /// However, it is recommended to use the Oklab color space instead, since it is
    /// more perceptually uniform compared to CIELAB while having the same computational cost.
    #[cfg(feature = "colorspaces")]
    Lab,
    /// The Oklab color space.
    ///
    /// This color space is perceptually uniform and should give the most accurate results
    /// compared to the other supported color spaces.
    #[cfg(feature = "colorspaces")]
    Oklab,
}

impl ColorSpace {
    /// The valid range of values for `f32` components of a [`Srgb`] color.
    pub const SRGB_F32_COMPONENT_RANGES: [(f32, f32); 3] = [(0.0, 1.0), (0.0, 1.0), (0.0, 1.0)];

    /// The range of possible values for `f32` components of a [`Lab`](palette::Lab) color,
    /// provided that it was converted from a [`Srgb<u8>`] color.
    #[cfg(feature = "colorspaces")]
    pub const LAB_F32_COMPONENT_RANGES_FROM_SRGB: [(f32, f32); 3] = [
        (0.0, 100.0),
        (-86.182686, 98.23433),
        (-107.86016, 94.477974),
    ];

    /// The range of possible values for `f32` components of an [`Oklab`](palette::Oklab) color,
    /// provided that it was converted from a [`Srgb<u8>`] color.
    #[cfg(feature = "colorspaces")]
    pub const OKLAB_F32_COMPONENT_RANGES_FROM_SRGB: [(f32, f32); 3] = [
        (0.0, 1.0),
        (-0.2338874, 0.2762164),
        (-0.31152815, 0.19856972),
    ];

    /// Returns the range of possible values for `f32` components of the given [`ColorSpace`],
    /// provided that the color is or was converted from a [`Srgb<u8>`] color.
    #[must_use]
    pub const fn f32_component_ranges_from_srgb(self) -> [(f32, f32); 3] {
        match self {
            ColorSpace::Srgb => Self::SRGB_F32_COMPONENT_RANGES,
            #[cfg(feature = "colorspaces")]
            ColorSpace::Lab => Self::LAB_F32_COMPONENT_RANGES_FROM_SRGB,
            #[cfg(feature = "colorspaces")]
            ColorSpace::Oklab => Self::OKLAB_F32_COMPONENT_RANGES_FROM_SRGB,
        }
    }

    /// Returns the default binner used to create histograms for [`Srgb<u8>`] colors.
    #[must_use]
    pub fn default_binner_srgb_u8() -> impl Binner3<u8, 32> {
        UIntBinner
    }

    /// Returns the default binner used to create histograms for [`Lab`](palette::Lab) colors
    /// that were converted from [`Srgb<u8>`] colors.
    #[must_use]
    #[cfg(feature = "colorspaces")]
    pub fn default_binner_lab_f32() -> impl Binner3<f32, 32> {
        FloatBinner::new(Self::LAB_F32_COMPONENT_RANGES_FROM_SRGB)
    }

    /// Returns the default binner used to create histograms for [`Oklab`](palette::Oklab) colors
    /// that were converted from [`Srgb<u8>`] colors.
    #[must_use]
    #[cfg(feature = "colorspaces")]
    pub fn default_binner_oklab_f32() -> impl Binner3<f32, 32> {
        FloatBinner::new(Self::OKLAB_F32_COMPONENT_RANGES_FROM_SRGB)
    }
}

/// Convert from [`Srgb<u8>`] to another color type
#[cfg(feature = "colorspaces")]
pub(crate) fn from_srgb<Color>(color: Srgb<u8>) -> Color
where
    LinSrgb: IntoColor<Color>,
{
    color.into_linear().into_color()
}

/// Convert from a color type to [`Srgb<u8>`]
#[cfg(feature = "colorspaces")]
pub(crate) fn to_srgb<Color>(color: Color) -> Srgb<u8>
where
    Color: IntoColor<LinSrgb>,
{
    color.into_color().into_encoding()
}

/// Convert a color slice to a different color type
#[cfg(feature = "colorspaces")]
pub(crate) fn convert_color_slice<FromColor, ToColor>(
    colors: ColorSlice<FromColor>,
    convert: impl Fn(FromColor) -> ToColor,
) -> Vec<ToColor>
where
    FromColor: Copy,
    ToColor: Copy,
{
    colors.iter().copied().map(convert).collect()
}

/// Convert a color slice to a different color type in parallel
#[cfg(all(feature = "colorspaces", feature = "threads"))]
pub(crate) fn convert_color_slice_par<FromColor, ToColor>(
    colors: ColorSlice<FromColor>,
    convert: impl Fn(FromColor) -> ToColor + Send + Sync,
) -> Vec<ToColor>
where
    FromColor: Copy + Send + Sync,
    ToColor: Copy + Send,
{
    colors.par_iter().copied().map(convert).collect()
}

#[cfg(feature = "colorspaces")]
impl QuantizeMethod<Srgb<u8>> {
    /// Converts to a different colorspace.
    #[allow(unused_variables)]
    pub(crate) fn convert_color_space_from_srgb<Color>(
        self,
        convert_to: impl Fn(Srgb<u8>) -> Color,
    ) -> QuantizeMethod<Color> {
        match self {
            QuantizeMethod::Wu(_) => QuantizeMethod::Wu(PhantomData),
            #[cfg(feature = "kmeans")]
            QuantizeMethod::Kmeans(KmeansOptions {
                sampling_factor,
                initial_centroids,
                seed,
                batch_size,
            }) => QuantizeMethod::Kmeans(KmeansOptions {
                initial_centroids: initial_centroids.map(|c| {
                    Centroids::new_unchecked(c.into_inner().into_iter().map(&convert_to).collect())
                }),
                sampling_factor,
                seed,
                batch_size,
            }),
        }
    }
}