///|
/// High-level export intent used by applications instead of raw encoder flags.
pub(all) enum ExportProfile {
Preview
Balanced
SmallFile
Archive
Sticker
} derive(Eq, Debug)
///|
pub(all) struct ExportPlan {
profile : ExportProfile
optimization : OptimizationOptions
encode : EncodeOptions
estimated_bytes : Int
quality_target : Int
} derive(Debug)
///|
pub fn ExportPlan::profile(self : ExportPlan) -> ExportProfile {
self.profile
}
///|
pub fn ExportPlan::optimization(self : ExportPlan) -> OptimizationOptions {
self.optimization
}
///|
pub fn ExportPlan::encode(self : ExportPlan) -> EncodeOptions {
self.encode
}
///|
pub fn ExportPlan::estimated_bytes(self : ExportPlan) -> Int {
self.estimated_bytes
}
///|
pub fn ExportPlan::quality_target(self : ExportPlan) -> Int {
self.quality_target
}
///|
fn profile_options(
profile : ExportProfile,
) -> (OptimizationOptions, EncodeOptions, Int) {
match profile {
Preview => {
let optimization = OptimizationOptions::default()
.colors(32)
.merge_equal(false)
.crop_deltas(false)
(optimization, EncodeOptions::new(max_colors=32, default_delay_cs=8), 450)
}
Balanced => {
let optimization = OptimizationOptions::default().colors(128).threshold(4)
(
optimization,
EncodeOptions::new(max_colors=128, default_delay_cs=6),
800,
)
}
SmallFile => {
let optimization = OptimizationOptions::default().colors(64).threshold(8)
(optimization, EncodeOptions::new(max_colors=64, default_delay_cs=6), 620)
}
Archive => {
let optimization = OptimizationOptions::default()
.colors(256)
.threshold(0)
.merge_equal(false)
(
optimization,
EncodeOptions::new(max_colors=256, default_delay_cs=4),
980,
)
}
Sticker => {
let optimization = OptimizationOptions::default()
.colors(128)
.threshold(2)
.merge_equal(true)
(
optimization,
EncodeOptions::new(max_colors=128, default_delay_cs=10),
760,
)
}
}
}
///|
/// Choose a practical optimization and palette plan for a target use case.
pub fn Animation::plan_export(
self : Animation,
profile : ExportProfile,
) -> ExportPlan raise GifError {
let (optimization, encode, quality_target) = profile_options(profile)
let report = self.optimization_report(optimization)
ExportPlan::{
profile,
optimization,
encode,
estimated_bytes: report.estimated_bytes(),
quality_target,
}
}
///|
/// Apply an export plan and encode it in one operation.
pub fn Animation::export_with_plan(
self : Animation,
plan : ExportPlan,
) -> Bytes raise GifError {
let optimized = self.optimize_for_size(plan.optimization)
encode_with_options(optimized, plan.encode)
}
///|
/// Produce a complete profile report for size and frame-retention review.
pub(all) struct ExportReport {
profile : ExportProfile
input_frames : Int
output_frames : Int
estimated_bytes : Int
actual_bytes : Int
retained_pixels_per_mille : Int
quality_target : Int
} derive(Debug)
///|
pub fn ExportReport::profile(self : ExportReport) -> ExportProfile {
self.profile
}
///|
pub fn ExportReport::input_frames(self : ExportReport) -> Int {
self.input_frames
}
///|
pub fn ExportReport::output_frames(self : ExportReport) -> Int {
self.output_frames
}
///|
pub fn ExportReport::estimated_bytes(self : ExportReport) -> Int {
self.estimated_bytes
}
///|
pub fn ExportReport::actual_bytes(self : ExportReport) -> Int {
self.actual_bytes
}
///|
pub fn ExportReport::retained_pixels_per_mille(self : ExportReport) -> Int {
self.retained_pixels_per_mille
}
///|
pub fn ExportReport::quality_target(self : ExportReport) -> Int {
self.quality_target
}
///|
/// Run an export profile and return measured encoded bytes.
pub fn Animation::export_report(
self : Animation,
profile : ExportProfile,
) -> ExportReport raise GifError {
let plan = self.plan_export(profile)
let optimized = self.optimize_for_size(plan.optimization)
let bytes = encode_with_options(optimized, plan.encode)
let full_pixels = self.width * self.height * self.frame_count()
let mut output_pixels = 0
for frame in optimized.frames {
output_pixels += frame.canvas().len()
}
let retained = if full_pixels <= 0 {
0
} else {
output_pixels * 1000 / full_pixels
}
ExportReport::{
profile,
input_frames: self.frame_count(),
output_frames: optimized.frame_count(),
estimated_bytes: plan.estimated_bytes,
actual_bytes: bytes.length(),
retained_pixels_per_mille: retained,
quality_target: plan.quality_target,
}
}
///|
/// Select a profile from a size budget using the optimizer's exact estimate.
pub fn Animation::profile_for_budget(
self : Animation,
budget_bytes : Int,
) -> ExportProfile raise GifError {
let safe = if budget_bytes < 1 { 1 } else { budget_bytes }
let profiles = [SmallFile, Sticker, Balanced, Archive]
let mut selected = SmallFile
for profile in profiles {
let plan = self.plan_export(profile)
if plan.estimated_bytes() <= safe {
selected = profile
}
}
selected
}