Fix layered image decoding with alpha
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea61f7b..a94d870 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@
* Use AOM_TUNE_IQ for layered image inter-frame encoding.
* Update aom.cmd/LocalAom.cmake: v3.13.3
* Update LocalAvm.cmake: research-v14.0.0
+* Fix decoding layered image with multiple scaled alpha layers
## [1.4.1] - 2026-03-20
diff --git a/src/codec_aom.c b/src/codec_aom.c
index c918ae8..92a348c 100644
--- a/src/codec_aom.c
+++ b/src/codec_aom.c
@@ -224,13 +224,6 @@
yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
}
- if (image->width && image->height) {
- if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) ||
- (image->depth != codec->internal->image->bit_depth) || (image->yuvFormat != yuvFormat)) {
- // Throw it all out
- avifImageFreePlanes(image, AVIF_PLANES_ALL);
- }
- }
image->width = codec->internal->image->d_w;
image->height = codec->internal->image->d_h;
image->depth = codec->internal->image->bit_depth;
@@ -252,15 +245,8 @@
}
image->imageOwnsYUVPlanes = AVIF_FALSE;
} else {
- // Alpha plane - ensure image is correct size, fill color
+ // Alpha plane - set image to correct size, fill alpha
- if (image->width && image->height) {
- if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) ||
- (image->depth != codec->internal->image->bit_depth)) {
- // Alpha plane doesn't match previous alpha plane decode, bail out
- return AVIF_FALSE;
- }
- }
image->width = codec->internal->image->d_w;
image->height = codec->internal->image->d_h;
image->depth = codec->internal->image->bit_depth;
diff --git a/src/codec_avm.c b/src/codec_avm.c
index e08ffc9..2328d6e 100644
--- a/src/codec_avm.c
+++ b/src/codec_avm.c
@@ -142,13 +142,6 @@
yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
}
- if (image->width && image->height) {
- if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) ||
- (image->depth != codec->internal->image->bit_depth) || (image->yuvFormat != yuvFormat)) {
- // Throw it all out
- avifImageFreePlanes(image, AVIF_PLANES_ALL);
- }
- }
image->width = codec->internal->image->d_w;
image->height = codec->internal->image->d_h;
image->depth = codec->internal->image->bit_depth;
@@ -203,15 +196,8 @@
image->imageOwnsYUVPlanes = AVIF_FALSE;
}
} else {
- // Alpha plane - ensure image is correct size, fill color
+ // Alpha plane - set image to correct size, fill alpha
- if (image->width && image->height) {
- if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) ||
- (image->depth != codec->internal->image->bit_depth)) {
- // Alpha plane doesn't match previous alpha plane decode, bail out
- return AVIF_FALSE;
- }
- }
image->width = codec->internal->image->d_w;
image->height = codec->internal->image->d_h;
image->depth = codec->internal->image->bit_depth;
diff --git a/src/codec_dav1d.c b/src/codec_dav1d.c
index 666dcef..3f83c35 100644
--- a/src/codec_dav1d.c
+++ b/src/codec_dav1d.c
@@ -189,13 +189,6 @@
break;
}
- if (image->width && image->height) {
- if ((image->width != (uint32_t)dav1dImage->p.w) || (image->height != (uint32_t)dav1dImage->p.h) ||
- (image->depth != (uint32_t)dav1dImage->p.bpc) || (image->yuvFormat != yuvFormat)) {
- // Throw it all out
- avifImageFreePlanes(image, AVIF_PLANES_ALL);
- }
- }
image->width = dav1dImage->p.w;
image->height = dav1dImage->p.h;
image->depth = dav1dImage->p.bpc;
@@ -217,15 +210,8 @@
}
image->imageOwnsYUVPlanes = AVIF_FALSE;
} else {
- // Alpha plane - ensure image is correct size, fill color
+ // Alpha plane - set image to correct size, fill alpha
- if (image->width && image->height) {
- if ((image->width != (uint32_t)dav1dImage->p.w) || (image->height != (uint32_t)dav1dImage->p.h) ||
- (image->depth != (uint32_t)dav1dImage->p.bpc)) {
- // Alpha plane doesn't match previous alpha plane decode, bail out
- return AVIF_FALSE;
- }
- }
image->width = dav1dImage->p.w;
image->height = dav1dImage->p.h;
image->depth = dav1dImage->p.bpc;
diff --git a/src/codec_libgav1.c b/src/codec_libgav1.c
index 43cb81f..7d07adf 100644
--- a/src/codec_libgav1.c
+++ b/src/codec_libgav1.c
@@ -96,14 +96,6 @@
break;
}
- if (image->width && image->height) {
- if ((image->width != (uint32_t)gav1Image->displayed_width[0]) ||
- (image->height != (uint32_t)gav1Image->displayed_height[0]) || (image->depth != (uint32_t)gav1Image->bitdepth) ||
- (image->yuvFormat != yuvFormat)) {
- // Throw it all out
- avifImageFreePlanes(image, AVIF_PLANES_ALL);
- }
- }
image->width = gav1Image->displayed_width[0];
image->height = gav1Image->displayed_height[0];
image->depth = gav1Image->bitdepth;
@@ -125,15 +117,8 @@
}
image->imageOwnsYUVPlanes = AVIF_FALSE;
} else {
- // Alpha plane - ensure image is correct size, fill color
+ // Alpha plane - set image to correct size, fill alpha
- if (image->width && image->height) {
- if ((image->width != (uint32_t)gav1Image->displayed_width[0]) ||
- (image->height != (uint32_t)gav1Image->displayed_height[0]) || (image->depth != (uint32_t)gav1Image->bitdepth)) {
- // Alpha plane doesn't match previous alpha plane decode, bail out
- return AVIF_FALSE;
- }
- }
image->width = gav1Image->displayed_width[0];
image->height = gav1Image->displayed_height[0];
image->depth = gav1Image->bitdepth;
diff --git a/tests/gtest/avifprogressivetest.cc b/tests/gtest/avifprogressivetest.cc
index d9e211a..3071b56 100644
--- a/tests/gtest/avifprogressivetest.cc
+++ b/tests/gtest/avifprogressivetest.cc
@@ -34,26 +34,31 @@
}
void TestDecode(uint8_t* data, size_t size, uint32_t expect_width,
- uint32_t expect_height) {
+ uint32_t expect_height, bool expect_alpha = false) {
ASSERT_EQ(avifDecoderSetIOMemory(decoder_.get(), data, size),
AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderParse(decoder_.get()), AVIF_RESULT_OK);
ASSERT_EQ(decoder_->progressiveState, AVIF_PROGRESSIVE_STATE_ACTIVE);
ASSERT_EQ(static_cast<uint32_t>(decoder_->imageCount),
encoder_->extraLayerCount + 1);
+ EXPECT_EQ(decoder_->alphaPresent, expect_alpha);
for (uint32_t layer = 0; layer < encoder_->extraLayerCount + 1; ++layer) {
ASSERT_EQ(avifDecoderNextImage(decoder_.get()), AVIF_RESULT_OK);
// libavif scales frame automatically.
ASSERT_EQ(decoder_->image->width, expect_width);
ASSERT_EQ(decoder_->image->height, expect_height);
+ if (expect_alpha) {
+ ASSERT_NE(decoder_->image->alphaPlane, nullptr);
+ }
// TODO(wtc): Check avifDecoderNthImageMaxExtent().
}
}
- void TestDecode(uint32_t expect_width, uint32_t expect_height) {
+ void TestDecode(uint32_t expect_width, uint32_t expect_height,
+ bool expect_alpha = false) {
TestDecode(encoded_avif_.data, encoded_avif_.size, expect_width,
- expect_height);
+ expect_height, expect_alpha);
// TODO(wtc): Check decoder_->image and image_ are similar, and better
// quality layer is more similar.
@@ -147,6 +152,41 @@
TestDecode(kImageSize, kImageSize);
}
+TEST_F(ProgressiveTest, DimensionChangeWithAlpha) {
+ if (avifLibYUVVersion() == 0) {
+ GTEST_SKIP() << "libyuv not available, skip test.";
+ }
+
+ const auto image =
+ testutil::CreateImage(kImageSize, kImageSize, 8, AVIF_PIXEL_FORMAT_YUV444,
+ AVIF_PLANES_ALL, AVIF_RANGE_FULL);
+ ASSERT_NE(image, nullptr);
+ testutil::FillImageGradient(image.get());
+
+ encoder_->extraLayerCount = 2;
+ encoder_->quality = 100;
+ encoder_->scalingMode = {{1, 2}, {1, 2}};
+
+ ASSERT_EQ(avifEncoderAddImage(encoder_.get(), image.get(), 1,
+ AVIF_ADD_IMAGE_FLAG_NONE),
+ AVIF_RESULT_OK);
+
+ // Encode the scaled image twice to verify frame buffer reallocation
+ // behavior during decode
+ ASSERT_EQ(avifEncoderAddImage(encoder_.get(), image.get(), 1,
+ AVIF_ADD_IMAGE_FLAG_NONE),
+ AVIF_RESULT_OK);
+
+ encoder_->scalingMode = {{1, 1}, {1, 1}};
+ ASSERT_EQ(avifEncoderAddImage(encoder_.get(), image.get(), 1,
+ AVIF_ADD_IMAGE_FLAG_NONE),
+ AVIF_RESULT_OK);
+
+ ASSERT_EQ(avifEncoderFinish(encoder_.get(), &encoded_avif_), AVIF_RESULT_OK);
+
+ TestDecode(kImageSize, kImageSize, /*expect_alpha=*/true);
+}
+
TEST_F(ProgressiveTest, LayeredGrid) {
encoder_->extraLayerCount = 1;
encoder_->quality = 21;