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;