use crate::block::*;
use crate::lzw::Compressor;
use crate::private::StepRaster;
use crate::{Error, Result, Step};
use pix::{Palette, Raster, gray::Gray8, rgb::Rgb};
use std::convert::TryInto;
use std::io::{self, Write};
pub struct BlockEnc<W: Write> {
writer: W,
}
impl<W: Write> BlockEnc<W> {
pub(crate) fn new(writer: W) -> Self {
BlockEnc { writer }
}
pub fn encode<B>(&mut self, block: B) -> Result<()>
where
B: Into<Block>,
{
use crate::block::Block::*;
let mut w = &mut self.writer;
match block.into() {
Header(b) => b.format(&mut w),
LogicalScreenDesc(b) => b.format(&mut w),
GlobalColorTable(b) => b.format(&mut w),
PlainText(b) => b.format(&mut w),
GraphicControl(b) => b.format(&mut w),
Comment(b) => b.format(&mut w),
Application(b) => b.format(&mut w),
Unknown(b) => b.format(&mut w),
ImageDesc(b) => b.format(&mut w),
LocalColorTable(b) => b.format(&mut w),
ImageData(b) => b.format(&mut w),
Trailer(b) => b.format(&mut w),
}?;
Ok(())
}
}
pub struct FrameEnc<W: Write> {
block_enc: BlockEnc<W>,
has_preamble: bool,
has_trailer: bool,
}
impl Header {
fn format<W: Write>(self, w: &mut W) -> io::Result<()> {
w.write_all(b"GIF")?;
w.write_all(&self.version())
}
}
impl LogicalScreenDesc {
fn format<W: Write>(self, w: &mut W) -> io::Result<()> {
let width = self.screen_width();
let height = self.screen_height();
w.write_all(&[
width as u8,
(width >> 8) as u8,
height as u8,
(height >> 8) as u8,
self.flags(),
self.background_color_idx(),
self.pixel_aspect_ratio(),
])
}
}
impl GlobalColorTable {
fn format<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write_all(self.colors())
}
}
impl PlainText {
fn format<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write_all(BlockCode::Extension_.signature())?;
w.write_all(&[ExtensionCode::PlainText_.into()])?;
for b in self.sub_blocks() {
debug_assert!(!b.is_empty() && b.len() < 256);
let len = b.len() as u8;
w.write_all(&[len])?; w.write_all(b)?;
}
w.write_all(&[0]) }
}
impl GraphicControl {
fn format<W: Write>(self, w: &mut W) -> io::Result<()> {
w.write_all(BlockCode::Extension_.signature())?;
let delay = self.delay_time_cs();
w.write_all(&[
ExtensionCode::GraphicControl_.into(),
4, self.flags(),
delay as u8,
(delay >> 8) as u8,
self.transparent_color_idx(),
0, ])
}
}
impl Comment {
fn format<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write_all(BlockCode::Extension_.signature())?;
w.write_all(&[ExtensionCode::Comment_.into()])?;
for c in self.comments() {
debug_assert!(!c.is_empty() && c.len() < 256);
let len = c.len() as u8;
w.write_all(&[len])?; w.write_all(c)?;
}
w.write_all(&[0]) }
}
impl Application {
fn format<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write_all(BlockCode::Extension_.signature())?;
w.write_all(&[ExtensionCode::Application_.into()])?;
for c in self.app_data() {
debug_assert!(!c.is_empty() && c.len() < 256);
let len = c.len() as u8;
w.write_all(&[len])?; w.write_all(c)?;
}
w.write_all(&[0]) }
}
impl Unknown {
fn format<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write_all(BlockCode::Extension_.signature())?;
w.write_all(self.ext_id())?;
for c in self.sub_blocks() {
debug_assert!(!c.is_empty() && c.len() < 256);
let len = c.len() as u8;
w.write_all(&[len])?; w.write_all(c)?;
}
w.write_all(&[0]) }
}
impl ImageDesc {
fn format<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write_all(BlockCode::ImageDesc_.signature())?;
let left = self.left();
let top = self.top();
let width = self.width();
let height = self.height();
w.write_all(&[
left as u8,
(left >> 8) as u8,
top as u8,
(top >> 8) as u8,
width as u8,
(width >> 8) as u8,
height as u8,
(height >> 8) as u8,
self.flags(),
])
}
}
impl LocalColorTable {
fn format<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write_all(self.colors())
}
}
impl ImageData {
fn format<W: Write>(&self, w: &mut W) -> io::Result<()> {
let min_code_bits =
next_high_bit(self.data().iter().copied().max().unwrap_or(0) + 1);
let min_code_bits = 2.max(min_code_bits).min(8);
w.write_all(&[min_code_bits])?;
let mut buffer = Vec::with_capacity(self.data().len());
let mut compressor = Compressor::new(min_code_bits);
compressor.compress(self.data(), &mut buffer);
for chunk in buffer.chunks(255) {
let len = chunk.len() as u8;
w.write_all(&[len])?; w.write_all(chunk)?;
}
w.write_all(&[0]) }
}
fn next_high_bit(value: u8) -> u8 {
u32::from(value).next_power_of_two().trailing_zeros() as u8
}
impl Trailer {
fn format<W: Write>(self, w: &mut W) -> io::Result<()> {
w.write_all(BlockCode::Trailer_.signature())
}
}
impl<W: Write> FrameEnc<W> {
pub(crate) fn new(block_enc: BlockEnc<W>) -> Self {
FrameEnc {
block_enc,
has_preamble: false,
has_trailer: false,
}
}
pub fn encode_preamble(&mut self, preamble: &Preamble) -> Result<()> {
if self.has_preamble {
return Err(Error::InvalidBlockSequence);
}
self.block_enc.encode(preamble.header)?;
self.block_enc.encode(preamble.logical_screen_desc)?;
if let Some(tbl) = &preamble.global_color_table {
self.block_enc.encode(tbl.clone())?;
}
if let Some(cnt) = &preamble.loop_count_ext {
self.block_enc.encode(cnt.clone())?;
}
for comment in &preamble.comments {
self.block_enc.encode(comment.clone())?;
}
self.has_preamble = true;
Ok(())
}
pub fn encode_frame(&mut self, frame: &Frame) -> Result<()> {
if self.has_trailer || !self.has_preamble {
return Err(Error::InvalidBlockSequence);
}
if let Some(ctrl) = &frame.graphic_control_ext {
self.block_enc.encode(*ctrl)?;
}
self.block_enc.encode(frame.image_desc)?;
if let Some(tbl) = &frame.local_color_table {
self.block_enc.encode(tbl.clone())?;
}
self.block_enc.encode(frame.image_data.clone())?;
Ok(())
}
pub fn encode_trailer(&mut self) -> Result<()> {
if self.has_trailer || !self.has_preamble {
return Err(Error::InvalidBlockSequence);
}
self.block_enc.encode(Trailer::default())?;
self.has_trailer = true;
Ok(())
}
}
pub struct StepEnc<W: Write> {
frame_enc: FrameEnc<W>,
global_color_table: (ColorTableConfig, Option<GlobalColorTable>),
loop_count: Option<Application>,
preamble: Option<Preamble>,
}
impl<W: Write> Drop for StepEnc<W> {
fn drop(&mut self) {
let _ = self.frame_enc.encode_trailer();
}
}
impl<W: Write> StepEnc<W> {
pub(crate) fn new(frame_enc: FrameEnc<W>) -> Self {
StepEnc {
frame_enc,
global_color_table: (ColorTableConfig::default(), None),
loop_count: None,
preamble: None,
}
}
pub fn with_loop_count(mut self, loop_count: u16) -> Self {
self.loop_count = Some(Application::with_loop_count(loop_count));
self
}
pub fn with_global_color_table(mut self, palette: &Palette) -> Self {
let (tbl_cfg, pal) = make_color_table(palette);
self.global_color_table =
(tbl_cfg, Some(GlobalColorTable::with_colors(&pal[..])));
self
}
fn encode_indexed_raster(
&mut self,
raster: &Raster<Gray8>,
palette: &Palette,
control: Option<GraphicControl>,
) -> Result<()> {
let image_desc = make_image_desc(raster)?;
let image_data = raster.into();
let (tbl_cfg, pal) = make_color_table(palette);
let logical_screen_desc = LogicalScreenDesc::default()
.with_screen_width(image_desc.width())
.with_screen_height(image_desc.height())
.with_color_table_config(tbl_cfg);
let global_color_table = Some(GlobalColorTable::with_colors(&pal[..]));
let loop_count_ext = self.loop_count.clone();
let preamble = Preamble {
logical_screen_desc,
global_color_table,
loop_count_ext,
..Preamble::default()
};
match &self.preamble {
Some(pre) => {
if !pre
.logical_screen_desc
.equal_size(preamble.logical_screen_desc)
{
return Err(Error::InvalidRasterDimensions);
}
if pre.global_color_table != preamble.global_color_table {
let frame = Frame::new(
control,
image_desc.with_color_table_config(tbl_cfg),
Some(LocalColorTable::with_colors(&pal[..])),
image_data,
);
return self.frame_enc.encode_frame(&frame);
}
}
None => {
self.frame_enc.encode_preamble(&preamble)?;
self.preamble = Some(preamble);
}
}
let frame = Frame::new(control, image_desc, None, image_data);
self.frame_enc.encode_frame(&frame)
}
pub fn encode_step(&mut self, step: &Step) -> Result<()> {
match &step.raster {
StepRaster::TrueColor(_) => {
todo!("convert raster to indexed raster");
}
StepRaster::Indexed(raster, palette) => {
self.encode_indexed_raster(
raster,
palette,
step.graphic_control_ext,
)?;
}
}
Ok(())
}
}
fn make_image_desc(raster: &Raster<Gray8>) -> Result<ImageDesc> {
let width = raster.width().try_into()?;
let height = raster.height().try_into()?;
Ok(ImageDesc::default().with_width(width).with_height(height))
}
fn make_color_table(palette: &Palette) -> (ColorTableConfig, Vec<u8>) {
let tbl_cfg = ColorTableConfig::new(
ColorTableExistence::Present,
ColorTableOrdering::NotSorted,
palette.len() as u16,
);
let mut pal = Vec::with_capacity(palette.len() * 3);
for clr in palette.colors() {
pal.push(u8::from(Rgb::red(*clr)));
pal.push(u8::from(Rgb::green(*clr)));
pal.push(u8::from(Rgb::blue(*clr)));
}
while pal.len() < tbl_cfg.size_bytes() {
pal.push(0);
}
(tbl_cfg, pal)
}
#[cfg(test)]
mod test {
use super::*;
use crate::Encoder;
use pix::{Palette, Raster, gray::Gray8, rgb::SRgb8};
#[test]
fn high_bits() {
assert_eq!(next_high_bit(0), 0);
assert_eq!(next_high_bit(2), 1);
assert_eq!(next_high_bit(3), 2);
assert_eq!(next_high_bit(4), 2);
assert_eq!(next_high_bit(5), 3);
assert_eq!(next_high_bit(7), 3);
assert_eq!(next_high_bit(8), 3);
assert_eq!(next_high_bit(9), 4);
assert_eq!(next_high_bit(16), 4);
}
fn check_encode(palette: Palette, raster: Raster<Gray8>, data: &[u8]) {
let mut bytes = vec![];
let mut enc = Encoder::new(&mut bytes).into_step_enc();
let step = Step::with_indexed(raster, palette);
enc.encode_step(&step).unwrap();
drop(enc);
assert_eq!(&bytes[..], data);
}
const GIF_2X2: &[u8] = &[
71, 73, 70, 56, 57, 97, 2, 0, 2, 0, 128, 0, 0, 0, 255, 0, 0, 255, 255,
44, 0, 0, 0, 0, 2, 0, 2, 0, 0, 2, 2, 12, 16, 0, 59,
];
#[test]
fn enc_2x2() {
let mut raster = Raster::with_clear(2, 2);
*raster.pixel_mut(0, 0) = Gray8::new(1);
*raster.pixel_mut(1, 1) = Gray8::new(1);
let mut palette = Palette::new(2);
palette.set_entry(SRgb8::new(0, 0xFF, 0));
palette.set_entry(SRgb8::new(0, 0xFF, 0xFF));
check_encode(palette, raster, GIF_2X2);
}
const GIF_3X3: &[u8] = &[
71, 73, 70, 56, 57, 97, 3, 0, 3, 0, 162, 0, 0, 255, 0, 0, 0, 255, 0, 0,
0, 255, 255, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0,
0, 0, 3, 0, 3, 0, 0, 3, 5, 24, 176, 2, 4, 35, 0, 59,
];
#[test]
fn enc_3x3() {
let mut raster = Raster::with_clear(3, 3);
*raster.pixel_mut(0, 0) = Gray8::new(1);
*raster.pixel_mut(1, 1) = Gray8::new(2);
*raster.pixel_mut(2, 2) = Gray8::new(3);
*raster.pixel_mut(0, 2) = Gray8::new(4);
let mut palette = Palette::new(5);
palette.set_entry(SRgb8::new(0xFF, 0, 0));
palette.set_entry(SRgb8::new(0, 0xFF, 0));
palette.set_entry(SRgb8::new(0, 0, 0xFF));
palette.set_entry(SRgb8::new(0xFF, 0xFF, 0));
palette.set_entry(SRgb8::new(0xFF, 0, 0xFF));
check_encode(palette, raster, GIF_3X3);
}
const GIF_4X4: &[u8] = &[
71, 73, 70, 56, 57, 97, 4, 0, 4, 0, 128, 0, 0, 255, 0, 0, 255, 255, 0,
44, 0, 0, 0, 0, 4, 0, 4, 0, 0, 2, 5, 12, 14, 134, 122, 81, 0, 59,
];
#[test]
fn enc_4x4() {
let mut raster = Raster::with_clear(4, 4);
*raster.pixel_mut(0, 0) = Gray8::new(1);
*raster.pixel_mut(1, 1) = Gray8::new(1);
*raster.pixel_mut(2, 2) = Gray8::new(1);
*raster.pixel_mut(3, 3) = Gray8::new(1);
let mut palette = Palette::new(2);
palette.set_entry(SRgb8::new(0xFF, 0, 0));
palette.set_entry(SRgb8::new(0xFF, 0xFF, 0));
check_encode(palette, raster, GIF_4X4);
}
}