1use crate::{GpuDevice, GpuError, Result};
15use rayon::prelude::*;
16
17use super::utils;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum BlendMode {
26 Normal,
28 Multiply,
30 Screen,
32 Overlay,
34}
35
36impl Default for BlendMode {
37 fn default() -> Self {
38 Self::Normal
39 }
40}
41
42#[derive(Debug, Clone)]
46pub struct BlendLayer<'a> {
47 pub data: &'a [u8],
49 pub width: u32,
51 pub height: u32,
53 pub opacity: f32,
55 pub blend_mode: BlendMode,
57}
58
59impl<'a> BlendLayer<'a> {
60 pub fn new(
67 data: &'a [u8],
68 width: u32,
69 height: u32,
70 opacity: f32,
71 blend_mode: BlendMode,
72 ) -> Result<Self> {
73 if !(0.0..=1.0).contains(&opacity) {
74 return Err(GpuError::Internal(format!(
75 "Layer opacity {opacity} is outside [0,1]"
76 )));
77 }
78 utils::validate_buffer_size(data, width, height, 4)?;
79 Ok(Self {
80 data,
81 width,
82 height,
83 opacity,
84 blend_mode,
85 })
86 }
87}
88
89pub struct LayerCompositor;
95
96impl LayerCompositor {
97 pub fn blend_layers(
114 _device: &GpuDevice,
115 layers: &[BlendLayer<'_>],
116 output: &mut [u8],
117 width: u32,
118 height: u32,
119 ) -> Result<()> {
120 Self::blend_layers_cpu(layers, output, width, height)
121 }
122
123 pub fn blend_layers_cpu(
129 layers: &[BlendLayer<'_>],
130 output: &mut [u8],
131 width: u32,
132 height: u32,
133 ) -> Result<()> {
134 utils::validate_dimensions(width, height)?;
135 let expected = (width * height * 4) as usize;
136 if output.len() < expected {
137 return Err(GpuError::InvalidBufferSize {
138 expected,
139 actual: output.len(),
140 });
141 }
142
143 for (idx, layer) in layers.iter().enumerate() {
145 if layer.width != width || layer.height != height {
146 return Err(GpuError::Internal(format!(
147 "Layer {idx} dimensions {}×{} do not match output {}×{}",
148 layer.width, layer.height, width, height
149 )));
150 }
151 }
152
153 output[..expected].fill(0);
155
156 for layer in layers {
158 Self::composite_layer(layer, output, width, height)?;
159 }
160
161 Ok(())
162 }
163
164 fn composite_layer(
166 layer: &BlendLayer<'_>,
167 acc: &mut [u8],
168 width: u32,
169 height: u32,
170 ) -> Result<()> {
171 let n_pixels = (width * height) as usize;
172 let opacity = layer.opacity;
173 let mode = layer.blend_mode;
174
175 acc.par_chunks_exact_mut(4)
176 .zip(layer.data.par_chunks_exact(4))
177 .take(n_pixels)
178 .for_each(|(dst, src)| {
179 let dr = dst[0] as f32 / 255.0;
181 let dg = dst[1] as f32 / 255.0;
182 let db = dst[2] as f32 / 255.0;
183 let da = dst[3] as f32 / 255.0;
184
185 let sr = src[0] as f32 / 255.0;
186 let sg = src[1] as f32 / 255.0;
187 let sb = src[2] as f32 / 255.0;
188 let sa = (src[3] as f32 / 255.0) * opacity;
189
190 let (br, bg, bb) = apply_blend(mode, sr, sg, sb, dr, dg, db);
192
193 let out_a = sa + da * (1.0 - sa);
195 let (or, og, ob) = if out_a > 1e-6 {
196 (
197 (br * sa + dr * da * (1.0 - sa)) / out_a,
198 (bg * sa + dg * da * (1.0 - sa)) / out_a,
199 (bb * sa + db * da * (1.0 - sa)) / out_a,
200 )
201 } else {
202 (0.0, 0.0, 0.0)
203 };
204
205 dst[0] = (or.clamp(0.0, 1.0) * 255.0).round() as u8;
206 dst[1] = (og.clamp(0.0, 1.0) * 255.0).round() as u8;
207 dst[2] = (ob.clamp(0.0, 1.0) * 255.0).round() as u8;
208 dst[3] = (out_a.clamp(0.0, 1.0) * 255.0).round() as u8;
209 });
210
211 Ok(())
212 }
213}
214
215#[inline(always)]
222fn apply_blend(
223 mode: BlendMode,
224 sr: f32,
225 sg: f32,
226 sb: f32,
227 dr: f32,
228 dg: f32,
229 db: f32,
230) -> (f32, f32, f32) {
231 match mode {
232 BlendMode::Normal => (sr, sg, sb),
233 BlendMode::Multiply => (sr * dr, sg * dg, sb * db),
234 BlendMode::Screen => (screen(sr, dr), screen(sg, dg), screen(sb, db)),
235 BlendMode::Overlay => (overlay(dr, sr), overlay(dg, sg), overlay(db, sb)),
236 }
237}
238
239#[inline(always)]
241fn screen(a: f32, b: f32) -> f32 {
242 1.0 - (1.0 - a) * (1.0 - b)
243}
244
245#[inline(always)]
247fn overlay(base: f32, blend: f32) -> f32 {
248 if base < 0.5 {
249 2.0 * base * blend
250 } else {
251 1.0 - 2.0 * (1.0 - base) * (1.0 - blend)
252 }
253}
254
255#[cfg(test)]
260mod tests {
261 use super::*;
262
263 fn solid_rgba(w: u32, h: u32, r: u8, g: u8, b: u8, a: u8) -> Vec<u8> {
264 let mut v = vec![0u8; (w * h * 4) as usize];
265 for px in v.chunks_exact_mut(4) {
266 px[0] = r;
267 px[1] = g;
268 px[2] = b;
269 px[3] = a;
270 }
271 v
272 }
273
274 #[test]
277 fn test_screen_blend_identity() {
278 assert!((screen(0.0, 0.7) - 0.7).abs() < 1e-6);
280 assert!((screen(1.0, 0.5) - 1.0).abs() < 1e-6);
282 }
283
284 #[test]
285 fn test_multiply_blend_zero() {
286 let (r, _g, b) = apply_blend(BlendMode::Multiply, 0.0, 0.5, 1.0, 0.5, 0.5, 0.5);
287 assert!((r - 0.0).abs() < 1e-6);
288 assert!((b - 0.5).abs() < 1e-6);
289 }
290
291 #[test]
292 fn test_overlay_midpoint() {
293 let v = overlay(0.5, 0.5);
295 assert!((v - 0.5).abs() < 1e-6, "overlay midpoint: {v}");
296 }
297
298 #[test]
301 fn test_blend_zero_layers_produces_black() {
302 let w = 4u32;
303 let h = 4u32;
304 let mut output = solid_rgba(w, h, 255, 255, 255, 255);
305 let result = LayerCompositor::blend_layers_cpu(&[], &mut output, w, h);
307 assert!(result.is_ok());
308 for &v in &output {
310 assert_eq!(v, 0, "zero layers should produce transparent black");
311 }
312 }
313
314 #[test]
315 fn test_blend_single_fully_opaque_layer() {
316 let w = 4u32;
317 let h = 4u32;
318 let src = solid_rgba(w, h, 200, 100, 50, 255);
319 let layer =
320 BlendLayer::new(&src, w, h, 1.0, BlendMode::Normal).expect("create blend layer");
321 let mut out = vec![0u8; (w * h * 4) as usize];
322 LayerCompositor::blend_layers_cpu(&[layer], &mut out, w, h).expect("blend single layer");
323 for i in 0..(w * h) as usize {
324 assert_eq!(out[i * 4], 200, "red mismatch at pixel {i}");
325 assert_eq!(out[i * 4 + 1], 100, "green mismatch at pixel {i}");
326 assert_eq!(out[i * 4 + 2], 50, "blue mismatch at pixel {i}");
327 assert_eq!(out[i * 4 + 3], 255, "alpha mismatch at pixel {i}");
328 }
329 }
330
331 #[test]
332 fn test_blend_two_layers_normal_over() {
333 let w = 4u32;
334 let h = 4u32;
335 let bg = solid_rgba(w, h, 0, 0, 255, 255); let fg = solid_rgba(w, h, 255, 0, 0, 128); let layers = [
338 BlendLayer::new(&bg, w, h, 1.0, BlendMode::Normal).expect("create bg layer"),
339 BlendLayer::new(&fg, w, h, 1.0, BlendMode::Normal).expect("create fg layer"),
340 ];
341 let mut out = vec![0u8; (w * h * 4) as usize];
342 LayerCompositor::blend_layers_cpu(&layers, &mut out, w, h).expect("blend two layers");
343 for i in 0..(w * h) as usize {
345 assert_eq!(out[i * 4 + 3], 255, "composite alpha should be 255");
346 assert!(out[i * 4] > 0, "red should be present");
348 }
349 }
350
351 #[test]
352 fn test_blend_multiply_two_identical_layers() {
353 let w = 4u32;
354 let h = 4u32;
355 let layer_data = solid_rgba(w, h, 128, 128, 128, 255);
357 let layers = [
358 BlendLayer::new(&layer_data, w, h, 1.0, BlendMode::Normal)
359 .expect("create normal layer"),
360 BlendLayer::new(&layer_data, w, h, 1.0, BlendMode::Multiply)
361 .expect("create multiply layer"),
362 ];
363 let mut out = vec![0u8; (w * h * 4) as usize];
364 LayerCompositor::blend_layers_cpu(&layers, &mut out, w, h).expect("blend multiply layers");
365 for i in 0..(w * h) as usize {
367 let r = out[i * 4];
368 assert!(
369 r >= 60 && r <= 68,
370 "multiply result {r} out of expected range [60,68]"
371 );
372 }
373 }
374
375 #[test]
376 fn test_blend_layer_dimension_mismatch() {
377 let w = 4u32;
378 let h = 4u32;
379 let small = solid_rgba(2, 2, 255, 0, 0, 255);
380 let layer = BlendLayer {
381 data: &small,
382 width: 2,
383 height: 2,
384 opacity: 1.0,
385 blend_mode: BlendMode::Normal,
386 };
387 let mut out = vec![0u8; (w * h * 4) as usize];
388 let result = LayerCompositor::blend_layers_cpu(&[layer], &mut out, w, h);
389 assert!(result.is_err(), "mismatched dimensions should error");
390 }
391
392 #[test]
393 fn test_blend_layer_invalid_opacity() {
394 let data = solid_rgba(4, 4, 0, 0, 0, 255);
395 let result = BlendLayer::new(&data, 4, 4, 1.5, BlendMode::Normal);
396 assert!(result.is_err(), "opacity > 1.0 should error");
397 }
398
399 #[test]
400 fn test_blend_screen_mode() {
401 let w = 4u32;
402 let h = 4u32;
403 let bg = solid_rgba(w, h, 128, 128, 128, 255);
404 let fg = solid_rgba(w, h, 128, 128, 128, 255);
405 let layers = [
406 BlendLayer::new(&bg, w, h, 1.0, BlendMode::Normal).expect("create bg layer"),
407 BlendLayer::new(&fg, w, h, 1.0, BlendMode::Screen).expect("create screen fg layer"),
408 ];
409 let mut out = vec![0u8; (w * h * 4) as usize];
410 LayerCompositor::blend_layers_cpu(&layers, &mut out, w, h).expect("blend screen layers");
411 for i in 0..(w * h) as usize {
413 let r = out[i * 4];
414 assert!(
415 r >= 185 && r <= 197,
416 "screen result {r} out of expected range [185,197]"
417 );
418 }
419 }
420
421 #[test]
422 fn test_blend_overlay_mode() {
423 let w = 4u32;
424 let h = 4u32;
425 let bg = solid_rgba(w, h, 100, 200, 50, 255);
426 let fg = solid_rgba(w, h, 200, 100, 150, 255);
427 let layers = [
428 BlendLayer::new(&bg, w, h, 1.0, BlendMode::Normal).expect("create bg layer"),
429 BlendLayer::new(&fg, w, h, 1.0, BlendMode::Overlay).expect("create overlay fg layer"),
430 ];
431 let mut out = vec![0u8; (w * h * 4) as usize];
432 let result = LayerCompositor::blend_layers_cpu(&layers, &mut out, w, h);
433 assert!(result.is_ok());
434 }
435}