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();
+}