Skip to content

Commit 0fba3f9

Browse files
Fix layered image decoding with alpha
1 parent b8d6c1a commit 0fba3f9

6 files changed

Lines changed: 73 additions & 73 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ The changes are relative to the previous release, unless the baseline is specifi
2020
* Use AOM_TUNE_IQ for layered image inter-frame encoding.
2121
* Update aom.cmd/LocalAom.cmake: v3.13.3
2222
* Update LocalAvm.cmake: research-v14.0.0
23+
* Fix decoding layered image with multiple scaled alpha layers
2324

2425
## [1.4.1] - 2026-03-20
2526

src/codec_aom.c

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -224,13 +224,9 @@ static avifBool aomCodecGetNextImage(struct avifCodec * codec,
224224
yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
225225
}
226226

227-
if (image->width && image->height) {
228-
if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) ||
229-
(image->depth != codec->internal->image->bit_depth) || (image->yuvFormat != yuvFormat)) {
230-
// Throw it all out
231-
avifImageFreePlanes(image, AVIF_PLANES_ALL);
232-
}
233-
}
227+
// Throw away the old color planes if there are
228+
avifImageFreePlanes(image, AVIF_PLANES_YUV);
229+
234230
image->width = codec->internal->image->d_w;
235231
image->height = codec->internal->image->d_h;
236232
image->depth = codec->internal->image->bit_depth;
@@ -244,28 +240,22 @@ static avifBool aomCodecGetNextImage(struct avifCodec * codec,
244240
image->matrixCoefficients = (avifMatrixCoefficients)codec->internal->image->mc;
245241

246242
// Steal the pointers from the decoder's image directly
247-
avifImageFreePlanes(image, AVIF_PLANES_YUV);
248243
int yuvPlaneCount = (yuvFormat == AVIF_PIXEL_FORMAT_YUV400) ? 1 : 3;
249244
for (int yuvPlane = 0; yuvPlane < yuvPlaneCount; ++yuvPlane) {
250245
image->yuvPlanes[yuvPlane] = codec->internal->image->planes[yuvPlane];
251246
image->yuvRowBytes[yuvPlane] = codec->internal->image->stride[yuvPlane];
252247
}
253248
image->imageOwnsYUVPlanes = AVIF_FALSE;
254249
} else {
255-
// Alpha plane - ensure image is correct size, fill color
250+
// Alpha plane - set image to correct size, fill alpha
251+
252+
// Throw away the old alpha plane if there are
253+
avifImageFreePlanes(image, AVIF_PLANES_A);
256254

257-
if (image->width && image->height) {
258-
if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) ||
259-
(image->depth != codec->internal->image->bit_depth)) {
260-
// Alpha plane doesn't match previous alpha plane decode, bail out
261-
return AVIF_FALSE;
262-
}
263-
}
264255
image->width = codec->internal->image->d_w;
265256
image->height = codec->internal->image->d_h;
266257
image->depth = codec->internal->image->bit_depth;
267258

268-
avifImageFreePlanes(image, AVIF_PLANES_A);
269259
image->alphaPlane = codec->internal->image->planes[0];
270260
image->alphaRowBytes = codec->internal->image->stride[0];
271261
*isLimitedRangeAlpha = (codec->internal->image->range == AOM_CR_STUDIO_RANGE);

src/codec_avm.c

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,9 @@ static avifBool avmCodecGetNextImage(struct avifCodec * codec,
142142
yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
143143
}
144144

145-
if (image->width && image->height) {
146-
if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) ||
147-
(image->depth != codec->internal->image->bit_depth) || (image->yuvFormat != yuvFormat)) {
148-
// Throw it all out
149-
avifImageFreePlanes(image, AVIF_PLANES_ALL);
150-
}
151-
}
145+
// Throw away the old color planes if there are
146+
avifImageFreePlanes(image, AVIF_PLANES_YUV);
147+
152148
image->width = codec->internal->image->d_w;
153149
image->height = codec->internal->image->d_h;
154150
image->depth = codec->internal->image->bit_depth;
@@ -172,7 +168,6 @@ static avifBool avmCodecGetNextImage(struct avifCodec * codec,
172168
image->transferCharacteristics = (avifTransferCharacteristics)codec->internal->image->tc;
173169
image->matrixCoefficients = (avifMatrixCoefficients)codec->internal->image->mc;
174170

175-
avifImageFreePlanes(image, AVIF_PLANES_YUV);
176171
int yuvPlaneCount = (yuvFormat == AVIF_PIXEL_FORMAT_YUV400) ? 1 : 3;
177172

178173
// avifImage assumes that a depth of 8 bits means an 8-bit buffer.
@@ -203,21 +198,15 @@ static avifBool avmCodecGetNextImage(struct avifCodec * codec,
203198
image->imageOwnsYUVPlanes = AVIF_FALSE;
204199
}
205200
} else {
206-
// Alpha plane - ensure image is correct size, fill color
201+
// Alpha plane - set image to correct size, fill alpha
202+
203+
// Throw away the old alpha plane if there are
204+
avifImageFreePlanes(image, AVIF_PLANES_A);
207205

208-
if (image->width && image->height) {
209-
if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) ||
210-
(image->depth != codec->internal->image->bit_depth)) {
211-
// Alpha plane doesn't match previous alpha plane decode, bail out
212-
return AVIF_FALSE;
213-
}
214-
}
215206
image->width = codec->internal->image->d_w;
216207
image->height = codec->internal->image->d_h;
217208
image->depth = codec->internal->image->bit_depth;
218209

219-
avifImageFreePlanes(image, AVIF_PLANES_A);
220-
221210
if (!avifImageUsesU16(image) && (codec->internal->image->fmt & AVM_IMG_FMT_HIGHBITDEPTH)) {
222211
AVIF_CHECK(avifImageAllocatePlanes(image, AVIF_PLANES_A) == AVIF_RESULT_OK);
223212
const uint8_t * srcRow = codec->internal->image->planes[0];

src/codec_dav1d.c

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,9 @@ static avifBool dav1dCodecGetNextImage(struct avifCodec * codec,
189189
break;
190190
}
191191

192-
if (image->width && image->height) {
193-
if ((image->width != (uint32_t)dav1dImage->p.w) || (image->height != (uint32_t)dav1dImage->p.h) ||
194-
(image->depth != (uint32_t)dav1dImage->p.bpc) || (image->yuvFormat != yuvFormat)) {
195-
// Throw it all out
196-
avifImageFreePlanes(image, AVIF_PLANES_ALL);
197-
}
198-
}
192+
// Throw away the old color planes if there are
193+
avifImageFreePlanes(image, AVIF_PLANES_YUV);
194+
199195
image->width = dav1dImage->p.w;
200196
image->height = dav1dImage->p.h;
201197
image->depth = dav1dImage->p.bpc;
@@ -209,28 +205,22 @@ static avifBool dav1dCodecGetNextImage(struct avifCodec * codec,
209205
image->matrixCoefficients = (avifMatrixCoefficients)dav1dImage->seq_hdr->mtrx;
210206

211207
// Steal the pointers from the decoder's image directly
212-
avifImageFreePlanes(image, AVIF_PLANES_YUV);
213208
int yuvPlaneCount = (yuvFormat == AVIF_PIXEL_FORMAT_YUV400) ? 1 : 3;
214209
for (int yuvPlane = 0; yuvPlane < yuvPlaneCount; ++yuvPlane) {
215210
image->yuvPlanes[yuvPlane] = dav1dImage->data[yuvPlane];
216211
image->yuvRowBytes[yuvPlane] = (uint32_t)dav1dImage->stride[(yuvPlane == AVIF_CHAN_Y) ? 0 : 1];
217212
}
218213
image->imageOwnsYUVPlanes = AVIF_FALSE;
219214
} else {
220-
// Alpha plane - ensure image is correct size, fill color
215+
// Alpha plane - set image to correct size, fill alpha
216+
217+
// Throw away the old alpha plane if there are
218+
avifImageFreePlanes(image, AVIF_PLANES_A);
221219

222-
if (image->width && image->height) {
223-
if ((image->width != (uint32_t)dav1dImage->p.w) || (image->height != (uint32_t)dav1dImage->p.h) ||
224-
(image->depth != (uint32_t)dav1dImage->p.bpc)) {
225-
// Alpha plane doesn't match previous alpha plane decode, bail out
226-
return AVIF_FALSE;
227-
}
228-
}
229220
image->width = dav1dImage->p.w;
230221
image->height = dav1dImage->p.h;
231222
image->depth = dav1dImage->p.bpc;
232223

233-
avifImageFreePlanes(image, AVIF_PLANES_A);
234224
image->alphaPlane = dav1dImage->data[0];
235225
image->alphaRowBytes = (uint32_t)dav1dImage->stride[0];
236226
*isLimitedRangeAlpha = (codec->internal->colorRange == AVIF_RANGE_LIMITED);

src/codec_libgav1.c

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,9 @@ static avifBool gav1CodecGetNextImage(struct avifCodec * codec,
9696
break;
9797
}
9898

99-
if (image->width && image->height) {
100-
if ((image->width != (uint32_t)gav1Image->displayed_width[0]) ||
101-
(image->height != (uint32_t)gav1Image->displayed_height[0]) || (image->depth != (uint32_t)gav1Image->bitdepth) ||
102-
(image->yuvFormat != yuvFormat)) {
103-
// Throw it all out
104-
avifImageFreePlanes(image, AVIF_PLANES_ALL);
105-
}
106-
}
99+
// Throw away the old color planes if there are
100+
avifImageFreePlanes(image, AVIF_PLANES_YUV);
101+
107102
image->width = gav1Image->displayed_width[0];
108103
image->height = gav1Image->displayed_height[0];
109104
image->depth = gav1Image->bitdepth;
@@ -117,23 +112,18 @@ static avifBool gav1CodecGetNextImage(struct avifCodec * codec,
117112
image->matrixCoefficients = (avifMatrixCoefficients)gav1Image->matrix_coefficients;
118113

119114
// Steal the pointers from the decoder's image directly
120-
avifImageFreePlanes(image, AVIF_PLANES_YUV);
121115
int yuvPlaneCount = (yuvFormat == AVIF_PIXEL_FORMAT_YUV400) ? 1 : 3;
122116
for (int yuvPlane = 0; yuvPlane < yuvPlaneCount; ++yuvPlane) {
123117
image->yuvPlanes[yuvPlane] = gav1Image->plane[yuvPlane];
124118
image->yuvRowBytes[yuvPlane] = gav1Image->stride[yuvPlane];
125119
}
126120
image->imageOwnsYUVPlanes = AVIF_FALSE;
127121
} else {
128-
// Alpha plane - ensure image is correct size, fill color
129-
130-
if (image->width && image->height) {
131-
if ((image->width != (uint32_t)gav1Image->displayed_width[0]) ||
132-
(image->height != (uint32_t)gav1Image->displayed_height[0]) || (image->depth != (uint32_t)gav1Image->bitdepth)) {
133-
// Alpha plane doesn't match previous alpha plane decode, bail out
134-
return AVIF_FALSE;
135-
}
136-
}
122+
// Alpha plane - set image to correct size, fill alpha
123+
124+
// Throw away the old alpha plane if there are
125+
avifImageFreePlanes(image, AVIF_PLANES_A);
126+
137127
image->width = gav1Image->displayed_width[0];
138128
image->height = gav1Image->displayed_height[0];
139129
image->depth = gav1Image->bitdepth;

tests/gtest/avifprogressivetest.cc

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,26 +34,31 @@ class ProgressiveTest : public testing::Test {
3434
}
3535

3636
void TestDecode(uint8_t* data, size_t size, uint32_t expect_width,
37-
uint32_t expect_height) {
37+
uint32_t expect_height, bool expect_alpha = false) {
3838
ASSERT_EQ(avifDecoderSetIOMemory(decoder_.get(), data, size),
3939
AVIF_RESULT_OK);
4040
ASSERT_EQ(avifDecoderParse(decoder_.get()), AVIF_RESULT_OK);
4141
ASSERT_EQ(decoder_->progressiveState, AVIF_PROGRESSIVE_STATE_ACTIVE);
4242
ASSERT_EQ(static_cast<uint32_t>(decoder_->imageCount),
4343
encoder_->extraLayerCount + 1);
44+
EXPECT_EQ(decoder_->alphaPresent, expect_alpha);
4445

4546
for (uint32_t layer = 0; layer < encoder_->extraLayerCount + 1; ++layer) {
4647
ASSERT_EQ(avifDecoderNextImage(decoder_.get()), AVIF_RESULT_OK);
4748
// libavif scales frame automatically.
4849
ASSERT_EQ(decoder_->image->width, expect_width);
4950
ASSERT_EQ(decoder_->image->height, expect_height);
51+
if (expect_alpha) {
52+
ASSERT_NE(decoder_->image->alphaPlane, nullptr);
53+
}
5054
// TODO(wtc): Check avifDecoderNthImageMaxExtent().
5155
}
5256
}
5357

54-
void TestDecode(uint32_t expect_width, uint32_t expect_height) {
58+
void TestDecode(uint32_t expect_width, uint32_t expect_height,
59+
bool expect_alpha = false) {
5560
TestDecode(encoded_avif_.data, encoded_avif_.size, expect_width,
56-
expect_height);
61+
expect_height, expect_alpha);
5762

5863
// TODO(wtc): Check decoder_->image and image_ are similar, and better
5964
// quality layer is more similar.
@@ -147,6 +152,41 @@ TEST_F(ProgressiveTest, DimensionChange) {
147152
TestDecode(kImageSize, kImageSize);
148153
}
149154

155+
TEST_F(ProgressiveTest, DimensionChangeWithAlpha) {
156+
if (avifLibYUVVersion() == 0) {
157+
GTEST_SKIP() << "libyuv not available, skip test.";
158+
}
159+
160+
const auto image =
161+
testutil::CreateImage(kImageSize, kImageSize, 8, AVIF_PIXEL_FORMAT_YUV444,
162+
AVIF_PLANES_ALL, AVIF_RANGE_FULL);
163+
ASSERT_NE(image, nullptr);
164+
testutil::FillImageGradient(image.get());
165+
166+
encoder_->extraLayerCount = 2;
167+
encoder_->quality = 100;
168+
encoder_->scalingMode = {{1, 2}, {1, 2}};
169+
170+
ASSERT_EQ(avifEncoderAddImage(encoder_.get(), image.get(), 1,
171+
AVIF_ADD_IMAGE_FLAG_NONE),
172+
AVIF_RESULT_OK);
173+
174+
// Encode the small image twice to verify frame buffer reallocation
175+
// behavior during decode
176+
ASSERT_EQ(avifEncoderAddImage(encoder_.get(), image.get(), 1,
177+
AVIF_ADD_IMAGE_FLAG_NONE),
178+
AVIF_RESULT_OK);
179+
180+
encoder_->scalingMode = {{1, 1}, {1, 1}};
181+
ASSERT_EQ(avifEncoderAddImage(encoder_.get(), image.get(), 1,
182+
AVIF_ADD_IMAGE_FLAG_NONE),
183+
AVIF_RESULT_OK);
184+
185+
ASSERT_EQ(avifEncoderFinish(encoder_.get(), &encoded_avif_), AVIF_RESULT_OK);
186+
187+
TestDecode(kImageSize, kImageSize, /*expect_alpha=*/true);
188+
}
189+
150190
TEST_F(ProgressiveTest, LayeredGrid) {
151191
encoder_->extraLayerCount = 1;
152192
encoder_->quality = 21;

0 commit comments

Comments
 (0)