Skip to content

Commit b49fbc9

Browse files
authored
Fix setting of cicp values in avifgainmaputil combine and convert. (#3110)
CICP must be set *before* calling ReadImage() so that the RGB->YUV conversion uses the right matrix coefficient for PNG/JPEG files. Also explicitly set the default matrix coefficients when not specified. For Avif files, we overwrite the CICP after reading the file in ReadImage() (keeps the previous behavior). Fix --cicp-input flag not being used in tonemap command. Fix memory leak when in 'combine' command when using with a file that already has a gain map. Fix memory leak in 'tonemap' and 'swapbase' command with RGB pixels. Fixes #3102 and #2869
1 parent 70fabb4 commit b49fbc9

11 files changed

Lines changed: 146 additions & 87 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ The changes are relative to the previous release, unless the baseline is specifi
1515
* Update libxml2.cmd/LocalLibXml2.cmake: v2.15.2
1616
* Update libyuv.cmd/LocalLibyuv.cmake: 6067afde5 (1922)
1717
* Support long path names in Windows
18+
* Fix cicp management and memory leaks in avifgainmaputil
19+
https://github.com/AOMediaCodec/libavif/issues/3102.
1820

1921
### Removed since 1.4.0
2022

apps/avifgainmaputil/combine_command.cc

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "combine_command.h"
55

6+
#include <cassert>
67
#include <cmath>
78

89
#include "avif/avif_cxx.h"
@@ -88,29 +89,21 @@ avifResult CombineCommand::Run() {
8889
if (base_image == nullptr || alternate_image == nullptr) {
8990
return AVIF_RESULT_OUT_OF_MEMORY;
9091
}
91-
avifResult result =
92-
ReadImage(base_image.get(), arg_base_filename_, pixel_format,
93-
arg_image_read_.depth, arg_image_read_.ignore_profile);
94-
if (result != AVIF_RESULT_OK) {
95-
std::cout << "Failed to read base image: " << avifResultToString(result)
96-
<< "\n";
97-
return result;
98-
}
9992
if (arg_base_cicp_.provenance() == argparse::Provenance::SPECIFIED) {
10093
base_image->colorPrimaries = arg_base_cicp_.value().color_primaries;
10194
base_image->transferCharacteristics =
10295
arg_base_cicp_.value().transfer_characteristics;
10396
base_image->matrixCoefficients = arg_base_cicp_.value().matrix_coefficients;
10497
}
105-
106-
result =
107-
ReadImage(alternate_image.get(), arg_alternate_filename_, pixel_format,
108-
arg_image_read_.depth, arg_image_read_.ignore_profile);
98+
avifResult result = ReadImage(
99+
base_image.get(), arg_base_filename_, pixel_format, arg_image_read_.depth,
100+
arg_image_read_.ignore_profile, /*ignore_gain_map=*/true);
109101
if (result != AVIF_RESULT_OK) {
110-
std::cout << "Failed to read alternate image: "
111-
<< avifResultToString(result) << "\n";
102+
std::cout << "Failed to read base image: " << avifResultToString(result)
103+
<< "\n";
112104
return result;
113105
}
106+
114107
if (arg_alternate_cicp_.provenance() == argparse::Provenance::SPECIFIED) {
115108
alternate_image->colorPrimaries =
116109
arg_alternate_cicp_.value().color_primaries;
@@ -119,6 +112,14 @@ avifResult CombineCommand::Run() {
119112
alternate_image->matrixCoefficients =
120113
arg_alternate_cicp_.value().matrix_coefficients;
121114
}
115+
result =
116+
ReadImage(alternate_image.get(), arg_alternate_filename_, pixel_format,
117+
arg_image_read_.depth, arg_image_read_.ignore_profile);
118+
if (result != AVIF_RESULT_OK) {
119+
std::cout << "Failed to read alternate image: "
120+
<< avifResultToString(result) << "\n";
121+
return result;
122+
}
122123

123124
const uint32_t downscaling = std::max<int>(1, arg_downscaling_);
124125
const uint32_t rounding = downscaling / 2;
@@ -129,6 +130,10 @@ avifResult CombineCommand::Run() {
129130
std::cout << "Creating a gain map of size " << gain_map_width << " x "
130131
<< gain_map_height << "\n";
131132

133+
// Because base_image is read with ignore_gain_map=true, there is no
134+
// preexisting gain map. Otherwise, overwriting the pointer would cause a
135+
// memory leak.
136+
assert(base_image->gainMap == nullptr);
132137
base_image->gainMap = avifGainMapCreate();
133138
base_image->gainMap->image =
134139
avifImageCreate(gain_map_width, gain_map_height, arg_gain_map_depth_,

apps/avifgainmaputil/convert_command.cc

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -52,32 +52,19 @@ avifResult ConvertCommand::Run() {
5252
return AVIF_RESULT_OUT_OF_MEMORY;
5353
}
5454

55-
const avifAppFileFormat file_format = avifReadImage(
56-
arg_input_filename_.value().c_str(),
57-
AVIF_APP_FILE_FORMAT_UNKNOWN /* guess format */, pixel_format,
58-
arg_image_read_.depth, AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
59-
arg_image_read_.ignore_profile,
60-
/*ignoreExif=*/false,
61-
/*ignoreXMP=*/false,
62-
/*ignoreGainMap=*/false, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(),
63-
/*outDepth=*/nullptr,
64-
/*sourceTiming=*/nullptr,
65-
/*frameIter=*/nullptr);
66-
if (file_format == AVIF_APP_FILE_FORMAT_UNKNOWN) {
67-
std::cout << "Failed to decode image: " << arg_input_filename_;
68-
return AVIF_RESULT_INVALID_ARGUMENT;
69-
}
7055
if (arg_cicp_.provenance() == argparse::Provenance::SPECIFIED) {
7156
image->colorPrimaries = arg_cicp_.value().color_primaries;
7257
image->transferCharacteristics = arg_cicp_.value().transfer_characteristics;
7358
image->matrixCoefficients = arg_cicp_.value().matrix_coefficients;
74-
} else if (image->icc.size == 0 &&
75-
image->colorPrimaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED &&
76-
image->transferCharacteristics ==
77-
AVIF_COLOR_PRIMARIES_UNSPECIFIED) {
78-
// If there is no ICC and no CICP, assume sRGB by default.
79-
image->colorPrimaries = AVIF_COLOR_PRIMARIES_SRGB;
80-
image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
59+
}
60+
61+
avifResult result =
62+
ReadImage(image.get(), arg_input_filename_.value(), pixel_format,
63+
arg_image_read_.depth, arg_image_read_.ignore_profile,
64+
/*ignore_gain_map*/ false);
65+
if (result != AVIF_RESULT_OK) {
66+
std::cout << "Failed to decode image: " << arg_input_filename_;
67+
return result;
8168
}
8269

8370
if (image->gainMap && image->gainMap->altICC.size == 0) {
@@ -110,8 +97,7 @@ avifResult ConvertCommand::Run() {
11097
if (new_base == nullptr) {
11198
return AVIF_RESULT_OUT_OF_MEMORY;
11299
}
113-
const avifResult result =
114-
ChangeBase(*image, depth, image->yuvFormat, new_base.get());
100+
result = ChangeBase(*image, depth, image->yuvFormat, new_base.get());
115101
if (result != AVIF_RESULT_OK) {
116102
return result;
117103
}
@@ -126,10 +112,9 @@ avifResult ConvertCommand::Run() {
126112
encoder->qualityAlpha = arg_image_encode_.quality_alpha;
127113
encoder->qualityGainMap = arg_gain_map_quality_;
128114
encoder->speed = arg_image_encode_.speed;
129-
const avifResult result =
130-
WriteAvifGrid(image.get(), arg_image_encode_.grid.value().grid_cols,
131-
arg_image_encode_.grid.value().grid_rows, encoder.get(),
132-
arg_output_filename_);
115+
result = WriteAvifGrid(image.get(), arg_image_encode_.grid.value().grid_cols,
116+
arg_image_encode_.grid.value().grid_rows,
117+
encoder.get(), arg_output_filename_);
133118
if (result != AVIF_RESULT_OK) {
134119
std::cout << "Failed to encode image: " << avifResultToString(result)
135120
<< " (" << encoder->diag.error << ")\n";

apps/avifgainmaputil/imageio.cc

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ avifResult WriteAvifGrid(const avifImage* image, int grid_cols, int grid_rows,
155155

156156
avifResult ReadImage(avifImage* image, const std::string& input_filename,
157157
avifPixelFormat requested_format, uint32_t requested_depth,
158-
bool ignore_profile) {
158+
bool ignore_profile, bool ignore_gain_map) {
159159
avifAppFileFormat input_format = avifGuessFileFormat(input_filename.c_str());
160160
if (input_format == AVIF_APP_FILE_FORMAT_UNKNOWN) {
161161
std::cerr << "Cannot determine input format: " << input_filename;
@@ -165,10 +165,21 @@ avifResult ReadImage(avifImage* image, const std::string& input_filename,
165165
if (decoder == nullptr) {
166166
return AVIF_RESULT_OUT_OF_MEMORY;
167167
}
168+
if (!ignore_gain_map) {
169+
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
170+
}
168171
avifResult result = ReadAvif(decoder.get(), input_filename, ignore_profile);
169172
if (result != AVIF_RESULT_OK) {
170173
return result;
171174
}
175+
if (ignore_gain_map && decoder->image->gainMap) {
176+
avifGainMapDestroy(decoder->image->gainMap);
177+
decoder->image->gainMap = nullptr;
178+
}
179+
const avifColorPrimaries in_primaries = image->colorPrimaries;
180+
const avifTransferCharacteristics in_transfer =
181+
image->transferCharacteristics;
182+
const avifMatrixCoefficients in_matrix = image->matrixCoefficients;
172183
if (decoder->image->imageOwnsYUVPlanes &&
173184
(decoder->image->alphaPlane == nullptr ||
174185
decoder->image->imageOwnsAlphaPlane)) {
@@ -179,26 +190,37 @@ avifResult ReadImage(avifImage* image, const std::string& input_filename,
179190
return result;
180191
}
181192
}
193+
if (in_primaries != AVIF_COLOR_PRIMARIES_UNSPECIFIED ||
194+
in_transfer != AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED ||
195+
in_matrix != AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED) {
196+
image->colorPrimaries = in_primaries;
197+
image->transferCharacteristics = in_transfer;
198+
image->matrixCoefficients = in_matrix;
199+
}
182200
} else {
183201
const avifAppFileFormat file_format = avifReadImage(
184202
input_filename.c_str(), AVIF_APP_FILE_FORMAT_UNKNOWN /* guess format */,
185203
requested_format, static_cast<int>(requested_depth),
186204
AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, ignore_profile,
187-
/*ignoreExif=*/false, /*ignoreXMP=*/false,
188-
/*ignoreGainMap=*/true, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image,
205+
/*ignoreExif=*/false, /*ignoreXMP=*/false, ignore_gain_map,
206+
AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image,
189207
/*outDepth=*/nullptr,
190208
/*sourceTiming=*/nullptr, /*frameIter=*/nullptr);
191209
if (file_format == AVIF_APP_FILE_FORMAT_UNKNOWN) {
192210
std::cout << "Failed to decode image: " << input_filename;
193211
return AVIF_RESULT_INVALID_ARGUMENT;
194212
}
195-
if (image->icc.size == 0) {
196-
// Assume sRGB by default.
197-
if (image->colorPrimaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED &&
198-
image->transferCharacteristics == AVIF_COLOR_PRIMARIES_UNSPECIFIED) {
199-
image->colorPrimaries = AVIF_COLOR_PRIMARIES_SRGB;
200-
image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
201-
}
213+
// Assume sRGB by default.
214+
if (image->icc.size == 0 &&
215+
image->colorPrimaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED &&
216+
image->transferCharacteristics == AVIF_COLOR_PRIMARIES_UNSPECIFIED) {
217+
image->colorPrimaries = AVIF_COLOR_PRIMARIES_SRGB;
218+
image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
219+
}
220+
if (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED) {
221+
// Explicitly set the default matrix coefficient, see
222+
// avifCalcYUVCoefficients().
223+
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601;
202224
}
203225
}
204226
return AVIF_RESULT_OK;

apps/avifgainmaputil/imageio.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ avifResult WriteImage(const avifImage* image, int grid_cols, int grid_rows,
1919
// Reads an image in any of the supported formats. Ignores any gain map.
2020
avifResult ReadImage(avifImage* image, const std::string& input_filename,
2121
avifPixelFormat requested_format, uint32_t requested_depth,
22-
bool ignore_profile);
22+
bool ignore_profile, bool ignore_gain_map = true);
2323

2424
// Reads an image in avif format given a pre-configured encoder.
2525
avifResult WriteAvif(const avifImage* image, avifEncoder* encoder,

apps/avifgainmaputil/swapbase_command.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ avifResult ChangeBase(const avifImage& image, int depth,
5454
}
5555

5656
avifRGBImage swapped_rgb;
57+
RGBImageCleanup rgb_cleanup(&swapped_rgb);
5758
avifRGBImageSetDefaults(&swapped_rgb, swapped);
5859

5960
avifContentLightLevelInformationBox clli = image.gainMap->altCLLI;

apps/avifgainmaputil/tonemap_command.cc

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ avifResult TonemapCommand::Run() {
6969
if (result != AVIF_RESULT_OK) {
7070
return result;
7171
}
72+
if (arg_input_cicp_.provenance() == argparse::Provenance::SPECIFIED) {
73+
decoder->image->colorPrimaries = arg_input_cicp_.value().color_primaries;
74+
decoder->image->transferCharacteristics =
75+
arg_input_cicp_.value().transfer_characteristics;
76+
decoder->image->matrixCoefficients =
77+
arg_input_cicp_.value().matrix_coefficients;
78+
}
7279

7380
avifImage* image = decoder->image;
7481
if (image->gainMap == nullptr || image->gainMap->image == nullptr) {
@@ -115,10 +122,9 @@ avifResult TonemapCommand::Run() {
115122
image->gainMap->altMatrixCoefficients};
116123
}
117124
if (cicp.color_primaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED) {
118-
// TODO(maryla): for now avifImageApplyGainMap always uses the primaries of
119-
// the base image, but it should take into account the metadata's
120-
// useBaseColorSpace property.
121-
cicp.color_primaries = image->colorPrimaries;
125+
cicp.color_primaries = image->gainMap->useBaseColorSpace
126+
? image->colorPrimaries
127+
: image->gainMap->altColorPrimaries;
122128
}
123129
if (cicp.transfer_characteristics ==
124130
AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED) {
@@ -181,7 +187,13 @@ avifResult TonemapCommand::Run() {
181187
if (tone_mapped == nullptr) {
182188
return AVIF_RESULT_OUT_OF_MEMORY;
183189
}
190+
tone_mapped->colorPrimaries = cicp.color_primaries;
191+
tone_mapped->transferCharacteristics = cicp.transfer_characteristics;
192+
tone_mapped->matrixCoefficients = cicp.matrix_coefficients;
193+
tone_mapped->clli = clli_box;
194+
184195
avifRGBImage tone_mapped_rgb;
196+
RGBImageCleanup rgb_cleanup(&tone_mapped_rgb);
185197
avifRGBImageSetDefaults(&tone_mapped_rgb, tone_mapped.get());
186198
avifDiagnostics diag;
187199
result = avifImageApplyGainMap(
@@ -200,11 +212,6 @@ avifResult TonemapCommand::Run() {
200212
return result;
201213
}
202214

203-
tone_mapped->clli = clli_box;
204-
tone_mapped->transferCharacteristics = cicp.transfer_characteristics;
205-
tone_mapped->colorPrimaries = cicp.color_primaries;
206-
tone_mapped->matrixCoefficients = cicp.matrix_coefficients;
207-
208215
return WriteImage(tone_mapped.get(), arg_image_encode_.grid.value().grid_cols,
209216
arg_image_encode_.grid.value().grid_rows,
210217
arg_output_filename_, arg_image_encode_.quality,

tests/CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,9 @@ if(AVIF_BUILD_APPS)
267267
add_cmd_test(test_cmd_stdin ${CMAKE_CURRENT_SOURCE_DIR}/data)
268268
add_cmd_test(test_cmd_targetsize ${CMAKE_CURRENT_SOURCE_DIR}/data)
269269
add_cmd_test(test_cmd_transform ${CMAKE_CURRENT_SOURCE_DIR}/data)
270+
add_cmd_test(test_cmd_avifgainmaputil ${CMAKE_CURRENT_SOURCE_DIR}/data)
270271

271-
if(AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION)
272-
add_cmd_test(test_cmd_avifgainmaputil ${CMAKE_CURRENT_SOURCE_DIR}/data)
273-
else()
272+
if(NOT AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION)
274273
set_tests_properties(test_cmd_stdin PROPERTIES DISABLED True)
275274
endif()
276275

@@ -361,6 +360,7 @@ if(AVIF_CODEC_AVM_ENABLED)
361360
avifrangetest
362361
aviftunetest
363362
avify4mtest
363+
test_cmd_avifgainmaputil
364364
PROPERTIES DISABLED True
365365
)
366366

tests/data/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,18 @@ after changing `kUpdateTestImages` to true in the `avifgainmaptest.cc`.
823823

824824
SDR image with a gain map to allow tone mapping to HDR. The gain map's width and height are halved compared to the base image.
825825

826+
### File [seine_sdr_gainmap_srgb_icc.avif](seine_sdr_gainmap_srgb_icc.avif)
827+
828+
![](seine_sdr_gainmap_srgb_icc.avif)
829+
830+
License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE)
831+
832+
Source : created by running the following command with avifenc compiled with libxml enabled:
833+
834+
```bash
835+
avifenc seine_sdr_gainmap_srgb.jpg seine_sdr_gainmap_srgb_icc.avif --qcolor 90 --qgain-map 90
836+
```
837+
826838
## Files colors*_hdr_*.avif and colors*_sdr_srgb.avif
827839

828840
![](colors_wcg_hdr_rec2020.avif)
127 KB
Binary file not shown.

0 commit comments

Comments
 (0)