Add wuffs_gif__quirk_delay_num_decoded_frames
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index ce57f3e..28854b1 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -3299,6 +3299,12 @@
wuffs_gif__decoder_workbuf_len_max_incl_worst_case //
WUFFS_BASE__POTENTIALLY_UNUSED = 1;
+#define WUFFS_GIF__QUIRK_DELAY_NUM_DECODED_FRAMES 1041635328
+
+static const uint32_t //
+ wuffs_gif__quirk_delay_num_decoded_frames //
+ WUFFS_BASE__POTENTIALLY_UNUSED = 1041635328;
+
#define WUFFS_GIF__QUIRK_IGNORE_TOO_MUCH_PIXEL_DATA 1041635329
static const uint32_t //
@@ -3435,10 +3441,12 @@
uint32_t f_metadata_fourcc_value;
uint64_t f_metadata_chunk_length_value;
uint64_t f_metadata_io_position;
+ bool f_quirk_enabled_delay_num_decoded_frames;
bool f_quirk_enabled_ignore_too_much_pixel_data;
bool f_quirk_enabled_image_bounds_are_strict;
bool f_quirk_enabled_background_is_opaque;
bool f_quirk_enabled_reject_empty_palette;
+ bool f_delayed_num_decoded_frames;
bool f_end_of_data;
bool f_restarted;
bool f_previous_lzw_decode_ended_abruptly;
@@ -8731,7 +8739,9 @@
return wuffs_base__make_empty_struct();
}
- if (a_quirk == 1041635329) {
+ if (a_quirk == 1041635328) {
+ self->private_impl.f_quirk_enabled_delay_num_decoded_frames = a_enabled;
+ } else if (a_quirk == 1041635329) {
self->private_impl.f_quirk_enabled_ignore_too_much_pixel_data = a_enabled;
} else if (a_quirk == 1041635330) {
self->private_impl.f_quirk_enabled_image_bounds_are_strict = a_enabled;
@@ -9106,6 +9116,7 @@
if (self->private_impl.f_call_sequence == 0) {
return wuffs_base__error__bad_call_sequence;
}
+ self->private_impl.f_delayed_num_decoded_frames = false;
self->private_impl.f_end_of_data = false;
self->private_impl.f_restarted = true;
self->private_impl.f_frame_config_io_position = a_io_position;
@@ -9300,8 +9311,12 @@
if (status) {
goto suspend;
}
- wuffs_base__u64__sat_add_indirect(
- &self->private_impl.f_num_decoded_frames_value, 1);
+ if (self->private_impl.f_quirk_enabled_delay_num_decoded_frames) {
+ self->private_impl.f_delayed_num_decoded_frames = true;
+ } else {
+ wuffs_base__u64__sat_add_indirect(
+ &self->private_impl.f_num_decoded_frames_value, 1);
+ }
wuffs_gif__decoder__reset_gc(self);
goto ok;
@@ -9491,6 +9506,11 @@
goto suspend;
}
} else if (v_block_type == 44) {
+ if (self->private_impl.f_delayed_num_decoded_frames) {
+ self->private_impl.f_delayed_num_decoded_frames = false;
+ wuffs_base__u64__sat_add_indirect(
+ &self->private_impl.f_num_decoded_frames_value, 1);
+ }
if (a_src.private_impl.buf) {
a_src.private_impl.buf->meta.ri =
((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
@@ -9506,6 +9526,11 @@
}
goto label_0_break;
} else if (v_block_type == 59) {
+ if (self->private_impl.f_delayed_num_decoded_frames) {
+ self->private_impl.f_delayed_num_decoded_frames = false;
+ wuffs_base__u64__sat_add_indirect(
+ &self->private_impl.f_num_decoded_frames_value, 1);
+ }
self->private_impl.f_end_of_data = true;
goto label_0_break;
} else {
diff --git a/std/gif/decode_gif.wuffs b/std/gif/decode_gif.wuffs
index d37643d..902fb1c 100644
--- a/std/gif/decode_gif.wuffs
+++ b/std/gif/decode_gif.wuffs
@@ -38,6 +38,22 @@
// The base38 encoding of "gif " is 0xF8586.
+// When this quirk is enabled, when skipping over frames, the number of frames
+// visited isn't incremented when the last byte of the N'th frame is seen.
+// Instead, it is incremented when the first byte of the N+1'th frame's header
+// is seen. There may be zero or more GIF extensions between the N'th frame's
+// payload and the N+1'th frame's header.
+//
+// For a well-formed GIF, this won't have much effect. For a malformed GIF,
+// this can affect the number of valid frames, if there is an error detected in
+// the extensions between one frame's payload and the next frame's header.
+//
+// Some other GIF decoders don't register the N'th frame as complete until they
+// see the N+1'th frame's header (or the end-of-animation terminator), so that
+// e.g. the API for visiting the N'th frame can also return whether it's the
+// final frame. Enabling this quirk allows for matching that behavior.
+pub const quirk_delay_num_decoded_frames base.u32 = (0xF8586 << 10) | 0
+
// When this quirk is enabled, silently ignore e.g. a frame that reports a
// width and height of 6 pixels each, followed by 50 pixel values. In that
// case, we process the first 36 pixel values and discard the excess 14.
@@ -135,11 +151,13 @@
metadata_chunk_length_value base.u64,
metadata_io_position base.u64,
+ quirk_enabled_delay_num_decoded_frames base.bool,
quirk_enabled_ignore_too_much_pixel_data base.bool,
quirk_enabled_image_bounds_are_strict base.bool,
quirk_enabled_background_is_opaque base.bool,
quirk_enabled_reject_empty_palette base.bool,
+ delayed_num_decoded_frames base.bool,
end_of_data base.bool,
restarted base.bool,
previous_lzw_decode_ended_abruptly base.bool,
@@ -194,7 +212,9 @@
)
pub func decoder.set_quirk_enabled!(quirk base.u32, enabled base.bool) {
- if args.quirk == quirk_ignore_too_much_pixel_data {
+ if args.quirk == quirk_delay_num_decoded_frames {
+ this.quirk_enabled_delay_num_decoded_frames = args.enabled
+ } else if args.quirk == quirk_ignore_too_much_pixel_data {
this.quirk_enabled_ignore_too_much_pixel_data = args.enabled
} else if args.quirk == quirk_image_bounds_are_strict {
this.quirk_enabled_image_bounds_are_strict = args.enabled
@@ -336,6 +356,7 @@
if this.call_sequence == 0 {
return base."#bad call sequence"
}
+ this.delayed_num_decoded_frames = false
this.end_of_data = false
this.restarted = true
this.frame_config_io_position = args.io_position
@@ -409,7 +430,11 @@
// Skip the blocks of LZW-compressed data.
this.skip_blocks?(src:args.src)
- this.num_decoded_frames_value ~sat+= 1
+ if this.quirk_enabled_delay_num_decoded_frames {
+ this.delayed_num_decoded_frames = true
+ } else {
+ this.num_decoded_frames_value ~sat+= 1
+ }
this.reset_gc!()
}
@@ -454,9 +479,17 @@
if block_type == 0x21 { // The spec calls 0x21 the "Extension Introducer".
this.decode_extension?(src:args.src)
} else if block_type == 0x2C { // The spec calls 0x2C the "Image Separator".
+ if this.delayed_num_decoded_frames {
+ this.delayed_num_decoded_frames = false
+ this.num_decoded_frames_value ~sat+= 1
+ }
this.decode_id_part0?(src:args.src)
break
} else if block_type == 0x3B { // The spec calls 0x3B the "Trailer".
+ if this.delayed_num_decoded_frames {
+ this.delayed_num_decoded_frames = false
+ this.num_decoded_frames_value ~sat+= 1
+ }
this.end_of_data = true
break
} else {
diff --git a/test/c/std/gif.c b/test/c/std/gif.c
index d90f04b..7457045 100644
--- a/test/c/std/gif.c
+++ b/test/c/std/gif.c
@@ -724,6 +724,60 @@
want_frame_config_bounds);
}
+const char* test_wuffs_gif_decode_delay_num_frames_decoded() {
+ CHECK_FOCUS(__func__);
+ wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+ .data = global_src_slice,
+ });
+ const char* status = read_file(&src, "test/data/animated-red-blue.gif");
+ if (status) {
+ return status;
+ }
+ if (src.meta.wi < 1) {
+ return "src file is too short";
+ }
+
+ // A GIF image should end with the 0x3B Trailer byte.
+ if (src.data.ptr[src.meta.wi - 1] != 0x3B) {
+ RETURN_FAIL("final byte: got 0x%02X, want 0x%02X",
+ src.data.ptr[src.meta.wi - 1], 0x3B);
+ }
+ // Replace that final byte with something invalid: neither 0x21 (Extension
+ // Introducer), 0x2C (Image Separator) or 0x3B (Trailer).
+ src.data.ptr[src.meta.wi - 1] = 0x99;
+
+ int q;
+ for (q = 0; q < 2; q++) {
+ src.meta.ri = 0;
+
+ wuffs_gif__decoder dec;
+ status = wuffs_gif__decoder__initialize(
+ &dec, sizeof dec, WUFFS_VERSION,
+ WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
+ if (status) {
+ RETURN_FAIL("q=%d: initialize: \"%s\"", q, status);
+ }
+ wuffs_gif__decoder__set_quirk_enabled(
+ &dec, wuffs_gif__quirk_delay_num_decoded_frames, q);
+
+ while (true) {
+ status = wuffs_gif__decoder__decode_frame_config(
+ &dec, NULL, wuffs_base__io_buffer__reader(&src));
+ if (status) {
+ break;
+ }
+ }
+
+ uint64_t got = wuffs_gif__decoder__num_decoded_frames(&dec);
+ uint64_t want = q ? 3 : 4;
+ if (got != want) {
+ RETURN_FAIL("q=%d: num_decoded_frames: got %" PRIu64 ", want %" PRIu64, q,
+ got, want);
+ }
+ }
+ return NULL;
+}
+
const char* test_wuffs_gif_decode_empty_palette() {
CHECK_FOCUS(__func__);
wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
@@ -2141,6 +2195,7 @@
test_wuffs_gif_decode_animated_small, //
test_wuffs_gif_decode_background_color, //
test_wuffs_gif_decode_bgra_nonpremul, //
+ test_wuffs_gif_decode_delay_num_frames_decoded, //
test_wuffs_gif_decode_empty_palette, //
test_wuffs_gif_decode_first_frame_is_opaque, //
test_wuffs_gif_decode_frame_out_of_bounds, //