initial stab at gif load/save bitmap support

the lzw encode / decode algorithm seems to work well enough, but could
be optimized and i'm worried there are still some lingering issues ...
This commit is contained in:
Gered 2022-12-29 22:19:35 -05:00
parent 8cb1d82b49
commit 0c1f50e63e
7 changed files with 1320 additions and 0 deletions

View file

@ -0,0 +1,608 @@
use std::fs::File;
use std::io::{BufReader, BufWriter, Read, Write};
use std::path::Path;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use thiserror::Error;
use crate::graphics::*;
use crate::utils::lzw::{lzw_decode, lzw_encode, LzwError};
const BITS_FOR_256_COLORS: u32 = 7; // formula is `2 ^ (bits + 1) = num_colors`
fn bits_to_num_colors(bits: u32) -> u32 {
1_u32.wrapping_shl(bits + 1)
}
fn read_raw_sub_block_data<T: Read>(reader: &mut T) -> Result<Box<[u8]>, GifError> {
let mut data = Vec::new();
let mut count = reader.read_u8()?;
while count > 0 {
let mut sub_block = vec![0u8; count as usize];
reader.read_exact(&mut sub_block)?;
data.append(&mut sub_block);
// read next sub block data size (or 0 if this is the end)
count = reader.read_u8()?;
}
Ok(data.into_boxed_slice())
}
fn write_raw_sub_block_data<T: Write>(data: &[u8], writer: &mut T) -> Result<(), GifError> {
let mut bytes_left = data.len();
let mut pos = 0;
while bytes_left > 0 {
let sub_block_length = if bytes_left >= 255 { 255 } else { bytes_left };
writer.write_u8(sub_block_length as u8)?;
let sub_block = &data[pos..sub_block_length];
writer.write_all(sub_block)?;
pos += sub_block_length;
bytes_left -= sub_block_length;
}
// terminator (sub block of zero length)
writer.write_u8(0)?;
Ok(())
}
#[derive(Error, Debug)]
pub enum GifError {
#[error("Bad or unsupported GIF file: {0}")]
BadFile(String),
#[error("GIF palette data error")]
BadPalette(#[from] PaletteError),
#[error("Unknown extension block: {0}")]
UnknownExtension(u8),
#[error("LZW encoding/decoding error")]
LzwError(#[from] LzwError),
#[error("")]
IOError(#[from] std::io::Error),
}
pub enum GifSettings {
Default,
TransparentColor(u8),
}
#[derive(Debug, Copy, Clone)]
struct GifHeader {
signature: [u8; 3],
version: [u8; 3],
screen_width: u16,
screen_height: u16,
flags: u8,
background_color: u8,
aspect_ratio: u8,
}
#[allow(dead_code)]
impl GifHeader {
pub fn has_global_color_table(&self) -> bool {
self.flags & 0b10000000 != 0
}
pub fn set_global_color_table(&mut self, value: bool) {
self.flags |= (value as u8).wrapping_shl(7);
}
pub fn color_resolution_bits(&self) -> u8 {
(self.flags & 0b01110000).wrapping_shr(4)
}
pub fn set_color_resolution_bits(&mut self, value: u8) {
self.flags |= (value & 0b111).wrapping_shl(4);
}
pub fn is_color_table_entries_sorted(&self) -> bool {
self.flags & 0b00001000 != 0
}
pub fn set_color_table_entries_sorted(&mut self, value: bool) {
self.flags |= (value as u8).wrapping_shl(3);
}
pub fn global_color_table_bits(&self) -> u8 {
self.flags & 0b00000111
}
pub fn set_global_color_table_bits(&mut self, value: u8) {
self.flags |= value & 0b111;
}
pub fn read<T: Read>(reader: &mut T) -> Result<Self, GifError> {
let mut signature = [0u8; 3];
reader.read_exact(&mut signature)?;
let mut version = [0u8; 3];
reader.read_exact(&mut version)?;
Ok(GifHeader {
signature,
version,
screen_width: reader.read_u16::<LittleEndian>()?,
screen_height: reader.read_u16::<LittleEndian>()?,
flags: reader.read_u8()?,
background_color: reader.read_u8()?,
aspect_ratio: reader.read_u8()?,
})
}
pub fn write<T: Write>(&self, writer: &mut T) -> Result<(), GifError> {
writer.write_all(&self.signature)?;
writer.write_all(&self.version)?;
writer.write_u16::<LittleEndian>(self.screen_width)?;
writer.write_u16::<LittleEndian>(self.screen_height)?;
writer.write_u8(self.flags)?;
writer.write_u8(self.background_color)?;
writer.write_u8(self.aspect_ratio)?;
Ok(())
}
}
const GIF_TRAILER: u8 = 0x3b;
const EXTENSION_INTRODUCER: u8 = 0x21;
const IMAGE_DESCRIPTOR_SEPARATOR: u8 = 0x2c;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum GifExtensionLabel {
GraphicControl = 0xf9,
PlainText = 0x01,
Application = 0xff,
Comment = 0xfe,
}
impl GifExtensionLabel {
pub fn from(value: u8) -> Result<Self, GifError> {
use GifExtensionLabel::*;
match value {
0xf9 => Ok(GraphicControl),
0x01 => Ok(PlainText),
0xff => Ok(Application),
0xfe => Ok(Comment),
_ => Err(GifError::UnknownExtension(value))
}
}
}
#[derive(Debug, Copy, Clone)]
struct GraphicControlExtension {
block_size: u8,
flags: u8,
delay: u16,
transparent_color: u8,
terminator: u8,
}
impl GraphicControlExtension {
pub fn read<T: Read>(reader: &mut T) -> Result<Self, GifError> {
Ok(GraphicControlExtension {
block_size: reader.read_u8()?,
flags: reader.read_u8()?,
delay: reader.read_u16::<LittleEndian>()?,
transparent_color: reader.read_u8()?,
terminator: reader.read_u8()?,
})
}
pub fn write<T: Write>(&self, writer: &mut T) -> Result<(), GifError> {
writer.write_u8(self.block_size)?;
writer.write_u8(self.flags)?;
writer.write_u16::<LittleEndian>(self.delay)?;
writer.write_u8(self.transparent_color)?;
writer.write_u8(self.terminator)?;
Ok(())
}
}
#[derive(Debug, Clone)]
struct PlainTextExtension {
block_size: u8,
text_x: u16,
text_y: u16,
text_width: u16,
text_height: u16,
cell_width: u8,
cell_height: u8,
foreground_color: u8,
background_color: u8,
data: Box<[u8]>,
}
#[allow(dead_code)]
impl PlainTextExtension {
pub fn read<T: Read>(reader: &mut T) -> Result<Self, GifError> {
Ok(PlainTextExtension {
block_size: reader.read_u8()?,
text_x: reader.read_u16::<LittleEndian>()?,
text_y: reader.read_u16::<LittleEndian>()?,
text_width: reader.read_u16::<LittleEndian>()?,
text_height: reader.read_u16::<LittleEndian>()?,
cell_width: reader.read_u8()?,
cell_height: reader.read_u8()?,
foreground_color: reader.read_u8()?,
background_color: reader.read_u8()?,
data: read_raw_sub_block_data(reader)?,
})
}
pub fn write<T: Write>(&self, writer: &mut T) -> Result<(), GifError> {
writer.write_u8(self.block_size)?;
writer.write_u16::<LittleEndian>(self.text_x)?;
writer.write_u16::<LittleEndian>(self.text_y)?;
writer.write_u16::<LittleEndian>(self.text_width)?;
writer.write_u16::<LittleEndian>(self.text_height)?;
writer.write_u8(self.cell_width)?;
writer.write_u8(self.cell_height)?;
writer.write_u8(self.foreground_color)?;
writer.write_u8(self.background_color)?;
write_raw_sub_block_data(&self.data, writer)?;
Ok(())
}
}
#[derive(Debug, Clone)]
struct ApplicationExtension {
block_size: u8,
identifier: [u8; 8],
authentication_code: [u8; 3],
data: Box<[u8]>,
}
#[allow(dead_code)]
impl ApplicationExtension {
pub fn read<T: Read>(reader: &mut T) -> Result<Self, GifError> {
let block_size = reader.read_u8()?;
let mut identifier = [0u8; 8];
reader.read_exact(&mut identifier)?;
let mut authentication_code = [0u8; 3];
reader.read_exact(&mut authentication_code)?;
Ok(ApplicationExtension {
block_size,
identifier,
authentication_code,
data: read_raw_sub_block_data(reader)?,
})
}
pub fn write<T: Write>(&self, writer: &mut T) -> Result<(), GifError> {
writer.write_u8(self.block_size)?;
writer.write_all(&self.identifier)?;
writer.write_all(&self.authentication_code)?;
write_raw_sub_block_data(&self.data, writer)?;
Ok(())
}
}
#[derive(Debug, Clone)]
struct CommentExtension {
data: Box<[u8]>,
}
#[allow(dead_code)]
impl CommentExtension {
pub fn read<T: Read>(reader: &mut T) -> Result<Self, GifError> {
Ok(CommentExtension {
data: read_raw_sub_block_data(reader)?,
})
}
pub fn write<T: Write>(&self, writer: &mut T) -> Result<(), GifError> {
write_raw_sub_block_data(&self.data, writer)?;
Ok(())
}
}
#[derive(Debug, Clone)]
struct LocalImageDescriptor {
x: u16,
y: u16,
width: u16,
height: u16,
flags: u8,
}
#[allow(dead_code)]
impl LocalImageDescriptor {
pub fn has_local_color_table(&self) -> bool {
self.flags & 0b10000000 != 0
}
pub fn set_local_color_table(&mut self, value: bool) {
self.flags |= (value as u8).wrapping_shl(7);
}
pub fn is_color_table_entries_sorted(&self) -> bool {
self.flags & 0b00100000 != 0
}
pub fn set_color_table_entries_sorted(&mut self, value: bool) {
self.flags |= (value as u8).wrapping_shl(5);
}
pub fn local_color_table_bits(&self) -> u8 {
self.flags & 0b00000111
}
pub fn set_local_color_table_bits(&mut self, value: u8) {
self.flags |= value & 0b111;
}
pub fn read<T: Read>(reader: &mut T) -> Result<Self, GifError> {
Ok(LocalImageDescriptor {
x: reader.read_u16::<LittleEndian>()?,
y: reader.read_u16::<LittleEndian>()?,
width: reader.read_u16::<LittleEndian>()?,
height: reader.read_u16::<LittleEndian>()?,
flags: reader.read_u8()?
})
}
pub fn write<T: Write>(&self, writer: &mut T) -> Result<(), GifError> {
writer.write_u16::<LittleEndian>(self.x)?;
writer.write_u16::<LittleEndian>(self.y)?;
writer.write_u16::<LittleEndian>(self.width)?;
writer.write_u16::<LittleEndian>(self.height)?;
writer.write_u8(self.flags)?;
Ok(())
}
}
fn load_image_section<T: ReadBytesExt>(
reader: &mut T,
gif_header: &GifHeader,
_graphic_control: &Option<GraphicControlExtension>,
) -> Result<(Bitmap, Option<Palette>), GifError> {
let descriptor = LocalImageDescriptor::read(reader)?;
let palette: Option<Palette>;
if descriptor.has_local_color_table() {
let num_colors = bits_to_num_colors(descriptor.local_color_table_bits() as u32) as usize;
palette = Some(Palette::load_num_colors_from_bytes(
reader,
PaletteFormat::Normal,
num_colors,
)?);
} else {
palette = None; // we expect to find a local color table later
}
let lzw_minimum_code_size = reader.read_u8()?;
let mut bitmap = Bitmap::new(gif_header.screen_width as u32, gif_header.screen_height as u32).unwrap();
let mut writer = bitmap.pixels_mut();
lzw_decode(reader, &mut writer, lzw_minimum_code_size as usize)?;
Ok((bitmap, palette))
}
fn save_image_section<T: WriteBytesExt>(
writer: &mut T,
bitmap: &Bitmap,
) -> Result<(), GifError> {
writer.write_u8(IMAGE_DESCRIPTOR_SEPARATOR)?;
let image_descriptor = LocalImageDescriptor {
x: 0,
y: 0,
width: bitmap.width as u16,
height: bitmap.height as u16,
flags: 0, // again, we're not using local color tables, so no flags to set here
};
image_descriptor.write(writer)?;
let lzw_minimum_code_size = 8;
writer.write_u8(lzw_minimum_code_size)?;
let mut reader = bitmap.pixels();
lzw_encode(&mut reader, writer, lzw_minimum_code_size as usize)?;
Ok(())
}
impl Bitmap {
pub fn load_gif_bytes<T: ReadBytesExt>(
reader: &mut T,
) -> Result<(Bitmap, Palette), GifError> {
let header = GifHeader::read(reader)?;
if header.signature != *b"GIF" || header.version != *b"89a" {
return Err(GifError::BadFile(String::from("Expected GIF89a header signature")));
}
// note that we might later overwrite this with a local color table (if this gif has one)
let mut palette: Option<Palette>;
if header.has_global_color_table() {
let num_colors = bits_to_num_colors(header.global_color_table_bits() as u32) as usize;
palette = Some(Palette::load_num_colors_from_bytes(
reader,
PaletteFormat::Normal,
num_colors,
)?);
} else {
palette = None; // we expect to find a local color table later
}
let mut bitmap: Option<Bitmap> = None;
let mut current_graphic_control: Option<GraphicControlExtension> = None;
loop {
let current_byte = reader.read_u8()?;
// check for eof via the gif's "trailer" block ...
if current_byte == 0x3b {
break;
}
// if we have already successfully read a bitmap and palette from this file, we can
// stop reading the rest. we only care about the first frame (if there are multiple)
// and palette we find
if bitmap.is_some() && palette.is_some() {
break;
}
match current_byte {
GIF_TRAILER => break,
IMAGE_DESCRIPTOR_SEPARATOR => {
let (frame_bitmap, frame_palette) = load_image_section(reader, &header, &current_graphic_control)?;
bitmap = Some(frame_bitmap);
if frame_palette.is_some() {
palette = frame_palette;
}
},
EXTENSION_INTRODUCER => {
let label = GifExtensionLabel::from(reader.read_u8()?)?;
match label {
GifExtensionLabel::GraphicControl => {
current_graphic_control = Some(GraphicControlExtension::read(reader)?);
},
GifExtensionLabel::PlainText => {
let _plain_text = PlainTextExtension::read(reader)?;
// todo: do something with this maybe
},
GifExtensionLabel::Application => {
let _application = ApplicationExtension::read(reader)?;
// todo: do something with this maybe
},
GifExtensionLabel::Comment => {
let _comment = CommentExtension::read(reader)?;
// todo: do something with this maybe
},
}
},
_ => {
return Err(GifError::BadFile(format!("Unexpected byte found {} not a file trailer, image separator or extension introducer", current_byte)))
}
}
}
if bitmap.is_none() {
return Err(GifError::BadFile(String::from("No image data was found")));
}
if palette.is_none() {
return Err(GifError::BadFile(String::from("No palette data was found")));
}
Ok((bitmap.unwrap(), palette.unwrap()))
}
pub fn load_gif_file(path: &Path) -> Result<(Bitmap, Palette), GifError> {
let f = File::open(path)?;
let mut reader = BufReader::new(f);
Self::load_gif_bytes(&mut reader)
}
pub fn to_gif_bytes<T: WriteBytesExt>(
&self,
writer: &mut T,
palette: &Palette,
settings: GifSettings,
) -> Result<(), GifError> {
let mut header = GifHeader {
signature: *b"GIF",
version: *b"89a",
screen_width: self.width as u16,
screen_height: self.height as u16,
flags: 0,
background_color: 0,
aspect_ratio: 0,
};
header.set_global_color_table(true);
header.set_global_color_table_bits(BITS_FOR_256_COLORS as u8);
header.set_color_resolution_bits(BITS_FOR_256_COLORS as u8);
header.write(writer)?;
// write the provided palette out as the global color table. we will not be providing any
// local color tables.
palette.to_bytes(writer, PaletteFormat::Normal)?;
let transparent_color: u8;
match settings {
GifSettings::Default => {
transparent_color = 0;
},
GifSettings::TransparentColor(color) => {
transparent_color = color;
}
}
writer.write_u8(EXTENSION_INTRODUCER)?;
writer.write_u8(GifExtensionLabel::GraphicControl as u8)?;
let graphic_control = GraphicControlExtension {
block_size: 4,
flags: 0,
delay: 0,
transparent_color,
terminator: 0,
};
graphic_control.write(writer)?;
save_image_section(writer, &self)?;
writer.write_u8(GIF_TRAILER)?;
Ok(())
}
pub fn to_gif_file(
&self,
path: &Path,
palette: &Palette,
settings: GifSettings
) -> Result<(), GifError> {
let f = File::create(path)?;
let mut writer = BufWriter::new(f);
self.to_gif_bytes(&mut writer, palette, settings)
}
}
#[cfg(test)]
pub mod tests {
use tempfile::TempDir;
use super::*;
pub static TEST_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../test-assets/test_bmp_pixels_raw.bin");
pub static TEST_LARGE_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw.bin");
#[test]
fn load_and_save() -> Result<(), GifError> {
let dp2_palette =
Palette::load_from_file(Path::new("./test-assets/dp2.pal"), PaletteFormat::Normal)
.unwrap();
let tmp_dir = TempDir::new()?;
let (bmp, palette) = Bitmap::load_gif_file(Path::new("./test-assets/test.gif"))?;
assert_eq!(16, bmp.width());
assert_eq!(16, bmp.height());
assert_eq!(bmp.pixels(), TEST_BMP_PIXELS_RAW);
assert_eq!(palette, dp2_palette);
let save_path = tmp_dir.path().join("test_save.gif");
bmp.to_gif_file(&save_path, &palette, GifSettings::Default)?;
let (reloaded_bmp, reloaded_palette) = Bitmap::load_gif_file(&save_path)?;
assert_eq!(16, reloaded_bmp.width());
assert_eq!(16, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW);
assert_eq!(reloaded_palette, dp2_palette);
Ok(())
}
#[test]
fn load_and_save_larger_image() -> Result<(), GifError> {
// this test is mostly useful to get a LZW decode and encode that includes at least one
// "clear code" and accompanying table reset
let tmp_dir = TempDir::new()?;
let (bmp, palette) = Bitmap::load_gif_file(Path::new("./test-assets/test_image.gif"))?;
assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
let save_path = tmp_dir.path().join("test_save.gif");
bmp.to_gif_file(&save_path, &palette, GifSettings::Default)?;
let (reloaded_bmp, _) = Bitmap::load_gif_file(&save_path)?;
assert_eq!(320, reloaded_bmp.width());
assert_eq!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
Ok(())
}
}

View file

@ -8,11 +8,13 @@ use crate::graphics::*;
use crate::math::*;
pub use self::blit::*;
pub use self::gif::*;
pub use self::iff::*;
pub use self::pcx::*;
pub use self::primitives::*;
pub mod blit;
pub mod gif;
pub mod iff;
pub mod pcx;
pub mod primitives;
@ -33,6 +35,9 @@ pub enum BitmapError {
#[error("Bitmap PCX file error")]
PcxError(#[from] pcx::PcxError),
#[error("Bitmap GIF file error")]
GifError(#[from] gif::GifError),
}
/// Container for 256 color 2D pixel/image data that can be rendered to the screen. Pixel data
@ -110,6 +115,7 @@ impl Bitmap {
let extension = extension.to_ascii_lowercase();
match extension.to_str() {
Some("pcx") => Ok(Self::load_pcx_file(path)?),
Some("gif") => Ok(Self::load_gif_file(path)?),
Some("iff") | Some("lbm") | Some("pbm") | Some("bbm") => {
Ok(Self::load_iff_file(path)?)
}

693
libretrogd/src/utils/lzw.rs Normal file
View file

@ -0,0 +1,693 @@
use std::collections::HashMap;
use byteorder::{ReadBytesExt, WriteBytesExt};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum LzwBytePackingError {
#[error("Code size bits {0} is unsupported")]
UnsupportedCodeSizeBits(usize),
#[error("Not enough bits available in the buffer to push new value in")]
NotEnoughBits,
}
#[derive(Error, Debug)]
pub enum LzwError {
#[error("Code size bits {0} is unsupported")]
UnsupportedCodeSizeBits(usize),
#[error("LZW byte packing/unpacking error")]
BytePackingError(#[from] LzwBytePackingError),
#[error("Encoding/decoding error: {0}")]
EncodingError(String),
#[error("LZW I/O error")]
IOError(#[from] std::io::Error),
}
type LzwCode = u16;
const GIF_MAX_SUB_CHUNK_SIZE: usize = 255;
const GIF_MAX_CODE_SIZE_BITS: usize = 8;
const MIN_BITS: usize = 2;
const MAX_BITS: usize = 12;
fn is_valid_min_code_size_bits(min_code_size_bits: usize) -> bool {
min_code_size_bits >= MIN_BITS && min_code_size_bits <= MAX_BITS
}
fn is_valid_gif_min_code_size_bits(min_code_size_bits: usize) -> bool {
min_code_size_bits >= MIN_BITS && min_code_size_bits <= GIF_MAX_CODE_SIZE_BITS
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
struct LzwDataBuffer {
prefix: Vec<u16>,
value: u16,
}
impl LzwDataBuffer {
pub fn new(value: u16) -> Self {
LzwDataBuffer {
prefix: Vec::new(),
value,
}
}
pub fn push_value(&mut self, value: u16) {
self.prefix.push(self.value);
self.value = value;
}
pub fn first(&self) -> u16 {
if let Some(value) = self.prefix.first() {
*value
} else {
self.value
}
}
pub fn write_all<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), LzwError> {
// todo: this feels icky to me?
for value in self.prefix.iter() {
writer.write_u8(*value as u8)?;
}
writer.write_u8(self.value as u8)?;
Ok(())
}
}
fn get_bitmask_for_bits(bits: usize) -> u32 {
let mut bitmask = 0;
for i in 0..bits {
bitmask |= 1u32.wrapping_shl(i as u32);
}
bitmask
}
fn get_table_size_for_bits(bits: usize) -> usize {
1usize.wrapping_shl(bits as u32)
}
fn get_max_code_value_for_bits(bits: usize) -> LzwCode {
(1 as LzwCode).wrapping_shl(bits as u32) - 1
}
#[derive(Debug)]
struct LzwBytePacker {
buffer: u32,
buffer_length: usize,
current_bit_size: usize,
bitmask: u32,
initial_bit_size: usize,
}
impl LzwBytePacker {
pub fn new(initial_bit_size: usize) -> Result<Self, LzwBytePackingError> {
if !is_valid_min_code_size_bits(initial_bit_size) {
return Err(LzwBytePackingError::UnsupportedCodeSizeBits(initial_bit_size));
}
Ok(LzwBytePacker {
buffer: 0,
buffer_length: 0,
current_bit_size: initial_bit_size,
bitmask: get_bitmask_for_bits(initial_bit_size),
initial_bit_size,
})
}
#[inline]
fn remaining_space(&self) -> usize {
32 - self.buffer_length
}
pub fn increase_bit_size(&mut self) -> Result<usize, LzwBytePackingError> {
if self.current_bit_size >= MAX_BITS {
return Err(LzwBytePackingError::UnsupportedCodeSizeBits(self.current_bit_size + 1));
} else {
self.current_bit_size += 1;
self.bitmask = get_bitmask_for_bits(self.current_bit_size);
Ok(self.current_bit_size)
}
}
pub fn reset_bit_size(&mut self) {
self.current_bit_size = self.initial_bit_size;
self.bitmask = get_bitmask_for_bits(self.current_bit_size);
}
pub fn push_code(&mut self, code: LzwCode) -> Result<(), LzwBytePackingError> {
if self.remaining_space() >= self.current_bit_size {
let value = (code as u32 & self.bitmask).wrapping_shl(self.buffer_length as u32);
self.buffer |= value;
self.buffer_length += self.current_bit_size;
Ok(())
} else {
Err(LzwBytePackingError::NotEnoughBits)
}
}
pub fn take_byte(&mut self) -> Option<u8> {
if self.buffer_length >= 8 {
let byte = (self.buffer & 0xff) as u8;
self.buffer = self.buffer.wrapping_shr(8);
self.buffer_length -= 8;
Some(byte)
} else {
None
}
}
pub fn flush_byte(&mut self) -> Option<u8> {
if self.buffer_length > 0 {
let byte = (self.buffer & 0xff) as u8;
self.buffer = self.buffer.wrapping_shr(8);
if self.buffer_length >= 8 {
self.buffer_length -= 8;
} else {
self.buffer_length = 0;
}
Some(byte)
} else {
None
}
}
}
#[derive(Debug)]
struct LzwBytesWriter {
packer: LzwBytePacker,
buffer: Vec<u8>,
}
impl LzwBytesWriter {
pub fn new(min_code_size_bits: usize) -> Result<Self, LzwError> {
if !is_valid_min_code_size_bits(min_code_size_bits) {
return Err(LzwError::UnsupportedCodeSizeBits(min_code_size_bits));
}
let packer = LzwBytePacker::new(min_code_size_bits)?;
Ok(LzwBytesWriter {
packer,
buffer: Vec::with_capacity(GIF_MAX_SUB_CHUNK_SIZE),
})
}
#[inline]
pub fn increase_bit_size(&mut self) -> Result<usize, LzwError> {
Ok(self.packer.increase_bit_size()?)
}
#[inline]
pub fn reset_bit_size(&mut self) {
self.packer.reset_bit_size()
}
fn write_buffer<T: WriteBytesExt>(&mut self, writer: &mut T) -> Result<(), LzwError> {
if !self.buffer.is_empty() {
writer.write_u8(self.buffer.len() as u8)?;
writer.write_all(&self.buffer)?;
self.buffer.clear();
}
Ok(())
}
pub fn write_code<T: WriteBytesExt>(
&mut self,
writer: &mut T,
code: LzwCode
) -> Result<(), LzwError> {
self.packer.push_code(code)?;
while let Some(byte) = self.packer.take_byte() {
self.buffer.push(byte);
if self.buffer.len() == GIF_MAX_SUB_CHUNK_SIZE {
self.write_buffer(writer)?;
}
}
Ok(())
}
pub fn flush<T: WriteBytesExt>(&mut self, writer: &mut T) -> Result<(), LzwError> {
while let Some(byte) = self.packer.flush_byte() {
self.buffer.push(byte);
if self.buffer.len() == GIF_MAX_SUB_CHUNK_SIZE {
self.write_buffer(writer)?;
}
}
self.write_buffer(writer)?;
// block terminator for data sub-block sequence
writer.write_u8(0)?;
Ok(())
}
}
#[derive(Debug)]
struct LzwByteUnpacker {
buffer: u32,
buffer_length: usize,
current_bit_size: usize,
bitmask: u32,
initial_bit_size: usize,
}
impl LzwByteUnpacker {
pub fn new(initial_bit_size: usize) -> Result<Self, LzwBytePackingError> {
if !is_valid_min_code_size_bits(initial_bit_size) {
return Err(LzwBytePackingError::UnsupportedCodeSizeBits(initial_bit_size));
}
Ok(LzwByteUnpacker {
buffer: 0,
buffer_length: 0,
current_bit_size: initial_bit_size,
bitmask: get_bitmask_for_bits(initial_bit_size),
initial_bit_size,
})
}
#[inline]
fn remaining_space(&self) -> usize {
32 - self.buffer_length
}
pub fn increase_bit_size(&mut self) -> Result<usize, LzwBytePackingError> {
if self.current_bit_size >= MAX_BITS {
return Err(LzwBytePackingError::UnsupportedCodeSizeBits(self.current_bit_size + 1));
} else {
self.current_bit_size += 1;
self.bitmask = get_bitmask_for_bits(self.current_bit_size);
Ok(self.current_bit_size)
}
}
pub fn reset_bit_size(&mut self) {
self.current_bit_size = self.initial_bit_size;
self.bitmask = get_bitmask_for_bits(self.current_bit_size);
}
pub fn push_byte(&mut self, byte: u8) -> Result<(), LzwBytePackingError> {
if self.remaining_space() >= 8 {
let value = (byte as u32).wrapping_shl(self.buffer_length as u32);
self.buffer |= value;
self.buffer_length += 8;
Ok(())
} else {
Err(LzwBytePackingError::NotEnoughBits)
}
}
pub fn take_code(&mut self) -> Option<LzwCode> {
if self.buffer_length >= self.current_bit_size {
let code = (self.buffer & self.bitmask) as LzwCode;
self.buffer = self.buffer.wrapping_shr(self.current_bit_size as u32);
self.buffer_length -= self.current_bit_size;
Some(code)
} else {
None
}
}
}
#[derive(Debug)]
struct LzwBytesReader {
unpacker: LzwByteUnpacker,
sub_chunk_remaining_bytes: u8,
reached_end: bool,
}
impl LzwBytesReader {
pub fn new(min_code_size_bits: usize) -> Result<Self, LzwError> {
if !is_valid_min_code_size_bits(min_code_size_bits) {
return Err(LzwError::UnsupportedCodeSizeBits(min_code_size_bits));
}
let unpacker = LzwByteUnpacker::new(min_code_size_bits)?;
Ok(LzwBytesReader {
unpacker,
sub_chunk_remaining_bytes: 0,
reached_end: false,
})
}
#[inline]
pub fn increase_bit_size(&mut self) -> Result<usize, LzwError> {
Ok(self.unpacker.increase_bit_size()?)
}
pub fn reset_bit_size(&mut self) {
self.unpacker.reset_bit_size()
}
fn read_byte<T: ReadBytesExt>(&mut self, reader: &mut T) -> Result<Option<u8>, LzwError> {
if self.reached_end {
return Ok(None);
}
if self.sub_chunk_remaining_bytes == 0 {
self.sub_chunk_remaining_bytes = reader.read_u8()?;
if self.sub_chunk_remaining_bytes == 0 {
self.reached_end = true;
return Ok(None);
}
}
self.sub_chunk_remaining_bytes -= 1;
Ok(Some(reader.read_u8()?))
}
pub fn read_code<T: ReadBytesExt> (
&mut self,
reader: &mut T,
) -> Result<Option<LzwCode>, LzwError> {
loop {
if let Some(code) = self.unpacker.take_code() {
return Ok(Some(code))
} else {
match self.read_byte(reader) {
Ok(Some(byte)) => self.unpacker.push_byte(byte)?,
Ok(None) => return Ok(None),
Err(LzwError::IOError(error)) if error.kind() == std::io::ErrorKind::UnexpectedEof => {
return Ok(None)
},
Err(error) => return Err(error),
};
}
}
}
}
pub fn lzw_encode<S, D>(
src: &mut S,
dest: &mut D,
min_code_size: usize
) -> Result<(), LzwError>
where
S: ReadBytesExt,
D: WriteBytesExt
{
if !is_valid_gif_min_code_size_bits(min_code_size) {
return Err(LzwError::UnsupportedCodeSizeBits(min_code_size));
}
// initialize the table, special codes, bit size info, etc
let initial_table_size = get_table_size_for_bits(min_code_size);
let clear_code = initial_table_size as LzwCode;
let end_of_info_code = initial_table_size as LzwCode + 1;
let mut table = HashMap::<LzwDataBuffer, LzwCode>::with_capacity(initial_table_size + 2);
for i in 0..initial_table_size {
table.insert(LzwDataBuffer::new(i as u16), i as LzwCode);
}
table.insert(LzwDataBuffer::new(clear_code), clear_code);
table.insert(LzwDataBuffer::new(end_of_info_code), end_of_info_code);
let mut current_bit_size = min_code_size + 1;
let mut max_code_value_for_bit_size = get_max_code_value_for_bits(current_bit_size);
let mut next_code = initial_table_size as LzwCode + 2;
// begin the output code stream
let mut writer = LzwBytesWriter::new(current_bit_size)?;
writer.write_code(dest, clear_code)?;
// read first byte to start things off before the main loop.
// if we eof here for some reason, just end the lzw stream like "normal" ... even though this
// isn't really a normal situation
let byte = match src.read_u8() {
Ok(byte) => byte,
Err(ref error) if error.kind() == std::io::ErrorKind::UnexpectedEof => {
writer.write_code(dest, end_of_info_code)?;
writer.flush(dest)?;
return Ok(());
},
Err(error) => return Err(LzwError::IOError(error))
};
let mut buffer = LzwDataBuffer::new(byte as u16);
loop {
// grab the next byte
let byte = match src.read_u8() {
Ok(byte) => byte,
Err(ref error) if error.kind() == std::io::ErrorKind::UnexpectedEof => break,
Err(error) => return Err(LzwError::IOError(error))
};
// check if the table currently contains a string composed of the current buffer plus
// the byte we just read (
let mut buffer_plus_byte = buffer.clone();
buffer_plus_byte.push_value(byte as u16);
if table.contains_key(&buffer_plus_byte) {
// we have a match, so lets just keep collecting bytes in our buffer ...
buffer.push_value(byte as u16);
} else {
let mut was_bit_size_increased = false;
let mut is_reset_needed = false;
// no match in the table, so we need to create a new code in the table for this
// string of bytes (buffer + byte) and also emit the code for _just_ the buffer string
let new_code = next_code;
next_code += 1;
if new_code > max_code_value_for_bit_size {
current_bit_size += 1;
if current_bit_size >= MAX_BITS {
is_reset_needed = true;
}
max_code_value_for_bit_size = get_max_code_value_for_bits(current_bit_size);
was_bit_size_increased = true;
}
table.insert(buffer_plus_byte, new_code);
if let Some(code) = table.get(&buffer) {
writer.write_code(dest, *code)?;
} else {
return Err(LzwError::EncodingError(format!("Expected to find code in table for buffer {:?} but none was found", buffer)));
}
if was_bit_size_increased {
writer.increase_bit_size()?;
}
if is_reset_needed {
// we reached the maximum code bit size, time to re-initialize the code table
table = HashMap::with_capacity(initial_table_size + 2);
for i in 0..initial_table_size {
table.insert(LzwDataBuffer::new(i as u16), i as LzwCode);
}
table.insert(LzwDataBuffer::new(clear_code), clear_code);
table.insert(LzwDataBuffer::new(end_of_info_code), end_of_info_code);
current_bit_size = min_code_size + 1;
max_code_value_for_bit_size = get_max_code_value_for_bits(current_bit_size);
next_code = initial_table_size as LzwCode + 2;
// reset the output code stream
writer.write_code(dest, clear_code)?;
writer.reset_bit_size();
}
buffer = LzwDataBuffer::new(byte as u16);
}
}
// flush the remaining buffer and finish up the output code stream
if let Some(code) = table.get(&buffer) {
writer.write_code(dest, *code)?;
} else {
return Err(LzwError::EncodingError(format!("No matching code for buffer {:?} at end of input stream", buffer)));
}
writer.write_code(dest, end_of_info_code)?;
writer.flush(dest)?;
Ok(())
}
pub fn lzw_decode<S, D>(
src: &mut S,
dest: &mut D,
min_code_size: usize
) -> Result<(), LzwError>
where
S: ReadBytesExt,
D: WriteBytesExt
{
if !is_valid_gif_min_code_size_bits(min_code_size) {
return Err(LzwError::UnsupportedCodeSizeBits(min_code_size));
}
// initialize some basic properties for decoding and the table here ... we initialize the
// actual table and the rest of the decoding properties we need a bit further on below
let mut current_bit_size = min_code_size + 1;
let initial_table_size = get_table_size_for_bits(min_code_size);
let clear_code = initial_table_size as LzwCode;
let end_of_info_code = initial_table_size as LzwCode + 1;
let mut reader = LzwBytesReader::new(current_bit_size)?;
// read the first code from the input code stream.
// we also return immediately without writing anything to the destination byte stream if there
// are no codes to read (kind of a weird situation, but no real reason to error ...?)
let mut code = match reader.read_code(src)? {
Some(code) => code,
None => return Ok(())
};
// the first code in the stream SHOULD be a clear code ... which we can just ignore because
// our table is freshly reset right now anyway. but we should flag this as an error if for some
// reason we didn't just read a clear code!
if code != clear_code {
return Err(LzwError::EncodingError(String::from("Unexpected first code value (not a clear code)")));
}
'outer: loop {
// initialize the table and some extra bits of info here so that whenever we read in a
// clear code from the input stream, we can just loop back here to handle it
let mut table = HashMap::<LzwCode, LzwDataBuffer>::with_capacity(initial_table_size + 2);
for i in 0..initial_table_size {
table.insert(i as LzwCode, LzwDataBuffer::new(i as u16));
}
table.insert(clear_code, LzwDataBuffer::new(clear_code));
table.insert(end_of_info_code, LzwDataBuffer::new(end_of_info_code));
let mut max_code_value_for_bit_size = get_max_code_value_for_bits(current_bit_size);
let mut next_code = initial_table_size as LzwCode + 2;
// read the next code which should actually be the first "interesting" value of the code stream
code = match reader.read_code(src)? {
Some(code) if code == end_of_info_code => return Ok(()),
Some(code) => code,
None => return Err(LzwError::EncodingError(String::from("Unexpected end of code stream"))),
};
// ok, now we're able to get started!
// simply write out the table string associated with the first code
if let Some(string) = table.get(&code) {
string.write_all(dest)?;
} else {
return Err(LzwError::EncodingError(format!("No table entry for code {}", code)));
}
let mut prev_code = code;
'inner: loop {
// grab the next code
code = match reader.read_code(src)? {
Some(code) if code == end_of_info_code => break 'outer,
Some(code) if code == clear_code => {
// reset the bit size and reader and then loop back to the outer loop which
// will handle actually resetting the code table
current_bit_size = min_code_size + 1;
reader.reset_bit_size();
break 'inner;
},
Some(code) => code,
None => return Err(LzwError::EncodingError(String::from("Unexpected end of code stream"))),
};
// note: prev_code should always be present since we looked it up in the table during a
// previous loop iteration ...
let prev_code_string = match table.get(&prev_code) {
Some(prev_code_string) => prev_code_string,
None => {
return Err(LzwError::EncodingError(format!("Previous code {} not found in table", prev_code)));
}
};
let new_code = next_code;
next_code += 1;
if let Some(string) = table.get(&code) {
// write out the matching table string for the code just read
string.write_all(dest)?;
// update the table accordingly
let k = string.first();
let mut new_string = prev_code_string.clone();
new_string.push_value(k);
table.insert(new_code, new_string);
} else {
// code is not yet present in the table.
// add prev_code string + the code we just read to the table and also write it out
let k = prev_code_string.first();
let mut new_string = prev_code_string.clone();
new_string.push_value(k);
new_string.write_all(dest)?;
table.insert(new_code, new_string);
}
if new_code == max_code_value_for_bit_size {
current_bit_size += 1;
max_code_value_for_bit_size = get_max_code_value_for_bits(current_bit_size);
reader.increase_bit_size()?;
}
prev_code = code;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
struct LzwTestData<'a> {
min_code_size: usize,
packed: &'a [u8],
unpacked: &'a [u8],
}
static LZW_TEST_DATA: &[LzwTestData] = &[
LzwTestData {
min_code_size: 2,
packed: &[0x16, 0x8c, 0x2d, 0x99, 0x87, 0x2a, 0x1c, 0xdc, 0x33, 0xa0, 0x02, 0x75, 0xec, 0x95, 0xfa, 0xa8, 0xde, 0x60, 0x8c, 0x04, 0x91, 0x4c, 0x01, 0x00],
unpacked: &[1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1],
},
LzwTestData {
min_code_size: 4,
packed: &[0x21, 0x70, 0x49, 0x79, 0x6a, 0x9d, 0xcb, 0x39, 0x7b, 0xa6, 0xd6, 0x96, 0xa4, 0x3d, 0x0f, 0xd8, 0x8d, 0x64, 0xb9, 0x1d, 0x28, 0xa9, 0x2d, 0x15, 0xfa, 0xc2, 0xf1, 0x37, 0x71, 0x33, 0xc5, 0x61, 0x4b, 0x04, 0x00],
unpacked: &[11, 11, 11, 11, 11, 7, 7, 7, 7, 7, 11, 11, 11, 11, 14, 14, 7, 7, 7, 7, 11, 11, 11, 14, 14, 14, 14, 7, 7, 7, 11, 11, 14, 14, 15, 15, 14, 14, 7, 7, 11, 14, 14, 15, 15, 15, 15, 14, 14, 7, 7, 14, 14, 15, 15, 15, 15, 14, 14, 11, 7, 7, 14, 14, 15, 15, 14, 14, 11, 11, 7, 7, 7, 14, 14, 14, 14, 11, 11, 11, 7, 7, 7, 7, 14, 14, 11, 11, 11, 11, 7, 7, 7, 7, 7, 11, 11, 11, 11, 11],
},
LzwTestData {
min_code_size: 8,
packed: &[0x0b, 0x00, 0x51, 0xfc, 0x1b, 0x28, 0x70, 0xa0, 0xc1, 0x83, 0x01, 0x01, 0x00],
unpacked: &[0x28, 0xff, 0xff, 0xff, 0x28, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
}
];
#[test]
fn lzw_compresses() -> Result<(), LzwError> {
for LzwTestData { packed, unpacked, min_code_size } in LZW_TEST_DATA {
let mut src = Cursor::new(*unpacked);
let mut dest = vec![0u8; 0];
lzw_encode(&mut src, &mut dest, *min_code_size)?;
assert_eq!(dest, *packed);
}
Ok(())
}
#[test]
fn lzw_decompresses() -> Result<(), LzwError> {
for LzwTestData { packed, unpacked, min_code_size } in LZW_TEST_DATA {
let mut src = Cursor::new(*packed);
let mut dest = vec![0u8; 0];
lzw_decode(&mut src, &mut dest, *min_code_size)?;
assert_eq!(dest, *unpacked);
}
Ok(())
}
}

View file

@ -6,6 +6,7 @@ use rand::Rng;
pub mod bytes;
pub mod io;
pub mod lzw;
pub mod packbits;
pub fn rnd_value<N: SampleUniform + PartialOrd>(low: N, high: N) -> N {

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long