Skip to content

Commit

Permalink
Simplify gain map API.
Browse files Browse the repository at this point in the history
Following changes in libavif AOMediaCodec/libavif#2481

PiperOrigin-RevId: 689374767
  • Loading branch information
Image Codecs authored and copybara-github committed Oct 24, 2024
1 parent 48d0186 commit 276bee9
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 177 deletions.
73 changes: 8 additions & 65 deletions c_api_tests/avifgainmaptest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ TEST(GainMapTest, DecodeGainMapGrid) {
std::string(data_path) + "color_grid_gainmap_different_grid.avif";
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
decoder->enableDecodingGainMap = true;
decoder->enableParsingGainMapMetadata = true;
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;

avifResult result = avifDecoderSetIOFile(decoder.get(), path.c_str());
ASSERT_EQ(result, AVIF_RESULT_OK)
Expand All @@ -33,12 +32,11 @@ TEST(GainMapTest, DecodeGainMapGrid) {
ASSERT_NE(decoded, nullptr);

// Verify that the gain map is present and matches the input.
EXPECT_TRUE(decoder->gainMapPresent);
EXPECT_NE(decoder->image->gainMap, nullptr);
// Color+alpha: 4x3 grid of 128x200 tiles.
EXPECT_EQ(decoded->width, 128u * 4u);
EXPECT_EQ(decoded->height, 200u * 3u);
EXPECT_EQ(decoded->depth, 10u);
ASSERT_NE(decoded->gainMap, nullptr);
ASSERT_NE(decoded->gainMap->image, nullptr);
// Gain map: 2x2 grid of 64x80 tiles.
EXPECT_EQ(decoded->gainMap->image->width, 64u * 2u);
Expand All @@ -57,8 +55,7 @@ TEST(GainMapTest, DecodeOriented) {
const std::string path = std::string(data_path) + "gainmap_oriented.avif";
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
decoder->enableDecodingGainMap = AVIF_TRUE;
decoder->enableParsingGainMapMetadata = AVIF_TRUE;
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
ASSERT_EQ(avifDecoderSetIOFile(decoder.get(), path.c_str()), AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);

Expand All @@ -71,37 +68,11 @@ TEST(GainMapTest, DecodeOriented) {
AVIF_TRANSFORM_NONE);
}

TEST(GainMapTest, IgnoreGainMap) {
const std::string path =
std::string(data_path) + "seine_sdr_gainmap_srgb.avif";
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
// Decode image, with enableDecodingGainMap false by default.

avifResult result = avifDecoderSetIOFile(decoder.get(), path.c_str());
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << decoder->diag.error;

// Just parse the image first.
result = avifDecoderParse(decoder.get());
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << decoder->diag.error;
avifImage* decoded = decoder->image;
ASSERT_NE(decoded, nullptr);

// Verify that the gain map is not detected.
EXPECT_FALSE(decoder->gainMapPresent);
// And not decoded because enableDecodingGainMap is false by default.
EXPECT_EQ(decoded->gainMap, nullptr);
}

TEST(GainMapTest, IgnoreGainMapButReadMetadata) {
const std::string path =
std::string(data_path) + "seine_sdr_gainmap_srgb.avif";
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
// Decode image, with enableDecodingGainMap false by default.
decoder->enableParsingGainMapMetadata = AVIF_TRUE; // Read gain map metadata

avifResult result = avifDecoderSetIOFile(decoder.get(), path.c_str());
ASSERT_EQ(result, AVIF_RESULT_OK)
Expand All @@ -113,43 +84,20 @@ TEST(GainMapTest, IgnoreGainMapButReadMetadata) {
ASSERT_NE(decoded, nullptr);

// Verify that the gain map was detected...
EXPECT_TRUE(decoder->gainMapPresent);
ASSERT_NE(decoded->gainMap, nullptr);
EXPECT_NE(decoder->image->gainMap, nullptr);
// ... but not decoded because enableDecodingGainMap is false by default.
EXPECT_EQ(decoded->gainMap->image, nullptr);
// Check that the gain map metadata WAS populated.
EXPECT_EQ(decoded->gainMap->alternateHdrHeadroom.n, 13);
EXPECT_EQ(decoded->gainMap->alternateHdrHeadroom.d, 10);
}

TEST(GainMapTest, DecodeGainMapTrueParseMetadataFalse) {
const std::string path =
std::string(data_path) + "seine_sdr_gainmap_srgb.avif";
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
decoder->enableParsingGainMapMetadata = AVIF_FALSE;
decoder->enableDecodingGainMap = AVIF_TRUE;

avifResult result = avifDecoderSetIOFile(decoder.get(), path.c_str());
ASSERT_EQ(result, AVIF_RESULT_OK)
<< avifResultToString(result) << " " << decoder->diag.error;
result = avifDecoderParse(decoder.get());

// Verify we get an error because the combination of
// enableDecodingGainMap=false and enableParsingGainMapMetadata=true
// is not allowed.
ASSERT_EQ(result, AVIF_RESULT_INVALID_ARGUMENT)
<< avifResultToString(result) << " " << decoder->diag.error;
}

TEST(GainMapTest, IgnoreColorAndAlpha) {
const std::string path =
std::string(data_path) + "seine_sdr_gainmap_srgb.avif";
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
decoder->ignoreColorAndAlpha = AVIF_TRUE;
decoder->enableDecodingGainMap = AVIF_TRUE;
decoder->enableParsingGainMapMetadata = AVIF_TRUE;
decoder->imageContentToDecode = AVIF_IMAGE_CONTENT_GAIN_MAP;

avifResult result = avifDecoderSetIOFile(decoder.get(), path.c_str());
ASSERT_EQ(result, AVIF_RESULT_OK)
Expand All @@ -172,8 +120,7 @@ TEST(GainMapTest, IgnoreColorAndAlpha) {
EXPECT_EQ(decoded->yuvRowBytes[2], 0u);
EXPECT_EQ(decoded->alphaRowBytes, 0u);
// The gain map was decoded.
EXPECT_TRUE(decoder->gainMapPresent);
ASSERT_NE(decoded->gainMap, nullptr);
EXPECT_NE(decoder->image->gainMap, nullptr);
ASSERT_NE(decoded->gainMap->image, nullptr);
// Including pixels.
EXPECT_GT(decoded->gainMap->image->yuvRowBytes[0], 0u);
Expand All @@ -184,11 +131,7 @@ TEST(GainMapTest, IgnoreAll) {
std::string(data_path) + "seine_sdr_gainmap_srgb.avif";
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
// Ignore both the main image and the gain map.
decoder->ignoreColorAndAlpha = AVIF_TRUE;
decoder->enableDecodingGainMap = AVIF_FALSE;
// But do read the gain map metadata
decoder->enableParsingGainMapMetadata = AVIF_TRUE;
decoder->imageContentToDecode = AVIF_IMAGE_CONTENT_NONE;

avifResult result = avifDecoderSetIOFile(decoder.get(), path.c_str());
ASSERT_EQ(result, AVIF_RESULT_OK)
Expand All @@ -199,7 +142,7 @@ TEST(GainMapTest, IgnoreAll) {
avifImage* decoded = decoder->image;
ASSERT_NE(decoded, nullptr);

EXPECT_TRUE(decoder->gainMapPresent);
EXPECT_NE(decoder->image->gainMap, nullptr);
ASSERT_EQ(decoder->image->gainMap->image, nullptr);

// But trying to access the next image should give an error because both
Expand Down
6 changes: 4 additions & 2 deletions examples/dec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ fn main() {
{
let settings = Settings {
strictness: Strictness::None,
enable_decoding_gainmap: true,
enable_parsing_gainmap_metadata: true,
image_content_to_decode: vec![
ImageContentTypeFlag::ColorAndAlpha,
ImageContentTypeFlag::GainMap,
],
allow_progressive: true,
..Settings::default()
};
Expand Down
13 changes: 9 additions & 4 deletions include/avif/avif.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ constexpr static const uint32_t AVIF_STRICT_ALPHA_ISPE_REQUIRED = (1 << 2);

constexpr static const uint32_t AVIF_STRICT_ENABLED = ((AVIF_STRICT_PIXI_REQUIRED | AVIF_STRICT_CLAP_VALID) | AVIF_STRICT_ALPHA_ISPE_REQUIRED);

constexpr static const uint32_t AVIF_IMAGE_CONTENT_NONE = 0;

constexpr static const uint32_t AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA = ((1 << 0) | (1 << 1));

constexpr static const uint32_t AVIF_IMAGE_CONTENT_GAIN_MAP = (1 << 2);

constexpr static const uint32_t AVIF_IMAGE_CONTENT_ALL = (AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_GAIN_MAP);

constexpr static const size_t CRABBY_AVIF_DIAGNOSTICS_ERROR_BUFFER_SIZE = 256;

constexpr static const size_t CRABBY_AVIF_PLANE_COUNT_YUV = 3;
Expand Down Expand Up @@ -412,10 +420,7 @@ struct avifDecoder {
avifIOStats ioStats;
avifDiagnostics diag;
avifDecoderData *data;
avifBool gainMapPresent;
avifBool enableDecodingGainMap;
avifBool enableParsingGainMapMetadata;
avifBool ignoreColorAndAlpha;
uint32_t imageContentToDecode;
avifBool imageSequenceTrackPresent;
Box<Decoder> rust_decoder;
avifImage image_object;
Expand Down
28 changes: 15 additions & 13 deletions src/capi/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,7 @@ pub struct avifDecoder {
pub diag: avifDiagnostics,
//avifIO * io;
pub data: *mut avifDecoderData,
pub gainMapPresent: avifBool,
pub enableDecodingGainMap: avifBool,
pub enableParsingGainMapMetadata: avifBool,
pub ignoreColorAndAlpha: avifBool,
pub imageContentToDecode: u32,
pub imageSequenceTrackPresent: avifBool,

// TODO: maybe wrap these fields in a private data kind of field?
Expand Down Expand Up @@ -95,10 +92,7 @@ impl Default for avifDecoder {
ioStats: Default::default(),
diag: avifDiagnostics::default(),
data: std::ptr::null_mut(),
gainMapPresent: AVIF_FALSE,
enableDecodingGainMap: AVIF_FALSE,
enableParsingGainMapMetadata: AVIF_FALSE,
ignoreColorAndAlpha: AVIF_FALSE,
imageContentToDecode: AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA,
imageSequenceTrackPresent: AVIF_FALSE,
rust_decoder: Box::<Decoder>::default(),
image_object: avifImage::default(),
Expand Down Expand Up @@ -174,16 +168,21 @@ impl From<&avifDecoder> for Settings {
}
Strictness::SpecificInclude(flags)
};
let mut image_content_to_decode_flags: Vec<ImageContentTypeFlag> = Vec::new();
if (decoder.imageContentToDecode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA) != 0 {
image_content_to_decode_flags.push(ImageContentTypeFlag::ColorAndAlpha);
}
if (decoder.imageContentToDecode & AVIF_IMAGE_CONTENT_GAIN_MAP) != 0 {
image_content_to_decode_flags.push(ImageContentTypeFlag::GainMap);
}
Self {
source: decoder.requestedSource,
strictness,
allow_progressive: decoder.allowProgressive == AVIF_TRUE,
allow_incremental: decoder.allowIncremental == AVIF_TRUE,
ignore_exif: decoder.ignoreExif == AVIF_TRUE,
ignore_xmp: decoder.ignoreXMP == AVIF_TRUE,
enable_decoding_gainmap: decoder.enableDecodingGainMap == AVIF_TRUE,
enable_parsing_gainmap_metadata: decoder.enableParsingGainMapMetadata == AVIF_TRUE,
ignore_color_and_alpha: decoder.ignoreColorAndAlpha == AVIF_TRUE,
image_content_to_decode: image_content_to_decode_flags,
codec_choice: match decoder.codecChoice {
avifCodecChoice::Auto => CodecChoice::Auto,
avifCodecChoice::Dav1d => CodecChoice::Dav1d,
Expand Down Expand Up @@ -223,10 +222,13 @@ fn rust_decoder_to_avifDecoder(src: &Decoder, dst: &mut avifDecoder) {
dst.ioStats = src.io_stats();

if src.gainmap_present() {
dst.gainMapPresent = AVIF_TRUE;
dst.gainmap_image_object = (&src.gainmap().image).into();
dst.gainmap_object = src.gainmap().into();
if src.settings.enable_decoding_gainmap {
if src
.settings
.image_content_to_decode
.contains(&ImageContentTypeFlag::GainMap)
{
dst.gainmap_object.image = (&mut dst.gainmap_image_object) as *mut avifImage;
}
dst.image_object.gainMap = (&mut dst.gainmap_object) as *mut avifGainMap;
Expand Down
5 changes: 5 additions & 0 deletions src/capi/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ pub const AVIF_STRICT_CLAP_VALID: u32 = 1 << 1;
pub const AVIF_STRICT_ALPHA_ISPE_REQUIRED: u32 = 1 << 2;
pub const AVIF_STRICT_ENABLED: u32 =
AVIF_STRICT_PIXI_REQUIRED | AVIF_STRICT_CLAP_VALID | AVIF_STRICT_ALPHA_ISPE_REQUIRED;
pub const AVIF_IMAGE_CONTENT_NONE: u32 = 0;
pub const AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA: u32 = 1 << 0 | 1 << 1;
pub const AVIF_IMAGE_CONTENT_GAIN_MAP: u32 = 1 << 2;
pub const AVIF_IMAGE_CONTENT_ALL: u32 =
AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_GAIN_MAP;
pub type avifStrictFlags = u32;

#[repr(C)]
Expand Down
40 changes: 25 additions & 15 deletions src/decoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ pub const DEFAULT_IMAGE_SIZE_LIMIT: u32 = 16384 * 16384;
pub const DEFAULT_IMAGE_DIMENSION_LIMIT: u32 = 32768;
pub const DEFAULT_IMAGE_COUNT_LIMIT: u32 = 12 * 3600 * 60;

#[derive(Debug, PartialEq)]
pub enum ImageContentTypeFlag {
ColorAndAlpha,
GainMap,
}

#[derive(Debug)]
pub struct Settings {
pub source: Source,
Expand All @@ -134,9 +140,7 @@ pub struct Settings {
pub strictness: Strictness,
pub allow_progressive: bool,
pub allow_incremental: bool,
pub enable_decoding_gainmap: bool,
pub enable_parsing_gainmap_metadata: bool,
pub ignore_color_and_alpha: bool,
pub image_content_to_decode: Vec<ImageContentTypeFlag>,
pub codec_choice: CodecChoice,
pub image_size_limit: u32,
pub image_dimension_limit: u32,
Expand All @@ -153,9 +157,7 @@ impl Default for Settings {
strictness: Default::default(),
allow_progressive: false,
allow_incremental: false,
enable_decoding_gainmap: false,
enable_parsing_gainmap_metadata: false,
ignore_color_and_alpha: false,
image_content_to_decode: vec![ImageContentTypeFlag::ColorAndAlpha],
codec_choice: Default::default(),
image_size_limit: DEFAULT_IMAGE_SIZE_LIMIT,
image_dimension_limit: DEFAULT_IMAGE_DIMENSION_LIMIT,
Expand Down Expand Up @@ -743,9 +745,6 @@ impl Decoder {
if self.io.is_none() {
return Err(AvifError::IoNotSet);
}
if self.settings.enable_decoding_gainmap && !self.settings.enable_parsing_gainmap_metadata {
return Err(AvifError::InvalidArgument);
}

if self.parse_state == ParseState::None {
self.reset();
Expand Down Expand Up @@ -894,7 +893,7 @@ impl Decoder {
}

// Optional gainmap item
if self.settings.enable_parsing_gainmap_metadata && avif_boxes.ftyp.has_tmap() {
if avif_boxes.ftyp.has_tmap() {
if let Some((tonemap_id, gainmap_id)) =
self.find_gainmap_item(item_ids[Category::Color.usize()])?
{
Expand All @@ -909,7 +908,11 @@ impl Decoder {
self.populate_grid_item_ids(gainmap_id, Category::Gainmap)?;
self.validate_gainmap_item(gainmap_id, tonemap_id)?;
self.gainmap_present = true;
if self.settings.enable_decoding_gainmap {
if self
.settings
.image_content_to_decode
.contains(&ImageContentTypeFlag::GainMap)
{
item_ids[Category::Gainmap.usize()] = gainmap_id;
}
}
Expand Down Expand Up @@ -1438,7 +1441,10 @@ impl Decoder {
fn decode_tiles(&mut self, image_index: usize) -> AvifResult<()> {
let mut decoded_something = false;
for category in Category::ALL {
if self.settings.ignore_color_and_alpha
if !self
.settings
.image_content_to_decode
.contains(&ImageContentTypeFlag::ColorAndAlpha)
&& (category == Category::Color || category == Category::Alpha)
{
continue;
Expand Down Expand Up @@ -1555,8 +1561,9 @@ impl Decoder {
// next to retrieve the number of top rows that can be immediately accessed from the luma plane
// of decoder->image, and alpha if any. The corresponding rows from the chroma planes,
// if any, can also be accessed (half rounded up if subsampled, same number of rows otherwise).
// If a gain map is present, and enable_decoding_gainmap is also on, the gain map's planes can
// also be accessed in the same way. The number of available gain map rows is at least:
// If a gain map is present, and image_content_to_decode contains ImageContentTypeFlag::GainMap,
// the gain map's planes can also be accessed in the same way.
// The number of available gain map rows is at least:
// decoder.decoded_row_count() * decoder.gainmap.image.height / decoder.image.height
// When gain map scaling is needed, callers might choose to use a few less rows depending on how
// many rows are needed by the scaling algorithm, to avoid the last row(s) changing when more
Expand All @@ -1572,7 +1579,10 @@ impl Decoder {
let first_tile_height = self.tiles[category][0].height;
let row_count = if category == Category::Gainmap.usize()
&& self.gainmap_present()
&& self.settings.enable_decoding_gainmap
&& self
.settings
.image_content_to_decode
.contains(&ImageContentTypeFlag::GainMap)
&& self.gainmap.image.height != 0
&& self.gainmap.image.height != self.image.height
{
Expand Down
Loading

0 comments on commit 276bee9

Please sign in to comment.