Coverage Report

Created: 2026-03-31 06:56

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