mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-07 04:18:12 +08:00
Replace the 'Luminance' node with a 'Desaturate' node with a better selection of desaturation methods (#4529)
* Remove the 'Threshold' node's luminance calculation dropdown so it always compares the Rec. 601 luma * Find the HSL lightness extremes before encoding and drop a stale luminance TODO
This commit is contained in:
@@ -16,8 +16,8 @@ use graphene_std::list::{Item, List, NodeIdPath};
|
|||||||
use graphene_std::math::float_noise::round_away_float_noise;
|
use graphene_std::math::float_noise::round_away_float_noise;
|
||||||
use graphene_std::memo::IORecord;
|
use graphene_std::memo::IORecord;
|
||||||
use graphene_std::raster::{
|
use graphene_std::raster::{
|
||||||
AdjustmentChannel, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
|
AdjustmentChannel, CellularDistanceFunction, CellularReturnType, DesaturateMethod, DomainWarpType, FractalType, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute, SelectiveColorChoice,
|
||||||
SelectiveColorChoice,
|
TonalRange,
|
||||||
};
|
};
|
||||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||||
use graphene_std::text::TextAlign;
|
use graphene_std::text::TextAlign;
|
||||||
@@ -238,11 +238,12 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
|
|||||||
List<ExtrudeJoiningAlgorithm>,
|
List<ExtrudeJoiningAlgorithm>,
|
||||||
List<PointSpacingType>,
|
List<PointSpacingType>,
|
||||||
List<StringCapitalization>,
|
List<StringCapitalization>,
|
||||||
List<LuminanceCalculation>,
|
List<DesaturateMethod>,
|
||||||
List<RedGreenBlue>,
|
List<RedGreenBlue>,
|
||||||
List<RedGreenBlueAlpha>,
|
List<RedGreenBlueAlpha>,
|
||||||
List<RelativeAbsolute>,
|
List<RelativeAbsolute>,
|
||||||
List<SelectiveColorChoice>,
|
List<SelectiveColorChoice>,
|
||||||
|
List<TonalRange>,
|
||||||
List<AdjustmentChannel>,
|
List<AdjustmentChannel>,
|
||||||
List<XY>,
|
List<XY>,
|
||||||
List<ScaleType>,
|
List<ScaleType>,
|
||||||
@@ -295,11 +296,12 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
|
|||||||
Item<ExtrudeJoiningAlgorithm>,
|
Item<ExtrudeJoiningAlgorithm>,
|
||||||
Item<PointSpacingType>,
|
Item<PointSpacingType>,
|
||||||
Item<StringCapitalization>,
|
Item<StringCapitalization>,
|
||||||
Item<LuminanceCalculation>,
|
Item<DesaturateMethod>,
|
||||||
Item<RedGreenBlue>,
|
Item<RedGreenBlue>,
|
||||||
Item<RedGreenBlueAlpha>,
|
Item<RedGreenBlueAlpha>,
|
||||||
Item<RelativeAbsolute>,
|
Item<RelativeAbsolute>,
|
||||||
Item<SelectiveColorChoice>,
|
Item<SelectiveColorChoice>,
|
||||||
|
Item<TonalRange>,
|
||||||
Item<AdjustmentChannel>,
|
Item<AdjustmentChannel>,
|
||||||
Item<XY>,
|
Item<XY>,
|
||||||
Item<ScaleType>,
|
Item<ScaleType>,
|
||||||
@@ -1065,11 +1067,12 @@ impl_table_item_layout_for_choice_enum!(
|
|||||||
ExtrudeJoiningAlgorithm,
|
ExtrudeJoiningAlgorithm,
|
||||||
PointSpacingType,
|
PointSpacingType,
|
||||||
StringCapitalization,
|
StringCapitalization,
|
||||||
LuminanceCalculation,
|
DesaturateMethod,
|
||||||
RedGreenBlue,
|
RedGreenBlue,
|
||||||
RedGreenBlueAlpha,
|
RedGreenBlueAlpha,
|
||||||
RelativeAbsolute,
|
RelativeAbsolute,
|
||||||
SelectiveColorChoice,
|
SelectiveColorChoice,
|
||||||
|
TonalRange,
|
||||||
AdjustmentChannel,
|
AdjustmentChannel,
|
||||||
XY,
|
XY,
|
||||||
ScaleType,
|
ScaleType,
|
||||||
@@ -1284,11 +1287,12 @@ macro_rules! known_item_types {
|
|||||||
ExtrudeJoiningAlgorithm,
|
ExtrudeJoiningAlgorithm,
|
||||||
PointSpacingType,
|
PointSpacingType,
|
||||||
StringCapitalization,
|
StringCapitalization,
|
||||||
LuminanceCalculation,
|
DesaturateMethod,
|
||||||
RedGreenBlue,
|
RedGreenBlue,
|
||||||
RedGreenBlueAlpha,
|
RedGreenBlueAlpha,
|
||||||
RelativeAbsolute,
|
RelativeAbsolute,
|
||||||
SelectiveColorChoice,
|
SelectiveColorChoice,
|
||||||
|
TonalRange,
|
||||||
AdjustmentChannel,
|
AdjustmentChannel,
|
||||||
XY,
|
XY,
|
||||||
ScaleType,
|
ScaleType,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use graphene_std::animation::RealTimeMode;
|
|||||||
use graphene_std::color::SRGBA8;
|
use graphene_std::color::SRGBA8;
|
||||||
use graphene_std::extract_xy::XY;
|
use graphene_std::extract_xy::XY;
|
||||||
use graphene_std::raster::{
|
use graphene_std::raster::{
|
||||||
AdjustmentChannel, BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
|
AdjustmentChannel, BlendMode, CellularDistanceFunction, CellularReturnType, Color, DesaturateMethod, DomainWarpType, FractalType, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
|
||||||
SelectiveColorChoice, TonalRange,
|
SelectiveColorChoice, TonalRange,
|
||||||
};
|
};
|
||||||
use graphene_std::raster_types::Image;
|
use graphene_std::raster_types::Image;
|
||||||
@@ -332,7 +332,7 @@ pub(crate) fn property_from_type(
|
|||||||
Some(x) if id_is::<PointSpacingType>(x) => enum_choice::<PointSpacingType>().for_socket(default_info).property_row(),
|
Some(x) if id_is::<PointSpacingType>(x) => enum_choice::<PointSpacingType>().for_socket(default_info).property_row(),
|
||||||
Some(x) if id_is::<BooleanOperation>(x) => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
|
Some(x) if id_is::<BooleanOperation>(x) => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
|
||||||
Some(x) if id_is::<CentroidType>(x) => enum_choice::<CentroidType>().for_socket(default_info).property_row(),
|
Some(x) if id_is::<CentroidType>(x) => enum_choice::<CentroidType>().for_socket(default_info).property_row(),
|
||||||
Some(x) if id_is::<LuminanceCalculation>(x) => enum_choice::<LuminanceCalculation>().for_socket(default_info).property_row(),
|
Some(x) if id_is::<DesaturateMethod>(x) => enum_choice::<DesaturateMethod>().for_socket(default_info).property_row(),
|
||||||
Some(x) if id_is::<QRCodeErrorCorrectionLevel>(x) => enum_choice::<QRCodeErrorCorrectionLevel>().for_socket(default_info).property_row(),
|
Some(x) if id_is::<QRCodeErrorCorrectionLevel>(x) => enum_choice::<QRCodeErrorCorrectionLevel>().for_socket(default_info).property_row(),
|
||||||
Some(x) if id_is::<ScaleType>(x) => enum_choice::<ScaleType>().for_socket(default_info).property_row(),
|
Some(x) if id_is::<ScaleType>(x) => enum_choice::<ScaleType>().for_socket(default_info).property_row(),
|
||||||
Some(x) if id_is::<InterpolationDistribution>(x) => enum_choice::<InterpolationDistribution>().for_socket(default_info).property_row(),
|
Some(x) if id_is::<InterpolationDistribution>(x) => enum_choice::<InterpolationDistribution>().for_socket(default_info).property_row(),
|
||||||
|
|||||||
@@ -591,8 +591,9 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
NodeReplacement {
|
NodeReplacement {
|
||||||
node: graphene_std::raster_nodes::adjustments::luminance::IDENTIFIER,
|
node: graphene_std::raster_nodes::adjustments::desaturate::IDENTIFIER,
|
||||||
aliases: &[
|
aliases: &[
|
||||||
|
"raster_nodes::adjustments::LuminanceNode",
|
||||||
"graphene_raster_nodes::adjustments::LuminanceNode",
|
"graphene_raster_nodes::adjustments::LuminanceNode",
|
||||||
"graphene_core::raster::adjustments::LuminanceNode",
|
"graphene_core::raster::adjustments::LuminanceNode",
|
||||||
"graphene_core::raster::LuminanceNode",
|
"graphene_core::raster::LuminanceNode",
|
||||||
|
|||||||
@@ -548,7 +548,8 @@ tagged_value! {
|
|||||||
// ENUM TYPES
|
// ENUM TYPES
|
||||||
// ==========
|
// ==========
|
||||||
BlendMode(core_types::blending::BlendMode),
|
BlendMode(core_types::blending::BlendMode),
|
||||||
LuminanceCalculation(raster_nodes::adjustments::LuminanceCalculation),
|
#[serde(alias = "LuminanceCalculation")]
|
||||||
|
DesaturateMethod(raster_nodes::adjustments::DesaturateMethod),
|
||||||
QRCodeErrorCorrectionLevel(vector_nodes::generator_nodes::QRCodeErrorCorrectionLevel),
|
QRCodeErrorCorrectionLevel(vector_nodes::generator_nodes::QRCodeErrorCorrectionLevel),
|
||||||
XY(graphene_core::extract_xy::XY),
|
XY(graphene_core::extract_xy::XY),
|
||||||
StringCapitalization(text_nodes::StringCapitalization),
|
StringCapitalization(text_nodes::StringCapitalization),
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||||||
ExtrudeJoiningAlgorithm,
|
ExtrudeJoiningAlgorithm,
|
||||||
PointSpacingType,
|
PointSpacingType,
|
||||||
StringCapitalization,
|
StringCapitalization,
|
||||||
LuminanceCalculation,
|
DesaturateMethod,
|
||||||
RedGreenBlue,
|
RedGreenBlue,
|
||||||
RedGreenBlueAlpha,
|
RedGreenBlueAlpha,
|
||||||
RelativeAbsolute,
|
RelativeAbsolute,
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ impl Luminance for RGBA16F {
|
|||||||
type LuminanceChannel = f32;
|
type LuminanceChannel = f32;
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn luminance(&self) -> f32 {
|
fn luminance(&self) -> f32 {
|
||||||
// TODO: verify this is correct for sRGB
|
|
||||||
0.2126 * self.red() + 0.7152 * self.green() + 0.0722 * self.blue()
|
0.2126 * self.red() + 0.7152 * self.green() + 0.0722 * self.blue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -541,37 +540,36 @@ impl Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Relative luminance using Rec.709 / sRGB-primary weights, computed on linear-light RGB.
|
/// Relative luminance using Rec.709 / sRGB-primary weights, computed on linear-light RGB.
|
||||||
// From https://stackoverflow.com/a/56678483/775283
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn luminance_rec_709(&self) -> f32 {
|
pub fn luminance_rec_709(&self) -> f32 {
|
||||||
|
// From https://en.wikipedia.org/wiki/Luma_(video)#Rec._601_luma_versus_Rec._709_luma_coefficients
|
||||||
0.2126 * self.red + 0.7152 * self.green + 0.0722 * self.blue
|
0.2126 * self.red + 0.7152 * self.green + 0.0722 * self.blue
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Luma using Rec.601 SDTV coefficients.
|
/// Luma using Rec.601 SDTV coefficients.
|
||||||
// From https://en.wikipedia.org/wiki/Luma_(video)#Rec._601_luma_versus_Rec._709_luma_coefficients
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn luminance_rec_601(&self) -> f32 {
|
pub fn luminance_rec_601(&self) -> f32 {
|
||||||
|
// From https://en.wikipedia.org/wiki/Luma_(video)#Rec._601_luma_versus_Rec._709_luma_coefficients
|
||||||
0.299 * self.red + 0.587 * self.green + 0.114 * self.blue
|
0.299 * self.red + 0.587 * self.green + 0.114 * self.blue
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Luma using rounded Rec.601 coefficients (`0.3 / 0.59 / 0.11`), as used by some legacy image processing.
|
/// Luma using rounded Rec.601 coefficients (`0.3 / 0.59 / 0.11`), as used by some legacy image processing.
|
||||||
// From https://en.wikipedia.org/wiki/Luma_(video)#Rec._601_luma_versus_Rec._709_luma_coefficients
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn luminance_rec_601_rounded(&self) -> f32 {
|
pub fn luminance_rec_601_rounded(&self) -> f32 {
|
||||||
|
// From https://en.wikipedia.org/wiki/Luma_(video)#Rec._601_luma_versus_Rec._709_luma_coefficients
|
||||||
0.3 * self.red + 0.59 * self.green + 0.11 * self.blue
|
0.3 * self.red + 0.59 * self.green + 0.11 * self.blue
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perceptual lightness (CIE L*) of the Rec.709 luminance, normalized to 0..1.
|
/// Perceptual lightness (OkLab L) of the linear-light RGB, 0..1.
|
||||||
// From https://stackoverflow.com/a/56678483/775283
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn luminance_perceptual(&self) -> f32 {
|
pub fn lightness_oklab(&self) -> f32 {
|
||||||
let luminance = self.luminance_rec_709();
|
// From https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
|
||||||
|
|
||||||
if luminance <= 0.008856 {
|
let long = 0.41222147 * self.red + 0.53633254 * self.green + 0.05144599 * self.blue;
|
||||||
(luminance * 903.3) / 100.
|
let medium = 0.2119035 * self.red + 0.6806995 * self.green + 0.10739696 * self.blue;
|
||||||
} else {
|
let short = 0.08830246 * self.red + 0.28171884 * self.green + 0.6299787 * self.blue;
|
||||||
(luminance.cbrt() * 116. - 16.) / 100.
|
|
||||||
}
|
0.21045426 * long.cbrt() + 0.7936178 * medium.cbrt() - 0.004072047 * short.cbrt()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct an opaque grayscale color where R = G = B = `luminance`.
|
/// Construct an opaque grayscale color where R = G = B = `luminance`.
|
||||||
@@ -1062,6 +1060,15 @@ impl Color {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oklab_lightness_spans_black_to_white() {
|
||||||
|
assert!(Color::BLACK.lightness_oklab().abs() < 1e-4);
|
||||||
|
assert!((Color::WHITE.lightness_oklab() - 1.).abs() < 1e-4);
|
||||||
|
// A gray keeps L at the cube root of its linear value, since the three cone responses sum to it
|
||||||
|
assert!((Color::from_luminance(0.18).lightness_oklab() - 0.18_f32.cbrt()).abs() < 1e-3);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hsl_roundtrip() {
|
fn hsl_roundtrip() {
|
||||||
for (red, green, blue) in [
|
for (red, green, blue) in [
|
||||||
|
|||||||
@@ -35,24 +35,52 @@ use vector_types::Gradient;
|
|||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27clrL%27%20%3D%20Color%20Lookup
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27clrL%27%20%3D%20Color%20Lookup
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Color%20Lookup%20(Photoshop%20CS6
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Color%20Lookup%20(Photoshop%20CS6
|
||||||
|
|
||||||
|
/// Conversion from a color to grayscale.
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny))]
|
#[cfg_attr(feature = "std", derive(dyn_any::DynAny))]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, node_macro::ChoiceType, bytemuck::NoUninit, BufferStruct, FromPrimitive, IntoPrimitive)]
|
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, node_macro::ChoiceType, bytemuck::NoUninit, BufferStruct, FromPrimitive, IntoPrimitive)]
|
||||||
#[widget(Dropdown)]
|
#[widget(Dropdown)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum LuminanceCalculation {
|
pub enum DesaturateMethod {
|
||||||
|
/// Light level of the color, the Y (luminance) of Rec. 709, which weights the linear-light RGB channels by `0.2126, 0.7152, 0.0722`.
|
||||||
|
///
|
||||||
|
/// Accessibility contrast ratios and SVG luminance masks use this.
|
||||||
#[default]
|
#[default]
|
||||||
#[label("sRGB")]
|
#[label("Luminance (Rec. 709)")]
|
||||||
SRGB,
|
#[cfg_attr(feature = "serde", serde(alias = "SRGB"))]
|
||||||
Perceptual,
|
LuminanceRec709,
|
||||||
AverageChannels,
|
/// Light level approximation for the color, the Y′ (luma) of Rec. 709, which weights the gamma-encoded RGB channels by `0.2126, 0.7152, 0.0722`.
|
||||||
MinimumChannels,
|
///
|
||||||
MaximumChannels,
|
/// CSS filter functions such as `grayscale()` use this.
|
||||||
|
#[label("Luma (Rec. 709)")]
|
||||||
|
LumaRec709,
|
||||||
|
/// Light level approximation for the color, the Y′ (luma) of Rec. 601, which weights the gamma-encoded RGB channels by `0.299, 0.587, 0.114`.
|
||||||
|
#[label("Luma (Rec. 601)")]
|
||||||
|
LumaRec601,
|
||||||
|
/// Perceptually uniform scale from black to white, the L (lightness) of OkLab.
|
||||||
|
#[label("Lightness (OkLab)")]
|
||||||
|
#[cfg_attr(feature = "serde", serde(alias = "Perceptual"))]
|
||||||
|
LightnessOkLab,
|
||||||
|
/// Mean of the three linear-light RGB channels.
|
||||||
|
#[menu_separator]
|
||||||
|
#[cfg_attr(feature = "serde", serde(alias = "AverageChannels"))]
|
||||||
|
ChannelsAverage,
|
||||||
|
/// Smallest of the three linear-light RGB channels.
|
||||||
|
#[cfg_attr(feature = "serde", serde(alias = "MinimumChannels"))]
|
||||||
|
ChannelsMinimum,
|
||||||
|
/// Largest of the three linear-light RGB channels, the V (value) of HSV.
|
||||||
|
#[cfg_attr(feature = "serde", serde(alias = "MaximumChannels"))]
|
||||||
|
ChannelsMaximum,
|
||||||
|
/// Midpoint of the largest and smallest gamma-encoded RGB channels, the L (lightness) of HSL.
|
||||||
|
///
|
||||||
|
/// The classic "Desaturate" command of many image editors uses this.
|
||||||
|
#[label("Lightness (HSL)")]
|
||||||
|
LightnessHsl,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
|
#[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))]
|
||||||
fn luminance<T: Adjust<Color>>(
|
fn desaturate<T: Adjust<Color>>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Raster<CPU>,
|
Raster<CPU>,
|
||||||
@@ -61,18 +89,38 @@ fn luminance<T: Adjust<Color>>(
|
|||||||
)]
|
)]
|
||||||
#[gpu_image]
|
#[gpu_image]
|
||||||
input: Item<T>,
|
input: Item<T>,
|
||||||
luminance_calc: Item<LuminanceCalculation>,
|
method: Item<DesaturateMethod>,
|
||||||
) -> Item<T> {
|
) -> Item<T> {
|
||||||
let mut input = input;
|
let mut input = input;
|
||||||
let luminance_calc = luminance_calc.into_element();
|
let method = method.into_element();
|
||||||
|
|
||||||
input.element_mut().adjust(|color| {
|
input.element_mut().adjust(|color| {
|
||||||
let luminance = match luminance_calc {
|
// Gamma-encoded formulas are decoded as if they were a gray
|
||||||
LuminanceCalculation::SRGB => color.luminance_rec_709(),
|
let gamma = || color.to_gamma_srgb_channels();
|
||||||
LuminanceCalculation::Perceptual => color.luminance_perceptual(),
|
let luminance = match method {
|
||||||
LuminanceCalculation::AverageChannels => color.average_rgb_channels(),
|
DesaturateMethod::LuminanceRec709 => color.luminance_rec_709(),
|
||||||
LuminanceCalculation::MinimumChannels => color.minimum_rgb_channels(),
|
DesaturateMethod::LumaRec709 => {
|
||||||
LuminanceCalculation::MaximumChannels => color.maximum_rgb_channels(),
|
let [r, g, b, _] = gamma();
|
||||||
|
srgb_to_linear(0.2126 * r + 0.7152 * g + 0.0722 * b)
|
||||||
|
}
|
||||||
|
DesaturateMethod::LumaRec601 => {
|
||||||
|
let [r, g, b, _] = gamma();
|
||||||
|
srgb_to_linear(0.299 * r + 0.587 * g + 0.114 * b)
|
||||||
|
}
|
||||||
|
DesaturateMethod::LightnessOkLab => {
|
||||||
|
// A gray's OkLab lightness is the cube root of its linear value, so cubing gives the gray of equal lightness
|
||||||
|
let lightness = color.lightness_oklab();
|
||||||
|
lightness * lightness * lightness
|
||||||
|
}
|
||||||
|
DesaturateMethod::ChannelsAverage => color.average_rgb_channels(),
|
||||||
|
DesaturateMethod::ChannelsMinimum => color.minimum_rgb_channels(),
|
||||||
|
DesaturateMethod::ChannelsMaximum => color.maximum_rgb_channels(),
|
||||||
|
DesaturateMethod::LightnessHsl => {
|
||||||
|
// The transfer curve is monotonic, so the extremes are found first and only they are encoded
|
||||||
|
let max = linear_to_srgb(color.maximum_rgb_channels());
|
||||||
|
let min = linear_to_srgb(color.minimum_rgb_channels());
|
||||||
|
srgb_to_linear((max + min) / 2.)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
color.map_rgb(|_| luminance)
|
color.map_rgb(|_| luminance)
|
||||||
});
|
});
|
||||||
@@ -1401,11 +1449,11 @@ fn color_balance<T: Adjust<Color>>(
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
mod _graphene_hash_impls {
|
mod _graphene_hash_impls {
|
||||||
use super::{
|
use super::{
|
||||||
AdjustmentChannel, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
|
AdjustmentChannel, CellularDistanceFunction, CellularReturnType, DesaturateMethod, DomainWarpType, FractalType, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
|
||||||
SelectiveColorChoice, TonalRange,
|
SelectiveColorChoice, TonalRange,
|
||||||
};
|
};
|
||||||
graphene_hash::impl_via_hash!(
|
graphene_hash::impl_via_hash!(
|
||||||
LuminanceCalculation,
|
DesaturateMethod,
|
||||||
RedGreenBlue,
|
RedGreenBlue,
|
||||||
RedGreenBlueAlpha,
|
RedGreenBlueAlpha,
|
||||||
NoiseType,
|
NoiseType,
|
||||||
|
|||||||
Reference in New Issue
Block a user