///|
/// Split a payload into GIF data sub-blocks. The returned bytes include the
/// length byte for every block and the final zero terminator.
pub fn pack_sub_blocks(payload : Bytes, block_size? : Int = 255) -> Bytes {
let safe_size = if block_size < 1 {
1
} else if block_size > 255 {
255
} else {
block_size
}
let bytes : Array[Byte] = []
let mut offset = 0
while offset < payload.length() {
let remaining = payload.length() - offset
let size = if remaining > safe_size { safe_size } else { remaining }
bytes.push(size.to_byte())
for i in 0.. Bytes raise GifError {
let cursor = GifCursor::new(data)
let payload : Array[Byte] = []
let mut terminated = false
while cursor.remaining() > 0 && !terminated {
let size = cursor.byte(context="sub-block size").to_int()
if size == 0 {
terminated = true
} else {
for _ in 0.. Bytes raise GifError {
pack_sub_blocks(lzw_encode(indices, min_code_size))
}
///|
/// Decode a complete GIF sub-block sequence containing an LZW payload.
pub fn lzw_decode_sub_blocks(
data : Bytes,
min_code_size : Int,
expected_len : Int,
) -> Array[Byte] raise GifError {
lzw_decode(unpack_sub_blocks(data), min_code_size, expected_len)
}
///|
/// Compression metrics for an index stream.
pub(all) struct LzwStats {
input_bytes : Int
compressed_bytes : Int
minimum_code_size : Int
} derive(Debug)
///|
pub fn LzwStats::input_bytes(self : LzwStats) -> Int {
self.input_bytes
}
///|
pub fn LzwStats::compressed_bytes(self : LzwStats) -> Int {
self.compressed_bytes
}
///|
pub fn LzwStats::minimum_code_size(self : LzwStats) -> Int {
self.minimum_code_size
}
///|
pub fn LzwStats::saving_per_mille(self : LzwStats) -> Int {
if self.input_bytes <= 0 {
0
} else {
(self.input_bytes - self.compressed_bytes) * 1000 / self.input_bytes
}
}
///|
pub fn analyze_lzw(
indices : ArrayView[Byte],
min_code_size : Int,
) -> LzwStats raise GifError {
let compressed = lzw_encode(indices, min_code_size)
LzwStats::{
input_bytes: indices.length(),
compressed_bytes: compressed.length(),
minimum_code_size: min_code_size,
}
}
///|
/// A conservative encoded-size estimate for a single frame.
pub(all) struct FrameEstimate {
width : Int
height : Int
palette_bytes : Int
lzw_bytes : Int
total_bytes : Int
} derive(Debug)
///|
pub fn FrameEstimate::width(self : FrameEstimate) -> Int {
self.width
}
///|
pub fn FrameEstimate::height(self : FrameEstimate) -> Int {
self.height
}
///|
pub fn FrameEstimate::palette_bytes(self : FrameEstimate) -> Int {
self.palette_bytes
}
///|
pub fn FrameEstimate::lzw_bytes(self : FrameEstimate) -> Int {
self.lzw_bytes
}
///|
pub fn FrameEstimate::total_bytes(self : FrameEstimate) -> Int {
self.total_bytes
}
///|
fn estimate_frame(
frame : Frame,
max_colors : Int,
) -> FrameEstimate raise GifError {
let quantized = match frame.transparent {
None => quantize(frame.canvas, max_colors~)
Some(color) => quantize(frame.canvas, max_colors~, transparent=color)
}
let palette = quantized.palette()
let (table_size, table_bits) = palette_table_size(palette)
let min_code_size = min_lzw_code_size(table_bits)
let compressed = lzw_encode(quantized.indices(), min_code_size)
let total = 19 +
table_size * 3 +
compressed.length() +
compressed.length() / 255 +
1
FrameEstimate::{
width: frame.canvas.width,
height: frame.canvas.height,
palette_bytes: table_size * 3,
lzw_bytes: compressed.length(),
total_bytes: total,
}
}
///|
/// Estimate frame sizes using the same quantizer and LZW encoder as `encode`.
pub fn estimate_frames(
animation : Animation,
max_colors? : Int = 256,
) -> Array[FrameEstimate] raise GifError {
let estimates : Array[FrameEstimate] = []
for frame in animation.frames {
estimates.push(estimate_frame(frame, max_colors))
}
estimates
}
///|
/// Return an estimate for the whole encoded stream.
pub fn estimate_size(
animation : Animation,
max_colors? : Int = 256,
) -> Int raise GifError {
let estimates = estimate_frames(animation, max_colors~)
let mut total = 13
for estimate in estimates {
total += estimate.total_bytes
}
let loop_bytes = match animation.loop_count {
Some(_) => 19
None => 0
}
total + loop_bytes
}