use otf_pixels_compress::LzwDecoder;
use otf_pixels_core::{
Animation, Codec, DecodeCapability, Decoder, Format, ImageDescriptor as CoreDescriptor, Limits,
PixelFormat, PixelsError, Result, Source,
};
use crate::format::{
Disposal, GraphicControl, ImageDescriptor, SIGNATURE_87A, SIGNATURE_89A, Screen,
interlaced_pass_rows, interlaced_row, label, read_sub_blocks, skip_sub_blocks,
};
const MAX_COMPRESSED: usize = 64 * 1024 * 1024;
const MAX_STREAM: usize = 512 * 1024 * 1024;
const MAX_EXTENSION: usize = 4096;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Frame {
pub pixels: Vec<u8>,
pub width: u32,
pub height: u32,
pub delay_centiseconds: u16,
pub disposal: Disposal,
}
#[derive(Debug)]
pub struct GifDecoder<S: Source> {
descriptor: CoreDescriptor,
screen: Screen,
source: Option<Bytes>,
animation: Option<Animation>,
_source: std::marker::PhantomData<fn() -> S>,
global: Vec<[u8; 3]>,
canvas: Vec<u8>,
started: bool,
row: u32,
finished: bool,
pending: Option<Pending>,
background: [u8; 4],
}
#[derive(Debug)]
struct Pending {
disposal: Disposal,
area: ImageDescriptor,
saved: Vec<u8>,
had_transparency: bool,
}
impl<S: Source> GifDecoder<S> {
pub fn new(mut source: S, limits: Limits) -> Result<Self> {
let mut header = [0_u8; 13];
source.read_exact(&mut header)?;
let signature = header.get(..6).unwrap_or(&[]);
if signature != SIGNATURE_87A && signature != SIGNATURE_89A {
return Err(PixelsError::malformed(
"gif",
"signature is neither GIF87a nor GIF89a",
));
}
let mut screen_bytes = [0_u8; 7];
screen_bytes.copy_from_slice(header.get(6..13).unwrap_or(&[0; 7]));
let screen = Screen::parse(&screen_bytes)?;
let width = u32::from(screen.width);
let height = u32::from(screen.height);
let descriptor = CoreDescriptor::with_limits(width, height, PixelFormat::Rgba8, &limits)?;
let mut global = Vec::new();
if screen.global_table_size > 0 {
global = read_table(&mut source, screen.global_table_size)?;
}
let mut rest = Vec::new();
let mut chunk = [0_u8; 64 * 1024];
loop {
match source.read(&mut chunk)? {
0 => break,
n => rest.extend_from_slice(chunk.get(..n).unwrap_or_default()),
}
if rest.len() > MAX_STREAM {
return Err(PixelsError::unsupported(format!(
"gif: streams over {} MiB are not read",
MAX_STREAM >> 20
)));
}
}
let animation = scan_animation(&rest);
let canvas_len = descriptor
.byte_len()
.ok_or_else(|| PixelsError::malformed("gif", "canvas size overflows"))?;
let background = global
.get(screen.background as usize)
.map_or([0, 0, 0, 0], |c| [c[0], c[1], c[2], 255]);
Ok(Self {
descriptor,
screen,
source: Some(std::io::Cursor::new(rest)),
animation,
_source: std::marker::PhantomData,
global,
canvas: vec![0_u8; canvas_len],
started: false,
row: 0,
finished: false,
pending: None,
background,
})
}
#[must_use]
pub const fn screen(&self) -> Screen {
self.screen
}
pub fn next_frame(&mut self) -> Result<Option<Frame>> {
if self.finished {
return Ok(None);
}
let Some(mut source) = self.source.take() else {
return Ok(None);
};
let result = self.decode_next(&mut source);
self.source = Some(source);
result
}
fn decode_next(&mut self, source: &mut Bytes) -> Result<Option<Frame>> {
self.apply_disposal();
let mut control = GraphicControl::default();
loop {
let mut marker = [0_u8; 1];
source.read_exact(&mut marker)?;
match marker[0] {
label::TRAILER => {
self.finished = true;
return Ok(None);
}
label::EXTENSION => {
let mut kind = [0_u8; 1];
source.read_exact(&mut kind)?;
match kind[0] {
label::GRAPHIC_CONTROL => {
let payload = read_sub_blocks(source, MAX_EXTENSION)?;
control = GraphicControl::parse(&payload);
}
_ => skip_sub_blocks(source)?,
}
}
label::IMAGE => {
let frame = self.decode_image(source, control)?;
return Ok(Some(frame));
}
other => {
return Err(PixelsError::malformed(
"gif",
format!("unknown block label {other:#04x}"),
));
}
}
}
}
fn apply_disposal(&mut self) {
let Some(pending) = self.pending.take() else {
return;
};
match pending.disposal {
Disposal::None | Disposal::Keep => {}
Disposal::Background => {
let fill = if pending.had_transparency {
[0, 0, 0, 0]
} else {
self.background
};
self.fill_area(pending.area, &fill);
}
Disposal::Previous => {
self.restore_area(pending.area, &pending.saved);
}
}
}
fn fill_area(&mut self, area: ImageDescriptor, value: &[u8; 4]) {
let width = self.descriptor.width;
let height = self.descriptor.height;
for y in u32::from(area.top)..u32::from(area.top) + u32::from(area.height) {
if y >= height {
break;
}
for x in u32::from(area.left)..u32::from(area.left) + u32::from(area.width) {
if x >= width {
break;
}
let at = ((y * width + x) * 4) as usize;
if let Some(slot) = self.canvas.get_mut(at..at + 4) {
slot.copy_from_slice(value);
}
}
}
}
fn restore_area(&mut self, area: ImageDescriptor, saved: &[u8]) {
let width = self.descriptor.width;
let height = self.descriptor.height;
let area_width = u32::from(area.width) as usize;
for row in 0..u32::from(area.height) {
let y = u32::from(area.top) + row;
if y >= height {
break;
}
for column in 0..u32::from(area.width) {
let x = u32::from(area.left) + column;
if x >= width {
break;
}
let from = ((row as usize * area_width) + column as usize) * 4;
let to = ((y * width + x) * 4) as usize;
let (Some(source), Some(target)) =
(saved.get(from..from + 4), self.canvas.get_mut(to..to + 4))
else {
continue;
};
target.copy_from_slice(source);
}
}
}
fn decode_image(&mut self, source: &mut Bytes, control: GraphicControl) -> Result<Frame> {
let mut bytes = [0_u8; 9];
source.read_exact(&mut bytes)?;
let image = ImageDescriptor::parse(&bytes)?;
let local = if image.local_table_size > 0 {
read_table(source, image.local_table_size)?
} else {
Vec::new()
};
let palette: Vec<[u8; 3]> = if local.is_empty() {
self.global.clone()
} else {
local
};
if palette.is_empty() {
return Err(PixelsError::malformed(
"gif",
"frame has neither a local nor a global colour table",
));
}
let mut minimum_width = [0_u8; 1];
source.read_exact(&mut minimum_width)?;
let compressed = read_sub_blocks(source, MAX_COMPRESSED)?;
let pixels = u32::from(image.width) as usize * u32::from(image.height) as usize;
let decoder =
LzwDecoder::gif(u32::from(minimum_width[0])).map_err(crate::compress_error)?;
let indices = decoder
.decode(&compressed, pixels)
.map_err(crate::compress_error)?;
let saved = if control.disposal == Disposal::Previous {
self.save_area(image)
} else {
Vec::new()
};
self.composite(image, &indices, &palette, control.transparent);
self.pending = Some(Pending {
disposal: control.disposal,
area: image,
saved,
had_transparency: control.transparent.is_some(),
});
self.started = true;
Ok(Frame {
pixels: self.canvas.clone(),
width: self.descriptor.width,
height: self.descriptor.height,
delay_centiseconds: control.delay_centiseconds,
disposal: control.disposal,
})
}
fn save_area(&self, area: ImageDescriptor) -> Vec<u8> {
let width = self.descriptor.width;
let height = self.descriptor.height;
let mut out =
vec![0_u8; u32::from(area.width) as usize * u32::from(area.height) as usize * 4];
let area_width = u32::from(area.width) as usize;
for row in 0..u32::from(area.height) {
let y = u32::from(area.top) + row;
if y >= height {
break;
}
for column in 0..u32::from(area.width) {
let x = u32::from(area.left) + column;
if x >= width {
break;
}
let from = ((y * width + x) * 4) as usize;
let to = ((row as usize * area_width) + column as usize) * 4;
let (Some(source), Some(target)) =
(self.canvas.get(from..from + 4), out.get_mut(to..to + 4))
else {
continue;
};
target.copy_from_slice(source);
}
}
out
}
fn composite(
&mut self,
image: ImageDescriptor,
indices: &[u8],
palette: &[[u8; 3]],
transparent: Option<u8>,
) {
let canvas_width = self.descriptor.width;
let canvas_height = self.descriptor.height;
let frame_width = u32::from(image.width);
let frame_height = u32::from(image.height);
for source_row in 0..frame_height {
let target_row = if image.interlaced {
match deinterlace(source_row, frame_height) {
Some(row) => row,
None => continue,
}
} else {
source_row
};
let y = u32::from(image.top) + target_row;
if y >= canvas_height {
continue;
}
for column in 0..frame_width {
let x = u32::from(image.left) + column;
if x >= canvas_width {
continue;
}
let index = (source_row * frame_width + column) as usize;
let Some(&entry) = indices.get(index) else {
continue;
};
if Some(entry) == transparent {
continue;
}
let colour = palette.get(entry as usize).copied().unwrap_or([0, 0, 0]);
let at = ((y * canvas_width + x) * 4) as usize;
if let Some(slot) = self.canvas.get_mut(at..at + 4) {
slot.copy_from_slice(&[colour[0], colour[1], colour[2], 255]);
}
}
}
}
fn ensure_started(&mut self) -> Result<()> {
if self.started {
return Ok(());
}
match self.next_frame()? {
Some(_) => Ok(()),
None => Err(PixelsError::malformed("gif", "stream contains no frames")),
}
}
}
type Bytes = std::io::Cursor<Vec<u8>>;
fn scan_animation(stream: &[u8]) -> Option<Animation> {
let mut source = stream;
let mut delays = Vec::new();
let mut delay = 0_u32;
let mut loop_count = 1;
let mut byte = [0_u8; 1];
while source.read_exact(&mut byte).is_ok() {
match byte[0] {
label::EXTENSION => {
if source.read_exact(&mut byte).is_err() {
break;
}
let Ok(payload) = read_sub_blocks(&mut source, MAX_EXTENSION) else {
break;
};
match byte[0] {
label::GRAPHIC_CONTROL => {
delay = u32::from(GraphicControl::parse(&payload).delay_centiseconds) * 10;
}
0xFF if payload.starts_with(b"NETSCAPE2.0")
|| payload.starts_with(b"ANIMEXTS1.0") =>
{
if let [1, lo, hi, ..] = payload.get(11..).unwrap_or_default() {
loop_count = u32::from(u16::from_le_bytes([*lo, *hi]));
}
}
_ => {}
}
}
label::IMAGE => {
let mut bytes = [0_u8; 9];
if source.read_exact(&mut bytes).is_err() {
break;
}
let Ok(image) = ImageDescriptor::parse(&bytes) else {
break;
};
let table = image.local_table_size * 3;
let mut code_size = [0_u8; 1];
if source.len() < table + 1 {
break;
}
source = source.get(table..).unwrap_or_default();
if source.read_exact(&mut code_size).is_err()
|| skip_sub_blocks(&mut source).is_err()
{
break;
}
delays.push(delay);
delay = 0;
}
_ => break,
}
}
Animation::new(delays, loop_count)
}
fn deinterlace(stored: u32, height: u32) -> Option<u32> {
let mut seen = 0;
for pass in 0..4 {
let rows = interlaced_pass_rows(pass, height);
if stored < seen + rows {
return interlaced_row(pass, stored - seen, height);
}
seen += rows;
}
None
}
fn read_table<S: Source>(source: &mut S, entries: usize) -> Result<Vec<[u8; 3]>> {
let mut bytes = vec![0_u8; entries * 3];
source.read_exact(&mut bytes)?;
Ok(bytes
.chunks_exact(3)
.map(|rgb| {
[
rgb.first().copied().unwrap_or(0),
rgb.get(1).copied().unwrap_or(0),
rgb.get(2).copied().unwrap_or(0),
]
})
.collect())
}
impl<S: Source + std::fmt::Debug> Decoder for GifDecoder<S> {
fn descriptor(&self) -> CoreDescriptor {
self.descriptor
}
fn capability(&self) -> DecodeCapability {
DecodeCapability::Sequential
}
fn animation(&self) -> Option<Animation> {
self.animation.clone()
}
fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
self.ensure_started()?;
if self.row >= self.descriptor.height {
return Err(PixelsError::invalid_argument(
"out",
format!("all {} rows have already been read", self.descriptor.height),
));
}
let row_bytes = self.descriptor.row_bytes();
if out.len() != row_bytes {
return Err(PixelsError::invalid_argument(
"out",
format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
));
}
let start = self.row as usize * row_bytes;
let row = self
.canvas
.get(start..start + row_bytes)
.ok_or_else(|| PixelsError::malformed("gif", "canvas is short"))?;
out.copy_from_slice(row);
self.row += 1;
Ok(())
}
}
#[must_use]
pub fn probe(prefix: &[u8]) -> bool {
let head = prefix.get(..6);
head == Some(&SIGNATURE_87A[..]) || head == Some(&SIGNATURE_89A[..])
}
#[derive(Debug, Clone, Copy, Default)]
pub struct GifCodec;
impl Codec for GifCodec {
fn format(&self) -> Format {
Format::Gif
}
fn magic_len(&self) -> usize {
6
}
fn probe(&self, prefix: &[u8]) -> bool {
probe(prefix)
}
}