Skip to content

Commit

Permalink
Only generate valid fuzzed avifGainMapMetadata (AOMediaCodec#2441)
Browse files Browse the repository at this point in the history
Also add avifGainMapMetadataValidate() to verify it is valid before
encoding.
  • Loading branch information
y-guyon authored Sep 25, 2024
1 parent ca3e4be commit 2fe5b3a
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 40 deletions.
2 changes: 2 additions & 0 deletions include/avif/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,8 @@ AVIF_NODISCARD avifBool avifSequenceHeaderParse(avifSequenceHeader * header, con
// Removing outliers helps with accuracy/compression.
avifResult avifFindMinMaxWithoutOutliers(const float * gainMapF, int numPixels, float * rangeMin, float * rangeMax);

avifResult avifGainMapMetadataValidate(const avifGainMapMetadata * metadata, avifDiagnostics * diag);

#endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP

#define AVIF_INDEFINITE_DURATION64 UINT64_MAX
Expand Down
10 changes: 10 additions & 0 deletions src/avif.c
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,16 @@ avifGainMap * avifGainMapCreate(void)
gainMap->altMatrixCoefficients = AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED;
gainMap->altYUVRange = AVIF_RANGE_FULL;
gainMap->metadata.useBaseColorSpace = AVIF_TRUE;
// Set all denominators to valid values (1).
for (int i = 0; i < 3; ++i) {
gainMap->metadata.gainMapMinD[i] = 1;
gainMap->metadata.gainMapMaxD[i] = 1;
gainMap->metadata.gainMapGammaD[i] = 1;
gainMap->metadata.baseOffsetD[i] = 1;
gainMap->metadata.alternateOffsetD[i] = 1;
}
gainMap->metadata.baseHdrHeadroomD = 1;
gainMap->metadata.alternateHdrHeadroomD = 1;
return gainMap;
}

Expand Down
20 changes: 20 additions & 0 deletions src/gainmap.c
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,26 @@ avifResult avifFindMinMaxWithoutOutliers(const float * gainMapF, int numPixels,
return AVIF_RESULT_OK;
}

avifResult avifGainMapMetadataValidate(const avifGainMapMetadata * metadata, avifDiagnostics * diag)
{
for (int i = 0; i < 3; ++i) {
if (metadata->gainMapMinD[i] == 0 || metadata->gainMapMaxD[i] == 0 || metadata->gainMapGammaD[i] == 0 ||
metadata->baseOffsetD[i] == 0 || metadata->alternateOffsetD[i] == 0) {
avifDiagnosticsPrintf(diag, "Per-channel denominator is 0 in avifGainMapMetadata");
return AVIF_RESULT_INVALID_ARGUMENT;
}
}
if (metadata->baseHdrHeadroomD == 0 || metadata->alternateHdrHeadroomD == 0) {
avifDiagnosticsPrintf(diag, "Headroom denominator is 0 in avifGainMapMetadata");
return AVIF_RESULT_INVALID_ARGUMENT;
}
if (metadata->useBaseColorSpace != 0 && metadata->useBaseColorSpace != 1) {
avifDiagnosticsPrintf(diag, "useBaseColorSpace is %d in avifGainMapMetadata", metadata->useBaseColorSpace);
return AVIF_RESULT_INVALID_ARGUMENT;
}
return AVIF_RESULT_OK;
}

static const float kEpsilon = 1e-10f;

// Decides which of 'basePrimaries' or 'altPrimaries' should be used for doing gain map math when creating a gain map.
Expand Down
10 changes: 6 additions & 4 deletions src/write.c
Original file line number Diff line number Diff line change
Expand Up @@ -914,8 +914,9 @@ static avifBool avifGainmapMetadataSize(const avifGainMapMetadata * metadata)
return sizeof(uint16_t) * 2 + sizeof(uint8_t) + sizeof(uint32_t) * 4 + channelCount * sizeof(uint32_t) * 10;
}

static avifResult avifWriteGainmapMetadata(avifRWStream * s, const avifGainMapMetadata * metadata)
static avifResult avifWriteGainmapMetadata(avifRWStream * s, const avifGainMapMetadata * metadata, avifDiagnostics * diag)
{
AVIF_CHECKRES(avifGainMapMetadataValidate(metadata, diag));
const size_t offset = avifRWStreamOffset(s);

// GainMapMetadata syntax as per clause C.2.2 of ISO 21496-1:
Expand Down Expand Up @@ -957,7 +958,7 @@ static avifResult avifWriteGainmapMetadata(avifRWStream * s, const avifGainMapMe
return AVIF_RESULT_OK;
}

static avifResult avifWriteToneMappedImagePayload(avifRWData * data, const avifGainMapMetadata * metadata)
static avifResult avifWriteToneMappedImagePayload(avifRWData * data, const avifGainMapMetadata * metadata, avifDiagnostics * diag)
{
avifRWStream s;
avifRWStreamStart(&s, data);
Expand All @@ -966,7 +967,7 @@ static avifResult avifWriteToneMappedImagePayload(avifRWData * data, const avifG
const uint8_t version = 0;
AVIF_CHECKRES(avifRWStreamWriteU8(&s, version)); // unsigned int(8) version = 0;
if (version == 0) {
AVIF_CHECKRES(avifWriteGainmapMetadata(&s, metadata)); // GainMapMetadata;
AVIF_CHECKRES(avifWriteGainmapMetadata(&s, metadata, diag)); // GainMapMetadata;
}
avifRWStreamFinishWrite(&s);
return AVIF_RESULT_OK;
Expand Down Expand Up @@ -1868,7 +1869,8 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
infeNameGainMap,
/*infeNameSize=*/strlen(infeNameGainMap) + 1,
/*cellIndex=*/0);
AVIF_CHECKRES(avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, &firstCell->gainMap->metadata));
AVIF_CHECKRES(
avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, &firstCell->gainMap->metadata, &encoder->diag));
// Even though the 'tmap' item is related to the gain map, it represents a color image and its metadata is more similar to the color item.
toneMappedItem->itemCategory = AVIF_ITEM_COLOR;
uint16_t toneMappedItemID = toneMappedItem->id;
Expand Down
18 changes: 0 additions & 18 deletions tests/gtest/avif_fuzztest_enc_dec_experimental.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,24 +97,6 @@ void EncodeDecodeValid(ImagePtr image, EncoderPtr encoder, DecoderPtr decoder) {
// hard to verify so do not check it.
}

// Note that avifGainMapMetadata is passed as a byte array
// because the C array fields in the struct seem to prevent fuzztest from
// handling it natively.
ImagePtr AddGainMapToImage(
ImagePtr image, ImagePtr gain_map,
const std::array<uint8_t, sizeof(avifGainMapMetadata)>& metadata) {
image->gainMap = avifGainMapCreate();
image->gainMap->image = gain_map.release();
std::memcpy(&image->gainMap->metadata, metadata.data(), metadata.size());
return image;
}

inline auto ArbitraryAvifImageWithGainMap() {
return fuzztest::Map(
AddGainMapToImage, ArbitraryAvifImage(), ArbitraryAvifImage(),
fuzztest::Arbitrary<std::array<uint8_t, sizeof(avifGainMapMetadata)>>());
}

FUZZ_TEST(EncodeDecodeAvifFuzzTest, EncodeDecodeValid)
.WithDomains(fuzztest::OneOf(ArbitraryAvifImage(),
ArbitraryAvifImageWithGainMap()),
Expand Down
18 changes: 0 additions & 18 deletions tests/gtest/avif_fuzztest_enc_dec_incr_experimental.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,24 +97,6 @@ void EncodeDecodeGridValid(ImagePtr image, EncoderPtr encoder,
ASSERT_EQ(decode_result, AVIF_RESULT_OK) << avifResultToString(decode_result);
}

// Note that avifGainMapMetadata is passed as a byte array
// because the C array fields in the struct seem to prevent fuzztest from
// handling it natively.
ImagePtr AddGainMapToImage(
ImagePtr image, ImagePtr gain_map,
const std::array<uint8_t, sizeof(avifGainMapMetadata)>& metadata) {
image->gainMap = avifGainMapCreate();
image->gainMap->image = gain_map.release();
std::memcpy(&image->gainMap->metadata, metadata.data(), metadata.size());
return image;
}

inline auto ArbitraryAvifImageWithGainMap() {
return fuzztest::Map(
AddGainMapToImage, ArbitraryAvifImage(), ArbitraryAvifImage(),
fuzztest::Arbitrary<std::array<uint8_t, sizeof(avifGainMapMetadata)>>());
}

FUZZ_TEST(EncodeDecodeAvifFuzzTest, EncodeDecodeGridValid)
.WithDomains(fuzztest::OneOf(ArbitraryAvifImage(),
ArbitraryAvifImageWithGainMap()),
Expand Down
38 changes: 38 additions & 0 deletions tests/gtest/avif_fuzztest_helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,44 @@ DecoderPtr AddGainMapOptionsToDecoder(DecoderPtr decoder,
// is no longer the case when this option is on.
return decoder;
}

ImagePtr AddGainMapToImage(
ImagePtr image, ImagePtr gain_map, int32_t gain_map_min_n0,
int32_t gain_map_min_n1, int32_t gain_map_min_n2, uint32_t gain_map_min_d0,
uint32_t gain_map_min_d1, uint32_t gain_map_min_d2, int32_t gain_map_max_n0,
int32_t gain_map_max_n1, int32_t gain_map_max_n2, uint32_t gain_map_max_d0,
uint32_t gain_map_max_d1, uint32_t gain_map_max_d2,
uint32_t gain_map_gamma_n0, uint32_t gain_map_gamma_n1,
uint32_t gain_map_gamma_n2, uint32_t gain_map_gamma_d0,
uint32_t gain_map_gamma_d1, uint32_t gain_map_gamma_d2,
int32_t base_offset_n0, int32_t base_offset_n1, int32_t base_offset_n2,
uint32_t base_offset_d0, uint32_t base_offset_d1, uint32_t base_offset_d2,
int32_t alternate_offset_n0, int32_t alternate_offset_n1,
int32_t alternate_offset_n2, uint32_t alternate_offset_d0,
uint32_t alternate_offset_d1, uint32_t alternate_offset_d2,
uint32_t base_hdr_headroom_n, uint32_t base_hdr_headroom_d,
uint32_t alternate_hdr_headroom_n, uint32_t alternate_hdr_headroom_d,
bool use_base_color_space) {
image->gainMap = avifGainMapCreate();
image->gainMap->image = gain_map.release();
image->gainMap->metadata = avifGainMapMetadata{
{gain_map_min_n0, gain_map_min_n1, gain_map_min_n2},
{gain_map_min_d0, gain_map_min_d1, gain_map_min_d2},
{gain_map_max_n0, gain_map_max_n1, gain_map_max_n2},
{gain_map_max_d0, gain_map_max_d1, gain_map_max_d2},
{gain_map_gamma_n0, gain_map_gamma_n1, gain_map_gamma_n2},
{gain_map_gamma_d0, gain_map_gamma_d1, gain_map_gamma_d2},
{base_offset_n0, base_offset_n1, base_offset_n2},
{base_offset_d0, base_offset_d1, base_offset_d2},
{alternate_offset_n0, alternate_offset_n1, alternate_offset_n2},
{alternate_offset_d0, alternate_offset_d1, alternate_offset_d2},
base_hdr_headroom_n,
base_hdr_headroom_d,
alternate_hdr_headroom_n,
alternate_hdr_headroom_d,
use_base_color_space};
return image;
}
#endif

//------------------------------------------------------------------------------
Expand Down
60 changes: 60 additions & 0 deletions tests/gtest/avif_fuzztest_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <cstdint>
#include <cstdlib>
#include <limits>
#include <vector>

#include "avif/avif.h"
Expand Down Expand Up @@ -169,6 +170,65 @@ inline auto ArbitraryAvifAnim() {
return fuzztest::OneOf(ArbitraryAvifAnim8b(), ArbitraryAvifAnim16b());
}

#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
// Note that avifGainMapMetadata is passed as many individual arguments
// because the C array fields in the struct seem to prevent fuzztest from
// handling it natively.
// TODO: Try StructOf<Metadata>(StructOf<uint32_t[3]>())?
ImagePtr AddGainMapToImage(
ImagePtr image, ImagePtr gain_map, int32_t gain_map_min_n0,
int32_t gain_map_min_n1, int32_t gain_map_min_n2, uint32_t gain_map_min_d0,
uint32_t gain_map_min_d1, uint32_t gain_map_min_d2, int32_t gain_map_max_n0,
int32_t gain_map_max_n1, int32_t gain_map_max_n2, uint32_t gain_map_max_d0,
uint32_t gain_map_max_d1, uint32_t gain_map_max_d2,
uint32_t gain_map_gamma_n0, uint32_t gain_map_gamma_n1,
uint32_t gain_map_gamma_n2, uint32_t gain_map_gamma_d0,
uint32_t gain_map_gamma_d1, uint32_t gain_map_gamma_d2,
int32_t base_offset_n0, int32_t base_offset_n1, int32_t base_offset_n2,
uint32_t base_offset_d0, uint32_t base_offset_d1, uint32_t base_offset_d2,
int32_t alternate_offset_n0, int32_t alternate_offset_n1,
int32_t alternate_offset_n2, uint32_t alternate_offset_d0,
uint32_t alternate_offset_d1, uint32_t alternate_offset_d2,
uint32_t base_hdr_headroom_n, uint32_t base_hdr_headroom_d,
uint32_t alternate_hdr_headroom_n, uint32_t alternate_hdr_headroom_d,
bool use_base_color_space);

inline auto ArbitraryAvifImageWithGainMap() {
return fuzztest::Map(
AddGainMapToImage, ArbitraryAvifImage(), ArbitraryAvifImage(),
fuzztest::Arbitrary<int32_t>(), fuzztest::Arbitrary<int32_t>(),
fuzztest::Arbitrary<int32_t>(),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::Arbitrary<int32_t>(), fuzztest::Arbitrary<int32_t>(),
fuzztest::Arbitrary<int32_t>(),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::Arbitrary<uint32_t>(), fuzztest::Arbitrary<uint32_t>(),
fuzztest::Arbitrary<uint32_t>(),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::Arbitrary<int32_t>(), fuzztest::Arbitrary<int32_t>(),
fuzztest::Arbitrary<int32_t>(),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::Arbitrary<int32_t>(), fuzztest::Arbitrary<int32_t>(),
fuzztest::Arbitrary<int32_t>(),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::Arbitrary<uint32_t>(),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::Arbitrary<uint32_t>(),
fuzztest::InRange<uint32_t>(1, std::numeric_limits<uint32_t>::max()),
fuzztest::Arbitrary<bool>());
}
#endif

// Generator for an arbitrary EncoderPtr.
inline auto ArbitraryAvifEncoder() {
const auto codec_choice = fuzztest::ElementOf<avifCodecChoice>(
Expand Down

0 comments on commit 2fe5b3a

Please sign in to comment.