Coverage Report

Created: 2026-06-30 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/imagemagick/coders/jxl.c
Line
Count
Source
1
/*
2
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3
%                                                                             %
4
%                                                                             %
5
%                                                                             %
6
%                              JJJ  X   X  L                                  %
7
%                               J    X X   L                                  %
8
%                               J     X    L                                  %
9
%                            J  J    X X   L                                  %
10
%                             JJ    X   X  LLLLL                              %
11
%                                                                             %
12
%                                                                             %
13
%                          JPEG XL (ISO/IEC 18181)                            %
14
%                                                                             %
15
%                               Dirk Lemstra                                  %
16
%                               December 2020                                 %
17
%                                                                             %
18
%                                                                             %
19
%  Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization         %
20
%  dedicated to making software imaging solutions freely available.           %
21
%                                                                             %
22
%  You may not use this file except in compliance with the License.  You may  %
23
%  obtain a copy of the License at                                            %
24
%                                                                             %
25
%    https://imagemagick.org/license/                                         %
26
%                                                                             %
27
%  Unless required by applicable law or agreed to in writing, software        %
28
%  distributed under the License is distributed on an "AS IS" BASIS,          %
29
%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
30
%  See the License for the specific language governing permissions and        %
31
%  limitations under the License.                                             %
32
%                                                                             %
33
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
34
%
35
%
36
*/
37
38
/*
39
  Include declarations.
40
*/
41
#include "MagickCore/studio.h"
42
#include "MagickCore/attribute.h"
43
#include "MagickCore/blob.h"
44
#include "MagickCore/blob-private.h"
45
#include "MagickCore/cache.h"
46
#include "MagickCore/channel.h"
47
#include "MagickCore/colorspace-private.h"
48
#include "MagickCore/exception.h"
49
#include "MagickCore/exception-private.h"
50
#include "MagickCore/image.h"
51
#include "MagickCore/image-private.h"
52
#include "MagickCore/layer.h"
53
#include "MagickCore/list.h"
54
#include "MagickCore/magick.h"
55
#include "MagickCore/memory_.h"
56
#include "MagickCore/monitor.h"
57
#include "MagickCore/monitor-private.h"
58
#include "MagickCore/option.h"
59
#include "MagickCore/profile-private.h"
60
#include "MagickCore/property.h"
61
#include "MagickCore/resource_.h"
62
#include "MagickCore/static.h"
63
#include "MagickCore/string_.h"
64
#include "MagickCore/string-private.h"
65
#include "MagickCore/module.h"
66
#if defined(MAGICKCORE_JXL_DELEGATE)
67
#include <jxl/decode.h>
68
#include <jxl/encode.h>
69
#include <jxl/thread_parallel_runner.h>
70
#include <jxl/version.h>
71
#endif
72

73
/*
74
  Typedef declarations.
75
*/
76
typedef struct MemoryManagerInfo
77
{
78
  Image
79
    *image;
80
81
  ExceptionInfo
82
    *exception;
83
} MemoryManagerInfo;
84
85
#if defined(MAGICKCORE_JXL_DELEGATE)
86
/*
87
  Forward declarations.
88
*/
89
static MagickBooleanType
90
  WriteJXLImage(const ImageInfo *,Image *,ExceptionInfo *);
91

92
/*
93
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
94
%                                                                             %
95
%                                                                             %
96
%                                                                             %
97
%   I s J X L                                                                 %
98
%                                                                             %
99
%                                                                             %
100
%                                                                             %
101
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
102
%
103
%  IsJXL() returns MagickTrue if the image format type, identified by the
104
%  magick string, is JXL.
105
%
106
%  The format of the IsJXL  method is:
107
%
108
%      MagickBooleanType IsJXL(const unsigned char *magick,const size_t length)
109
%
110
%  A description of each parameter follows:
111
%
112
%    o magick: compare image format pattern against these bytes.
113
%
114
%    o length: Specifies the length of the magick string.
115
%
116
*/
117
static MagickBooleanType IsJXL(const unsigned char *magick,const size_t length)
118
0
{
119
0
  JxlSignature
120
0
    signature = JxlSignatureCheck(magick,length);
121
122
0
  if ((signature == JXL_SIG_NOT_ENOUGH_BYTES) || (signature == JXL_SIG_INVALID))
123
0
    return(MagickFalse);
124
0
  return(MagickTrue);
125
0
}
126
127
/*
128
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
129
%                                                                             %
130
%                                                                             %
131
%                                                                             %
132
%   R e a d J X L I m a g e                                                   %
133
%                                                                             %
134
%                                                                             %
135
%                                                                             %
136
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
137
%
138
%  ReadJXLImage() reads a JXL image file and returns it.  It allocates
139
%  the memory necessary for the new Image structure and returns a pointer to
140
%  the new image.
141
%
142
%  The format of the ReadJXLImage method is:
143
%
144
%      Image *ReadJXLImage(const ImageInfo *image_info,
145
%        ExceptionInfo *exception)
146
%
147
%  A description of each parameter follows:
148
%
149
%    o image_info: the image info.
150
%
151
%    o exception: return any errors or warnings in this structure.
152
%
153
*/
154
155
static void *JXLAcquireMemory(void *opaque,size_t size)
156
33.7M
{
157
33.7M
  unsigned char
158
33.7M
    *data;
159
160
33.7M
  data=(unsigned char *) AcquireQuantumMemory(size,sizeof(*data));
161
33.7M
  if (data == (unsigned char *) NULL)
162
6
    {
163
6
      MemoryManagerInfo
164
6
        *memory_manager_info;
165
166
6
      memory_manager_info=(MemoryManagerInfo *) opaque;
167
6
      (void) ThrowMagickException(memory_manager_info->exception,
168
6
        GetMagickModule(),CoderError,"MemoryAllocationFailed","`%s'",
169
6
        memory_manager_info->image->filename);
170
6
    }
171
33.7M
  return(data);
172
33.7M
}
173
174
static inline StorageType JXLDataTypeToStorageType(Image *image,
175
  const JxlDataType data_type,ExceptionInfo *exception)
176
6.11k
{
177
6.11k
  switch (data_type)
178
6.11k
  {
179
645
    case JXL_TYPE_FLOAT:
180
645
      return(FloatPixel);
181
0
    case JXL_TYPE_FLOAT16:
182
0
      (void) SetImageProperty(image,"quantum:format","floating-point",
183
0
        exception);
184
0
      return(FloatPixel);
185
2.45k
    case JXL_TYPE_UINT16:
186
2.45k
      return(ShortPixel);
187
3.01k
    case JXL_TYPE_UINT8:
188
3.01k
      return(CharPixel);
189
0
    default:
190
0
      return(UndefinedPixel);
191
6.11k
  }
192
6.11k
}
193
194
static inline JxlOrientation OrientationToJXLOrientation(
195
  const OrientationType orientation)
196
2.86k
{
197
2.86k
  switch (orientation)
198
2.86k
  {
199
40
    default:
200
2.82k
    case TopLeftOrientation:
201
2.82k
      return(JXL_ORIENT_IDENTITY);
202
4
    case TopRightOrientation:
203
4
      return(JXL_ORIENT_FLIP_HORIZONTAL);
204
0
    case BottomRightOrientation:
205
0
      return(JXL_ORIENT_ROTATE_180);
206
0
    case BottomLeftOrientation:
207
0
      return(JXL_ORIENT_FLIP_VERTICAL);
208
1
    case LeftTopOrientation:
209
1
      return(JXL_ORIENT_TRANSPOSE);
210
0
    case RightTopOrientation:
211
0
      return(JXL_ORIENT_ROTATE_90_CW);
212
1
    case RightBottomOrientation:
213
1
      return(JXL_ORIENT_ANTI_TRANSPOSE);
214
30
    case LeftBottomOrientation:
215
30
      return(JXL_ORIENT_ROTATE_90_CCW);
216
2.86k
  }
217
2.86k
}
218
219
static inline OrientationType JXLOrientationToOrientation(
220
  const JxlOrientation orientation)
221
18.2k
{
222
18.2k
  switch (orientation)
223
18.2k
  {
224
0
    default:
225
17.4k
    case JXL_ORIENT_IDENTITY:
226
17.4k
      return(TopLeftOrientation);
227
172
    case JXL_ORIENT_FLIP_HORIZONTAL:
228
172
      return(TopRightOrientation);
229
40
    case JXL_ORIENT_ROTATE_180:
230
40
      return(BottomRightOrientation);
231
67
    case JXL_ORIENT_FLIP_VERTICAL:
232
67
      return(BottomLeftOrientation);
233
75
    case JXL_ORIENT_TRANSPOSE:
234
75
      return(LeftTopOrientation);
235
33
    case JXL_ORIENT_ROTATE_90_CW:
236
33
      return(RightTopOrientation);
237
25
    case JXL_ORIENT_ANTI_TRANSPOSE:
238
25
      return(RightBottomOrientation);
239
366
    case JXL_ORIENT_ROTATE_90_CCW:
240
366
      return(LeftBottomOrientation);
241
18.2k
  }
242
18.2k
}
243
244
static void JXLRelinquishMemory(void *magick_unused(opaque),void *address)
245
33.7M
{
246
33.7M
  magick_unreferenced(opaque);
247
33.7M
  (void) RelinquishMagickMemory(address);
248
33.7M
}
249
250
static inline void JXLSetMemoryManager(JxlMemoryManager *memory_manager,
251
  MemoryManagerInfo *memory_manager_info,Image *image,ExceptionInfo *exception)
252
22.1k
{
253
22.1k
  memory_manager_info->image=image;
254
22.1k
  memory_manager_info->exception=exception;
255
22.1k
  memory_manager->opaque=memory_manager_info;
256
22.1k
  memory_manager->alloc=JXLAcquireMemory;
257
22.1k
  memory_manager->free=JXLRelinquishMemory;
258
22.1k
}
259
260
static inline void JXLSetFormat(Image *image,JxlPixelFormat *pixel_format,
261
  ExceptionInfo *exception)
262
24.0k
{
263
24.0k
  const char
264
24.0k
    *property;
265
266
24.0k
  pixel_format->num_channels=((image->alpha_trait & BlendPixelTrait) != 0) ?
267
19.8k
    4U : 3U;
268
24.0k
  if (IsGrayColorspace(image->colorspace) != MagickFalse)
269
357
    pixel_format->num_channels=((image->alpha_trait & BlendPixelTrait) != 0) ?
270
199
      2U : 1U;
271
24.0k
  pixel_format->data_type=(image->depth > 16) ? JXL_TYPE_FLOAT :
272
24.0k
    (image->depth > 8) ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8;
273
24.0k
  property=GetImageProperty(image,"quantum:format",exception);
274
24.0k
  if (property != (char *) NULL)
275
0
    {
276
0
      QuantumFormatType format = (QuantumFormatType) ParseCommandOption(
277
0
        MagickQuantumFormatOptions,MagickFalse,property);
278
0
      if ((format == FloatingPointQuantumFormat) &&
279
0
          (pixel_format->data_type == JXL_TYPE_UINT16))
280
0
        {
281
0
          pixel_format->data_type=JXL_TYPE_FLOAT16;
282
0
          (void) SetImageProperty(image,"quantum:format","floating-point",
283
0
            exception);
284
0
        }
285
0
    }
286
24.0k
}
287
288
static inline void JXLInitImage(Image *image,JxlBasicInfo *basic_info)
289
18.2k
{
290
18.2k
  image->columns=basic_info->xsize;
291
18.2k
  image->rows=basic_info->ysize;
292
18.2k
  image->depth=basic_info->bits_per_sample;
293
18.2k
  if (basic_info->alpha_bits != 0)
294
2.32k
    image->alpha_trait=BlendPixelTrait;
295
18.2k
  image->orientation=JXLOrientationToOrientation(basic_info->orientation);
296
18.2k
  if (basic_info->have_animation == JXL_TRUE)
297
695
    {
298
695
      if ((basic_info->animation.tps_numerator > 0) &&
299
695
          (basic_info->animation.tps_denominator > 0))
300
695
      image->ticks_per_second=basic_info->animation.tps_numerator /
301
695
        basic_info->animation.tps_denominator;
302
695
      image->iterations=basic_info->animation.num_loops;
303
695
    }
304
18.2k
}
305
306
static inline MagickBooleanType JXLPatchExifProfile(StringInfo *exif_profile)
307
114
{
308
114
  size_t
309
114
    exif_length;
310
311
114
  StringInfo
312
114
    *snippet;
313
314
114
  unsigned char
315
114
    *exif_datum;
316
317
114
  unsigned int
318
114
    offset=0;
319
320
114
  if (GetStringInfoLength(exif_profile) < 4)
321
1
    return(MagickFalse);
322
323
  /*
324
    Extract and cache Exif profile.
325
  */
326
113
  snippet=SplitStringInfo(exif_profile,4);
327
113
  offset|=(unsigned int) (*(GetStringInfoDatum(snippet)+0)) << 24;
328
113
  offset|=(unsigned int) (*(GetStringInfoDatum(snippet)+1)) << 16;
329
113
  offset|=(unsigned int) (*(GetStringInfoDatum(snippet)+2)) << 8;
330
113
  offset|=(unsigned int) (*(GetStringInfoDatum(snippet)+3)) << 0;
331
113
  snippet=DestroyStringInfo(snippet);
332
  /*
333
    Strip any EOI marker if payload starts with a JPEG marker.
334
  */
335
113
  exif_length=GetStringInfoLength(exif_profile);
336
113
  exif_datum=GetStringInfoDatum(exif_profile);
337
113
  if ((exif_length > 2) && 
338
110
      ((memcmp(exif_datum,"\xff\xd8",2) == 0) ||
339
109
        (memcmp(exif_datum,"\xff\xe1",2) == 0)) &&
340
1
      (memcmp(exif_datum+exif_length-2,"\xff\xd9",2) == 0))
341
0
    SetStringInfoLength(exif_profile,exif_length-2);
342
  /*
343
    Skip to actual Exif payload.
344
  */
345
113
  if (offset < GetStringInfoLength(exif_profile))
346
73
    (void) DestroyStringInfo(SplitStringInfo(exif_profile,offset));
347
113
  return(MagickTrue);
348
114
}
349
350
static inline void JXLAddProfilesToImage(Image *image,
351
  StringInfo **exif_profile,StringInfo **xmp_profile,ExceptionInfo *exception)
352
19.7k
{
353
19.7k
  if (*exif_profile != (StringInfo *) NULL)
354
114
    {
355
114
      if (JXLPatchExifProfile(*exif_profile) != MagickFalse)
356
113
        {
357
113
          (void) SetImageProfilePrivate(image,*exif_profile,exception);
358
113
          *exif_profile=(StringInfo *) NULL;
359
113
        }
360
1
      else
361
1
        *exif_profile=DestroyStringInfo(*exif_profile);
362
114
    }
363
19.7k
  if (*xmp_profile != (StringInfo *) NULL)
364
0
    {
365
0
      (void) SetImageProfilePrivate(image,*xmp_profile,exception);
366
0
      *xmp_profile=(StringInfo *) NULL;
367
0
    }
368
19.7k
}
369
370
static Image *ReadJXLImage(const ImageInfo *image_info,
371
  ExceptionInfo *exception)
372
19.2k
{
373
19.2k
  Image
374
19.2k
    *image;
375
376
19.2k
  JxlBasicInfo
377
19.2k
    basic_info;
378
379
19.2k
  JxlDecoder
380
19.2k
    *jxl_info;
381
382
19.2k
  JxlDecoderStatus
383
19.2k
    events_wanted,
384
19.2k
    jxl_status;
385
386
19.2k
  JxlMemoryManager
387
19.2k
    memory_manager;
388
389
19.2k
  JxlPixelFormat
390
19.2k
    pixel_format;
391
392
19.2k
  MagickBooleanType
393
19.2k
    status;
394
395
19.2k
  MemoryManagerInfo
396
19.2k
    memory_manager_info;
397
398
19.2k
  size_t
399
19.2k
    extent = 0,
400
19.2k
    image_count = 0,
401
19.2k
    input_size;
402
403
19.2k
  StringInfo
404
19.2k
    *exif_profile = (StringInfo *) NULL,
405
19.2k
    *xmp_profile = (StringInfo *) NULL;
406
407
19.2k
  unsigned char
408
19.2k
    *pixels,
409
19.2k
    *output_buffer = (unsigned char *) NULL;
410
411
19.2k
  void
412
19.2k
    *runner = NULL;
413
414
  /*
415
    Open image file.
416
  */
417
19.2k
  assert(image_info != (const ImageInfo *) NULL);
418
19.2k
  assert(image_info->signature == MagickCoreSignature);
419
19.2k
  assert(exception != (ExceptionInfo *) NULL);
420
19.2k
  assert(exception->signature == MagickCoreSignature);
421
19.2k
  if (IsEventLogging() != MagickFalse)
422
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
423
0
      image_info->filename);
424
19.2k
  image=AcquireImage(image_info, exception);
425
19.2k
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
426
19.2k
  if (status == MagickFalse)
427
0
    {
428
0
      image=DestroyImageList(image);
429
0
      return((Image *) NULL);
430
0
    }
431
  /*
432
    Initialize JXL delegate library.
433
  */
434
19.2k
  (void) memset(&basic_info,0,sizeof(basic_info));
435
19.2k
  (void) memset(&pixel_format,0,sizeof(pixel_format));
436
19.2k
  JXLSetMemoryManager(&memory_manager,&memory_manager_info,image,exception);
437
19.2k
  jxl_info=JxlDecoderCreate(&memory_manager);
438
19.2k
  if (jxl_info == (JxlDecoder *) NULL)
439
19.2k
    ThrowReaderException(CoderError,"MemoryAllocationFailed");
440
19.2k
  (void) JxlDecoderSetKeepOrientation(jxl_info,JXL_TRUE);
441
19.2k
  (void) JxlDecoderSetUnpremultiplyAlpha(jxl_info,JXL_TRUE);
442
19.2k
  events_wanted=(JxlDecoderStatus) (JXL_DEC_BASIC_INFO | JXL_DEC_BOX |
443
19.2k
    JXL_DEC_FRAME);
444
19.2k
  if (image_info->ping == MagickFalse)
445
17.2k
    {
446
17.2k
      events_wanted=(JxlDecoderStatus) (events_wanted | JXL_DEC_FULL_IMAGE |
447
17.2k
        JXL_DEC_COLOR_ENCODING);
448
17.2k
      runner=JxlThreadParallelRunnerCreate(NULL,(size_t) GetMagickResourceLimit(
449
17.2k
        ThreadResource));
450
17.2k
      if (runner == (void *) NULL)
451
0
        {
452
0
          JxlDecoderDestroy(jxl_info);
453
0
          ThrowReaderException(CoderError,"MemoryAllocationFailed");
454
0
        }
455
17.2k
      jxl_status=JxlDecoderSetParallelRunner(jxl_info,JxlThreadParallelRunner,
456
17.2k
        runner);
457
17.2k
      if (jxl_status != JXL_DEC_SUCCESS)
458
0
        {
459
0
          JxlThreadParallelRunnerDestroy(runner);
460
0
          JxlDecoderDestroy(jxl_info);
461
0
          ThrowReaderException(CoderError,"MemoryAllocationFailed");
462
0
        }
463
17.2k
    }
464
19.2k
  if (JxlDecoderSubscribeEvents(jxl_info,(int) events_wanted) != JXL_DEC_SUCCESS)
465
0
    {
466
0
      if (runner != NULL)
467
0
        JxlThreadParallelRunnerDestroy(runner);
468
0
      JxlDecoderDestroy(jxl_info);
469
0
      ThrowReaderException(CoderError,"UnableToReadImageData");
470
0
    }
471
19.2k
  input_size=MagickMaxBufferExtent;
472
19.2k
  pixels=(unsigned char *) AcquireQuantumMemory(input_size,sizeof(*pixels));
473
19.2k
  if (pixels == (unsigned char *) NULL)
474
0
    {
475
0
      if (runner != NULL)
476
0
        JxlThreadParallelRunnerDestroy(runner);
477
0
      JxlDecoderDestroy(jxl_info);
478
0
      ThrowReaderException(CoderError,"MemoryAllocationFailed");
479
0
    }
480
  /*
481
    Decode JXL byte stream.
482
  */
483
19.2k
  jxl_status=JXL_DEC_NEED_MORE_INPUT;
484
115k
  while ((jxl_status != JXL_DEC_SUCCESS) && (jxl_status != JXL_DEC_ERROR))
485
96.1k
  {
486
96.1k
    jxl_status=JxlDecoderProcessInput(jxl_info);
487
96.1k
    switch (jxl_status)
488
96.1k
    {
489
26.0k
      case JXL_DEC_NEED_MORE_INPUT:
490
26.0k
      {
491
26.0k
        size_t
492
26.0k
          remaining;
493
494
26.0k
        ssize_t
495
26.0k
          count;
496
497
26.0k
        remaining=JxlDecoderReleaseInput(jxl_info);
498
26.0k
        if (remaining > 0)
499
79
          (void) memmove(pixels,pixels+input_size-remaining,remaining);
500
26.0k
        count=ReadBlob(image,input_size-remaining,pixels+remaining);
501
26.0k
        if (count <= 0)
502
6.72k
          {
503
6.72k
            JxlDecoderCloseInput(jxl_info);
504
6.72k
            break;
505
6.72k
          }
506
19.2k
        jxl_status=JxlDecoderSetInput(jxl_info,(const uint8_t *) pixels,
507
19.2k
          (size_t) count);
508
19.2k
        if (jxl_status == JXL_DEC_SUCCESS)
509
19.2k
          jxl_status=JXL_DEC_NEED_MORE_INPUT;
510
19.2k
        break;
511
26.0k
      }
512
17.8k
      case JXL_DEC_BASIC_INFO:
513
17.8k
      {
514
17.8k
        jxl_status=JxlDecoderGetBasicInfo(jxl_info,&basic_info);
515
17.8k
        if (jxl_status != JXL_DEC_SUCCESS)
516
0
          break;
517
17.8k
        if ((basic_info.have_animation == JXL_TRUE) &&
518
243
            (basic_info.animation.have_timecodes == JXL_TRUE))
519
6
          {
520
            /*
521
              We currently don't support animations with time codes.
522
            */
523
6
            (void) ThrowMagickException(exception,GetMagickModule(),
524
6
              MissingDelegateError,"NoDecodeDelegateForThisImageFormat","`%s'",
525
6
              image->filename);
526
6
            break;
527
6
          }
528
17.8k
        JXLInitImage(image,&basic_info);
529
17.8k
        jxl_status=JXL_DEC_BASIC_INFO;
530
17.8k
        status=SetImageExtent(image,image->columns,image->rows,exception);
531
17.8k
        if (status == MagickFalse)
532
141
          {
533
141
            jxl_status=JXL_DEC_ERROR;
534
141
            break;
535
141
          }
536
17.6k
        break;
537
17.8k
      }
538
17.6k
      case JXL_DEC_COLOR_ENCODING:
539
14.3k
      {
540
14.3k
        JxlColorEncoding
541
14.3k
          color_encoding;
542
543
14.3k
        size_t
544
14.3k
          profile_size;
545
546
14.3k
        StringInfo
547
14.3k
          *profile;
548
549
14.3k
        (void) memset(&color_encoding,0,sizeof(color_encoding));
550
14.3k
        JXLSetFormat(image,&pixel_format,exception);
551
14.3k
#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
552
14.3k
        jxl_status=JxlDecoderGetColorAsEncodedProfile(jxl_info,
553
14.3k
          JXL_COLOR_PROFILE_TARGET_DATA,&color_encoding);
554
#else
555
        jxl_status=JxlDecoderGetColorAsEncodedProfile(jxl_info,&pixel_format,
556
          JXL_COLOR_PROFILE_TARGET_DATA,&color_encoding);
557
#endif
558
14.3k
        if (jxl_status == JXL_DEC_SUCCESS)
559
14.3k
          {
560
14.3k
            if (color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_LINEAR)
561
60
              {
562
60
                image->colorspace=RGBColorspace;
563
60
                image->gamma=1.0;
564
60
              }
565
14.3k
            if (color_encoding.color_space == JXL_COLOR_SPACE_GRAY)
566
313
              {
567
313
                image->colorspace=GRAYColorspace;
568
313
                if (color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_LINEAR)
569
40
                  {
570
40
                    image->colorspace=LinearGRAYColorspace;
571
40
                    image->gamma=1.0;
572
40
                  }
573
313
              }
574
14.3k
            if (color_encoding.white_point == JXL_WHITE_POINT_CUSTOM)
575
157
              {
576
157
                image->chromaticity.white_point.x=
577
157
                  color_encoding.white_point_xy[0];
578
157
                image->chromaticity.white_point.y=
579
157
                  color_encoding.white_point_xy[1];
580
157
              }
581
14.3k
            if (color_encoding.primaries == JXL_PRIMARIES_CUSTOM)
582
127
              {
583
127
                image->chromaticity.red_primary.x=
584
127
                  color_encoding.primaries_red_xy[0];
585
127
                image->chromaticity.red_primary.y=
586
127
                  color_encoding.primaries_red_xy[1];
587
127
                image->chromaticity.green_primary.x=
588
127
                  color_encoding.primaries_green_xy[0];
589
127
                image->chromaticity.green_primary.y=
590
127
                  color_encoding.primaries_green_xy[1];
591
127
                image->chromaticity.blue_primary.x=
592
127
                  color_encoding.primaries_blue_xy[0];
593
127
                image->chromaticity.blue_primary.y=
594
127
                  color_encoding.primaries_blue_xy[1];
595
127
              }
596
14.3k
            if (color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_GAMMA)
597
161
              image->gamma=color_encoding.gamma;
598
14.3k
            switch (color_encoding.rendering_intent)
599
14.3k
            {
600
368
              case JXL_RENDERING_INTENT_PERCEPTUAL:
601
368
              {
602
368
                image->rendering_intent=PerceptualIntent;
603
368
                break;
604
0
              }
605
13.9k
              case JXL_RENDERING_INTENT_RELATIVE:
606
13.9k
              {
607
13.9k
                image->rendering_intent=RelativeIntent;
608
13.9k
                break;
609
0
              }
610
8
              case JXL_RENDERING_INTENT_SATURATION: 
611
8
              {
612
8
                image->rendering_intent=SaturationIntent;
613
8
                break;
614
0
              }
615
6
              case JXL_RENDERING_INTENT_ABSOLUTE: 
616
6
              {
617
6
                image->rendering_intent=AbsoluteIntent;
618
6
                break;
619
0
              }
620
0
              default:
621
0
              {
622
0
                image->rendering_intent=UndefinedIntent;
623
0
                break;
624
0
              }
625
14.3k
            }
626
14.3k
          }
627
20
        else
628
20
          if (jxl_status != JXL_DEC_ERROR)
629
0
            break;
630
14.3k
#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
631
14.3k
        jxl_status=JxlDecoderGetICCProfileSize(jxl_info,
632
14.3k
          JXL_COLOR_PROFILE_TARGET_ORIGINAL,&profile_size);
633
#else
634
        jxl_status=JxlDecoderGetICCProfileSize(jxl_info,&pixel_format,
635
          JXL_COLOR_PROFILE_TARGET_ORIGINAL,&profile_size);
636
#endif
637
14.3k
        if (jxl_status != JXL_DEC_SUCCESS)
638
3
          break;
639
14.3k
        profile=AcquireProfileStringInfo("icc",profile_size,exception);
640
14.3k
        if (profile != (StringInfo *) NULL)
641
14.3k
          {
642
14.3k
  #if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
643
14.3k
            jxl_status=JxlDecoderGetColorAsICCProfile(jxl_info,
644
14.3k
              JXL_COLOR_PROFILE_TARGET_ORIGINAL,GetStringInfoDatum(profile),
645
14.3k
              profile_size);
646
#else
647
            jxl_status=JxlDecoderGetColorAsICCProfile(jxl_info,&pixel_format,
648
              JXL_COLOR_PROFILE_TARGET_ORIGINAL,GetStringInfoDatum(profile),
649
              profile_size);
650
#endif
651
14.3k
            if (jxl_status == JXL_DEC_SUCCESS)
652
14.3k
              (void) SetImageProfilePrivate(image,profile,exception);
653
0
            else
654
0
              profile=DestroyStringInfo(profile);
655
14.3k
          }
656
14.3k
        if (jxl_status == JXL_DEC_SUCCESS)
657
14.3k
          jxl_status=JXL_DEC_COLOR_ENCODING;
658
14.3k
        break;
659
14.3k
      }
660
7.04k
      case JXL_DEC_FRAME:
661
7.04k
      {
662
7.04k
        JxlFrameHeader
663
7.04k
          frame_header;
664
665
7.04k
        if (image_count++ != 0)
666
458
          {
667
458
            JXLAddProfilesToImage(image,&exif_profile,&xmp_profile,exception);
668
            /*
669
              Allocate next image structure.
670
            */
671
458
            AcquireNextImage(image_info,image,exception);
672
458
            if (GetNextImageInList(image) == (Image *) NULL)
673
0
              break;
674
458
            image=SyncNextImageInList(image);
675
458
            JXLInitImage(image,&basic_info);
676
458
            status=SetImageExtent(image,image->columns,image->rows,exception);
677
458
            if (status == MagickFalse)
678
4
              {
679
4
                jxl_status=JXL_DEC_ERROR;
680
4
                break;
681
4
              }
682
458
          }
683
7.04k
        (void) memset(&frame_header,0,sizeof(frame_header));
684
7.04k
        if (JxlDecoderGetFrameHeader(jxl_info,&frame_header) == JXL_DEC_SUCCESS)
685
7.04k
          image->delay=(size_t) frame_header.duration;
686
7.04k
        if ((basic_info.have_animation == JXL_TRUE) &&
687
604
            (basic_info.alpha_bits != 0))
688
107
          image->dispose=BackgroundDispose;
689
7.04k
        break;
690
7.04k
      }
691
6.83k
      case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
692
6.83k
      {
693
6.83k
        status=SetImageExtent(image,image->columns,image->rows,exception);
694
6.83k
        if (status == MagickFalse)
695
0
          {
696
0
            jxl_status=JXL_DEC_ERROR;
697
0
            break;
698
0
          }
699
6.83k
        (void) ResetImagePixels(image,exception);
700
6.83k
        JXLSetFormat(image,&pixel_format,exception);
701
6.83k
        if (extent == 0)
702
6.53k
          {
703
6.53k
            jxl_status=JxlDecoderImageOutBufferSize(jxl_info,&pixel_format,
704
6.53k
              &extent);
705
6.53k
            if (jxl_status != JXL_DEC_SUCCESS)
706
0
              break;
707
6.53k
            output_buffer=(unsigned char *) AcquireQuantumMemory(extent,
708
6.53k
              sizeof(*output_buffer));
709
6.53k
            if (output_buffer == (unsigned char *) NULL)
710
0
              {
711
0
                (void) ThrowMagickException(exception,GetMagickModule(),
712
0
                  CoderError,"MemoryAllocationFailed","`%s'",image->filename);
713
0
                break;
714
0
              }
715
6.53k
          }
716
6.83k
        jxl_status=JxlDecoderSetImageOutBuffer(jxl_info,&pixel_format,
717
6.83k
          output_buffer,extent);
718
6.83k
        if (jxl_status == JXL_DEC_SUCCESS)
719
6.83k
          jxl_status=JXL_DEC_NEED_IMAGE_OUT_BUFFER;
720
6.83k
        break;
721
6.83k
      }
722
3.29k
      case JXL_DEC_FULL_IMAGE:
723
3.29k
      {
724
3.29k
        const char
725
3.29k
          *map = "RGB";
726
727
3.29k
        StorageType
728
3.29k
          type;
729
730
3.29k
        if (output_buffer == (unsigned char *) NULL)
731
0
          {
732
0
            (void) ThrowMagickException(exception,GetMagickModule(),
733
0
              CorruptImageError,"UnableToReadImageData","`%s'",image->filename);
734
0
            break;
735
0
          }
736
3.29k
        type=JXLDataTypeToStorageType(image,pixel_format.data_type,exception);
737
3.29k
        if (type == UndefinedPixel)
738
0
          {
739
0
            (void) ThrowMagickException(exception,GetMagickModule(),
740
0
              CorruptImageError,"Unsupported data type","`%s'",image->filename);
741
0
            break;
742
0
          }
743
3.29k
        if ((image->alpha_trait & BlendPixelTrait) != 0)
744
1.09k
          map="RGBA";
745
3.29k
        if (IsGrayColorspace(image->colorspace) != MagickFalse)
746
158
          {
747
158
            map="I";
748
158
            if ((image->alpha_trait & BlendPixelTrait) != 0)
749
98
              map="IA";
750
158
          }
751
3.29k
        status=ImportImagePixels(image,0,0,image->columns,image->rows,map,
752
3.29k
          type,output_buffer,exception);
753
3.29k
        if (status == MagickFalse)
754
0
          jxl_status=JXL_DEC_ERROR;
755
3.29k
        break;
756
3.29k
      }
757
1.70k
      case JXL_DEC_BOX:
758
1.70k
      {
759
1.70k
        JxlBoxType
760
1.70k
          type;
761
762
1.70k
        uint64_t
763
1.70k
          size;
764
765
1.70k
        (void) JxlDecoderReleaseBoxBuffer(jxl_info);
766
1.70k
        jxl_status=JxlDecoderGetBoxType(jxl_info,type,JXL_FALSE);
767
1.70k
        if (jxl_status != JXL_DEC_SUCCESS)
768
0
          break;
769
1.70k
        jxl_status=JxlDecoderGetBoxSizeRaw(jxl_info,&size);
770
1.70k
        if ((jxl_status != JXL_DEC_SUCCESS) || (size <= 8))
771
85
          break;
772
1.61k
        size-=8;
773
1.61k
        if (LocaleNCompare(type,"Exif",sizeof(type)) == 0)
774
131
          {
775
            /*
776
              Read Exif profile.
777
            */
778
131
          if (exif_profile == (StringInfo *) NULL)
779
122
            {
780
122
              exif_profile=AcquireProfileStringInfo("exif",(size_t) size,
781
122
                exception);
782
122
              if (exif_profile != (StringInfo *) NULL)
783
114
                jxl_status=JxlDecoderSetBoxBuffer(jxl_info,
784
114
                  GetStringInfoDatum(exif_profile),(size_t) size);
785
122
            }
786
131
          }
787
1.61k
        if (LocaleNCompare(type,"xml ",sizeof(type)) == 0)
788
0
          {
789
            /*
790
              Read XMP profile.
791
            */
792
0
            if (xmp_profile == (StringInfo *) NULL)
793
0
              {
794
0
                xmp_profile=AcquireProfileStringInfo("xmp",(size_t) size,
795
0
                  exception);
796
0
                if (xmp_profile != (StringInfo *) NULL)
797
0
                  jxl_status=JxlDecoderSetBoxBuffer(jxl_info,
798
0
                    GetStringInfoDatum(xmp_profile),(size_t) size);
799
0
              }
800
0
          }
801
1.61k
        if (jxl_status == JXL_DEC_SUCCESS)
802
1.61k
          jxl_status=JXL_DEC_BOX;
803
1.61k
        break;
804
1.70k
      }
805
2.97k
      case JXL_DEC_SUCCESS:
806
19.0k
      case JXL_DEC_ERROR:
807
19.0k
        break;
808
0
      default:
809
0
      {
810
0
        (void) ThrowMagickException(exception,GetMagickModule(),
811
0
          CorruptImageError,"Unsupported status type","`%d'",jxl_status);
812
0
        jxl_status=JXL_DEC_ERROR;
813
0
        break;
814
2.97k
      }
815
96.1k
    }
816
96.1k
  }
817
19.2k
  (void) JxlDecoderReleaseBoxBuffer(jxl_info);
818
19.2k
  JXLAddProfilesToImage(image,&exif_profile,&xmp_profile,exception);
819
19.2k
  output_buffer=(unsigned char *) RelinquishMagickMemory(output_buffer);
820
19.2k
  pixels=(unsigned char *) RelinquishMagickMemory(pixels);
821
19.2k
  if (runner != NULL)
822
17.2k
    JxlThreadParallelRunnerDestroy(runner);
823
19.2k
  JxlDecoderDestroy(jxl_info);
824
19.2k
  if (jxl_status == JXL_DEC_ERROR)
825
16.2k
    ThrowReaderException(CorruptImageError,"UnableToReadImageData");
826
3.06k
  if (CloseBlob(image) == MagickFalse)
827
0
    status=MagickFalse;
828
3.06k
  if (status == MagickFalse)
829
0
    return(DestroyImageList(image));
830
3.06k
  return(GetFirstImageInList(image));
831
3.06k
}
832
#endif
833
834
/*
835
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
836
%                                                                             %
837
%                                                                             %
838
%                                                                             %
839
%   R e g i s t e r J X L I m a g e                                           %
840
%                                                                             %
841
%                                                                             %
842
%                                                                             %
843
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
844
%
845
%  RegisterJXLImage() adds properties for the JXL image format to
846
%  the list of supported formats.  The properties include the image format
847
%  tag, a method to read and/or write the format, whether the format
848
%  supports the saving of more than one frame to the same file or blob,
849
%  whether the format supports native in-memory I/O, and a brief
850
%  description of the format.
851
%
852
%  The format of the RegisterJXLImage method is:
853
%
854
%      size_t RegisterJXLImage(void)
855
%
856
*/
857
ModuleExport size_t RegisterJXLImage(void)
858
10
{
859
10
  char
860
10
    version[MagickPathExtent];
861
862
10
  MagickInfo
863
10
    *entry;
864
865
10
  *version='\0';
866
10
#if defined(MAGICKCORE_JXL_DELEGATE)
867
10
  (void) FormatLocaleString(version,MagickPathExtent,"libjxl %u.%u.%u",
868
10
    (JxlDecoderVersion()/1000000),(JxlDecoderVersion()/1000) % 1000,
869
10
    (JxlDecoderVersion() % 1000));
870
10
#endif
871
10
  entry=AcquireMagickInfo("JXL", "JXL", "JPEG XL (ISO/IEC 18181)");
872
10
#if defined(MAGICKCORE_JXL_DELEGATE)
873
10
  entry->decoder=(DecodeImageHandler *) ReadJXLImage;
874
10
  entry->encoder=(EncodeImageHandler *) WriteJXLImage;
875
10
  entry->magick=(IsImageFormatHandler *) IsJXL;
876
10
#endif
877
10
  entry->mime_type=ConstantString("image/jxl");
878
10
  if (*version != '\0')
879
10
    entry->version=ConstantString(version);
880
10
  (void) RegisterMagickInfo(entry);
881
10
  return(MagickImageCoderSignature);
882
10
}
883
884
/*
885
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
886
%                                                                             %
887
%                                                                             %
888
%                                                                             %
889
%   U n r e g i s t e r J X L I m a g e                                       %
890
%                                                                             %
891
%                                                                             %
892
%                                                                             %
893
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
894
%
895
%  UnregisterJXLImage() removes format registrations made by the
896
%  JXL module from the list of supported formats.
897
%
898
%  The format of the UnregisterJXLImage method is:
899
%
900
%      UnregisterJXLImage(void)
901
%
902
*/
903
ModuleExport void UnregisterJXLImage(void)
904
0
{
905
0
  (void) UnregisterMagickInfo("JXL");
906
0
}
907
908
#if defined(MAGICKCORE_JXL_DELEGATE)
909
/*
910
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
911
%                                                                             %
912
%                                                                             %
913
%                                                                             %
914
%  W r i t e J X L I m a g e                                                  %
915
%                                                                             %
916
%                                                                             %
917
%                                                                             %
918
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
919
%
920
%  WriteJXLImage() writes a JXL image file and returns it.  It
921
%  allocates the memory necessary for the new Image structure and returns a
922
%  pointer to the new image.
923
%
924
%  The format of the WriteJXLImage method is:
925
%
926
%      MagickBooleanType WriteJXLImage(const ImageInfo *image_info,
927
%        Image *image)
928
%
929
%  A description of each parameter follows:
930
%
931
%    o image_info: the image info.
932
%
933
%    o image:  The image.
934
%
935
*/
936
937
static JxlEncoderStatus JXLWriteMetadata(const Image *image,
938
  JxlEncoder *jxl_info, const StringInfo *icc_profile)
939
2.82k
{
940
2.82k
  JxlColorEncoding
941
2.82k
    color_encoding;
942
943
2.82k
  JxlEncoderStatus
944
2.82k
    jxl_status;
945
946
2.82k
  if (icc_profile != (StringInfo *) NULL)
947
0
    {
948
0
      jxl_status=JxlEncoderSetICCProfile(jxl_info,(const uint8_t *)
949
0
        GetStringInfoDatum(icc_profile),GetStringInfoLength(icc_profile));
950
0
      return(jxl_status);
951
0
    }
952
2.82k
  (void) memset(&color_encoding,0,sizeof(color_encoding));
953
2.82k
  color_encoding.color_space=JXL_COLOR_SPACE_RGB;
954
2.82k
  if (IsRGBColorspace(image->colorspace) == MagickFalse)
955
2.81k
    JxlColorEncodingSetToSRGB(&color_encoding,
956
2.81k
      IsGrayColorspace(image->colorspace) != MagickFalse);
957
9
  else
958
9
    JxlColorEncodingSetToLinearSRGB(&color_encoding,
959
9
      IsGrayColorspace(image->colorspace) != MagickFalse);
960
2.82k
  jxl_status=JxlEncoderSetColorEncoding(jxl_info,&color_encoding);
961
2.82k
  return(jxl_status);
962
2.82k
}
963
964
static inline float JXLGetDistance(float quality)
965
0
{
966
0
  return quality >= 100.0f ? 0.0f
967
0
         : quality >= 30
968
0
             ? 0.1f + (100 - quality) * 0.09f
969
0
             : 53.0f / 3000.0f * quality * quality - 23.0f / 20.0f * quality + 25.0f;
970
0
}
971
972
static inline MagickBooleanType JXLSameFrameType(const Image *image,
973
  const Image *frame)
974
0
{
975
0
  if (image->depth != frame->depth)
976
0
    return(MagickFalse);
977
0
  if (image->alpha_trait != frame->alpha_trait)
978
0
    return(MagickFalse);
979
0
  if (image->colorspace != frame->colorspace)
980
0
    return(MagickFalse);
981
0
  return(MagickTrue);
982
0
}
983
984
static MagickBooleanType WriteJXLImage(const ImageInfo *image_info,Image *image,
985
  ExceptionInfo *exception)
986
2.86k
{
987
2.86k
  const char
988
2.86k
    *option;
989
990
2.86k
  const StringInfo
991
2.86k
    *icc_profile = (StringInfo *) NULL,
992
2.86k
    *exif_profile = (StringInfo *) NULL,
993
2.86k
    *xmp_profile = (StringInfo *) NULL;
994
995
2.86k
  JxlBasicInfo
996
2.86k
    basic_info;
997
998
2.86k
  JxlEncoder
999
2.86k
    *jxl_info;
1000
1001
2.86k
  JxlEncoderFrameSettings
1002
2.86k
    *frame_settings;
1003
1004
2.86k
  JxlEncoderStatus
1005
2.86k
    jxl_status;
1006
1007
2.86k
  JxlFrameHeader
1008
2.86k
    frame_header;
1009
1010
2.86k
  JxlMemoryManager
1011
2.86k
    memory_manager;
1012
1013
2.86k
  JxlPixelFormat
1014
2.86k
    pixel_format;
1015
1016
2.86k
  MagickBooleanType
1017
2.86k
    status;
1018
1019
2.86k
  MemoryInfo
1020
2.86k
    *pixel_info = (MemoryInfo *) NULL;
1021
1022
2.86k
  MemoryManagerInfo
1023
2.86k
    memory_manager_info;
1024
1025
2.86k
  size_t
1026
2.86k
    channels_size,
1027
2.86k
    sample_size;
1028
1029
2.86k
  unsigned char
1030
2.86k
    *pixels;
1031
1032
2.86k
  void
1033
2.86k
    *runner;
1034
1035
  /*
1036
    Open output image file.
1037
  */
1038
2.86k
  assert(image_info != (const ImageInfo *) NULL);
1039
2.86k
  assert(image_info->signature == MagickCoreSignature);
1040
2.86k
  assert(image != (Image *) NULL);
1041
2.86k
  assert(image->signature == MagickCoreSignature);
1042
2.86k
  assert(exception != (ExceptionInfo *) NULL);
1043
2.86k
  assert(exception->signature == MagickCoreSignature);
1044
2.86k
  if (IsEventLogging() != MagickFalse)
1045
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1046
2.86k
  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
1047
2.86k
  if (status == MagickFalse)
1048
0
    return(status);
1049
2.86k
  if ((IssRGBCompatibleColorspace(image->colorspace) == MagickFalse) &&
1050
0
      (IsCMYKColorspace(image->colorspace) == MagickFalse))
1051
0
    (void) TransformImageColorspace(image,sRGBColorspace,exception);
1052
2.86k
  if ((image_info->adjoin != MagickFalse) &&
1053
2.86k
      (GetNextImageInList(image) != (Image *) NULL))
1054
0
    {
1055
0
      Image
1056
0
        *frame;
1057
1058
0
      MagickBooleanType
1059
0
        has_alpha;
1060
1061
0
      size_t
1062
0
        depth;
1063
1064
0
      depth=image->depth;
1065
0
      has_alpha=MagickFalse;
1066
0
      for (frame=image; frame != (Image *) NULL; frame=GetNextImageInList(frame))
1067
0
      {
1068
0
        if ((frame->alpha_trait & BlendPixelTrait) != 0)
1069
0
          has_alpha=MagickTrue;
1070
0
        if (frame->depth > depth)
1071
0
          depth=frame->depth;
1072
0
      }
1073
0
      for (frame=image; frame != (Image *) NULL; frame=GetNextImageInList(frame))
1074
0
      {
1075
0
        frame->depth=depth;
1076
0
        if (has_alpha != MagickFalse)
1077
0
          {
1078
0
            if ((frame->alpha_trait & BlendPixelTrait) == 0)
1079
0
              (void) SetImageAlphaChannel(frame,OpaqueAlphaChannel,exception);
1080
0
          }
1081
0
        if (frame->colorspace != image->colorspace)
1082
0
          (void) TransformImageColorspace(frame,image->colorspace,exception);
1083
0
      }
1084
0
    }
1085
  /*
1086
    Initialize JXL delegate library.
1087
  */
1088
2.86k
  (void) memset(&basic_info,0,sizeof(basic_info));
1089
2.86k
  (void) memset(&frame_header,0,sizeof(frame_header));
1090
2.86k
  (void) memset(&pixel_format,0,sizeof(pixel_format));
1091
2.86k
  JXLSetMemoryManager(&memory_manager,&memory_manager_info,image,exception);
1092
2.86k
  jxl_info=JxlEncoderCreate(&memory_manager);
1093
2.86k
  if (jxl_info == (JxlEncoder *) NULL)
1094
2.86k
    ThrowWriterException(CoderError,"MemoryAllocationFailed");
1095
2.86k
  runner=JxlThreadParallelRunnerCreate(NULL,(size_t) GetMagickResourceLimit(
1096
2.86k
    ThreadResource));
1097
2.86k
  if (runner == (void *) NULL)
1098
0
    {
1099
0
      JxlEncoderDestroy(jxl_info);
1100
0
      ThrowWriterException(CoderError,"MemoryAllocationFailed");
1101
0
    }
1102
2.86k
  jxl_status=JxlEncoderSetParallelRunner(jxl_info,JxlThreadParallelRunner,
1103
2.86k
    runner);
1104
2.86k
  if (jxl_status != JXL_ENC_SUCCESS)
1105
0
    {
1106
0
      JxlThreadParallelRunnerDestroy(runner);
1107
0
      JxlEncoderDestroy(jxl_info);
1108
0
      return(MagickFalse);
1109
0
    }
1110
2.86k
  JXLSetFormat(image,&pixel_format,exception);
1111
2.86k
  JxlEncoderInitBasicInfo(&basic_info);
1112
2.86k
  basic_info.xsize=(uint32_t) image->columns;
1113
2.86k
  basic_info.ysize=(uint32_t) image->rows;
1114
2.86k
  basic_info.bits_per_sample=8;
1115
2.86k
  if (pixel_format.data_type == JXL_TYPE_UINT16)
1116
1.09k
    basic_info.bits_per_sample=16;
1117
1.77k
  else
1118
1.77k
    if (pixel_format.data_type == JXL_TYPE_FLOAT)
1119
318
      {
1120
318
        basic_info.bits_per_sample=32;
1121
318
        basic_info.exponent_bits_per_sample=8;
1122
318
      }
1123
1.45k
    else
1124
1.45k
      if (pixel_format.data_type == JXL_TYPE_FLOAT16)
1125
0
        {
1126
0
          basic_info.bits_per_sample=16;
1127
0
          basic_info.exponent_bits_per_sample=8;
1128
0
        }
1129
2.86k
  if (IsGrayColorspace(image->colorspace) != MagickFalse)
1130
177
    basic_info.num_color_channels=1;
1131
2.86k
  if ((image->alpha_trait & BlendPixelTrait) != 0)
1132
931
    {
1133
931
      basic_info.alpha_bits=basic_info.bits_per_sample;
1134
931
      basic_info.alpha_exponent_bits=basic_info.exponent_bits_per_sample;
1135
931
      basic_info.num_extra_channels=1;
1136
931
    }
1137
2.86k
  if (image_info->quality == 100)
1138
0
    {
1139
0
      basic_info.uses_original_profile=JXL_TRUE;
1140
0
      icc_profile=GetImageProfile(image,"icc");
1141
0
    }
1142
2.86k
  if ((image_info->adjoin != MagickFalse) &&
1143
2.86k
      (GetNextImageInList(image) != (Image *) NULL))
1144
0
    {
1145
0
      basic_info.have_animation=JXL_TRUE;
1146
0
      basic_info.animation.num_loops=(uint32_t) image->iterations;
1147
0
      basic_info.animation.tps_numerator=(uint32_t) image->ticks_per_second;
1148
0
      basic_info.animation.tps_denominator=1;
1149
0
      JxlEncoderInitFrameHeader(&frame_header);
1150
0
    }
1151
2.86k
  basic_info.orientation=OrientationToJXLOrientation(image->orientation);
1152
2.86k
  jxl_status=JxlEncoderSetBasicInfo(jxl_info,&basic_info);
1153
2.86k
  if (jxl_status != JXL_ENC_SUCCESS)
1154
39
    {
1155
39
      JxlThreadParallelRunnerDestroy(runner);
1156
39
      JxlEncoderDestroy(jxl_info);
1157
39
      ThrowWriterException(CoderError,"UnableToWriteImageData");
1158
0
    }
1159
2.82k
  frame_settings=JxlEncoderFrameSettingsCreate(jxl_info,
1160
2.82k
    (JxlEncoderFrameSettings *) NULL);
1161
2.82k
  if (frame_settings == (JxlEncoderFrameSettings *) NULL)
1162
0
    {
1163
0
      JxlThreadParallelRunnerDestroy(runner);
1164
0
      JxlEncoderDestroy(jxl_info);
1165
0
      ThrowWriterException(CoderError,"MemoryAllocationFailed");
1166
0
    }
1167
2.82k
  if (image_info->quality == 100)
1168
0
    {
1169
0
      (void) JxlEncoderSetFrameDistance(frame_settings,0.f);
1170
0
      (void) JxlEncoderSetFrameLossless(frame_settings,JXL_TRUE);
1171
0
    }
1172
2.82k
  else if (image_info->quality != 0)
1173
0
    (void) JxlEncoderSetFrameDistance(frame_settings,
1174
0
      JXLGetDistance((float) image_info->quality));
1175
2.82k
  option=GetImageOption(image_info,"jxl:effort");
1176
2.82k
  if (option != (const char *) NULL)
1177
0
    (void) JxlEncoderFrameSettingsSetOption(frame_settings,
1178
0
      JXL_ENC_FRAME_SETTING_EFFORT,StringToInteger(option));
1179
2.82k
  option=GetImageOption(image_info,"jxl:decoding-speed");
1180
2.82k
  if (option != (const char *) NULL)
1181
0
    (void) JxlEncoderFrameSettingsSetOption(frame_settings,
1182
0
      JXL_ENC_FRAME_SETTING_DECODING_SPEED,StringToInteger(option));
1183
2.82k
  exif_profile=GetImageProfile(image,"exif");
1184
2.82k
  xmp_profile=GetImageProfile(image,"xmp");
1185
2.82k
  if ((exif_profile != (StringInfo *) NULL) ||
1186
2.78k
      (xmp_profile != (StringInfo *) NULL))
1187
41
    {
1188
41
      (void) JxlEncoderUseBoxes(jxl_info);
1189
41
      if ((exif_profile != (StringInfo *) NULL) &&
1190
41
          (GetStringInfoLength(exif_profile) > 6))
1191
41
        {
1192
          /*
1193
            Add Exif profile.  Assumes "Exif\0\0" JPEG APP1 prefix.
1194
          */
1195
41
          StringInfo
1196
41
            *profile;
1197
1198
41
          profile=BlobToStringInfo("\0\0\0\6",4);
1199
41
          if (profile != (StringInfo *) NULL)
1200
41
            {
1201
41
              ConcatenateStringInfo(profile,exif_profile);
1202
41
              (void) JxlEncoderAddBox(jxl_info,"Exif",
1203
41
                GetStringInfoDatum(profile),GetStringInfoLength(profile),0);
1204
41
              profile=DestroyStringInfo(profile);
1205
41
            }
1206
41
        }
1207
41
      if (xmp_profile != (StringInfo *) NULL)
1208
0
        {
1209
          /*
1210
            Add XMP profile.
1211
          */
1212
0
          (void) JxlEncoderAddBox(jxl_info,"xml ",GetStringInfoDatum(
1213
0
            xmp_profile),GetStringInfoLength(xmp_profile),0);
1214
0
        }
1215
41
      (void) JxlEncoderCloseBoxes(jxl_info);
1216
41
    }
1217
2.82k
  jxl_status=JXLWriteMetadata(image,jxl_info,icc_profile);
1218
2.82k
  if (jxl_status != JXL_ENC_SUCCESS)
1219
0
    {
1220
0
      JxlThreadParallelRunnerDestroy(runner);
1221
0
      JxlEncoderDestroy(jxl_info);
1222
0
      ThrowWriterException(CoderError,"UnableToWriteImageData");
1223
0
    }
1224
  /*
1225
    Write image as a JXL stream.
1226
  */
1227
2.82k
  sample_size=sizeof(char);
1228
2.82k
  if ((pixel_format.data_type == JXL_TYPE_FLOAT) ||
1229
2.50k
      (pixel_format.data_type == JXL_TYPE_FLOAT16))
1230
318
    sample_size=sizeof(float);
1231
2.50k
  else
1232
2.50k
    if (pixel_format.data_type == JXL_TYPE_UINT16)
1233
1.05k
      sample_size=sizeof(short);
1234
2.82k
  if (IsGrayColorspace(image->colorspace) != MagickFalse)
1235
177
    channels_size=(((image->alpha_trait & BlendPixelTrait) != 0) ? 2U : 1U)*
1236
177
      sample_size;
1237
2.64k
  else
1238
2.64k
    channels_size=(((image->alpha_trait & BlendPixelTrait) != 0) ? 4U : 3U)*
1239
2.64k
      sample_size;
1240
2.82k
  do
1241
2.82k
  {
1242
2.82k
    Image
1243
2.82k
      *next;
1244
1245
2.82k
    size_t
1246
2.82k
      bytes_per_row;
1247
1248
2.82k
    if (HeapOverflowSanityCheckGetSize(image->columns,channels_size,&bytes_per_row) != MagickFalse)
1249
0
      {
1250
0
        (void) ThrowMagickException(exception,GetMagickModule(),CoderError,
1251
0
          "MemoryAllocationFailed","`%s'",image->filename);
1252
0
        status=MagickFalse;
1253
0
        break;
1254
0
      }
1255
2.82k
    pixel_info=AcquireVirtualMemory(bytes_per_row,image->rows*sizeof(*pixels));
1256
2.82k
    if (pixel_info == (MemoryInfo *) NULL)
1257
0
      {
1258
0
        (void) ThrowMagickException(exception,GetMagickModule(),CoderError,
1259
0
          "MemoryAllocationFailed","`%s'",image->filename);
1260
0
        status=MagickFalse;
1261
0
        break;
1262
0
      }
1263
1264
2.82k
    if (basic_info.have_animation == JXL_TRUE)
1265
0
      {
1266
0
        JxlBlendInfo
1267
0
          alpha_blend_info;
1268
1269
0
        frame_header.duration=(uint32_t) image->delay;
1270
0
        if ((image->previous == (Image *) NULL) ||
1271
0
            (image->previous->dispose == BackgroundDispose) ||
1272
0
            (image->previous->dispose == PreviousDispose))
1273
0
          {
1274
0
            frame_header.layer_info.blend_info.blendmode=JXL_BLEND_REPLACE;
1275
0
            frame_header.layer_info.blend_info.source=0;
1276
0
          }
1277
0
        else
1278
0
          {
1279
0
            frame_header.layer_info.blend_info.blendmode=JXL_BLEND_BLEND;
1280
0
            frame_header.layer_info.blend_info.source=1;
1281
0
          }
1282
0
        frame_header.layer_info.save_as_reference=1;
1283
0
        if ((image->page.width != 0) && (image->page.height != 0))
1284
0
          {
1285
0
            frame_header.layer_info.have_crop=JXL_TRUE;
1286
0
            frame_header.layer_info.crop_x0=(int32_t) image->page.x;
1287
0
            frame_header.layer_info.crop_y0=(int32_t) image->page.y;
1288
0
            frame_header.layer_info.xsize=(uint32_t) image->columns;
1289
0
            frame_header.layer_info.ysize=(uint32_t) image->rows;
1290
0
          }
1291
0
        jxl_status=JxlEncoderSetFrameHeader(frame_settings,&frame_header);
1292
0
        if (jxl_status != JXL_ENC_SUCCESS)
1293
0
          break;
1294
0
        if (basic_info.num_extra_channels > 0)
1295
0
          {
1296
0
            JxlEncoderInitBlendInfo(&alpha_blend_info);
1297
0
            alpha_blend_info.blendmode=frame_header.layer_info.blend_info.blendmode;
1298
0
            alpha_blend_info.source=frame_header.layer_info.blend_info.source;
1299
0
            (void) JxlEncoderSetExtraChannelBlendInfo(frame_settings,0,&alpha_blend_info);
1300
0
          }
1301
0
      }
1302
2.82k
    pixels=(unsigned char *) GetVirtualMemoryBlob(pixel_info);
1303
2.82k
    if (IsGrayColorspace(image->colorspace) != MagickFalse)
1304
177
      status=ExportImagePixels(image,0,0,image->columns,image->rows,
1305
177
        ((image->alpha_trait & BlendPixelTrait) != 0) ? "IA" : "I",
1306
177
        JXLDataTypeToStorageType(image,pixel_format.data_type,exception),
1307
177
        pixels,exception);
1308
2.64k
    else
1309
2.64k
      status=ExportImagePixels(image,0,0,image->columns,image->rows,
1310
2.64k
        ((image->alpha_trait & BlendPixelTrait) != 0) ? "RGBA" : "RGB",
1311
2.64k
        JXLDataTypeToStorageType(image,pixel_format.data_type,exception),
1312
2.64k
        pixels,exception);
1313
2.82k
    if (status == MagickFalse)
1314
0
      {
1315
0
        (void) ThrowMagickException(exception,GetMagickModule(),CoderError,
1316
0
          "MemoryAllocationFailed","`%s'",image->filename);
1317
0
        status=MagickFalse;
1318
0
        break;
1319
0
      }
1320
2.82k
    if (jxl_status != JXL_ENC_SUCCESS)
1321
0
      break;
1322
2.82k
    jxl_status=JxlEncoderAddImageFrame(frame_settings,&pixel_format,pixels,
1323
2.82k
      bytes_per_row*image->rows);
1324
2.82k
    if (jxl_status != JXL_ENC_SUCCESS)
1325
0
      break;
1326
2.82k
    next=GetNextImageInList(image);
1327
2.82k
    if (next == (Image*) NULL)
1328
2.82k
      break;
1329
0
    if (JXLSameFrameType(image,next) == MagickFalse)
1330
0
      {
1331
0
       (void) ThrowMagickException(exception,GetMagickModule(),ImageError,
1332
0
         "FramesNotSameDimensions","`%s'",image->filename);
1333
0
       status=MagickFalse;
1334
0
       break;
1335
0
      }
1336
0
    pixel_info=RelinquishVirtualMemory(pixel_info);
1337
0
    image=SyncNextImageInList(image);
1338
0
  } while (image_info->adjoin != MagickFalse);
1339
2.82k
  if (pixel_info != (MemoryInfo *) NULL)
1340
2.82k
    pixel_info=RelinquishVirtualMemory(pixel_info);
1341
2.82k
  if (jxl_status == JXL_ENC_SUCCESS)
1342
2.82k
    {
1343
2.82k
      unsigned char
1344
2.82k
        *output_buffer;
1345
1346
2.82k
      JxlEncoderCloseInput(jxl_info);
1347
2.82k
      output_buffer=(unsigned char *) AcquireQuantumMemory(
1348
2.82k
        MagickMaxBufferExtent,sizeof(*output_buffer));
1349
2.82k
      if (output_buffer == (unsigned char *) NULL)
1350
0
        {
1351
0
          JxlThreadParallelRunnerDestroy(runner);
1352
0
          JxlEncoderDestroy(jxl_info);
1353
0
          ThrowWriterException(CoderError,"MemoryAllocationFailed");
1354
0
        }
1355
2.82k
      jxl_status=JXL_ENC_NEED_MORE_OUTPUT;
1356
5.69k
      while (jxl_status == JXL_ENC_NEED_MORE_OUTPUT)
1357
2.87k
      {
1358
2.87k
        size_t
1359
2.87k
          extent;
1360
1361
2.87k
        ssize_t
1362
2.87k
          count;
1363
1364
2.87k
        unsigned char
1365
2.87k
          *p;
1366
1367
        /*
1368
          Encode the pixel stream.
1369
        */
1370
2.87k
        extent=MagickMaxBufferExtent;
1371
2.87k
        p=output_buffer;
1372
2.87k
        jxl_status=JxlEncoderProcessOutput(jxl_info,&p,&extent);
1373
2.87k
        count=WriteBlob(image,MagickMaxBufferExtent-extent,output_buffer);
1374
2.87k
        if (count != (ssize_t) (MagickMaxBufferExtent-extent))
1375
0
          {
1376
0
            jxl_status=JXL_ENC_ERROR;
1377
0
            break;
1378
0
          }
1379
2.87k
      }
1380
2.82k
      output_buffer=(unsigned char *) RelinquishMagickMemory(output_buffer);
1381
2.82k
    }
1382
2.82k
  JxlThreadParallelRunnerDestroy(runner);
1383
2.82k
  JxlEncoderDestroy(jxl_info);
1384
2.82k
  if (jxl_status != JXL_ENC_SUCCESS)
1385
2.73k
    ThrowWriterException(CoderError,"UnableToWriteImageData");
1386
2.73k
  if (CloseBlob(image) == MagickFalse)
1387
0
    status=MagickFalse;
1388
2.73k
  return(status);
1389
2.82k
}
1390
#endif