use crate::entities::frame::{Frame, FrameStatus, PixelBuffer};
use super::gpu_compositor::GpuCompositor;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BlendMode {
Normal,
Screen,
Add,
Subtract,
Multiply,
Divide,
Difference,
Overlay,
}
#[derive(Debug)]
pub enum CompositorType {
Cpu(CpuCompositor),
Gpu(GpuCompositor),
}
impl Clone for CompositorType {
fn clone(&self) -> Self {
if matches!(self, CompositorType::Gpu(_)) {
log::warn!("CompositorType::clone() called on GPU variant - downgrading to CPU. \
This may indicate a bug if not during serialization.");
}
CompositorType::Cpu(CpuCompositor)
}
}
pub const IDENTITY_TRANSFORM: [f32; 9] = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
impl CompositorType {
pub fn blend(&mut self, frames: Vec<(Frame, f32, BlendMode, [f32; 9])>) -> Option<Frame> {
match self {
CompositorType::Cpu(cpu) => cpu.blend(frames),
CompositorType::Gpu(gpu) => gpu.blend(frames),
}
}
pub fn blend_with_dim(
&mut self,
frames: Vec<(Frame, f32, BlendMode, [f32; 9])>,
dim: (usize, usize),
) -> Option<Frame> {
match self {
CompositorType::Cpu(cpu) => cpu.blend_with_dim(frames, dim),
CompositorType::Gpu(gpu) => gpu.blend_with_dim(frames, dim),
}
}
}
impl Default for CompositorType {
fn default() -> Self {
CompositorType::Cpu(CpuCompositor)
}
}
#[inline]
fn apply_blend(b: f32, t: f32, mode: &BlendMode) -> f32 {
let t_clamped = t.clamp(0.0, 1.0);
let b_clamped = b.clamp(0.0, 1.0);
match mode {
BlendMode::Normal => t_clamped,
BlendMode::Screen => 1.0 - (1.0 - b_clamped) * (1.0 - t_clamped),
BlendMode::Add => (b_clamped + t_clamped).min(1.0),
BlendMode::Subtract => (b_clamped - t_clamped).max(0.0),
BlendMode::Multiply => b_clamped * t_clamped,
BlendMode::Divide => {
if t_clamped <= 0.00001 {
b_clamped
} else {
(b_clamped / t_clamped).min(1.0)
}
}
BlendMode::Difference => (b_clamped - t_clamped).abs(),
BlendMode::Overlay => {
if b_clamped < 0.5 {
2.0 * b_clamped * t_clamped
} else {
1.0 - 2.0 * (1.0 - b_clamped) * (1.0 - t_clamped)
}
}
}
}
#[derive(Clone, Debug)]
pub struct CpuCompositor;
impl CpuCompositor {
fn blend_f32(
bottom: &[f32],
top: &[f32],
opacity: f32,
mode: &BlendMode,
result: &mut [f32],
) {
debug_assert_eq!(bottom.len(), top.len());
debug_assert_eq!(bottom.len(), result.len());
for i in (0..bottom.len()).step_by(4) {
let top_alpha = top[i + 3] * opacity;
let inv_alpha = 1.0 - top_alpha;
result[i] = bottom[i] * inv_alpha + apply_blend(bottom[i], top[i], mode) * top_alpha;
result[i + 1] = bottom[i + 1] * inv_alpha + apply_blend(bottom[i + 1], top[i + 1], mode) * top_alpha;
result[i + 2] = bottom[i + 2] * inv_alpha + apply_blend(bottom[i + 2], top[i + 2], mode) * top_alpha;
result[i + 3] = bottom[i + 3] * inv_alpha + top_alpha;
}
}
fn blend_f16(
bottom: &[half::f16],
top: &[half::f16],
opacity: f32,
mode: &BlendMode,
result: &mut [half::f16],
) {
use half::f16;
debug_assert_eq!(bottom.len(), top.len());
debug_assert_eq!(bottom.len(), result.len());
for i in (0..bottom.len()).step_by(4) {
let top_alpha = top[i + 3].to_f32() * opacity;
let inv_alpha = 1.0 - top_alpha;
let b0 = bottom[i].to_f32();
let b1 = bottom[i + 1].to_f32();
let b2 = bottom[i + 2].to_f32();
let b3 = bottom[i + 3].to_f32();
let t0 = top[i].to_f32();
let t1 = top[i + 1].to_f32();
let t2 = top[i + 2].to_f32();
result[i] = f16::from_f32(b0 * inv_alpha + apply_blend(b0, t0, mode) * top_alpha);
result[i + 1] = f16::from_f32(b1 * inv_alpha + apply_blend(b1, t1, mode) * top_alpha);
result[i + 2] = f16::from_f32(b2 * inv_alpha + apply_blend(b2, t2, mode) * top_alpha);
result[i + 3] = f16::from_f32(b3 * inv_alpha + top_alpha);
}
}
fn blend_u8(bottom: &[u8], top: &[u8], opacity: f32, mode: &BlendMode, result: &mut [u8]) {
debug_assert_eq!(bottom.len(), top.len());
debug_assert_eq!(bottom.len(), result.len());
for i in (0..bottom.len()).step_by(4) {
let top_alpha = (top[i + 3] as f32 / 255.0) * opacity;
let inv_alpha = 1.0 - top_alpha;
let r = bottom[i] as f32 / 255.0;
let g = bottom[i + 1] as f32 / 255.0;
let b = bottom[i + 2] as f32 / 255.0;
let tr = top[i] as f32 / 255.0;
let tg = top[i + 1] as f32 / 255.0;
let tb = top[i + 2] as f32 / 255.0;
let out_r = r * inv_alpha + apply_blend(r, tr, mode) * top_alpha;
let out_g = g * inv_alpha + apply_blend(g, tg, mode) * top_alpha;
let out_b = b * inv_alpha + apply_blend(b, tb, mode) * top_alpha;
let out_a = bottom[i + 3] as f32 / 255.0 * inv_alpha + top_alpha;
result[i] = (out_r.clamp(0.0, 1.0) * 255.0) as u8;
result[i + 1] = (out_g.clamp(0.0, 1.0) * 255.0) as u8;
result[i + 2] = (out_b.clamp(0.0, 1.0) * 255.0) as u8;
result[i + 3] = (out_a.clamp(0.0, 1.0) * 255.0) as u8;
}
}
pub(crate) fn blend(&self, frames: Vec<(Frame, f32, BlendMode, [f32; 9])>) -> Option<Frame> {
if let Some((first, _, _, _)) = frames.first() {
let dim = (first.width(), first.height());
return self.blend_with_dim(frames, dim);
}
None
}
pub(crate) fn blend_with_dim(
&self,
frames: Vec<(Frame, f32, BlendMode, [f32; 9])>,
dim: (usize, usize),
) -> Option<Frame> {
use log::trace;
trace!(
"CpuCompositor::blend_with_dim() called with {} frames into {}x{}",
frames.len(),
dim.0,
dim.1
);
if frames.is_empty() {
trace!(" -> empty frames, returning None");
return None;
}
let min_status = frames
.iter()
.map(|(f, _, _, _)| f.status())
.min_by_key(|s| match s {
FrameStatus::Error => 0,
FrameStatus::Placeholder => 1,
FrameStatus::Header => 2,
FrameStatus::Loading | FrameStatus::Composing | FrameStatus::Expired => 3,
FrameStatus::Loaded => 4,
})
.unwrap_or(FrameStatus::Placeholder);
trace!(" -> min_status from inputs: {:?}", min_status);
let (width, height) = dim;
let mut iter = frames.iter();
let (base_frame, _, _, _) = iter.next().unwrap(); let mut result = base_frame.clone();
result.crop(width, height, crate::entities::frame::CropAlign::LeftTop);
for (layer_frame, opacity, mode, _transform) in iter {
let result_buffer = result.buffer();
let layer_buffer = layer_frame.buffer();
let lw = layer_frame.width();
let lh = layer_frame.height();
let overlap_w = width.min(lw);
let overlap_h = height.min(lh);
if overlap_w == 0 || overlap_h == 0 {
continue;
}
macro_rules! blend_rows {
($blend_fn:ident, $curr:expr, $layer:expr, $out:expr) => {{
let base_stride = width * 4;
let layer_stride = lw * 4;
for y in 0..overlap_h {
let b_off = y * base_stride;
let l_off = y * layer_stride;
let base_slice = &$curr[b_off..b_off + overlap_w * 4];
let layer_slice = &$layer[l_off..l_off + overlap_w * 4];
let out_slice = &mut $out[b_off..b_off + overlap_w * 4];
Self::$blend_fn(base_slice, layer_slice, *opacity, mode, out_slice);
}
}};
}
match (&*result_buffer, &*layer_buffer) {
(PixelBuffer::F32(curr), PixelBuffer::F32(layer)) => {
let mut blended = curr.clone();
blend_rows!(blend_f32, curr, layer, blended);
result = Frame::from_f32_buffer_with_status(blended, width, height, min_status);
}
(PixelBuffer::F16(curr), PixelBuffer::F16(layer)) => {
let mut blended = curr.clone();
blend_rows!(blend_f16, curr, layer, blended);
result = Frame::from_f16_buffer_with_status(blended, width, height, min_status);
}
(PixelBuffer::U8(curr), PixelBuffer::U8(layer)) => {
let mut blended = curr.clone();
blend_rows!(blend_u8, curr, layer, blended);
result = Frame::from_u8_buffer_with_status(blended, width, height, min_status);
}
_ => {
log::warn!("Pixel format mismatch during compositing, skipping layer");
continue;
}
}
}
Some(result)
}
}