Skip to content

Commit

Permalink
Testing sequence number of APNG frame split across 2 fdAT chunks.
Browse files Browse the repository at this point in the history
  • Loading branch information
anforowicz committed Nov 14, 2023
1 parent a568f6c commit e75843f
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 4 deletions.
81 changes: 81 additions & 0 deletions src/decoder/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,16 @@ mod tests {
write_chunk(w, b"fcTL", &data);
}

/// Writes an fdAT chunk.
/// See https://wiki.mozilla.org/APNG_Specification#.60fdAT.60:_The_Frame_Data_Chunk
fn write_fdat(w: &mut impl Write, sequence_number: u32, image_data: &[u8]) {
let mut data = Vec::new();
data.write_u32::<byteorder::BigEndian>(sequence_number)
.unwrap();
data.write_all(&image_data).unwrap();
write_chunk(w, b"fdAT", &data);
}

/// Writes PNG signature and chunks that can precede an fdAT chunk that is expected
/// to have
/// - `sequence_number` set to 0
Expand Down Expand Up @@ -1724,4 +1734,75 @@ mod tests {
let msg = format!("{err}");
assert_eq!("fdAT chunk shorter than 4 bytes", msg);
}

#[test]
fn test_frame_split_across_two_fdat_chunks() {
// Generate test data where the 2nd animation frame is split across 2 fdAT chunks.
//
// This is similar to the example given in
// https://wiki.mozilla.org/APNG_Specification#Chunk_Sequence_Numbers:
//
// ```
// Sequence number Chunk
// (none) `acTL`
// 0 `fcTL` first frame
// (none) `IDAT` first frame / default image
// 1 `fcTL` second frame
// 2 first `fdAT` for second frame
// 3 second `fdAT` for second frame
// ```
let png = {
let mut png = Vec::new();
write_fdat_prefix(&mut png, 2, 8);
let image_data = generate_rgba8_with_width(8);
write_fdat(&mut png, 2, &image_data[..30]);
write_fdat(&mut png, 3, &image_data[30..]);
write_iend(&mut png);
png
};

// Start decoding.
let decoder = Decoder::new(png.as_slice());
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size()];
let Some(animation_control) = reader.info().animation_control else {
panic!("No acTL");
};
assert_eq!(animation_control.num_frames, 2);

// Process the 1st animation frame.
let first_frame: Vec<u8>;
{
reader.next_frame(&mut buf).unwrap();
first_frame = buf.clone();

// Note that the doc comment of `Reader::next_frame` says that "[...]
// can be checked afterwards by calling `info` **after** a successful call and
// inspecting the `frame_control` data.". (Note the **emphasis** on "after".)
let Some(frame_control) = reader.info().frame_control else {
panic!("No fcTL (1st frame)");
};
// The sequence number is taken from the `fcTL` chunk that comes before the `IDAT`
// chunk.
assert_eq!(frame_control.sequence_number, 0);
}

// Process the 2nd animation frame.
let second_frame: Vec<u8>;
{
reader.next_frame(&mut buf).unwrap();
second_frame = buf.clone();

// Same as above - updated `frame_control` is available *after* the `next_frame` call.
let Some(frame_control) = reader.info().frame_control else {
panic!("No fcTL (2nd frame)");
};
// The sequence number is taken from the `fcTL` chunk that comes before the two `fdAT`
// chunks. Note that sequence numbers inside `fdAT` chunks are not publically exposed
// (but they are still checked when decoding to verify that they are sequential).
assert_eq!(frame_control.sequence_number, 1);
}

assert_eq!(first_frame, second_frame);
}
}
15 changes: 11 additions & 4 deletions src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ pub fn write_rgba8_ihdr_with_width(w: &mut impl Write, width: u32) {
write_chunk(w, b"IHDR", &data);
}

/// Writes an IDAT chunk.
pub fn write_rgba8_idat_with_width(w: &mut impl Write, width: u32) {
/// Generates RGBA8 `width` x `width` image and wraps it in a store-only zlib container.
pub fn generate_rgba8_with_width(width: u32) -> Vec<u8> {
// Generate arbitrary test pixels.
let image_pixels = {
let mut row = Vec::new();
Expand All @@ -101,9 +101,16 @@ pub fn write_rgba8_idat_with_width(w: &mut impl Write, width: u32) {
store_only_compressor.write_data(&image_pixels).unwrap();
store_only_compressor.finish().unwrap();

write_chunk(w, b"IDAT", &zlib_data);
zlib_data
}

/// Writes an IDAT chunk.
pub fn write_rgba8_idat_with_width(w: &mut impl Write, width: u32) {
write_chunk(w, b"IDAT", &generate_rgba8_with_width(width));
}

fn write_iend(w: &mut impl Write) {
/// Writes an IEND chunk.
/// See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IEND
pub fn write_iend(w: &mut impl Write) {
write_chunk(w, b"IEND", &[]);
}

0 comments on commit e75843f

Please sign in to comment.