Fix avifIOStats at decoding

Add avifiostatstest.cc.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 484b771..3ac460a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,6 +56,7 @@
   internal functions now return NULL if a memory allocation failed.
 * avifEncoderSetCodecSpecificOption() now returns avifResult instead of void to
   report memory allocation failures.
+* At decoding, avifIOStats now returns the same values as at encoding.
 
 ## [0.11.1] - 2022-10-19
 
diff --git a/include/avif/avif.h b/include/avif/avif.h
index 97ced6e..8eb629a 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -821,7 +821,9 @@
 // Useful stats related to a read/write
 typedef struct avifIOStats
 {
+    // Size in bytes of the AV1 image item or track data containing color samples.
     size_t colorOBUSize;
+    // Size in bytes of the AV1 image item or track data containing alpha samples.
     size_t alphaOBUSize;
 } avifIOStats;
 
diff --git a/src/read.c b/src/read.c
index f49a653..43d26be 100644
--- a/src/read.c
+++ b/src/read.c
@@ -3748,9 +3748,6 @@
             }
         }
 
-        decoder->ioStats.colorOBUSize = colorItem->size;
-        decoder->ioStats.alphaOBUSize = alphaItem ? alphaItem->size : 0;
-
         decoder->image->width = colorItem->width;
         decoder->image->height = colorItem->height;
         decoder->alphaPresent = (alphaItem != NULL);
@@ -3777,6 +3774,12 @@
                 // Every sample must have some data
                 return AVIF_RESULT_BMFF_PARSE_FAILED;
             }
+
+            if (tile->input->alpha) {
+                decoder->ioStats.alphaOBUSize += sample->size;
+            } else {
+                decoder->ioStats.colorOBUSize += sample->size;
+            }
         }
     }
 
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index f80f65c..5e4f459 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -108,6 +108,11 @@
     target_link_libraries(avifincrtest aviftest_helpers avifincrtest_helpers)
     add_test(NAME avifincrtest COMMAND avifincrtest ${CMAKE_CURRENT_SOURCE_DIR}/data/)
 
+    add_executable(avifiostatstest gtest/avifiostatstest.cc)
+    target_link_libraries(avifiostatstest avif_internal aviftest_helpers ${GTEST_LIBRARIES})
+    target_include_directories(avifiostatstest PRIVATE ${GTEST_INCLUDE_DIRS})
+    add_test(NAME avifiostatstest COMMAND avifiostatstest ${CMAKE_CURRENT_SOURCE_DIR}/data/)
+
     add_executable(avifmetadatatest gtest/avifmetadatatest.cc)
     target_link_libraries(avifmetadatatest aviftest_helpers ${GTEST_LIBRARIES})
     target_include_directories(avifmetadatatest PRIVATE ${GTEST_INCLUDE_DIRS})
diff --git a/tests/gtest/avifiostatstest.cc b/tests/gtest/avifiostatstest.cc
new file mode 100644
index 0000000..8e9d776
--- /dev/null
+++ b/tests/gtest/avifiostatstest.cc
@@ -0,0 +1,158 @@
+// Copyright 2023 Google LLC
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include <array>
+#include <iostream>
+#include <ostream>
+
+#include "avif/avif.h"
+#include "avif/internal.h"
+#include "aviftest_helpers.h"
+#include "gtest/gtest.h"
+
+namespace libavif {
+namespace {
+
+// Used to pass the data folder path to the GoogleTest suites.
+const char* data_path = nullptr;
+
+//------------------------------------------------------------------------------
+
+bool GetIoStatsFromEncode(avifIOStats& encoder_io_stats, int quality,
+                          int qualityAlpha, bool translucent,
+                          int num_frames = 1, bool encode_as_grid = false) {
+  testutil::AvifImagePtr image =
+      testutil::ReadImage(data_path, "paris_exif_xmp_icc.jpg");
+  AVIF_CHECK(image != nullptr);
+  if (translucent) {
+    // Make up some alpha from luma.
+    image->alphaPlane = image->yuvPlanes[AVIF_CHAN_Y];
+    image->alphaRowBytes = image->yuvRowBytes[AVIF_CHAN_Y];
+    AVIF_CHECK(!image->imageOwnsAlphaPlane);
+  }
+
+  testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
+  AVIF_CHECK(encoder != nullptr);
+  encoder->speed = AVIF_SPEED_FASTEST;
+  encoder->quality = quality;
+  encoder->qualityAlpha = qualityAlpha;
+  for (int frame = 0; frame < num_frames; ++frame) {
+    if (encode_as_grid) {
+      const avifCropRect left_rect = {0, 0, image->width / 2, image->height};
+      testutil::AvifImagePtr left_cell(avifImageCreateEmpty(),
+                                       avifImageDestroy);
+      AVIF_CHECK(avifImageSetViewRect(left_cell.get(), image.get(),
+                                      &left_rect) == AVIF_RESULT_OK);
+      const avifCropRect right_rect = {image->width / 2, 0, image->width / 2,
+                                       image->height};
+      testutil::AvifImagePtr right_cell(avifImageCreateEmpty(),
+                                        avifImageDestroy);
+      AVIF_CHECK(avifImageSetViewRect(right_cell.get(), image.get(),
+                                      &right_rect) == AVIF_RESULT_OK);
+      const std::array<avifImage*, 2> pointers = {left_cell.get(),
+                                                  right_cell.get()};
+      AVIF_CHECK(avifEncoderAddImageGrid(encoder.get(), /*gridCols=*/2,
+                                         /*gridRows=*/1, pointers.data(),
+                                         AVIF_ADD_IMAGE_FLAG_NONE) ==
+                 AVIF_RESULT_OK);
+    } else {
+      AVIF_CHECK(avifEncoderAddImage(encoder.get(), image.get(),
+                                     /*durationInTimescales=*/1,
+                                     AVIF_ADD_IMAGE_FLAG_NONE) ==
+                 AVIF_RESULT_OK);
+    }
+
+    // Watermark each frame.
+    image->yuvPlanes[AVIF_CHAN_Y][0] =
+        (image->yuvPlanes[AVIF_CHAN_Y][0] + 1u) % 256u;
+  }
+
+  testutil::AvifRwData encoded;
+  AVIF_CHECK(avifEncoderFinish(encoder.get(), &encoded) == AVIF_RESULT_OK);
+  encoder_io_stats = encoder->ioStats;
+
+  // Make sure decoding gives the same stats.
+  testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
+  AVIF_CHECK(decoder != nullptr);
+  AVIF_CHECK(avifDecoderSetIOMemory(decoder.get(), encoded.data,
+                                    encoded.size) == AVIF_RESULT_OK);
+  AVIF_CHECK(avifDecoderParse(decoder.get()) == AVIF_RESULT_OK);
+  EXPECT_EQ(decoder->ioStats.colorOBUSize, encoder_io_stats.colorOBUSize);
+  EXPECT_EQ(decoder->ioStats.alphaOBUSize, encoder_io_stats.alphaOBUSize);
+  return true;
+}
+
+//------------------------------------------------------------------------------
+
+TEST(IoStatsTest, ColorObuSize) {
+  avifIOStats io_stats;
+  ASSERT_TRUE(GetIoStatsFromEncode(io_stats, AVIF_QUALITY_DEFAULT,
+                                   AVIF_QUALITY_DEFAULT,
+                                   /*translucent=*/false));
+  EXPECT_GT(io_stats.colorOBUSize, 0u);
+  EXPECT_EQ(io_stats.alphaOBUSize, 0u);
+}
+
+TEST(IoStatsTest, AlphaObuSize) {
+  avifIOStats io_stats;
+  ASSERT_TRUE(GetIoStatsFromEncode(io_stats, AVIF_QUALITY_WORST,
+                                   AVIF_QUALITY_WORST, /*translucent=*/true));
+  EXPECT_GT(io_stats.colorOBUSize, 0u);
+  EXPECT_GT(io_stats.alphaOBUSize, 0u);
+}
+
+// Disabled because segfault happens with some libaom versions (such as 3.5.0)
+// but not others (such as 3.6.0). TODO(yguyon): Find the commit that fixed it.
+TEST(DISABLED_IoStatsTest, AnimationObuSize) {
+  avifIOStats io_stats;
+  ASSERT_TRUE(GetIoStatsFromEncode(io_stats, AVIF_QUALITY_LOSSLESS,
+                                   AVIF_QUALITY_LOSSLESS,
+                                   /*translucent=*/true));
+
+  avifIOStats io_stats_grid;
+  ASSERT_TRUE(GetIoStatsFromEncode(io_stats_grid, AVIF_QUALITY_LOSSLESS,
+                                   AVIF_QUALITY_LOSSLESS, /*translucent=*/true,
+                                   /*num_frames=*/3));
+  EXPECT_GT(io_stats_grid.colorOBUSize, io_stats.colorOBUSize);
+  EXPECT_GT(io_stats_grid.alphaOBUSize, io_stats.alphaOBUSize);
+}
+
+TEST(IoStatsTest, GridObuSize) {
+  avifIOStats io_stats;
+  ASSERT_TRUE(GetIoStatsFromEncode(io_stats, AVIF_QUALITY_BEST,
+                                   AVIF_QUALITY_BEST, /*translucent=*/true));
+
+  avifIOStats io_stats_grid;
+  ASSERT_TRUE(GetIoStatsFromEncode(io_stats_grid, AVIF_QUALITY_BEST,
+                                   AVIF_QUALITY_BEST, /*translucent=*/true,
+                                   /*num_frames=*/1, /*encode_as_grid=*/true));
+  EXPECT_GT(io_stats_grid.colorOBUSize, io_stats.colorOBUSize / 2);
+  EXPECT_LT(io_stats_grid.colorOBUSize, io_stats.colorOBUSize * 3 / 2);
+  EXPECT_GT(io_stats_grid.alphaOBUSize, io_stats.alphaOBUSize / 2);
+  EXPECT_LT(io_stats_grid.alphaOBUSize, io_stats.alphaOBUSize * 3 / 2);
+}
+
+TEST(IoStatsTest, GridAnimation) {
+  avifIOStats io_stats;
+  // Animated grids are not allowed.
+  ASSERT_FALSE(GetIoStatsFromEncode(io_stats, AVIF_QUALITY_BEST,
+                                    AVIF_QUALITY_BEST, /*translucent=*/false,
+                                    /*num_frames=*/3, /*encode_as_grid=*/true));
+}
+
+//------------------------------------------------------------------------------
+
+}  // namespace
+}  // namespace libavif
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  if (argc != 2) {
+    std::cerr << "There must be exactly one argument containing the path to "
+                 "the test data folder"
+              << std::endl;
+    return 1;
+  }
+  libavif::data_path = argv[1];
+  return RUN_ALL_TESTS();
+}