///|
/// A rectangular patch containing pixels that differ from a previous canvas.
pub(all) struct PixelPatch {
rect : GifRect
pixels : Array[Rgba]
} derive(Debug)
///|
pub fn PixelPatch::rect(self : PixelPatch) -> GifRect {
self.rect
}
///|
pub fn PixelPatch::pixels(self : PixelPatch) -> ArrayView[Rgba] {
self.pixels[:]
}
///|
pub fn PixelPatch::pixel_count(self : PixelPatch) -> Int {
self.pixels.length()
}
///|
pub fn PixelPatch::area(self : PixelPatch) -> Int {
self.rect.area()
}
///|
pub fn PixelPatch::is_empty(self : PixelPatch) -> Bool {
self.pixels.length() == 0
}
///|
/// Extract a changed rectangle from `source` relative to `base`.
pub fn Canvas::diff_patch(
self : Canvas,
base : Canvas,
threshold? : Int = 0,
) -> PixelPatch? {
match changed_rect(base, self, threshold) {
None => None
Some(rect) => {
let pixels : Array[Rgba] = []
for y in 0.. pixels.push(rgba(0, 0, 0, 0))
Some(color) => pixels.push(color)
}
}
}
Some(PixelPatch::{ rect, pixels })
}
}
}
///|
/// Apply a patch with clipping and alpha compositing.
pub fn Canvas::apply_patch(self : Canvas, patch : PixelPatch) -> Int {
let rect = patch.rect
let mut written = 0
for y in 0..= 0 &&
destination_y >= 0 &&
destination_x < self.width &&
destination_y < self.height {
let source = patch.pixels[x + y * rect.width()]
if self.blend_pixel(x=destination_x, y=destination_y, color=source) {
written += 1
}
}
}
}
written
}
///|
/// Apply a patch by replacing pixels rather than alpha blending.
pub fn Canvas::write_patch(self : Canvas, patch : PixelPatch) -> Int {
let rect = patch.rect
let mut written = 0
for y in 0.. Canvas {
let result = Canvas::new(width~, height~, fill=background)
for patch in patches {
ignore(result.write_patch(patch))
}
result
}
///|
pub(all) struct PatchFrame {
patch : PixelPatch
delay_cs : Int
} derive(Debug)
///|
pub fn PatchFrame::patch(self : PatchFrame) -> PixelPatch {
self.patch
}
///|
pub fn PatchFrame::delay_cs(self : PatchFrame) -> Int {
self.delay_cs
}
///|
/// Compute one patch per frame, using the logical background as the first base.
pub fn Animation::patch_frames(
self : Animation,
threshold? : Int = 0,
) -> Array[PatchFrame] {
let result : Array[PatchFrame] = []
let mut previous = self.background_canvas()
for frame in self.frames {
match frame.canvas.diff_patch(previous, threshold~) {
None => {
let empty = PixelPatch::{
rect: GifRect::new(left=0, top=0, width=0, height=0),
pixels: [],
}
result.push(PatchFrame::{ patch: empty, delay_cs: frame.delay_cs })
}
Some(patch) =>
result.push(PatchFrame::{ patch, delay_cs: frame.delay_cs })
}
previous = frame.canvas
}
result
}
///|
/// Reconstruct full frames from patch frames.
pub fn animation_from_patches(
width~ : Int,
height~ : Int,
background~ : Rgba,
patches : ArrayView[PatchFrame],
loop_count? : Int? = Some(0),
) -> Animation {
let frames : Array[Frame] = []
let current = Canvas::new(width~, height~, fill=background)
for patch_frame in patches {
if !patch_frame.patch.is_empty() {
ignore(current.write_patch(patch_frame.patch))
}
frames.push(
Frame::new(canvas=current.copy(), delay_cs=patch_frame.delay_cs),
)
}
Animation::{ width, height, frames, loop_count, background }
}
///|
/// Return a patch suitable for a GIF local image descriptor.
pub fn PixelPatch::as_frame(self : PixelPatch, delay_cs~ : Int) -> Frame {
let canvas = Canvas::new(
width=self.rect.width(),
height=self.rect.height(),
fill=rgba(0, 0, 0, 0),
)
for y in 0.. Int {
let patches = self.patch_frames(threshold~)
let full = self.width * self.height * patches.length()
if full == 0 {
0
} else {
let mut changed = 0
for item in patches {
changed += item.patch.area()
}
changed * 1000 / full
}
}