Move avifImageIsOpaque() from write.c to avif.h
Make it public so that it can be used in aviftest_helpers.cc and by
users.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 59ba49c..2726765 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@
should be updated to set quality (and qualityAlpha if applicable) and leave
minQuantizer, maxQuantizer, minQuantizerAlpha, and maxQuantizerAlpha
initialized to the default values.
+* Add the public API function avifImageIsOpaque() in avif.h.
### Changed
* Exif and XMP metadata is exported to PNG and JPEG files by default,
diff --git a/apps/shared/avifpng.c b/apps/shared/avifpng.c
index 775ce73..a91a121 100644
--- a/apps/shared/avifpng.c
+++ b/apps/shared/avifpng.c
@@ -412,7 +412,7 @@
rgb.chromaUpsampling = chromaUpsampling;
rgb.depth = rgbDepth;
colorType = PNG_COLOR_TYPE_RGBA;
- if (!avif->alphaPlane) {
+ if (avifImageIsOpaque(avif)) {
colorType = PNG_COLOR_TYPE_RGB;
rgb.format = AVIF_RGB_FORMAT_RGB;
}
diff --git a/include/avif/avif.h b/include/avif/avif.h
index d778fa4..e4350d0 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -1175,6 +1175,7 @@
// Helpers
AVIF_API avifBool avifImageUsesU16(const avifImage * image);
+AVIF_API avifBool avifImageIsOpaque(const avifImage * image);
// channel can be an avifChannelIndex.
AVIF_API uint8_t * avifImagePlane(const avifImage * image, int channel);
AVIF_API uint32_t avifImagePlaneRowBytes(const avifImage * image, int channel);
diff --git a/src/avif.c b/src/avif.c
index 75ea27c..595cf53 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -392,6 +392,34 @@
return (image->depth > 8);
}
+avifBool avifImageIsOpaque(const avifImage * image)
+{
+ if (!image->alphaPlane) {
+ return AVIF_TRUE;
+ }
+
+ const uint32_t opaqueValue = (1u << image->depth) - 1u;
+ const uint8_t * row = image->alphaPlane;
+ for (uint32_t y = 0; y < image->height; ++y) {
+ if (avifImageUsesU16(image)) {
+ const uint16_t * row16 = (const uint16_t *)row;
+ for (uint32_t x = 0; x < image->width; ++x) {
+ if (row16[x] != opaqueValue) {
+ return AVIF_FALSE;
+ }
+ }
+ } else {
+ for (uint32_t x = 0; x < image->width; ++x) {
+ if (row[x] != opaqueValue) {
+ return AVIF_FALSE;
+ }
+ }
+ }
+ row += image->alphaRowBytes;
+ }
+ return AVIF_TRUE;
+}
+
uint8_t * avifImagePlane(const avifImage * image, int channel)
{
if ((channel == AVIF_CHAN_Y) || (channel == AVIF_CHAN_U) || (channel == AVIF_CHAN_V)) {
diff --git a/src/write.c b/src/write.c
index 7c420db..ff9117c 100644
--- a/src/write.c
+++ b/src/write.c
@@ -36,7 +36,6 @@
static const char xmpContentType[] = AVIF_CONTENT_TYPE_XMP;
static const size_t xmpContentTypeSize = sizeof(xmpContentType);
-static avifBool avifImageIsOpaque(const avifImage * image);
static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg);
// ---------------------------------------------------------------------------
@@ -1919,34 +1918,6 @@
return avifEncoderFinish(encoder, output);
}
-static avifBool avifImageIsOpaque(const avifImage * image)
-{
- if (!image->alphaPlane) {
- return AVIF_TRUE;
- }
-
- int maxChannel = (1 << image->depth) - 1;
- if (avifImageUsesU16(image)) {
- for (uint32_t j = 0; j < image->height; ++j) {
- for (uint32_t i = 0; i < image->width; ++i) {
- uint16_t * p = (uint16_t *)&image->alphaPlane[(i * 2) + (j * image->alphaRowBytes)];
- if (*p != maxChannel) {
- return AVIF_FALSE;
- }
- }
- }
- } else {
- for (uint32_t j = 0; j < image->height; ++j) {
- for (uint32_t i = 0; i < image->width; ++i) {
- if (image->alphaPlane[i + (j * image->alphaRowBytes)] != maxChannel) {
- return AVIF_FALSE;
- }
- }
- }
- }
- return AVIF_TRUE;
-}
-
static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg)
{
avifBoxMarker av1C = avifRWStreamWriteBox(s, "av1C", AVIF_BOX_SIZE_TBD);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 632ba7a..e340ff8 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -98,6 +98,11 @@
target_include_directories(avifmetadatatest PRIVATE ${GTEST_INCLUDE_DIRS})
add_test(NAME avifmetadatatest COMMAND avifmetadatatest ${CMAKE_CURRENT_SOURCE_DIR}/data/)
+ add_executable(avifopaquetest gtest/avifopaquetest.cc)
+ target_link_libraries(avifopaquetest aviftest_helpers ${GTEST_BOTH_LIBRARIES})
+ target_include_directories(avifopaquetest PRIVATE ${GTEST_INCLUDE_DIRS})
+ add_test(NAME avifopaquetest COMMAND avifopaquetest)
+
add_executable(avifrgbtoyuvtest gtest/avifrgbtoyuvtest.cc)
target_link_libraries(avifrgbtoyuvtest aviftest_helpers ${GTEST_BOTH_LIBRARIES})
target_include_directories(avifrgbtoyuvtest PRIVATE ${GTEST_INCLUDE_DIRS})
diff --git a/tests/gtest/avifopaquetest.cc b/tests/gtest/avifopaquetest.cc
new file mode 100644
index 0000000..5a48526
--- /dev/null
+++ b/tests/gtest/avifopaquetest.cc
@@ -0,0 +1,31 @@
+// Copyright 2022 Google LLC
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include "avif/avif.h"
+#include "aviftest_helpers.h"
+#include "gtest/gtest.h"
+
+namespace libavif {
+namespace {
+
+TEST(OpaqueTest, AlphaAndNoAlpha) {
+ for (bool alpha_is_opaque : {false, true}) {
+ for (int depth : {8, 10, 12}) {
+ testutil::AvifImagePtr alpha = testutil::CreateImage(
+ 1, 1, depth, AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_ALL);
+ testutil::AvifImagePtr no_alpha = testutil::CreateImage(
+ 1, 1, depth, AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_YUV);
+
+ const uint32_t max_value = (1u << depth) - 1;
+ const uint32_t yuva[] = {max_value, max_value, max_value,
+ alpha_is_opaque ? max_value : (max_value - 1)};
+ testutil::FillImagePlain(alpha.get(), yuva);
+ testutil::FillImagePlain(no_alpha.get(), yuva);
+
+ EXPECT_EQ(testutil::AreImagesEqual(*alpha, *no_alpha), alpha_is_opaque);
+ }
+ }
+}
+
+} // namespace
+} // namespace libavif
diff --git a/tests/gtest/aviftest_helpers.cc b/tests/gtest/aviftest_helpers.cc
index daa2cc9..f48e970 100644
--- a/tests/gtest/aviftest_helpers.cc
+++ b/tests/gtest/aviftest_helpers.cc
@@ -166,6 +166,12 @@
const uint8_t* row1 = avifImagePlane(&image1, c);
const uint8_t* row2 = avifImagePlane(&image2, c);
if (!row1 != !row2) {
+ // Maybe one image contains an opaque alpha channel while the other has no
+ // alpha channel, but they should still be considered equal.
+ if (c == AVIF_CHAN_A && avifImageIsOpaque(&image1) &&
+ avifImageIsOpaque(&image2)) {
+ continue;
+ }
return false;
}
const uint32_t row_bytes1 = avifImagePlaneRowBytes(&image1, c);