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.2-0/coders/jxl.c
===================================================================
--- ImageMagick-7.1.2-0.orig/coders/jxl.c
+++ ImageMagick-7.1.2-0/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)
@@ -919,17 +933,13 @@ static inline float JXLGetDistance(float
 }
 
 static inline MagickBooleanType JXLSameFrameType(const Image *image,
-  const Image *next)
+  const Image *frame)
 {
-  if (image->columns != next->columns)
-    return(MagickFalse);
-  if (image->rows != next->rows)
+  if (image->depth != frame->depth)
     return(MagickFalse);
-  if (image->depth != next->depth)
+  if (image->alpha_trait != frame->alpha_trait)
     return(MagickFalse);
-  if (image->alpha_trait != next->alpha_trait)
-    return(MagickFalse);
-  if (image->colorspace != next->colorspace)
+  if (image->colorspace != frame->colorspace)
     return(MagickFalse);
   return(MagickTrue);
 }
@@ -970,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;
@@ -1001,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.
   */
@@ -1066,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)
@@ -1143,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)
@@ -1206,9 +1295,11 @@ static MagickBooleanType WriteJXLImage(c
        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
