Coverage Report

Created: 2026-11-12 07:35

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