diff options
| author | frozolotl <44589151+frozolotl@users.noreply.github.com> | 2024-02-28 15:09:13 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-28 14:09:13 +0000 |
| commit | 9d8df00ffb587f1e6062ae471d3da3b1ac61ba9e (patch) | |
| tree | 621ff09d5c66f411d0a838de22be38d8fe57bb55 | |
| parent | a518e2dd4d829b45b0887da28acb77d0568894ab (diff) | |
Implement alpha modification methods for colors (#3516)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
| -rw-r--r-- | crates/typst/src/visualize/color.rs | 68 | ||||
| -rw-r--r-- | tests/typ/compiler/color.typ | 16 |
2 files changed, 83 insertions, 1 deletions
diff --git a/crates/typst/src/visualize/color.rs b/crates/typst/src/visualize/color.rs index f51875bc..2821ba7c 100644 --- a/crates/typst/src/visualize/color.rs +++ b/crates/typst/src/visualize/color.rs @@ -6,7 +6,7 @@ use ecow::{eco_format, EcoString, EcoVec}; use once_cell::sync::Lazy; use palette::encoding::{self, Linear}; use palette::{ - Darken, Desaturate, FromColor, Lighten, OklabHue, RgbHue, Saturate, ShiftHue, + Alpha, Darken, Desaturate, FromColor, Lighten, OklabHue, RgbHue, Saturate, ShiftHue, }; use qcms::Profile; @@ -1128,6 +1128,47 @@ impl Color { ) -> StrResult<Color> { Self::mix_iter(colors, space) } + + /// Makes a color more transparent by a given factor. + /// + /// This method is relative to the existing alpha value. + /// If the scale is positive, calculates `alpha - alpha * scale`. + /// Negative scales behave like `color.opacify(-scale)`. + /// + /// ```example + /// #block(fill: red)[opaque] + /// #block(fill: red.transparentize(50%))[half red] + /// #block(fill: red.transparentize(75%))[quarter red] + /// ``` + #[func] + pub fn transparentize( + self, + /// The factor to change the alpha value by. + scale: Ratio, + ) -> StrResult<Color> { + self.scale_alpha(-scale) + } + + /// Makes a color more opaque by a given scale. + /// + /// This method is relative to the existing alpha value. + /// If the scale is positive, calculates `alpha + scale - alpha * scale`. + /// Negative scales behave like `color.transparentize(-scale)`. + /// + /// ```example + /// #let half-red = red.transparentize(50%) + /// #block(fill: half-red.opacify(100%))[opaque] + /// #block(fill: half-red.opacify(50%))[three quarters red] + /// #block(fill: half-red.opacify(-50%))[one quarter red] + /// ``` + #[func] + pub fn opacify( + self, + /// The scale to change the alpha value by. + scale: Ratio, + ) -> StrResult<Color> { + self.scale_alpha(scale) + } } impl Color { @@ -1265,6 +1306,31 @@ impl Color { self } + /// Scales the alpha value of a color by a given amount. + /// + /// For positive scales, computes `alpha + scale - alpha * scale`. + /// For non-positive scales, computes `alpha + alpha * scale`. + fn scale_alpha(self, scale: Ratio) -> StrResult<Color> { + #[inline] + fn transform<C>(mut color: Alpha<C, f32>, scale: Ratio) -> Alpha<C, f32> { + let scale = scale.get() as f32; + let factor = if scale > 0.0 { 1.0 - color.alpha } else { color.alpha }; + color.alpha = (color.alpha + scale * factor).clamp(0.0, 1.0); + color + } + + Ok(match self { + Color::Luma(c) => Color::Luma(transform(c, scale)), + Color::Oklab(c) => Color::Oklab(transform(c, scale)), + Color::Oklch(c) => Color::Oklch(transform(c, scale)), + Color::Rgb(c) => Color::Rgb(transform(c, scale)), + Color::LinearRgb(c) => Color::LinearRgb(transform(c, scale)), + Color::Cmyk(_) => bail!("CMYK does not have an alpha component"), + Color::Hsl(c) => Color::Hsl(transform(c, scale)), + Color::Hsv(c) => Color::Hsv(transform(c, scale)), + }) + } + /// Converts the color to a vec of four floats. pub fn to_vec4(&self) -> [f32; 4] { match self { diff --git a/tests/typ/compiler/color.typ b/tests/typ/compiler/color.typ index f165ac0f..ac83355d 100644 --- a/tests/typ/compiler/color.typ +++ b/tests/typ/compiler/color.typ @@ -83,3 +83,19 @@ #test-repr(luma(20%).lighten(50%), luma(60%)) #test-repr(luma(80%).darken(20%), luma(64%)) #test-repr(luma(80%).negate(space: luma), luma(20%)) + +--- +// Test alpha modification. +// Ref: false +#test-repr(luma(100%, 100%).transparentize(50%), luma(100%, 50%)) +#test-repr(luma(100%, 100%).transparentize(75%), luma(100%, 25%)) +#test-repr(luma(100%, 50%).transparentize(50%), luma(100%, 25%)) +#test-repr(luma(100%, 10%).transparentize(250%), luma(100%, 0%)) +#test-repr(luma(100%, 40%).transparentize(-50%), luma(100%, 70%)) +#test-repr(luma(100%, 0%).transparentize(-100%), luma(100%, 100%)) + +#test-repr(luma(100%, 50%).opacify(50%), luma(100%, 75%)) +#test-repr(luma(100%, 20%).opacify(100%), luma(100%, 100%)) +#test-repr(luma(100%, 100%).opacify(250%), luma(100%, 100%)) +#test-repr(luma(100%, 50%).opacify(-50%), luma(100%, 25%)) +#test-repr(luma(100%, 0%).opacify(0%), luma(100%, 0%)) |
