use crate::api::ExtensionError;
use crate::budget::check_canvas;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum Blend {
Source,
Over,
OverWebp,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum Dispose {
None,
Background,
Previous,
}
pub(crate) struct PartialFrame {
pub(crate) x: u32,
pub(crate) y: u32,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) pixels: Vec<u8>,
pub(crate) blend: Blend,
pub(crate) dispose: Dispose,
}
pub(crate) struct Compositor {
width: u32,
height: u32,
canvas: Vec<u8>,
pending: Option<(Dispose, [u32; 4])>,
saved: Option<([u32; 4], Vec<u8>)>,
drawn: bool,
key: bool,
}
impl Compositor {
pub(crate) fn new(width: u32, height: u32) -> Result<Self, ExtensionError> {
check_canvas(width, height)?;
Ok(Self {
width,
height,
canvas: vec![0; width as usize * height as usize * 4],
pending: None,
saved: None,
drawn: false,
key: false,
})
}
pub(crate) fn canvas(&self) -> &[u8] {
&self.canvas
}
pub(crate) fn draw(&mut self, frame: &PartialFrame) -> Result<(), ExtensionError> {
let declared = u64::from(frame.width) * u64::from(frame.height) * 4;
if u64::try_from(frame.pixels.len()).ok() != Some(declared) {
return Err(ExtensionError::Malformed(format!(
"a frame declares {}x{} pixels and its picture holds {} bytes",
frame.width,
frame.height,
frame.pixels.len()
)));
}
let previous = self.pending.take();
let full = |[x, y, width, height]: [u32; 4]| {
x == 0 && y == 0 && width >= self.width && height >= self.height
};
let key = !self.drawn
|| matches!(previous, Some((Dispose::Background, rect)) if full(rect) || self.key);
let unblended = match previous {
_ if key => Some(self.clip([0, 0, self.width, self.height])),
Some((Dispose::Background, rect)) => Some(self.clip(rect)),
_ => None,
};
let inside = |x: u32, y: u32| {
unblended.is_some_and(|[left, top, right, bottom]| {
(left..right).contains(&x) && (top..bottom).contains(&y)
})
};
self.drawn = true;
self.key = key;
match previous {
Some((Dispose::Background, rect)) => self.clear(rect),
Some((Dispose::Previous, _)) => {
if let Some((rect, pixels)) = self.saved.take() {
self.restore(rect, &pixels);
}
}
Some((Dispose::None, _)) | None => {}
}
let rect = [frame.x, frame.y, frame.width, frame.height];
if frame.dispose == Dispose::Previous {
let clipped = self.clip(rect);
self.saved = Some((clipped, self.copy(clipped)));
}
let [left, top, right, bottom] = self.clip(rect);
for y in top..bottom {
for x in left..right {
let from = ((y - frame.y) * frame.width + (x - frame.x)) as usize * 4;
let to = (y * self.width + x) as usize * 4;
let source = &frame.pixels[from..from + 4];
let target = &mut self.canvas[to..to + 4];
match frame.blend {
Blend::Source => target.copy_from_slice(source),
Blend::Over => over(source, target),
Blend::OverWebp if inside(x, y) => target.copy_from_slice(source),
Blend::OverWebp => over_webp(source, target),
}
}
}
self.pending = Some((frame.dispose, rect));
Ok(())
}
fn clip(&self, [x, y, width, height]: [u32; 4]) -> [u32; 4] {
let left = x.min(self.width);
let top = y.min(self.height);
let right = x.saturating_add(width).min(self.width);
let bottom = y.saturating_add(height).min(self.height);
[left, top, right, bottom]
}
fn copy(&self, [left, top, right, bottom]: [u32; 4]) -> Vec<u8> {
let mut pixels = Vec::with_capacity(((right - left) * (bottom - top)) as usize * 4);
for row in top..bottom {
let from = (row * self.width + left) as usize * 4;
let to = (row * self.width + right) as usize * 4;
pixels.extend_from_slice(&self.canvas[from..to]);
}
pixels
}
fn restore(&mut self, [left, top, right, bottom]: [u32; 4], pixels: &[u8]) {
let row_bytes = (right - left) as usize * 4;
if row_bytes == 0 {
return;
}
for (row, saved) in (top..bottom).zip(pixels.chunks_exact(row_bytes)) {
let from = (row * self.width + left) as usize * 4;
self.canvas[from..from + row_bytes].copy_from_slice(saved);
}
}
fn clear(&mut self, rect: [u32; 4]) {
let [left, top, right, bottom] = self.clip(rect);
for row in top..bottom {
let from = (row * self.width + left) as usize * 4;
let to = (row * self.width + right) as usize * 4;
self.canvas[from..to].fill(0);
}
}
}
fn over(source: &[u8], target: &mut [u8]) {
let source_alpha = u32::from(source[3]);
if source_alpha == 255 {
target.copy_from_slice(source);
return;
}
if source_alpha == 0 {
return;
}
let target_alpha = u32::from(target[3]);
let covered = target_alpha * (255 - source_alpha);
let alpha = source_alpha * 255 + covered;
for channel in 0..3 {
let value =
u32::from(source[channel]) * source_alpha * 255 + u32::from(target[channel]) * covered;
target[channel] = u8::try_from((value + alpha / 2) / alpha).unwrap_or(255);
}
target[3] = u8::try_from((alpha + 127) / 255).unwrap_or(255);
}
fn over_webp(source: &[u8], target: &mut [u8]) {
let source_alpha = u32::from(source[3]);
if source_alpha == 255 {
target.copy_from_slice(source);
return;
}
if source_alpha == 0 {
return;
}
let dst_factor = (u32::from(target[3]) * (256 - source_alpha)) >> 8;
let alpha = source_alpha + dst_factor;
let scale = (1_u32 << 24) / alpha;
for channel in 0..3 {
let unscaled = u64::from(source[channel]) * u64::from(source_alpha)
+ u64::from(target[channel]) * u64::from(dst_factor);
target[channel] = u8::try_from((unscaled * u64::from(scale)) >> 24).unwrap_or(255);
}
target[3] = u8::try_from(alpha).unwrap_or(255);
}