Commit 9505e6f88 for imagemagick.org
commit 9505e6f886cbdc3459dccf7c6dcc76c444954f49
Author: Madars <mad182@gmail.com>
Date: Mon Apr 6 16:38:24 2026 +0300
Fix JXL animated export transparent blending and offset frames (#8656)
* Fix JXL animated export transparent blending and offset frames
- Enforced alpha_trait normalization using TransparentAlphaChannel for missing alpha channels to prevent solid white backgrounds when converting sequences containing mixed transparencies.
- Removed strict geometry checks from JXLSameFrameType inside JXL encoder and moved buffer allocation natively into the sequential frame loop to accurately allocate dynamically varying dimensions.
- Populated JxlFrameHeader layer info parameters (have_crop, crop_x0, etc) to appropriately support animation sub-frames.
- Correctly parsed image->previous->dispose property to map ImageMagick's dispose rules straight into JXL's blendmode behaviors (JXL_BLEND_REPLACE vs JXL_BLEND_BLEND) safely on both color and alpha channels.
* Variable name frame makes more sense than next in this context
* Fix white areas in animated JXL exports by using OpaqueAlphaChannel instead of TransparentAlphaChannel
* Rename next variable to frame in JXLSameFrameType
diff --git a/coders/jxl.c b/coders/jxl.c
index a40583713..3126c7f98 100644
--- a/coders/jxl.c
+++ b/coders/jxl.c
@@ -43,6 +43,7 @@
#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"
@@ -932,17 +933,13 @@ static inline float JXLGetDistance(float quality)
}
static inline MagickBooleanType JXLSameFrameType(const Image *image,
- const Image *next)
+ const Image *frame)
{
- if (image->columns != next->columns)
+ if (image->depth != frame->depth)
return(MagickFalse);
- if (image->rows != next->rows)
+ if (image->alpha_trait != frame->alpha_trait)
return(MagickFalse);
- if (image->depth != next->depth)
- 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);
}
@@ -983,7 +980,7 @@ static MagickBooleanType WriteJXLImage(const ImageInfo *image_info,Image *image,
status;
MemoryInfo
- *pixel_info;
+ *pixel_info = (MemoryInfo *) NULL;
MemoryManagerInfo
memory_manager_info;
@@ -1015,6 +1012,39 @@ static MagickBooleanType WriteJXLImage(const ImageInfo *image_info,Image *image,
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.
*/
@@ -1165,31 +1195,65 @@ static MagickBooleanType WriteJXLImage(const ImageInfo *image_info,Image *image,
((pixel_format.data_type == JXL_TYPE_FLOAT) ? sizeof(float) :
(pixel_format.data_type == JXL_TYPE_UINT16) ? sizeof(short) :
sizeof(char));
- if (HeapOverflowSanityCheck(image->columns,channels_size) != MagickFalse)
- {
- JxlThreadParallelRunnerDestroy(runner);
- JxlEncoderDestroy(jxl_info);
- ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
- }
- bytes_per_row=image->columns*channels_size;
- pixel_info=AcquireVirtualMemory(bytes_per_row,image->rows*sizeof(*pixels));
- if (pixel_info == (MemoryInfo *) NULL)
- {
- JxlThreadParallelRunnerDestroy(runner);
- JxlEncoderDestroy(jxl_info);
- ThrowWriterException(CoderError,"MemoryAllocationFailed");
- }
do
{
Image
*next;
+ if (HeapOverflowSanityCheck(image->columns,channels_size) != MagickFalse)
+ {
+ (void) ThrowMagickException(exception,GetMagickModule(),CoderError,
+ "MemoryAllocationFailed","`%s'",image->filename);
+ status=MagickFalse;
+ break;
+ }
+ bytes_per_row=image->columns*channels_size;
+ 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)
@@ -1225,9 +1289,11 @@ static MagickBooleanType WriteJXLImage(const ImageInfo *image_info,Image *image,
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