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-2021 ImageMagick Studio LLC, a non-profit organization      %
20 %  dedicated to making software imaging solutions freely available.           %
21 %                                                                             %
22 %  You may not use this file except in compliance with the License.  You may  %
23 %  obtain a copy of the License at                                            %
24 %                                                                             %
25 %    https://imagemagick.org/script/license.php                               %
26 %                                                                             %
27 %  Unless required by applicable law or agreed to in writing, software        %
28 %  distributed under the License is distributed on an "AS IS" BASIS,          %
29 %  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
30 %  See the License for the specific language governing permissions and        %
31 %  limitations under the License.                                             %
32 %                                                                             %
33 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
34 %
35 %
36 */
37 
38 /*
39   Include declarations.
40 */
41 #include "MagickCore/studio.h"
42 #include "MagickCore/attribute.h"
43 #include "MagickCore/blob.h"
44 #include "MagickCore/blob-private.h"
45 #include "MagickCore/cache.h"
46 #include "MagickCore/exception.h"
47 #include "MagickCore/exception-private.h"
48 #include "MagickCore/image.h"
49 #include "MagickCore/image-private.h"
50 #include "MagickCore/list.h"
51 #include "MagickCore/magick.h"
52 #include "MagickCore/memory_.h"
53 #include "MagickCore/monitor.h"
54 #include "MagickCore/monitor-private.h"
55 #include "MagickCore/option.h"
56 #include "MagickCore/resource_.h"
57 #include "MagickCore/static.h"
58 #include "MagickCore/string_.h"
59 #include "MagickCore/string-private.h"
60 #include "MagickCore/module.h"
61 #if defined(MAGICKCORE_JXL_DELEGATE)
62 #include <jxl/decode.h>
63 #include <jxl/encode.h>
64 #include <jxl/thread_parallel_runner.h>
65 #endif
66 
67 /*
68   Typedef declarations.
69 */
70 typedef struct MemoryManagerInfo
71 {
72   Image
73     *image;
74 
75   ExceptionInfo
76     *exception;
77 } MemoryManagerInfo;
78 
79 /*
80   Forward declarations.
81 */
82 static MagickBooleanType
83   WriteJXLImage(const ImageInfo *,Image *,ExceptionInfo *);
84 
85 #if defined(MAGICKCORE_JXL_DELEGATE)
JXLAcquireMemory(void * opaque,size_t size)86 static void *JXLAcquireMemory(void *opaque, size_t size)
87 {
88   unsigned char
89     *data;
90 
91   data=(unsigned char *) AcquireQuantumMemory(size,sizeof(*data));
92   if (data == (unsigned char *) NULL)
93     {
94       MemoryManagerInfo
95         *memory_manager_info;
96 
97       memory_manager_info=(MemoryManagerInfo *) opaque;
98       (void) ThrowMagickException(memory_manager_info->exception,
99         GetMagickModule(),CoderError,"MemoryAllocationFailed","`%s'",
100         memory_manager_info->image->filename);
101     }
102   return(data);
103 }
104 
JXLRelinquishMemory(void * magick_unused (opaque),void * address)105 static void JXLRelinquishMemory(void *magick_unused(opaque),void *address)
106 {
107   magick_unreferenced(opaque);
108   (void) RelinquishMagickMemory(address);
109 }
110 
JXLSetMemoryManager(JxlMemoryManager * memory_manager,MemoryManagerInfo * memory_manager_info,Image * image,ExceptionInfo * exception)111 static inline void JXLSetMemoryManager(JxlMemoryManager *memory_manager,
112   MemoryManagerInfo *memory_manager_info,Image *image,ExceptionInfo *exception)
113 {
114   memory_manager_info->image=image;
115   memory_manager_info->exception=exception;
116   memory_manager->opaque=memory_manager_info;
117   memory_manager->alloc=JXLAcquireMemory;
118   memory_manager->free=JXLRelinquishMemory;
119 }
120 
JXLSetFormat(Image * image,JxlPixelFormat * format)121 static inline void JXLSetFormat(Image *image,JxlPixelFormat *format)
122 {
123   format->num_channels=(image->alpha_trait == BlendPixelTrait) ? 4 : 3;
124   format->data_type=(image->depth > 16) ? JXL_TYPE_FLOAT : (image->depth > 8) ?
125    JXL_TYPE_UINT16 : JXL_TYPE_UINT8;
126 }
127 
128 /*
129 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
130 %                                                                             %
131 %                                                                             %
132 %                                                                             %
133 %   R e a d J X L I m a g e                                                   %
134 %                                                                             %
135 %                                                                             %
136 %                                                                             %
137 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
138 %
139 %  ReadJXLImage() reads a JXL image file and returns it.  It allocates
140 %  the memory necessary for the new Image structure and returns a pointer to
141 %  the new image.
142 %
143 %  The format of the ReadJXLImage method is:
144 %
145 %      Image *ReadJXLImage(const ImageInfo *image_info,
146 %        ExceptionInfo *exception)
147 %
148 %  A description of each parameter follows:
149 %
150 %    o image_info: the image info.
151 %
152 %    o exception: return any errors or warnings in this structure.
153 %
154 */
JXLOrientationToOrientation(JxlOrientation orientation)155 static inline OrientationType JXLOrientationToOrientation(
156   JxlOrientation orientation)
157 {
158   switch (orientation)
159   {
160     default:
161     case JXL_ORIENT_IDENTITY:
162       return TopLeftOrientation;
163     case JXL_ORIENT_FLIP_HORIZONTAL:
164       return TopRightOrientation;
165     case JXL_ORIENT_ROTATE_180:
166       return BottomRightOrientation;
167     case JXL_ORIENT_FLIP_VERTICAL:
168       return BottomLeftOrientation;
169     case JXL_ORIENT_TRANSPOSE:
170       return LeftTopOrientation;
171     case JXL_ORIENT_ROTATE_90_CW:
172       return RightTopOrientation;
173     case JXL_ORIENT_ANTI_TRANSPOSE:
174       return RightBottomOrientation;
175     case JXL_ORIENT_ROTATE_90_CCW:
176       return LeftBottomOrientation;
177   }
178 }
179 
JXLDataTypeToStorageType(const JxlDataType data_type)180 static inline StorageType JXLDataTypeToStorageType(const JxlDataType data_type)
181 {
182   switch (data_type)
183   {
184     case JXL_TYPE_FLOAT:
185       return FloatPixel;
186     case JXL_TYPE_UINT16:
187       return ShortPixel;
188     case JXL_TYPE_UINT8:
189       return CharPixel;
190     default:
191       return UndefinedPixel;
192   }
193 }
194 
ReadJXLImage(const ImageInfo * image_info,ExceptionInfo * exception)195 static Image *ReadJXLImage(const ImageInfo *image_info,ExceptionInfo *exception)
196 {
197   Image
198     *image;
199 
200   JxlPixelFormat
201     format;
202 
203   JxlDecoderStatus
204     events_wanted;
205 
206   JxlDecoder
207     *decoder;
208 
209   JxlDecoderStatus
210     decoder_status;
211 
212   JxlMemoryManager
213     memory_manager;
214 
215   MagickBooleanType
216     status;
217 
218   MemoryManagerInfo
219     memory_manager_info;
220 
221   size_t
222     input_size;
223 
224   unsigned char
225     *input_buffer,
226     *output_buffer;
227 
228   void
229     *runner;
230 
231   /*
232     Open image file.
233   */
234   assert(image_info != (const ImageInfo *) NULL);
235   assert(image_info->signature == MagickCoreSignature);
236   if (image_info->debug != MagickFalse)
237     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
238       image_info->filename);
239   assert(exception != (ExceptionInfo *) NULL);
240   assert(exception->signature == MagickCoreSignature);
241   image=AcquireImage(image_info, exception);
242   status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
243   if (status == MagickFalse)
244     {
245       image=DestroyImageList(image);
246       return((Image *) NULL);
247     }
248   JXLSetMemoryManager(&memory_manager,&memory_manager_info,image,exception);
249   decoder=JxlDecoderCreate(&memory_manager);
250   if (decoder == (JxlDecoder *) NULL)
251     ThrowReaderException(CoderError,"MemoryAllocationFailed");
252   runner=JxlThreadParallelRunnerCreate(NULL,(size_t) GetMagickResourceLimit(
253     ThreadResource));
254   if (runner == (void *) NULL)
255     {
256       JxlDecoderDestroy(decoder);
257       ThrowReaderException(CoderError,"MemoryAllocationFailed");
258     }
259   decoder_status=JxlDecoderSetParallelRunner(decoder,JxlThreadParallelRunner,
260     runner);
261   if (decoder_status != JXL_DEC_SUCCESS)
262     {
263       JxlThreadParallelRunnerDestroy(runner);
264       JxlDecoderDestroy(decoder);
265       ThrowReaderException(CoderError,"MemoryAllocationFailed");
266     }
267   events_wanted=JXL_DEC_BASIC_INFO;
268   if (image_info->ping == MagickFalse)
269     events_wanted|=JXL_DEC_FULL_IMAGE | JXL_DEC_COLOR_ENCODING;
270   if (JxlDecoderSubscribeEvents(decoder,events_wanted) != JXL_DEC_SUCCESS)
271     {
272       JxlThreadParallelRunnerDestroy(runner);
273       JxlDecoderDestroy(decoder);
274       ThrowReaderException(CoderError,"UnableToReadImageData");
275     }
276   input_size=MagickMaxBufferExtent;
277   input_buffer=AcquireQuantumMemory(input_size,sizeof(*input_buffer));
278   if (input_buffer == (unsigned char *) NULL)
279     {
280       JxlThreadParallelRunnerDestroy(runner);
281       JxlDecoderDestroy(decoder);
282       ThrowReaderException(CoderError,"MemoryAllocationFailed");
283     }
284   output_buffer=(unsigned char *) NULL;
285   memset(&format,0,sizeof(format));
286   decoder_status=JXL_DEC_NEED_MORE_INPUT;
287   while ((decoder_status != JXL_DEC_SUCCESS) &&
288          (decoder_status != JXL_DEC_ERROR))
289   {
290     decoder_status=JxlDecoderProcessInput(decoder);
291     switch (decoder_status)
292     {
293       case JXL_DEC_NEED_MORE_INPUT:
294       {
295         size_t
296           remaining;
297 
298         ssize_t
299           count;
300 
301         remaining=JxlDecoderReleaseInput(decoder);
302         if (remaining > 0)
303           memmove(input_buffer,input_buffer+input_size-remaining,remaining);
304         count=ReadBlob(image,input_size-remaining,input_buffer+remaining);
305         if (count <= 0)
306           {
307             decoder_status=JXL_DEC_SUCCESS;
308             ThrowMagickException(exception,GetMagickModule(),CoderError,
309               "InsufficientImageDataInFile","`%s'",image->filename);
310             break;
311           }
312         decoder_status=JxlDecoderSetInput(decoder,(const uint8_t *) input_buffer,
313           (size_t) count);
314         if (decoder_status == JXL_DEC_SUCCESS)
315           decoder_status=JXL_DEC_NEED_MORE_INPUT;
316         break;
317       }
318       case JXL_DEC_BASIC_INFO:
319       {
320         JxlBasicInfo
321           basic_info;
322 
323         decoder_status=JxlDecoderGetBasicInfo(decoder,&basic_info);
324         if (decoder_status != JXL_DEC_SUCCESS)
325           break;
326         /* For now we dont support images with an animation */
327         if (basic_info.have_animation == 1)
328           {
329             ThrowMagickException(exception,GetMagickModule(),
330               MissingDelegateError,"NoDecodeDelegateForThisImageFormat",
331               "`%s'",image->filename);
332             break;
333           }
334         image->columns=basic_info.xsize;
335         image->rows=basic_info.ysize;
336         image->depth=basic_info.bits_per_sample;
337         if (basic_info.alpha_bits != 0)
338           image->alpha_trait=BlendPixelTrait;
339         image->orientation=JXLOrientationToOrientation(basic_info.orientation);
340         decoder_status=JXL_DEC_BASIC_INFO;
341         break;
342       }
343       case JXL_DEC_COLOR_ENCODING:
344       {
345         size_t
346           profile_size;
347 
348         StringInfo
349           *profile;
350 
351         decoder_status=JxlDecoderGetICCProfileSize(decoder,&format,
352           JXL_COLOR_PROFILE_TARGET_ORIGINAL,&profile_size);
353         if (decoder_status != JXL_DEC_SUCCESS)
354           break;
355         profile=AcquireStringInfo(profile_size);
356         decoder_status=JxlDecoderGetColorAsICCProfile(decoder,&format,
357           JXL_COLOR_PROFILE_TARGET_ORIGINAL,GetStringInfoDatum(profile),
358           profile_size);
359         (void) SetImageProfile(image,"icc",profile,exception);
360         DestroyStringInfo(profile);
361         if (decoder_status == JXL_DEC_SUCCESS)
362           decoder_status=JXL_DEC_COLOR_ENCODING;
363         break;
364       }
365       case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
366       {
367         size_t
368           output_size;
369 
370         JXLSetFormat(image,&format);
371         decoder_status=JxlDecoderImageOutBufferSize(decoder,&format,
372           &output_size);
373         if (decoder_status != JXL_DEC_SUCCESS)
374           break;
375         status=SetImageExtent(image,image->columns,image->rows,exception);
376         if (status == MagickFalse)
377           break;
378         output_buffer=AcquireQuantumMemory(output_size,sizeof(*output_buffer));
379         if (output_buffer == (unsigned char *) NULL)
380           {
381             ThrowMagickException(exception,GetMagickModule(),CoderError,
382               "MemoryAllocationFailed","`%s'",image->filename);
383             break;
384           }
385         decoder_status=JxlDecoderSetImageOutBuffer(decoder,&format,
386           output_buffer,output_size);
387         if (decoder_status == JXL_DEC_SUCCESS)
388           decoder_status=JXL_DEC_NEED_IMAGE_OUT_BUFFER;
389       }
390       case JXL_DEC_FULL_IMAGE:
391       {
392         StorageType
393           type;
394 
395         if (output_buffer == (unsigned char *) NULL)
396           {
397             ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
398               "UnableToReadImageData","`%s'",image->filename);
399             break;
400           }
401         type=JXLDataTypeToStorageType(format.data_type);
402         if (type == UndefinedPixel)
403           {
404             ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
405               "Unsupported data type","`%s'",image->filename);
406             break;
407           }
408         status=ImportImagePixels(image,0,0,image->columns,image->rows,
409           image->alpha_trait == BlendPixelTrait ? "RGBA" : "RGB",type,
410           output_buffer,exception);
411         if (status == MagickFalse)
412           decoder_status=JXL_DEC_ERROR;
413         break;
414       }
415       case JXL_DEC_SUCCESS:
416       case JXL_DEC_ERROR:
417         break;
418       default:
419         decoder_status=JXL_DEC_ERROR;
420         break;
421     }
422   }
423   output_buffer=(unsigned char *) RelinquishMagickMemory(output_buffer);
424   input_buffer=(unsigned char *) RelinquishMagickMemory(input_buffer);
425   JxlThreadParallelRunnerDestroy(runner);
426   JxlDecoderDestroy(decoder);
427   if (decoder_status == JXL_DEC_ERROR)
428     ThrowReaderException(CorruptImageError,"UnableToReadImageData");
429   (void) CloseBlob(image);
430   return(image);
431 }
432 #endif
433 
434 /*
435 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
436 %                                                                             %
437 %                                                                             %
438 %                                                                             %
439 %   R e g i s t e r J X L I m a g e                                           %
440 %                                                                             %
441 %                                                                             %
442 %                                                                             %
443 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
444 %
445 %  RegisterJXLImage() adds properties for the JXL image format to
446 %  the list of supported formats.  The properties include the image format
447 %  tag, a method to read and/or write the format, whether the format
448 %  supports the saving of more than one frame to the same file or blob,
449 %  whether the format supports native in-memory I/O, and a brief
450 %  description of the format.
451 %
452 %  The format of the RegisterJXLImage method is:
453 %
454 %      size_t RegisterJXLImage(void)
455 %
456 */
RegisterJXLImage(void)457 ModuleExport size_t RegisterJXLImage(void)
458 {
459   MagickInfo
460     *entry;
461 
462   entry=AcquireMagickInfo("JXL", "JXL", "JPEG XL (ISO/IEC 18181)");
463 #if defined(MAGICKCORE_JXL_DELEGATE)
464   entry->decoder=(DecodeImageHandler *) ReadJXLImage;
465   entry->encoder=(EncodeImageHandler *) WriteJXLImage;
466 #endif
467   entry->flags^=CoderAdjoinFlag;
468   (void) RegisterMagickInfo(entry);
469   return(MagickImageCoderSignature);
470 }
471 
472 /*
473 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
474 %                                                                             %
475 %                                                                             %
476 %                                                                             %
477 %   U n r e g i s t e r J X L I m a g e                                       %
478 %                                                                             %
479 %                                                                             %
480 %                                                                             %
481 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
482 %
483 %  UnregisterJXLImage() removes format registrations made by the
484 %  JXL module from the list of supported formats.
485 %
486 %  The format of the UnregisterJXLImage method is:
487 %
488 %      UnregisterJXLImage(void)
489 %
490 */
UnregisterJXLImage(void)491 ModuleExport void UnregisterJXLImage(void)
492 {
493   (void) UnregisterMagickInfo("JXL");
494 }
495 
496 #if defined(MAGICKCORE_JXL_DELEGATE)
497 /*
498 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
499 %                                                                             %
500 %                                                                             %
501 %                                                                             %
502 %  W r i t e J X L I m a g e                                                  %
503 %                                                                             %
504 %                                                                             %
505 %                                                                             %
506 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
507 %
508 %  WriteJXLImage() writes a JXL image file and returns it.  It
509 %  allocates the memory necessary for the new Image structure and returns a
510 %  pointer to the new image.
511 %
512 %  The format of the WriteJXLImage method is:
513 %
514 %      MagickBooleanType WriteJXLImage(const ImageInfo *image_info,
515 %        Image *image)
516 %
517 %  A description of each parameter follows:
518 %
519 %    o image_info: the image info.
520 %
521 %    o image:  The image.
522 %
523 */
524 
JXLWriteMetadata(const Image * image,JxlEncoder * encoder)525 static JxlEncoderStatus JXLWriteMetadata(const Image *image,
526   JxlEncoder *encoder)
527 {
528   JxlColorEncoding
529     color_encoding;
530 
531   JxlEncoderStatus
532     encoder_status;
533 
534   memset(&color_encoding,0,sizeof(color_encoding));
535   JxlColorEncodingSetToSRGB(&color_encoding,
536     IsImageGray(image) == MagickTrue ? JXL_TRUE : JXL_FALSE);
537   encoder_status=JxlEncoderSetColorEncoding(encoder,&color_encoding);
538   return(encoder_status);
539 }
540 
WriteJXLImage(const ImageInfo * image_info,Image * image,ExceptionInfo * exception)541 static MagickBooleanType WriteJXLImage(const ImageInfo *image_info,Image *image,
542   ExceptionInfo *exception)
543 {
544   const char
545     *option;
546 
547   JxlBasicInfo
548     basic_info;
549 
550   JxlEncoder
551     *encoder;
552 
553   JxlEncoderOptions
554     *encoder_options;
555 
556   JxlEncoderStatus
557     encoder_status;
558 
559   JxlMemoryManager
560     memory_manager;
561 
562   JxlPixelFormat
563     format;
564 
565   MagickBooleanType
566     status;
567 
568   MemoryManagerInfo
569     memory_manager_info;
570 
571   size_t
572     bytes_per_row;
573 
574   unsigned char
575     *input_buffer;
576 
577   void
578     *runner;
579 
580   /*
581     Open output image file.
582   */
583   assert(image_info != (const ImageInfo *) NULL);
584   assert(image_info->signature == MagickCoreSignature);
585   assert(image != (Image *) NULL);
586   assert(image->signature == MagickCoreSignature);
587   if (image->debug != MagickFalse)
588     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
589   assert(exception != (ExceptionInfo *) NULL);
590   assert(exception->signature == MagickCoreSignature);
591   status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
592   if (status == MagickFalse)
593     return(status);
594   JXLSetMemoryManager(&memory_manager,&memory_manager_info,image,exception);
595   encoder=JxlEncoderCreate(&memory_manager);
596   if (encoder == (JxlEncoder *) NULL)
597     ThrowWriterException(CoderError,"MemoryAllocationFailed");
598   runner=JxlThreadParallelRunnerCreate(NULL,(size_t) GetMagickResourceLimit(
599     ThreadResource));
600   if (runner == (void *) NULL)
601     {
602       JxlEncoderDestroy(encoder);
603       ThrowWriterException(CoderError,"MemoryAllocationFailed");
604     }
605   encoder_status=JxlEncoderSetParallelRunner(encoder,JxlThreadParallelRunner,
606     runner);
607   if (encoder_status != JXL_ENC_SUCCESS)
608     {
609       JxlThreadParallelRunnerDestroy(runner);
610       JxlEncoderDestroy(encoder);
611       return(MagickFalse);
612     }
613   memset(&format,0,sizeof(format));
614   JXLSetFormat(image,&format);
615   JxlEncoderInitBasicInfo(&basic_info);
616   basic_info.xsize=(uint32_t) image->columns;
617   basic_info.ysize=(uint32_t) image->rows;
618   basic_info.bits_per_sample=8;
619   if (format.data_type == JXL_TYPE_UINT16)
620     basic_info.bits_per_sample=16;
621   else if (format.data_type == JXL_TYPE_FLOAT)
622     {
623       basic_info.bits_per_sample=32;
624       basic_info.exponent_bits_per_sample=8;
625     }
626   if (image->alpha_trait == BlendPixelTrait)
627     basic_info.alpha_bits=basic_info.bits_per_sample;
628   encoder_status=JxlEncoderSetBasicInfo(encoder,&basic_info);
629   if (encoder_status != JXL_ENC_SUCCESS)
630     {
631       JxlThreadParallelRunnerDestroy(runner);
632       JxlEncoderDestroy(encoder);
633       ThrowWriterException(CoderError,"UnableToWriteImageData");
634     }
635   encoder_options=JxlEncoderOptionsCreate(encoder,(JxlEncoderOptions *) NULL);
636   if (encoder_options == (JxlEncoderOptions *) NULL)
637     {
638       JxlThreadParallelRunnerDestroy(runner);
639       JxlEncoderDestroy(encoder);
640       ThrowWriterException(CoderError,"MemoryAllocationFailed");
641     }
642   if (image->quality == 100)
643     JxlEncoderOptionsSetLossless(encoder_options,JXL_TRUE);
644   else
645     {
646       float
647         distance;
648 
649       distance=(image_info->quality >= 30) ? 0.1f+(float) (100-MagickMin(100,
650         image_info->quality))*0.09f : 6.4f+(float) pow(2.5f,(30-
651         image_info->quality)/5.0f)/6.25f;
652       JxlEncoderOptionsSetDistance(encoder_options,distance);
653     }
654   option=GetImageOption(image_info,"jxl:effort");
655   if (option != (const char *) NULL)
656     JxlEncoderOptionsSetEffort(encoder_options,StringToInteger(option));
657   encoder_status=JXLWriteMetadata(image,encoder);
658   encoder_status=JXL_ENC_SUCCESS;
659   if (encoder_status != JXL_ENC_SUCCESS)
660     {
661       JxlThreadParallelRunnerDestroy(runner);
662       JxlEncoderDestroy(encoder);
663       ThrowWriterException(CoderError,"UnableToWriteImageData");
664     }
665   bytes_per_row=image->columns*
666     ((image->alpha_trait == BlendPixelTrait) ? 4 : 3)*
667     ((format.data_type == JXL_TYPE_FLOAT) ? sizeof(float) :
668      (format.data_type == JXL_TYPE_UINT16) ? sizeof(short) : sizeof(char));
669   input_buffer=AcquireQuantumMemory(bytes_per_row,image->rows*
670     sizeof(*input_buffer));
671   if (input_buffer == (unsigned char *) NULL)
672     {
673       JxlThreadParallelRunnerDestroy(runner);
674       JxlEncoderDestroy(encoder);
675       ThrowWriterException(CoderError,"MemoryAllocationFailed");
676     }
677   status=ExportImagePixels(image,0,0,image->columns,image->rows,
678     image->alpha_trait == BlendPixelTrait ? "RGBA" : "RGB",
679     JXLDataTypeToStorageType(format.data_type),input_buffer,exception);
680   if (status == MagickFalse)
681     {
682       input_buffer=(unsigned char *) RelinquishMagickMemory(input_buffer);
683       JxlThreadParallelRunnerDestroy(runner);
684       JxlEncoderDestroy(encoder);
685       ThrowWriterException(CoderError,"MemoryAllocationFailed");
686     }
687   encoder_status=JxlEncoderAddImageFrame(encoder_options,&format,input_buffer,
688     bytes_per_row*image->rows);
689   if (encoder_status == JXL_ENC_SUCCESS)
690     {
691       unsigned char
692         *output_buffer;
693 
694       output_buffer=AcquireQuantumMemory(MagickMaxBufferExtent,
695         sizeof(*output_buffer));
696       if (output_buffer == (unsigned char *) NULL)
697         {
698           input_buffer=(unsigned char *) RelinquishMagickMemory(input_buffer);
699           JxlThreadParallelRunnerDestroy(runner);
700           JxlEncoderDestroy(encoder);
701           ThrowWriterException(CoderError,"MemoryAllocationFailed");
702         }
703       encoder_status=JXL_ENC_NEED_MORE_OUTPUT;
704       while (encoder_status == JXL_ENC_NEED_MORE_OUTPUT)
705       {
706         size_t
707           count;
708 
709         unsigned char
710           *p;
711 
712         count=MagickMaxBufferExtent;
713         p=output_buffer;
714         encoder_status=JxlEncoderProcessOutput(encoder,&p,&count);
715         (void) WriteBlob(image,MagickMaxBufferExtent-count,output_buffer);
716       }
717       output_buffer=(unsigned char *) RelinquishMagickMemory(output_buffer);
718     }
719   input_buffer=(unsigned char *) RelinquishMagickMemory(input_buffer);
720   JxlThreadParallelRunnerDestroy(runner);
721   JxlEncoderDestroy(encoder);
722   if (encoder_status != JXL_ENC_SUCCESS)
723     ThrowWriterException(CoderError,"UnableToWriteImageData");
724   (void) CloseBlob(image);
725   return(status);
726 }
727 #endif
728