use std::fs::File;
use std::path::Path;
use gif::{Encoder, Frame, Repeat};
use image::DynamicImage;
use crate::configuration::{FrameOutputOptions, PixelFormat};
use crate::error::UnbundleError;
#[derive(Debug, Clone)]
pub struct GifOptions {
pub width: Option<u32>,
pub frame_delay: u16,
pub repeat: Option<u16>,
}
impl Default for GifOptions {
fn default() -> Self {
Self {
width: None,
frame_delay: 10,
repeat: None,
}
}
}
impl GifOptions {
pub fn new() -> Self {
Self::default()
}
pub fn width(mut self, width: u32) -> Self {
self.width = Some(width);
self
}
pub fn with_width(self, width: u32) -> Self {
self.width(width)
}
pub fn frame_delay(mut self, delay: u16) -> Self {
self.frame_delay = delay;
self
}
pub fn with_frame_delay(self, delay: u16) -> Self {
self.frame_delay(delay)
}
pub fn repeat(mut self, repeat: Option<u16>) -> Self {
self.repeat = repeat;
self
}
pub fn with_repeat(self, repeat: Option<u16>) -> Self {
self.repeat(repeat)
}
pub(crate) fn to_frame_output_config(
&self,
_source_width: u32,
_source_height: u32,
) -> FrameOutputOptions {
let mut frame_output = FrameOutputOptions::default();
frame_output.pixel_format = PixelFormat::Rgba8;
if let Some(width) = self.width {
frame_output.width = Some(width);
}
frame_output
}
}
pub(crate) fn encode_gif<P: AsRef<Path>>(
path: P,
frames: &[DynamicImage],
config: &GifOptions,
) -> Result<(), UnbundleError> {
log::debug!(
"Encoding {} frames to GIF file {:?} (width={:?}, delay={})",
frames.len(),
path.as_ref(),
config.width,
config.frame_delay,
);
if frames.is_empty() {
return Ok(());
}
let first = &frames[0];
let width = first.width() as u16;
let height = first.height() as u16;
let file = File::create(path.as_ref())
.map_err(|e| UnbundleError::GifEncodeError(format!("Failed to create GIF file: {e}")))?;
let mut encoder = Encoder::new(file, width, height, &[])
.map_err(|e| UnbundleError::GifEncodeError(format!("Failed to create GIF encoder: {e}")))?;
let repeat = match config.repeat {
None => Repeat::Infinite,
Some(n) => Repeat::Finite(n),
};
encoder
.set_repeat(repeat)
.map_err(|e| UnbundleError::GifEncodeError(format!("Failed to set GIF repeat: {e}")))?;
for image in frames {
let rgba = image.to_rgba8();
let mut pixels = rgba.into_raw();
let mut gif_frame = Frame::from_rgba_speed(width, height, &mut pixels, 10);
gif_frame.delay = config.frame_delay;
encoder.write_frame(&gif_frame).map_err(|e| {
UnbundleError::GifEncodeError(format!("Failed to write GIF frame: {e}"))
})?;
}
Ok(())
}
pub(crate) fn encode_gif_to_memory(
frames: &[DynamicImage],
config: &GifOptions,
) -> Result<Vec<u8>, UnbundleError> {
log::debug!(
"Encoding {} frames to GIF in memory (width={:?}, delay={})",
frames.len(),
config.width,
config.frame_delay,
);
if frames.is_empty() {
return Ok(Vec::new());
}
let first = &frames[0];
let width = first.width() as u16;
let height = first.height() as u16;
let mut buffer = Vec::new();
{
let mut encoder = Encoder::new(&mut buffer, width, height, &[]).map_err(|e| {
UnbundleError::GifEncodeError(format!("Failed to create GIF encoder: {e}"))
})?;
let repeat = match config.repeat {
None => Repeat::Infinite,
Some(n) => Repeat::Finite(n),
};
encoder
.set_repeat(repeat)
.map_err(|e| UnbundleError::GifEncodeError(format!("Failed to set GIF repeat: {e}")))?;
for image in frames {
let rgba = image.to_rgba8();
let mut pixels = rgba.into_raw();
let mut gif_frame = Frame::from_rgba_speed(width, height, &mut pixels, 10);
gif_frame.delay = config.frame_delay;
encoder.write_frame(&gif_frame).map_err(|e| {
UnbundleError::GifEncodeError(format!("Failed to write GIF frame: {e}"))
})?;
}
}
Ok(buffer)
}