Fix direct modifications of decoder->image. (#3281)
Directly modifying it goes against the API contract.
Create views instead.
diff --git a/apps/avifdec.c b/apps/avifdec.c
index a9d23ae..f5e6567 100644
--- a/apps/avifdec.c
+++ b/apps/avifdec.c
@@ -304,6 +304,7 @@
// ------ After this point, use 'goto cleanup;' in case of failure ------
int returnCode = 1;
+ avifImage * imageView = NULL;
avifDecoder * decoder = avifDecoderCreate();
if (!decoder) {
fprintf(stderr, "Memory allocation failure\n");
@@ -366,6 +367,10 @@
const avifBool decodeAllFrames = frameIndex == DECODE_ALL_FRAMES;
int currIndex = decodeAllFrames ? 0 : frameIndex;
for (;;) {
+ if (imageView != NULL && imageView != decoder->image) {
+ avifImageDestroy(imageView);
+ }
+ imageView = NULL;
result = decodeAllFrames ? avifDecoderNextImage(decoder) : avifDecoderNthImage(decoder, frameIndex);
if (result != AVIF_RESULT_OK) {
break;
@@ -400,20 +405,34 @@
}
}
- if (ignoreICC && (decoder->image->icc.size > 0)) {
- printf("[--ignore-icc] Discarding ICC profile.\n");
- // This cannot fail.
- result = avifImageSetProfileICC(decoder->image, NULL, 0);
- assert(result == AVIF_RESULT_OK);
- }
-
- if (iccOverrideFilename) {
- printf("[--icc] Setting ICC profile: %s\n", iccOverrideFilename);
- result = avifImageSetProfileICC(decoder->image, iccOverride.data, iccOverride.size);
- if (result != AVIF_RESULT_OK) {
- fprintf(stderr, "ERROR: Failed to set ICC: %s\n", avifResultToString(result));
+ if (ignoreICC) {
+ imageView = avifImageCreateEmpty();
+ if (!imageView) {
+ fprintf(stderr, "ERROR: Out of memory\n");
goto cleanup;
}
+ if (decoder->image->icc.size > 0) {
+ printf("[--ignore-icc] Discarding ICC profile.\n");
+ }
+ result = avifImageCreateView(imageView,
+ decoder->image,
+ /*ignoreColorProfile=*/AVIF_TRUE,
+ /*ignoreAlpha=*/AVIF_FALSE,
+ /*ignoreGainMap=*/AVIF_FALSE);
+ if (result != AVIF_RESULT_OK) {
+ fprintf(stderr, "ERROR: Failed to create image view\n");
+ goto cleanup;
+ }
+ if (iccOverrideFilename) {
+ printf("[--icc] Setting ICC profile: %s\n", iccOverrideFilename);
+ result = avifImageSetProfileICC(imageView, iccOverride.data, iccOverride.size);
+ if (result != AVIF_RESULT_OK) {
+ fprintf(stderr, "ERROR: Failed to set ICC: %s\n", avifResultToString(result));
+ goto cleanup;
+ }
+ }
+ } else {
+ imageView = decoder->image;
}
if (decodeAllFrames) {
@@ -436,11 +455,11 @@
fprintf(stderr, "ERROR: Unable to generate output filename\n");
goto cleanup;
}
- if (!avifWriteToFile(outputFormat, frameFilename, decoder->image, rawColor, jpegQuality, pngCompressionLevel, requestedDepth, chromaUpsampling)) {
+ if (!avifWriteToFile(outputFormat, frameFilename, imageView, rawColor, jpegQuality, pngCompressionLevel, requestedDepth, chromaUpsampling)) {
goto cleanup;
}
} else {
- if (!avifWriteToFile(outputFormat, outputFilename, decoder->image, rawColor, jpegQuality, pngCompressionLevel, requestedDepth, chromaUpsampling)) {
+ if (!avifWriteToFile(outputFormat, outputFilename, imageView, rawColor, jpegQuality, pngCompressionLevel, requestedDepth, chromaUpsampling)) {
goto cleanup;
}
if (isSequence && !frameIndexSpecified) {
@@ -476,6 +495,9 @@
if (returnCode != 0) {
avifDumpDiagnostics(&decoder->diag);
}
+ if (imageView != NULL && imageView != decoder->image) {
+ avifImageDestroy(imageView);
+ }
avifDecoderDestroy(decoder);
}
avifRWDataFree(&iccOverride);
diff --git a/apps/avifgainmaputil/extractgainmap_command.cc b/apps/avifgainmaputil/extractgainmap_command.cc
index 5779ab8..b926296 100644
--- a/apps/avifgainmaputil/extractgainmap_command.cc
+++ b/apps/avifgainmaputil/extractgainmap_command.cc
@@ -4,6 +4,7 @@
#include "extractgainmap_command.h"
#include "avif/avif_cxx.h"
+#include "avifutil.h"
#include "imageio.h"
namespace avif {
@@ -25,21 +26,30 @@
decoder->maxThreads = arg_jobs_.jobs.value();
decoder->imageContentToDecode = AVIF_IMAGE_CONTENT_GAIN_MAP;
- avifResult result =
- ReadAvif(decoder.get(), arg_input_filename_, /*ignore_profile=*/true,
- /*ignore_alpha=*/false);
+ avifResult result = ReadAvif(decoder.get(), arg_input_filename_);
if (result != AVIF_RESULT_OK) {
return result;
}
- if (decoder->image->gainMap == nullptr ||
- decoder->image->gainMap->image == nullptr) {
+ ImagePtr image(avifImageCreateEmpty());
+ if (!image) {
+ return AVIF_RESULT_OUT_OF_MEMORY;
+ }
+ result = avifImageCreateView(image.get(), decoder->image,
+ /*ignoreColorProfile=*/true,
+ /*ignoreAlpha=*/false,
+ /*ignoreGainMap=*/false);
+ if (result != AVIF_RESULT_OK) {
+ return result;
+ }
+
+ if (image->gainMap == nullptr || image->gainMap->image == nullptr) {
std::cerr << "Input image " << arg_input_filename_
<< " does not contain a gain map\n";
return AVIF_RESULT_INVALID_ARGUMENT;
}
- return WriteImage(decoder->image->gainMap->image,
+ return WriteImage(image->gainMap->image,
arg_image_encode_.grid.value().grid_cols,
arg_image_encode_.grid.value().grid_rows,
arg_output_filename_, arg_image_encode_.quality,
diff --git a/apps/avifgainmaputil/imageio.cc b/apps/avifgainmaputil/imageio.cc
index 7786301..7aae803 100644
--- a/apps/avifgainmaputil/imageio.cc
+++ b/apps/avifgainmaputil/imageio.cc
@@ -205,29 +205,31 @@
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
}
decoder->maxThreads = jobs;
- avifResult result =
- ReadAvif(decoder.get(), input_filename, ignore_profile, ignore_alpha);
+ avifResult result = ReadAvif(decoder.get(), input_filename);
if (result != AVIF_RESULT_OK) {
return result;
}
- if (ignore_gain_map && decoder->image->gainMap) {
- avifGainMapDestroy(decoder->image->gainMap);
- decoder->image->gainMap = nullptr;
+
+ ImagePtr view(avifImageCreateEmpty());
+ if (!view) {
+ return AVIF_RESULT_OUT_OF_MEMORY;
}
+ result = avifImageCreateView(view.get(), decoder->image, ignore_profile,
+ ignore_alpha, ignore_gain_map);
+ if (result != AVIF_RESULT_OK) {
+ return result;
+ }
+
const avifColorPrimaries in_primaries = image->colorPrimaries;
const avifTransferCharacteristics in_transfer =
image->transferCharacteristics;
const avifMatrixCoefficients in_matrix = image->matrixCoefficients;
- if (decoder->image->imageOwnsYUVPlanes &&
- (decoder->image->alphaPlane == nullptr ||
- decoder->image->imageOwnsAlphaPlane)) {
- std::swap(*image, *decoder->image);
- } else {
- result = avifImageCopy(image, decoder->image, AVIF_PLANES_ALL);
- if (result != AVIF_RESULT_OK) {
- return result;
- }
+
+ result = avifImageCopy(image, view.get(), AVIF_PLANES_ALL);
+ if (result != AVIF_RESULT_OK) {
+ return result;
}
+
if (in_primaries != AVIF_COLOR_PRIMARIES_UNSPECIFIED ||
in_transfer != AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED ||
in_matrix != AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED) {
@@ -264,8 +266,7 @@
return AVIF_RESULT_OK;
}
-avifResult ReadAvif(avifDecoder* decoder, const std::string& input_filename,
- bool ignore_profile, bool ignore_alpha) {
+avifResult ReadAvif(avifDecoder* decoder, const std::string& input_filename) {
avifResult result = avifDecoderSetIOFile(decoder, input_filename.c_str());
if (result != AVIF_RESULT_OK) {
std::cerr << "Cannot open file for read: " << input_filename << "\n";
@@ -283,15 +284,6 @@
<< " (" << decoder->diag.error << ")\n";
return result;
}
- if (ignore_profile) {
- avifRWDataFree(&decoder->image->icc);
- if (decoder->image->gainMap) {
- avifRWDataFree(&decoder->image->gainMap->altICC);
- }
- }
- if (ignore_alpha && decoder->image->alphaPlane) {
- avifImageFreePlanes(decoder->image, AVIF_PLANES_A);
- }
return AVIF_RESULT_OK;
}
diff --git a/apps/avifgainmaputil/imageio.h b/apps/avifgainmaputil/imageio.h
index 308bd2a..48d0275 100644
--- a/apps/avifgainmaputil/imageio.h
+++ b/apps/avifgainmaputil/imageio.h
@@ -33,8 +33,7 @@
// Reads an image in avif format given a pre-configured decoder.
// The image can be accessed at decoder->image.
-avifResult ReadAvif(avifDecoder* decoder, const std::string& input_filename,
- bool ignore_profile, bool ignore_alpha);
+avifResult ReadAvif(avifDecoder* decoder, const std::string& input_filename);
} // namespace avif
diff --git a/apps/avifgainmaputil/swapbase_command.cc b/apps/avifgainmaputil/swapbase_command.cc
index d46c904..38a5a7f 100644
--- a/apps/avifgainmaputil/swapbase_command.cc
+++ b/apps/avifgainmaputil/swapbase_command.cc
@@ -4,6 +4,7 @@
#include "swapbase_command.h"
#include "avif/avif_cxx.h"
+#include "avifutil.h"
#include "imageio.h"
namespace avif {
@@ -164,14 +165,23 @@
}
decoder->maxThreads = arg_jobs_.jobs.value();
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
- avifResult result =
- ReadAvif(decoder.get(), arg_input_filename_,
- arg_image_read_.ignore_profile, arg_image_read_.ignore_alpha);
+ avifResult result = ReadAvif(decoder.get(), arg_input_filename_);
if (result != AVIF_RESULT_OK) {
return result;
}
- avifImage* const image = decoder->image;
+ ImagePtr image(avifImageCreateEmpty());
+ if (!image) {
+ return AVIF_RESULT_OUT_OF_MEMORY;
+ }
+ result = avifImageCreateView(image.get(), decoder->image,
+ arg_image_read_.ignore_profile,
+ arg_image_read_.ignore_alpha,
+ /*ignore_gain_map=*/false);
+ if (result != AVIF_RESULT_OK) {
+ return result;
+ }
+
if (image->gainMap == nullptr || image->gainMap->image == nullptr) {
std::cerr << "Input image " << arg_input_filename_
<< " does not contain a gain map\n";
diff --git a/apps/avifgainmaputil/tonemap_command.cc b/apps/avifgainmaputil/tonemap_command.cc
index 27d366f..42fe627 100644
--- a/apps/avifgainmaputil/tonemap_command.cc
+++ b/apps/avifgainmaputil/tonemap_command.cc
@@ -6,6 +6,7 @@
#include <cmath>
#include "avif/avif_cxx.h"
+#include "avifutil.h"
#include "imageio.h"
namespace avif {
@@ -66,21 +67,30 @@
}
decoder->maxThreads = arg_jobs_.jobs.value();
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
- avifResult result =
- ReadAvif(decoder.get(), arg_input_filename_,
- arg_image_read_.ignore_profile, arg_image_read_.ignore_alpha);
+ avifResult result = ReadAvif(decoder.get(), arg_input_filename_);
if (result != AVIF_RESULT_OK) {
return result;
}
- if (arg_input_cicp_.provenance() == argparse::Provenance::SPECIFIED) {
- decoder->image->colorPrimaries = arg_input_cicp_.value().color_primaries;
- decoder->image->transferCharacteristics =
- arg_input_cicp_.value().transfer_characteristics;
- decoder->image->matrixCoefficients =
- arg_input_cicp_.value().matrix_coefficients;
+
+ ImagePtr image(avifImageCreateEmpty());
+ if (!image) {
+ return AVIF_RESULT_OUT_OF_MEMORY;
+ }
+ result = avifImageCreateView(image.get(), decoder->image,
+ arg_image_read_.ignore_profile,
+ arg_image_read_.ignore_alpha,
+ /*ignore_gain_map=*/false);
+ if (result != AVIF_RESULT_OK) {
+ return result;
}
- avifImage* const image = decoder->image;
+ if (arg_input_cicp_.provenance() == argparse::Provenance::SPECIFIED) {
+ image->colorPrimaries = arg_input_cicp_.value().color_primaries;
+ image->transferCharacteristics =
+ arg_input_cicp_.value().transfer_characteristics;
+ image->matrixCoefficients = arg_input_cicp_.value().matrix_coefficients;
+ }
+
if (image->gainMap == nullptr || image->gainMap->image == nullptr) {
std::cerr << "Input image " << arg_input_filename_
<< " does not contain a gain map\n";
@@ -203,7 +213,7 @@
}
avifDiagnostics diag;
result = avifImageApplyGainMap(
- image, image->gainMap, arg_headroom_, cicp.color_primaries,
+ image.get(), image->gainMap, arg_headroom_, cicp.color_primaries,
cicp.transfer_characteristics, &tone_mapped_rgb,
clli_set ? nullptr : &clli_box, &diag);
if (result != AVIF_RESULT_OK) {
diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c
index cd2f6dc..2fe63e1 100644
--- a/apps/shared/avifutil.c
+++ b/apps/shared/avifutil.c
@@ -823,3 +823,94 @@
}
return AVIF_RESULT_OK;
}
+
+avifResult avifImageCreateView(avifImage * dstImage, const avifImage * srcImage, avifBool ignoreColorProfile, avifBool ignoreAlpha, avifBool ignoreGainMap)
+{
+ avifResult res = AVIF_RESULT_OK;
+ if (!dstImage || !srcImage) {
+ return AVIF_RESULT_INVALID_ARGUMENT;
+ }
+
+ avifCropRect rect = { 0, 0, srcImage->width, srcImage->height };
+ res = avifImageSetViewRect(dstImage, srcImage, &rect);
+ if (res != AVIF_RESULT_OK) {
+ return res;
+ }
+
+ if (ignoreAlpha && dstImage->alphaPlane) {
+ avifImageFreePlanes(dstImage, AVIF_PLANES_A);
+ }
+
+ if (!ignoreColorProfile) {
+ res = avifImageSetProfileICC(dstImage, srcImage->icc.data, srcImage->icc.size);
+ if (res != AVIF_RESULT_OK) {
+ return res;
+ }
+ }
+
+ // Using avifRWDataSet directly to match the internal avifImageCopy() behavior.
+ // This avoids re-extracting Exif orientation into irot/imir on an already decoded image.
+ res = avifRWDataSet(&dstImage->exif, srcImage->exif.data, srcImage->exif.size);
+ if (res != AVIF_RESULT_OK) {
+ return res;
+ }
+ res = avifImageSetMetadataXMP(dstImage, srcImage->xmp.data, srcImage->xmp.size);
+ if (res != AVIF_RESULT_OK) {
+ return res;
+ }
+
+ for (size_t i = 0; i < srcImage->numProperties; ++i) {
+ if (memcmp(srcImage->properties[i].boxtype, "uuid", 4) == 0) {
+ res = avifImageAddUUIDProperty(dstImage,
+ srcImage->properties[i].usertype,
+ srcImage->properties[i].boxPayload.data,
+ srcImage->properties[i].boxPayload.size);
+ } else {
+ res = avifImageAddOpaqueProperty(dstImage,
+ srcImage->properties[i].boxtype,
+ srcImage->properties[i].boxPayload.data,
+ srcImage->properties[i].boxPayload.size);
+ }
+ if (res != AVIF_RESULT_OK) {
+ return res;
+ }
+ }
+
+ if (!ignoreGainMap && srcImage->gainMap) {
+ dstImage->gainMap = avifGainMapCreate();
+ if (!dstImage->gainMap) {
+ return AVIF_RESULT_OUT_OF_MEMORY;
+ }
+
+ // Copy all gain map scalars at once
+ *dstImage->gainMap = *srcImage->gainMap;
+ // Reset pointers to prevent shared ownership and double-free
+ dstImage->gainMap->image = NULL;
+ dstImage->gainMap->altICC.data = NULL;
+ dstImage->gainMap->altICC.size = 0;
+
+ if (!ignoreColorProfile && srcImage->gainMap->altICC.size > 0) {
+ res = avifRWDataSet(&dstImage->gainMap->altICC, srcImage->gainMap->altICC.data, srcImage->gainMap->altICC.size);
+ if (res != AVIF_RESULT_OK) {
+ return res;
+ }
+ }
+
+ if (srcImage->gainMap->image) {
+ dstImage->gainMap->image = avifImageCreateEmpty();
+ if (!dstImage->gainMap->image) {
+ return AVIF_RESULT_OUT_OF_MEMORY;
+ }
+ res = avifImageCreateView(dstImage->gainMap->image,
+ srcImage->gainMap->image,
+ ignoreColorProfile,
+ /*ignoreAlpha=*/AVIF_TRUE,
+ /*ignoreGainMap=*/AVIF_TRUE);
+ if (res != AVIF_RESULT_OK) {
+ return res;
+ }
+ }
+ }
+
+ return AVIF_RESULT_OK;
+}
diff --git a/apps/shared/avifutil.h b/apps/shared/avifutil.h
index 192bbc7..30f6996 100644
--- a/apps/shared/avifutil.h
+++ b/apps/shared/avifutil.h
@@ -128,6 +128,12 @@
// This must match the cited fallback for "--yuv auto" in avifenc.c's syntax() function.
#define AVIF_APP_DEFAULT_PIXEL_FORMAT AVIF_PIXEL_FORMAT_YUV444
+// Creates a shallow copy of an image (a view of the pixels, but deep copy of metadata).
+// If a gain map is present in 'srcImage', the gain map of 'dstImage' is also set to
+// a view of the original gain map.
+// 'dstImage' should be an empty image. It will not own the pixel data.
+avifResult avifImageCreateView(avifImage * dstImage, const avifImage * srcImage, avifBool ignoreColorProfile, avifBool ignoreAlpha, avifBool ignoreGainMap);
+
#ifdef __cplusplus
} // extern "C"
#endif