Decode multiple graphic controls in a GIF
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 14d3e60..71fbd24 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -3425,7 +3425,6 @@
uint8_t f_interlace;
bool f_seen_num_loops;
uint32_t f_num_loops;
- bool f_seen_graphic_control;
bool f_gc_has_transparent_index;
uint8_t f_gc_transparent_index;
uint8_t f_gc_disposal;
@@ -9376,7 +9375,6 @@
static wuffs_base__empty_struct //
wuffs_gif__decoder__reset_gc(wuffs_gif__decoder* self) {
self->private_impl.f_call_sequence = 5;
- self->private_impl.f_seen_graphic_control = false;
self->private_impl.f_gc_has_transparent_index = false;
self->private_impl.f_gc_transparent_index = 0;
self->private_impl.f_gc_disposal = 0;
@@ -10256,10 +10254,6 @@
switch (coro_susp_point) {
WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
- if (self->private_impl.f_seen_graphic_control) {
- status = wuffs_gif__error__bad_graphic_control;
- goto exit;
- }
{
WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
@@ -10344,7 +10338,6 @@
status = wuffs_gif__error__bad_graphic_control;
goto exit;
}
- self->private_impl.f_seen_graphic_control = true;
goto ok;
ok:
diff --git a/script/make-artificial.go b/script/make-artificial.go
index 5e2e96d..4c65088 100644
--- a/script/make-artificial.go
+++ b/script/make-artificial.go
@@ -513,9 +513,10 @@
func stateGif(line string) (stateFunc, error) {
const (
- cmdB = "bytes "
- cmdL = "lzw "
- cmdLC = "loopCount "
+ cmdB = "bytes "
+ cmdGCD = "graphicControlDuration "
+ cmdL = "lzw "
+ cmdLC = "loopCount "
)
outer:
switch {
@@ -547,6 +548,25 @@
}
return stateGif, nil
+ case strings.HasPrefix(line, cmdGCD):
+ s := line[len(cmdGCD):]
+ if !strings.HasSuffix(s, "ms") {
+ break
+ }
+ s = s[:len(s)-2]
+ duration, s, ok := parseNum(s)
+ if !ok || s != "" {
+ break
+ }
+ duration /= 10 // GIF's unit of time is 10ms.
+ out = append(out,
+ 0x21, 0xF9, 0x04, 0x00,
+ uint8(duration>>0),
+ uint8(duration>>8),
+ 0x00, 0x00,
+ )
+ return stateGif, nil
+
case strings.HasPrefix(line, cmdL):
s := line[len(cmdL):]
litWidth, s, ok := parseNum(s)
diff --git a/std/gif/decode_gif.wuffs b/std/gif/decode_gif.wuffs
index debe38a..dee425c 100644
--- a/std/gif/decode_gif.wuffs
+++ b/std/gif/decode_gif.wuffs
@@ -146,7 +146,6 @@
seen_num_loops base.bool,
num_loops base.u32,
- seen_graphic_control base.bool,
gc_has_transparent_index base.bool,
gc_transparent_index base.u8,
gc_disposal base.u8,
@@ -414,7 +413,6 @@
this.call_sequence = 5
// The Image Descriptor is mandatory, but the Graphic Control extension is
// optional. Reset the GC related fields for the next decode_frame call.
- this.seen_graphic_control = false
this.gc_has_transparent_index = false
this.gc_transparent_index = 0
this.gc_disposal = 0
@@ -681,9 +679,6 @@
var flags base.u8
var gc_duration_centiseconds base.u16
- if this.seen_graphic_control {
- return "#bad graphic control"
- }
c = args.src.read_u8?()
if c <> 4 {
return "#bad graphic control"
@@ -719,7 +714,6 @@
if c <> 0 {
return "#bad graphic control"
}
- this.seen_graphic_control = true
}
// decode_id_partX reads an Image Descriptor. The Image Separator byte has
diff --git a/test/c/std/gif.c b/test/c/std/gif.c
index 52ff77a..42fca32 100644
--- a/test/c/std/gif.c
+++ b/test/c/std/gif.c
@@ -1245,6 +1245,40 @@
src, 0, wuffs_base__suspension__short_read, false);
}
+const char* test_wuffs_gif_decode_multiple_graphic_controls() {
+ CHECK_FOCUS(__func__);
+ wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+ .data = global_src_slice,
+ });
+ const char* status =
+ read_file(&src, "test/data/artificial/gif-multiple-graphic-controls.gif");
+ if (status) {
+ return status;
+ }
+
+ wuffs_gif__decoder dec;
+ status = wuffs_gif__decoder__initialize(
+ &dec, sizeof dec, WUFFS_VERSION,
+ WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
+ if (status) {
+ RETURN_FAIL("initialize: \"%s\"", status);
+ }
+ wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
+ wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
+ status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, src_reader);
+ if (status) {
+ RETURN_FAIL("decode_frame_config: \"%s\"", status);
+ }
+
+ int got = wuffs_base__frame_config__duration(&fc) /
+ WUFFS_BASE__FLICKS_PER_MILLISECOND;
+ int want = 300;
+ if (got != want) {
+ RETURN_FAIL("duration: got %d, want %d", got, want);
+ }
+ return NULL;
+}
+
const char* test_wuffs_gif_decode_multiple_loop_counts() {
CHECK_FOCUS(__func__);
wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
@@ -2025,6 +2059,7 @@
test_wuffs_gif_decode_input_is_a_png, //
test_wuffs_gif_decode_metadata, //
test_wuffs_gif_decode_missing_two_src_bytes, //
+ test_wuffs_gif_decode_multiple_graphic_controls, //
test_wuffs_gif_decode_multiple_loop_counts, //
test_wuffs_gif_decode_pixel_data_none, //
test_wuffs_gif_decode_pixel_data_not_enough, //
diff --git a/test/data/artificial/gif-multiple-graphic-controls.gif b/test/data/artificial/gif-multiple-graphic-controls.gif
new file mode 100644
index 0000000..ca49790
--- /dev/null
+++ b/test/data/artificial/gif-multiple-graphic-controls.gif
Binary files differ
diff --git a/test/data/artificial/gif-multiple-graphic-controls.gif.make-artificial.txt b/test/data/artificial/gif-multiple-graphic-controls.gif.make-artificial.txt
new file mode 100644
index 0000000..c2ec26a
--- /dev/null
+++ b/test/data/artificial/gif-multiple-graphic-controls.gif.make-artificial.txt
@@ -0,0 +1,31 @@
+# Feed this file to script/make-artificial.go
+
+# This GIF image contains multiple "Graphic Control Extensions" for a frame.
+#
+# The GIF89a specification says that "at most one Graphic Control Extension may
+# precede a graphic rendering block", but in practice, some encoders emit more
+# than one, and decoders ignore all but the last one.
+
+make gif
+
+header
+
+image {
+ imageWidthHeight 1 1
+ palette {
+ 0x00 0x00 0xFF
+ 0x11 0x00 0xFF
+ 0x22 0x00 0xFF
+ 0x33 0x00 0xFF
+ }
+}
+
+graphicControlDuration 200ms
+graphicControlDuration 300ms
+
+frame {
+ frameLeftTopWidthHeight 0 0 1 1
+}
+lzw 2 0x00
+
+trailer