Disable Sample Transform decoding by default (#3018)
Add AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS.
Add --sato flag to avifdec.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b738639..b886768 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,14 +20,19 @@
a cICP chunk and other color information chunks, such as iCCP (ICC profile),
the other chunks are ignored as per the PNG Specification Third Edition
Section 4.3.
+* Support reading Sample-Transform-based 16-bit AVIF files when
+ avifDecoder::imageContentToDecode & AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS is
+ not zero.
* Support Sample Transform derived image items with grid input image items.
+* Add --sato flag to avifdec to enable Sample Transforms support at decoding.
* Add --grid option to avifgainmaputil.
### Changed since 1.3.0
* Set avifDecoder::image->depth to the same value after avifDecoderParse() as
- after avifDecoderNextImage() when AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM is
- enabled and when the file to decode contains a 'sato' derived image item.
+ after avifDecoderNextImage() when the file to decode contains a 'sato' derived
+ image item.
+* avifdec only enables Sample Transform decoding when --depth is set to 16.
* Update dav1d.cmd/dav1d_android.sh/LocalDav1d.cmake: 1.5.3
* Update googletest.cmd/LocalGTest.cmake: v1.17.0
* Update libjpeg.cmd/LocalJpeg.cmake: 3.1.3
diff --git a/apps/avifdec.c b/apps/avifdec.c
index d937aeb..08cff7b 100644
--- a/apps/avifdec.c
+++ b/apps/avifdec.c
@@ -34,6 +34,7 @@
printf(" -j,--jobs J : Number of jobs (worker threads), or 'all' to potentially use as many cores as possible. (Default: all)\n");
printf(" -c,--codec C : Codec to use (choose from versions list below)\n");
printf(" -d,--depth D : Output depth, either 8 or 16. (PNG only; For y4m, depth is retained, and JPEG is always 8bpc)\n");
+ printf(" --sato : Enable Sample Transforms decoding (e.g. 16-bit AVIF)\n");
printf(" -q,--quality Q : Output quality in 0..100. (JPEG only, default: %d)\n", DEFAULT_JPEG_QUALITY);
printf(" --png-compress L : PNG compression level in 0..9 (PNG only; 0=none, 9=max). Defaults to libpng's builtin default\n");
printf(" -u,--upsampling U : Chroma upsampling (for 420/422). One of 'automatic' (default), 'fastest', 'best', 'nearest', or 'bilinear'\n");
@@ -88,6 +89,7 @@
const char * inputFilename = NULL;
const char * outputFilename = NULL;
int requestedDepth = 0;
+ avifBool enableSampleTransforms = AVIF_FALSE;
int jobs = -1;
int jpegQuality = DEFAULT_JPEG_QUALITY;
int pngCompressionLevel = -1; // -1 is a sentinel to avifPNGWrite() to skip calling png_set_compression_level()
@@ -168,6 +170,8 @@
fprintf(stderr, "ERROR: invalid depth: %s\n", arg);
return 1;
}
+ } else if (!strcmp(arg, "--sato")) {
+ enableSampleTransforms = AVIF_TRUE;
} else if (!strcmp(arg, "-q") || !strcmp(arg, "--quality")) {
NEXTARG();
jpegQuality = atoi(arg);
@@ -312,7 +316,9 @@
decoder->strictFlags = strictFlags;
decoder->allowProgressive = allowProgressive;
if (infoOnly) {
- decoder->imageContentToDecode = AVIF_IMAGE_CONTENT_ALL;
+ decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP | AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
+ } else if (enableSampleTransforms) {
+ decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
}
avifResult result = avifDecoderSetIOFile(decoder, inputFilename);
diff --git a/include/avif/avif.h b/include/avif/avif.h
index 204a96e..5650c80 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -1234,6 +1234,11 @@
AVIF_IMAGE_CONTENT_GAIN_MAP = (1 << 2),
AVIF_IMAGE_CONTENT_ALL = AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_GAIN_MAP,
+ // Mostly used for bit depth extensions to go beyond the underlying codec capability
+ // (e.g. 16-bit AVIF). Not part of AVIF_IMAGE_CONTENT_ALL as this is a rare use case.
+ // Has no effect without AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA.
+ AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS = (1 << 3),
+
AVIF_IMAGE_CONTENT_DECODE_DEFAULT = AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA,
} avifImageContentTypeFlag;
typedef uint32_t avifImageContentTypeFlags;
diff --git a/src/read.c b/src/read.c
index 7789541..2c16c0f 100644
--- a/src/read.c
+++ b/src/read.c
@@ -6242,7 +6242,8 @@
// AVIF_ITEM_SAMPLE_TRANSFORM (not used through mainItems because not a coded item (well grids are not coded items either but it's different)).
avifDecoderItem * const sampleTransformItem = avifDecoderDataFindSampleTransformImageItem(data);
- if (sampleTransformItem != NULL) {
+ if ((decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA) &&
+ (decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS) && sampleTransformItem != NULL) {
AVIF_ASSERT_OR_RETURN(data->sampleTransformNumInputImageItems == 0);
for (uint32_t i = 0; i < data->meta->items.count; ++i) {
@@ -6367,12 +6368,14 @@
AVIF_CHECKRES(avifDecoderAdoptGridTileCodecTypeIfNeeded(decoder, mainItems[c], &data->tileInfos[c]));
- if (c == AVIF_ITEM_COLOR || c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_COLOR ||
- c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_1_COLOR || c == AVIF_ITEM_ALPHA ||
- c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_ALPHA || c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_1_ALPHA) {
+ if (c == AVIF_ITEM_COLOR || c == AVIF_ITEM_ALPHA) {
if (!(decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA)) {
continue;
}
+ } else if (c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_COLOR || c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_1_COLOR ||
+ c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_ALPHA || c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_1_ALPHA) {
+ AVIF_ASSERT_OR_RETURN((decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA) &&
+ (decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS));
} else {
AVIF_ASSERT_OR_RETURN(c == AVIF_ITEM_GAIN_MAP);
if (!(decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_GAIN_MAP)) {
@@ -6942,7 +6945,6 @@
// decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA was equal to 0.
// Only apply Sample Transforms if there is a color item to apply it onto.
if (decoder->data->tileInfos[AVIF_ITEM_COLOR].tileCount != 0 && decoder->data->meta->sampleTransformExpression.count > 0) {
- // TODO(yguyon): Add a field in avifDecoder and only perform sample transformations upon request.
AVIF_CHECKRES(avifDecoderApplySampleTransform(decoder, decoder->image));
}
diff --git a/tests/gtest/avif16bittest.cc b/tests/gtest/avif16bittest.cc
index d6151a6..d522338 100644
--- a/tests/gtest/avif16bittest.cc
+++ b/tests/gtest/avif16bittest.cc
@@ -83,8 +83,15 @@
}
ASSERT_EQ(avifEncoderFinish(encoder.get(), &encoded), AVIF_RESULT_OK);
- const ImagePtr decoded = testutil::Decode(encoded.data, encoded.size);
+ ImagePtr decoded(avifImageCreateEmpty());
ASSERT_NE(decoded, nullptr);
+ DecoderPtr decoder(avifDecoderCreate());
+ ASSERT_NE(decoder, nullptr);
+ decoder->imageContentToDecode =
+ AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
+ ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
+ encoded.size),
+ AVIF_RESULT_OK);
ASSERT_EQ(image->depth, decoded->depth);
ASSERT_EQ(image->width, decoded->width);
@@ -100,8 +107,14 @@
std::memcpy(&encoded.data[i], "zzzz", 4);
}
}
- const ImagePtr decoded_no_sato = testutil::Decode(encoded.data, encoded.size);
+ ImagePtr decoded_no_sato(avifImageCreateEmpty());
ASSERT_NE(decoded_no_sato, nullptr);
+ DecoderPtr decoder_no_sato(avifDecoderCreate());
+ ASSERT_NE(decoder_no_sato, nullptr);
+ decoder_no_sato->imageContentToDecode = decoder->imageContentToDecode;
+ ASSERT_EQ(avifDecoderReadMemory(decoder_no_sato.get(), decoded_no_sato.get(),
+ encoded.data, encoded.size),
+ AVIF_RESULT_OK);
// Only the most significant bits of each sample can be retrieved.
// They should be encoded losslessly no matter the quantizer settings.
ImagePtr image_no_sato = testutil::CreateImage(
@@ -297,12 +310,13 @@
ASSERT_NE(decoded, nullptr);
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
- decoder->imageContentToDecode = content_to_decode;
+ decoder->imageContentToDecode =
+ content_to_decode | AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
encoded.size),
- content_to_decode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA ||
+ (content_to_decode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA) ||
(create_gainmap &&
- content_to_decode & AVIF_IMAGE_CONTENT_GAIN_MAP)
+ (content_to_decode & AVIF_IMAGE_CONTENT_GAIN_MAP))
? AVIF_RESULT_OK
: AVIF_RESULT_NO_CONTENT);
@@ -331,6 +345,7 @@
// Gain maps are not supported in the same file as 'sato' items.
/*create_gainmap=*/testing::Values(false),
/*use_grid=*/testing::Values(true),
+ // AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS is always set in this test.
testing::Values(AVIF_IMAGE_CONTENT_NONE,
AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA,
AVIF_IMAGE_CONTENT_GAIN_MAP, AVIF_IMAGE_CONTENT_ALL)));
diff --git a/tests/gtest/avif_fuzztest_dec_incr.cc b/tests/gtest/avif_fuzztest_dec_incr.cc
index a8299f6..2bdb7d1 100644
--- a/tests/gtest/avif_fuzztest_dec_incr.cc
+++ b/tests/gtest/avif_fuzztest_dec_incr.cc
@@ -117,7 +117,8 @@
/*give_size_hint=*/Arbitrary<bool>(),
fuzztest::BitFlagCombinationOf<avifImageContentTypeFlags>(
{AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA,
- AVIF_IMAGE_CONTENT_GAIN_MAP}),
+ AVIF_IMAGE_CONTENT_GAIN_MAP,
+ AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS}),
/*use_nth_image_api=*/Arbitrary<bool>());
//------------------------------------------------------------------------------
diff --git a/tests/gtest/avif_fuzztest_helpers.h b/tests/gtest/avif_fuzztest_helpers.h
index 827c2bd..0932e01 100644
--- a/tests/gtest/avif_fuzztest_helpers.h
+++ b/tests/gtest/avif_fuzztest_helpers.h
@@ -297,7 +297,11 @@
AddGainMapOptionsToDecoder, ArbitraryBaseAvifDecoder(),
fuzztest::ElementOf<avifImageContentTypeFlags>(
{AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA,
- AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_GAIN_MAP}));
+ AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_GAIN_MAP,
+ AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA |
+ AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS,
+ AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_GAIN_MAP |
+ AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS}));
}
// Generator for an arbitrary DecoderPtr.
diff --git a/tests/gtest/avifaltrtest.cc b/tests/gtest/avifaltrtest.cc
index f1c1a01..ee96515 100644
--- a/tests/gtest/avifaltrtest.cc
+++ b/tests/gtest/avifaltrtest.cc
@@ -39,6 +39,7 @@
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);
@@ -59,6 +60,7 @@
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);
@@ -84,6 +86,20 @@
AVIF_RESULT_NO_CONTENT);
}
+TEST(AltrTest, OnlySampleTranformContentToDecode) {
+ const std::string file_path =
+ std::string(data_path) + "weld_sato_12B_8B_q0.avif";
+
+ DecoderPtr decoder(avifDecoderCreate());
+ ASSERT_NE(decoder, nullptr);
+ // Has no effect without AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA.
+ decoder->imageContentToDecode = AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
+ ImagePtr image(avifImageCreateEmpty());
+ ASSERT_NE(image, nullptr);
+ ASSERT_EQ(avifDecoderReadFile(decoder.get(), image.get(), file_path.c_str()),
+ AVIF_RESULT_NO_CONTENT);
+}
+
//------------------------------------------------------------------------------
} // namespace
diff --git a/tests/gtest/avifdecodetest.cc b/tests/gtest/avifdecodetest.cc
index ec8ca68..699f01a 100644
--- a/tests/gtest/avifdecodetest.cc
+++ b/tests/gtest/avifdecodetest.cc
@@ -40,19 +40,28 @@
for (const std::string file_name :
{"paris_icc_exif_xmp.avif", "draw_points_idat.avif",
"sofa_grid1x5_420.avif", "color_grid_alpha_nogrid.avif",
- "seine_sdr_gainmap_srgb.avif", "draw_points_idat_progressive.avif"}) {
- SCOPED_TRACE(file_name);
- DecoderPtr decoder(avifDecoderCreate());
- ASSERT_NE(decoder, nullptr);
- // Do not decode anything.
- decoder->imageContentToDecode = AVIF_IMAGE_CONTENT_NONE;
- ASSERT_EQ(avifDecoderSetIOFile(
- decoder.get(), (std::string(data_path) + file_name).c_str()),
- AVIF_RESULT_OK);
- ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK)
- << decoder->diag.error;
- EXPECT_EQ(decoder->imageSequenceTrackPresent, AVIF_FALSE);
- EXPECT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_NO_CONTENT);
+ "seine_sdr_gainmap_srgb.avif", "draw_points_idat_progressive.avif",
+ "weld_sato_12B_8B_q0.avif"}) {
+ for (
+ avifImageContentTypeFlag image_content_to_decode : {
+ AVIF_IMAGE_CONTENT_NONE,
+ AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS // Equivalent to NONE without
+ // AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA.
+ }) {
+ SCOPED_TRACE(file_name);
+ DecoderPtr decoder(avifDecoderCreate());
+ ASSERT_NE(decoder, nullptr);
+ // Do not decode anything.
+ decoder->imageContentToDecode = image_content_to_decode;
+ ASSERT_EQ(
+ avifDecoderSetIOFile(decoder.get(),
+ (std::string(data_path) + file_name).c_str()),
+ AVIF_RESULT_OK);
+ ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK)
+ << decoder->diag.error;
+ EXPECT_EQ(decoder->imageSequenceTrackPresent, AVIF_FALSE);
+ EXPECT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_NO_CONTENT);
+ }
}
}