#![doc(html_logo_url = "https://gif.ski/icon.png")]
use gif;
use imagequant;
use resize;
use lodepng;
use gif_dispose;
#[macro_use] extern crate error_chain;
use gif::*;
use rgb::*;
use imgref::*;
use imagequant::*;
mod error;
pub use crate::error::*;
mod ordqueue;
use crate::ordqueue::*;
pub mod progress;
use crate::progress::*;
pub mod c_api;
use std::path::PathBuf;
use std::io::prelude::*;
use std::sync::Arc;
use std::borrow::Cow;
use std::thread;
type DecodedImage = CatResult<(ImgVec<RGBA8>, u16)>;
#[derive(Copy, Clone)]
pub struct Settings {
pub width: Option<u32>,
pub height: Option<u32>,
pub quality: u8,
pub once: bool,
pub fast: bool,
}
pub struct Collector {
width: Option<u32>,
height: Option<u32>,
queue: OrdQueue<DecodedImage>,
}
pub struct Writer {
queue_iter: Option<OrdQueueIter<DecodedImage>>,
settings: Settings,
}
struct GIFFrame {
image: ImgVec<u8>,
pal: Vec<RGBA8>,
delay: u16,
dispose: gif::DisposalMethod,
}
enum WriteInitState<W: Write> {
Uninit(W),
Init(Encoder<W>),
}
pub fn new(settings: Settings) -> CatResult<(Collector, Writer)> {
let (queue, queue_iter) = ordqueue::new(4);
Ok((Collector {
queue,
width: settings.width,
height: settings.height,
}, Writer {
queue_iter: Some(queue_iter),
settings,
}))
}
impl Collector {
pub fn add_frame_rgba(&mut self, frame_index: usize, image: ImgVec<RGBA8>, delay: u16) -> CatResult<()> {
self.queue.push(frame_index, Ok((Self::resized_binary_alpha(image, self.width, self.height), delay)))
}
pub fn add_frame_png_file(&mut self, frame_index: usize, path: PathBuf, delay: u16) -> CatResult<()> {
let width = self.width;
let height = self.height;
let image = lodepng::decode32_file(&path)
.chain_err(|| format!("Can't load {}", path.display()))?;
self.queue.push(frame_index, Ok((Self::resized_binary_alpha(ImgVec::new(image.buffer, image.width, image.height), width, height), delay)))
}
fn resized_binary_alpha(mut image: ImgVec<RGBA8>, width: Option<u32>, height: Option<u32>) -> ImgVec<RGBA8> {
if let Some(width) = width {
if image.width() != image.stride() {
let mut contig = Vec::with_capacity(image.width()*image.height());
contig.extend(image.rows().flat_map(|r| r.iter().cloned()));
image = ImgVec::new(contig, image.width(), image.height());
}
let dst_width = (width as usize).min(image.width());
let dst_height = height.map(|h| (h as usize).min(image.height())).unwrap_or(image.height() * dst_width / image.width());
let mut r = resize::new(image.width(), image.height(), dst_width, dst_height, resize::Pixel::RGBA, resize::Type::Lanczos3);
let mut dst = vec![RGBA::new(0,0,0,0); dst_width * dst_height];
r.resize(image.buf.as_bytes(), dst.as_bytes_mut());
image = ImgVec::new(dst, dst_width, dst_height)
}
const DITHER: [u8; 64] = [
0*2+8,48*2+8,12*2+8,60*2+8, 3*2+8,51*2+8,15*2+8,63*2+8,
32*2+8,16*2+8,44*2+8,28*2+8,35*2+8,19*2+8,47*2+8,31*2+8,
8*2+8,56*2+8, 4*2+8,52*2+8,11*2+8,59*2+8, 7*2+8,55*2+8,
40*2+8,24*2+8,36*2+8,20*2+8,43*2+8,27*2+8,39*2+8,23*2+8,
2*2+8,50*2+8,14*2+8,62*2+8, 1*2+8,49*2+8,13*2+8,61*2+8,
34*2+8,18*2+8,46*2+8,30*2+8,33*2+8,17*2+8,45*2+8,29*2+8,
10*2+8,58*2+8, 6*2+8,54*2+8, 9*2+8,57*2+8, 5*2+8,53*2+8,
42*2+8,26*2+8,38*2+8,22*2+8,41*2+8,25*2+8,37*2+8,21*2+8];
for (y, row) in image.rows_mut().enumerate() {
for (x, px) in row.iter_mut().enumerate() {
if px.a < 255 {
px.a = if px.a < DITHER[(y&7) * 8 + (x&7)] {0} else {255};
}
}
}
image
}
}
impl Writer {
fn quantize(image: ImgRef<'_, RGBA8>, importance_map: &[u8], background: Option<ImgRef<'_, RGBA8>>, settings: &Settings) -> CatResult<(ImgVec<u8>, Vec<RGBA8>)> {
let mut liq = Attributes::new();
if settings.fast {
liq.set_speed(10);
}
let quality = if background.is_some() {
settings.quality.into()
} else {
100
};
liq.set_quality(0, quality);
let mut img = liq.new_image_stride(image.buf, image.width(), image.height(), image.stride(), 0.)?;
img.set_importance_map(importance_map)?;
if let Some(bg) = background {
img.set_background(liq.new_image(bg.buf, bg.width(), bg.height(), 0.)?)?;
}
img.add_fixed_color(RGBA8::new(0,0,0,0));
let mut res = liq.quantize(&img)?;
res.set_dithering_level(0.5);
let (pal, pal_img) = res.remapped(&mut img)?;
debug_assert_eq!(img.width() * img.height(), pal_img.len());
Ok((Img::new(pal_img, img.width(), img.height()), pal))
}
fn write_frames<W: Write + Send>(write_queue_iter: OrdQueueIter<Arc<GIFFrame>>, outfile: W, settings: &Settings, reporter: &mut dyn ProgressReporter) -> CatResult<()> {
let mut enc = WriteInitState::Uninit(outfile);
for f in write_queue_iter {
let GIFFrame {ref pal, ref image, delay, dispose} = *f;
if !reporter.increase() {
Err(ErrorKind::Aborted)?
}
let mut transparent_index = None;
let mut pal_rgb = Vec::with_capacity(3 * pal.len());
for (i, p) in pal.into_iter().enumerate() {
if p.a == 0 {
transparent_index = Some(i as u8);
}
pal_rgb.extend_from_slice([p.rgb()].as_bytes());
}
enc = match enc {
WriteInitState::Uninit(w) => {
let mut enc = Encoder::new(w, image.width() as u16, image.height() as u16, &[])?;
if !settings.once {
enc.write_extension(gif::ExtensionData::Repetitions(gif::Repeat::Infinite))?;
}
WriteInitState::Init(enc)
},
x => x,
};
let enc = match enc {
WriteInitState::Init(ref mut r) => r,
_ => unreachable!(),
};
enc.write_frame(&Frame {
delay,
dispose,
transparent: transparent_index,
needs_user_input: false,
top: 0,
left: 0,
width: image.width() as u16,
height: image.height() as u16,
interlaced: false,
palette: Some(pal_rgb),
buffer: Cow::Borrowed(&image.buf),
})?;
}
Ok(())
}
pub fn write<W: Write + Send>(mut self, outfile: W, reporter: &mut dyn ProgressReporter) -> CatResult<()> {
let (write_queue, write_queue_iter) = ordqueue::new(4);
let queue_iter = self.queue_iter.take().unwrap();
let settings = self.settings.clone();
let make_thread = thread::spawn(move || {
Self::make_frames(queue_iter, write_queue, &settings)
});
Self::write_frames(write_queue_iter, outfile, &self.settings, reporter)?;
make_thread.join().unwrap()?;
Ok(())
}
fn make_frames(queue_iter: OrdQueueIter<DecodedImage>, mut write_queue: OrdQueue<Arc<GIFFrame>>, settings: &Settings) -> CatResult<()> {
let mut decode_iter = queue_iter.enumerate().map(|(i,tmp)| tmp.map(|(image, delay)|(i,image,delay)));
let mut screen = None;
let mut curr_frame = if let Some(a) = decode_iter.next() {
Some(a?)
} else {
Err("Found no usable frames to encode")?
};
let mut importance_map = vec![255u8; curr_frame.as_ref().unwrap().1.buf.len()];
let mut next_frame = if let Some(a) = decode_iter.next() {
Some(a?)
} else {
None
};
let mut previous_frame_dispose = gif::DisposalMethod::Background;
while let Some((i, image, delay)) = curr_frame.take() {
curr_frame = next_frame.take();
next_frame = if let Some(a) = decode_iter.next() {
Some(a?)
} else {
None
};
let mut dispose = gif::DisposalMethod::Keep;
if let Some((_, ref next, _)) = next_frame {
if next.width() != image.width() || next.height() != image.height() {
Err(format!("Frame {} has wrong size ({}×{}, expected {}×{})", i+1,
next.width(), next.height(), image.width(), image.height()))?;
}
debug_assert_eq!(next.width(), image.width());
importance_map.clear();
importance_map.extend(next.rows().zip(image.rows()).flat_map(|(n,curr)| n.iter().cloned().zip(curr.iter().cloned())).map(|(n,curr)| {
if n.a < curr.a {
dispose = gif::DisposalMethod::Background;
}
255 - (colordiff(n,curr) / (255*255*6/170)) as u8
}));
};
if screen.is_none() {
screen = Some(gif_dispose::Screen::new(image.width(), image.height(), RGBA8::new(0,0,0,0), None));
}
let screen = screen.as_mut().unwrap();
let has_prev_frame = i > 0 && previous_frame_dispose == gif::DisposalMethod::Keep;
if has_prev_frame {
let q = 100 - settings.quality as u32;
let min_diff = 80 + q * q;
debug_assert_eq!(image.width(), screen.pixels.width());
importance_map.chunks_mut(image.width()).zip(screen.pixels.rows().zip(image.rows()))
.flat_map(|(px, (a,b))| {
px.iter_mut().zip(a.iter().cloned().zip(b.iter().cloned()))
})
.for_each(|(px, (a,b))| {
let diff = colordiff(a,b);
*px = if diff < min_diff {
0
} else {
let t = diff / 32;
((t * t).min(256) as u16 * u16::from(*px) / 256) as u8
}
});
}
let (image8, image8_pal) = {
let bg = if has_prev_frame {Some(screen.pixels.as_ref())} else {None};
Self::quantize(image.as_ref(), &importance_map, bg, settings)?
};
let transparent_index = image8_pal.iter().position(|p| p.a == 0).map(|i| i as u8);
let frame = Arc::new(GIFFrame {
image: image8,
pal: image8_pal,
dispose,
delay,
});
write_queue.push(i, frame.clone())?;
screen.blit(Some(&frame.pal), dispose, 0, 0, frame.image.as_ref(), transparent_index)?;
previous_frame_dispose = dispose;
}
Ok(())
}
}
#[inline]
fn colordiff(a: RGBA8, b: RGBA8) -> u32 {
if a.a == 0 || b.a == 0 {
return 255*255*6;
}
(i32::from(a.r as i16 - b.r as i16) * i32::from(a.r as i16 - b.r as i16)) as u32 * 2 +
(i32::from(a.g as i16 - b.g as i16) * i32::from(a.g as i16 - b.g as i16)) as u32 * 3 +
(i32::from(a.b as i16 - b.b as i16) * i32::from(a.b as i16 - b.b as i16)) as u32
}