Repository: typst/typst
File: crates/typst/src/visualize/color.rs (Rust)

Code before:
            Color::Rgb(c) => Color::Rgb(transform(c, factor)),
            Color::LinearRgb(c) => Color::LinearRgb(transform(c, factor)),
            Color::Cmyk(_) => bail!("CMYK does have an alpha component"),
            Color::Hsl(c) => Color::Hsl(transform(c, factor)),
            Color::Hsv(c) => Color::Hsv(transform(c, factor)),
        })
    }

    /// Make a color more opaque by a given factor.
    ///
    /// This method is relative to the existing alpha value.
    ///
    /// ```example
    /// #block(fill: red)[opaque]
    /// #block(fill: red.opacify(50%))[half red]
    /// #block(fill: red.opacify(75%))[quarter red]
    /// ```
    #[func]
    pub fn opacify(
        self,
        /// The factor to change the alpha value by.
        factor: Ratio,
    ) -> StrResult<Color> {
        #[inline]
        fn transform<C>(mut color: Alpha<C, f32>, factor: Ratio) -> Alpha<C, f32> {
            color.alpha = (color.alpha * (1.0 + factor.get() as f32)).clamp(0.0, 1.0);
            color
        }

        Ok(match self {
            Color::Luma(c) => Color::Luma(transform(c, factor)),
            Color::Oklab(c) => Color::Oklab(transform(c, factor)),
            Color::Oklch(c) => Color::Oklch(transform(c, factor)),
            Color::Rgb(c) => Color::Rgb(transform(c, factor)),
            Color::LinearRgb(c) => Color::LinearRgb(transform(c, factor)),
            Color::Cmyk(_) => bail!("CMYK does have an alpha component"),
            Color::Hsl(c) => Color::Hsl(transform(c, factor)),
            Color::Hsv(c) => Color::Hsv(transform(c, factor)),
        })
    }
}

impl Color {
    /// Same as [`Color::mix`], but takes an iterator instead of a vector.
    pub fn mix_iter(
        colors: impl IntoIterator<
            Item = WeightedColor,
            IntoIter = impl ExactSizeIterator<Item = WeightedColor>,
        >,
        space: ColorSpace,
    ) -> StrResult<Color> {

Review comment (suggestion):
This is not in line with what Sass recommends for their `color.scale` method: They take the remainder between the current alpha and a totally opaque color and scale that, so that would work like this:
```suggestion
            color.alpha = (factor.get() as f32 * (1.0 - color.alpha) + color.alpha).clamp(0.0, 1.0);
```

Code after:
    ///
    /// ```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 {
    /// Same as [`Color::mix`], but takes an iterator instead of a vector.
    pub fn mix_iter(
        colors: impl IntoIterator<
            Item = WeightedColor,
            IntoIter = impl ExactSizeIterator<Item = WeightedColor>,
        >,
        space: ColorSpace,
    ) -> StrResult<Color> {
        let mut colors = colors.into_iter();
        if space.hue_index().is_some() && colors.len() > 2 {
            bail!("cannot mix more than two colors in a hue-based space");
        }

        let m = if space.hue_index().is_some() && colors.len() == 2 {
            let mut m = [0.0; 4];

            let WeightedColor { color: c0, weight: w0 } = colors.next().unwrap();
            let WeightedColor { color: c1, weight: w1 } = colors.next().unwrap();

            let c0 = c0.to_space(space).to_vec4();
            let c1 = c1.to_space(space).to_vec4();
            let w0 = w0 as f32;
            let w1 = w1 as f32;

            if w0 + w1 <= 0.0 {
                bail!("sum of weights must be positive");
            }

            for i in 0..4 {
                m[i] = (w0 * c0[i] + w1 * c1[i]) / (w0 + w1);
            }

            // Ensure that the hue circle is traversed in the short direction.