container: Adapt header
Rename 'is_opaque' to 'has_alpha'
Remove 'background_color'
Read/write 'loop_forever' no matter if 'is_animation'
Reorder fields
Change-Id: If3e1bb386ed52750d70654d95e2b08410864183a
Reviewed-on: https://chromium-review.googlesource.com/c/codecs/libwebp2/+/3303817
Reviewed-by: Pascal Massimino <skal@google.com>
Tested-by: WebM Builds <builds@webmproject.org>
diff --git a/doc/container/README.md b/doc/container/README.md
index 898653b..e73052a 100644
--- a/doc/container/README.md
+++ b/doc/container/README.md
@@ -13,15 +13,16 @@
total file size is not needed.
- No progressive decoding but a solid color signaled in the header and/or a
preview chunk of a few hundred bytes.
-- Animation frame count is not stored. Each frame signals whether there is
- another one afterwards.
+- Animation frame count is not stored in the header. Each frame should signal
+ whether there is another one afterwards (left to codec implementation).
- Drop niche loop count. An animation either plays once or loops infinitely.
- Only the ICC profile is necessary before pixel decoding. XMP and EXIF
metadata is located at the end of the bitstream.
-- Interleave alpha data within pixel chunks instead of having a separate
- cumbersome alpha chunk.
-- Spatially slice the image into independent tiles, to limit minimum memory
- usage and for easier encoding / decoding multithreading.
+- Alpha data should be interleaved within pixel chunks instead of having a
+ separate cumbersome alpha chunk (left to codec implementation).
+- Spatially slice the image into independent tiles, to limit memory usage and
+ for easier encoding / decoding multithreading (left to codec
+ implementation).
## Specification
@@ -35,53 +36,29 @@
### Chunks
-A still image has the following chunks in order:
+An image has the following chunks in order:
Chunk | Optional | Content | Coding
----------- | -------- | ------------------- | ---------------------------------
Header | No | Width/height etc. | Bit-packing
-Preview | Optional | Image approximation | VarInt chunk size + encoded bytes
ICC profile | Optional | Color space | VarInt chunk size + encoded bytes
-First tile | No | Pixels | VarInt chunk size + encoded bytes
-... | ... | ... | ...
-Last tile | No | Pixels | VarInt chunk size + encoded bytes
+Preview | Optional | Image approximation | VarInt chunk size + encoded bytes
+Codec impl | No | Pixels | VarInt chunk size + encoded bytes
XMP | Optional | Appended as is | VarInt chunk size + encoded bytes
EXIF | Optional | Appended as is | VarInt chunk size + encoded bytes
-An animated image just adds ANMF separators, containing a frame's window and
-duration, between tiles of different frames:
-
-Chunk | Optional | Content | Coding
----------- | -------- | -------------------- | ---------------------------------
-Header | No | Width/height etc. | Bit-packing
-Preview | Optional | Image approximation | VarInt chunk size + encoded bytes
-ICC | Optional | Color space | VarInt chunk size + encoded bytes
-**ANMF** | **No** | **Frame 0 features** | **Bit-packing + VarInt**
-First tile | No | Pixels of frame 0 | VarInt chunk size + encoded bytes
-... | ... | ... | ...
-Last tile | No | Pixels of frame 0 | VarInt chunk size + encoded bytes
-**ANMF** | **No** | **Frame 1 features** | **Bit-packing + VarInt**
-First tile | No | Pixels of frame 1 | VarInt chunk size + encoded bytes
-... | ... | ... | ...
-Last tile | No | Pixels of frame 1 | VarInt chunk size + encoded bytes
-XMP | Optional | Appended as is | VarInt chunk size + encoded bytes
-EXIF | Optional | Appended as is | VarInt chunk size + encoded bytes
-
#### Header
-Around 10-byte long, the main header contains the most important information
-about the image, such as the format tag, width, height, orientation, is opaque,
-is animated, tile size etc. \
+Being 10-byte long, the main header contains the most important information
+about the image, such as the format tag, width, height, orientation, has alpha,
+is animated, etc. \
It uses bit-packing for easy encoding and decoding on top of providing a fixed
header size.
-Animations use a slightly longer header to also store the loop mode and the
-background color. ANMF separators also use bit-packing.
-
#### Regular chunk
All remaining data is stored sequentially in regular chunks, whether it is a
-preview, a tile, XMP etc. A chunk is preceded by its size encoded as a
+preview, pixels, XMP etc. A chunk is preceded by its size encoded as a
variable-length integer, limiting it to ~500 megabytes.
## Library API
diff --git a/doc/container/avif.cc b/doc/container/avif.cc
index cbccb53..6b52dc9 100644
--- a/doc/container/avif.cc
+++ b/doc/container/avif.cc
@@ -413,7 +413,7 @@
offset_till_codec_bytes = codec_bytes - data;
image.orientation = kOrientationMapping[angle][mirror];
- image.is_opaque = true; // TODO(yguyon): Handle alpha
+ image.has_alpha = false; // TODO(yguyon): Handle alpha
return true;
}
diff --git a/doc/container/container.cc b/doc/container/container.cc
index 127acb5..ca83939 100644
--- a/doc/container/container.cc
+++ b/doc/container/container.cc
@@ -81,7 +81,7 @@
bool DecodeRawPixels(const uint8_t data[], size_t data_size, Image& image) {
// TODO(yguyon): Implement if (image.is_animation)
if (image.format == Format::kARGB8) {
- if (data_size != image.width * image.height * (image.is_opaque ? 3 : 4)) {
+ if (data_size != image.width * image.height * (image.has_alpha ? 4 : 3)) {
return false;
}
assert(image.pixels.empty());
@@ -106,33 +106,31 @@
header.EncodeUInt(image.width, 1, 1 << kImageDimNumBits);
header.EncodeUInt(image.height, 1, 1 << kImageDimNumBits);
header.EncodeUInt(static_cast<uint32_t>(image.orientation), 0, 7);
- header.EncodeUInt((image.format == Format::kARGB8) ? 0
- : (image.format == Format::kAYUV10) ? 1
- : (image.format == Format::kAYUV12) ? 2
- : 3,
- 0, 3);
- header.EncodeBool(image.is_opaque);
+ header.EncodeBool(image.has_alpha);
+ header.EncodeBool(image.is_animation);
+ header.EncodeBool(image.is_animation ? image.loop_forever : false);
header.EncodeUInt(image.preview_color, 0, (1 << 12) - 1);
header.EncodeBool(config.create_preview);
header.EncodeBool(!image.icc.empty());
header.EncodeBool(!image.xmp.empty());
header.EncodeBool(!image.exif.empty());
- header.EncodeBool(image.is_animation);
- if (image.is_animation) {
- header.EncodeBool(image.loop_forever);
- header.EncodeUInt(image.background_color, 0u, 4294967295u);
- }
-
+ header.EncodeUInt((image.format == Format::kARGB8) ? 0
+ : (image.format == Format::kAYUV10) ? 1
+ : (image.format == Format::kAYUV12) ? 2
+ : 3,
+ 0, 3);
+ header.EncodeUInt(kContainerHeaderPaddingValue, 0, 15);
+ assert(header.GetNumBits() == kContainerHeaderNumBits);
data.reserve(header.GetNumBytes());
data.insert(data.end(), header.GetBytes(),
header.GetBytes() + header.GetNumBytes());
- if (config.create_preview) {
- abort(); // TODO(yguyon): Implement
- }
if (!image.icc.empty()) {
WriteChunk(image.icc.data(), image.icc.size(), data);
}
+ if (config.create_preview) {
+ abort(); // TODO(yguyon): Implement
+ }
WriteChunk(codec_bytes, num_codec_bytes, data);
@@ -161,21 +159,21 @@
image.width = header.DecodeUInt(1, 1 << kImageDimNumBits);
image.height = header.DecodeUInt(1, 1 << kImageDimNumBits);
image.orientation = static_cast<Orientation>(header.DecodeUInt(0, 7));
- const uint32_t format = header.DecodeUInt(0, 3);
- image.format = (format == 0) ? Format::kARGB8
- : (format == 1) ? Format::kAYUV10
- : (format == 2) ? Format::kAYUV12
- : Format::kUnknown;
- image.is_opaque = header.DecodeBool();
+ image.has_alpha = header.DecodeBool();
+ image.is_animation = header.DecodeBool();
+ image.loop_forever = header.DecodeBool() && image.is_animation;
image.preview_color = header.DecodeUInt(/*num_bits=*/12);
has_preview = header.DecodeBool();
has_icc = header.DecodeBool();
has_xmp = header.DecodeBool();
has_exif = header.DecodeBool();
- image.is_animation = header.DecodeBool();
- image.loop_forever = image.is_animation ? header.DecodeBool() : false;
- image.background_color =
- image.is_animation ? header.DecodeUInt(/*num_bits=*/32) : 0;
+ const uint32_t format = header.DecodeUInt(0, 3);
+ image.format = (format == 0) ? Format::kARGB8
+ : (format == 1) ? Format::kAYUV10
+ : (format == 2) ? Format::kAYUV12
+ : Format::kUnknown;
+ if (header.DecodeUInt(0, 15) != kContainerHeaderPaddingValue) return false;
+ assert(header.GetNumReadBits() == kContainerHeaderNumBits);
num_read_bytes = header.GetNumReadBytes();
return header.Ok();
}
@@ -194,8 +192,8 @@
return false;
}
- if ((has_preview && !SkipChunk(data, data_size, data_pos)) ||
- (has_icc && !ReadChunk(data, data_size, data_pos, image.icc))) {
+ if ((has_icc && !ReadChunk(data, data_size, data_pos, image.icc)) ||
+ (has_preview && !SkipChunk(data, data_size, data_pos))) {
return false;
}
@@ -226,7 +224,7 @@
"90Clockwise",
"FlipTopLeftToBotRgt",
"270Clockwise"};
- static const char* kFormatStr[] = {"ARGB8", "AYUV10", "Unknown"};
+ static const char* kFormatStr[] = {"ARGB8", "AYUV10", "AYUV12", "Unknown"};
static const char* kBoolStr[] = {"false", "true"};
std::stringstream ss;
@@ -253,18 +251,15 @@
<< " width " << image.width << std::endl
<< " height " << image.height << std::endl
<< " orientation " << kOrientationStr[orientation] << std::endl
- << " format " << kFormatStr[format] << std::endl
- << " is_opaque " << kBoolStr[image.is_opaque] << std::endl
+ << " has_alpha " << kBoolStr[image.has_alpha] << std::endl
+ << " is_animation " << kBoolStr[image.is_animation] << std::endl
+ << " loop_forever " << kBoolStr[image.loop_forever] << std::endl
<< " preview_color " << image.preview_color << std::endl
<< " has_preview " << kBoolStr[has_preview] << std::endl
<< " has_icc " << kBoolStr[has_icc] << std::endl
<< " has_xmp " << kBoolStr[has_xmp] << std::endl
<< " has_exif " << kBoolStr[has_exif] << std::endl
- << " is_animation " << kBoolStr[image.is_animation] << std::endl;
- if (image.is_animation) {
- ss << " loop_forever " << kBoolStr[image.loop_forever] << std::endl
- << " background_color " << image.background_color << std::endl;
- }
+ << " format " << kFormatStr[format] << std::endl;
return ss.str();
}
diff --git a/doc/container/container.h b/doc/container/container.h
index 9af93a4..2b732b4 100644
--- a/doc/container/container.h
+++ b/doc/container/container.h
@@ -39,6 +39,8 @@
static constexpr uint32_t kContainerCodecAv1 = 0x9; // av1-obu tile group only
static constexpr uint32_t kContainerCodecWp2 = 0x6; // WebP2 (unimplemented)
static constexpr uint32_t kImageDimNumBits = 14; // Maximum log2 width, height.
+static constexpr uint32_t kContainerHeaderNumBits = 80; // 10 bytes
+static constexpr uint32_t kContainerHeaderPaddingValue = 15; // 4 bits
// Maximum (inclusive) size of a data chunk.
static constexpr uint32_t kMaxChunkSize = 1u << 29; // ~500 MB
@@ -67,7 +69,7 @@
Orientation orientation = Orientation::kOriginal; // To be applied.
Format format = Format::kARGB8; // Describes 'pixels'.
- bool is_opaque = false; // True if no alpha. False has no guarantee.
+ bool has_alpha = false; // The image is opaque if false.
uint32_t preview_color = 0; // Average color. RGB444 format.
Data icc; // Color profile chunk. Empty if none.
@@ -77,7 +79,6 @@
bool is_animation = false; // True if the image is animated (unsupported).
// For 'is_animation=true' only:
bool loop_forever = false; // Play once if false, forever if true.
- uint32_t background_color = 0; // ARGB8888 color to dispose the canvas with.
Data pixels; // Raw samples in 'format'. 'orientation' is not applied.
};
diff --git a/doc/container/convert.cc b/doc/container/convert.cc
index 690236f..30b3662 100644
--- a/doc/container/convert.cc
+++ b/doc/container/convert.cc
@@ -196,7 +196,7 @@
? container::Format::kAYUV12
: container::Format::kAYUV10
: container::Format::kARGB8;
- image.is_opaque = true; // TODO(yguyon): Handle transparency
+ image.has_alpha = false; // TODO(yguyon): Handle transparency
}
if (!input_is_container && !input_is_avif && !input_is_obu) {
diff --git a/doc/container/testutil.cc b/doc/container/testutil.cc
index 7603a3f..9872264 100644
--- a/doc/container/testutil.cc
+++ b/doc/container/testutil.cc
@@ -32,17 +32,16 @@
EXPECT_EQ(a.width, b.width);
EXPECT_EQ(a.height, b.height);
EXPECT_EQ(a.orientation, b.orientation);
- EXPECT_EQ(a.format, b.format);
- EXPECT_EQ(a.is_opaque, b.is_opaque);
+ EXPECT_EQ(a.has_alpha, b.has_alpha);
+ EXPECT_EQ(a.is_animation, b.is_animation);
+ if (a.is_animation && b.is_animation) {
+ EXPECT_EQ(a.loop_forever, b.loop_forever);
+ }
EXPECT_EQ(a.preview_color, b.preview_color);
EXPECT_EQ(a.icc, b.icc);
EXPECT_EQ(a.xmp, b.xmp);
EXPECT_EQ(a.exif, b.exif);
- EXPECT_EQ(a.is_animation, b.is_animation);
- if (a.is_animation && b.is_animation) {
- EXPECT_EQ(a.loop_forever, b.loop_forever);
- EXPECT_EQ(a.background_color, b.background_color);
- }
+ EXPECT_EQ(a.format, b.format);
EXPECT_EQ(a.pixels, b.pixels);
}
@@ -56,12 +55,11 @@
input.width = 1000;
input.height = 1000;
input.orientation = Orientation::kOriginal;
- input.format = Format::kARGB8;
- input.is_opaque = true;
- input.preview_color = 0x0;
+ input.has_alpha = false;
input.is_animation = false;
input.loop_forever = true;
- input.background_color = 0xFF000000u;
+ input.preview_color = 0x0;
+ input.format = Format::kARGB8;
// Random metadata. The content does not matter.
for (Data* data : {&input.icc, &input.xmp, &input.exif}) {
@@ -70,7 +68,7 @@
}
// Random pixel values. The content does not matter.
- input.pixels.resize(input.width * input.height * (input.is_opaque ? 3 : 4));
+ input.pixels.resize(input.width * input.height * (input.has_alpha ? 4 : 3));
for (uint8_t& byte : input.pixels) byte = random() % 255;
return input;