Skip to content

Commit 35e290d

Browse files
authored
Disable Sample Transform decoding by default (#3018)
Add AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS. Add --sato flag to avifdec.
1 parent 31ca095 commit 35e290d

9 files changed

Lines changed: 91 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@ The changes are relative to the previous release, unless the baseline is specifi
2020
a cICP chunk and other color information chunks, such as iCCP (ICC profile),
2121
the other chunks are ignored as per the PNG Specification Third Edition
2222
Section 4.3.
23+
* Support reading Sample-Transform-based 16-bit AVIF files when
24+
avifDecoder::imageContentToDecode & AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS is
25+
not zero.
2326
* Support Sample Transform derived image items with grid input image items.
27+
* Add --sato flag to avifdec to enable Sample Transforms support at decoding.
2428
* Add --grid option to avifgainmaputil.
2529

2630
### Changed since 1.3.0
2731

2832
* Set avifDecoder::image->depth to the same value after avifDecoderParse() as
29-
after avifDecoderNextImage() when AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM is
30-
enabled and when the file to decode contains a 'sato' derived image item.
33+
after avifDecoderNextImage() when the file to decode contains a 'sato' derived
34+
image item.
35+
* avifdec only enables Sample Transform decoding when --depth is set to 16.
3136
* Update dav1d.cmd/dav1d_android.sh/LocalDav1d.cmake: 1.5.3
3237
* Update googletest.cmd/LocalGTest.cmake: v1.17.0
3338
* Update libjpeg.cmd/LocalJpeg.cmake: 3.1.3

apps/avifdec.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ static void syntax(void)
3434
printf(" -j,--jobs J : Number of jobs (worker threads), or 'all' to potentially use as many cores as possible. (Default: all)\n");
3535
printf(" -c,--codec C : Codec to use (choose from versions list below)\n");
3636
printf(" -d,--depth D : Output depth, either 8 or 16. (PNG only; For y4m, depth is retained, and JPEG is always 8bpc)\n");
37+
printf(" --sato : Enable Sample Transforms decoding (e.g. 16-bit AVIF)\n");
3738
printf(" -q,--quality Q : Output quality in 0..100. (JPEG only, default: %d)\n", DEFAULT_JPEG_QUALITY);
3839
printf(" --png-compress L : PNG compression level in 0..9 (PNG only; 0=none, 9=max). Defaults to libpng's builtin default\n");
3940
printf(" -u,--upsampling U : Chroma upsampling (for 420/422). One of 'automatic' (default), 'fastest', 'best', 'nearest', or 'bilinear'\n");
@@ -88,6 +89,7 @@ int main(int argc, char * argv[])
8889
const char * inputFilename = NULL;
8990
const char * outputFilename = NULL;
9091
int requestedDepth = 0;
92+
avifBool enableSampleTransforms = AVIF_FALSE;
9193
int jobs = -1;
9294
int jpegQuality = DEFAULT_JPEG_QUALITY;
9395
int pngCompressionLevel = -1; // -1 is a sentinel to avifPNGWrite() to skip calling png_set_compression_level()
@@ -168,6 +170,8 @@ int main(int argc, char * argv[])
168170
fprintf(stderr, "ERROR: invalid depth: %s\n", arg);
169171
return 1;
170172
}
173+
} else if (!strcmp(arg, "--sato")) {
174+
enableSampleTransforms = AVIF_TRUE;
171175
} else if (!strcmp(arg, "-q") || !strcmp(arg, "--quality")) {
172176
NEXTARG();
173177
jpegQuality = atoi(arg);
@@ -312,7 +316,9 @@ int main(int argc, char * argv[])
312316
decoder->strictFlags = strictFlags;
313317
decoder->allowProgressive = allowProgressive;
314318
if (infoOnly) {
315-
decoder->imageContentToDecode = AVIF_IMAGE_CONTENT_ALL;
319+
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP | AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
320+
} else if (enableSampleTransforms) {
321+
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
316322
}
317323

318324
avifResult result = avifDecoderSetIOFile(decoder, inputFilename);

include/avif/avif.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,6 +1234,11 @@ typedef enum avifImageContentTypeFlag
12341234
AVIF_IMAGE_CONTENT_GAIN_MAP = (1 << 2),
12351235
AVIF_IMAGE_CONTENT_ALL = AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_GAIN_MAP,
12361236

1237+
// Mostly used for bit depth extensions to go beyond the underlying codec capability
1238+
// (e.g. 16-bit AVIF). Not part of AVIF_IMAGE_CONTENT_ALL as this is a rare use case.
1239+
// Has no effect without AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA.
1240+
AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS = (1 << 3),
1241+
12371242
AVIF_IMAGE_CONTENT_DECODE_DEFAULT = AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA,
12381243
} avifImageContentTypeFlag;
12391244
typedef uint32_t avifImageContentTypeFlags;

src/read.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6242,7 +6242,8 @@ avifResult avifDecoderReset(avifDecoder * decoder)
62426242

62436243
// AVIF_ITEM_SAMPLE_TRANSFORM (not used through mainItems because not a coded item (well grids are not coded items either but it's different)).
62446244
avifDecoderItem * const sampleTransformItem = avifDecoderDataFindSampleTransformImageItem(data);
6245-
if (sampleTransformItem != NULL) {
6245+
if ((decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA) &&
6246+
(decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS) && sampleTransformItem != NULL) {
62466247
AVIF_ASSERT_OR_RETURN(data->sampleTransformNumInputImageItems == 0);
62476248

62486249
for (uint32_t i = 0; i < data->meta->items.count; ++i) {
@@ -6367,12 +6368,14 @@ avifResult avifDecoderReset(avifDecoder * decoder)
63676368

63686369
AVIF_CHECKRES(avifDecoderAdoptGridTileCodecTypeIfNeeded(decoder, mainItems[c], &data->tileInfos[c]));
63696370

6370-
if (c == AVIF_ITEM_COLOR || c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_COLOR ||
6371-
c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_1_COLOR || c == AVIF_ITEM_ALPHA ||
6372-
c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_ALPHA || c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_1_ALPHA) {
6371+
if (c == AVIF_ITEM_COLOR || c == AVIF_ITEM_ALPHA) {
63736372
if (!(decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA)) {
63746373
continue;
63756374
}
6375+
} else if (c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_COLOR || c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_1_COLOR ||
6376+
c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_ALPHA || c == AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_1_ALPHA) {
6377+
AVIF_ASSERT_OR_RETURN((decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA) &&
6378+
(decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS));
63766379
} else {
63776380
AVIF_ASSERT_OR_RETURN(c == AVIF_ITEM_GAIN_MAP);
63786381
if (!(decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_GAIN_MAP)) {
@@ -6942,7 +6945,6 @@ avifResult avifDecoderNextImage(avifDecoder * decoder)
69426945
// decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA was equal to 0.
69436946
// Only apply Sample Transforms if there is a color item to apply it onto.
69446947
if (decoder->data->tileInfos[AVIF_ITEM_COLOR].tileCount != 0 && decoder->data->meta->sampleTransformExpression.count > 0) {
6945-
// TODO(yguyon): Add a field in avifDecoder and only perform sample transformations upon request.
69466948
AVIF_CHECKRES(avifDecoderApplySampleTransform(decoder, decoder->image));
69476949
}
69486950

tests/gtest/avif16bittest.cc

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,15 @@ TEST_P(SampleTransformTest, Avif16bit) {
8383
}
8484
ASSERT_EQ(avifEncoderFinish(encoder.get(), &encoded), AVIF_RESULT_OK);
8585

86-
const ImagePtr decoded = testutil::Decode(encoded.data, encoded.size);
86+
ImagePtr decoded(avifImageCreateEmpty());
8787
ASSERT_NE(decoded, nullptr);
88+
DecoderPtr decoder(avifDecoderCreate());
89+
ASSERT_NE(decoder, nullptr);
90+
decoder->imageContentToDecode =
91+
AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
92+
ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
93+
encoded.size),
94+
AVIF_RESULT_OK);
8895

8996
ASSERT_EQ(image->depth, decoded->depth);
9097
ASSERT_EQ(image->width, decoded->width);
@@ -100,8 +107,14 @@ TEST_P(SampleTransformTest, Avif16bit) {
100107
std::memcpy(&encoded.data[i], "zzzz", 4);
101108
}
102109
}
103-
const ImagePtr decoded_no_sato = testutil::Decode(encoded.data, encoded.size);
110+
ImagePtr decoded_no_sato(avifImageCreateEmpty());
104111
ASSERT_NE(decoded_no_sato, nullptr);
112+
DecoderPtr decoder_no_sato(avifDecoderCreate());
113+
ASSERT_NE(decoder_no_sato, nullptr);
114+
decoder_no_sato->imageContentToDecode = decoder->imageContentToDecode;
115+
ASSERT_EQ(avifDecoderReadMemory(decoder_no_sato.get(), decoded_no_sato.get(),
116+
encoded.data, encoded.size),
117+
AVIF_RESULT_OK);
105118
// Only the most significant bits of each sample can be retrieved.
106119
// They should be encoded losslessly no matter the quantizer settings.
107120
ImagePtr image_no_sato = testutil::CreateImage(
@@ -297,12 +310,13 @@ TEST_P(GainmapSampleTransformTest, ImageContentToDecode) {
297310
ASSERT_NE(decoded, nullptr);
298311
DecoderPtr decoder(avifDecoderCreate());
299312
ASSERT_NE(decoder, nullptr);
300-
decoder->imageContentToDecode = content_to_decode;
313+
decoder->imageContentToDecode =
314+
content_to_decode | AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
301315
ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
302316
encoded.size),
303-
content_to_decode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA ||
317+
(content_to_decode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA) ||
304318
(create_gainmap &&
305-
content_to_decode & AVIF_IMAGE_CONTENT_GAIN_MAP)
319+
(content_to_decode & AVIF_IMAGE_CONTENT_GAIN_MAP))
306320
? AVIF_RESULT_OK
307321
: AVIF_RESULT_NO_CONTENT);
308322

@@ -331,6 +345,7 @@ INSTANTIATE_TEST_SUITE_P(
331345
// Gain maps are not supported in the same file as 'sato' items.
332346
/*create_gainmap=*/testing::Values(false),
333347
/*use_grid=*/testing::Values(true),
348+
// AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS is always set in this test.
334349
testing::Values(AVIF_IMAGE_CONTENT_NONE,
335350
AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA,
336351
AVIF_IMAGE_CONTENT_GAIN_MAP, AVIF_IMAGE_CONTENT_ALL)));

tests/gtest/avif_fuzztest_dec_incr.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ FUZZ_TEST(DecodeAvifFuzzTest, DecodeIncr)
117117
/*give_size_hint=*/Arbitrary<bool>(),
118118
fuzztest::BitFlagCombinationOf<avifImageContentTypeFlags>(
119119
{AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA,
120-
AVIF_IMAGE_CONTENT_GAIN_MAP}),
120+
AVIF_IMAGE_CONTENT_GAIN_MAP,
121+
AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS}),
121122
/*use_nth_image_api=*/Arbitrary<bool>());
122123

123124
//------------------------------------------------------------------------------

tests/gtest/avif_fuzztest_helpers.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,11 @@ inline auto ArbitraryAvifDecoderWithGainMapOptions() {
297297
AddGainMapOptionsToDecoder, ArbitraryBaseAvifDecoder(),
298298
fuzztest::ElementOf<avifImageContentTypeFlags>(
299299
{AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA,
300-
AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_GAIN_MAP}));
300+
AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_GAIN_MAP,
301+
AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA |
302+
AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS,
303+
AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_GAIN_MAP |
304+
AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS}));
301305
}
302306

303307
// Generator for an arbitrary DecoderPtr.

tests/gtest/avifaltrtest.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ TEST(AltrTest, SampleTransformDepthEqualToInput) {
3939

4040
DecoderPtr decoder(avifDecoderCreate());
4141
ASSERT_NE(decoder, nullptr);
42+
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
4243
ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), encoded.data, encoded.size),
4344
AVIF_RESULT_OK);
4445
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);
@@ -59,6 +60,7 @@ TEST(AltrTest, SampleTransformDepthParseNextEqual) {
5960

6061
DecoderPtr decoder(avifDecoderCreate());
6162
ASSERT_NE(decoder, nullptr);
63+
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
6264
ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), encoded.data, encoded.size),
6365
AVIF_RESULT_OK);
6466

@@ -84,6 +86,20 @@ TEST(AltrTest, ZeroImageContentToDecode) {
8486
AVIF_RESULT_NO_CONTENT);
8587
}
8688

89+
TEST(AltrTest, OnlySampleTranformContentToDecode) {
90+
const std::string file_path =
91+
std::string(data_path) + "weld_sato_12B_8B_q0.avif";
92+
93+
DecoderPtr decoder(avifDecoderCreate());
94+
ASSERT_NE(decoder, nullptr);
95+
// Has no effect without AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA.
96+
decoder->imageContentToDecode = AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
97+
ImagePtr image(avifImageCreateEmpty());
98+
ASSERT_NE(image, nullptr);
99+
ASSERT_EQ(avifDecoderReadFile(decoder.get(), image.get(), file_path.c_str()),
100+
AVIF_RESULT_NO_CONTENT);
101+
}
102+
87103
//------------------------------------------------------------------------------
88104

89105
} // namespace

tests/gtest/avifdecodetest.cc

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,28 @@ TEST(AvifDecodeTest, ImageContentToDecodeNone) {
4040
for (const std::string file_name :
4141
{"paris_icc_exif_xmp.avif", "draw_points_idat.avif",
4242
"sofa_grid1x5_420.avif", "color_grid_alpha_nogrid.avif",
43-
"seine_sdr_gainmap_srgb.avif", "draw_points_idat_progressive.avif"}) {
44-
SCOPED_TRACE(file_name);
45-
DecoderPtr decoder(avifDecoderCreate());
46-
ASSERT_NE(decoder, nullptr);
47-
// Do not decode anything.
48-
decoder->imageContentToDecode = AVIF_IMAGE_CONTENT_NONE;
49-
ASSERT_EQ(avifDecoderSetIOFile(
50-
decoder.get(), (std::string(data_path) + file_name).c_str()),
51-
AVIF_RESULT_OK);
52-
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK)
53-
<< decoder->diag.error;
54-
EXPECT_EQ(decoder->imageSequenceTrackPresent, AVIF_FALSE);
55-
EXPECT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_NO_CONTENT);
43+
"seine_sdr_gainmap_srgb.avif", "draw_points_idat_progressive.avif",
44+
"weld_sato_12B_8B_q0.avif"}) {
45+
for (
46+
avifImageContentTypeFlag image_content_to_decode : {
47+
AVIF_IMAGE_CONTENT_NONE,
48+
AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS // Equivalent to NONE without
49+
// AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA.
50+
}) {
51+
SCOPED_TRACE(file_name);
52+
DecoderPtr decoder(avifDecoderCreate());
53+
ASSERT_NE(decoder, nullptr);
54+
// Do not decode anything.
55+
decoder->imageContentToDecode = image_content_to_decode;
56+
ASSERT_EQ(
57+
avifDecoderSetIOFile(decoder.get(),
58+
(std::string(data_path) + file_name).c_str()),
59+
AVIF_RESULT_OK);
60+
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK)
61+
<< decoder->diag.error;
62+
EXPECT_EQ(decoder->imageSequenceTrackPresent, AVIF_FALSE);
63+
EXPECT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_NO_CONTENT);
64+
}
5665
}
5766
}
5867

0 commit comments

Comments
 (0)