use crate::color::{OctreeOptions, octree_quant};
use crate::core::{ImageFormat, Pix, PixColormap, PixelDepth};
use crate::io::{IoError, IoResult, header::ImageHeader};
use gif::{ColorOutput, DecodeOptions, Encoder, Frame, Repeat};
use std::io::{Read, Write};
pub fn read_header_gif(data: &[u8]) -> IoResult<ImageHeader> {
let mut options = DecodeOptions::new();
options.set_color_output(ColorOutput::Indexed);
let decoder = options
.read_info(data)
.map_err(|e| IoError::DecodeError(format!("GIF decode error: {}", e)))?;
let width = decoder.width() as u32;
let height = decoder.height() as u32;
let num_colors = decoder.global_palette().map_or(0, |p| (p.len() / 3) as u32);
Ok(ImageHeader {
width,
height,
depth: 8,
bps: 8,
spp: 1,
has_colormap: true,
num_colors,
format: ImageFormat::Gif,
x_resolution: None,
y_resolution: None,
})
}
pub fn read_gif<R: Read>(reader: R) -> IoResult<Pix> {
let mut options = DecodeOptions::new();
options.set_color_output(ColorOutput::Indexed);
let mut decoder = options
.read_info(reader)
.map_err(|e| IoError::DecodeError(format!("GIF decode error: {}", e)))?;
let frame = decoder
.read_next_frame()
.map_err(|e| IoError::DecodeError(format!("GIF frame error: {}", e)))?
.ok_or_else(|| IoError::InvalidData("no frames in GIF".to_string()))?
.clone();
if decoder
.read_next_frame()
.map_err(|e| IoError::DecodeError(format!("GIF frame error: {}", e)))?
.is_some()
{
return Err(IoError::UnsupportedFormat(
"animated GIF not supported".to_string(),
));
}
let palette: &[u8] = if let Some(ref local_palette) = frame.palette {
local_palette
} else if let Some(global_palette) = decoder.global_palette() {
global_palette
} else {
return Err(IoError::InvalidData("GIF has no color map".to_string()));
};
let ncolors = palette.len() / 3;
if ncolors == 0 || ncolors > 256 {
return Err(IoError::InvalidData(format!(
"invalid palette size: {}",
ncolors
)));
}
let depth = if ncolors <= 2 {
PixelDepth::Bit1
} else if ncolors <= 4 {
PixelDepth::Bit2
} else if ncolors <= 16 {
PixelDepth::Bit4
} else {
PixelDepth::Bit8
};
let width = frame.width as u32;
let height = frame.height as u32;
let pix = Pix::new(width, height, depth)?;
let mut pix_mut = pix.try_into_mut().unwrap();
let mut cmap = PixColormap::new(depth.bits()).map_err(IoError::Core)?;
for chunk in palette.chunks(3) {
if chunk.len() == 3 {
cmap.add_rgb(chunk[0], chunk[1], chunk[2])
.map_err(IoError::Core)?;
}
}
pix_mut.set_colormap(Some(cmap)).map_err(IoError::Core)?;
let buffer = &frame.buffer;
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) as usize;
if idx < buffer.len() {
let val = buffer[idx] as u32;
pix_mut.set_pixel_unchecked(x, y, val);
}
}
}
Ok(pix_mut.into())
}
pub fn write_gif<W: Write>(pix: &Pix, mut writer: W) -> IoResult<()> {
let (write_pix, cmap) = prepare_pix_for_gif(pix)?;
let width = write_pix.width() as u16;
let height = write_pix.height() as u16;
let cmap_len = cmap.len();
let gif_palette_size = cmap_len.next_power_of_two().max(2);
let mut palette = Vec::with_capacity(gif_palette_size * 3);
for i in 0..gif_palette_size {
if i < cmap_len {
if let Some((r, g, b)) = cmap.get_rgb(i) {
palette.push(r);
palette.push(g);
palette.push(b);
} else {
palette.extend_from_slice(&[0, 0, 0]);
}
} else {
palette.extend_from_slice(&[0, 0, 0]);
}
}
let mut encoder = Encoder::new(&mut writer, width, height, &palette)
.map_err(|e| IoError::EncodeError(format!("GIF encoder error: {}", e)))?;
encoder
.set_repeat(Repeat::Finite(0))
.map_err(|e| IoError::EncodeError(format!("GIF repeat error: {}", e)))?;
let mut buffer = Vec::with_capacity((width as usize) * (height as usize));
for y in 0..(height as u32) {
for x in 0..(width as u32) {
let val = write_pix.get_pixel(x, y).unwrap_or(0);
buffer.push(val as u8);
}
}
let mut frame = Frame::from_indexed_pixels(width, height, buffer, None);
frame.palette = None;
encoder
.write_frame(&frame)
.map_err(|e| IoError::EncodeError(format!("GIF frame write error: {}", e)))?;
Ok(())
}
fn prepare_pix_for_gif(pix: &Pix) -> IoResult<(Pix, PixColormap)> {
match pix.depth() {
PixelDepth::Bit1 | PixelDepth::Bit2 | PixelDepth::Bit4 | PixelDepth::Bit8 => {
if let Some(cmap) = pix.colormap() {
let new_pix = clone_pix(pix)?;
Ok((new_pix, cmap.clone()))
} else {
let (new_pix, cmap) = create_grayscale_colormapped_pix(pix)?;
Ok((new_pix, cmap))
}
}
PixelDepth::Bit16 => {
let (new_pix, cmap) = convert_16bpp_to_8bpp_grayscale(pix)?;
Ok((new_pix, cmap))
}
PixelDepth::Bit32 => {
let quantized = octree_quant(pix, &OctreeOptions { max_colors: 256 })
.map_err(|e| IoError::EncodeError(format!("quantization error: {}", e)))?;
let cmap = quantized
.colormap()
.ok_or_else(|| IoError::EncodeError("quantized image has no colormap".to_string()))?
.clone();
Ok((quantized, cmap))
}
}
}
fn clone_pix(pix: &Pix) -> IoResult<Pix> {
let new_pix = Pix::new(pix.width(), pix.height(), pix.depth())?;
let mut new_mut = new_pix.try_into_mut().unwrap();
for y in 0..pix.height() {
for x in 0..pix.width() {
if let Some(val) = pix.get_pixel(x, y) {
new_mut.set_pixel_unchecked(x, y, val);
}
}
}
if let Some(cmap) = pix.colormap() {
new_mut
.set_colormap(Some(cmap.clone()))
.map_err(IoError::Core)?;
}
Ok(new_mut.into())
}
fn create_grayscale_colormapped_pix(pix: &Pix) -> IoResult<(Pix, PixColormap)> {
let depth = pix.depth();
let max_val = match depth {
PixelDepth::Bit1 => 1,
PixelDepth::Bit2 => 3,
PixelDepth::Bit4 => 15,
PixelDepth::Bit8 => 255,
_ => return Err(IoError::UnsupportedFormat("unsupported depth".to_string())),
};
let mut cmap = PixColormap::new(depth.bits()).map_err(IoError::Core)?;
let num_entries = max_val + 1;
for i in 0..num_entries {
let gray = ((i * 255) / max_val) as u8;
cmap.add_rgb(gray, gray, gray).map_err(IoError::Core)?;
}
let new_pix = Pix::new(pix.width(), pix.height(), depth)?;
let mut new_mut = new_pix.try_into_mut().unwrap();
for y in 0..pix.height() {
for x in 0..pix.width() {
if let Some(val) = pix.get_pixel(x, y) {
new_mut.set_pixel_unchecked(x, y, val);
}
}
}
new_mut
.set_colormap(Some(cmap.clone()))
.map_err(IoError::Core)?;
Ok((new_mut.into(), cmap))
}
fn convert_16bpp_to_8bpp_grayscale(pix: &Pix) -> IoResult<(Pix, PixColormap)> {
let mut cmap = PixColormap::new(8).map_err(IoError::Core)?;
for i in 0..=255u8 {
cmap.add_rgb(i, i, i).map_err(IoError::Core)?;
}
let new_pix = Pix::new(pix.width(), pix.height(), PixelDepth::Bit8)?;
let mut new_mut = new_pix.try_into_mut().unwrap();
for y in 0..pix.height() {
for x in 0..pix.width() {
if let Some(val16) = pix.get_pixel(x, y) {
let val8 = val16 >> 8;
new_mut.set_pixel_unchecked(x, y, val8);
}
}
}
new_mut
.set_colormap(Some(cmap.clone()))
.map_err(IoError::Core)?;
Ok((new_mut.into(), cmap))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::pixel;
use std::io::Cursor;
fn create_paletted_pix() -> Pix {
let pix = Pix::new(10, 10, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
let mut cmap = PixColormap::new(8).unwrap();
cmap.add_rgb(255, 0, 0).unwrap(); cmap.add_rgb(0, 255, 0).unwrap(); cmap.add_rgb(0, 0, 255).unwrap(); cmap.add_rgb(255, 255, 0).unwrap(); pix_mut.set_colormap(Some(cmap)).unwrap();
for y in 0..10 {
for x in 0..10 {
let val = (x + y) % 4;
pix_mut.set_pixel(x, y, val).unwrap();
}
}
pix_mut.into()
}
#[test]
fn test_gif_roundtrip_paletted() {
let pix = create_paletted_pix();
let mut buffer = Vec::new();
write_gif(&pix, &mut buffer).unwrap();
let cursor = Cursor::new(buffer);
let pix2 = read_gif(cursor).unwrap();
assert_eq!(pix2.width(), 10);
assert_eq!(pix2.height(), 10);
assert!(pix2.has_colormap());
for y in 0..10 {
for x in 0..10 {
assert_eq!(
pix2.get_pixel(x, y),
pix.get_pixel(x, y),
"mismatch at ({}, {})",
x,
y
);
}
}
}
#[test]
fn test_gif_roundtrip_1bpp() {
let pix = Pix::new(16, 16, PixelDepth::Bit1).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
let mut cmap = PixColormap::new(1).unwrap();
cmap.add_rgb(255, 255, 255).unwrap(); cmap.add_rgb(0, 0, 0).unwrap(); pix_mut.set_colormap(Some(cmap)).unwrap();
for y in 0..16 {
for x in 0..16 {
let val = (x + y) % 2;
pix_mut.set_pixel(x, y, val).unwrap();
}
}
let pix: Pix = pix_mut.into();
let mut buffer = Vec::new();
write_gif(&pix, &mut buffer).unwrap();
let cursor = Cursor::new(buffer);
let pix2 = read_gif(cursor).unwrap();
assert_eq!(pix2.depth(), PixelDepth::Bit1);
for y in 0..16 {
for x in 0..16 {
assert_eq!(pix2.get_pixel(x, y), pix.get_pixel(x, y));
}
}
}
#[test]
fn test_gif_roundtrip_2bpp() {
let pix = Pix::new(8, 8, PixelDepth::Bit2).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
let mut cmap = PixColormap::new(2).unwrap();
cmap.add_rgb(0, 0, 0).unwrap();
cmap.add_rgb(85, 85, 85).unwrap();
cmap.add_rgb(170, 170, 170).unwrap();
cmap.add_rgb(255, 255, 255).unwrap();
pix_mut.set_colormap(Some(cmap)).unwrap();
for y in 0..8 {
for x in 0..8 {
let val = (x + y) % 4;
pix_mut.set_pixel(x, y, val).unwrap();
}
}
let pix: Pix = pix_mut.into();
let mut buffer = Vec::new();
write_gif(&pix, &mut buffer).unwrap();
let cursor = Cursor::new(buffer);
let pix2 = read_gif(cursor).unwrap();
assert_eq!(pix2.depth(), PixelDepth::Bit2);
for y in 0..8 {
for x in 0..8 {
assert_eq!(pix2.get_pixel(x, y), pix.get_pixel(x, y));
}
}
}
#[test]
fn test_gif_roundtrip_4bpp() {
let pix = Pix::new(8, 8, PixelDepth::Bit4).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
let mut cmap = PixColormap::new(4).unwrap();
for i in 0..16 {
let gray = (i * 17) as u8;
cmap.add_rgb(gray, gray, gray).unwrap();
}
pix_mut.set_colormap(Some(cmap)).unwrap();
for y in 0..8 {
for x in 0..8 {
let val = (x + y) % 16;
pix_mut.set_pixel(x, y, val).unwrap();
}
}
let pix: Pix = pix_mut.into();
let mut buffer = Vec::new();
write_gif(&pix, &mut buffer).unwrap();
let cursor = Cursor::new(buffer);
let pix2 = read_gif(cursor).unwrap();
assert_eq!(pix2.depth(), PixelDepth::Bit4);
for y in 0..8 {
for x in 0..8 {
assert_eq!(pix2.get_pixel(x, y), pix.get_pixel(x, y));
}
}
}
#[test]
fn test_gif_grayscale_without_colormap() {
let pix = Pix::new(8, 8, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
for y in 0..8 {
for x in 0..8 {
let val = (x + y) * 16;
pix_mut.set_pixel(x, y, val).unwrap();
}
}
let pix: Pix = pix_mut.into();
let mut buffer = Vec::new();
write_gif(&pix, &mut buffer).unwrap();
let cursor = Cursor::new(buffer);
let pix2 = read_gif(cursor).unwrap();
assert!(pix2.has_colormap());
}
#[test]
fn test_gif_16bpp_conversion() {
let pix = Pix::new(4, 4, PixelDepth::Bit16).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
for y in 0..4 {
for x in 0..4 {
let val = (x + y) * 16384; pix_mut.set_pixel(x, y, val).unwrap();
}
}
let pix: Pix = pix_mut.into();
let mut buffer = Vec::new();
write_gif(&pix, &mut buffer).unwrap();
let cursor = Cursor::new(buffer);
let pix2 = read_gif(cursor).unwrap();
assert_eq!(pix2.depth(), PixelDepth::Bit8);
assert!(pix2.has_colormap());
}
#[test]
fn test_gif_32bpp_quantization() {
let pix = Pix::new(16, 16, PixelDepth::Bit32).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
for y in 0..16 {
for x in 0..16 {
let r = (x * 16) as u8;
let g = (y * 16) as u8;
let b = 128u8;
let pixel = pixel::compose_rgb(r, g, b);
pix_mut.set_pixel_unchecked(x, y, pixel);
}
}
let pix: Pix = pix_mut.into();
let mut buffer = Vec::new();
write_gif(&pix, &mut buffer).unwrap();
let cursor = Cursor::new(buffer);
let pix2 = read_gif(cursor).unwrap();
assert_eq!(pix2.depth(), PixelDepth::Bit8);
assert!(pix2.has_colormap());
}
#[test]
fn test_gif_colormap_preservation() {
let pix = create_paletted_pix();
let original_cmap = pix.colormap().unwrap();
let mut buffer = Vec::new();
write_gif(&pix, &mut buffer).unwrap();
let cursor = Cursor::new(buffer);
let pix2 = read_gif(cursor).unwrap();
let read_cmap = pix2.colormap().unwrap();
for i in 0..4 {
let orig = original_cmap.get_rgb(i);
let read = read_cmap.get_rgb(i);
assert_eq!(orig, read, "colormap mismatch at index {}", i);
}
}
}