Skip to content

Commit 5d173c7

Browse files
committed
Support for non-separable blend modes
Added support for the following non-separable blend modes for Color: * Hue * Saturation * Color * Luminosity
1 parent a6b9350 commit 5d173c7

File tree

1 file changed

+48
-23
lines changed

1 file changed

+48
-23
lines changed

src/color/model.rs

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,7 @@ impl Color {
180180
let [sr, sg, sb, sa] = self.into_linear();
181181
let [dr, dg, db, da] = bg.into_linear();
182182

183-
let br = Self::blend_channel(mode, sr, dr);
184-
let bg_ = Self::blend_channel(mode, sg, dg);
185-
let bb = Self::blend_channel(mode, sb, db);
183+
let [br, bg_, bb] = Self::blend_channel(mode, [sr, sg, sb], [dr, dg, db]);
186184

187185
// Porter–Duff combination in premultiplied form
188186
let a_out = sa + da - sa * da;
@@ -781,45 +779,63 @@ impl Color {
781779
[out_r, out_g, out_b]
782780
}
783781

782+
/// Combine two colors with a blend function.
783+
fn blend<F>(backdrop: &[ColorFloat; 3], source: &[ColorFloat; 3], mut f: F) -> [ColorFloat; 3]
784+
where
785+
F: FnMut(ColorFloat, ColorFloat) -> ColorFloat,
786+
{
787+
backdrop
788+
.iter()
789+
.zip(source)
790+
.map(|(&b, &s)| f(b, s))
791+
.collect::<Vec<_>>()
792+
.try_into()
793+
.unwrap()
794+
}
795+
784796
#[inline]
785-
fn blend_channel(mode: BlendMode, b: ColorFloat, s: ColorFloat) -> ColorFloat {
797+
fn blend_channel(
798+
mode: BlendMode,
799+
backdrop: [ColorFloat; 3],
800+
source: [ColorFloat; 3],
801+
) -> [ColorFloat; 3] {
786802
use BlendMode::*;
787803

788804
match mode {
789805
// source: https://www.w3.org/TR/compositing-1/
790806
// separable blend modes
791-
Normal => s,
792-
Multiply => b * s,
793-
Screen => b + s - (b * s),
794-
Overlay => Self::blend_channel(HardLight, b, s),
795-
Darken => b.min(s),
796-
Lighten => b.max(s),
797-
ColorDodge => {
807+
Normal => source,
808+
Multiply => Self::blend(&backdrop, &source, |b, s| b * s),
809+
Screen => Self::blend(&backdrop, &source, |b, s| b + s - (b * s)),
810+
Overlay => Self::blend_channel(HardLight, backdrop, source),
811+
Darken => Self::blend(&backdrop, &source, |b, s| b.min(s)),
812+
Lighten => Self::blend(&backdrop, &source, |b, s| b.max(s)),
813+
ColorDodge => Self::blend(&backdrop, &source, |b, s| {
798814
if b == 0.0 {
799815
0.0
800816
} else if s == 1.0 {
801817
1.0
802818
} else {
803819
(1.0 as ColorFloat).min(b / (1.0 - s))
804820
}
805-
}
806-
ColorBurn => {
821+
}),
822+
ColorBurn => Self::blend(&backdrop, &source, |b, s| {
807823
if b == 1.0 {
808824
1.0
809825
} else if s == 0.0 {
810826
0.0
811827
} else {
812828
1.0 - (1.0 as ColorFloat).min((1.0 - b) / s)
813829
}
814-
}
815-
HardLight => {
830+
}),
831+
HardLight => Self::blend(&backdrop, &source, |b, s| {
816832
if s <= 0.5 {
817-
Self::blend_channel(Multiply, b, 2.0 * s)
833+
2.0 * b * s
818834
} else {
819-
Self::blend_channel(Screen, b, 2.0 * s - 1.0)
835+
1.0 - 2.0 * (1.0 - b) * (1.0 - s)
820836
}
821-
}
822-
SoftLight => {
837+
}),
838+
SoftLight => Self::blend(&backdrop, &source, |b, s| {
823839
if s <= 0.5 {
824840
b - (1.0 - 2.0 * s) * b * (1.0 - b)
825841
} else {
@@ -830,11 +846,20 @@ impl Color {
830846
};
831847
b + (2.0 * s - 1.0) * (d - b)
832848
}
833-
}
834-
Difference => (b - s).abs(),
835-
Exclusion => b + s - 2.0 * b * s,
849+
}),
850+
Difference => Self::blend(&backdrop, &source, |b, s| (b - s).abs()),
851+
Exclusion => Self::blend(&backdrop, &source, |b, s| b + s - 2.0 * b * s),
836852
// non-separable blend modes
837-
_ => s, // TODO placeholder
853+
Hue => Self::set_lum(
854+
Self::set_sat(source, Self::sat(backdrop)),
855+
Self::lum(backdrop),
856+
),
857+
Saturation => Self::set_lum(
858+
Self::set_sat(backdrop, Self::sat(source)),
859+
Self::lum(backdrop),
860+
),
861+
Color => Self::set_lum(source, Self::lum(backdrop)),
862+
Luminosity => Self::set_lum(backdrop, Self::lum(source)),
838863
}
839864
}
840865
}

0 commit comments

Comments
 (0)