Skip to content

Commit 3be072b

Browse files
committed
test: add regression tests for small interlaced GIF decode (#68)
Three valid interlaced GIFs (7x7, 9x9, 47x63) produced by gifsicle 1.95 are rejected with "image truncated". All decode correctly with giftopnm (netpbm) and are valid per the GIF89a spec. Root cause: when decoding interlaced frames, read_into_buffer passes per-row slices to the LZW decoder. For small images (7-47 byte rows), weezl's NoProgress status triggers two bugs in DecodeSubBlock: 1. Input data is skipped instead of retried with a smaller buffer 2. NoProgress during flush is treated as end-of-stream Related: image-rs/weezl#72
1 parent eedbce5 commit 3be072b

1 file changed

Lines changed: 73 additions & 0 deletions

File tree

tests/small_interlaced.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//! Regression tests for small interlaced GIF decoding.
2+
//!
3+
//! These GIFs were produced by gifsicle 1.95 and are valid per the GIF89a spec
4+
//! (verified with giftopnm from netpbm). The gif crate incorrectly rejects them
5+
//! as "image truncated" due to how it handles weezl's NoProgress status when
6+
//! decoding interlaced frames with small per-row output buffers.
7+
//!
8+
//! Related: https://github.com/image-rs/weezl/pull/72 (yield_on_full fixes)
9+
10+
// 39 bytes: 7x7 interlaced grayscale GIF (2-color palette) from gifsicle 1.95
11+
const SMALL_INTERLACED_7X7: &[u8] = &[
12+
0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x07, 0x00, 0x07, 0x00, 0xf0, 0x00, 0x00, 0xad, 0xad, 0xad,
13+
0xff, 0xff, 0xff, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x40, 0x02, 0x06, 0x84,
14+
0x8f, 0xa9, 0xcb, 0x8d, 0x05, 0x00, 0x3b,
15+
];
16+
17+
// 71 bytes: 9x9 interlaced bilevel GIF from gifsicle 1.95
18+
const SMALL_INTERLACED_9X9: &[u8] = &[
19+
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x09, 0x00, 0x09, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
20+
0xff, 0xff, 0xff, 0x21, 0xff, 0x0b, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4d, 0x61, 0x67, 0x69, 0x63,
21+
0x6b, 0x0e, 0x67, 0x61, 0x6d, 0x6d, 0x61, 0x3d, 0x30, 0x2e, 0x34, 0x35, 0x34, 0x35, 0x34, 0x35,
22+
0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x09, 0x00, 0x40, 0x02, 0x08, 0x84, 0x8f, 0xa9,
23+
0xcb, 0xed, 0x0f, 0x4f, 0x01, 0x00, 0x3b,
24+
];
25+
26+
// 149 bytes: 47x63 interlaced grayscale GIF from gifsicle 1.95
27+
const SMALL_INTERLACED_47X63: &[u8] = &[
28+
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x2f, 0x00, 0x3f, 0x00, 0xf0, 0x00, 0x00, 0x23, 0x23, 0x23,
29+
0xdc, 0xdc, 0xdc, 0x21, 0xff, 0x0b, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4d, 0x61, 0x67, 0x69, 0x63,
30+
0x6b, 0x0e, 0x67, 0x61, 0x6d, 0x6d, 0x61, 0x3d, 0x30, 0x2e, 0x34, 0x35, 0x34, 0x35, 0x34, 0x35,
31+
0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x3f, 0x00, 0x40, 0x02, 0x56, 0x8c, 0x8f, 0xa9,
32+
0xcb, 0xed, 0x0f, 0xa3, 0x9c, 0xb4, 0xda, 0x0a, 0xb2, 0xde, 0xbc, 0xfb, 0x0f, 0x86, 0xe2, 0x48,
33+
0x6a, 0xd7, 0xc9, 0x94, 0xea, 0xca, 0xb6, 0xee, 0x0b, 0xc7, 0xf2, 0x4c, 0xd7, 0xf6, 0x8d, 0xe7,
34+
0xfa, 0xce, 0xf7, 0xfe, 0x0f, 0x0c, 0x0a, 0x87, 0xc4, 0xa2, 0xf1, 0x88, 0x4c, 0x2a, 0x97, 0xcc,
35+
0x66, 0x10, 0x05, 0x45, 0x28, 0xa3, 0x54, 0xa7, 0xf5, 0x49, 0x85, 0x5e, 0xb7, 0xbd, 0xac, 0x96,
36+
0x0b, 0xc6, 0x79, 0x51, 0xe1, 0x72, 0x6d, 0x7c, 0x32, 0xab, 0x63, 0xe8, 0xcb, 0xfa, 0x0d, 0x8f,
37+
0xcb, 0x4b, 0x05, 0x00, 0x3b,
38+
];
39+
40+
/// Decode a GIF using read_next_frame and return the frame pixels + dimensions.
41+
fn decode_gif(data: &[u8]) -> Result<(Vec<u8>, u16, u16), gif::DecodingError> {
42+
let mut opts = gif::DecodeOptions::new();
43+
opts.set_color_output(gif::ColorOutput::Indexed);
44+
let mut decoder = opts.read_info(data)?;
45+
let frame = decoder
46+
.read_next_frame()?
47+
.expect("expected at least one frame");
48+
Ok((frame.buffer.to_vec(), frame.width, frame.height))
49+
}
50+
51+
#[test]
52+
fn small_interlaced_7x7_decodes() {
53+
let (pixels, w, h) =
54+
decode_gif(SMALL_INTERLACED_7X7).expect("7x7 interlaced GIF should decode");
55+
assert_eq!((w, h), (7, 7));
56+
assert_eq!(pixels.len(), 49); // 7*7 indexed pixels
57+
}
58+
59+
#[test]
60+
fn small_interlaced_9x9_decodes() {
61+
let (pixels, w, h) =
62+
decode_gif(SMALL_INTERLACED_9X9).expect("9x9 interlaced GIF should decode");
63+
assert_eq!((w, h), (9, 9));
64+
assert_eq!(pixels.len(), 81); // 9*9 indexed pixels
65+
}
66+
67+
#[test]
68+
fn small_interlaced_47x63_decodes() {
69+
let (pixels, w, h) =
70+
decode_gif(SMALL_INTERLACED_47X63).expect("47x63 interlaced GIF should decode");
71+
assert_eq!((w, h), (47, 63));
72+
assert_eq!(pixels.len(), 2961); // 47*63 indexed pixels
73+
}

0 commit comments

Comments
 (0)