Skip to content

Commit e75843f

Browse files
committed
Testing sequence number of APNG frame split across 2 fdAT chunks.
1 parent a568f6c commit e75843f

File tree

2 files changed

+92
-4
lines changed

2 files changed

+92
-4
lines changed

src/decoder/stream.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1662,6 +1662,16 @@ mod tests {
16621662
write_chunk(w, b"fcTL", &data);
16631663
}
16641664

1665+
/// Writes an fdAT chunk.
1666+
/// See https://wiki.mozilla.org/APNG_Specification#.60fdAT.60:_The_Frame_Data_Chunk
1667+
fn write_fdat(w: &mut impl Write, sequence_number: u32, image_data: &[u8]) {
1668+
let mut data = Vec::new();
1669+
data.write_u32::<byteorder::BigEndian>(sequence_number)
1670+
.unwrap();
1671+
data.write_all(&image_data).unwrap();
1672+
write_chunk(w, b"fdAT", &data);
1673+
}
1674+
16651675
/// Writes PNG signature and chunks that can precede an fdAT chunk that is expected
16661676
/// to have
16671677
/// - `sequence_number` set to 0
@@ -1724,4 +1734,75 @@ mod tests {
17241734
let msg = format!("{err}");
17251735
assert_eq!("fdAT chunk shorter than 4 bytes", msg);
17261736
}
1737+
1738+
#[test]
1739+
fn test_frame_split_across_two_fdat_chunks() {
1740+
// Generate test data where the 2nd animation frame is split across 2 fdAT chunks.
1741+
//
1742+
// This is similar to the example given in
1743+
// https://wiki.mozilla.org/APNG_Specification#Chunk_Sequence_Numbers:
1744+
//
1745+
// ```
1746+
// Sequence number Chunk
1747+
// (none) `acTL`
1748+
// 0 `fcTL` first frame
1749+
// (none) `IDAT` first frame / default image
1750+
// 1 `fcTL` second frame
1751+
// 2 first `fdAT` for second frame
1752+
// 3 second `fdAT` for second frame
1753+
// ```
1754+
let png = {
1755+
let mut png = Vec::new();
1756+
write_fdat_prefix(&mut png, 2, 8);
1757+
let image_data = generate_rgba8_with_width(8);
1758+
write_fdat(&mut png, 2, &image_data[..30]);
1759+
write_fdat(&mut png, 3, &image_data[30..]);
1760+
write_iend(&mut png);
1761+
png
1762+
};
1763+
1764+
// Start decoding.
1765+
let decoder = Decoder::new(png.as_slice());
1766+
let mut reader = decoder.read_info().unwrap();
1767+
let mut buf = vec![0; reader.output_buffer_size()];
1768+
let Some(animation_control) = reader.info().animation_control else {
1769+
panic!("No acTL");
1770+
};
1771+
assert_eq!(animation_control.num_frames, 2);
1772+
1773+
// Process the 1st animation frame.
1774+
let first_frame: Vec<u8>;
1775+
{
1776+
reader.next_frame(&mut buf).unwrap();
1777+
first_frame = buf.clone();
1778+
1779+
// Note that the doc comment of `Reader::next_frame` says that "[...]
1780+
// can be checked afterwards by calling `info` **after** a successful call and
1781+
// inspecting the `frame_control` data.". (Note the **emphasis** on "after".)
1782+
let Some(frame_control) = reader.info().frame_control else {
1783+
panic!("No fcTL (1st frame)");
1784+
};
1785+
// The sequence number is taken from the `fcTL` chunk that comes before the `IDAT`
1786+
// chunk.
1787+
assert_eq!(frame_control.sequence_number, 0);
1788+
}
1789+
1790+
// Process the 2nd animation frame.
1791+
let second_frame: Vec<u8>;
1792+
{
1793+
reader.next_frame(&mut buf).unwrap();
1794+
second_frame = buf.clone();
1795+
1796+
// Same as above - updated `frame_control` is available *after* the `next_frame` call.
1797+
let Some(frame_control) = reader.info().frame_control else {
1798+
panic!("No fcTL (2nd frame)");
1799+
};
1800+
// The sequence number is taken from the `fcTL` chunk that comes before the two `fdAT`
1801+
// chunks. Note that sequence numbers inside `fdAT` chunks are not publically exposed
1802+
// (but they are still checked when decoding to verify that they are sequential).
1803+
assert_eq!(frame_control.sequence_number, 1);
1804+
}
1805+
1806+
assert_eq!(first_frame, second_frame);
1807+
}
17271808
}

src/test_utils.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ pub fn write_rgba8_ihdr_with_width(w: &mut impl Write, width: u32) {
7373
write_chunk(w, b"IHDR", &data);
7474
}
7575

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

104-
write_chunk(w, b"IDAT", &zlib_data);
104+
zlib_data
105+
}
106+
107+
/// Writes an IDAT chunk.
108+
pub fn write_rgba8_idat_with_width(w: &mut impl Write, width: u32) {
109+
write_chunk(w, b"IDAT", &generate_rgba8_with_width(width));
105110
}
106111

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

0 commit comments

Comments
 (0)