From b14274d1e44050ab977b2a31e69590080898996b Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Sun, 25 Sep 2022 20:04:33 +0200 Subject: [PATCH] Methods to modify colors --- src/eval/methods.rs | 7 ++ src/geom/paint.rs | 126 +++++++++++++++++++++++++++++++++++- tests/ref/utility/color.png | Bin 271 -> 595 bytes tests/typ/utility/color.typ | 25 ++++++- 4 files changed, 154 insertions(+), 4 deletions(-) diff --git a/src/eval/methods.rs b/src/eval/methods.rs index 19f3d65ee..57fff681b 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 522db1bed..121bb003e 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 a03795f7ffa8e5ec512c9aba6c85ce231e257fb5..496013bb7b7bf28dcf7487b5f73e21d509659787 100644 GIT binary patch literal 595 zcmeAS@N?(olHy`uVBq!ia0y~yU}OPeeGX=z$dtYQXMvPQfKQ0)|NsBrzWjRk+S@Z{ z&YV5`Z10A<>*rlt(0hJj_3`HH16dtPWzjqJ+Vrw~Hig+ubTKS8QHxNQag-L;5#*8O zVi#axW@lt%0vguMduR^Of@7X8jv*Dd-rlt4a#j>zy`cTlDQN#+{qt6ev3A@XEEAVc zc)sK&Ka*^4CKpw$R zPlQNfWs5R9PIU(24}brEay6imfAyEIO_){~$RDiWMbW)0e&SPpZBZ04a4;V#VR>wd kHH^0?t&v7Z*neUy%9fv&wDz_SFk%@zUHx3vIVCg!02knqL;wH) literal 271 zcmeAS@N?(olHy`uVBq!ia0y~yU}OQZpRq6l$%A*FfjImDJ|V9E|NlR8=8RsOUY5@$ zpcr4cx*?F-?djqeQgQ3;?Sq^R20U#S<6|7c3z!ya@A&UyE52vRbge!4P37JSD(62n zH8eDQ2z3%`1=3*fA>AqWS3j3^P6