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