palette/blend/
blend.rs

1use crate::{
2    bool_mask::LazySelect,
3    cast::{self, ArrayCast},
4    num::{Abs, Arithmetics, Clamp, MinMax, One, PartialCmp, Real, Sqrt, Zero},
5    stimulus::{Stimulus, StimulusColor},
6    Alpha,
7};
8
9use super::{blend_alpha, PreAlpha, Premultiply};
10
11/// A trait for different ways of mixing colors together.
12///
13/// This implements the classic separable blend modes, [as described by
14/// W3C](https://www.w3.org/TR/compositing-1/#blending).
15///
16/// _Note: The default implementations of the blend modes are meant for color
17/// components in the range [0.0, 1.0] and may otherwise produce strange
18/// results._
19pub trait Blend {
20    /// Multiply `self` with `other`. This uses the alpha component to regulate
21    /// the effect, so it's not just plain component wise multiplication.
22    #[must_use]
23    fn multiply(self, other: Self) -> Self;
24
25    /// Make a color which is at least as light as `self` or `other`.
26    #[must_use]
27    fn screen(self, other: Self) -> Self;
28
29    /// Multiply `self` or `other` if other is dark, or screen them if `other`
30    /// is light. This results in an S curve.
31    #[must_use]
32    fn overlay(self, other: Self) -> Self;
33
34    /// Return the darkest parts of `self` and `other`.
35    #[must_use]
36    fn darken(self, other: Self) -> Self;
37
38    /// Return the lightest parts of `self` and `other`.
39    #[must_use]
40    fn lighten(self, other: Self) -> Self;
41
42    /// Lighten `other` to reflect `self`. Results in `other` if `self` is
43    /// black.
44    #[must_use]
45    fn dodge(self, other: Self) -> Self;
46
47    /// Darken `other` to reflect `self`. Results in `other` if `self` is
48    /// white.
49    #[must_use]
50    fn burn(self, other: Self) -> Self;
51
52    /// Multiply `self` or `other` if other is dark, or screen them if `self`
53    /// is light. This is similar to `overlay`, but depends on `self` instead
54    /// of `other`.
55    #[must_use]
56    fn hard_light(self, other: Self) -> Self;
57
58    /// Lighten `other` if `self` is light, or darken `other` as if it's burned
59    /// if `self` is dark. The effect is increased if the components of `self`
60    /// is further from 0.5.
61    #[must_use]
62    fn soft_light(self, other: Self) -> Self;
63
64    /// Return the absolute difference between `self` and `other`. It's
65    /// basically `abs(self - other)`, but regulated by the alpha component.
66    #[must_use]
67    fn difference(self, other: Self) -> Self;
68
69    /// Similar to `difference`, but appears to result in a lower contrast.
70    /// `other` is inverted if `self` is white, and preserved if `self` is
71    /// black.
72    #[must_use]
73    fn exclusion(self, other: Self) -> Self;
74}
75
76impl<C, T, const N: usize> Blend for PreAlpha<C>
77where
78    C: Premultiply<Scalar = T> + StimulusColor + ArrayCast<Array = [T; N]> + Clone,
79    T: Real + Zero + One + MinMax + Clamp + Sqrt + Abs + Arithmetics + PartialCmp + Clone,
80    T::Mask: LazySelect<T>,
81{
82    #[inline]
83    fn multiply(self, other: Self) -> Self {
84        blend_separable(self.into(), other.into(), multiply_blend)
85    }
86
87    #[inline]
88    fn screen(self, other: Self) -> Self {
89        blend_separable(self.into(), other.into(), screen_blend)
90    }
91
92    #[inline]
93    fn overlay(self, other: Self) -> Self {
94        blend_separable(self.into(), other.into(), overlay_blend)
95    }
96
97    #[inline]
98    fn darken(self, other: Self) -> Self {
99        blend_separable(self.into(), other.into(), darken_blend)
100    }
101
102    #[inline]
103    fn lighten(self, other: Self) -> Self {
104        blend_separable(self.into(), other.into(), lighten_blend)
105    }
106
107    #[inline]
108    fn dodge(self, other: Self) -> Self {
109        blend_separable(self.into(), other.into(), dodge_blend)
110    }
111
112    #[inline]
113    fn burn(self, other: Self) -> Self {
114        blend_separable(self.into(), other.into(), burn_blend)
115    }
116
117    #[inline]
118    fn hard_light(self, other: Self) -> Self {
119        blend_separable(self.into(), other.into(), hard_light_blend)
120    }
121
122    #[inline]
123    fn soft_light(self, other: Self) -> Self {
124        blend_separable(self.into(), other.into(), soft_light_blend)
125    }
126
127    #[inline]
128    fn difference(self, other: Self) -> Self {
129        blend_separable(self.into(), other.into(), difference_blend)
130    }
131
132    #[inline]
133    fn exclusion(self, other: Self) -> Self {
134        blend_separable(self.into(), other.into(), exclusion_blend)
135    }
136}
137
138impl<C, T, const N: usize> Blend for C
139where
140    C: Premultiply<Scalar = T> + StimulusColor + ArrayCast<Array = [T; N]> + Clone,
141    T: Real + Zero + One + MinMax + Clamp + Sqrt + Abs + Arithmetics + PartialCmp + Clone,
142    T::Mask: LazySelect<T>,
143{
144    fn multiply(self, other: Self) -> Self {
145        let src = BlendInput::new_opaque(self);
146        let dst = BlendInput::new_opaque(other);
147        blend_separable(src, dst, multiply_blend)
148            .unpremultiply()
149            .color
150    }
151
152    fn screen(self, other: Self) -> Self {
153        let src = BlendInput::new_opaque(self);
154        let dst = BlendInput::new_opaque(other);
155        blend_separable(src, dst, screen_blend)
156            .unpremultiply()
157            .color
158    }
159
160    fn overlay(self, other: Self) -> Self {
161        let src = BlendInput::new_opaque(self);
162        let dst = BlendInput::new_opaque(other);
163        blend_separable(src, dst, overlay_blend)
164            .unpremultiply()
165            .color
166    }
167
168    fn darken(self, other: Self) -> Self {
169        let src = BlendInput::new_opaque(self);
170        let dst = BlendInput::new_opaque(other);
171        blend_separable(src, dst, darken_blend)
172            .unpremultiply()
173            .color
174    }
175
176    fn lighten(self, other: Self) -> Self {
177        let src = BlendInput::new_opaque(self);
178        let dst = BlendInput::new_opaque(other);
179        blend_separable(src, dst, lighten_blend)
180            .unpremultiply()
181            .color
182    }
183
184    fn dodge(self, other: Self) -> Self {
185        let src = BlendInput::new_opaque(self);
186        let dst = BlendInput::new_opaque(other);
187        blend_separable(src, dst, dodge_blend).unpremultiply().color
188    }
189
190    fn burn(self, other: Self) -> Self {
191        let src = BlendInput::new_opaque(self);
192        let dst = BlendInput::new_opaque(other);
193        blend_separable(src, dst, burn_blend).unpremultiply().color
194    }
195
196    fn hard_light(self, other: Self) -> Self {
197        let src = BlendInput::new_opaque(self);
198        let dst = BlendInput::new_opaque(other);
199        blend_separable(src, dst, hard_light_blend)
200            .unpremultiply()
201            .color
202    }
203
204    fn soft_light(self, other: Self) -> Self {
205        let src = BlendInput::new_opaque(self);
206        let dst = BlendInput::new_opaque(other);
207        blend_separable(src, dst, soft_light_blend)
208            .unpremultiply()
209            .color
210    }
211
212    fn difference(self, other: Self) -> Self {
213        let src = BlendInput::new_opaque(self);
214        let dst = BlendInput::new_opaque(other);
215        blend_separable(src, dst, difference_blend)
216            .unpremultiply()
217            .color
218    }
219
220    fn exclusion(self, other: Self) -> Self {
221        let src = BlendInput::new_opaque(self);
222        let dst = BlendInput::new_opaque(other);
223        blend_separable(src, dst, exclusion_blend)
224            .unpremultiply()
225            .color
226    }
227}
228
229impl<C, T, const N: usize> Blend for Alpha<C, T>
230where
231    C: Premultiply<Scalar = T> + StimulusColor + ArrayCast<Array = [T; N]> + Clone,
232    T: Real + Zero + One + MinMax + Clamp + Sqrt + Abs + Arithmetics + PartialCmp + Clone,
233    T::Mask: LazySelect<T>,
234{
235    #[inline]
236    fn multiply(self, other: Self) -> Self {
237        blend_separable(self.into(), other.into(), multiply_blend).unpremultiply()
238    }
239
240    #[inline]
241    fn screen(self, other: Self) -> Self {
242        blend_separable(self.into(), other.into(), screen_blend).unpremultiply()
243    }
244
245    #[inline]
246    fn overlay(self, other: Self) -> Self {
247        blend_separable(self.into(), other.into(), overlay_blend).unpremultiply()
248    }
249
250    #[inline]
251    fn darken(self, other: Self) -> Self {
252        blend_separable(self.into(), other.into(), darken_blend).unpremultiply()
253    }
254
255    #[inline]
256    fn lighten(self, other: Self) -> Self {
257        blend_separable(self.into(), other.into(), lighten_blend).unpremultiply()
258    }
259
260    #[inline]
261    fn dodge(self, other: Self) -> Self {
262        blend_separable(self.into(), other.into(), dodge_blend).unpremultiply()
263    }
264
265    #[inline]
266    fn burn(self, other: Self) -> Self {
267        blend_separable(self.into(), other.into(), burn_blend).unpremultiply()
268    }
269
270    #[inline]
271    fn hard_light(self, other: Self) -> Self {
272        blend_separable(self.into(), other.into(), hard_light_blend).unpremultiply()
273    }
274
275    #[inline]
276    fn soft_light(self, other: Self) -> Self {
277        blend_separable(self.into(), other.into(), soft_light_blend).unpremultiply()
278    }
279
280    #[inline]
281    fn difference(self, other: Self) -> Self {
282        blend_separable(self.into(), other.into(), difference_blend).unpremultiply()
283    }
284
285    #[inline]
286    fn exclusion(self, other: Self) -> Self {
287        blend_separable(self.into(), other.into(), exclusion_blend).unpremultiply()
288    }
289}
290
291struct BlendInput<C: Premultiply> {
292    color: C,
293    color_pre: C,
294    alpha: C::Scalar,
295}
296
297impl<C> BlendInput<C>
298where
299    C: Premultiply + Clone,
300{
301    fn new_opaque(color: C) -> Self {
302        BlendInput {
303            color_pre: color.clone(),
304            color,
305            alpha: C::Scalar::max_intensity(),
306        }
307    }
308}
309
310impl<C> From<Alpha<C, C::Scalar>> for BlendInput<C>
311where
312    C: Premultiply + Clone,
313{
314    fn from(color: Alpha<C, C::Scalar>) -> Self {
315        let color_pre: PreAlpha<C> = color.color.clone().premultiply(color.alpha);
316        BlendInput {
317            color: color.color,
318            color_pre: color_pre.color,
319            alpha: color_pre.alpha,
320        }
321    }
322}
323
324impl<C> From<PreAlpha<C>> for BlendInput<C>
325where
326    C: Premultiply + Clone,
327{
328    fn from(color: PreAlpha<C>) -> Self {
329        let color_pre = color.color.clone();
330        let (color, alpha) = C::unpremultiply(color);
331        BlendInput {
332            color,
333            color_pre,
334            alpha,
335        }
336    }
337}
338
339#[inline]
340fn multiply_blend<T>(src: T, dst: T) -> T
341where
342    T: Arithmetics,
343{
344    src * dst
345}
346
347#[inline]
348fn screen_blend<T>(src: T, dst: T) -> T
349where
350    T: Arithmetics + Clone,
351{
352    src.clone() + &dst - src * dst
353}
354
355#[inline]
356fn overlay_blend<T>(src: T, dst: T) -> T
357where
358    T: One + Arithmetics + PartialCmp + Clone,
359    T::Mask: LazySelect<T>,
360{
361    hard_light_blend(dst, src)
362}
363
364#[inline]
365fn darken_blend<T>(src: T, dst: T) -> T
366where
367    T: MinMax,
368{
369    src.min(dst)
370}
371
372#[inline]
373fn lighten_blend<T>(src: T, dst: T) -> T
374where
375    T: MinMax,
376{
377    src.max(dst)
378}
379
380#[inline]
381fn dodge_blend<T>(src: T, dst: T) -> T
382where
383    T: One + Zero + MinMax + Arithmetics + PartialCmp,
384    T::Mask: LazySelect<T>,
385{
386    // The original algorithm assumes values within [0, 1], but we check for
387    // values outside it and clamp.
388    lazy_select! {
389        if dst.lt_eq(&T::zero()) => T::zero(),
390        if src.gt_eq(&T::one()) => T::one(),
391        else => T::one().min(dst / (T::one() - src)),
392    }
393}
394
395#[inline]
396fn burn_blend<T>(src: T, dst: T) -> T
397where
398    T: One + Zero + MinMax + Arithmetics + PartialCmp,
399    T::Mask: LazySelect<T>,
400{
401    // The original algorithm assumes values within [0, 1], but we check for
402    // values outside it and clamp.
403    lazy_select! {
404        if dst.gt_eq(&T::one()) => T::one(),
405        if src.lt_eq(&T::zero()) => T::zero(),
406        else => T::one() - T::one().min((T::one() - dst) / src),
407    }
408}
409
410#[inline]
411fn hard_light_blend<T>(src: T, dst: T) -> T
412where
413    T: One + Arithmetics + PartialCmp + Clone,
414    T::Mask: LazySelect<T>,
415{
416    let two_src = src.clone() + src;
417
418    lazy_select! {
419        if two_src.lt_eq(&T::one()) => multiply_blend(two_src.clone(), dst.clone()),
420        else => screen_blend(two_src.clone() - T::one(), dst.clone()),
421    }
422}
423
424#[inline]
425fn soft_light_blend<T>(src: T, dst: T) -> T
426where
427    T: Real + One + Arithmetics + Sqrt + PartialCmp + Clone,
428    T::Mask: LazySelect<T>,
429{
430    let four = T::from_f64(4.0);
431    let twelve = T::from_f64(12.0);
432
433    let four_dst = dst.clone() * &four;
434    let two_src = src.clone() + &src;
435
436    let d_dst = lazy_select! {
437        if four_dst.lt_eq(&T::one()) => {
438            let sixteen_dst = four_dst * &four;
439            ((sixteen_dst - twelve) * &dst + four) * &dst
440        },
441        else => dst.clone().sqrt(),
442    };
443
444    lazy_select! {
445        if two_src.lt_eq(&T::one()) => {
446            dst.clone() - (T::one() - &two_src) * &dst * (T::one() - &dst)
447        },
448        else => dst.clone() + (two_src.clone() - T::one()) * (d_dst - &dst),
449    }
450}
451
452#[inline]
453fn difference_blend<T>(src: T, dst: T) -> T
454where
455    T: Arithmetics + Abs,
456{
457    (dst - src).abs()
458}
459
460#[inline]
461fn exclusion_blend<T>(src: T, dst: T) -> T
462where
463    T: Arithmetics + Clone,
464{
465    dst.clone() + &src - (dst.clone() + dst) * src
466}
467
468#[inline]
469fn blend_separable<C, T, F, const N: usize>(
470    src: BlendInput<C>,
471    mut dst: BlendInput<C>,
472    mut blend: F,
473) -> PreAlpha<C>
474where
475    C: ArrayCast<Array = [T; N]> + Premultiply<Scalar = T>,
476    T: One + Zero + Arithmetics + Clamp + Clone,
477    F: FnMut(T, T) -> T,
478{
479    let src_alpha = src.alpha.clone();
480    let zipped_input = zip_input(src, dst.color, &mut dst.color_pre, dst.alpha.clone());
481
482    for (src, src_pre, src_alpha, dst, dst_pre, dst_alpha) in zipped_input {
483        *dst_pre = src_pre * (T::one() - &dst_alpha)
484            + blend(src, dst) * &src_alpha * dst_alpha
485            + (T::one() - src_alpha) * &*dst_pre;
486    }
487
488    PreAlpha {
489        color: dst.color_pre,
490        alpha: blend_alpha(src_alpha, dst.alpha),
491    }
492}
493
494fn zip_input<'a, C, T, const N: usize>(
495    src: BlendInput<C>,
496    dst: C,
497    dst_pre: &'a mut C,
498    dst_alpha: T,
499) -> impl Iterator<Item = (T, T, T, T, &'a mut T, T)>
500where
501    C: ArrayCast<Array = [T; N]> + Premultiply<Scalar = T>,
502    T: 'a + Clone,
503{
504    let src_alpha = src.alpha;
505    IntoIterator::into_iter(cast::into_array(src.color))
506        .zip(cast::into_array(src.color_pre))
507        .zip(cast::into_array(dst))
508        .zip(cast::into_array_mut(dst_pre))
509        .map(move |(((src_color, src_pre), dst_color), dst_pre)| {
510            (
511                src_color,
512                src_pre,
513                src_alpha.clone(),
514                dst_color,
515                dst_pre,
516                dst_alpha.clone(),
517            )
518        })
519}