///|
/// Centralized limits used by public APIs and command-line validation.
pub(all) struct GifLimits {
max_dimension : Int
max_colors : Int
max_delay_cs : Int
max_frames : Int
max_comment_bytes : Int
} derive(Debug)
///|
pub fn GifLimits::default() -> GifLimits {
{
max_dimension: 65535,
max_colors: 256,
max_delay_cs: 65535,
max_frames: 100000,
max_comment_bytes: 65535,
}
}
///|
pub fn GifLimits::dimension(self : GifLimits) -> Int {
self.max_dimension
}
///|
pub fn GifLimits::colors(self : GifLimits) -> Int {
self.max_colors
}
///|
pub fn GifLimits::delay(self : GifLimits) -> Int {
self.max_delay_cs
}
///|
pub fn GifLimits::frames(self : GifLimits) -> Int {
self.max_frames
}
///|
pub fn GifLimits::comment_bytes(self : GifLimits) -> Int {
self.max_comment_bytes
}
///|
pub fn GifLimits::valid_dimensions(
self : GifLimits,
width : Int,
height : Int,
) -> Bool {
width > 0 &&
height > 0 &&
width <= self.max_dimension &&
height <= self.max_dimension
}
///|
pub fn GifLimits::clamp_delay(self : GifLimits, delay : Int) -> Int {
if delay < 0 {
0
} else if delay > self.max_delay_cs {
self.max_delay_cs
} else {
delay
}
}
///|
pub fn GifLimits::clamp_frames(self : GifLimits, count : Int) -> Int {
if count < 0 {
0
} else if count > self.max_frames {
self.max_frames
} else {
count
}
}
///|
/// Count violations against canonical GIF limits.
pub fn validate_limits(
animation : Animation,
limits : GifLimits,
) -> Array[String] {
let issues : Array[String] = []
if !limits.valid_dimensions(animation.width, animation.height) {
issues.push("logical screen dimensions exceed GIF limits")
}
if animation.frame_count() > limits.max_frames {
issues.push("frame count exceeds configured safety limit")
}
for _, frame in animation.frames {
if frame.canvas().width > limits.max_dimension ||
frame.canvas().height > limits.max_dimension {
issues.push("frame {index} exceeds configured dimension limit")
}
if frame.delay_cs() > limits.max_delay_cs {
issues.push("frame delay exceeds 16-bit GIF limit")
}
}
issues
}
///|
/// Clamp an animation to configured dimensions and delay limits.
pub fn Animation::clamp_limits(
self : Animation,
limits : GifLimits,
) -> Animation {
let repaired = repair_animation(self)
let result = Animation::new(width=repaired.width, height=repaired.height)
.with_loop_count(loop_count=repaired.loop_count)
.with_background(background=repaired.background)
for frame in repaired.frames {
ignore(
result.add_frame(
frame.with_delay(delay_cs=limits.clamp_delay(frame.delay_cs())),
),
)
}
result
}