///|
/// Multi-animation composition helpers for stickers and screen-recording scenes.
pub(all) enum CompositeAlignment {
TopLeft
Center
BottomRight
Tile
} derive(Eq, Debug)
///|
pub(all) struct CompositeOptions {
alignment : CompositeAlignment
gap : Int
background : Rgba
loop_to_longest : Bool
} derive(Debug)
///|
pub fn CompositeOptions::default() -> CompositeOptions {
{
alignment: Center,
gap: 0,
background: rgba(0, 0, 0, 0),
loop_to_longest: true,
}
}
///|
pub fn CompositeOptions::alignment(
self : CompositeOptions,
value : CompositeAlignment,
) -> CompositeOptions {
{ ..self, alignment: value }
}
///|
pub fn CompositeOptions::gap(
self : CompositeOptions,
value : Int,
) -> CompositeOptions {
{ ..self, gap: if value < 0 { 0 } else { value } }
}
///|
pub fn CompositeOptions::background(
self : CompositeOptions,
value : Rgba,
) -> CompositeOptions {
{ ..self, background: value }
}
///|
pub fn CompositeOptions::loop_to_longest(
self : CompositeOptions,
value : Bool,
) -> CompositeOptions {
{ ..self, loop_to_longest: value }
}
///|
fn aligned_offset(
width : Int,
height : Int,
layer_width : Int,
layer_height : Int,
alignment : CompositeAlignment,
) -> (Int, Int) {
match alignment {
TopLeft => (0, 0)
Center => ((width - layer_width) / 2, (height - layer_height) / 2)
BottomRight => (width - layer_width, height - layer_height)
Tile => (0, 0)
}
}
///|
/// Composite one animation over another at a shared timeline position.
pub fn composite_animations(
base : Animation,
overlay : Animation,
options? : CompositeOptions = CompositeOptions::default(),
) -> Animation {
let width = if base.width > overlay.width {
base.width
} else {
overlay.width
}
let height = if base.height > overlay.height {
base.height
} else {
overlay.height
}
let count = if options.loop_to_longest {
if base.frame_count() > overlay.frame_count() {
base.frame_count()
} else {
overlay.frame_count()
}
} else if base.frame_count() < overlay.frame_count() {
base.frame_count()
} else {
overlay.frame_count()
}
let result = Animation::new(width~, height~)
if count == 0 {
result
} else {
for index in 0.. overlay_frame.delay_cs() {
base_frame.delay_cs()
} else {
overlay_frame.delay_cs()
}
ignore(result.add_frame(Frame::new(canvas~, delay_cs=delay)))
}
result
}
}
///|
/// Compose three animation layers in back-to-front order.
pub fn composite_three(
base : Animation,
middle : Animation,
front : Animation,
options : CompositeOptions,
) -> Animation {
composite_animations(
composite_animations(base, middle, options~),
front,
options~,
)
}
///|
/// Stack a static canvas over every frame at a selected alignment.
pub fn Animation::overlay_static(
self : Animation,
overlay : Canvas,
alignment : CompositeAlignment,
) -> Animation {
let result = Animation::new(width=self.width, height=self.height)
.with_loop_count(loop_count=self.loop_count)
.with_background(background=self.background)
let (left, top) = aligned_offset(
self.width,
self.height,
overlay.width,
overlay.height,
alignment,
)
for frame in self.frames {
let canvas = frame.canvas().copy()
ignore(canvas.blit(overlay, destination_x=left, destination_y=top))
ignore(result.add_frame(frame.with_canvas(canvas)))
}
result
}
///|
/// Make a tiled animation from a single source animation.
pub fn Animation::tile_frames(
self : Animation,
columns : Int,
rows : Int,
gap : Int,
background : Rgba,
) -> Animation {
let safe_columns = if columns < 1 { 1 } else { columns }
let safe_rows = if rows < 1 { 1 } else { rows }
let output_width = self.width * safe_columns + (safe_columns - 1) * gap
let output_height = self.height * safe_rows + (safe_rows - 1) * gap
let result = Animation::new(width=output_width, height=output_height)
for index, frame in self.frames {
let canvas = Canvas::new(
width=output_width,
height=output_height,
fill=background,
)
for row in 0.. Animation {
if self.frames.length() <= 1 {
self
} else {
let frames : Array[Frame] = []
let mut current = self.frames[0]
let mut current_hash = current.canvas().hash()
for i in 1.. Array[Int] {
let count = if base.frame_count() < overlay.frame_count() {
base.frame_count()
} else {
overlay.frame_count()
}
let result : Array[Int] = []
for i in 0..