Commit f49eedaba for imagemagick.org

commit f49eedaba6b57ab30a66bfc7c16bfe85b733102a
Author: Greg B <64932474+gregbenz@users.noreply.github.com>
Date:   Thu Jun 11 09:54:26 2026 -0500

    Detect Ultra HDR JPEGs from gain-map metadata (#8794)

    Co-authored-by: Greg Benz <git@gregbenzphotography.com>

diff --git a/coders/jpeg.c b/coders/jpeg.c
index a10c1bce5..88b18463d 100644
--- a/coders/jpeg.c
+++ b/coders/jpeg.c
@@ -72,6 +72,7 @@
 #include "MagickCore/option.h"
 #include "MagickCore/option-private.h"
 #include "MagickCore/pixel-accessor.h"
+#include "MagickCore/policy.h"
 #include "MagickCore/profile.h"
 #include "MagickCore/profile-private.h"
 #include "MagickCore/property.h"
@@ -137,7 +138,11 @@ typedef struct _JPEGClientInfo
     *image;

   MagickBooleanType
-    finished;
+    detect_uhdr,
+    finished,
+    skip_icc_profiles,
+    skip_app_profiles,
+    uhdr_metadata;

   StringInfo
     *profiles[MaxJPEGProfiles+1];
@@ -182,8 +187,33 @@ typedef struct _QuantizationTable
 /*
   Const declarations.
 */
+#if defined(MAGICKCORE_JPEG_DELEGATE)
+typedef enum
+{
+  JPEGGainmapSignatureAPP1 = 0x01,
+  JPEGGainmapSignatureAPP2 = 0x02
+} JPEGGainmapSignatureContext;
+
+typedef struct _JPEGGainmapSignature
+{
+  const char
+    *signature;
+
+  unsigned int
+    contexts;
+} JPEGGainmapSignature;
+
 static const char
   xmp_namespace[] = "http://ns.adobe.com/xap/1.0/";
+
+static const JPEGGainmapSignature
+  gainmap_signatures[] =
+  {
+    { "http://ns.adobe.com/hdr-gain-map/1.0/", JPEGGainmapSignatureAPP1 },
+    { "http://ns.google.com/photos/1.0/gainmap/", JPEGGainmapSignatureAPP1 },
+    { "urn:iso:std:iso:ts:21496:-1", JPEGGainmapSignatureAPP2 }
+  };
+#endif

 /*
   Forward declarations.
@@ -226,6 +256,212 @@ static MagickBooleanType IsJPEG(const unsigned char *magick,const size_t length)
     return(MagickTrue);
   return(MagickFalse);
 }
+
+#if defined(MAGICKCORE_JPEG_DELEGATE)
+static MagickBooleanType IsGainmapSignatureContext(
+  const JPEGGainmapSignature *gainmap_signature,
+  const JPEGGainmapSignatureContext context)
+{
+  if ((gainmap_signature->contexts & (unsigned int) context) == 0)
+    return(MagickFalse);
+  return(MagickTrue);
+}
+
+static MagickBooleanType IsJPEGProfileMatching(const unsigned char *profile,
+  const size_t length,const char *signature)
+{
+  size_t
+    i,
+    signature_length;
+
+  signature_length=strlen(signature);
+  if (length < signature_length)
+    return(MagickFalse);
+  for (i=0; i <= (length-signature_length); i++)
+    if (memcmp(profile+i,signature,signature_length) == 0)
+      return(MagickTrue);
+  return(MagickFalse);
+}
+
+static const char *GetGainmapSignatureWithPrefix(const unsigned char *profile,
+  const size_t length,const JPEGGainmapSignatureContext context)
+{
+  size_t
+    i;
+
+  for (i=0; i < (sizeof(gainmap_signatures)/sizeof(*gainmap_signatures)); i++)
+  {
+    const char
+      *signature;
+
+    size_t
+      signature_length;
+
+    if (IsGainmapSignatureContext(gainmap_signatures+i,context) == MagickFalse)
+      continue;
+    signature=gainmap_signatures[i].signature;
+    signature_length=strlen(signature);
+    if ((length <= signature_length) &&
+        (memcmp(profile,signature,length) == 0))
+      return(signature);
+  }
+  return((const char *) NULL);
+}
+
+static size_t GetMaxGainmapSignatureLength(
+  const JPEGGainmapSignatureContext context)
+{
+  size_t
+    i,
+    max_signature_length = 0;
+
+  for (i=0; i < (sizeof(gainmap_signatures)/sizeof(*gainmap_signatures)); i++)
+  {
+    size_t
+      signature_length;
+
+    if (IsGainmapSignatureContext(gainmap_signatures+i,context) == MagickFalse)
+      continue;
+    signature_length=strlen(gainmap_signatures[i].signature);
+    if (signature_length > max_signature_length)
+      max_signature_length=signature_length;
+  }
+  return(max_signature_length);
+}
+
+static MagickBooleanType IsGainmapSignatureWindowMatching(
+  const unsigned char *window,const size_t length,
+  const JPEGGainmapSignatureContext context)
+{
+  size_t
+    i;
+
+  for (i=0; i < (sizeof(gainmap_signatures)/sizeof(*gainmap_signatures)); i++)
+  {
+    const char
+      *signature;
+
+    size_t
+      signature_length;
+
+    if (IsGainmapSignatureContext(gainmap_signatures+i,context) == MagickFalse)
+      continue;
+    signature=gainmap_signatures[i].signature;
+    signature_length=strlen(signature);
+    if ((length >= signature_length) &&
+        (memcmp(window+length-signature_length,signature,signature_length) ==
+          0))
+      return(MagickTrue);
+  }
+  return(MagickFalse);
+}
+
+static MagickBooleanType IsUltraHDRJPEGProfile(const unsigned char *profile,
+  const size_t length)
+{
+  size_t
+    i;
+
+  for (i=0; i < (sizeof(gainmap_signatures)/sizeof(*gainmap_signatures)); i++)
+    if ((IsGainmapSignatureContext(gainmap_signatures+i,
+        JPEGGainmapSignatureAPP1) != MagickFalse) &&
+        (IsJPEGProfileMatching(profile,length,gainmap_signatures[i].signature) !=
+        MagickFalse))
+      return(MagickTrue);
+  return(MagickFalse);
+}
+
+static MagickBooleanType IsJPEGXMPProfile(const unsigned char *profile,
+  const size_t length)
+{
+  if (length <= strlen(xmp_namespace))
+    return(MagickFalse);
+  if (LocaleNCompare((const char *) profile,xmp_namespace,
+      strlen(xmp_namespace)-1) == 0)
+    return(MagickTrue);
+  return(MagickFalse);
+}
+
+#if defined(MAGICKCORE_UHDR_DELEGATE)
+static void SetUltraHDRCoderFilename(ImageInfo *uhdr_info,
+  const char *filename)
+{
+  if (LocaleNCompare(filename,"UHDR:",5) == 0)
+    (void) CopyMagickString(uhdr_info->filename,filename,MagickPathExtent);
+  else
+    (void) FormatLocaleString(uhdr_info->filename,MagickPathExtent,
+      "UHDR:%s",filename);
+  (void) CopyMagickString(uhdr_info->magick,"UHDR",MagickPathExtent);
+}
+
+static MagickBooleanType IsUltraHDRJPEGAutoRoutingEnabled(
+  const ImageInfo *image_info,const PolicyRights rights)
+{
+  const char
+    *option;
+
+  option=GetImageOption(image_info,"jpeg:detect-uhdr");
+  if (IsStringFalse(option) != MagickFalse)
+    return(MagickFalse);
+  if (IsRightsAuthorized(CoderPolicyDomain,rights,"UHDR") == MagickFalse)
+    return(MagickFalse);
+  if (IsRightsAuthorized(ModulePolicyDomain,rights,"UHDr") == MagickFalse)
+    return(MagickFalse);
+  return(MagickTrue);
+}
+
+static MagickBooleanType IsUltraHDRJPEGReadOptionsCompatible(
+  const ImageInfo *image_info)
+{
+  if (GetImageOption(image_info,"jpeg:size") != (const char *) NULL)
+    return(MagickFalse);
+  if (GetImageOption(image_info,"jpeg:colors") != (const char *) NULL)
+    return(MagickFalse);
+  if (GetImageOption(image_info,"jpeg:block-smoothing") != (const char *) NULL)
+    return(MagickFalse);
+  if (GetImageOption(image_info,"jpeg:dct-method") != (const char *) NULL)
+    return(MagickFalse);
+  if (GetImageOption(image_info,"jpeg:fancy-upsampling") !=
+      (const char *) NULL)
+    return(MagickFalse);
+  return(MagickTrue);
+}
+
+static Image *ReadUltraHDRJPEGImage(const ImageInfo *image_info,
+  ExceptionInfo *exception)
+{
+  Image
+    *images;
+
+  ImageInfo
+    *uhdr_info;
+
+  uhdr_info=CloneImageInfo(image_info);
+  SetUltraHDRCoderFilename(uhdr_info,image_info->filename);
+  (void) SetImageOption(uhdr_info,"jpeg:detect-uhdr","false");
+  images=ReadImage(uhdr_info,exception);
+  uhdr_info=DestroyImageInfo(uhdr_info);
+  return(images);
+}
+
+static MagickBooleanType WriteUltraHDRJPEGImage(const ImageInfo *image_info,
+  Image *image,ExceptionInfo *exception)
+{
+  ImageInfo
+    *uhdr_info;
+
+  MagickBooleanType
+    status;
+
+  uhdr_info=CloneImageInfo(image_info);
+  SetUltraHDRCoderFilename(uhdr_info,image_info->filename);
+  (void) SetImageOption(uhdr_info,"jpeg:detect-uhdr","false");
+  status=WriteImage(uhdr_info,image,exception);
+  uhdr_info=DestroyImageInfo(uhdr_info);
+  return(status);
+}
+#endif
+#endif

 #if defined(MAGICKCORE_JPEG_DELEGATE)
 /*
@@ -507,6 +743,109 @@ static MagickBooleanType ReadProfilePayload(j_decompress_ptr jpeg_info,
   return(MagickTrue);
 }

+static void ThrowJPEGProfileEOFException(j_decompress_ptr jpeg_info)
+{
+  JPEGClientInfo
+    *client_info;
+
+  client_info=(JPEGClientInfo *) jpeg_info->client_data;
+  (void) ThrowMagickException(client_info->exception,GetMagickModule(),
+    CorruptImageError,"InsufficientImageDataInFile","`%s'",
+    client_info->image->filename);
+}
+
+static MagickBooleanType SkipProfilePayload(j_decompress_ptr jpeg_info,
+  const size_t length)
+{
+  if (length == 0)
+    return(MagickTrue);
+  (*jpeg_info->src->skip_input_data)(jpeg_info,(long) length);
+  if (jpeg_info->err->msg_code == JWRN_JPEG_EOF)
+    {
+      ThrowJPEGProfileEOFException(jpeg_info);
+      return(MagickFalse);
+    }
+  return(MagickTrue);
+}
+
+static MagickBooleanType ReadUltraHDRAPP1Profile(j_decompress_ptr jpeg_info,
+  const size_t length)
+{
+  char
+    xmp_signature[MagickPathExtent];
+
+  int
+    c;
+
+  JPEGClientInfo
+    *client_info;
+
+  size_t
+    i,
+    max_signature_length,
+    signature_length,
+    window_length;
+
+  unsigned char
+    window[MagickPathExtent];
+
+  client_info=(JPEGClientInfo *) jpeg_info->client_data;
+  signature_length=strlen(xmp_namespace)-1;
+  if (length <= signature_length)
+    return(SkipProfilePayload(jpeg_info,length));
+  for (i=0; i < signature_length; i++)
+  {
+    c=GetCharacter(jpeg_info);
+    if (c == EOF)
+      {
+        ThrowJPEGProfileEOFException(jpeg_info);
+        return(MagickFalse);
+      }
+    xmp_signature[i]=(char) c;
+  }
+  xmp_signature[i]='\0';
+  if (LocaleNCompare(xmp_signature,xmp_namespace,signature_length) != 0)
+    return(SkipProfilePayload(jpeg_info,length-signature_length));
+  max_signature_length=GetMaxGainmapSignatureLength(JPEGGainmapSignatureAPP1);
+  window_length=0;
+  for (i=signature_length; i < length; i++)
+  {
+    c=GetCharacter(jpeg_info);
+    if (c == EOF)
+      {
+        ThrowJPEGProfileEOFException(jpeg_info);
+        return(MagickFalse);
+      }
+    if (window_length < max_signature_length)
+      window[window_length++]=(unsigned char) c;
+    else
+      {
+        (void) memmove(window,window+1,max_signature_length-1);
+        window[max_signature_length-1]=(unsigned char) c;
+      }
+    if (IsGainmapSignatureWindowMatching(window,window_length,
+        JPEGGainmapSignatureAPP1) != MagickFalse)
+      {
+        client_info->uhdr_metadata=MagickTrue;
+        (void) SetImageArtifact(client_info->image,"jpeg:hdrgm","true");
+        return(SkipProfilePayload(jpeg_info,length-i-1));
+      }
+  }
+  return(MagickTrue);
+}
+
+static MagickBooleanType ReadSkippedAPPProfile(j_decompress_ptr jpeg_info,
+  const int marker,const size_t length)
+{
+  JPEGClientInfo
+    *client_info;
+
+  client_info=(JPEGClientInfo *) jpeg_info->client_data;
+  if ((marker == 1) && (client_info->detect_uhdr != MagickFalse))
+    return(ReadUltraHDRAPP1Profile(jpeg_info,length));
+  return(SkipProfilePayload(jpeg_info,length));
+}
+
 static boolean ReadComment(j_decompress_ptr jpeg_info)
 {
 #define GetProfileLength(jpeg_info,length) \
@@ -600,40 +939,92 @@ static boolean ReadICCProfile(j_decompress_ptr jpeg_info)
   for (i=0; i < 12; i++)
     magick[i]=(char) GetCharacter(jpeg_info);
   magick[i]='\0';
+  client_info=(JPEGClientInfo *) jpeg_info->client_data;
   if (LocaleCompare(magick,ICC_PROFILE) != 0)
     {
+      const char
+        *gainmap_signature;
+
+      const size_t
+        prefix_length = 12;
+
       if (LocaleCompare(magick,"MPF") == 0)
         {
+          if (client_info->skip_icc_profiles != MagickFalse)
+            return(SkipProfilePayload(jpeg_info,length-prefix_length) !=
+              MagickFalse ? TRUE : FALSE);
           /*
             Multi-picture support (Future).
           */
-          status=ReadProfilePayload(jpeg_info,ICC_INDEX,length-12);
+          status=ReadProfilePayload(jpeg_info,ICC_INDEX,length-prefix_length);
           if (status == MagickFalse)
             return(FALSE);
-          client_info=(JPEGClientInfo *) jpeg_info->client_data;
           status=SetImageProfile(client_info->image,"MPF",
             client_info->profiles[ICC_INDEX],client_info->exception);
           client_info->profiles[ICC_INDEX]=DestroyStringInfo(
             client_info->profiles[ICC_INDEX]);
           return(TRUE);
         }
+      gainmap_signature=GetGainmapSignatureWithPrefix((const unsigned char *)
+        magick,prefix_length,JPEGGainmapSignatureAPP2);
+      if (gainmap_signature != (const char *) NULL)
+        {
+          if (client_info->detect_uhdr != MagickFalse)
+            {
+              MagickBooleanType
+                signature_match = MagickFalse;
+
+              size_t
+                signature_length;
+
+              signature_length=strlen(gainmap_signature);
+              if (length >= signature_length)
+                {
+                  signature_match=MagickTrue;
+                  for (i=(ssize_t) prefix_length;
+                       i < (ssize_t) signature_length; i++)
+                  {
+                    int
+                      c;
+
+                    c=GetCharacter(jpeg_info);
+                    if (c == EOF)
+                      {
+                        ThrowJPEGProfileEOFException(jpeg_info);
+                        return(FALSE);
+                      }
+                    if (c != (int) ((unsigned char) gainmap_signature[i]))
+                      signature_match=MagickFalse;
+                  }
+                  if (signature_match != MagickFalse)
+                    {
+                      client_info->uhdr_metadata=MagickTrue;
+                      (void) SetImageArtifact(client_info->image,"jpeg:hdrgm",
+                        "true");
+                    }
+                  return(SkipProfilePayload(jpeg_info,length-signature_length)
+                    != MagickFalse ? TRUE : FALSE);
+                }
+            }
+          return(SkipProfilePayload(jpeg_info,length-prefix_length) !=
+            MagickFalse ? TRUE : FALSE);
+        }
       /*
         Not a ICC profile, return.
       */
-      for (i=0; i < (ssize_t) (length-12); i++)
-        if (GetCharacter(jpeg_info) == EOF)
-          break;
-      return(TRUE);
+      return(SkipProfilePayload(jpeg_info,length-prefix_length) != MagickFalse ?
+        TRUE : FALSE);
     }
   (void) GetCharacter(jpeg_info);  /* id */
   (void) GetCharacter(jpeg_info);  /* markers */
   length-=14;
+  image=client_info->image;
+  exception=client_info->exception;
+  if (client_info->skip_icc_profiles != MagickFalse)
+    return(SkipProfilePayload(jpeg_info,length) != MagickFalse ? TRUE : FALSE);
   status=ReadProfilePayload(jpeg_info,ICC_INDEX,length);
   if (status == MagickFalse)
     return(FALSE);
-  client_info=(JPEGClientInfo *) jpeg_info->client_data;
-  image=client_info->image;
-  exception=client_info->exception;
   status=SetImageProfile(image,"icc",client_info->profiles[ICC_INDEX],
     exception);
   return(status == MagickFalse ? FALSE : TRUE);
@@ -755,16 +1146,26 @@ static boolean ReadAPPProfiles(j_decompress_ptr jpeg_info)
   client_info=(JPEGClientInfo *) jpeg_info->client_data;
   image=client_info->image;
   exception=client_info->exception;
+  if (client_info->skip_app_profiles != MagickFalse)
+    return(ReadSkippedAPPProfile(jpeg_info,marker,length) != MagickFalse ?
+      TRUE : FALSE);
   if (client_info->profiles[marker] != (StringInfo *) NULL)
     offset=GetStringInfoLength(client_info->profiles[marker]);
   status=ReadProfilePayload(jpeg_info,marker,length);
   if (status == MagickFalse)
     return(FALSE);
+  p=GetStringInfoDatum(client_info->profiles[marker])+offset;
+  if ((marker == 1) &&
+      (client_info->detect_uhdr != MagickFalse) &&
+      (IsJPEGXMPProfile(p,length) != MagickFalse) &&
+      (IsUltraHDRJPEGProfile(p,length) != MagickFalse))
+    {
+      client_info->uhdr_metadata=MagickTrue;
+      (void) SetImageArtifact(image,"jpeg:hdrgm","true");
+    }
   if (marker != 1)
     return(TRUE);
-  p=GetStringInfoDatum(client_info->profiles[marker])+offset;
-  if ((length > strlen(xmp_namespace)) &&
-      (LocaleNCompare((char *) p,xmp_namespace,strlen(xmp_namespace)-1) == 0))
+  if (IsJPEGXMPProfile(p,length) != MagickFalse)
     {
       size_t
         i;
@@ -1159,6 +1560,13 @@ static Image *ReadOneJPEGImage(const ImageInfo *image_info,
   memory_info=(MemoryInfo *) NULL;
   client_info->exception=exception;
   client_info->image=image;
+  option=GetImageOption(image_info,"profile:skip");
+  client_info->skip_icc_profiles=IsOptionMember("ICC",option) != MagickFalse ?
+    MagickTrue : MagickFalse;
+  client_info->skip_app_profiles=IsOptionMember("APP",option) != MagickFalse ?
+    MagickTrue : MagickFalse;
+  client_info->detect_uhdr=IsStringFalse(GetImageOption(image_info,
+    "jpeg:detect-uhdr")) == MagickFalse ? MagickTrue : MagickFalse;
   if (setjmp(client_info->error_recovery) != 0)
     {
       client_info=JPEGCleanup(jpeg_info,client_info);
@@ -1176,12 +1584,12 @@ static Image *ReadOneJPEGImage(const ImageInfo *image_info,
   jpeg_info->progress=(&jpeg_progress);
   JPEGSourceManager(jpeg_info,image);
   jpeg_set_marker_processor(jpeg_info,JPEG_COM,ReadComment);
-  option=GetImageOption(image_info,"profile:skip");
   for (i=1; i < MaxJPEGProfiles; i++)
   {
     if (i == ICC_INDEX)
       {
-        if (IsOptionMember("ICC",option) == MagickFalse)
+        if ((client_info->skip_icc_profiles == MagickFalse) ||
+            (client_info->detect_uhdr != MagickFalse))
           jpeg_set_marker_processor(jpeg_info,ICC_MARKER,ReadICCProfile);
       }
     else if (i == IPTC_INDEX)
@@ -1194,12 +1602,72 @@ static Image *ReadOneJPEGImage(const ImageInfo *image_info,
         /*
           Ignore APP14 as this will change the colors of the image.
         */
-        if (IsOptionMember("APP",option) == MagickFalse)
+        if ((client_info->skip_app_profiles == MagickFalse) ||
+            ((i == 1) && (client_info->detect_uhdr != MagickFalse)))
           jpeg_set_marker_processor(jpeg_info,(int) (JPEG_APP0+i),
             ReadAPPProfiles);
       }
   }
   (void) jpeg_read_header(jpeg_info,TRUE);
+#if defined(MAGICKCORE_UHDR_DELEGATE)
+  if ((client_info->uhdr_metadata != MagickFalse) && (*offset == 0) &&
+      (GetBlobStreamData(image) != (const unsigned char *) NULL) &&
+      (GetBlobSize(image) != 0) &&
+      (LocaleCompare(image_info->magick,"MPO") != 0) &&
+      (IsUltraHDRJPEGReadOptionsCompatible(image_info) != MagickFalse) &&
+      (IsUltraHDRJPEGAutoRoutingEnabled(image_info,ReadPolicyRights) !=
+        MagickFalse))
+    {
+      const char
+        *sampling_factor;
+
+      ExceptionInfo
+        *uhdr_exception;
+
+      Image
+        *uhdr_image;
+
+      JPEGSetImageQuality(jpeg_info,image);
+      JPEGSetImageSamplingFactor(jpeg_info,image,exception);
+      if (jpeg_info->saw_JFIF_marker != 0)
+        {
+          if (jpeg_info->density_unit == 1)
+            image->units=PixelsPerInchResolution;
+          else if (jpeg_info->density_unit == 2)
+            image->units=PixelsPerCentimeterResolution;
+          if (IsAspectRatio(jpeg_info) == MagickFalse)
+            {
+              if (jpeg_info->X_density != 0)
+                image->resolution.x=(double) jpeg_info->X_density;
+              if (jpeg_info->Y_density != 0)
+                image->resolution.y=(double) jpeg_info->Y_density;
+            }
+        }
+      uhdr_exception=AcquireExceptionInfo();
+      uhdr_image=ReadUltraHDRJPEGImage(image_info,uhdr_exception);
+      if (uhdr_image != (Image *) NULL)
+        {
+          (void) SetImageArtifact(uhdr_image,"jpeg:hdrgm","true");
+          if (image->quality != UndefinedCompressionQuality)
+            uhdr_image->quality=image->quality;
+          uhdr_image->resolution=image->resolution;
+          uhdr_image->units=image->units;
+          sampling_factor=GetImageProperty(image,"jpeg:sampling-factor",
+            exception);
+          if (sampling_factor != (const char *) NULL)
+            (void) SetImageProperty(uhdr_image,"jpeg:sampling-factor",
+              sampling_factor,exception);
+          InheritException(exception,uhdr_exception);
+          uhdr_exception=DestroyExceptionInfo(uhdr_exception);
+          client_info=JPEGCleanup(jpeg_info,client_info);
+          (void) CloseBlob(image);
+          image=DestroyImage(image);
+          return(uhdr_image);
+        }
+      uhdr_exception=DestroyExceptionInfo(uhdr_exception);
+      (void) DeleteImageArtifact(image,"jpeg:hdrgm");
+    }
+#endif
   if (IsYCbCrCompatibleColorspace(image_info->colorspace) != MagickFalse)
     jpeg_info->out_color_space=JCS_YCbCr;
   /*
@@ -3171,6 +3639,13 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
   struct jpeg_compress_struct
     jpeg_info;

+#if defined(MAGICKCORE_UHDR_DELEGATE)
+  if ((GetImageProfile(image,"hdrgm") != (const StringInfo *) NULL) &&
+      (GetImageOption(image_info,"jpeg:extent") == (const char *) NULL) &&
+      (IsUltraHDRJPEGAutoRoutingEnabled(image_info,WritePolicyRights) !=
+        MagickFalse))
+    return(WriteUltraHDRJPEGImage(image_info,image,exception));
+#endif
   return(WriteJPEGImage_(image_info,image,&jpeg_info,exception));
 }
 #endif
diff --git a/coders/uhdr.c b/coders/uhdr.c
index 32cceb3f2..db5d796fb 100644
--- a/coders/uhdr.c
+++ b/coders/uhdr.c
@@ -186,6 +186,20 @@ static Image *ReadUHDRImage(const ImageInfo *image_info,
   uhdr_color_transfer_t decoded_img_ct =
       (option != (const char *)NULL) ? map_ct_to_uhdr_ct(option) : UHDR_CT_SRGB;

+  const char *profile_skip = GetImageOption(image_info, "profile:skip");
+
+  MagickBooleanType
+    skip_app_profiles = IsOptionMember("APP",profile_skip),
+    skip_exif_profile = IsOptionMember("EXIF",profile_skip),
+    skip_gainmap_profile = IsOptionMember("HDRGM",profile_skip),
+    skip_icc_profile = IsOptionMember("ICC",profile_skip);
+
+  if (skip_app_profiles != MagickFalse)
+    {
+      skip_exif_profile=MagickTrue;
+      skip_gainmap_profile=MagickTrue;
+    }
+
   uhdr_img_fmt_t
     decoded_img_fmt = UHDR_CT_SRGB;

@@ -242,7 +256,8 @@ static Image *ReadUHDRImage(const ImageInfo *image_info,
     uhdr_gainmap_metadata_t
       *gainmap_info = uhdr_dec_get_gainmap_metadata(handle);

-    if ((gainmap_image != (uhdr_mem_block_t *) NULL) &&
+    if ((skip_gainmap_profile == MagickFalse) &&
+        (gainmap_image != (uhdr_mem_block_t *) NULL) &&
         (gainmap_info != (uhdr_gainmap_metadata_t *) NULL))
     {
       char
@@ -289,7 +304,7 @@ static Image *ReadUHDRImage(const ImageInfo *image_info,
 #undef CHECK_IF_ERR

   uhdr_mem_block_t *exif = uhdr_dec_get_exif(handle);
-  if (exif != NULL)
+  if ((skip_exif_profile == MagickFalse) && (exif != NULL))
   {
     StringInfo *exif_data = BlobToProfileStringInfo("exif",exif->data,
       exif->data_sz,exception);
@@ -297,7 +312,8 @@ static Image *ReadUHDRImage(const ImageInfo *image_info,
   }

   uhdr_mem_block_t *icc = uhdr_dec_get_icc(handle);
-  if ((icc != NULL) && (icc->data != NULL) && (icc->data_sz != 0))
+  if ((skip_icc_profile == MagickFalse) && (icc != NULL) &&
+      (icc->data != NULL) && (icc->data_sz != 0))
   {
     const unsigned char
       *icc_data_start = (const unsigned char *) icc->data;
@@ -320,10 +336,17 @@ static Image *ReadUHDRImage(const ImageInfo *image_info,
     (void) SetImageProfilePrivate(image,icc_data,exception);
   }

-  SetImageColorspace(image, RGBColorspace, exception);
-
   if (decoded_img_ct == UHDR_CT_LINEAR)
-    image->gamma = 1.0;
+    {
+      SetImageColorspace(image,RGBColorspace,exception);
+      image->gamma=1.0;
+    }
+  else if ((decoded_img_ct == UHDR_CT_SRGB) && (dst->cg == UHDR_CG_DISPLAY_P3))
+    SetImageColorspace(image,DisplayP3Colorspace,exception);
+  else if (decoded_img_ct == UHDR_CT_SRGB)
+    SetImageColorspace(image,sRGBColorspace,exception);
+  else
+    SetImageColorspace(image,RGBColorspace,exception);

   image->compression = JPEGCompression;
   if (decoded_img_fmt == UHDR_IMG_FMT_32bppRGBA8888)
@@ -1036,6 +1059,7 @@ static StringInfo *TransformGainMapProfile(const ImageInfo *image_info,
   (void) CopyMagickString(gainmap_info->filename,"JPEG:hdrgm.jpg",
     MagickPathExtent);
   (void) CopyMagickString(gainmap_info->magick,"JPEG",MagickPathExtent);
+  (void) SetImageOption(gainmap_info,"jpeg:detect-uhdr","false");
   gainmap_images=BlobToImage(gainmap_info,GetStringInfoDatum(gainmap_profile),
     GetStringInfoLength(gainmap_profile),exception);
   if (gainmap_images == (Image *) NULL)
@@ -1182,6 +1206,9 @@ static MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,

   const StringInfo *gainmap_profile = GetImageProfile(image,"hdrgm");

+  const size_t
+    image_count = GetImageListLength(image);
+
   if (gainmap_profile != (const StringInfo *) NULL)
     {
       /*
@@ -1260,7 +1287,7 @@ static MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,
   int
     hdrIntentMinDepth = hdr_ct == UHDR_CT_LINEAR ? 16 : 10;

-  for (int i = 0; i < (ssize_t) GetImageListLength(image); i++)
+  for (int i = 0; i < (ssize_t) image_count; i++)
   {
     /* Classify image as hdr/sdr intent basing on depth */
     int
@@ -1538,7 +1565,7 @@ static MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,
     }

 next_image:
-    if (i != (ssize_t) GetImageListLength(image) - 1)
+    if (i != (ssize_t) image_count - 1)
     {
       if (GetNextImageInList(image) == (Image *) NULL)
       {
@@ -1551,7 +1578,7 @@ next_image:
     }

     status = SetImageProgress(image, SaveImageTag, (MagickOffsetType)i,
-      GetImageListLength(image));
+      image_count);
     if (status == MagickFalse)
       break;
   }