Skip to content

Commit 45ef4c1

Browse files
committed
Helpers for non-separable blend modes
Added helper functions for implementing non-separable blend mode functions in Color. Added: * lum() * clip_color() * set_lum() * sat() * set_sat()
1 parent 102d018 commit 45ef4c1

File tree

1 file changed

+132
-2
lines changed

1 file changed

+132
-2
lines changed

src/color/model.rs

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::color::ColorFloat;
1515

1616
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1717
pub enum BlendMode {
18-
/// Standard Porter-Duff over (equivalent to `Color::over`)
18+
// separable blend modes
1919
Normal,
2020
Multiply,
2121
Screen,
@@ -28,6 +28,11 @@ pub enum BlendMode {
2828
SoftLight,
2929
Difference,
3030
Exclusion,
31+
// non-separable blend modes
32+
Hue,
33+
Saturation,
34+
Color,
35+
Luminosity,
3136
}
3237

3338
// stores sRGB under the hood, with lots of conversion funcs
@@ -189,7 +194,7 @@ impl Color {
189194
};
190195

191196
// Encode back to sRGB u8 (your encoders already clamp)
192-
Color::from_linear([cr, cg, cb, ca])
197+
Self::from_linear([cr, cg, cb, ca])
193198
}
194199

195200
// Faster (but slightly less accurate) "over" in sRGB space.
@@ -641,6 +646,130 @@ impl Color {
641646
}
642647
}
643648

649+
// helpers for non-separable blend modes
650+
fn lum(c: [ColorFloat; 3]) -> ColorFloat {
651+
let [r, g, b] = c;
652+
0.3 * r + 0.59 * g + 0.11 * b
653+
}
654+
655+
fn clip_color(c: [ColorFloat; 3]) -> [ColorFloat; 3] {
656+
let [r, g, b] = c;
657+
let l = Self::lum(c);
658+
let n = r.min(g).min(b);
659+
let x = r.max(g).max(b);
660+
if n < 0.0 {
661+
c.iter()
662+
.map(|&v| l + (((v - l) * l) / (l - n)))
663+
.collect::<Vec<_>>()
664+
.try_into()
665+
.unwrap()
666+
} else if x > 1.0 {
667+
c.iter()
668+
.map(|&v| l + (((v - l) * (1.0 - l)) / (x - l)))
669+
.collect::<Vec<_>>()
670+
.try_into()
671+
.unwrap()
672+
} else {
673+
c
674+
}
675+
}
676+
677+
fn set_lum(c: [ColorFloat; 3], l: ColorFloat) -> [ColorFloat; 3] {
678+
let [r, g, b] = c;
679+
let d = l - Self::lum(c);
680+
Self::clip_color([r + d, g + d, b + d])
681+
}
682+
683+
fn sat(c: [ColorFloat; 3]) -> ColorFloat {
684+
let [r, g, b] = c;
685+
r.max(g).max(b) - r.min(g).min(b)
686+
}
687+
688+
fn set_sat(c: [ColorFloat; 3], s: ColorFloat) -> [ColorFloat; 3] {
689+
let [r, g, b] = c;
690+
let max: ColorFloat;
691+
let max_ch: char;
692+
let min: ColorFloat;
693+
let min_ch: char;
694+
let mid: ColorFloat;
695+
let mid_ch: char;
696+
697+
if r >= g && r >= b {
698+
max = r;
699+
max_ch = 'r';
700+
701+
if g <= b {
702+
min = g;
703+
min_ch = 'g';
704+
mid = b;
705+
mid_ch = 'b';
706+
} else {
707+
min = b;
708+
min_ch = 'b';
709+
mid = g;
710+
mid_ch = 'g';
711+
}
712+
} else if g >= r && g >= b {
713+
max = g;
714+
max_ch = 'g';
715+
if r <= b {
716+
min = r;
717+
min_ch = 'r';
718+
mid = b;
719+
mid_ch = 'b';
720+
} else {
721+
min = b;
722+
min_ch = 'b';
723+
mid = r;
724+
mid_ch = 'r';
725+
}
726+
} else {
727+
max = b;
728+
max_ch = 'b';
729+
if r <= g {
730+
min = r;
731+
min_ch = 'r';
732+
mid = g;
733+
mid_ch = 'g';
734+
} else {
735+
min = g;
736+
min_ch = 'g';
737+
mid = r;
738+
mid_ch = 'r';
739+
}
740+
}
741+
742+
let chroma = max - min;
743+
if chroma == 0.0 {
744+
return [0.0, 0.0, 0.0];
745+
}
746+
747+
let scale = s / chroma;
748+
749+
let new_max = mid + (max - mid) * scale;
750+
let new_min = mid - (mid - min) * scale;
751+
let new_mid = mid;
752+
753+
let (mut out_r, mut out_g, mut out_b) = (0.0, 0.0, 0.0);
754+
match max_ch {
755+
'r' => out_r = new_max,
756+
'g' => out_g = new_max,
757+
_ => out_b = new_max,
758+
}
759+
match min_ch {
760+
'r' => out_r = new_min,
761+
'g' => out_g = new_min,
762+
_ => out_b = new_min,
763+
}
764+
match mid_ch {
765+
'r' => out_r = new_mid,
766+
'g' => out_g = new_mid,
767+
_ => out_b = new_mid,
768+
}
769+
770+
[out_r, out_g, out_b]
771+
}
772+
644773
#[inline]
645774
fn blend_channel(mode: BlendMode, b: ColorFloat, s: ColorFloat) -> ColorFloat {
646775
use BlendMode::*;
@@ -694,6 +823,7 @@ impl Color {
694823
Difference => (b - s).abs(),
695824
Exclusion => b + s - 2.0 * b * s,
696825
// non-separable blend modes
826+
_ => s, // TODO placeholder
697827
}
698828
}
699829
}

0 commit comments

Comments
 (0)