From 1c7767fc5f822c6edc104c1220d523e96fa20b5a Mon Sep 17 00:00:00 2001
From: Dirk Lemstra <dirk@lemstra.org>
Date: Thu, 9 Apr 2026 18:41:09 +0200
Subject: [PATCH] Patch to correct the sample size for 16 bit floats in the JXL
 encoder (GHSA-jvgr-9ph5-m8v4)

---
 coders/jxl.c | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)

Index: ImageMagick-7.1.1-43/coders/jxl.c
===================================================================
--- ImageMagick-7.1.1-43.orig/coders/jxl.c
+++ ImageMagick-7.1.1-43/coders/jxl.c
@@ -22,7 +22,7 @@
 %  You may not use this file except in compliance with the License.  You may  %
 %  obtain a copy of the License at                                            %
 %                                                                             %
-%    https://imagemagick.org/script/license.php                               %
+%    https://imagemagick.org/license/                                         %
 %                                                                             %
 %  Unless required by applicable law or agreed to in writing, software        %
 %  distributed under the License is distributed on an "AS IS" BASIS,          %
@@ -43,11 +43,13 @@
 #include "MagickCore/blob.h"
 #include "MagickCore/blob-private.h"
 #include "MagickCore/cache.h"
+#include "MagickCore/channel.h"
 #include "MagickCore/colorspace-private.h"
 #include "MagickCore/exception.h"
 #include "MagickCore/exception-private.h"
 #include "MagickCore/image.h"
 #include "MagickCore/image-private.h"
+#include "MagickCore/layer.h"
 #include "MagickCore/list.h"
 #include "MagickCore/magick.h"
 #include "MagickCore/memory_.h"
@@ -626,6 +628,9 @@ static Image *ReadJXLImage(const ImageIn
       }
       case JXL_DEC_FRAME:
       {
+        JxlFrameHeader
+          frame_header;
+
         if (image_count++ != 0)
           {
             JXLAddProfilesToImage(image,&exif_profile,&xmp_profile,exception);
@@ -638,13 +643,22 @@ static Image *ReadJXLImage(const ImageIn
             image=SyncNextImageInList(image);
             JXLInitImage(image,&basic_info);
           }
+        (void) memset(&frame_header,0,sizeof(frame_header));
+        if (JxlDecoderGetFrameHeader(jxl_info,&frame_header) == JXL_DEC_SUCCESS)
+          image->delay=(size_t) frame_header.duration;
+        if ((basic_info.have_animation == JXL_TRUE) &&
+            (basic_info.alpha_bits != 0))
+          image->dispose=BackgroundDispose;
         break;
       }
       case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
       {
         status=SetImageExtent(image,image->columns,image->rows,exception);
         if (status == MagickFalse)
-          break;
+          {
+            jxl_status=JXL_DEC_ERROR;
+            break;
+          }
         (void) ResetImagePixels(image,exception);
         JXLSetFormat(image,&pixel_format,exception);
         if (extent == 0)
@@ -730,7 +744,7 @@ static Image *ReadJXLImage(const ImageIn
                 exception);
               if (exif_profile != (StringInfo *) NULL)
                 jxl_status=JxlDecoderSetBoxBuffer(jxl_info,
-                  GetStringInfoDatum(exif_profile),size);
+                  GetStringInfoDatum(exif_profile),(size_t) size);
             }
           }
         if (LocaleNCompare(type,"xml ",sizeof(type)) == 0)
@@ -744,7 +758,7 @@ static Image *ReadJXLImage(const ImageIn
                   exception);
                 if (xmp_profile != (StringInfo *) NULL)
                   jxl_status=JxlDecoderSetBoxBuffer(jxl_info,
-                    GetStringInfoDatum(xmp_profile),size);
+                    GetStringInfoDatum(xmp_profile),(size_t) size);
               }
           }
         if (jxl_status == JXL_DEC_SUCCESS)
@@ -884,7 +898,7 @@ ModuleExport void UnregisterJXLImage(voi
 */
 
 static JxlEncoderStatus JXLWriteMetadata(const Image *image,
-  JxlEncoder *jxl_info)
+  JxlEncoder *jxl_info, const StringInfo *icc_profile)
 {
   JxlColorEncoding
     color_encoding;
@@ -892,6 +906,12 @@ static JxlEncoderStatus JXLWriteMetadata
   JxlEncoderStatus
     jxl_status;
 
+  if (icc_profile != (StringInfo *) NULL)
+    {
+      jxl_status=JxlEncoderSetICCProfile(jxl_info,(const uint8_t *)
+        GetStringInfoDatum(icc_profile),GetStringInfoLength(icc_profile));
+      return(jxl_status);
+    }
   (void) memset(&color_encoding,0,sizeof(color_encoding));
   color_encoding.color_space=JXL_COLOR_SPACE_RGB;
   if (IsRGBColorspace(image->colorspace) == MagickFalse)
@@ -904,13 +924,24 @@ static JxlEncoderStatus JXLWriteMetadata
   return(jxl_status);
 }
 
-static inline float JXLGetDistance(const ImageInfo *image_info)
+static inline float JXLGetDistance(float quality)
 {
-  if (image_info->quality == 0)
-    return(1.0f);
-  if (image_info->quality >= 30)
-    return(0.1f+(float) (100-MagickMin(100,image_info->quality))*0.09f);
-  return(6.24f+(float) pow(2.5f,(30.0-image_info->quality)/5.0)/6.25f);
+  return quality >= 100.0f ? 0.0f
+         : quality >= 30
+             ? 0.1f + (100 - quality) * 0.09f
+             : 53.0f / 3000.0f * quality * quality - 23.0f / 20.0f * quality + 25.0f;
+}
+
+static inline MagickBooleanType JXLSameFrameType(const Image *image,
+  const Image *frame)
+{
+  if (image->depth != frame->depth)
+    return(MagickFalse);
+  if (image->alpha_trait != frame->alpha_trait)
+    return(MagickFalse);
+  if (image->colorspace != frame->colorspace)
+    return(MagickFalse);
+  return(MagickTrue);
 }
 
 static MagickBooleanType WriteJXLImage(const ImageInfo *image_info,Image *image,
@@ -920,6 +951,7 @@ static MagickBooleanType WriteJXLImage(c
     *option;
 
   const StringInfo
+    *icc_profile = (StringInfo *) NULL,
     *exif_profile = (StringInfo *) NULL,
     *xmp_profile = (StringInfo *) NULL;
 
@@ -948,13 +980,14 @@ static MagickBooleanType WriteJXLImage(c
     status;
 
   MemoryInfo
-    *pixel_info;
+    *pixel_info = (MemoryInfo *) NULL;
 
   MemoryManagerInfo
     memory_manager_info;
 
   size_t
-    bytes_per_row;
+    channels_size,
+    sample_size;
 
   unsigned char
     *pixels;
@@ -979,6 +1012,39 @@ static MagickBooleanType WriteJXLImage(c
   if ((IssRGBCompatibleColorspace(image->colorspace) == MagickFalse) &&
       (IsCMYKColorspace(image->colorspace) == MagickFalse))
     (void) TransformImageColorspace(image,sRGBColorspace,exception);
+  if ((image_info->adjoin != MagickFalse) &&
+      (GetNextImageInList(image) != (Image *) NULL))
+    {
+      Image
+        *frame;
+
+      MagickBooleanType
+        has_alpha;
+
+      size_t
+        depth;
+
+      depth=image->depth;
+      has_alpha=MagickFalse;
+      for (frame=image; frame != (Image *) NULL; frame=GetNextImageInList(frame))
+      {
+        if ((frame->alpha_trait & BlendPixelTrait) != 0)
+          has_alpha=MagickTrue;
+        if (frame->depth > depth)
+          depth=frame->depth;
+      }
+      for (frame=image; frame != (Image *) NULL; frame=GetNextImageInList(frame))
+      {
+        frame->depth=depth;
+        if (has_alpha != MagickFalse)
+          {
+            if ((frame->alpha_trait & BlendPixelTrait) == 0)
+              (void) SetImageAlphaChannel(frame,OpaqueAlphaChannel,exception);
+          }
+        if (frame->colorspace != image->colorspace)
+          (void) TransformImageColorspace(frame,image->colorspace,exception);
+      }
+    }
   /*
     Initialize JXL delegate library.
   */
@@ -1032,7 +1098,10 @@ static MagickBooleanType WriteJXLImage(c
       basic_info.num_extra_channels=1;
     }
   if (image_info->quality == 100)
-    basic_info.uses_original_profile=JXL_TRUE;
+    {
+      basic_info.uses_original_profile=JXL_TRUE;
+      icc_profile=GetImageProfile(image,"icc");
+    }
   if ((image_info->adjoin != MagickFalse) &&
       (GetNextImageInList(image) != (Image *) NULL))
     {
@@ -1041,7 +1110,6 @@ static MagickBooleanType WriteJXLImage(c
       basic_info.animation.tps_numerator=(uint32_t) image->ticks_per_second;
       basic_info.animation.tps_denominator=1;
       JxlEncoderInitFrameHeader(&frame_header);
-      frame_header.duration=1;
     }
   jxl_status=JxlEncoderSetBasicInfo(jxl_info,&basic_info);
   if (jxl_status != JXL_ENC_SUCCESS)
@@ -1063,9 +1131,9 @@ static MagickBooleanType WriteJXLImage(c
       (void) JxlEncoderSetFrameDistance(frame_settings,0.f);
       (void) JxlEncoderSetFrameLossless(frame_settings,JXL_TRUE);
     }
-  else
+  else if (image_info->quality != 0)
     (void) JxlEncoderSetFrameDistance(frame_settings,
-      JXLGetDistance(image_info));
+      JXLGetDistance((float) image_info->quality));
   option=GetImageOption(image_info,"jxl:effort");
   if (option != (const char *) NULL)
     (void) JxlEncoderFrameSettingsSetOption(frame_settings,
@@ -1108,7 +1176,7 @@ static MagickBooleanType WriteJXLImage(c
         }
       (void) JxlEncoderCloseBoxes(jxl_info);
     }
-  jxl_status=JXLWriteMetadata(image,jxl_info);
+  jxl_status=JXLWriteMetadata(image,jxl_info,icc_profile);
   if (jxl_status != JXL_ENC_SUCCESS)
     {
       JxlThreadParallelRunnerDestroy(runner);
@@ -1118,34 +1186,80 @@ static MagickBooleanType WriteJXLImage(c
   /*
     Write image as a JXL stream.
   */
-  bytes_per_row=image->columns*
-    (((image->alpha_trait & BlendPixelTrait) != 0) ? 4 : 3)*
-    ((pixel_format.data_type == JXL_TYPE_FLOAT) ? sizeof(float) :
-     (pixel_format.data_type == JXL_TYPE_UINT16) ? sizeof(short) :
-     sizeof(char));
+  sample_size=sizeof(char);
+  if ((pixel_format.data_type == JXL_TYPE_FLOAT) ||
+      (pixel_format.data_type == JXL_TYPE_FLOAT16))
+    sample_size=sizeof(float);
+  else
+    if (pixel_format.data_type == JXL_TYPE_UINT16)
+      sample_size=sizeof(short);
   if (IsGrayColorspace(image->colorspace) != MagickFalse)
-    bytes_per_row=image->columns*
-      (((image->alpha_trait & BlendPixelTrait) != 0) ? 2 : 1)*
-      ((pixel_format.data_type == JXL_TYPE_FLOAT) ? sizeof(float) :
-       (pixel_format.data_type == JXL_TYPE_UINT16) ? sizeof(short) :
-       sizeof(char));
-  pixel_info=AcquireVirtualMemory(bytes_per_row,image->rows*sizeof(*pixels));
-  if (pixel_info == (MemoryInfo *) NULL)
-    {
-      JxlThreadParallelRunnerDestroy(runner);
-      JxlEncoderDestroy(jxl_info);
-      ThrowWriterException(CoderError,"MemoryAllocationFailed");
-    }
+    channels_size=(((image->alpha_trait & BlendPixelTrait) != 0) ? 2U : 1U)*
+      sample_size;
+  else
+    channels_size=(((image->alpha_trait & BlendPixelTrait) != 0) ? 4U : 3U)*
+      sample_size;
   do
   {
     Image
       *next;
 
+    size_t
+      bytes_per_row;
+
+    if (HeapOverflowSanityCheckGetSize(image->columns,channels_size,&bytes_per_row) != MagickFalse)
+      {
+        (void) ThrowMagickException(exception,GetMagickModule(),CoderError,
+          "MemoryAllocationFailed","`%s'",image->filename);
+        status=MagickFalse;
+        break;
+      }
+    pixel_info=AcquireVirtualMemory(bytes_per_row,image->rows*sizeof(*pixels));
+    if (pixel_info == (MemoryInfo *) NULL)
+      {
+        (void) ThrowMagickException(exception,GetMagickModule(),CoderError,
+          "MemoryAllocationFailed","`%s'",image->filename);
+        status=MagickFalse;
+        break;
+      }
+
     if (basic_info.have_animation == JXL_TRUE)
       {
+        JxlBlendInfo
+          alpha_blend_info;
+
+        frame_header.duration=(uint32_t) image->delay;
+        if ((image->previous == (Image *) NULL) ||
+            (image->previous->dispose == BackgroundDispose) ||
+            (image->previous->dispose == PreviousDispose))
+          {
+            frame_header.layer_info.blend_info.blendmode=JXL_BLEND_REPLACE;
+            frame_header.layer_info.blend_info.source=0;
+          }
+        else
+          {
+            frame_header.layer_info.blend_info.blendmode=JXL_BLEND_BLEND;
+            frame_header.layer_info.blend_info.source=1;
+          }
+        frame_header.layer_info.save_as_reference=1;
+        if ((image->page.width != 0) && (image->page.height != 0))
+          {
+            frame_header.layer_info.have_crop=JXL_TRUE;
+            frame_header.layer_info.crop_x0=(int32_t) image->page.x;
+            frame_header.layer_info.crop_y0=(int32_t) image->page.y;
+            frame_header.layer_info.xsize=(uint32_t) image->columns;
+            frame_header.layer_info.ysize=(uint32_t) image->rows;
+          }
         jxl_status=JxlEncoderSetFrameHeader(frame_settings,&frame_header);
         if (jxl_status != JXL_ENC_SUCCESS)
           break;
+        if (basic_info.num_extra_channels > 0)
+          {
+            JxlEncoderInitBlendInfo(&alpha_blend_info);
+            alpha_blend_info.blendmode=frame_header.layer_info.blend_info.blendmode;
+            alpha_blend_info.source=frame_header.layer_info.blend_info.source;
+            (void) JxlEncoderSetExtraChannelBlendInfo(frame_settings,0,&alpha_blend_info);
+          }
       }
     pixels=(unsigned char *) GetVirtualMemoryBlob(pixel_info);
     if (IsGrayColorspace(image->colorspace) != MagickFalse)
@@ -1174,16 +1288,18 @@ static MagickBooleanType WriteJXLImage(c
     next=GetNextImageInList(image);
     if (next == (Image*) NULL)
       break;
-    if ((next->columns != image->columns) || (next->rows != image->rows))
+    if (JXLSameFrameType(image,next) == MagickFalse)
       {
        (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
          "FramesNotSameDimensions","`%s'",image->filename);
        status=MagickFalse;
        break;
       }
+    pixel_info=RelinquishVirtualMemory(pixel_info);
     image=SyncNextImageInList(image);
   } while (image_info->adjoin != MagickFalse);
-  pixel_info=RelinquishVirtualMemory(pixel_info);
+  if (pixel_info != (MemoryInfo *) NULL)
+    pixel_info=RelinquishVirtualMemory(pixel_info);
   if (jxl_status == JXL_ENC_SUCCESS)
     {
       unsigned char
