Coverage Report

Created: 2025-10-12 07:48

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