summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfrozolotl <44589151+frozolotl@users.noreply.github.com>2024-02-27 12:15:17 +0100
committerGitHub <noreply@github.com>2024-02-27 11:15:17 +0000
commit79615a01bd4266e6a5adb385650735768ea96d56 (patch)
tree7a5844b3174bfc2602d6eeb32e03ed040e3b2bb8
parent0aa925435628f5a6f29478e7e19ed2d4f50eabf8 (diff)
Improve color negation (#3500)
-rw-r--r--crates/typst/src/visualize/color.rs81
-rw-r--r--tests/ref/compiler/color.pngbin1407 -> 1354 bytes
-rw-r--r--tests/typ/compiler/color.typ4
-rw-r--r--tests/typ/compute/construct.typ2
4 files changed, 42 insertions, 45 deletions
diff --git a/crates/typst/src/visualize/color.rs b/crates/typst/src/visualize/color.rs
index e22f668c..29bd3fca 100644
--- a/crates/typst/src/visualize/color.rs
+++ b/crates/typst/src/visualize/color.rs
@@ -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()),
diff --git a/tests/ref/compiler/color.png b/tests/ref/compiler/color.png
index 69dbe5e8..2b416f64 100644
--- a/tests/ref/compiler/color.png
+++ b/tests/ref/compiler/color.png
Binary files differ
diff --git a/tests/typ/compiler/color.typ b/tests/typ/compiler/color.typ
index ec1f9902..f165ac0f 100644
--- a/tests/typ/compiler/color.typ
+++ b/tests/typ/compiler/color.typ
@@ -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%))
diff --git a/tests/typ/compute/construct.typ b/tests/typ/compute/construct.typ
index dcf59755..e429efc5 100644
--- a/tests/typ/compute/construct.typ
+++ b/tests/typ/compute/construct.typ
@@ -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.