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