use crate::SettingsExt;
use crate::error::CatResult;
use crate::GIFFrame;
use crate::Settings;
use rgb::ComponentBytes;
use std::io::Write;
pub(crate) struct RustEncoder<W: Write> {
writer: Option<W>,
gif_enc: Option<gif::Encoder<W>>,
}
impl<W: Write> RustEncoder<W> {
pub fn new(writer: W) -> Self {
Self {
writer: Some(writer),
gif_enc: None,
}
}
}
impl<W: Write> RustEncoder<W> {
pub fn compress_frame(f: GIFFrame, settings: &SettingsExt) -> CatResult<gif::Frame<'static>> {
let GIFFrame {left, top, pal, image, dispose, transparent_index} = f;
let (buffer, width, height) = image.into_contiguous_buf();
let mut pal_rgb = Vec::with_capacity(3 * pal.len());
for p in &pal {
pal_rgb.extend_from_slice([p.rgb()].as_bytes());
}
let mut frame = gif::Frame {
delay: 1, dispose,
transparent: transparent_index,
needs_user_input: false,
top,
left,
width: width as u16,
height: height as u16,
interlaced: false,
palette: Some(pal_rgb),
buffer: buffer.into(),
};
#[allow(unused)]
let loss = settings.gifsicle_loss();
#[cfg(feature = "gifsicle")]
if loss > 0 {
Self::compress_gifsicle(&mut frame, loss)?;
return Ok(frame);
}
frame.make_lzw_pre_encoded();
Ok(frame)
}
#[cfg(feature = "gifsicle")]
fn compress_gifsicle(frame: &mut gif::Frame<'static>, loss: u32) -> CatResult<()> {
use crate::Error;
use gifsicle::*;
use std::mem::MaybeUninit;
use std::ptr;
let pal = frame.palette.as_ref().ok_or(Error::Gifsicle)?;
let min_code_size = (pal.len() as u32 / 3).max(2).next_power_of_two().trailing_zeros();
unsafe {
let g = Gif_NewImage().as_mut().ok_or(crate::Error::Gifsicle)?;
let mut g = scopeguard::guard(g, |g| {
Gif_DeleteImage(g);
});
g.top = frame.top;
g.left = frame.left;
g.delay = frame.delay; g.width = frame.width;
g.height = frame.height;
g.disposal = match frame.dispose {
gif::DisposalMethod::Any => Disposal::None,
gif::DisposalMethod::Keep => Disposal::Asis,
gif::DisposalMethod::Background => Disposal::Background,
gif::DisposalMethod::Previous => Disposal::Previous,
} as _;
g.transparent = frame.transparent.map_or(-1, i16::from);
g.local = Gif_NewFullColormap(0, pal.len() as _); if g.local.is_null() {
return Err(Error::Gifsicle);
}
for c in pal.chunks_exact(3) {
Gif_AddColor(g.local, &mut Gif_Color {
gfc_red: c[0],
gfc_green: c[1],
gfc_blue: c[2],
haspixel: 0, pixel: 0,
}, -1);
}
let gci = Gif_CompressInfo {
flags: 0,
loss: loss as _,
padding: [ptr::null_mut(); 7],
};
let mut grr = MaybeUninit::zeroed();
if gifsicle::Gif_WriterInit(&mut grr, ptr::null_mut(), &gci) == 0 {
return Err(Error::Gifsicle);
}
let mut grr = scopeguard::guard(grr.assume_init_mut(), |grr| {
gifsicle::Gif_WriterCleanup(grr);
});
let mut res = Gif_SetUncompressedImage(&mut **g, frame.buffer.as_ptr() as *mut u8, None, 0);
if res != 0 {
res = gifsicle::Gif_WriteCompressedData(ptr::null_mut(), *g, min_code_size as _, &mut **grr);
}
drop(g);
if res == 0 {
return Err(Error::Gifsicle);
}
frame.buffer = extract_chunks(&grr).ok_or(Error::Gifsicle)?.into();
}
Ok(())
}
pub fn write_frame(&mut self, mut frame: gif::Frame<'static>, delay: u16, screen_width: u16, screen_height: u16, settings: &Settings) -> CatResult<()> {
frame.delay = delay;
let writer = &mut self.writer;
let enc = match self.gif_enc {
None => {
let w = writer.take().ok_or(crate::Error::ThreadSend)?;
let mut enc = gif::Encoder::new(w, screen_width, screen_height, &[])?;
enc.write_extension(gif::ExtensionData::Repetitions(settings.repeat))?;
self.gif_enc.get_or_insert(enc)
},
Some(ref mut enc) => enc,
};
enc.write_lzw_pre_encoded_frame(&frame)?;
Ok(())
}
}
#[cfg(feature = "gifsicle")]
unsafe fn extract_chunks(grr: &gifsicle::Gif_Writer) -> Option<Vec<u8>> {
let encoded = grr.v.as_ref()?;
let encoded = std::slice::from_raw_parts(encoded, grr.pos as usize);
let mut out = Vec::with_capacity(encoded.len());
let (&code_size, mut chunks) = encoded.split_first()?;
out.push(code_size);
while let Some((&next_len, rest)) = chunks.split_first() {
let next_len = next_len as usize;
if next_len == 0 || next_len > rest.len() {
break;
}
let (chunk, rest) = rest.split_at(next_len);
out.extend_from_slice(chunk);
chunks = rest;
}
Some(out)
}