Skip to content

Commit 42db323

Browse files
authored
Merge pull request #155 from kornelski/skip-decoding
Option to skip decoding, while keeping LZW-compressed data
2 parents 3e2be46 + 6e27f08 commit 42db323

File tree

8 files changed

+131
-28
lines changed

8 files changed

+131
-28
lines changed

fuzz/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
21
[package]
32
name = "gif-fuzz"
43
version = "0.0.0"
54
authors = ["Automatically generated"]
65
publish = false
7-
edition = "2018"
6+
edition = "2021"
87

98
[package.metadata]
109
cargo-fuzz = true

gif-afl/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
name = "gif-afl"
33
version = "0.1.0"
44
authors = ["nwin <[email protected]>"]
5+
edition = "2021"
56

67
[dependencies.gif]
78
version = "*"
89
path = "../"
910

1011
[dependencies]
1112
afl = "*"
12-
afl-plugin = "*"
13+
afl-plugin = "*"

gif-c/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ license = "MIT OR Apache-2.0"
44
version = "0.0.0"
55
description = "C bindings to the gif library"
66
authors = ["HeroicKatora <[email protected]>"]
7-
87
homepage = "https://github.com/image-rs/image-gif"
98
repository = "https://github.com/image-rs/image-gif"
10-
edition = "2018"
9+
edition = "2021"
1110

1211
[lib]
1312
crate-type = ["staticlib", "cdylib"]

src/common.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,8 @@ impl Frame<'static> {
325325
#[cfg(feature = "color_quant")]
326326
pub fn from_rgb_speed(width: u16, height: u16, pixels: &[u8], speed: i32) -> Frame<'static> {
327327
assert_eq!(width as usize * height as usize * 3, pixels.len(), "Too much or too little pixel data for the given width and height to create a GIF Frame");
328-
let mut vec: Vec<u8> = Vec::with_capacity(pixels.len() + width as usize * height as usize);
328+
let mut vec: Vec<u8> = Vec::new();
329+
vec.try_reserve_exact(pixels.len() + width as usize * height as usize).expect("OOM");
329330
for v in pixels.chunks_exact(3) {
330331
vec.extend_from_slice(&[v[0], v[1], v[2], 0xFF])
331332
}

src/encoder.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,13 @@ impl<W: Write> Encoder<W> {
141141
/// if no global palette shall be used an empty slice may be supplied.
142142
pub fn new(w: W, width: u16, height: u16, global_palette: &[u8]) -> Result<Self, EncodingError> {
143143
let buffer_size = (width as usize) * (height as usize);
144+
let mut buffer = Vec::new();
145+
buffer.try_reserve_exact(buffer_size).map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?;
144146
Encoder {
145147
w: Some(w),
146148
global_palette: false,
147-
width: width,
148-
height: height,
149-
buffer: Vec::with_capacity(buffer_size)
149+
width, height,
150+
buffer,
150151
}.write_global_palette(global_palette)
151152
}
152153

src/reader/decoder.rs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ pub enum Extensions {
110110
Skip
111111
}
112112

113+
#[derive(Debug, Copy, Clone)]
114+
pub enum FrameDataType {
115+
Pixels,
116+
Lzw { min_code_size: u8 },
117+
}
118+
113119
/// Indicates whether a certain object has been decoded
114120
#[derive(Debug)]
115121
pub enum Decoded<'a> {
@@ -133,15 +139,16 @@ pub enum Decoded<'a> {
133139
/// Indicates the label of the extension which might be unknown. A label of `0` is used when
134140
/// the sub block does not belong to an extension.
135141
BlockFinished(AnyExtension, &'a [u8]),
136-
/// Decoded all information of the next frame.
142+
/// Decoded all information of the next frame, except the image data.
137143
///
138144
/// The returned frame does **not** contain any owned image data.
139-
Frame(&'a Frame<'static>),
145+
FrameMetadata(&'a Frame<'static>, FrameDataType),
140146
/// Decoded some data of the current frame.
141147
BytesDecoded(usize),
148+
/// Copied compressed data of the current frame.
149+
LzwData(&'a [u8]),
142150
/// No more data available the current frame.
143151
DataEnd,
144-
145152
}
146153

147154
/// Internal state of the GIF decoder
@@ -159,7 +166,10 @@ enum State {
159166
SkipBlock(usize),
160167
LocalPalette(usize),
161168
LzwInit(u8),
169+
/// Decompresses LZW
162170
DecodeSubBlock(usize),
171+
/// Keeps LZW compressed
172+
CopySubBlock(usize),
163173
FrameDecoded,
164174
Trailer
165175
}
@@ -258,6 +268,7 @@ pub struct StreamingDecoder {
258268
state: Option<State>,
259269
lzw_reader: LzwReader,
260270
skip_extensions: bool,
271+
skip_frame_decoding: bool,
261272
check_frame_consistency: bool,
262273
allow_unknown_blocks: bool,
263274
version: Version,
@@ -299,6 +310,7 @@ impl StreamingDecoder {
299310
state: Some(Magic(0, [0; 6])),
300311
lzw_reader: LzwReader::new(options.check_for_end_code),
301312
skip_extensions: true,
313+
skip_frame_decoding: options.skip_frame_decoding,
302314
check_frame_consistency: options.check_frame_consistency,
303315
allow_unknown_blocks: options.allow_unknown_blocks,
304316
version: Version::V87a,
@@ -405,7 +417,7 @@ impl StreamingDecoder {
405417
}
406418
}
407419

408-
fn next_state<'a>(&'a mut self, buf: &[u8], decode_bytes_into: Option<&mut [u8]>) -> Result<(usize, Decoded<'a>), DecodingError> {
420+
fn next_state<'a>(&'a mut self, buf: &'a [u8], decode_bytes_into: Option<&mut [u8]>) -> Result<(usize, Decoded<'a>), DecodingError> {
409421
macro_rules! goto (
410422
($n:expr, $state:expr) => ({
411423
self.state = Some($state);
@@ -495,7 +507,7 @@ impl StreamingDecoder {
495507
let global_table = b & 0x80 != 0;
496508
let entries = if global_table {
497509
let entries = PLTE_CHANNELS*(1 << ((b & 0b111) + 1) as usize);
498-
self.global_color_table.reserve_exact(entries);
510+
self.global_color_table.try_reserve_exact(entries).map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?;
499511
entries
500512
} else {
501513
0usize
@@ -556,9 +568,9 @@ impl StreamingDecoder {
556568

557569
if local_table {
558570
let entries = PLTE_CHANNELS * (1 << (table_size + 1));
559-
560-
self.current_frame_mut().palette =
561-
Some(Vec::with_capacity(entries));
571+
let mut pal = Vec::new();
572+
pal.try_reserve_exact(entries).map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?;
573+
self.current_frame_mut().palette = Some(pal);
562574
goto!(LocalPalette(entries))
563575
} else {
564576
goto!(Byte(CodeSize))
@@ -663,12 +675,29 @@ impl StreamingDecoder {
663675
}
664676
}
665677
LzwInit(min_code_size) => {
666-
// Reset validates the min code size
667-
self.lzw_reader.reset(min_code_size)?;
668-
669-
goto!(DecodeSubBlock(b as usize), emit Decoded::Frame(self.current_frame_mut()))
678+
if !self.skip_frame_decoding {
679+
// Reset validates the min code size
680+
self.lzw_reader.reset(min_code_size)?;
681+
goto!(DecodeSubBlock(b as usize), emit Decoded::FrameMetadata(self.current_frame_mut(), FrameDataType::Pixels))
682+
} else {
683+
goto!(CopySubBlock(b as usize), emit Decoded::FrameMetadata(self.current_frame_mut(), FrameDataType::Lzw { min_code_size }))
684+
}
685+
}
686+
CopySubBlock(left) => {
687+
debug_assert!(self.skip_frame_decoding);
688+
let n = cmp::min(left, buf.len());
689+
if left > 0 {
690+
goto!(n, CopySubBlock(left - n), emit Decoded::LzwData(&buf[..n]))
691+
} else if b != 0 {
692+
goto!(CopySubBlock(b as usize))
693+
} else {
694+
// end of image data reached
695+
self.current = None;
696+
goto!(0, FrameDecoded, emit Decoded::DataEnd)
697+
}
670698
}
671699
DecodeSubBlock(left) => {
700+
debug_assert!(!self.skip_frame_decoding);
672701
if left > 0 {
673702
let n = cmp::min(left, buf.len());
674703
if self.lzw_reader.has_ended() || decode_bytes_into.is_none() {

src/reader/mod.rs

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::common::{Block, Frame};
1111
mod decoder;
1212
pub use self::decoder::{
1313
PLTE_CHANNELS, StreamingDecoder, Decoded, DecodingError, DecodingFormatError, Extensions,
14-
Version
14+
Version, FrameDataType
1515
};
1616

1717
const N_CHANNELS: usize = 4;
@@ -89,6 +89,7 @@ pub struct DecodeOptions {
8989
memory_limit: MemoryLimit,
9090
color_output: ColorOutput,
9191
check_frame_consistency: bool,
92+
skip_frame_decoding: bool,
9293
check_for_end_code: bool,
9394
allow_unknown_blocks: bool,
9495
}
@@ -100,6 +101,7 @@ impl DecodeOptions {
100101
memory_limit: MemoryLimit::Bytes(50_000_000.try_into().unwrap()), // 50 MB
101102
color_output: ColorOutput::Indexed,
102103
check_frame_consistency: false,
104+
skip_frame_decoding: false,
103105
check_for_end_code: false,
104106
allow_unknown_blocks: false,
105107
}
@@ -129,6 +131,18 @@ impl DecodeOptions {
129131
self.check_frame_consistency = check;
130132
}
131133

134+
/// Configure whether to skip decoding frames.
135+
///
136+
/// The default is false.
137+
///
138+
/// When turned on, LZW decoding is skipped. `Decoder::read_next_frame` will return
139+
/// compressed LZW bytes in frame's data.
140+
/// `Decoder::next_frame_info` will return the metadata of the next frame as usual.
141+
/// This is useful to count frames without incurring the overhead of decoding.
142+
pub fn skip_frame_decoding(&mut self, skip: bool) {
143+
self.skip_frame_decoding = skip;
144+
}
145+
132146
/// Configure if LZW encoded blocks must end with a marker end code.
133147
///
134148
/// The default is `false`.
@@ -210,6 +224,7 @@ pub struct Decoder<R: Read> {
210224
bg_color: Option<u8>,
211225
global_palette: Option<Vec<u8>>,
212226
current_frame: Frame<'static>,
227+
current_frame_data_type: FrameDataType,
213228
buffer: Vec<u8>,
214229
}
215230

@@ -237,6 +252,7 @@ impl<R> Decoder<R> where R: Read {
237252
color_output: options.color_output,
238253
memory_limit: options.memory_limit,
239254
current_frame: Frame::default(),
255+
current_frame_data_type: FrameDataType::Pixels,
240256
}
241257
}
242258

@@ -277,8 +293,9 @@ impl<R> Decoder<R> where R: Read {
277293
pub fn next_frame_info(&mut self) -> Result<Option<&Frame<'static>>, DecodingError> {
278294
loop {
279295
match self.decoder.decode_next(None)? {
280-
Some(Decoded::Frame(frame)) => {
296+
Some(Decoded::FrameMetadata(frame, frame_data_type)) => {
281297
self.current_frame = frame.clone();
298+
self.current_frame_data_type = frame_data_type;
282299
if frame.palette.is_none() && self.global_palette.is_none() {
283300
return Err(DecodingError::format(
284301
"no color table available for current frame"
@@ -311,11 +328,21 @@ impl<R> Decoder<R> where R: Read {
311328
pixel_bytes, self.buffer_size(),
312329
"Checked computation diverges from required buffer size"
313330
);
314-
315-
let mut vec = vec![0; pixel_bytes];
316-
self.read_into_buffer(&mut vec)?;
317-
self.current_frame.buffer = Cow::Owned(vec);
318-
self.current_frame.interlaced = false;
331+
match self.current_frame_data_type {
332+
FrameDataType::Pixels => {
333+
let mut vec = vec![0; pixel_bytes];
334+
self.read_into_buffer(&mut vec)?;
335+
self.current_frame.buffer = Cow::Owned(vec);
336+
self.current_frame.interlaced = false;
337+
}
338+
FrameDataType::Lzw { min_code_size } => {
339+
let mut vec = Vec::new();
340+
// Guesstimate 2bpp
341+
vec.try_reserve(pixel_bytes/4).map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?;
342+
self.copy_lzw_into_buffer(min_code_size, &mut vec)?;
343+
self.current_frame.buffer = Cow::Owned(vec);
344+
},
345+
}
319346
Ok(Some(&self.current_frame))
320347
} else {
321348
Ok(None)
@@ -348,6 +375,21 @@ impl<R> Decoder<R> where R: Read {
348375
Ok(())
349376
}
350377

378+
fn copy_lzw_into_buffer(&mut self, min_code_size: u8, buf: &mut Vec<u8>) -> Result<(), DecodingError> {
379+
// `write_lzw_pre_encoded_frame` smuggles `min_code_size` in the first byte.
380+
buf.push(min_code_size);
381+
loop {
382+
match self.decoder.decode_next(None)? {
383+
Some(Decoded::LzwData(data)) => {
384+
buf.try_reserve(data.len()).map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?;
385+
buf.extend_from_slice(data);
386+
},
387+
Some(Decoded::DataEnd) => return Ok(()),
388+
_ => return Err(DecodingError::format("unexpected data")),
389+
}
390+
}
391+
}
392+
351393
/// Reads data of the current frame into a pre-allocated buffer until the buffer has been
352394
/// filled completely.
353395
///

tests/decode.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,34 @@ fn check_for_end_code_is_configurable() {
8282
assert!(decoder.read_next_frame().is_err());
8383
}
8484
}
85+
86+
#[test]
87+
fn check_skip_frame_data() {
88+
let image: &[u8] = include_bytes!("samples/moon_impact.gif");
89+
90+
let mut options = DecodeOptions::new();
91+
options.skip_frame_decoding(true);
92+
let mut decoder = options.clone().read_info(image).unwrap();
93+
94+
for _ in 0..14 {
95+
assert!(matches!(decoder.next_frame_info().unwrap(), Some(_)));
96+
}
97+
98+
assert!(matches!(decoder.next_frame_info(), Ok(None)));
99+
}
100+
101+
#[test]
102+
fn check_skip_frame_data_decode_frame_error() {
103+
let image: &[u8] = include_bytes!("samples/moon_impact.gif");
104+
105+
let mut options = DecodeOptions::new();
106+
options.skip_frame_decoding(true);
107+
let mut skipping_decoder = options.read_info(image).unwrap();
108+
let mut normal_decoder = DecodeOptions::new().read_info(image).unwrap();
109+
110+
while let Ok(Some(_normal_frame)) = normal_decoder.read_next_frame() {
111+
let _compressed_frame = skipping_decoder.read_next_frame().unwrap().unwrap();
112+
}
113+
assert!(skipping_decoder.read_next_frame().unwrap().is_none());
114+
115+
}

0 commit comments

Comments
 (0)