Report image metadata such as ICCP and XMP
diff --git a/doc/changelog.md b/doc/changelog.md
index 205ad84..3d9501c 100644
--- a/doc/changelog.md
+++ b/doc/changelog.md
@@ -37,6 +37,7 @@
- Added a `reset` method.
- Added `peek_uxx`, `skip_fast` and `write_fast_uxx` methods.
- Renamed some `read_uxx` methods as `read_uxx_as_uyy`.
+- Report image metadata such as ICCP and XMP.
- Added I/O positions.
- Added extra fields (uninitialized internal buffers) to structs.
- Tweaked how marks and limits work.
@@ -68,4 +69,4 @@
---
-Updated on March 2019.
+Updated on April 2019.
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 901504e..091e922 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -3305,6 +3305,21 @@
wuffs_base__image_config* a_dst,
wuffs_base__io_reader a_src);
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct //
+wuffs_gif__decoder__set_report_metadata(wuffs_gif__decoder* self,
+ uint32_t a_fourcc,
+ bool a_report);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status //
+wuffs_gif__decoder__ack_metadata_chunk(wuffs_gif__decoder* self,
+ wuffs_base__io_reader a_src);
+
+WUFFS_BASE__MAYBE_STATIC uint32_t //
+wuffs_gif__decoder__metadata_fourcc(const wuffs_gif__decoder* self);
+
+WUFFS_BASE__MAYBE_STATIC uint64_t //
+wuffs_gif__decoder__metadata_chunk_length(const wuffs_gif__decoder* self);
+
WUFFS_BASE__MAYBE_STATIC uint32_t //
wuffs_gif__decoder__num_animation_loops(const wuffs_gif__decoder* self);
@@ -3363,6 +3378,11 @@
uint32_t f_width;
uint32_t f_height;
uint8_t f_call_sequence;
+ bool f_ignore_metadata;
+ bool f_report_metadata_xmp;
+ uint32_t f_metadata_fourcc_value;
+ uint64_t f_metadata_chunk_length_value;
+ uint64_t f_metadata_io_position;
bool f_end_of_data;
bool f_restarted;
bool f_previous_lzw_decode_ended_abruptly;
@@ -3390,6 +3410,7 @@
wuffs_base__pixel_swizzler f_swizzler;
uint32_t p_decode_image_config[1];
+ uint32_t p_ack_metadata_chunk[1];
uint32_t p_decode_frame_config[1];
uint32_t p_skip_frame[1];
uint32_t p_decode_frame[1];
@@ -3412,9 +3433,15 @@
wuffs_lzw__decoder f_lzw;
struct {
+ wuffs_base__status v_status;
+ } s_decode_image_config[1];
+ struct {
uint64_t scratch;
} s_skip_frame[1];
struct {
+ wuffs_base__status v_status;
+ } s_decode_up_to_id_part1[1];
+ struct {
uint8_t v_c[6];
uint32_t v_i;
} s_decode_header[1];
@@ -3425,12 +3452,16 @@
uint64_t scratch;
} s_decode_lsd[1];
struct {
+ wuffs_base__status v_status;
+ } s_decode_extension[1];
+ struct {
uint64_t scratch;
} s_skip_blocks[1];
struct {
uint8_t v_block_size;
- bool v_not_animexts;
- bool v_not_netscape;
+ bool v_is_animexts;
+ bool v_is_netscape;
+ bool v_is_xmp;
uint64_t scratch;
} s_decode_ae[1];
struct {
@@ -3490,6 +3521,26 @@
return wuffs_gif__decoder__decode_image_config(this, a_dst, a_src);
}
+ inline wuffs_base__empty_struct //
+ set_report_metadata(uint32_t a_fourcc, bool a_report) {
+ return wuffs_gif__decoder__set_report_metadata(this, a_fourcc, a_report);
+ }
+
+ inline wuffs_base__status //
+ ack_metadata_chunk(wuffs_base__io_reader a_src) {
+ return wuffs_gif__decoder__ack_metadata_chunk(this, a_src);
+ }
+
+ inline uint32_t //
+ metadata_fourcc() const {
+ return wuffs_gif__decoder__metadata_fourcc(this);
+ }
+
+ inline uint64_t //
+ metadata_chunk_length() const {
+ return wuffs_gif__decoder__metadata_chunk_length(this);
+ }
+
inline uint32_t //
num_animation_loops() const {
return wuffs_gif__decoder__num_animation_loops(this);
@@ -8469,6 +8520,12 @@
78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48,
};
+static const uint8_t //
+ wuffs_gif__xmpdataxmp[11] //
+ WUFFS_BASE__POTENTIALLY_UNUSED = {
+ 88, 77, 80, 32, 68, 97, 116, 97, 88, 77, 80,
+};
+
// ---------------- Private Initializer Prototypes
// ---------------- Private Function Prototypes
@@ -8613,32 +8670,55 @@
wuffs_base__status status = NULL;
bool v_ffio = false;
+ wuffs_base__status v_status = NULL;
uint32_t coro_susp_point = self->private_impl.p_decode_image_config[0];
if (coro_susp_point) {
+ v_status = self->private_data.s_decode_image_config[0].v_status;
}
switch (coro_susp_point) {
WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
- if (self->private_impl.f_call_sequence >= 3) {
+ if (self->private_impl.f_call_sequence == 0) {
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+ status = wuffs_gif__decoder__decode_header(self, a_src);
+ if (status) {
+ goto suspend;
+ }
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
+ status = wuffs_gif__decoder__decode_lsd(self, a_src);
+ if (status) {
+ goto suspend;
+ }
+ } else if (self->private_impl.f_call_sequence != 2) {
status = wuffs_base__error__bad_call_sequence;
goto exit;
}
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
- status = wuffs_gif__decoder__decode_header(self, a_src);
- if (status) {
- goto suspend;
+ label_0_continue:;
+ while (true) {
+ {
+ wuffs_base__status t_0 =
+ wuffs_gif__decoder__decode_up_to_id_part1(self, a_src);
+ v_status = t_0;
+ }
+ if (wuffs_base__status__is_suspension(v_status)) {
+ status = v_status;
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3);
+ goto label_0_continue;
+ }
+ if (wuffs_base__status__is_ok(v_status)) {
+ goto label_0_break;
+ }
+ status = v_status;
+ if (wuffs_base__status__is_error(status)) {
+ goto exit;
+ } else if (wuffs_base__status__is_suspension(status)) {
+ status = wuffs_base__error__cannot_return_a_suspension;
+ goto exit;
+ }
+ goto ok;
}
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
- status = wuffs_gif__decoder__decode_lsd(self, a_src);
- if (status) {
- goto suspend;
- }
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
- status = wuffs_gif__decoder__decode_up_to_id_part1(self, a_src);
- if (status) {
- goto suspend;
- }
+ label_0_break:;
v_ffio =
(!self->private_impl.f_gc_has_transparent_index &&
(self->private_impl.f_frame_rect_x0 == 0) &&
@@ -8663,6 +8743,7 @@
suspend:
self->private_impl.p_decode_image_config[0] = coro_susp_point;
self->private_impl.active_coroutine = 1;
+ self->private_data.s_decode_image_config[0].v_status = v_status;
goto exit;
exit:
@@ -8672,6 +8753,156 @@
return status;
}
+// -------- func gif.decoder.set_report_metadata
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct //
+wuffs_gif__decoder__set_report_metadata(wuffs_gif__decoder* self,
+ uint32_t a_fourcc,
+ bool a_report) {
+ if (!self) {
+ return wuffs_base__make_empty_struct();
+ }
+ if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+ return wuffs_base__make_empty_struct();
+ }
+
+ if (a_fourcc == 1481461792) {
+ self->private_impl.f_report_metadata_xmp = a_report;
+ }
+ return wuffs_base__make_empty_struct();
+}
+
+// -------- func gif.decoder.ack_metadata_chunk
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status //
+wuffs_gif__decoder__ack_metadata_chunk(wuffs_gif__decoder* self,
+ wuffs_base__io_reader a_src) {
+ if (!self) {
+ return wuffs_base__error__bad_receiver;
+ }
+ if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+ return (self->private_impl.magic == WUFFS_BASE__DISABLED)
+ ? wuffs_base__error__disabled_by_previous_error
+ : wuffs_base__error__initialize_not_called;
+ }
+ if ((self->private_impl.active_coroutine != 0) &&
+ (self->private_impl.active_coroutine != 2)) {
+ self->private_impl.magic = WUFFS_BASE__DISABLED;
+ return wuffs_base__error__interleaved_coroutine_calls;
+ }
+ self->private_impl.active_coroutine = 0;
+ wuffs_base__status status = NULL;
+
+ uint8_t* iop_a_src = NULL;
+ uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+ uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+ if (a_src.private_impl.buf) {
+ iop_a_src =
+ a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
+ if (!a_src.private_impl.mark) {
+ a_src.private_impl.mark = iop_a_src;
+ a_src.private_impl.limit =
+ a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
+ }
+ io0_a_src = a_src.private_impl.mark;
+ io1_a_src = a_src.private_impl.limit;
+ }
+
+ uint32_t coro_susp_point = self->private_impl.p_ack_metadata_chunk[0];
+ if (coro_susp_point) {
+ }
+ switch (coro_susp_point) {
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+ if (self->private_impl.f_call_sequence != 1) {
+ status = wuffs_base__error__bad_call_sequence;
+ goto exit;
+ }
+ if ((a_src.private_impl.buf
+ ? wuffs_base__u64__sat_add(
+ a_src.private_impl.buf->meta.pos,
+ ((uint64_t)(iop_a_src - a_src.private_impl.buf->data.ptr)))
+ : 0) != self->private_impl.f_metadata_io_position) {
+ status = wuffs_base__error__bad_i_o_position;
+ goto exit;
+ }
+ while (((uint64_t)(io1_a_src - iop_a_src)) <= 0) {
+ status = wuffs_base__suspension__short_read;
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+ }
+ self->private_impl.f_metadata_chunk_length_value =
+ (((uint64_t)(wuffs_base__load_u8be(iop_a_src))) + 1);
+ if (self->private_impl.f_metadata_chunk_length_value > 1) {
+ self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add(
+ (a_src.private_impl.buf
+ ? wuffs_base__u64__sat_add(
+ a_src.private_impl.buf->meta.pos,
+ ((uint64_t)(iop_a_src - a_src.private_impl.buf->data.ptr)))
+ : 0),
+ self->private_impl.f_metadata_chunk_length_value);
+ status = wuffs_base__warning__metadata_reported;
+ goto ok;
+ }
+ (iop_a_src += 1, wuffs_base__make_empty_struct());
+ self->private_impl.f_call_sequence = 2;
+ self->private_impl.f_metadata_fourcc_value = 0;
+ self->private_impl.f_metadata_io_position = 0;
+ status = NULL;
+ goto ok;
+ goto ok;
+ ok:
+ self->private_impl.p_ack_metadata_chunk[0] = 0;
+ goto exit;
+ }
+
+ goto suspend;
+suspend:
+ self->private_impl.p_ack_metadata_chunk[0] = coro_susp_point;
+ self->private_impl.active_coroutine = 2;
+
+ goto exit;
+exit:
+ 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));
+ }
+
+ if (wuffs_base__status__is_error(status)) {
+ self->private_impl.magic = WUFFS_BASE__DISABLED;
+ }
+ return status;
+}
+
+// -------- func gif.decoder.metadata_fourcc
+
+WUFFS_BASE__MAYBE_STATIC uint32_t //
+wuffs_gif__decoder__metadata_fourcc(const wuffs_gif__decoder* self) {
+ if (!self) {
+ return 0;
+ }
+ if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+ (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+ return 0;
+ }
+
+ return self->private_impl.f_metadata_fourcc_value;
+}
+
+// -------- func gif.decoder.metadata_chunk_length
+
+WUFFS_BASE__MAYBE_STATIC uint64_t //
+wuffs_gif__decoder__metadata_chunk_length(const wuffs_gif__decoder* self) {
+ if (!self) {
+ return 0;
+ }
+ if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+ (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+ return 0;
+ }
+
+ return self->private_impl.f_metadata_chunk_length_value;
+}
+
// -------- func gif.decoder.num_animation_loops
WUFFS_BASE__MAYBE_STATIC uint32_t //
@@ -8798,7 +9029,7 @@
: wuffs_base__error__initialize_not_called;
}
if ((self->private_impl.active_coroutine != 0) &&
- (self->private_impl.active_coroutine != 2)) {
+ (self->private_impl.active_coroutine != 3)) {
self->private_impl.magic = WUFFS_BASE__DISABLED;
return wuffs_base__error__interleaved_coroutine_calls;
}
@@ -8813,6 +9044,7 @@
switch (coro_susp_point) {
WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+ self->private_impl.f_ignore_metadata = true;
(memset(&self->private_impl.f_dirty_y, 0, sizeof(wuffs_base__range_ie_u32)),
wuffs_base__make_empty_struct());
if (!self->private_impl.f_end_of_data) {
@@ -8875,7 +9107,7 @@
goto suspend;
suspend:
self->private_impl.p_decode_frame_config[0] = coro_susp_point;
- self->private_impl.active_coroutine = 2;
+ self->private_impl.active_coroutine = 3;
goto exit;
exit:
@@ -9002,7 +9234,7 @@
return wuffs_base__error__bad_argument;
}
if ((self->private_impl.active_coroutine != 0) &&
- (self->private_impl.active_coroutine != 3)) {
+ (self->private_impl.active_coroutine != 4)) {
self->private_impl.magic = WUFFS_BASE__DISABLED;
return wuffs_base__error__interleaved_coroutine_calls;
}
@@ -9015,6 +9247,7 @@
switch (coro_susp_point) {
WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+ self->private_impl.f_ignore_metadata = true;
if (self->private_impl.f_call_sequence != 4) {
WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
status = wuffs_gif__decoder__decode_frame_config(self, NULL, a_src);
@@ -9045,7 +9278,7 @@
goto suspend;
suspend:
self->private_impl.p_decode_frame[0] = coro_susp_point;
- self->private_impl.active_coroutine = 3;
+ self->private_impl.active_coroutine = 4;
goto exit;
exit:
@@ -9076,6 +9309,7 @@
wuffs_base__status status = NULL;
uint8_t v_block_type = 0;
+ wuffs_base__status v_status = NULL;
uint8_t* iop_a_src = NULL;
uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
@@ -9094,17 +9328,21 @@
uint32_t coro_susp_point = self->private_impl.p_decode_up_to_id_part1[0];
if (coro_susp_point) {
+ v_status = self->private_data.s_decode_up_to_id_part1[0].v_status;
}
switch (coro_susp_point) {
WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
if (!self->private_impl.f_restarted) {
- self->private_impl.f_frame_config_io_position =
- (a_src.private_impl.buf
- ? wuffs_base__u64__sat_add(
- a_src.private_impl.buf->meta.pos,
- ((uint64_t)(iop_a_src - a_src.private_impl.buf->data.ptr)))
- : 0);
+ if (self->private_impl.f_call_sequence != 2) {
+ self->private_impl.f_frame_config_io_position =
+ (a_src.private_impl.buf
+ ? wuffs_base__u64__sat_add(
+ a_src.private_impl.buf->meta.pos,
+ ((uint64_t)(iop_a_src -
+ a_src.private_impl.buf->data.ptr)))
+ : 0);
+ }
} else if (self->private_impl.f_frame_config_io_position !=
(a_src.private_impl.buf
? wuffs_base__u64__sat_add(
@@ -9128,19 +9366,39 @@
v_block_type = t_0;
}
if (v_block_type == 33) {
- 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));
+ label_0_continue:;
+ while (true) {
+ {
+ 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));
+ }
+ wuffs_base__status t_1 =
+ wuffs_gif__decoder__decode_extension(self, a_src);
+ if (a_src.private_impl.buf) {
+ iop_a_src = a_src.private_impl.buf->data.ptr +
+ a_src.private_impl.buf->meta.ri;
+ }
+ v_status = t_1;
+ }
+ if (wuffs_base__status__is_suspension(v_status)) {
+ status = v_status;
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2);
+ goto label_0_continue;
+ }
+ if (wuffs_base__status__is_ok(v_status)) {
+ goto label_0_break;
+ }
+ status = v_status;
+ if (wuffs_base__status__is_error(status)) {
+ goto exit;
+ } else if (wuffs_base__status__is_suspension(status)) {
+ status = wuffs_base__error__cannot_return_a_suspension;
+ goto exit;
+ }
+ goto ok;
}
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
- status = wuffs_gif__decoder__decode_extension(self, a_src);
- if (a_src.private_impl.buf) {
- iop_a_src = a_src.private_impl.buf->data.ptr +
- a_src.private_impl.buf->meta.ri;
- }
- if (status) {
- goto suspend;
- }
+ label_0_break:;
} else if (v_block_type == 44) {
if (a_src.private_impl.buf) {
a_src.private_impl.buf->meta.ri =
@@ -9155,16 +9413,16 @@
if (status) {
goto suspend;
}
- goto label_0_break;
+ goto label_1_break;
} else if (v_block_type == 59) {
self->private_impl.f_end_of_data = true;
- goto label_0_break;
+ goto label_1_break;
} else {
status = wuffs_gif__error__bad_block;
goto exit;
}
}
- label_0_break:;
+ label_1_break:;
goto ok;
ok:
@@ -9175,6 +9433,7 @@
goto suspend;
suspend:
self->private_impl.p_decode_up_to_id_part1[0] = coro_susp_point;
+ self->private_data.s_decode_up_to_id_part1[0].v_status = v_status;
goto exit;
exit:
@@ -9459,6 +9718,7 @@
wuffs_base__status status = NULL;
uint8_t v_label = 0;
+ wuffs_base__status v_status = NULL;
uint8_t* iop_a_src = NULL;
uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
@@ -9477,6 +9737,7 @@
uint32_t coro_susp_point = self->private_impl.p_decode_extension[0];
if (coro_susp_point) {
+ v_status = self->private_data.s_decode_extension[0].v_status;
}
switch (coro_susp_point) {
WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
@@ -9507,19 +9768,38 @@
status = NULL;
goto ok;
} else if (v_label == 255) {
- 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));
+ label_0_continue:;
+ while (true) {
+ {
+ 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));
+ }
+ wuffs_base__status t_1 = wuffs_gif__decoder__decode_ae(self, a_src);
+ if (a_src.private_impl.buf) {
+ iop_a_src = a_src.private_impl.buf->data.ptr +
+ a_src.private_impl.buf->meta.ri;
+ }
+ v_status = t_1;
+ }
+ if (wuffs_base__status__is_suspension(v_status)) {
+ status = v_status;
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3);
+ goto label_0_continue;
+ }
+ if (wuffs_base__status__is_ok(v_status)) {
+ goto label_0_break;
+ }
+ status = v_status;
+ if (wuffs_base__status__is_error(status)) {
+ goto exit;
+ } else if (wuffs_base__status__is_suspension(status)) {
+ status = wuffs_base__error__cannot_return_a_suspension;
+ goto exit;
+ }
+ goto ok;
}
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
- status = wuffs_gif__decoder__decode_ae(self, a_src);
- if (a_src.private_impl.buf) {
- iop_a_src =
- a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
- }
- if (status) {
- goto suspend;
- }
+ label_0_break:;
status = NULL;
goto ok;
}
@@ -9546,6 +9826,7 @@
goto suspend;
suspend:
self->private_impl.p_decode_extension[0] = coro_susp_point;
+ self->private_data.s_decode_extension[0].v_status = v_status;
goto exit;
exit:
@@ -9643,8 +9924,9 @@
uint8_t v_c = 0;
uint8_t v_block_size = 0;
- bool v_not_animexts = false;
- bool v_not_netscape = false;
+ bool v_is_animexts = false;
+ bool v_is_netscape = false;
+ bool v_is_xmp = false;
uint8_t* iop_a_src = NULL;
uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
@@ -9664,8 +9946,9 @@
uint32_t coro_susp_point = self->private_impl.p_decode_ae[0];
if (coro_susp_point) {
v_block_size = self->private_data.s_decode_ae[0].v_block_size;
- v_not_animexts = self->private_data.s_decode_ae[0].v_not_animexts;
- v_not_netscape = self->private_data.s_decode_ae[0].v_not_netscape;
+ v_is_animexts = self->private_data.s_decode_ae[0].v_is_animexts;
+ v_is_netscape = self->private_data.s_decode_ae[0].v_is_netscape;
+ v_is_xmp = self->private_data.s_decode_ae[0].v_is_xmp;
}
switch (coro_susp_point) {
WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
@@ -9698,8 +9981,9 @@
iop_a_src += self->private_data.s_decode_ae[0].scratch;
goto label_0_break;
}
- v_not_animexts = false;
- v_not_netscape = false;
+ v_is_animexts = true;
+ v_is_netscape = true;
+ v_is_xmp = true;
v_block_size = 0;
while (v_block_size < 11) {
{
@@ -9711,10 +9995,11 @@
uint8_t t_1 = *iop_a_src++;
v_c = t_1;
}
- v_not_animexts =
- (v_not_animexts || (v_c != wuffs_gif__animexts1dot0[v_block_size]));
- v_not_netscape =
- (v_not_netscape || (v_c != wuffs_gif__netscape2dot0[v_block_size]));
+ v_is_animexts =
+ (v_is_animexts && (v_c == wuffs_gif__animexts1dot0[v_block_size]));
+ v_is_netscape =
+ (v_is_netscape && (v_c == wuffs_gif__netscape2dot0[v_block_size]));
+ v_is_xmp = (v_is_xmp && (v_c == wuffs_gif__xmpdataxmp[v_block_size]));
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
@@ -9724,88 +10009,108 @@
#pragma GCC diagnostic pop
#endif
}
- if (v_not_animexts && v_not_netscape) {
- goto label_0_break;
- }
- {
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
- if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
- status = wuffs_base__suspension__short_read;
- goto suspend;
- }
- uint8_t t_2 = *iop_a_src++;
- v_block_size = t_2;
- }
- if (v_block_size != 3) {
- self->private_data.s_decode_ae[0].scratch = ((uint32_t)(v_block_size));
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
- if (self->private_data.s_decode_ae[0].scratch >
- ((uint64_t)(io1_a_src - iop_a_src))) {
- self->private_data.s_decode_ae[0].scratch -=
- ((uint64_t)(io1_a_src - iop_a_src));
- iop_a_src = io1_a_src;
- status = wuffs_base__suspension__short_read;
- goto suspend;
- }
- iop_a_src += self->private_data.s_decode_ae[0].scratch;
- goto label_0_break;
- }
- {
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
- if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
- status = wuffs_base__suspension__short_read;
- goto suspend;
- }
- uint8_t t_3 = *iop_a_src++;
- v_c = t_3;
- }
- if (v_c != 1) {
- self->private_data.s_decode_ae[0].scratch = 2;
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
- if (self->private_data.s_decode_ae[0].scratch >
- ((uint64_t)(io1_a_src - iop_a_src))) {
- self->private_data.s_decode_ae[0].scratch -=
- ((uint64_t)(io1_a_src - iop_a_src));
- iop_a_src = io1_a_src;
- status = wuffs_base__suspension__short_read;
- goto suspend;
- }
- iop_a_src += self->private_data.s_decode_ae[0].scratch;
- goto label_0_break;
- }
- {
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
- uint32_t t_4;
- if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
- t_4 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
- iop_a_src += 2;
- } else {
- self->private_data.s_decode_ae[0].scratch = 0;
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
- while (true) {
- if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
- status = wuffs_base__suspension__short_read;
- goto suspend;
- }
- uint64_t* scratch = &self->private_data.s_decode_ae[0].scratch;
- uint32_t num_bits_4 = ((uint32_t)(*scratch >> 56));
- *scratch <<= 8;
- *scratch >>= 8;
- *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_4;
- if (num_bits_4 == 8) {
- t_4 = ((uint32_t)(*scratch));
- break;
- }
- num_bits_4 += 8;
- *scratch |= ((uint64_t)(num_bits_4)) << 56;
+ if (v_is_animexts || v_is_netscape) {
+ {
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
+ if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+ status = wuffs_base__suspension__short_read;
+ goto suspend;
}
+ uint8_t t_2 = *iop_a_src++;
+ v_block_size = t_2;
}
- self->private_impl.f_num_loops = t_4;
- }
- self->private_impl.f_seen_num_loops = true;
- if ((0 < self->private_impl.f_num_loops) &&
- (self->private_impl.f_num_loops <= 65535)) {
- self->private_impl.f_num_loops += 1;
+ if (v_block_size != 3) {
+ self->private_data.s_decode_ae[0].scratch =
+ ((uint32_t)(v_block_size));
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
+ if (self->private_data.s_decode_ae[0].scratch >
+ ((uint64_t)(io1_a_src - iop_a_src))) {
+ self->private_data.s_decode_ae[0].scratch -=
+ ((uint64_t)(io1_a_src - iop_a_src));
+ iop_a_src = io1_a_src;
+ status = wuffs_base__suspension__short_read;
+ goto suspend;
+ }
+ iop_a_src += self->private_data.s_decode_ae[0].scratch;
+ goto label_0_break;
+ }
+ {
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
+ if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+ status = wuffs_base__suspension__short_read;
+ goto suspend;
+ }
+ uint8_t t_3 = *iop_a_src++;
+ v_c = t_3;
+ }
+ if (v_c != 1) {
+ self->private_data.s_decode_ae[0].scratch = 2;
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
+ if (self->private_data.s_decode_ae[0].scratch >
+ ((uint64_t)(io1_a_src - iop_a_src))) {
+ self->private_data.s_decode_ae[0].scratch -=
+ ((uint64_t)(io1_a_src - iop_a_src));
+ iop_a_src = io1_a_src;
+ status = wuffs_base__suspension__short_read;
+ goto suspend;
+ }
+ iop_a_src += self->private_data.s_decode_ae[0].scratch;
+ goto label_0_break;
+ }
+ {
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
+ uint32_t t_4;
+ if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+ t_4 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
+ iop_a_src += 2;
+ } else {
+ self->private_data.s_decode_ae[0].scratch = 0;
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
+ while (true) {
+ if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+ status = wuffs_base__suspension__short_read;
+ goto suspend;
+ }
+ uint64_t* scratch = &self->private_data.s_decode_ae[0].scratch;
+ uint32_t num_bits_4 = ((uint32_t)(*scratch >> 56));
+ *scratch <<= 8;
+ *scratch >>= 8;
+ *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_4;
+ if (num_bits_4 == 8) {
+ t_4 = ((uint32_t)(*scratch));
+ break;
+ }
+ num_bits_4 += 8;
+ *scratch |= ((uint64_t)(num_bits_4)) << 56;
+ }
+ }
+ self->private_impl.f_num_loops = t_4;
+ }
+ self->private_impl.f_seen_num_loops = true;
+ if ((0 < self->private_impl.f_num_loops) &&
+ (self->private_impl.f_num_loops <= 65535)) {
+ self->private_impl.f_num_loops += 1;
+ }
+ } else if (self->private_impl.f_ignore_metadata) {
+ } else if (v_is_xmp && self->private_impl.f_report_metadata_xmp) {
+ while (((uint64_t)(io1_a_src - iop_a_src)) <= 0) {
+ status = wuffs_base__suspension__short_read;
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(10);
+ }
+ self->private_impl.f_metadata_chunk_length_value =
+ (((uint64_t)(wuffs_base__load_u8be(iop_a_src))) + 1);
+ self->private_impl.f_metadata_fourcc_value = 1481461792;
+ self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add(
+ (a_src.private_impl.buf
+ ? wuffs_base__u64__sat_add(
+ a_src.private_impl.buf->meta.pos,
+ ((uint64_t)(iop_a_src -
+ a_src.private_impl.buf->data.ptr)))
+ : 0),
+ self->private_impl.f_metadata_chunk_length_value);
+ self->private_impl.f_call_sequence = 1;
+ status = wuffs_base__warning__metadata_reported;
+ goto ok;
}
goto label_0_break;
}
@@ -9814,7 +10119,7 @@
a_src.private_impl.buf->meta.ri =
((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
}
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10);
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11);
status = wuffs_gif__decoder__skip_blocks(self, a_src);
if (a_src.private_impl.buf) {
iop_a_src =
@@ -9834,8 +10139,9 @@
suspend:
self->private_impl.p_decode_ae[0] = coro_susp_point;
self->private_data.s_decode_ae[0].v_block_size = v_block_size;
- self->private_data.s_decode_ae[0].v_not_animexts = v_not_animexts;
- self->private_data.s_decode_ae[0].v_not_netscape = v_not_netscape;
+ self->private_data.s_decode_ae[0].v_is_animexts = v_is_animexts;
+ self->private_data.s_decode_ae[0].v_is_netscape = v_is_netscape;
+ self->private_data.s_decode_ae[0].v_is_xmp = v_is_xmp;
goto exit;
exit:
diff --git a/script/make-artificial.go b/script/make-artificial.go
index d436b99..40dee66 100644
--- a/script/make-artificial.go
+++ b/script/make-artificial.go
@@ -512,6 +512,7 @@
func stateGif(line string) (stateFunc, error) {
const (
+ cmdB = "bytes "
cmdL = "lzw "
cmdLC = "loopCount "
)
@@ -532,6 +533,18 @@
out = append(out, 0x3B)
return stateGif, nil
+ case strings.HasPrefix(line, cmdB):
+ s := line[len(cmdB):]
+ for s != "" {
+ x, ok := uint32(0), false
+ x, s, ok = parseHex(s)
+ if !ok {
+ break outer
+ }
+ out = append(out, uint8(x))
+ }
+ 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 50b6bd1..46bc404 100644
--- a/std/gif/decode_gif.wuffs
+++ b/std/gif/decode_gif.wuffs
@@ -49,6 +49,8 @@
// Call sequence states:
// - 0: initial state.
+ // - 1: metadata reported; image config decode is in progress.
+ // - 2: metadata finished; image config decode is in progress.
// - 3: image config decoded, including the first frame's bounds, but not
// the first frame's pixels.
// - 4: frame config decoded.
@@ -56,10 +58,16 @@
//
// State transitions:
//
- // - 0 -> 3: via IC
+ // - 0 -> 1: via IC (metadata reported)
+ // - 0 -> 3: via IC (metadata not reported)
// - 0 -> 4: via FC with implicit IC
// - 0 -> 5: via F with implicit IC and FC
//
+ // - 1 -> 2: via AMC
+ //
+ // - 2 -> 1: via IC (metadata reported)
+ // - 2 -> 3: via IC (metadata not reported)
+ //
// - 3 -> 4: via FC
// - 3 -> 5: via F with implicit FC
//
@@ -70,11 +78,18 @@
// - 5 -> 5: via F with implicit FC
//
// Where:
- // - F is decode_frame, implicit means skip_frame
- // - FC is decode_frame_config, implicit means nullptr args.dst
- // - IC is decode_image_config, implicit means nullptr args.dst
+ // - AMC is ack_metadata_chunk
+ // - F is decode_frame, implicit means skip_frame
+ // - FC is decode_frame_config, implicit means nullptr args.dst
+ // - IC is decode_image_config, implicit means nullptr args.dst
call_sequence base.u8,
+ ignore_metadata base.bool,
+ report_metadata_xmp base.bool,
+ metadata_fourcc_value base.u32,
+ metadata_chunk_length_value base.u64,
+ metadata_io_position base.u64,
+
end_of_data base.bool,
restarted base.bool,
previous_lzw_decode_ended_abruptly base.bool,
@@ -128,15 +143,28 @@
)
pub func decoder.decode_image_config?(dst nptr base.image_config, src base.io_reader) {
- var ffio base.bool
+ var ffio base.bool
+ var status base.status // TODO: this shouldn't be necessary.
- if this.call_sequence >= 3 {
+ if this.call_sequence == 0 {
+ this.decode_header?(src:args.src)
+ this.decode_lsd?(src:args.src)
+ } else if this.call_sequence <> 2 {
return base."#bad call sequence"
}
- this.decode_header?(src:args.src)
- this.decode_lsd?(src:args.src)
- this.decode_up_to_id_part1?(src:args.src)
+ // TODO: this should be just "this.decode_up_to_id_part1?(src:args.src)".
+ while true {
+ status =? this.decode_up_to_id_part1?(src:args.src)
+ if status.is_suspension() {
+ yield? status
+ continue
+ }
+ if status.is_ok() {
+ break
+ }
+ return status
+ }
// TODO: if this.end_of_data, return an error and/or set dst to zero?
@@ -161,6 +189,46 @@
this.call_sequence = 3
}
+pub func decoder.set_report_metadata!(fourcc base.u32, report base.bool) {
+ if args.fourcc == 0x584D5020 { // "XMP "
+ this.report_metadata_xmp = args.report
+ }
+}
+
+pub func decoder.ack_metadata_chunk?(src base.io_reader) {
+ if this.call_sequence <> 1 {
+ return base."#bad call sequence"
+ }
+ if args.src.position() <> this.metadata_io_position {
+ return base."#bad I/O position"
+ }
+ while args.src.available() <= 0,
+ post args.src.available() > 0,
+ {
+ yield? base."$short read"
+ }
+ // The +1 is because XMP metadata's encoding includes each block's leading
+ // byte (the block size) as part of the metadata passed to the caller.
+ this.metadata_chunk_length_value = args.src.peek_u8_as_u64() + 1
+ if this.metadata_chunk_length_value > 1 {
+ this.metadata_io_position = args.src.position() ~sat+ this.metadata_chunk_length_value
+ return base."@metadata reported"
+ }
+ args.src.skip_fast!(actual:1, worst_case:1)
+ this.call_sequence = 2
+ this.metadata_fourcc_value = 0
+ this.metadata_io_position = 0
+ return ok
+}
+
+pub func decoder.metadata_fourcc() base.u32 {
+ return this.metadata_fourcc_value
+}
+
+pub func decoder.metadata_chunk_length() base.u64 {
+ return this.metadata_chunk_length_value
+}
+
pub func decoder.num_animation_loops() base.u32 {
if this.seen_num_loops {
return this.num_loops
@@ -212,6 +280,7 @@
pub func decoder.decode_frame_config?(dst nptr base.frame_config, src base.io_reader) {
var blend base.u8
+ this.ignore_metadata = true
this.dirty_y.reset!()
if not this.end_of_data {
@@ -272,6 +341,7 @@
// TODO: honor args.opts.
pub func decoder.decode_frame?(dst ptr base.pixel_buffer, src base.io_reader, workbuf slice base.u8, opts nptr base.decode_frame_options) {
+ this.ignore_metadata = true
if this.call_sequence <> 4 {
this.decode_frame_config?(dst:nullptr, src:args.src)
}
@@ -295,9 +365,12 @@
pri func decoder.decode_up_to_id_part1?(src base.io_reader) {
var block_type base.u8
+ var status base.status // TODO: this shouldn't be necessary.
if not this.restarted {
- this.frame_config_io_position = args.src.position()
+ if this.call_sequence <> 2 {
+ this.frame_config_io_position = args.src.position()
+ }
} else if this.frame_config_io_position <> args.src.position() {
return base."#bad restart"
} else {
@@ -307,7 +380,18 @@
while true {
block_type = args.src.read_u8?()
if block_type == 0x21 { // The spec calls 0x21 the "Extension Introducer".
- this.decode_extension?(src:args.src)
+ // TODO: this should be just "this.decode_extension?(src:args.src)".
+ while true {
+ status =? this.decode_extension?(src:args.src)
+ if status.is_suspension() {
+ yield? status
+ continue
+ }
+ if status.is_ok() {
+ break
+ }
+ return status
+ }
} else if block_type == 0x2C { // The spec calls 0x2C the "Image Separator".
this.decode_id_part0?(src:args.src)
break
@@ -388,14 +472,26 @@
// - section 25 "Plain Text Extension" on page 18.
// - section 26 "Application Extension" on page 21.
pri func decoder.decode_extension?(src base.io_reader) {
- var label base.u8
+ var label base.u8
+ var status base.status // TODO: this shouldn't be necessary.
label = args.src.read_u8?()
if label == 0xF9 { // The spec calls 0xF9 the "Graphic Control Label".
this.decode_gc?(src:args.src)
return ok
} else if label == 0xFF { // The spec calls 0xFF the "Application Extension Label".
- this.decode_ae?(src:args.src)
+ // TODO: this should be just "this.decode_ae?(src:args.src)".
+ while true {
+ status =? this.decode_ae?(src:args.src)
+ if status.is_suspension() {
+ yield? status
+ continue
+ }
+ if status.is_ok() {
+ break
+ }
+ return status
+ }
return ok
}
// We skip over all other extensions, including 0x01 "Plain Text Label" and
@@ -425,12 +521,18 @@
0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2E, 0x30,
]
+// xmpdataxmp is "XMP DataXMP" as bytes.
+pri const xmpdataxmp array[11] base.u8 = [
+ 0x58, 0x4D, 0x50, 0x20, 0x44, 0x61, 0x74, 0x61, 0x58, 0x4D, 0x50,
+]
+
// decode_ae reads an Application Extension.
pri func decoder.decode_ae?(src base.io_reader) {
- var c base.u8
- var block_size base.u8
- var not_animexts base.bool
- var not_netscape base.bool
+ var c base.u8
+ var block_size base.u8
+ var is_animexts base.bool
+ var is_netscape base.bool
+ var is_xmp base.bool
// This "while true" always executes exactly once, as it ends with a
// "break", but using "break"s throughout simplifies the control flow.
@@ -440,54 +542,75 @@
return ok
}
- // Look only for an 11 byte "ANIMEXTS1.0" or "NETSCAPE2.0" extension,
- // as per:
+ // Look only for an 11 byte "ANIMEXTS1.0", "NETSCAPE2.0" or other
+ // extension, as per:
// - http://www.vurdalakov.net/misc/gif/animexts-looping-application-extension
// - http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension
+ //
+ // Other extensions include XMP metadata.
if block_size <> 11 {
args.src.skip?(n:block_size as base.u32)
break
}
- not_animexts = false
- not_netscape = false
+ is_animexts = true
+ is_netscape = true
+ is_xmp = true
block_size = 0 // Re-purpose the block_size variable as a counter.
while block_size < 11 {
c = args.src.read_u8?()
- not_animexts = not_animexts or (c <> animexts1dot0[block_size])
- not_netscape = not_netscape or (c <> netscape2dot0[block_size])
+ is_animexts = is_animexts and (c == animexts1dot0[block_size])
+ is_netscape = is_netscape and (c == netscape2dot0[block_size])
+ is_xmp = is_xmp and (c == xmpdataxmp[block_size])
block_size += 1
}
- if not_animexts and not_netscape {
- break
- }
- // Those 11 bytes should be followed by 0x03, 0x01 and then the loop
- // count.
- block_size = args.src.read_u8?()
- if block_size <> 3 {
- args.src.skip?(n:block_size as base.u32)
- break
- }
- c = args.src.read_u8?()
- if c <> 0x01 {
- args.src.skip?(n:2)
- break
- }
- this.num_loops = args.src.read_u16le_as_u32?()
- this.seen_num_loops = true
+ if is_animexts or is_netscape {
+ // Those 11 bytes should be followed by 0x03, 0x01 and then the loop
+ // count.
+ block_size = args.src.read_u8?()
+ if block_size <> 3 {
+ args.src.skip?(n:block_size as base.u32)
+ break
+ }
+ c = args.src.read_u8?()
+ if c <> 0x01 {
+ args.src.skip?(n:2)
+ break
+ }
+ this.num_loops = args.src.read_u16le_as_u32?()
+ this.seen_num_loops = true
- // A loop count of N, in the wire format, actually means "repeat N
- // times after the first play", if N is positive. A zero N means to
- // loop forever. Playing the frames exactly once is denoted by the
- // *absence* of this NETSCAPE2.0 application extension.
- //
- // For example, if there are four frames: A, B, C, D, and N is 2, then
- // each frame is actually played N+1 or 3 times: ABCDABCDABCD.
- //
- // Thus, we increment N if it is positive. The comparison against
- // 0xFFFF will never fail, but is necessary for the overflow checker.
- if (0 < this.num_loops) and (this.num_loops <= 0xFFFF) {
- this.num_loops += 1
+ // A loop count of N, in the wire format, actually means "repeat N
+ // times after the first play", if N is positive. A zero N means to
+ // loop forever. Playing the frames exactly once is denoted by the
+ // *absence* of this NETSCAPE2.0 application extension.
+ //
+ // For example, if there are four frames: A, B, C, D, and N is 2, then
+ // each frame is actually played N+1 or 3 times: ABCDABCDABCD.
+ //
+ // Thus, we increment N if it is positive. The comparison against
+ // 0xFFFF will never fail, but is necessary for the overflow checker.
+ if (0 < this.num_loops) and (this.num_loops <= 0xFFFF) {
+ this.num_loops += 1
+ }
+
+ } else if this.ignore_metadata {
+ // No-op.
+
+ } else if is_xmp and this.report_metadata_xmp {
+ while args.src.available() <= 0,
+ post args.src.available() > 0,
+ {
+ yield? base."$short read"
+ }
+ // The +1 is because XMP metadata's encoding includes each block's
+ // leading byte (the block size) as part of the metadata passed to
+ // the caller.
+ this.metadata_chunk_length_value = args.src.peek_u8_as_u64() + 1
+ this.metadata_fourcc_value = 0x584D5020 // "XMP "
+ this.metadata_io_position = args.src.position() ~sat+ this.metadata_chunk_length_value
+ this.call_sequence = 1
+ return base."@metadata reported"
}
break
diff --git a/test/c/std/gif.c b/test/c/std/gif.c
index 6ed3277..8b37259 100644
--- a/test/c/std/gif.c
+++ b/test/c/std/gif.c
@@ -930,6 +930,165 @@
return NULL;
}
+const char* test_wuffs_gif_decode_metadata() {
+ 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-metadata.gif");
+ if (status) {
+ return status;
+ }
+
+ int iccp;
+ for (iccp = 0; iccp < 2; iccp++) {
+ int xmp;
+ for (xmp = 0; xmp < 2; xmp++) {
+ bool seen_iccp = false;
+ bool seen_xmp = false;
+
+ 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);
+ }
+
+ if (iccp) {
+ wuffs_gif__decoder__set_report_metadata(&dec, WUFFS_BASE__FOURCC__ICCP,
+ true);
+ }
+ if (xmp) {
+ wuffs_gif__decoder__set_report_metadata(&dec, WUFFS_BASE__FOURCC__XMP,
+ true);
+ }
+
+ wuffs_base__image_config ic = ((wuffs_base__image_config){});
+ src.meta.ri = 0;
+
+ while (true) {
+ status = wuffs_gif__decoder__decode_image_config(
+ &dec, &ic, wuffs_base__io_buffer__reader(&src));
+ if (!status) {
+ break;
+ } else if (status != wuffs_base__warning__metadata_reported) {
+ RETURN_FAIL(
+ "decode_image_config (iccp=%d, xmp=%d): got \"%s\", want \"%s\"",
+ iccp, xmp, status, wuffs_base__warning__metadata_reported);
+ }
+
+ const char* want = "";
+ char got_buffer[100];
+ int got_length = 0;
+ uint32_t got_fourcc = wuffs_gif__decoder__metadata_fourcc(&dec);
+
+ switch (wuffs_gif__decoder__metadata_fourcc(&dec)) {
+ case WUFFS_BASE__FOURCC__ICCP:
+ // TODO: want = "etc";
+ seen_iccp = true;
+ break;
+ case WUFFS_BASE__FOURCC__XMP:
+ want = "\x05\x17\x27\x37\x47\x57\x03\x77\x87\x97";
+ seen_xmp = true;
+ break;
+ default:
+ RETURN_FAIL(
+ "metadata_fourcc (iccp=%d, xmp=%d): unexpected FourCC "
+ "0x%08" PRIX32,
+ iccp, xmp, got_fourcc);
+ }
+
+ while (true) {
+ uint64_t n = wuffs_gif__decoder__metadata_chunk_length(&dec);
+ if ((n > 100) || (n + got_length > 100)) {
+ RETURN_FAIL(
+ "metadata_chunk_length (iccp=%d, xmp=%d): too much "
+ "metadata (vs buffer size)",
+ iccp, xmp);
+ }
+ if (n > wuffs_base__io_buffer__reader_available(&src)) {
+ RETURN_FAIL(
+ "metadata_chunk_length (iccp=%d, xmp=%d): too much "
+ "metadata (vs available)",
+ iccp, xmp);
+ }
+ memcpy(got_buffer + got_length, src.data.ptr + src.meta.ri, n);
+ got_length += n;
+ src.meta.ri += n;
+
+ status = wuffs_gif__decoder__ack_metadata_chunk(
+ &dec, wuffs_base__io_buffer__reader(&src));
+ if (!status) {
+ break;
+ } else if (status != wuffs_base__warning__metadata_reported) {
+ RETURN_FAIL(
+ "ack_metadata_chunk (iccp=%d, xmp=%d): got \"%s\", want \"%s\"",
+ iccp, xmp, status, wuffs_base__warning__metadata_reported);
+ }
+ }
+
+ int want_length = strlen(want);
+ if ((got_length != want_length) ||
+ strncmp(got_buffer, want, want_length)) {
+ RETURN_FAIL("metadata (iccp=%d, xmp=%d): fourcc=0x%08" PRIX32
+ ": values differed",
+ iccp, xmp, got_fourcc);
+ }
+ }
+
+ if (iccp != seen_iccp) {
+ // TODO.
+ }
+
+ if (xmp != seen_xmp) {
+ RETURN_FAIL("seen_xmp (iccp=%d, xmp=%d): got %d, want %d", iccp, xmp,
+ seen_xmp, xmp);
+ }
+
+ {
+ uint64_t got = wuffs_base__image_config__first_frame_io_position(&ic);
+ uint64_t want = 25;
+ if (got != want) {
+ RETURN_FAIL("first_frame_io_position: got %" PRIu64 ", want %" PRIu64,
+ got, want);
+ }
+ }
+
+ {
+ uint32_t got = wuffs_gif__decoder__num_animation_loops(&dec);
+ uint32_t want = 2001;
+ if (got != want) {
+ RETURN_FAIL("num_animation_loops: got %" PRIu32 ", want %" PRIu32,
+ got, want);
+ }
+ }
+
+ {
+ wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
+ status = wuffs_gif__decoder__decode_frame_config(
+ &dec, &fc, wuffs_base__io_buffer__reader(&src));
+ if (status) {
+ RETURN_FAIL("decode_frame_config (iccp=%d, xmp=%d): %s", iccp, xmp,
+ status);
+ }
+ uint32_t got = wuffs_base__frame_config__width(&fc);
+ uint32_t want = 1;
+ if (got != want) {
+ RETURN_FAIL(
+ "decode_frame_config (iccp=%d, xmp=%d): width: got %" PRIu32
+ ", want %" PRIu32,
+ iccp, xmp, got, want);
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
const char* test_wuffs_gif_decode_missing_two_src_bytes() {
CHECK_FOCUS(__func__);
@@ -1711,6 +1870,7 @@
test_wuffs_gif_decode_input_is_a_gif_many_medium_reads, //
test_wuffs_gif_decode_input_is_a_gif_many_small_reads, //
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_loop_counts, //
test_wuffs_gif_decode_pixel_data_none, //
diff --git a/test/data/artificial/gif-metadata.gif b/test/data/artificial/gif-metadata.gif
new file mode 100644
index 0000000..8d01384
--- /dev/null
+++ b/test/data/artificial/gif-metadata.gif
Binary files differ
diff --git a/test/data/artificial/gif-metadata.gif.make-artificial.txt b/test/data/artificial/gif-metadata.gif.make-artificial.txt
new file mode 100644
index 0000000..197f857
--- /dev/null
+++ b/test/data/artificial/gif-metadata.gif.make-artificial.txt
@@ -0,0 +1,54 @@
+# Feed this file to script/make-artificial.go
+
+make gif
+
+header
+
+image {
+ imageWidthHeight 2 2
+ palette {
+ 0x00 0x00 0xFF
+ 0x11 0x00 0xFF
+ 0x22 0x00 0xFF
+ 0x33 0x00 0xFF
+ }
+}
+
+# ICCP metadata.
+#
+# Extension (Application Extension), 11 bytes for AI and AAC.
+bytes 0x21 0xFF 0x0B
+# Application Identifier "ICCRGBG1".
+bytes 0x49 0x43 0x43 0x52 0x47 0x42 0x47 0x31
+# Application Authentication Code "012".
+bytes 0x30 0x31 0x32
+# A block of arbitrary data.
+bytes 0x05 0x16 0x26 0x36 0x46 0x56
+# Another block of arbitrary data.
+bytes 0x03 0x76 0x86 0x96
+# Block Terminator.
+bytes 0x00
+
+# XMP metadata.
+#
+# Extension (Application Extension), 11 bytes for AI and AAC.
+bytes 0x21 0xFF 0x0B
+# Application Identifier "XMP Data".
+bytes 0x58 0x4D 0x50 0x20 0x44 0x61 0x74 0x61
+# Application Authentication Code "XMP".
+bytes 0x58 0x4D 0x50
+# A block of arbitrary data.
+bytes 0x05 0x17 0x27 0x37 0x47 0x57
+# Another block of arbitrary data.
+bytes 0x03 0x77 0x87 0x97
+# Block Terminator.
+bytes 0x00
+
+loopCount 2000
+
+frame {
+ frameLeftTopWidthHeight 1 0 1 1
+}
+lzw 4 0x01
+
+trailer