Commit e5ce98cfe for imagemagick.org

commit e5ce98cfe2ac07c895e709284227a16975b2dc67
Author: Cristy <urban-warrior@imagemagick.org>
Date:   Sun Jun 7 08:17:35 2026 -0400

    https://github.com/ImageMagick/ImageMagick/issues/8743

diff --git a/coders/uhdr.c b/coders/uhdr.c
index 5efd5a3f7..9a22bf7b6 100644
--- a/coders/uhdr.c
+++ b/coders/uhdr.c
@@ -43,6 +43,8 @@
 #include "MagickCore/module.h"
 #include "MagickCore/option.h"
 #include "MagickCore/profile-private.h"
+#include "MagickCore/string_.h"
+#include "MagickCore/string-private.h"
 #if defined(MAGICKCORE_UHDR_DELEGATE)
 #include "ultrahdr_api.h"
 #include "uhdr.h"
@@ -138,6 +140,10 @@ static uhdr_color_transfer_t map_ct_to_uhdr_ct(const char *input_ct)
 static Image *ReadUHDRImage(const ImageInfo *image_info,
   ExceptionInfo *exception)
 {
+#define SetUHDRProperty(name, fmt, value) \
+  (void) FormatLocaleString(buffer, sizeof(buffer), fmt, value); \
+  (void) SetImageProperty(image, "uhdr:GCamera." name, buffer, exception)
+
   Image
     *image;

@@ -164,10 +170,10 @@ static Image *ReadUHDRImage(const ImageInfo *image_info,

   const char *option = GetImageOption(image_info, "uhdr:output-color-transfer");
   uhdr_color_transfer_t decoded_img_ct =
-      (option != (const char *)NULL) ? map_ct_to_uhdr_ct(option) : UHDR_CT_UNSPECIFIED;
+      (option != (const char *)NULL) ? map_ct_to_uhdr_ct(option) : UHDR_CT_SRGB;

   uhdr_img_fmt_t
-    decoded_img_fmt;
+    decoded_img_fmt = UHDR_CT_SRGB;

   if (decoded_img_ct == UHDR_CT_LINEAR)
     decoded_img_fmt = UHDR_IMG_FMT_64bppRGBAHalfFloat;
@@ -201,27 +207,67 @@ static Image *ReadUHDRImage(const ImageInfo *image_info,
   image->rows = uhdr_dec_get_image_height(handle);

   if (image_info->ping != MagickFalse)
-  {
-    uhdr_release_decoder(handle);
-    status = CloseBlob(image);
-    if (status == MagickFalse)
-      return (DestroyImageList(image));
-    return (GetFirstImageInList(image));
-  }
+    {
+      uhdr_release_decoder(handle);
+      (void) CloseBlob(image);
+      return(GetFirstImageInList(image));
+    }

   CHECK_IF_ERR(uhdr_decode(handle))

   uhdr_raw_image_t
     *dst = uhdr_get_decoded_image(handle);

-  status = SetImageExtent(image, image->columns, image->rows, exception);
-  if (status == MagickFalse)
+  /*
+    Preserve compressed gain‑map + metadata for transcoding.
+  */
   {
-    uhdr_release_decoder(handle);
-    CloseBlob(image);
-    return (DestroyImageList(image));
+    uhdr_mem_block_t
+      *gainmap_image = uhdr_dec_get_gainmap_image(handle);
+
+    uhdr_gainmap_metadata_t
+      *gainmap_info = uhdr_dec_get_gainmap_metadata(handle);
+
+    if ((gainmap_image != (uhdr_mem_block_t *) NULL) &&
+        (gainmap_info != (uhdr_gainmap_metadata_t *) NULL))
+    {
+      char
+        buffer[MagickPathExtent];
+
+      /*
+        Set gainmap as a binary profile.
+      */
+      StringInfo *gainmap_profile = BlobToProfileStringInfo("uhdr:gainmap",
+        gainmap_image->data,gainmap_image->data_sz,exception);
+      (void) SetImageProfilePrivate(image,gainmap_profile,exception);
+
+      /*
+        Set metadata as properties.
+      */
+      SetUHDRProperty("GainMapMaxR","%g",gainmap_info->max_content_boost[0]);
+      SetUHDRProperty("GainMapMaxG","%g",gainmap_info->max_content_boost[1]);
+      SetUHDRProperty("GainMapMaxB","%g",gainmap_info->max_content_boost[2]);
+      SetUHDRProperty("GainMapMinR","%g",gainmap_info->min_content_boost[0]);
+      SetUHDRProperty("GainMapMinG","%g",gainmap_info->min_content_boost[1]);
+      SetUHDRProperty("GainMapMinB","%g",gainmap_info->min_content_boost[2]);
+      SetUHDRProperty("GainMapGammaR","%g",gainmap_info->gamma[0]);
+      SetUHDRProperty("GainMapGammaG","%g",gainmap_info->gamma[1]);
+      SetUHDRProperty("GainMapGammaB","%g",gainmap_info->gamma[2]);
+      SetUHDRProperty("HDRCapacityMin","%g",gainmap_info->hdr_capacity_min);
+      SetUHDRProperty("HDRCapacityMax","%g",gainmap_info->hdr_capacity_max);
+      SetUHDRProperty("UseBaseColorGrade","%d",gainmap_info->use_base_cg);
+      SetUHDRProperty("GainMapPreserved","%s","true");
+    }
   }

+  status=SetImageExtent(image,image->columns,image->rows,exception);
+  if (status == MagickFalse)
+    {
+      uhdr_release_decoder(handle);
+      CloseBlob(image);
+      return(DestroyImageList(image));
+    }
+
 #undef CHECK_IF_ERR

   uhdr_mem_block_t *exif = uhdr_dec_get_exif(handle);
@@ -511,7 +557,7 @@ static uhdr_mem_block_t GetExifProfile(Image *image, ExceptionInfo *exception)
     *profile;

   uhdr_mem_block_t
-    uhdr_profile = {};
+    uhdr_profile = { 0 };

   ResetImageProfileIterator(image);
   for (name = GetNextImageProfile(image); name != (const char *)NULL;)
@@ -567,6 +613,12 @@ static void fillRawImageDescriptor(uhdr_raw_image_t *imgDescriptor, const ImageI
 static MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,
   Image *images,ExceptionInfo *exception)
 {
+#define GetUHDRProptery(name,field) \
+  do { \
+    const char *v = GetImageProperty(image,"uhdr:GCamera." name,exception); \
+    if (v != (const char *) NULL) gainmap_info.field=(float) atof(v); \
+  } while (0)
+
   Image
     *image = images;

@@ -586,22 +638,79 @@ static MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,
   assert(image->signature == MagickCoreSignature);
   if (IsEventLogging() != MagickFalse)
     (void) LogMagickEvent(TraceEvent, GetMagickModule(), "%s", image->filename);
-  status = OpenBlob(image_info, image, WriteBinaryBlobMode, exception);
+  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
   if (status == MagickFalse)
     return (status);

+  const StringInfo *gainmap_profile = GetImageProfile(image,"uhdr:gainmap");
+
+  if (gainmap_profile != (const StringInfo *) NULL)
+    {
+      /*
+        Preserve gainmap+metadata (for transcoding). If these exist, we do not
+        regenerate the gainmap.  Instead, we pass them directly to the encoder.
+      */
+      uhdr_compressed_image_t gainmap_image = { 0 };
+      uhdr_gainmap_metadata_t gainmap_info = { 0 };
+
+      /*
+        Compressed image descriptor.
+      */
+      gainmap_image.data=(void *) GetStringInfoDatum(gainmap_profile);
+      gainmap_image.data_sz=GetStringInfoLength(gainmap_profile);
+      gainmap_image.capacity=gainmap_image.data_sz;
+      gainmap_image.cg=UHDR_CG_UNSPECIFIED;
+      gainmap_image.ct=UHDR_CT_UNSPECIFIED;
+      gainmap_image.range=UHDR_CR_UNSPECIFIED;
+      /*
+        Gainmap metadata descriptor.
+      */
+      GetUHDRProptery("GainMapMaxR",max_content_boost[0]);
+      GetUHDRProptery("GainMapMaxG",max_content_boost[1]);
+      GetUHDRProptery("GainMapMaxB",max_content_boost[2]);
+
+      GetUHDRProptery("GainMapMinR",min_content_boost[0]);
+      GetUHDRProptery("GainMapMinG",min_content_boost[1]);
+      GetUHDRProptery("GainMapMinB",min_content_boost[2]);
+
+      GetUHDRProptery("GainMapGammaR", gamma[0]);
+      GetUHDRProptery("GainMapGammaG", gamma[1]);
+      GetUHDRProptery("GainMapGammaB", gamma[2]);
+
+      GetUHDRProptery("HDRCapacityMin", hdr_capacity_min);
+      GetUHDRProptery("HDRCapacityMax", hdr_capacity_max);
+
+      {
+        const char *v = GetImageProperty(image,"uhdr:GCamera.UseBaseColorGrade",exception);
+        if (v != (const char *) NULL)
+          gainmap_info.use_base_cg=atoi(v);
+      }
+
+      /*
+        Set gainmap.
+      */
+      uhdr_codec_private_t *encoder = uhdr_create_encoder();
+      if (encoder == NULL)
+        {
+          ThrowMagickException(exception,GetMagickModule(),CoderError,
+            "FailedToCreateEncoder","`%s'",image->filename);
+          return(MagickFalse);
+        }
+      uhdr_enc_set_gainmap_image(encoder,&gainmap_image,&gainmap_info);
+    }
+
   const char
-    *option = GetImageOption(image_info, "uhdr:hdr-color-transfer");
+    *option = GetImageOption(image_info,"uhdr:hdr-color-transfer");

   uhdr_color_transfer_t
-    hdr_ct = (option != (const char *)NULL) ? map_ct_to_uhdr_ct(option) : UHDR_CT_UNSPECIFIED;
+    hdr_ct = (option != (const char *) NULL) ? map_ct_to_uhdr_ct(option) : UHDR_CT_UNSPECIFIED;

   if (hdr_ct == UHDR_CT_UNSPECIFIED)
-  {
-    (void)ThrowMagickException(exception, GetMagickModule(), ConfigureWarning,
-                               "invalid hdr color transfer received, ", "%s", "exiting ... ");
-    return (MagickFalse);
-  }
+    {
+      (void) ThrowMagickException(exception,GetMagickModule(),ConfigureWarning,
+        "invalid hdr color transfer received, ","%s","exiting ... ");
+      return(MagickFalse);
+    }

   /*
     HDR intent:
@@ -614,7 +723,7 @@ static MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,
   int
     hdrIntentMinDepth = hdr_ct == UHDR_CT_LINEAR ? 16 : 10;

-  for (int i = 0; i < GetImageListLength(image); i++)
+  for (int i = 0; i < (ssize_t) GetImageListLength(image); i++)
   {
     /* Classify image as hdr/sdr intent basing on depth */
     int
@@ -637,17 +746,17 @@ static MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,
           "WidthOrHeightExceedsLimit","%s",image->filename);
         goto next_image;
       }
-    bpp = image->depth >= hdrIntentMinDepth ? 2 : 1;
+    bpp = (int) image->depth >= hdrIntentMinDepth ? 2 : 1;
     if (IssRGBCompatibleColorspace(image->colorspace) && !IsGrayColorspace(image->colorspace))
     {
-      if (image->depth >= hdrIntentMinDepth && hdr_ct == UHDR_CT_LINEAR)
+      if ((int) image->depth >= hdrIntentMinDepth && hdr_ct == UHDR_CT_LINEAR)
         bpp = 8; /* rgbahalf float */
       else
         bpp = 4; /* rgba1010102 or rgba8888 */
     }
     else if (IsYCbCrCompatibleColorspace(image->colorspace))
     {
-      if (image->depth >= hdrIntentMinDepth && hdr_ct == UHDR_CT_LINEAR)
+      if ((int) image->depth >= hdrIntentMinDepth && hdr_ct == UHDR_CT_LINEAR)
       {
         (void) ThrowMagickException(exception, GetMagickModule(), ConfigureWarning,
           "linear color transfer inputs MUST be compatible with RGB Colorspace, ", "%s",
@@ -687,14 +796,14 @@ static MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,
         picSize/=2;
       }

-    if ((image->depth < hdrIntentMinDepth) && (image->depth != 8))
+    if (((int) image->depth < hdrIntentMinDepth) && (image->depth != 8))
     {
       (void) ThrowMagickException(exception, GetMagickModule(), ConfigureWarning,
         "Received image with unexpected bit depth","%s","ignoring ...");
       goto next_image;
     }

-    if ((image->depth >= hdrIntentMinDepth) &&
+    if (((int) image->depth >= hdrIntentMinDepth) &&
         (hdrImgDescriptor.planes[UHDR_PLANE_Y] != NULL))
     {
       (void) ThrowMagickException(exception, GetMagickModule(), ConfigureWarning,
@@ -718,7 +827,7 @@ static MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,
       break;
     }

-    if (image->depth >= hdrIntentMinDepth)
+    if ((int) image->depth >= hdrIntentMinDepth)
     {
       if (IsYCbCrCompatibleColorspace(image->colorspace))
       {
@@ -806,7 +915,7 @@ static MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,

       for (x = 0; x < (ssize_t) image->columns; x++)
       {
-        if (image->depth >= hdrIntentMinDepth)
+        if ((int) image->depth >= hdrIntentMinDepth)
         {
           if (hdrImgDescriptor.fmt == UHDR_IMG_FMT_24bppYCbCrP010)
           {
@@ -892,7 +1001,7 @@ static MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,
     }

 next_image:
-    if (i != GetImageListLength(image) - 1)
+    if (i != (ssize_t) GetImageListLength(image) - 1)
     {
       if (GetNextImageInList(image) == (Image *) NULL)
       {
@@ -990,7 +1099,8 @@ next_image:
       option = GetImageOption(image_info, "uhdr:target-display-peak-brightness");
       float targetDispPeakBrightness = option != (const char *)NULL ? atof(option) : -1.0f;

-      if (targetDispPeakBrightness != -1.0f)
+      if (targetDispPeakBrightness > 0.0f)
+
         CHECK_IF_ERR(uhdr_enc_set_target_display_peak_brightness(handle, targetDispPeakBrightness))
     }