Let GIF decode an opaque but non-black background
diff --git a/example/gifplayer/gifplayer.c b/example/gifplayer/gifplayer.c
index 74ff1be..84926a2 100644
--- a/example/gifplayer/gifplayer.c
+++ b/example/gifplayer/gifplayer.c
@@ -100,6 +100,7 @@
wuffs_base__slice_u8 printbuf = {0};
bool first_play = true;
+wuffs_base__color_u32_argb_premul background_color = 0;
uint32_t num_loops_remaining = 0;
wuffs_base__pixel_buffer pb = {0};
@@ -130,6 +131,7 @@
const char* reset_color = "\x1B[0m";
bool color_flag = false;
+bool quirk_background_is_opaque_flag = false;
const int stdout_fd = 1;
static inline uint32_t load_u32le(uint8_t* p) {
@@ -146,7 +148,7 @@
wuffs_base__color_u32_argb_premul* d =
curr_dst_buffer + (y * width) + bounds.min_incl_x;
for (x = bounds.min_incl_x; x < bounds.max_excl_x; x++) {
- *d++ = 0;
+ *d++ = background_color;
}
}
}
@@ -296,6 +298,11 @@
return status;
}
+ if (quirk_background_is_opaque_flag) {
+ wuffs_gif__decoder__set_quirk_enabled(
+ &dec, wuffs_gif__quirk_background_is_opaque, true);
+ }
+
wuffs_base__io_buffer src;
src.data.ptr = src_buffer;
src.data.len = src_len;
@@ -316,6 +323,7 @@
}
uint32_t width = wuffs_base__pixel_config__width(&ic.pixcfg);
uint32_t height = wuffs_base__pixel_config__height(&ic.pixcfg);
+ background_color = wuffs_base__image_config__background_color(&ic);
if ((width > MAX_DIMENSION) || (height > MAX_DIMENSION)) {
return "image dimensions are too large";
}
@@ -334,7 +342,14 @@
return status;
}
memset(pixbuf.ptr, 0, pixbuf.len);
- memset(curr_dst_buffer, 0, dst_len);
+ }
+
+ {
+ size_t i;
+ size_t n = dst_len / sizeof(wuffs_base__color_u32_argb_premul);
+ for (i = 0; i < n; i++) {
+ curr_dst_buffer[i] = background_color;
+ }
}
while (1) {
@@ -423,6 +438,9 @@
if (!strcmp(argv[i], "-color")) {
color_flag = true;
}
+ if (!strcmp(argv[i], "-quirk_background_is_opaque")) {
+ quirk_background_is_opaque_flag = true;
+ }
}
const char* status = read_stdin();
diff --git a/internal/cgen/base/image-public.h b/internal/cgen/base/image-public.h
index ec65b09..948075e 100644
--- a/internal/cgen/base/image-public.h
+++ b/internal/cgen/base/image-public.h
@@ -538,6 +538,7 @@
struct {
uint64_t first_frame_io_position;
bool first_frame_is_opaque;
+ wuffs_base__color_u32_argb_premul background_color;
} private_impl;
#ifdef __cplusplus
@@ -546,11 +547,13 @@
uint32_t width,
uint32_t height,
uint64_t first_frame_io_position,
- bool first_frame_is_opaque);
+ bool first_frame_is_opaque,
+ wuffs_base__color_u32_argb_premul background_color);
inline void invalidate();
inline bool is_valid() const;
inline uint64_t first_frame_io_position() const;
inline bool first_frame_is_opaque() const;
+ inline wuffs_base__color_u32_argb_premul background_color() const;
#endif // __cplusplus
} wuffs_base__image_config;
@@ -566,13 +569,15 @@
// TODO: Should this function return bool? An error type?
static inline void //
-wuffs_base__image_config__set(wuffs_base__image_config* c,
- wuffs_base__pixel_format pixfmt,
- wuffs_base__pixel_subsampling pixsub,
- uint32_t width,
- uint32_t height,
- uint64_t first_frame_io_position,
- bool first_frame_is_opaque) {
+wuffs_base__image_config__set(
+ wuffs_base__image_config* c,
+ wuffs_base__pixel_format pixfmt,
+ wuffs_base__pixel_subsampling pixsub,
+ uint32_t width,
+ uint32_t height,
+ uint64_t first_frame_io_position,
+ bool first_frame_is_opaque,
+ wuffs_base__color_u32_argb_premul background_color) {
if (!c) {
return;
}
@@ -583,6 +588,7 @@
c->pixcfg.private_impl.height = height;
c->private_impl.first_frame_io_position = first_frame_io_position;
c->private_impl.first_frame_is_opaque = first_frame_is_opaque;
+ c->private_impl.background_color = background_color;
return;
}
@@ -592,6 +598,7 @@
c->pixcfg.private_impl.height = 0;
c->private_impl.first_frame_io_position = 0;
c->private_impl.first_frame_is_opaque = 0;
+ c->private_impl.background_color = 0;
}
static inline void //
@@ -603,6 +610,7 @@
c->pixcfg.private_impl.height = 0;
c->private_impl.first_frame_io_position = 0;
c->private_impl.first_frame_is_opaque = 0;
+ c->private_impl.background_color = 0;
}
}
@@ -623,17 +631,25 @@
return c ? c->private_impl.first_frame_is_opaque : false;
}
+static inline wuffs_base__color_u32_argb_premul //
+wuffs_base__image_config__background_color(const wuffs_base__image_config* c) {
+ return c ? c->private_impl.background_color : 0;
+}
+
#ifdef __cplusplus
inline void //
-wuffs_base__image_config::set(wuffs_base__pixel_format pixfmt,
- wuffs_base__pixel_subsampling pixsub,
- uint32_t width,
- uint32_t height,
- uint64_t first_frame_io_position,
- bool first_frame_is_opaque) {
+wuffs_base__image_config::set(
+ wuffs_base__pixel_format pixfmt,
+ wuffs_base__pixel_subsampling pixsub,
+ uint32_t width,
+ uint32_t height,
+ uint64_t first_frame_io_position,
+ bool first_frame_is_opaque,
+ wuffs_base__color_u32_argb_premul background_color) {
wuffs_base__image_config__set(this, pixfmt, pixsub, width, height,
- first_frame_io_position, first_frame_is_opaque);
+ first_frame_io_position, first_frame_is_opaque,
+ background_color);
}
inline void //
@@ -656,6 +672,11 @@
return wuffs_base__image_config__first_frame_is_opaque(this);
}
+inline wuffs_base__color_u32_argb_premul //
+wuffs_base__image_config::background_color() const {
+ return wuffs_base__image_config__background_color(this);
+}
+
#endif // __cplusplus
// --------
diff --git a/internal/cgen/data.go b/internal/cgen/data.go
index 832e20c..f05413e 100644
--- a/internal/cgen/data.go
+++ b/internal/cgen/data.go
@@ -126,11 +126,11 @@
"((bits_per_pixel % 8) != 0)) {\n // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel?\n return 0;\n }\n uint64_t bytes_per_pixel = bits_per_pixel / 8;\n\n uint64_t n =\n ((uint64_t)c->private_impl.width) * ((uint64_t)c->private_impl.height);\n if (n > (UINT64_MAX / bytes_per_pixel)) {\n return 0;\n }\n n *= bytes_per_pixel;\n\n if (wuffs_base__pixel_format__is_indexed(c->private_impl.pixfmt)) {\n if (n > (UINT64_MAX - 1024)) {\n return 0;\n }\n n += 1024;\n }\n\n return n;\n}\n\n#ifdef __cplusplus\n\ninline void //\nwuffs_base__pixel_config::set(wuffs_base__pixel_format pixfmt,\n wuffs_base__pixel_subsampling pixsub,\n uint32_t width,\n uint32_t height) {\n wuffs_base__pixel_config__set(this, pixfmt, pixsub, width, height);\n}\n\ninline void //\nwuffs_base__pixel_config::invalidate() {\n wuffs_base__pixel_config__invalidate(this);\n}\n\ninline bool //\nwuffs_base__pixel_config::is_valid() const {\n return wuff" +
"s_base__pixel_config__is_valid(this);\n}\n\ninline wuffs_base__pixel_format //\nwuffs_base__pixel_config::pixel_format() const {\n return wuffs_base__pixel_config__pixel_format(this);\n}\n\ninline wuffs_base__pixel_subsampling //\nwuffs_base__pixel_config::pixel_subsampling() const {\n return wuffs_base__pixel_config__pixel_subsampling(this);\n}\n\ninline wuffs_base__rect_ie_u32 //\nwuffs_base__pixel_config::bounds() const {\n return wuffs_base__pixel_config__bounds(this);\n}\n\ninline uint32_t //\nwuffs_base__pixel_config::width() const {\n return wuffs_base__pixel_config__width(this);\n}\n\ninline uint32_t //\nwuffs_base__pixel_config::height() const {\n return wuffs_base__pixel_config__height(this);\n}\n\ninline uint64_t //\nwuffs_base__pixel_config::pixbuf_len() const {\n return wuffs_base__pixel_config__pixbuf_len(this);\n}\n\n#endif // __cplusplus\n\n" +
"" +
- "// --------\n\ntypedef struct {\n wuffs_base__pixel_config pixcfg;\n\n // Do not access the private_impl's fields directly. There is no API/ABI\n // compatibility or safety guarantee if you do so.\n struct {\n uint64_t first_frame_io_position;\n bool first_frame_is_opaque;\n } private_impl;\n\n#ifdef __cplusplus\n inline void set(wuffs_base__pixel_format pixfmt,\n wuffs_base__pixel_subsampling pixsub,\n uint32_t width,\n uint32_t height,\n uint64_t first_frame_io_position,\n bool first_frame_is_opaque);\n inline void invalidate();\n inline bool is_valid() const;\n inline uint64_t first_frame_io_position() const;\n inline bool first_frame_is_opaque() const;\n#endif // __cplusplus\n\n} wuffs_base__image_config;\n\nstatic inline wuffs_base__image_config //\nwuffs_base__null_image_config() {\n wuffs_base__image_config ret;\n ret.pixcfg = wuffs_base__null_pixel_config();\n ret.private_impl.first_frame_io_position = 0;\n ret.private_impl.fir" +
- "st_frame_is_opaque = false;\n return ret;\n}\n\n// TODO: Should this function return bool? An error type?\nstatic inline void //\nwuffs_base__image_config__set(wuffs_base__image_config* c,\n wuffs_base__pixel_format pixfmt,\n wuffs_base__pixel_subsampling pixsub,\n uint32_t width,\n uint32_t height,\n uint64_t first_frame_io_position,\n bool first_frame_is_opaque) {\n if (!c) {\n return;\n }\n if (wuffs_base__pixel_format__is_valid(pixfmt)) {\n c->pixcfg.private_impl.pixfmt = pixfmt;\n c->pixcfg.private_impl.pixsub = pixsub;\n c->pixcfg.private_impl.width = width;\n c->pixcfg.private_impl.height = height;\n c->private_impl.first_frame_io_position = first_frame_io_position;\n c->private_impl.first_frame_is_opaque = first_frame_is_opaque;\n return;\n }\n\n c->pixcfg.private_impl.pixfmt = 0;\n c->pixcfg.private_impl.pixsub = 0;\n c->pix" +
- "cfg.private_impl.width = 0;\n c->pixcfg.private_impl.height = 0;\n c->private_impl.first_frame_io_position = 0;\n c->private_impl.first_frame_is_opaque = 0;\n}\n\nstatic inline void //\nwuffs_base__image_config__invalidate(wuffs_base__image_config* c) {\n if (c) {\n c->pixcfg.private_impl.pixfmt = 0;\n c->pixcfg.private_impl.pixsub = 0;\n c->pixcfg.private_impl.width = 0;\n c->pixcfg.private_impl.height = 0;\n c->private_impl.first_frame_io_position = 0;\n c->private_impl.first_frame_is_opaque = 0;\n }\n}\n\nstatic inline bool //\nwuffs_base__image_config__is_valid(const wuffs_base__image_config* c) {\n return c && wuffs_base__pixel_config__is_valid(&(c->pixcfg));\n}\n\nstatic inline uint64_t //\nwuffs_base__image_config__first_frame_io_position(\n const wuffs_base__image_config* c) {\n return c ? c->private_impl.first_frame_io_position : 0;\n}\n\nstatic inline bool //\nwuffs_base__image_config__first_frame_is_opaque(\n const wuffs_base__image_config* c) {\n return c ? c->private_impl.first_frame_is_opaqu" +
- "e : false;\n}\n\n#ifdef __cplusplus\n\ninline void //\nwuffs_base__image_config::set(wuffs_base__pixel_format pixfmt,\n wuffs_base__pixel_subsampling pixsub,\n uint32_t width,\n uint32_t height,\n uint64_t first_frame_io_position,\n bool first_frame_is_opaque) {\n wuffs_base__image_config__set(this, pixfmt, pixsub, width, height,\n first_frame_io_position, first_frame_is_opaque);\n}\n\ninline void //\nwuffs_base__image_config::invalidate() {\n wuffs_base__image_config__invalidate(this);\n}\n\ninline bool //\nwuffs_base__image_config::is_valid() const {\n return wuffs_base__image_config__is_valid(this);\n}\n\ninline uint64_t //\nwuffs_base__image_config::first_frame_io_position() const {\n return wuffs_base__image_config__first_frame_io_position(this);\n}\n\ninline bool //\nwuffs_base__image_config::first_frame_is_opaque() const {\n return wuffs_base__image_co" +
- "nfig__first_frame_is_opaque(this);\n}\n\n#endif // __cplusplus\n\n" +
+ "// --------\n\ntypedef struct {\n wuffs_base__pixel_config pixcfg;\n\n // Do not access the private_impl's fields directly. There is no API/ABI\n // compatibility or safety guarantee if you do so.\n struct {\n uint64_t first_frame_io_position;\n bool first_frame_is_opaque;\n wuffs_base__color_u32_argb_premul background_color;\n } private_impl;\n\n#ifdef __cplusplus\n inline void set(wuffs_base__pixel_format pixfmt,\n wuffs_base__pixel_subsampling pixsub,\n uint32_t width,\n uint32_t height,\n uint64_t first_frame_io_position,\n bool first_frame_is_opaque,\n wuffs_base__color_u32_argb_premul background_color);\n inline void invalidate();\n inline bool is_valid() const;\n inline uint64_t first_frame_io_position() const;\n inline bool first_frame_is_opaque() const;\n inline wuffs_base__color_u32_argb_premul background_color() const;\n#endif // __cplusplus\n\n} wuffs_base__image_config;\n\nstatic inline wuffs_base__image_" +
+ "config //\nwuffs_base__null_image_config() {\n wuffs_base__image_config ret;\n ret.pixcfg = wuffs_base__null_pixel_config();\n ret.private_impl.first_frame_io_position = 0;\n ret.private_impl.first_frame_is_opaque = false;\n return ret;\n}\n\n// TODO: Should this function return bool? An error type?\nstatic inline void //\nwuffs_base__image_config__set(\n wuffs_base__image_config* c,\n wuffs_base__pixel_format pixfmt,\n wuffs_base__pixel_subsampling pixsub,\n uint32_t width,\n uint32_t height,\n uint64_t first_frame_io_position,\n bool first_frame_is_opaque,\n wuffs_base__color_u32_argb_premul background_color) {\n if (!c) {\n return;\n }\n if (wuffs_base__pixel_format__is_valid(pixfmt)) {\n c->pixcfg.private_impl.pixfmt = pixfmt;\n c->pixcfg.private_impl.pixsub = pixsub;\n c->pixcfg.private_impl.width = width;\n c->pixcfg.private_impl.height = height;\n c->private_impl.first_frame_io_position = first_frame_io_position;\n c->private_impl.first_frame_is_opaque = first_frame_is_opaque;" +
+ "\n c->private_impl.background_color = background_color;\n return;\n }\n\n c->pixcfg.private_impl.pixfmt = 0;\n c->pixcfg.private_impl.pixsub = 0;\n c->pixcfg.private_impl.width = 0;\n c->pixcfg.private_impl.height = 0;\n c->private_impl.first_frame_io_position = 0;\n c->private_impl.first_frame_is_opaque = 0;\n c->private_impl.background_color = 0;\n}\n\nstatic inline void //\nwuffs_base__image_config__invalidate(wuffs_base__image_config* c) {\n if (c) {\n c->pixcfg.private_impl.pixfmt = 0;\n c->pixcfg.private_impl.pixsub = 0;\n c->pixcfg.private_impl.width = 0;\n c->pixcfg.private_impl.height = 0;\n c->private_impl.first_frame_io_position = 0;\n c->private_impl.first_frame_is_opaque = 0;\n c->private_impl.background_color = 0;\n }\n}\n\nstatic inline bool //\nwuffs_base__image_config__is_valid(const wuffs_base__image_config* c) {\n return c && wuffs_base__pixel_config__is_valid(&(c->pixcfg));\n}\n\nstatic inline uint64_t //\nwuffs_base__image_config__first_frame_io_position(\n const wuffs_base__ima" +
+ "ge_config* c) {\n return c ? c->private_impl.first_frame_io_position : 0;\n}\n\nstatic inline bool //\nwuffs_base__image_config__first_frame_is_opaque(\n const wuffs_base__image_config* c) {\n return c ? c->private_impl.first_frame_is_opaque : false;\n}\n\nstatic inline wuffs_base__color_u32_argb_premul //\nwuffs_base__image_config__background_color(const wuffs_base__image_config* c) {\n return c ? c->private_impl.background_color : 0;\n}\n\n#ifdef __cplusplus\n\ninline void //\nwuffs_base__image_config::set(\n wuffs_base__pixel_format pixfmt,\n wuffs_base__pixel_subsampling pixsub,\n uint32_t width,\n uint32_t height,\n uint64_t first_frame_io_position,\n bool first_frame_is_opaque,\n wuffs_base__color_u32_argb_premul background_color) {\n wuffs_base__image_config__set(this, pixfmt, pixsub, width, height,\n first_frame_io_position, first_frame_is_opaque,\n background_color);\n}\n\ninline void //\nwuffs_base__image_config::invalidate() {\n wuffs_ba" +
+ "se__image_config__invalidate(this);\n}\n\ninline bool //\nwuffs_base__image_config::is_valid() const {\n return wuffs_base__image_config__is_valid(this);\n}\n\ninline uint64_t //\nwuffs_base__image_config::first_frame_io_position() const {\n return wuffs_base__image_config__first_frame_io_position(this);\n}\n\ninline bool //\nwuffs_base__image_config::first_frame_is_opaque() const {\n return wuffs_base__image_config__first_frame_is_opaque(this);\n}\n\ninline wuffs_base__color_u32_argb_premul //\nwuffs_base__image_config::background_color() const {\n return wuffs_base__image_config__background_color(this);\n}\n\n#endif // __cplusplus\n\n" +
"" +
"// --------\n\n// wuffs_base__animation_blend encodes, for an animated image, how to blend the\n// transparent pixels of this frame with the existing canvas. In Porter-Duff\n// compositing operator terminology:\n// - 0 means the frame may be transparent, and should be blended \"src over\n// dst\", also known as just \"over\".\n// - 1 means the frame may be transparent, and should be blended \"src\".\n// - 2 means the frame is completely opaque, so that \"src over dst\" and \"src\"\n// are equivalent.\n//\n// These semantics are conservative. It is valid for a completely opaque frame\n// to have a blend value other than 2.\ntypedef uint8_t wuffs_base__animation_blend;\n\n#define WUFFS_BASE__ANIMATION_BLEND__SRC_OVER_DST \\\n ((wuffs_base__animation_blend)0)\n#define WUFFS_BASE__ANIMATION_BLEND__SRC ((wuffs_base__animation_blend)1)\n#define WUFFS_BASE__ANIMATION_BLEND__OPAQUE ((wuffs_base__animation_blend)2)\n\n" +
"" +
diff --git a/lang/builtin/builtin.go b/lang/builtin/builtin.go
index 8ec2a1d..96f7a62 100644
--- a/lang/builtin/builtin.go
+++ b/lang/builtin/builtin.go
@@ -312,7 +312,7 @@
// ---- image_config
"image_config.set!(pixfmt u32, pixsub u32, width u32, height u32, " +
- "first_frame_io_position u64, first_frame_is_opaque bool)",
+ "first_frame_io_position u64, first_frame_is_opaque bool, background_color u32)",
// ---- pixel_buffer
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 71fbd24..5b40d60 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -2068,6 +2068,7 @@
struct {
uint64_t first_frame_io_position;
bool first_frame_is_opaque;
+ wuffs_base__color_u32_argb_premul background_color;
} private_impl;
#ifdef __cplusplus
@@ -2076,11 +2077,13 @@
uint32_t width,
uint32_t height,
uint64_t first_frame_io_position,
- bool first_frame_is_opaque);
+ bool first_frame_is_opaque,
+ wuffs_base__color_u32_argb_premul background_color);
inline void invalidate();
inline bool is_valid() const;
inline uint64_t first_frame_io_position() const;
inline bool first_frame_is_opaque() const;
+ inline wuffs_base__color_u32_argb_premul background_color() const;
#endif // __cplusplus
} wuffs_base__image_config;
@@ -2096,13 +2099,15 @@
// TODO: Should this function return bool? An error type?
static inline void //
-wuffs_base__image_config__set(wuffs_base__image_config* c,
- wuffs_base__pixel_format pixfmt,
- wuffs_base__pixel_subsampling pixsub,
- uint32_t width,
- uint32_t height,
- uint64_t first_frame_io_position,
- bool first_frame_is_opaque) {
+wuffs_base__image_config__set(
+ wuffs_base__image_config* c,
+ wuffs_base__pixel_format pixfmt,
+ wuffs_base__pixel_subsampling pixsub,
+ uint32_t width,
+ uint32_t height,
+ uint64_t first_frame_io_position,
+ bool first_frame_is_opaque,
+ wuffs_base__color_u32_argb_premul background_color) {
if (!c) {
return;
}
@@ -2113,6 +2118,7 @@
c->pixcfg.private_impl.height = height;
c->private_impl.first_frame_io_position = first_frame_io_position;
c->private_impl.first_frame_is_opaque = first_frame_is_opaque;
+ c->private_impl.background_color = background_color;
return;
}
@@ -2122,6 +2128,7 @@
c->pixcfg.private_impl.height = 0;
c->private_impl.first_frame_io_position = 0;
c->private_impl.first_frame_is_opaque = 0;
+ c->private_impl.background_color = 0;
}
static inline void //
@@ -2133,6 +2140,7 @@
c->pixcfg.private_impl.height = 0;
c->private_impl.first_frame_io_position = 0;
c->private_impl.first_frame_is_opaque = 0;
+ c->private_impl.background_color = 0;
}
}
@@ -2153,17 +2161,25 @@
return c ? c->private_impl.first_frame_is_opaque : false;
}
+static inline wuffs_base__color_u32_argb_premul //
+wuffs_base__image_config__background_color(const wuffs_base__image_config* c) {
+ return c ? c->private_impl.background_color : 0;
+}
+
#ifdef __cplusplus
inline void //
-wuffs_base__image_config::set(wuffs_base__pixel_format pixfmt,
- wuffs_base__pixel_subsampling pixsub,
- uint32_t width,
- uint32_t height,
- uint64_t first_frame_io_position,
- bool first_frame_is_opaque) {
+wuffs_base__image_config::set(
+ wuffs_base__pixel_format pixfmt,
+ wuffs_base__pixel_subsampling pixsub,
+ uint32_t width,
+ uint32_t height,
+ uint64_t first_frame_io_position,
+ bool first_frame_is_opaque,
+ wuffs_base__color_u32_argb_premul background_color) {
wuffs_base__image_config__set(this, pixfmt, pixsub, width, height,
- first_frame_io_position, first_frame_is_opaque);
+ first_frame_io_position, first_frame_is_opaque,
+ background_color);
}
inline void //
@@ -2186,6 +2202,11 @@
return wuffs_base__image_config__first_frame_is_opaque(this);
}
+inline wuffs_base__color_u32_argb_premul //
+wuffs_base__image_config::background_color() const {
+ return wuffs_base__image_config__background_color(this);
+}
+
#endif // __cplusplus
// --------
@@ -3290,10 +3311,10 @@
wuffs_gif__quirk_image_bounds_are_strict //
WUFFS_BASE__POTENTIALLY_UNUSED = 1041635330;
-#define WUFFS_GIF__QUIRK_INITIAL_BACKGROUND_IS_OPAQUE 1041635331
+#define WUFFS_GIF__QUIRK_BACKGROUND_IS_OPAQUE 1041635331
-static const uint32_t //
- wuffs_gif__quirk_initial_background_is_opaque //
+static const uint32_t //
+ wuffs_gif__quirk_background_is_opaque //
WUFFS_BASE__POTENTIALLY_UNUSED = 1041635331;
#define WUFFS_GIF__QUIRK_REJECT_EMPTY_PALETTE 1041635332
@@ -3416,7 +3437,7 @@
uint64_t f_metadata_io_position;
bool f_quirk_enabled_ignore_too_much_pixel_data;
bool f_quirk_enabled_image_bounds_are_strict;
- bool f_quirk_enabled_initial_background_is_opaque;
+ bool f_quirk_enabled_background_is_opaque;
bool f_quirk_enabled_reject_empty_palette;
bool f_end_of_data;
bool f_restarted;
@@ -3425,6 +3446,7 @@
uint8_t f_interlace;
bool f_seen_num_loops;
uint32_t f_num_loops;
+ uint32_t f_background_color_u32_argb_premul;
bool f_gc_has_transparent_index;
uint8_t f_gc_transparent_index;
uint8_t f_gc_disposal;
@@ -3475,6 +3497,7 @@
} s_decode_header[1];
struct {
uint8_t v_flags;
+ uint8_t v_background_color_index;
uint32_t v_num_palette_entries;
uint32_t v_i;
uint64_t scratch;
@@ -8713,7 +8736,7 @@
} else if (a_quirk == 1041635330) {
self->private_impl.f_quirk_enabled_image_bounds_are_strict = a_enabled;
} else if (a_quirk == 1041635331) {
- self->private_impl.f_quirk_enabled_initial_background_is_opaque = a_enabled;
+ self->private_impl.f_quirk_enabled_background_is_opaque = a_enabled;
} else if (a_quirk == 1041635332) {
self->private_impl.f_quirk_enabled_reject_empty_palette = a_enabled;
}
@@ -8771,7 +8794,7 @@
goto suspend;
}
v_ffio = !self->private_impl.f_gc_has_transparent_index;
- if (!self->private_impl.f_quirk_enabled_initial_background_is_opaque) {
+ if (!self->private_impl.f_quirk_enabled_background_is_opaque) {
v_ffio =
(v_ffio && (self->private_impl.f_frame_rect_x0 == 0) &&
(self->private_impl.f_frame_rect_y0 == 0) &&
@@ -8782,7 +8805,8 @@
wuffs_base__image_config__set(
a_dst, 1191444488, 0, self->private_impl.f_width,
self->private_impl.f_height,
- self->private_impl.f_frame_config_io_position, v_ffio);
+ self->private_impl.f_frame_config_io_position, v_ffio,
+ self->private_impl.f_background_color_u32_argb_premul);
}
self->private_impl.f_call_sequence = 3;
@@ -9586,8 +9610,10 @@
wuffs_base__status status = NULL;
uint8_t v_flags = 0;
+ uint8_t v_background_color_index = 0;
uint32_t v_num_palette_entries = 0;
uint32_t v_i = 0;
+ uint32_t v_j = 0;
uint32_t v_argb = 0;
uint8_t* iop_a_src = NULL;
@@ -9608,6 +9634,8 @@
uint32_t coro_susp_point = self->private_impl.p_decode_lsd[0];
if (coro_susp_point) {
v_flags = self->private_data.s_decode_lsd[0].v_flags;
+ v_background_color_index =
+ self->private_data.s_decode_lsd[0].v_background_color_index;
v_num_palette_entries =
self->private_data.s_decode_lsd[0].v_num_palette_entries;
v_i = self->private_data.s_decode_lsd[0].v_i;
@@ -9682,50 +9710,54 @@
uint8_t t_2 = *iop_a_src++;
v_flags = t_2;
}
- self->private_data.s_decode_lsd[0].scratch = 2;
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
- if (self->private_data.s_decode_lsd[0].scratch >
- ((uint64_t)(io1_a_src - iop_a_src))) {
- self->private_data.s_decode_lsd[0].scratch -=
- ((uint64_t)(io1_a_src - iop_a_src));
- iop_a_src = io1_a_src;
+ {
+ 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_background_color_index = t_3;
+ }
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
+ if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
status = wuffs_base__suspension__short_read;
goto suspend;
}
- iop_a_src += self->private_data.s_decode_lsd[0].scratch;
+ iop_a_src++;
v_i = 0;
self->private_impl.f_has_global_palette = ((v_flags & 128) != 0);
if (self->private_impl.f_has_global_palette) {
v_num_palette_entries = (((uint32_t)(1)) << (1 + (v_flags & 7)));
while (v_i < v_num_palette_entries) {
{
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
- uint32_t t_3;
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
+ uint32_t t_4;
if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 3)) {
- t_3 = ((uint32_t)(wuffs_base__load_u24be(iop_a_src)));
+ t_4 = ((uint32_t)(wuffs_base__load_u24be(iop_a_src)));
iop_a_src += 3;
} else {
self->private_data.s_decode_lsd[0].scratch = 0;
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
+ 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_lsd[0].scratch;
- uint32_t num_bits_3 = ((uint32_t)(*scratch & 0xFF));
+ uint32_t num_bits_4 = ((uint32_t)(*scratch & 0xFF));
*scratch >>= 8;
*scratch <<= 8;
- *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_3);
- if (num_bits_3 == 16) {
- t_3 = ((uint32_t)(*scratch >> 40));
+ *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_4);
+ if (num_bits_4 == 16) {
+ t_4 = ((uint32_t)(*scratch >> 40));
break;
}
- num_bits_3 += 8;
- *scratch |= ((uint64_t)(num_bits_3));
+ num_bits_4 += 8;
+ *scratch |= ((uint64_t)(num_bits_4));
}
}
- v_argb = t_3;
+ v_argb = t_4;
}
v_argb |= 4278190080;
self->private_data.f_palettes[0][((4 * v_i) + 0)] =
@@ -9738,6 +9770,23 @@
((uint8_t)(((v_argb >> 24) & 255)));
v_i += 1;
}
+ if (self->private_impl.f_quirk_enabled_background_is_opaque) {
+ if ((v_background_color_index != 0) &&
+ (((uint32_t)(v_background_color_index)) < v_num_palette_entries)) {
+ v_j = (4 * ((uint32_t)(v_background_color_index)));
+ self->private_impl.f_background_color_u32_argb_premul =
+ ((((uint32_t)(self->private_data.f_palettes[0][(v_j + 0)]))
+ << 0) |
+ (((uint32_t)(self->private_data.f_palettes[0][(v_j + 1)]))
+ << 8) |
+ (((uint32_t)(self->private_data.f_palettes[0][(v_j + 2)]))
+ << 16) |
+ (((uint32_t)(self->private_data.f_palettes[0][(v_j + 3)]))
+ << 24));
+ } else {
+ self->private_impl.f_background_color_u32_argb_premul = 4278190080;
+ }
+ }
}
while (v_i < 256) {
self->private_data.f_palettes[0][((4 * v_i) + 0)] = 0;
@@ -9758,6 +9807,8 @@
self->private_impl.p_decode_lsd[0] =
wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
self->private_data.s_decode_lsd[0].v_flags = v_flags;
+ self->private_data.s_decode_lsd[0].v_background_color_index =
+ v_background_color_index;
self->private_data.s_decode_lsd[0].v_num_palette_entries =
v_num_palette_entries;
self->private_data.s_decode_lsd[0].v_i = v_i;
diff --git a/script/make-artificial.go b/script/make-artificial.go
index 4c65088..a8c74c6 100644
--- a/script/make-artificial.go
+++ b/script/make-artificial.go
@@ -499,8 +499,9 @@
}
var gifGlobals struct {
- imageWidth uint32
- imageHeight uint32
+ imageWidth uint32
+ imageHeight uint32
+ imageBackgroundColorIndex uint32
frameLeft uint32
frameTop uint32
@@ -646,7 +647,7 @@
} else {
out = append(out, 0x80|uint8(n-1))
}
- out = append(out, 0x00) // TODO: background index.
+ out = append(out, uint8(g.imageBackgroundColorIndex))
out = append(out, 0x00)
for _, x := range g.globalPalette {
out = append(out, x[0], x[1], x[2])
@@ -655,10 +656,18 @@
}
const (
+ cmdBCI = "backgroundColorIndex "
cmdIWH = "imageWidthHeight "
cmdP = "palette {"
)
switch {
+ case strings.HasPrefix(line, cmdBCI):
+ s := line[len(cmdBCI):]
+ if i, _, ok := parseNum(s); ok {
+ g.imageBackgroundColorIndex = i
+ }
+ return stateGifImage, nil
+
case strings.HasPrefix(line, cmdIWH):
s := line[len(cmdIWH):]
if w, s, ok := parseNum(s); ok {
diff --git a/std/gif/decode_gif.wuffs b/std/gif/decode_gif.wuffs
index dee425c..3298f3b 100644
--- a/std/gif/decode_gif.wuffs
+++ b/std/gif/decode_gif.wuffs
@@ -52,10 +52,18 @@
// https://github.com/google/wuffs/blob/master/test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt
pub const quirk_image_bounds_are_strict base.u32 = (0xF8586 << 10) | 2
-// When this quirk is enabled, if the initial frame bounds is smaller than the
-// image bounds, those pixels outside the initial frame bounds are assumed to
-// start as opaque black instead of transparent black.
-pub const quirk_initial_background_is_opaque base.u32 = (0xF8586 << 10) | 3
+// When this quirk is enabled, the background color is opaque instead of always
+// being transparent black. If the background color index in the GIF header is
+// non-zero but less than the global palette's size, the background color is
+// that global palette's entry. Otherwise, it is opaque black.
+//
+// Specifically, if the initial frame bounds is smaller than the image bounds,
+// those pixels outside the initial frame bounds are assumed to start as that
+// opaque background color. The same color should be used when processing
+// WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND. In both cases, the
+// caller of Wuffs, not Wuffs itself, is responsible for filling the pixel
+// buffer with that color.
+pub const quirk_background_is_opaque base.u32 = (0xF8586 << 10) | 3
// When this quirk is enabled, a frame with no explicit palette is rejected,
// instead of implicitly having a palette with every entry being opaque black.
@@ -127,10 +135,10 @@
metadata_chunk_length_value base.u64,
metadata_io_position base.u64,
- quirk_enabled_ignore_too_much_pixel_data base.bool,
- quirk_enabled_image_bounds_are_strict base.bool,
- quirk_enabled_initial_background_is_opaque base.bool,
- quirk_enabled_reject_empty_palette 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,
end_of_data base.bool,
restarted base.bool,
@@ -146,6 +154,8 @@
seen_num_loops base.bool,
num_loops base.u32,
+ background_color_u32_argb_premul base.u32,
+
gc_has_transparent_index base.bool,
gc_transparent_index base.u8,
gc_disposal base.u8,
@@ -188,8 +198,8 @@
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
- } else if args.quirk == quirk_initial_background_is_opaque {
- this.quirk_enabled_initial_background_is_opaque = args.enabled
+ } else if args.quirk == quirk_background_is_opaque {
+ this.quirk_enabled_background_is_opaque = args.enabled
} else if args.quirk == quirk_reject_empty_palette {
this.quirk_enabled_reject_empty_palette = args.enabled
}
@@ -210,7 +220,7 @@
// TODO: if this.end_of_data, return an error and/or set dst to zero?
ffio = not this.gc_has_transparent_index
- if not this.quirk_enabled_initial_background_is_opaque {
+ if not this.quirk_enabled_background_is_opaque {
ffio = ffio and
(this.frame_rect_x0 == 0) and
(this.frame_rect_y0 == 0) and
@@ -227,7 +237,8 @@
width:this.width,
height:this.height,
first_frame_io_position:this.frame_config_io_position,
- first_frame_is_opaque:ffio)
+ first_frame_is_opaque:ffio,
+ background_color:this.background_color_u32_argb_premul)
}
this.call_sequence = 3
@@ -469,16 +480,19 @@
//
// See the spec section 18 "Logical Screen Descriptor" on page 8.
pri func decoder.decode_lsd?(src base.io_reader) {
- var flags base.u8
- var num_palette_entries base.u32[..256]
- var i base.u32
- var argb base.u32
+ var flags base.u8
+ var background_color_index base.u8
+ var num_palette_entries base.u32[..256]
+ var i base.u32
+ var j base.u32[..1020]
+ var argb base.u32
this.width = args.src.read_u16le_as_u32?()
this.height = args.src.read_u16le_as_u32?()
flags = args.src.read_u8?()
- // Ignore the Background Color Index and Pixel Aspect Ratio bytes.
- args.src.skip?(n:2)
+ background_color_index = args.src.read_u8?()
+ // Ignore the Pixel Aspect Ratio byte.
+ args.src.skip?(n:1)
// Read the optional Global Color Table.
i = 0
@@ -497,6 +511,21 @@
this.palettes[0][(4 * i) + 3] = ((argb >> 24) & 0xFF) as base.u8
i += 1
}
+
+ if this.quirk_enabled_background_is_opaque {
+ if (background_color_index <> 0) and
+ ((background_color_index as base.u32) < num_palette_entries) {
+
+ j = 4 * (background_color_index as base.u32)
+ this.background_color_u32_argb_premul =
+ ((this.palettes[0][j + 0] as base.u32) << 0) |
+ ((this.palettes[0][j + 1] as base.u32) << 8) |
+ ((this.palettes[0][j + 2] as base.u32) << 16) |
+ ((this.palettes[0][j + 3] as base.u32) << 24)
+ } else {
+ this.background_color_u32_argb_premul = 0xFF000000
+ }
+ }
}
// Set the remaining palette entries to opaque black.
diff --git a/test/c/std/gif.c b/test/c/std/gif.c
index 42fca32..bfc55ee 100644
--- a/test/c/std/gif.c
+++ b/test/c/std/gif.c
@@ -797,6 +797,48 @@
return NULL;
}
+const char* test_wuffs_gif_decode_background_color() {
+ 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-background-color.gif");
+ if (status) {
+ return status;
+ }
+ 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_background_is_opaque, q);
+
+ wuffs_base__image_config ic = ((wuffs_base__image_config){});
+ wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
+ status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src_reader);
+ if (status) {
+ RETURN_FAIL("q=%d: decode_image_config: \"%s\"", q, status);
+ }
+
+ wuffs_base__color_u32_argb_premul got =
+ wuffs_base__image_config__background_color(&ic);
+ wuffs_base__color_u32_argb_premul want = q ? 0xFF80C3C3 : 0x00000000;
+
+ if (got != want) {
+ RETURN_FAIL("q=%d: got 0x%08" PRIX32 ", want 0x%08" PRIX32, q, got, want);
+ }
+ }
+ return NULL;
+}
+
const char* test_wuffs_gif_decode_first_frame_is_opaque() {
CHECK_FOCUS(__func__);
wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
@@ -819,7 +861,7 @@
RETURN_FAIL("q=%d: initialize: \"%s\"", q, status);
}
wuffs_gif__decoder__set_quirk_enabled(
- &dec, wuffs_gif__quirk_initial_background_is_opaque, q);
+ &dec, wuffs_gif__quirk_background_is_opaque, q);
wuffs_base__image_config ic = ((wuffs_base__image_config){});
wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
@@ -2048,6 +2090,7 @@
test_wuffs_gif_decode_animated_big, //
test_wuffs_gif_decode_animated_medium, //
test_wuffs_gif_decode_animated_small, //
+ test_wuffs_gif_decode_background_color, //
test_wuffs_gif_decode_bgra_nonpremul, //
test_wuffs_gif_decode_empty_palette, //
test_wuffs_gif_decode_first_frame_is_opaque, //
diff --git a/test/data/artificial/gif-background-color.gif b/test/data/artificial/gif-background-color.gif
new file mode 100644
index 0000000..d25e59e
--- /dev/null
+++ b/test/data/artificial/gif-background-color.gif
Binary files differ
diff --git a/test/data/artificial/gif-background-color.gif.make-artificial.txt b/test/data/artificial/gif-background-color.gif.make-artificial.txt
new file mode 100644
index 0000000..390cf63
--- /dev/null
+++ b/test/data/artificial/gif-background-color.gif.make-artificial.txt
@@ -0,0 +1,36 @@
+# Feed this file to script/make-artificial.go
+
+make gif
+
+header
+
+image {
+ imageWidthHeight 4 1
+ backgroundColorIndex 3
+ palette {
+ 0x80 0x00 0x00
+ 0x80 0x00 0xC1
+ 0x80 0xC2 0x00
+ 0x80 0xC3 0xC3
+ }
+}
+
+loopCount 1
+
+# TODO: graphicControl animationDisposalRestoreBackground 100ms
+bytes 0x21 0xF9 0x04 0x08 0x0A 0x00 0x00 0x00
+
+frame {
+ frameLeftTopWidthHeight 0 0 2 1
+}
+lzw 2 0x00 0x01
+
+# TODO: graphicControl animationDisposalRestoreBackground 200ms
+bytes 0x21 0xF9 0x04 0x08 0x14 0x00 0x00 0x00
+
+frame {
+ frameLeftTopWidthHeight 2 0 1 1
+}
+lzw 2 0x02
+
+trailer