Improve color negation (#3500)

This commit is contained in:
frozolotl 2024-02-27 12:15:17 +01:00 committed by GitHub
parent 0aa9254356
commit 79615a01bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 42 additions and 45 deletions

View File

@ -4,10 +4,9 @@ use std::str::FromStr;
use ecow::{eco_format, EcoString, EcoVec};
use once_cell::sync::Lazy;
use palette::convert::FromColorUnclamped;
use palette::encoding::{self, Linear};
use palette::{
Darken, Desaturate, FromColor, Lighten, Okhsva, OklabHue, RgbHue, Saturate, ShiftHue,
Darken, Desaturate, FromColor, Lighten, OklabHue, RgbHue, Saturate, ShiftHue,
};
use qcms::Profile;
@ -1010,16 +1009,29 @@ impl Color {
})
}
/// Produces the negative of the color.
/// Produces the complementary color using a provided color space.
/// You can think of it as the opposite side on a color wheel.
///
/// ```example
/// #square(fill: yellow)
/// #square(fill: yellow.negate())
/// #square(fill: yellow.negate(space: rgb))
/// ```
#[func]
pub fn negate(self) -> Color {
match self {
pub fn negate(
self,
/// The color space used for the transformation. By default, a perceptual color space is used.
#[named]
#[default(ColorSpace::Oklab)]
space: ColorSpace,
) -> Color {
let result = match self.to_space(space) {
Self::Luma(c) => Self::Luma(Luma::new(1.0 - c.luma, c.alpha)),
Self::Oklab(c) => Self::Oklab(Oklab::new(c.l, -c.a, -c.b, c.alpha)),
Self::Oklab(c) => Self::Oklab(Oklab::new(1.0 - c.l, -c.a, -c.b, c.alpha)),
Self::Oklch(c) => Self::Oklch(Oklch::new(
c.l,
-c.chroma,
OklabHue::from_degrees(360.0 - c.hue.into_degrees()),
1.0 - c.l,
c.chroma,
OklabHue::from_degrees(c.hue.into_degrees() + 180.0),
c.alpha,
)),
Self::LinearRgb(c) => Self::LinearRgb(LinearRgb::new(
@ -1033,18 +1045,19 @@ impl Color {
}
Self::Cmyk(c) => Self::Cmyk(Cmyk::new(1.0 - c.c, 1.0 - c.m, 1.0 - c.y, c.k)),
Self::Hsl(c) => Self::Hsl(Hsl::new(
RgbHue::from_degrees(360.0 - c.hue.into_degrees()),
RgbHue::from_degrees(c.hue.into_degrees() + 180.0),
c.saturation,
c.lightness,
c.alpha,
)),
Self::Hsv(c) => Self::Hsv(Hsv::new(
RgbHue::from_degrees(360.0 - c.hue.into_degrees()),
RgbHue::from_degrees(c.hue.into_degrees() + 180.0),
c.saturation,
c.value,
c.alpha,
)),
}
};
result.to_space(self.space())
}
/// Rotates the hue of the color by a given angle.
@ -1304,10 +1317,8 @@ impl Color {
pub fn to_luma(self) -> Self {
Self::Luma(match self {
Self::Luma(c) => c,
// Perform sRGB gamut mapping by converting to Okhsv first.
// This yields better results than clamping.
Self::Oklab(c) => Luma::from_color(Okhsva::from_color(c)),
Self::Oklch(c) => Luma::from_color(Okhsva::from_color(c)),
Self::Oklab(c) => Luma::from_color(c),
Self::Oklch(c) => Luma::from_color(c),
Self::Rgb(c) => Luma::from_color(c),
Self::LinearRgb(c) => Luma::from_color(c),
Self::Cmyk(c) => Luma::from_color(c.to_rgba()),
@ -1320,9 +1331,7 @@ impl Color {
Self::Oklab(match self {
Self::Luma(c) => Oklab::from_color(c),
Self::Oklab(c) => c,
// No clamping is necessary for this conversion because the
// lightness property is the same for both Oklab and Oklch.
Self::Oklch(c) => Oklab::from_color_unclamped(c),
Self::Oklch(c) => Oklab::from_color(c),
Self::Rgb(c) => Oklab::from_color(c),
Self::LinearRgb(c) => Oklab::from_color(c),
Self::Cmyk(c) => Oklab::from_color(c.to_rgba()),
@ -1334,9 +1343,7 @@ impl Color {
pub fn to_oklch(self) -> Self {
Self::Oklch(match self {
Self::Luma(c) => Oklch::from_color(c),
// No clamping is necessary for this conversion because the
// lightness property is the same for both Oklab and Oklch.
Self::Oklab(c) => Oklch::from_color_unclamped(c),
Self::Oklab(c) => Oklch::from_color(c),
Self::Oklch(c) => c,
Self::Rgb(c) => Oklch::from_color(c),
Self::LinearRgb(c) => Oklch::from_color(c),
@ -1349,10 +1356,8 @@ impl Color {
pub fn to_rgb(self) -> Self {
Self::Rgb(match self {
Self::Luma(c) => Rgb::from_color(c),
// Perform sRGB gamut mapping by converting to Okhsv first.
// This yields better results than clamping.
Self::Oklab(c) => Rgb::from_color(Okhsva::from_color(c)),
Self::Oklch(c) => Rgb::from_color(Okhsva::from_color(c)),
Self::Oklab(c) => Rgb::from_color(c),
Self::Oklch(c) => Rgb::from_color(c),
Self::Rgb(c) => c,
Self::LinearRgb(c) => Rgb::from_linear(c),
Self::Cmyk(c) => Rgb::from_color(c.to_rgba()),
@ -1364,10 +1369,8 @@ impl Color {
pub fn to_linear_rgb(self) -> Self {
Self::LinearRgb(match self {
Self::Luma(c) => LinearRgb::from_color(c),
// Perform sRGB gamut mapping by converting to Okhsv first.
// This yields better results than clamping.
Self::Oklab(c) => LinearRgb::from_color(Okhsva::from_color(c)),
Self::Oklch(c) => LinearRgb::from_color(Okhsva::from_color(c)),
Self::Oklab(c) => LinearRgb::from_color(c),
Self::Oklch(c) => LinearRgb::from_color(c),
Self::Rgb(c) => LinearRgb::from_color(c),
Self::LinearRgb(c) => c,
Self::Cmyk(c) => LinearRgb::from_color(c.to_rgba()),
@ -1379,10 +1382,8 @@ impl Color {
pub fn to_cmyk(self) -> Self {
Self::Cmyk(match self {
Self::Luma(c) => Cmyk::from_luma(c),
// Perform sRGB gamut mapping by converting to Okhsv first.
// This yields better results than clamping.
Self::Oklab(c) => Cmyk::from_rgba(Rgb::from_color(Okhsva::from_color(c))),
Self::Oklch(c) => Cmyk::from_rgba(Rgb::from_color(Okhsva::from_color(c))),
Self::Oklab(c) => Cmyk::from_rgba(Rgb::from_color(c)),
Self::Oklch(c) => Cmyk::from_rgba(Rgb::from_color(c)),
Self::Rgb(c) => Cmyk::from_rgba(c),
Self::LinearRgb(c) => Cmyk::from_rgba(Rgb::from_linear(c)),
Self::Cmyk(c) => c,
@ -1394,10 +1395,8 @@ impl Color {
pub fn to_hsl(self) -> Self {
Self::Hsl(match self {
Self::Luma(c) => Hsl::from_color(c),
// Perform sRGB gamut mapping by converting to Okhsv first.
// This yields better results than clamping.
Self::Oklab(c) => Hsl::from_color(Okhsva::from_color(c)),
Self::Oklch(c) => Hsl::from_color(Okhsva::from_color(c)),
Self::Oklab(c) => Hsl::from_color(c),
Self::Oklch(c) => Hsl::from_color(c),
Self::Rgb(c) => Hsl::from_color(c),
Self::LinearRgb(c) => Hsl::from_color(Rgb::from_linear(c)),
Self::Cmyk(c) => Hsl::from_color(c.to_rgba()),
@ -1409,10 +1408,8 @@ impl Color {
pub fn to_hsv(self) -> Self {
Self::Hsv(match self {
Self::Luma(c) => Hsv::from_color(c),
// Perform sRGB gamut mapping by converting to Okhsv first.
// This yields better results than clamping.
Self::Oklab(c) => Hsv::from_color(Okhsva::from_color(c)),
Self::Oklch(c) => Hsv::from_color(Okhsva::from_color(c)),
Self::Oklab(c) => Hsv::from_color(c),
Self::Oklch(c) => Hsv::from_color(c),
Self::Rgb(c) => Hsv::from_color(c),
Self::LinearRgb(c) => Hsv::from_color(Rgb::from_linear(c)),
Self::Cmyk(c) => Hsv::from_color(c.to_rgba()),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -8,7 +8,7 @@
spacing: 1fr,
rect(width: 1cm, fill: cmyk(69%, 11%, 69%, 41%)),
rect(width: 1cm, fill: c),
rect(width: 1cm, fill: c.negate()),
rect(width: 1cm, fill: c.negate(space: cmyk)),
)
#for x in range(0, 11) {
@ -82,4 +82,4 @@
// Ref: false
#test-repr(luma(20%).lighten(50%), luma(60%))
#test-repr(luma(80%).darken(20%), luma(64%))
#test-repr(luma(80%).negate(), luma(20%))
#test-repr(luma(80%).negate(space: luma), luma(20%))

View File

@ -11,7 +11,7 @@
// Test color modification methods.
#test(rgb(25, 35, 45).lighten(10%), rgb(48, 57, 66))
#test(rgb(40, 30, 20).darken(10%), rgb(36, 27, 18))
#test(rgb("#133337").negate(), rgb(236, 204, 200))
#test(rgb("#133337").negate(space: rgb), rgb(236, 204, 200))
#test(white.lighten(100%), white)
// Color mixing, in Oklab space by default.