Reset Sample Transform state in avifDecoderReset() (#3327)
Clear the parsed Sample Transform expression and input count before the decode input is rebuilt. Otherwise a second reset, including one performed by avifDecoderSetSource(), encounters stale state and returns AVIF_RESULT_INTERNAL_ERROR, or asserts in debug builds.
Add coverage for direct reset and avifDecoderSetSource().
Signed-off-by: David Sarkisyan <david@srkyn.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 472f710..31d7841 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,8 @@
* Parse the children of the 'iref' and 'grpl' boxes within their own
declared box size. Files with an EntityToGroupBox carrying grouping type
specific data after the entity_id array are no longer rejected.
+* Reset Sample Transform decoder state in avifDecoderReset() so repeated resets
+ and avifDecoderSetSource() calls do not fail.
## [1.4.2] - 2026-05-26
diff --git a/src/read.c b/src/read.c
index 9709e1e..29817af 100644
--- a/src/read.c
+++ b/src/read.c
@@ -6093,6 +6093,8 @@
memset(&data->tileInfos[c].grid, 0, sizeof(data->tileInfos[c].grid));
}
avifDecoderDataClearTiles(data);
+ data->sampleTransformNumInputImageItems = 0;
+ avifArrayDestroy(&data->meta->sampleTransformExpression);
// Prepare / cleanup decoded image state
if (decoder->image) {
diff --git a/tests/gtest/avifaltrtest.cc b/tests/gtest/avifaltrtest.cc
index 28a0822..0f0640a 100644
--- a/tests/gtest/avifaltrtest.cc
+++ b/tests/gtest/avifaltrtest.cc
@@ -73,6 +73,28 @@
EXPECT_EQ(decoder->image->depth, depth);
}
+TEST(AltrTest, SampleTransformDecoderReset) {
+ const testutil::AvifRwData encoded =
+ testutil::ReadFile(std::string(data_path) + "weld_sato_12B_8B_q0.avif");
+
+ DecoderPtr decoder(avifDecoderCreate());
+ ASSERT_NE(decoder, nullptr);
+ decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
+ ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), encoded.data, encoded.size),
+ AVIF_RESULT_OK);
+
+ ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);
+ ASSERT_EQ(avifDecoderReset(decoder.get()), AVIF_RESULT_OK);
+ ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
+ EXPECT_EQ(decoder->image->depth, 16u);
+
+ // avifDecoderSetSource() automatically resets an already parsed decoder.
+ ASSERT_EQ(avifDecoderSetSource(decoder.get(), AVIF_DECODER_SOURCE_AUTO),
+ AVIF_RESULT_OK);
+ ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
+ EXPECT_EQ(decoder->image->depth, 16u);
+}
+
// Verifies the fix for https://github.com/AOMediaCodec/libavif/issues/2979.
TEST(AltrTest, ZeroImageContentToDecode) {
const std::string file_path =