1use crate::media::Frame;
4use crate::model::BlendMode;
5
6#[inline]
8pub fn blend_channel(mode: BlendMode, s: f32, d: f32) -> f32 {
9 use BlendMode::*;
10 match mode {
11 Normal => s,
12 Multiply => s * d,
13 Screen => 1.0 - (1.0 - s) * (1.0 - d),
14 Overlay => {
15 if d <= 0.5 {
16 2.0 * s * d
17 } else {
18 1.0 - 2.0 * (1.0 - s) * (1.0 - d)
19 }
20 }
21 Darken => s.min(d),
22 Lighten => s.max(d),
23 Add => (s + d).min(1.0),
24 Subtract => (d - s).max(0.0),
25 Difference => (s - d).abs(),
26 SoftLight => {
27 if s <= 0.5 {
28 d - (1.0 - 2.0 * s) * d * (1.0 - d)
29 } else {
30 let g = if d <= 0.25 { ((16.0 * d - 12.0) * d + 4.0) * d } else { d.sqrt() };
31 d + (2.0 * s - 1.0) * (g - d)
32 }
33 }
34 HardLight => {
35 if s <= 0.5 {
36 2.0 * s * d
37 } else {
38 1.0 - 2.0 * (1.0 - s) * (1.0 - d)
39 }
40 }
41 ColorDodge => {
42 if d <= 0.0 {
43 0.0
44 } else if s >= 1.0 {
45 1.0
46 } else {
47 (d / (1.0 - s)).min(1.0)
48 }
49 }
50 ColorBurn => {
51 if d >= 1.0 {
52 1.0
53 } else if s <= 0.0 {
54 0.0
55 } else {
56 1.0 - ((1.0 - d) / s).min(1.0)
57 }
58 }
59 }
60}
61
62#[inline]
64fn div255(x: u32) -> u32 {
65 (x + 128 + ((x + 128) >> 8)) >> 8
66}
67
68pub fn composite_row(dst: &mut [u8], src: &[u8], mode: BlendMode, opacity: f32) {
71 let op = (opacity.clamp(0.0, 1.0) * 255.0 + 0.5) as u32;
72 if op == 0 {
73 return;
74 }
75 let px = dst.chunks_exact_mut(4).zip(src.chunks_exact(4));
76 if mode == BlendMode::Normal {
77 for (d, s) in px {
78 let a = div255(s[3] as u32 * op);
79 if a == 0 {
80 continue;
81 }
82 if a == 255 {
83 d[..3].copy_from_slice(&s[..3]);
84 } else {
85 let ia = 255 - a;
86 d[0] = div255(d[0] as u32 * ia + s[0] as u32 * a) as u8;
87 d[1] = div255(d[1] as u32 * ia + s[1] as u32 * a) as u8;
88 d[2] = div255(d[2] as u32 * ia + s[2] as u32 * a) as u8;
89 }
90 d[3] = 255;
91 }
92 } else {
93 let opf = op as f32 / 255.0;
94 for (d, s) in px {
95 if s[3] == 0 {
96 continue;
97 }
98 let a = s[3] as f32 / 255.0 * opf;
99 for i in 0..3 {
100 let sv = s[i] as f32 / 255.0;
101 let dv = d[i] as f32 / 255.0;
102 let b = blend_channel(mode, sv, dv);
103 d[i] = ((dv + (b - dv) * a) * 255.0 + 0.5) as u8;
104 }
105 d[3] = 255;
106 }
107 }
108}
109
110pub fn composite_rect(
114 dst: &mut Frame,
115 layer: &Frame,
116 mode: BlendMode,
117 opacity: f32,
118 x0: u32,
119 y0: u32,
120 x1: u32,
121 y1: u32,
122) {
123 if dst.width != layer.width || dst.height != layer.height {
124 return;
125 }
126 let stride = dst.stride();
127 let (x0, x1) = (x0.min(dst.width) as usize * 4, x1.min(dst.width) as usize * 4);
128 if x0 >= x1 {
129 return;
130 }
131 for y in y0..y1.min(dst.height) {
132 let row = y as usize * stride;
133 composite_row(&mut dst.rgba[row + x0..row + x1], &layer.rgba[row + x0..row + x1], mode, opacity);
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn channel_formulas() {
143 assert!((blend_channel(BlendMode::Multiply, 0.5, 0.5) - 0.25).abs() < 1e-6);
144 assert!((blend_channel(BlendMode::Screen, 0.5, 0.5) - 0.75).abs() < 1e-6);
145 assert!((blend_channel(BlendMode::Add, 0.7, 0.7) - 1.0).abs() < 1e-6);
146 assert!((blend_channel(BlendMode::Difference, 0.2, 0.7) - 0.5).abs() < 1e-6);
147 assert!((blend_channel(BlendMode::Normal, 0.3, 0.9) - 0.3).abs() < 1e-6);
148 }
149
150 #[test]
151 fn composite_half_alpha_and_modes() {
152 let mut dst = Frame::new(2, 1);
153 dst.fill([100, 100, 100, 255]);
154 let mut layer = Frame::new(2, 1);
155 layer.rgba.copy_from_slice(&[200, 200, 200, 128, 50, 50, 50, 0]);
156 composite_rect(&mut dst, &layer, BlendMode::Normal, 1.0, 0, 0, 2, 1);
157 assert_eq!(&dst.rgba[..4], &[150, 150, 150, 255]);
159 assert_eq!(&dst.rgba[4..], &[100, 100, 100, 255]);
161
162 dst.fill([100, 100, 100, 255]);
164 layer.rgba[3] = 255;
165 composite_rect(&mut dst, &layer, BlendMode::Normal, 0.5, 0, 0, 2, 1);
166 assert_eq!(dst.rgba[0], 150);
167
168 dst.fill([128, 128, 128, 255]);
170 layer.rgba[..4].copy_from_slice(&[128, 128, 128, 255]);
171 composite_rect(&mut dst, &layer, BlendMode::Multiply, 1.0, 0, 0, 2, 1);
172 assert!((dst.rgba[0] as i32 - 64).abs() <= 1, "{}", dst.rgba[0]);
173 dst.fill([128, 128, 128, 255]);
175 composite_rect(&mut dst, &layer, BlendMode::Screen, 1.0, 0, 0, 2, 1);
176 assert!((dst.rgba[0] as i32 - 191).abs() <= 1, "{}", dst.rgba[0]);
177 assert_eq!(dst.rgba[3], 255);
178 }
179
180 #[test]
181 fn rect_limited() {
182 let mut dst = Frame::new(4, 4);
183 dst.fill([0, 0, 0, 255]);
184 let mut layer = Frame::new(4, 4);
185 layer.fill([255, 0, 0, 255]);
186 composite_rect(&mut dst, &layer, BlendMode::Normal, 1.0, 1, 1, 3, 3);
187 let px = |x: usize, y: usize| dst.rgba[(y * 4 + x) * 4];
188 assert_eq!(px(0, 0), 0);
189 assert_eq!(px(1, 1), 255);
190 assert_eq!(px(2, 2), 255);
191 assert_eq!(px(3, 3), 0);
192 }
193}