zng_color/
mix.rs

1use super::{Hsla, Hsva, PreMulRgba, Rgba, clamp_normal};
2use zng_layout::unit::Factor;
3
4use pastey::*;
5
6pub use zng_view_api::MixBlendMode;
7
8macro_rules! impl_mix {
9    (
10        separable {$(
11            $Mode:ident => |$fg:ident, $bg:ident, $ca0:ident, $ca1:ident| $mix:expr,
12        )+}
13
14        non_separable {$(
15            $NsMode:ident => |[$fgh:ident, $fgs:ident, $fgl:ident], [$bgh:ident, $bgs:ident, $bgl:ident]| $ns_mix:expr,
16        )+}
17    ) => {
18        /// Color mix and adjustment methods.
19        pub trait MixAdjust {
20            /// MixAdjust `self` over `background` using the `mode`.
21            fn mix(self, mode: MixBlendMode, background: Self) -> Self where Self:Sized {
22                match mode {
23                    $(MixBlendMode::$Mode => paste!(self.[<mix_ $Mode:lower>](background)),)+
24                    $(MixBlendMode::$NsMode => paste!(self.[<mix_ $NsMode:lower>](background)),)+
25                    _ => unreachable!()
26                }
27            }
28
29            $(
30                paste! {
31                    #[doc = "MixAdjust `self` over `background` using the [`MixBlendMode::" $Mode "`]."]
32                    ///
33                    fn [<mix_ $Mode:lower>](self, background: Self) -> Self;
34                }
35            )+
36
37            $(
38                paste! {
39                    #[doc = "MixAdjust `self` over `background` using the [`MixBlendMode::`" $NsMode "`]."]
40                    ///
41                    /// This method converts both inputs to [`Hsla`] and the result back to `Rgba`.
42                    fn [<mix_ $NsMode:lower>](self, background: Self) -> Self;
43                }
44            )+
45
46            /// Adds the `amount` to the color *lightness*.
47            ///
48            /// # Examples
49            ///
50            /// Add `10%` of the current lightness to the `DARK_RED` color:
51            ///
52            /// ```
53            /// # use zng_color::*;
54            /// # use zng_layout::unit::*;
55            /// web_colors::DARK_RED.lighten(10.pct())
56            /// # ;
57            /// ```
58            fn lighten<A: Into<Factor>>(self, amount: A) -> Self;
59
60            /// Subtracts the `amount` from the color *lightness*.
61            ///
62            /// # Examples
63            ///
64            /// Removes `10%` of the current lightness from the `DARK_RED` color:
65            ///
66            /// ```
67            /// # use zng_color::*;
68            /// # use zng_layout::unit::*;
69            /// web_colors::DARK_RED.darken(10.pct())
70            /// # ;
71            fn darken<A: Into<Factor>>(self, amount: A) -> Self;
72
73            /// Subtracts the `amount` from the color *saturation*.
74            ///
75            /// # Examples
76            ///
77            /// Removes `10%` of the current saturation from the `RED` color:
78            ///
79            /// ```
80            /// # use zng_color::*;
81            /// # use zng_layout::unit::*;
82            /// colors::RED.desaturate(10.pct())
83            /// # ;
84            fn desaturate<A: Into<Factor>>(self, amount: A) -> Self;
85
86            /// Returns a copy of this color with a new `lightness`.
87            fn with_lightness<L: Into<Factor>>(self, lightness: L) -> Self;
88        }
89
90        impl PreMulRgba {
91            $(
92                paste! {
93                    fn [<mix_ $Mode:lower _impl>](self, background: PreMulRgba) -> PreMulRgba {
94                        PreMulRgba {
95                            red: {
96                                let $fg = self.red;
97                                let $bg = background.red;
98                                let $ca0 = self.alpha;
99                                let $ca1 = background.alpha;
100                                $mix
101                            },
102                            green: {
103                                let $fg = self.green;
104                                let $bg = background.green;
105                                let $ca0 = self.alpha;
106                                let $ca1 = background.alpha;
107                                $mix
108                            },
109                            blue: {
110                                let $fg = self.blue;
111                                let $bg = background.blue;
112                                let $ca0 = self.alpha;
113                                let $ca1 = background.alpha;
114                                $mix
115                            },
116                            alpha: {
117                                let fga = self.alpha;
118                                let bga = background.alpha;
119                                (fga + bga - fga * bga).max(0.0).min(1.0)
120                            },
121                        }
122                    }
123                }
124            )+
125        }
126
127        impl Hsla {
128            $(
129                paste! {
130                    fn [<mix_ $NsMode:lower _impl>](self, background: Hsla) -> Hsla {
131                        let $fgh = self.hue;
132                        let $fgs = self.saturation;
133                        let $fgl = self.lightness;
134
135                        let $bgh = background.hue;
136                        let $bgs = background.saturation;
137                        let $bgl = background.lightness;
138
139                        let [h, s, l] = { $ns_mix };
140
141                        Hsla {
142                            hue: h,
143                            saturation: s,
144                            lightness: l,
145                            alpha: {
146                                let fga = self.alpha;
147                                let bga = background.alpha;
148                                (fga + bga - fga * bga).clamp(0.0, 1.0)
149                            }
150                        }
151                    }
152                }
153            )+
154        }
155
156        impl MixAdjust for PreMulRgba {
157            $(
158                paste! {
159                    fn [<mix_ $Mode:lower>](self, background: Self) -> Self {
160                       self.[<mix_ $Mode:lower _impl>](background)
161                    }
162                }
163            )+
164            $(
165                paste! {
166                    fn [<mix_ $NsMode:lower>](self, background: Self) -> Self {
167                        Hsla::from(self).[<mix_ $NsMode:lower>](Hsla::from(background)).into()
168                    }
169                }
170            )+
171
172            fn lighten<A: Into<Factor>>(self, amount: A) -> Self {
173                Hsla::from(self).lighten(amount.into()).into()
174            }
175
176            fn darken<A: Into<Factor>>(self, amount: A) -> Self {
177                Hsla::from(self).darken(amount.into()).into()
178            }
179
180            fn desaturate<A: Into<Factor>>(self, amount: A) -> Self {
181                Hsla::from(self).desaturate(amount).into()
182            }
183
184            fn with_lightness<L: Into<Factor>>(self, lightness: L) -> Self {
185                Hsla::from(self).with_lightness(lightness).into()
186            }
187        }
188
189        impl MixAdjust for Hsla {
190            $(
191                paste! {
192                    fn [<mix_ $Mode:lower>](self, background: Self) -> Self {
193                        PreMulRgba::from(self).[<mix_ $Mode:lower>](PreMulRgba::from(background)).into()
194                    }
195                }
196            )+
197
198            $(
199                paste! {
200                    fn [<mix_ $NsMode:lower>](self, background: Self) -> Self {
201                       self.[<mix_ $NsMode:lower _impl>](background)
202                    }
203                }
204            )+
205
206            fn lighten<A: Into<Factor>>(self, amount: A) -> Self {
207                let mut lighter = self;
208                lighter.lightness = clamp_normal(lighter.lightness + (lighter.lightness * amount.into().0));
209                lighter
210            }
211
212            fn darken<A: Into<Factor>>(self, amount: A) -> Self {
213                let mut darker = self;
214                darker.lightness = clamp_normal(darker.lightness - (darker.lightness * amount.into().0));
215                darker
216            }
217
218            fn desaturate<A: Into<Factor>>(self, amount: A) -> Self {
219                let mut d = self;
220                d.saturation = clamp_normal(d.saturation - (d.saturation * amount.into().0));
221                d
222            }
223
224            fn with_lightness<L: Into<Factor>>(mut self, lightness: L) -> Self {
225                self.set_lightness(lightness);
226                self
227            }
228        }
229
230        impl MixAdjust for Rgba {
231            $(
232                paste! {
233                    fn [<mix_ $Mode:lower>](self, background: Self) -> Self {
234                        PreMulRgba::from(self).[<mix_ $Mode:lower>](PreMulRgba::from(background)).into()
235                    }
236                }
237            )+
238            $(
239                paste! {
240                    fn [<mix_ $NsMode:lower>](self, background: Self) -> Self {
241                        Hsla::from(self).[<mix_ $NsMode:lower>](Hsla::from(background)).into()
242                    }
243                }
244            )+
245
246            fn lighten<A: Into<Factor>>(self, amount: A) -> Self {
247                Hsla::from(self).lighten(amount.into()).into()
248            }
249
250            fn darken<A: Into<Factor>>(self, amount: A) -> Self {
251                Hsla::from(self).darken(amount.into()).into()
252            }
253
254            fn desaturate<A: Into<Factor>>(self, amount: A) -> Self {
255                Hsla::from(self).desaturate(amount).into()
256            }
257
258            fn with_lightness<L: Into<Factor>>(self, lightness: L) -> Self {
259                Hsla::from(self).with_lightness(lightness).into()
260            }
261        }
262
263        impl MixAdjust for Hsva {
264            $(
265                paste! {
266                    fn [<mix_ $Mode:lower>](self, background: Self) -> Self {
267                        PreMulRgba::from(self).[<mix_ $Mode:lower>](PreMulRgba::from(background)).into()
268                    }
269                }
270            )+
271            $(
272                paste! {
273                    fn [<mix_ $NsMode:lower>](self, background: Self) -> Self {
274                        Hsla::from(self).[<mix_ $NsMode:lower>](Hsla::from(background)).into()
275                    }
276                }
277            )+
278
279            fn lighten<A: Into<Factor>>(self, amount: A) -> Self {
280                Hsla::from(self).lighten(amount.into()).into()
281            }
282
283            fn darken<A: Into<Factor>>(self, amount: A) -> Self {
284                Hsla::from(self).darken(amount.into()).into()
285            }
286
287            fn desaturate<A: Into<Factor>>(self, amount: A) -> Self {
288                Hsla::from(self).desaturate(amount).into()
289            }
290
291            fn with_lightness<L: Into<Factor>>(self, lightness: L) -> Self {
292                Hsla::from(self).with_lightness(lightness).into()
293            }
294        }
295    };
296}
297
298#[rustfmt::skip]// zng fmt can't handle this syntax and is slightly slower because it causes rustfmt errors
299impl_mix! {
300    separable {
301        Normal => |fg, bg, fga, _bga| fg + bg * (1.0 - fga),
302
303        Multiply => |fg, bg, fga, bga| fg * bg + fg * (1.0 - bga) + bg * (1.0 - fga),
304
305        Screen => |fg, bg, _fga, _bga| fg + bg - fg * bg,
306
307        Overlay => |fg, bg, fga, bga| if bg * 2.0 <= bga {
308            2.0 * fg * bg + fg * (1.0 - bga) + bg * (1.0 - fga)
309        } else {
310            fg * (1.0 + bga) + bg * (1.0 + fga) - 2.0 * fg * bg - fga * bga
311        },
312
313        Darken => |fg, bg, fga, bga| (fg * bga).min(bg * fga) + fg * (1.0 - bga) + bg * (1.0 - fga),
314
315        Lighten => |fg, bg, fga, bga| (fg * bga).max(bg * fga) + fg * (1.0 - bga) + bg * (1.0 - fga),
316
317        ColorDodge => |fg, bg, fga, bga| if fg == fga {
318            fga * bga + fg * (1.0 - bga) + bg * (1.0 - fga)
319        } else {
320            fga * bga * 1.0_f32.min((bg / bga) * fga / (fga - fg)) + fg * (1.0 - bga) + bg * (1.0 - fga)
321        },
322
323        ColorBurn => |fg, bg, fga, bga| fga * bga * (1.0 - 1.0_f32.min((1.0 - bg / bga) * fga / fg)) + fg * (1.0 - bga) + bg * (1.0 - fga),
324
325        HardLight => |fg, bg, fga, bga| if fg * 2.0 <= fga {
326            2.0 * fg * bg + fg * (1.0 - bga) + bg * (1.0 - fga)
327        } else {
328            fg * (1.0 + bga) + bg * (1.0 + fga) - 2.0 * fg * bg - fga * bga
329        },
330
331        SoftLight => |fg, bg, fga, bga| {
332            let m = bg / bga;
333
334            if fg * 2.0 <= fga {
335                bg * (fga + (2.0 * fg - fga) * (1.0 - m)) + fg * (1.0 - bga) + bg * (1.0 - fga)
336            } else if bg * 4.0 <= bga {
337                let m2 = m * m;
338                let m3 = m2 * m;
339                bga * (2.0 * fg - fga) * (m3 * 16.0 - m2 * 12.0 - m * 3.0) + fg - fg * bga + bg
340            } else {
341                bga * (2.0 * fg - fga) * (m.sqrt() - m) + fg - fg * bga + bg
342            }
343        },
344
345        Difference => |fg, bg, fga, bga| fg + bg - 2.0 * (fg * bga).min(bg * fga),
346
347        Exclusion => |fg, bg, _fga, _bga| fg + bg - 2.0 * fg * bg,
348
349        PlusLighter => |fg, bg, fga, bga| (bg * bga + fg * fga).clamp(0.0, 1.0),
350    }
351
352    non_separable {
353        Hue => |[fgh, _fgs, _fgl], [_bgh, bgs, bgl]| [fgh, bgs, bgl],
354
355        Saturation => |[_fgh, fgs, _fgl], [bgh, _bgs, bgl]| [bgh, fgs, bgl],
356
357        Color => |[fgh, fgs, _fgl], [_bgh, _bgs, bgl]| [fgh, fgs, bgl],
358
359        Luminosity => |[fgh, _fgs, _fbl], [bgh, bgs, _bgl]| [bgh, bgs, fgh],
360    }
361}