summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/eval/methods.rs7
-rw-r--r--src/geom/paint.rs126
-rw-r--r--tests/ref/utility/color.pngbin271 -> 595 bytes
-rw-r--r--tests/typ/utility/color.typ25
4 files changed, 154 insertions, 4 deletions
diff --git a/src/eval/methods.rs b/src/eval/methods.rs
index 19f3d65e..57fff681 100644
--- a/src/eval/methods.rs
+++ b/src/eval/methods.rs
@@ -108,6 +108,13 @@ pub fn call(
_ => return missing(),
},
+ Value::Color(color) => match method {
+ "lighten" => Value::Color(color.lighten(args.expect("amount")?)),
+ "darken" => Value::Color(color.darken(args.expect("amount")?)),
+ "negate" => Value::Color(color.negate()),
+ _ => return missing(),
+ },
+
_ => return missing(),
};
diff --git a/src/geom/paint.rs b/src/geom/paint.rs
index 522db1be..121bb003 100644
--- a/src/geom/paint.rs
+++ b/src/geom/paint.rs
@@ -67,6 +67,37 @@ impl Color {
Self::Cmyk(cmyk) => cmyk.to_rgba(),
}
}
+
+ /// Lighten this color by the given factor.
+ pub fn lighten(self, factor: Ratio) -> Self {
+ let ratio = factor.get();
+
+ match self {
+ Self::Luma(luma) => Self::Luma(luma.lighten(ratio)),
+ Self::Rgba(rgba) => Self::Rgba(rgba.lighten(ratio)),
+ Self::Cmyk(cmyk) => Self::Cmyk(cmyk.lighten(ratio)),
+ }
+ }
+
+ /// Darken this color by the given factor.
+ pub fn darken(self, factor: Ratio) -> Self {
+ let ratio = factor.get();
+
+ match self {
+ Self::Luma(luma) => Self::Luma(luma.darken(ratio)),
+ Self::Rgba(rgba) => Self::Rgba(rgba.darken(ratio)),
+ Self::Cmyk(cmyk) => Self::Cmyk(cmyk.darken(ratio)),
+ }
+ }
+
+ /// Negate this color.
+ pub fn negate(self) -> Self {
+ match self {
+ Self::Luma(luma) => Self::Luma(luma.negate()),
+ Self::Rgba(rgba) => Self::Rgba(rgba.negate()),
+ Self::Cmyk(cmyk) => Self::Cmyk(cmyk.negate()),
+ }
+ }
}
impl Debug for Color {
@@ -91,7 +122,7 @@ impl LumaColor {
/// Convert to an opque RGBA color.
pub const fn to_rgba(self) -> RgbaColor {
- RgbaColor::new(self.0, self.0, self.0, 255)
+ RgbaColor::new(self.0, self.0, self.0, u8::MAX)
}
/// Convert to CMYK as a fraction of true black.
@@ -103,6 +134,21 @@ impl LumaColor {
(self.0 as f64 * 0.90) as u8,
)
}
+
+ /// Lighten this color by a factor.
+ pub fn lighten(self, factor: f64) -> Self {
+ Self(self.0.saturating_add(((u8::MAX - self.0) as f64 * factor) as u8))
+ }
+
+ /// Darken this color by a factor.
+ pub fn darken(self, factor: f64) -> Self {
+ Self(self.0.saturating_sub((self.0 as f64 * factor) as u8))
+ }
+
+ /// Negate this color.
+ pub fn negate(self) -> Self {
+ Self(u8::MAX - self.0)
+ }
}
impl Debug for LumaColor {
@@ -135,6 +181,46 @@ impl RgbaColor {
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
+
+ // Lighten this color by a factor.
+ //
+ // The alpha channel is not affected.
+ pub fn lighten(self, factor: f64) -> Self {
+ let lighten = |c: u8| c.saturating_add(((u8::MAX - c) as f64 * factor) as u8);
+
+ Self {
+ r: lighten(self.r),
+ g: lighten(self.g),
+ b: lighten(self.b),
+ a: self.a,
+ }
+ }
+
+ // Darken this color by a factor.
+ //
+ // The alpha channel is not affected.
+ pub fn darken(self, factor: f64) -> Self {
+ let darken = |c: u8| c.saturating_sub((c as f64 * factor) as u8);
+
+ Self {
+ r: darken(self.r),
+ g: darken(self.g),
+ b: darken(self.b),
+ a: self.a,
+ }
+ }
+
+ // Negate this color.
+ //
+ // The alpha channel is not affected.
+ pub fn negate(self) -> Self {
+ Self {
+ r: u8::MAX - self.r,
+ g: u8::MAX - self.g,
+ b: u8::MAX - self.b,
+ a: self.a,
+ }
+ }
}
impl FromStr for RgbaColor {
@@ -161,7 +247,7 @@ impl FromStr for RgbaColor {
return Err("string has wrong length");
}
- let mut values: [u8; 4] = [255; 4];
+ let mut values: [u8; 4] = [u8::MAX; 4];
for elem in if alpha { 0 .. 4 } else { 0 .. 3 } {
let item_len = if long { 2 } else { 1 };
@@ -250,6 +336,42 @@ impl CmykColor {
a: 255,
}
}
+
+ /// Lighten this color by a factor.
+ pub fn lighten(self, factor: f64) -> Self {
+ let lighten = |c: u8| c.saturating_sub((c as f64 * factor) as u8);
+
+ Self {
+ c: lighten(self.c),
+ m: lighten(self.m),
+ y: lighten(self.y),
+ k: lighten(self.k),
+ }
+ }
+
+ /// Darken this color by a factor.
+ pub fn darken(self, factor: f64) -> Self {
+ let darken = |c: u8| c.saturating_add(((u8::MAX - c) as f64 * factor) as u8);
+
+ Self {
+ c: darken(self.c),
+ m: darken(self.m),
+ y: darken(self.y),
+ k: darken(self.k),
+ }
+ }
+
+ /// Negate this color.
+ ///
+ /// Does not affect the key component.
+ pub fn negate(self) -> Self {
+ Self {
+ c: u8::MAX - self.c,
+ m: u8::MAX - self.m,
+ y: u8::MAX - self.y,
+ k: self.k,
+ }
+ }
}
impl Debug for CmykColor {
diff --git a/tests/ref/utility/color.png b/tests/ref/utility/color.png
index a03795f7..496013bb 100644
--- a/tests/ref/utility/color.png
+++ b/tests/ref/utility/color.png
Binary files differ
diff --git a/tests/typ/utility/color.typ b/tests/typ/utility/color.typ
index 449655bf..3ff141d4 100644
--- a/tests/typ/utility/color.typ
+++ b/tests/typ/utility/color.typ
@@ -8,6 +8,12 @@
// Alpha channel.
#test(rgb(255, 0, 0, 50%), rgb("ff000080"))
+// 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(white.lighten(100%), white)
+
---
// Test gray color conversion.
// Ref: true
@@ -15,10 +21,25 @@
#rect(fill: luma(80%))
---
+// Test gray color modification.
+#test(luma(20%).lighten(50%), luma(60%))
+#test(luma(80%).darken(20%), luma(64.5%))
+#test(luma(80%).negate(), luma(20%))
+
+---
// Test CMYK color conversion.
// Ref: true
-#rect(fill: cmyk(69%, 11%, 69%, 41%))
-#rect(fill: cmyk(50%, 64%, 16%, 17%))
+#let c = cmyk(50%, 64%, 16%, 17%)
+#rect(width: 1cm, fill: cmyk(69%, 11%, 69%, 41%))
+#rect(width: 1cm, fill: c)
+#rect(width: 1cm, fill: c.negate())
+
+#for x in range(0, 11) {
+ square(width: 9pt, fill: c.lighten(x * 10%))
+}
+#for x in range(0, 11) {
+ square(width: 9pt, fill: c.darken(x * 10%))
+}
---
// Error for values that are out of range.