use super::super::damage::RING;
use super::super::source::validate_size;
use super::super::{LiveImageError, LiveImageStatus, PixelRect};
use super::{LiveImageWriter, check_source};
pub struct LiveImageDiffWriter {
writer: LiveImageWriter,
copy: Vec<u8>,
size: Option<(u32, u32)>,
generation: u64,
rects: Vec<PixelRect>,
}
impl LiveImageDiffWriter {
pub fn new(writer: LiveImageWriter) -> Self {
Self {
writer,
copy: Vec::new(),
size: None,
generation: 0,
rects: Vec::new(),
}
}
pub fn write_frame(
&mut self,
width: u32,
height: u32,
src: &[u8],
src_stride: usize,
) -> Result<Option<u64>, LiveImageError> {
validate_size(width, height)?;
let bpp = self.writer.source_ref().format().bytes_per_pixel();
check_source(width, height, src, src_stride, bpp)?;
let same_size = self.size == Some((width, height));
self.rects.clear();
if same_size {
changed_rects(
&mut self.rects,
&self.copy,
src,
src_stride,
width,
height,
bpp,
);
if self.rects.is_empty() && self.source_holds_copy(width, height) {
return Ok(None);
}
}
let mut guard = self.writer.lock()?;
let exact = same_size && {
let state = guard.state();
state.generation == self.generation && !state.pending.force_full && !state.pending.wrote
};
debug_assert!(
!exact || !self.rects.is_empty(),
"the source holds this writer's copy, yet its lock-free check said otherwise"
);
let whole = !exact;
if whole {
if let Err(error) = guard.resize(width, height) {
guard.counted = false;
return Err(error);
}
guard.write_rect(PixelRect::full(width, height), src, src_stride)?;
} else {
for rect in &self.rects {
let at = rect.y as usize * src_stride + rect.x as usize * bpp;
guard.write_rect(*rect, &src[at..], src_stride)?;
}
}
let generation = guard.commit();
let row = width as usize * bpp;
if whole {
self.copy.clear();
self.copy.reserve(row * height as usize);
for y in 0..height as usize {
self.copy
.extend_from_slice(&src[y * src_stride..y * src_stride + row]);
}
} else {
for rect in &self.rects {
let (x, span) = (rect.x as usize * bpp, rect.width as usize * bpp);
for y in rect.y as usize..(rect.y + rect.height) as usize {
let from = y * src_stride + x;
self.copy[y * row + x..y * row + x + span]
.copy_from_slice(&src[from..from + span]);
}
}
}
self.size = Some((width, height));
self.generation = generation;
Ok(Some(generation))
}
fn source_holds_copy(&self, width: u32, height: u32) -> bool {
let source = self.writer.source_ref();
let meta = source.meta();
source.generation() == self.generation
&& meta.status == LiveImageStatus::Live
&& meta.size == Some((width, height))
}
pub fn forget(&mut self) {
self.copy = Vec::new();
self.size = None;
}
pub fn writer(&self) -> &LiveImageWriter {
&self.writer
}
pub fn into_writer(self) -> LiveImageWriter {
self.writer
}
}
impl std::fmt::Debug for LiveImageDiffWriter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LiveImageDiffWriter")
.field("writer", &self.writer)
.field("size", &self.size)
.field("generation", &self.generation)
.finish_non_exhaustive()
}
}
pub(super) fn changed_rects(
out: &mut Vec<PixelRect>,
copy: &[u8],
src: &[u8],
src_stride: usize,
width: u32,
height: u32,
bpp: usize,
) {
out.clear();
let row = width as usize * bpp;
let mut band: Option<(u32, u32, u32)> = None; for y in 0..height {
let theirs = &src[y as usize * src_stride..][..row];
let ours = ©[y as usize * row..][..row];
if theirs == ours {
if let Some((top, x0, x1)) = band.take() {
out.push(PixelRect::new(x0, top, x1 - x0, y - top));
}
continue;
}
let first = first_difference(theirs, ours) / bpp;
let last = last_difference(theirs, ours) + 1;
let (x0, x1) = (first as u32, last.div_ceil(bpp) as u32);
band = Some(match band {
Some((top, b0, b1)) => (top, b0.min(x0), b1.max(x1)),
None => (y, x0, x1),
});
}
if let Some((top, x0, x1)) = band {
out.push(PixelRect::new(x0, top, x1 - x0, height - top));
}
while out.len() > RING {
let (at, _) = out
.windows(2)
.enumerate()
.map(|(i, pair)| {
let merged = pair[0].union(&pair[1]);
(i, merged.area() - pair[0].area() - pair[1].area())
})
.min_by_key(|&(_, added)| added)
.expect("more than RING rects have neighbours");
let merged = out[at].union(&out[at + 1]);
out[at] = merged;
out.remove(at + 1);
}
}
const CHUNK: usize = 64;
fn first_difference(a: &[u8], b: &[u8]) -> usize {
let start = a
.chunks(CHUNK)
.zip(b.chunks(CHUNK))
.position(|(x, y)| x != y)
.map_or(0, |chunk| chunk * CHUNK);
start
+ a[start..]
.iter()
.zip(&b[start..])
.position(|(x, y)| x != y)
.unwrap_or(0)
}
fn last_difference(a: &[u8], b: &[u8]) -> usize {
let tail = a.len() % CHUNK;
let end = if a[a.len() - tail..] != b[b.len() - tail..] {
a.len()
} else {
let body = a.len() - tail;
let (ours, _) = a[..body].as_chunks::<CHUNK>();
let (theirs, _) = b[..body].as_chunks::<CHUNK>();
ours.iter()
.zip(theirs)
.rposition(|(x, y)| x != y)
.map_or(0, |chunk| (chunk + 1) * CHUNK)
};
let from = end.saturating_sub(CHUNK.max(tail));
from + a[from..end]
.iter()
.zip(&b[from..end])
.rposition(|(x, y)| x != y)
.unwrap_or(0)
}
#[cfg(test)]
mod tests;