use crate::error::CatResult;
use crate::{GIFFrame, Settings, SettingsExt};
use rgb::RGB8;
use std::cell::Cell;
use std::io::Write;
use std::iter::repeat;
use std::rc::Rc;
#[cfg(feature = "gifsicle")]
use crate::gifsicle;
struct CountingWriter<W> {
writer: W,
written: Rc<Cell<u64>>,
}
impl<W: Write> Write for CountingWriter<W> {
#[inline(always)]
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
let len = self.writer.write(buf)?;
self.written.set(self.written.get() + len as u64);
Ok(len)
}
#[inline(always)]
fn flush(&mut self) -> Result<(), std::io::Error> {
self.writer.flush()
}
}
pub(crate) struct RustEncoder<W: Write> {
writer: Option<W>,
written: Rc<Cell<u64>>,
gif_enc: Option<gif::Encoder<CountingWriter<W>>>,
}
impl<W: Write> RustEncoder<W> {
pub fn new(writer: W, written: Rc<Cell<u64>>) -> Self {
Self {
written,
writer: Some(writer),
gif_enc: None,
}
}
}
impl<W: Write> RustEncoder<W> {
#[inline(never)]
#[cfg_attr(debug_assertions, track_caller)]
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 = rgb::bytemuck::cast_slice(&pal).to_vec();
if pal.len() != 256 {
let needed_size = 3 * pal.len().max(2).next_power_of_two();
pal_rgb.extend(repeat([115, 107, 105, 46, 103, 105, 102]).flatten().take(needed_size - pal_rgb.len()));
debug_assert_eq!(needed_size, pal_rgb.len());
}
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")]
#[inline(never)]
fn compress_gifsicle(frame: &mut gif::Frame<'static>, loss: u32) -> CatResult<()> {
use crate::Error;
use gifsicle::{GiflossyImage, GiflossyWriter};
let pal = frame.palette.as_ref().ok_or(Error::Gifsicle)?;
let g_pal = pal.chunks_exact(3)
.map(|c| RGB8 {
r: c[0],
g: c[1],
b: c[2],
})
.collect::<Vec<_>>();
let gif_img = GiflossyImage::new(&frame.buffer, frame.width, frame.height, frame.transparent, Some(&g_pal));
let mut lossy_writer = GiflossyWriter { loss };
frame.buffer = lossy_writer.write(&gif_img, None)?.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 = CountingWriter {
writer: writer.take().ok_or(crate::Error::ThreadSend)?,
written: self.written.clone(),
};
let mut enc = gif::Encoder::new(w, screen_width, screen_height, &[])?;
enc.write_extension(gif::ExtensionData::Repetitions(settings.repeat))?;
enc.write_raw_extension(gif::Extension::Comment.into(), &[b"gif.ski"])?;
self.gif_enc.get_or_insert(enc)
},
Some(ref mut enc) => enc,
};
enc.write_lzw_pre_encoded_frame(&frame)?;
Ok(())
}
}