allow LZW to be done at Frame time rather than Encoder time

This commit is contained in:
lifning 2022-07-01 22:36:12 -07:00
parent c4f91b5286
commit 312227220e
2 changed files with 48 additions and 27 deletions

View File

@ -4,7 +4,9 @@ extern crate color_quant;
use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
use std::io;
use crate::encoder::lzw_encode;
/// Disposal method
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
@ -154,7 +156,9 @@ pub struct Frame<'a> {
pub palette: Option<Vec<u8>>,
/// Buffer containing the image data.
/// Only indices unless configured differently.
pub buffer: Cow<'a, [u8]>
pub buffer: Cow<'a, [u8]>,
/// Compressed at frame creation time such that at write-time we don't waste CPU
pub precompressed: Option<Vec<u8>>,
}
impl<'a> Default for Frame<'a> {
@ -170,7 +174,8 @@ impl<'a> Default for Frame<'a> {
height: 0,
interlaced: false,
palette: None,
buffer: Cow::Borrowed(&[])
buffer: Cow::Borrowed(&[]),
precompressed: None,
}
}
}
@ -331,6 +336,15 @@ impl Frame<'static> {
pub(crate) fn required_bytes(&self) -> usize {
usize::from(self.width) * usize::from(self.height)
}
/// For situations where we're writing the same Frame multiple times and don't want to burn CPU
pub fn precompress(&mut self, temp: &mut Vec<u8>) -> io::Result<usize> {
let mut compressed = Vec::new();
lzw_encode(&self.buffer, &mut compressed, temp)?;
let size = compressed.len();
self.precompressed = Some(compressed);
Ok(size)
}
}
#[test]

View File

@ -214,35 +214,16 @@ impl<W: Write> Encoder<W> {
writer.write_le(flags).map_err(Into::into)
}
}?;
self.write_image_block(&frame.buffer)
if let Some(compressed) = &frame.precompressed {
self.w.as_mut().unwrap().write_all(compressed).map_err(Into::into)
} else {
self.write_image_block(&frame.buffer)
}
}
fn write_image_block(&mut self, data: &[u8]) -> Result<(), EncodingError> {
let writer = self.w.as_mut().unwrap();
{
let min_code_size: u8 = match flag_size(*data.iter().max().unwrap_or(&0) as usize + 1) + 1 {
1 => 2, // As per gif spec: The minimal code size has to be >= 2
n => n
};
writer.write_le(min_code_size)?;
self.buffer.clear();
let mut enc = LzwEncoder::new(BitOrder::Lsb, min_code_size);
let len = enc.into_vec(&mut self.buffer).encode_all(data).consumed_out;
// Write blocks. `chunks_exact` seems to be slightly faster
// than `chunks` according to both Rust docs and benchmark results.
let mut iter = self.buffer[..len].chunks_exact(0xFF);
while let Some(full_block) = iter.next() {
writer.write_le(0xFFu8)?;
writer.write_all(full_block)?;
}
let last_block = iter.remainder();
if !last_block.is_empty() {
writer.write_le(last_block.len() as u8)?;
writer.write_all(last_block)?;
}
}
writer.write_le(0u8).map_err(Into::into)
lzw_encode(data, writer, &mut self.buffer).map_err(Into::into)
}
fn write_color_table(&mut self, table: &[u8]) -> Result<(), EncodingError> {
@ -348,6 +329,32 @@ impl<W: Write> Encoder<W> {
}
}
pub fn lzw_encode<W: Write>(data: &[u8], writer: &mut W, temp: &mut Vec<u8>) -> io::Result<()> {
temp.clear();
let min_code_size: u8 = match flag_size(*data.iter().max().unwrap_or(&0) as usize + 1) + 1 {
1 => 2, // As per gif spec: The minimal code size has to be >= 2
n => n
};
writer.write_le(min_code_size)?;
let mut enc = LzwEncoder::new(BitOrder::Lsb, min_code_size);
let len = enc.into_vec(temp).encode_all(data).consumed_out;
// Write blocks. `chunks_exact` seems to be slightly faster
// than `chunks` according to both Rust docs and benchmark results.
let mut iter = temp[..len].chunks_exact(0xFF);
while let Some(full_block) = iter.next() {
writer.write_le(0xFFu8)?;
writer.write_all(full_block)?;
}
let last_block = iter.remainder();
if !last_block.is_empty() {
writer.write_le(last_block.len() as u8)?;
writer.write_all(last_block)?;
}
writer.write_le(0u8)?;
Ok(())
}
/// GIF encoder.
pub struct Encoder<W: Write> {
w: Option<W>,