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,
}),
}
}
}