///|
/// Transition shapes for generating in-between GIF frames.
pub(all) enum TransitionKind {
CrossFade
WipeLeft
WipeRight
WipeUp
WipeDown
ZoomIn
ZoomOut
Dissolve
} derive(Eq, Debug)
///|
pub(all) struct TransitionOptions {
kind : TransitionKind
steps : Int
delay_cs : Int
seed : Int
} derive(Debug)
///|
pub fn TransitionOptions::default() -> TransitionOptions {
{ kind: CrossFade, steps: 6, delay_cs: 4, seed: 1 }
}
///|
pub fn TransitionOptions::kind(
self : TransitionOptions,
value : TransitionKind,
) -> TransitionOptions {
{ ..self, kind: value }
}
///|
pub fn TransitionOptions::steps(
self : TransitionOptions,
value : Int,
) -> TransitionOptions {
{
..self,
steps: if value < 1 {
1
} else if value > 256 {
256
} else {
value
},
}
}
///|
pub fn TransitionOptions::delay(
self : TransitionOptions,
value : Int,
) -> TransitionOptions {
{
..self,
delay_cs: if value < 0 {
0
} else if value > 65535 {
65535
} else {
value
},
}
}
///|
pub fn TransitionOptions::seed(
self : TransitionOptions,
value : Int,
) -> TransitionOptions {
{ ..self, seed: value }
}
///|
fn transition_pixel(
left : Rgba,
right : Rgba,
x : Int,
y : Int,
width : Int,
height : Int,
step : Int,
steps : Int,
kind : TransitionKind,
seed : Int,
) -> Rgba {
let progress = if steps <= 0 { 1000 } else { step * 1000 / steps }
let safe_width = if width == 0 { 1 } else { width }
let safe_height = if height == 0 { 1 } else { height }
match kind {
CrossFade => mix_color(left, right, progress, 1000)
WipeLeft => if x * 1000 / safe_width <= progress { right } else { left }
WipeRight =>
if (width - x) * 1000 / safe_width <= progress {
right
} else {
left
}
WipeUp => if y * 1000 / safe_height <= progress { right } else { left }
WipeDown =>
if (height - y) * 1000 / safe_height <= progress {
right
} else {
left
}
ZoomIn => {
let center_x = width / 2
let center_y = height / 2
let source_x = center_x + (x - center_x) * (1000 - progress) / 1000
let source_y = center_y + (y - center_y) * (1000 - progress) / 1000
if source_x == x && source_y == y {
right
} else {
mix_color(left, right, progress, 1000)
}
}
ZoomOut => {
let pulse = (progress + (x + y + seed) % 17) % 1000
mix_color(left, right, pulse, 1000)
}
Dissolve => {
let noise = ((x * 928371 + y * 364479 + seed * 17) % 1000).abs()
if noise <= progress {
right
} else {
left
}
}
}
}
///|
fn transition_canvas(
left : Canvas,
right : Canvas,
step : Int,
steps : Int,
options : TransitionOptions,
) -> Canvas {
let width = if left.width < right.width { left.width } else { right.width }
let height = if left.height < right.height {
left.height
} else {
right.height
}
let result = Canvas::new(width~, height~, fill=rgba(0, 0, 0, 0))
for y in 0.. Array[Frame] {
let result : Array[Frame] = []
let steps = if options.steps < 1 { 1 } else { options.steps }
for step in 1.. Animation {
if self.frames.length() <= 1 {
self
} else {
let result = Animation::new(width=self.width, height=self.height)
.with_loop_count(loop_count=self.loop_count)
.with_background(background=self.background)
for i, frame in self.frames {
ignore(result.add_frame(frame))
if i + 1 < self.frames.length() {
for transition in transition_frames(frame, self.frames[i + 1], options~) {
ignore(result.add_frame(transition))
}
}
}
result
}
}
///|
/// Crossfade two complete animations by pairing frames cyclically.
pub fn crossfade_animations(
left : Animation,
right : Animation,
steps : Int,
delay_cs : Int,
) -> Animation {
let width = if left.width < right.width { left.width } else { right.width }
let height = if left.height < right.height {
left.height
} else {
right.height
}
let result = Animation::new(width~, height~)
let count = if left.frame_count() > right.frame_count() {
left.frame_count()
} else {
right.frame_count()
}
let safe_steps = if steps < 1 { 1 } else { steps }
for index in 0.. Animation {
let safe_steps = if steps < 1 { 1 } else { steps }
let result = Animation::new(width=left.width, height=left.height)
for step in 0..<=safe_steps {
ignore(
result.add_frame(
Frame::new(
canvas=transition_canvas(
left,
right,
step,
safe_steps,
TransitionOptions::default(),
),
delay_cs~,
),
),
)
}
let mut step = safe_steps - 1
while step > 0 {
ignore(
result.add_frame(
Frame::new(
canvas=transition_canvas(
left,
right,
step,
safe_steps,
TransitionOptions::default(),
),
delay_cs~,
),
),
)
step -= 1
}
result
}