| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % JJJ X X L % |
| % J X X L % |
| % J X L % |
| % J J X X L % |
| % JJ X X LLLLL % |
| % % |
| % % |
| % Read/Write JPEG XL Lossless JPEG1 Recompression % |
| % % |
| % Dirk Lemstra % |
| % December 2020 % |
| % % |
| % % |
| % Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization % |
| % dedicated to making software imaging solutions freely available. % |
| % % |
| % You may not use this file except in compliance with the License. You may % |
| % obtain a copy of the License at % |
| % % |
| % https://imagemagick.org/script/license.php % |
| % % |
| % Unless required by applicable law or agreed to in writing, software % |
| % distributed under the License is distributed on an "AS IS" BASIS, % |
| % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % |
| % See the License for the specific language governing permissions and % |
| % limitations under the License. % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % |
| */ |
| |
| /* |
| Include declarations. |
| */ |
| #include "MagickCore/studio.h" |
| #include "MagickCore/attribute.h" |
| #include "MagickCore/blob.h" |
| #include "MagickCore/blob-private.h" |
| #include "MagickCore/cache.h" |
| #include "MagickCore/exception.h" |
| #include "MagickCore/exception-private.h" |
| #include "MagickCore/image.h" |
| #include "MagickCore/image-private.h" |
| #include "MagickCore/list.h" |
| #include "MagickCore/magick.h" |
| #include "MagickCore/memory_.h" |
| #include "MagickCore/monitor.h" |
| #include "MagickCore/monitor-private.h" |
| #include "MagickCore/resource_.h" |
| #include "MagickCore/static.h" |
| #include "MagickCore/string_.h" |
| #include "MagickCore/module.h" |
| #if defined(MAGICKCORE_JXL_DELEGATE) |
| #include <jxl/decode.h> |
| #include <jxl/encode.h> |
| #include <jxl/thread_parallel_runner.h> |
| #endif |
| |
| /* |
| Typedef declarations. |
| */ |
| typedef struct MemoryManagerInfo |
| { |
| Image |
| *image; |
| |
| ExceptionInfo |
| *exception; |
| } MemoryManagerInfo; |
| |
| /* |
| Forward declarations. |
| */ |
| static MagickBooleanType |
| WriteJXLImage(const ImageInfo *,Image *,ExceptionInfo *); |
| |
| #if defined(MAGICKCORE_JXL_DELEGATE) |
| static void *JXLAcquireMemory(void *opaque, size_t size) |
| { |
| unsigned char |
| *data; |
| |
| data=(unsigned char *) AcquireQuantumMemory(size,sizeof(*data)); |
| if (data == (unsigned char *) NULL) |
| { |
| MemoryManagerInfo |
| *memory_manager_info; |
| |
| memory_manager_info=(MemoryManagerInfo *) opaque; |
| (void) ThrowMagickException(memory_manager_info->exception, |
| GetMagickModule(),CoderError,"MemoryAllocationFailed","`%s'", |
| memory_manager_info->image->filename); |
| } |
| return(data); |
| } |
| |
| static void JXLRelinquishMemory(void *magick_unused(opaque),void *address) |
| { |
| magick_unreferenced(opaque); |
| (void) RelinquishMagickMemory(address); |
| } |
| |
| static inline void JXLSetMemoryManager(JxlMemoryManager *memory_manager, |
| MemoryManagerInfo *memory_manager_info,Image *image,ExceptionInfo *exception) |
| { |
| memory_manager_info->image=image; |
| memory_manager_info->exception=exception; |
| memory_manager->opaque=memory_manager_info; |
| memory_manager->alloc=JXLAcquireMemory; |
| memory_manager->free=JXLRelinquishMemory; |
| } |
| |
| static inline void JXLSetFormat(Image *image,JxlPixelFormat *format) |
| { |
| format->num_channels=(image->alpha_trait == BlendPixelTrait) ? 4 : 3; |
| format->data_type=(image->depth > 8) ? JXL_TYPE_FLOAT : JXL_TYPE_UINT8; |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % R e a d J X L I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % ReadJXLImage() reads a JXL image file and returns it. It allocates |
| % the memory necessary for the new Image structure and returns a pointer to |
| % the new image. |
| % |
| % The format of the ReadJXLImage method is: |
| % |
| % Image *ReadJXLImage(const ImageInfo *image_info, |
| % ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image_info: the image info. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| static inline OrientationType JXLOrientationToOrientation( |
| JxlOrientation orientation) |
| { |
| switch (orientation) |
| { |
| default: |
| case JXL_ORIENT_IDENTITY: |
| return TopLeftOrientation; |
| case JXL_ORIENT_FLIP_HORIZONTAL: |
| return TopRightOrientation; |
| case JXL_ORIENT_ROTATE_180: |
| return BottomRightOrientation; |
| case JXL_ORIENT_FLIP_VERTICAL: |
| return BottomLeftOrientation; |
| case JXL_ORIENT_TRANSPOSE: |
| return LeftTopOrientation; |
| case JXL_ORIENT_ROTATE_90_CW: |
| return RightTopOrientation; |
| case JXL_ORIENT_ANTI_TRANSPOSE: |
| return RightBottomOrientation; |
| case JXL_ORIENT_ROTATE_90_CCW: |
| return LeftBottomOrientation; |
| } |
| } |
| |
| static Image *ReadJXLImage(const ImageInfo *image_info,ExceptionInfo *exception) |
| { |
| Image |
| *image; |
| |
| JxlPixelFormat |
| format; |
| |
| JxlDecoderStatus |
| events_wanted; |
| |
| JxlDecoder |
| *decoder; |
| |
| JxlDecoderStatus |
| decoder_status; |
| |
| JxlMemoryManager |
| memory_manager; |
| |
| MagickBooleanType |
| status; |
| |
| MemoryManagerInfo |
| memory_manager_info; |
| |
| size_t |
| input_size; |
| |
| unsigned char |
| *input_buffer, |
| *output_buffer; |
| |
| void |
| *runner; |
| |
| /* |
| Open image file. |
| */ |
| assert(image_info != (const ImageInfo *) NULL); |
| assert(image_info->signature == MagickCoreSignature); |
| if (image_info->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s", |
| image_info->filename); |
| assert(exception != (ExceptionInfo *) NULL); |
| assert(exception->signature == MagickCoreSignature); |
| image=AcquireImage(image_info, exception); |
| status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); |
| if (status == MagickFalse) |
| { |
| image=DestroyImageList(image); |
| return((Image *) NULL); |
| } |
| JXLSetMemoryManager(&memory_manager,&memory_manager_info,image,exception); |
| decoder=JxlDecoderCreate(&memory_manager); |
| if (decoder == (JxlDecoder *) NULL) |
| ThrowReaderException(CoderError,"MemoryAllocationFailed"); |
| runner=JxlThreadParallelRunnerCreate(NULL,(size_t) GetMagickResourceLimit( |
| ThreadResource)); |
| if (runner == (void *) NULL) |
| { |
| JxlDecoderDestroy(decoder); |
| ThrowWriterException(CoderError,"MemoryAllocationFailed"); |
| } |
| decoder_status=JxlDecoderSetParallelRunner(decoder,JxlThreadParallelRunner, |
| runner); |
| if (decoder_status != JXL_DEC_SUCCESS) |
| { |
| JxlThreadParallelRunnerDestroy(runner); |
| JxlDecoderDestroy(decoder); |
| ThrowWriterException(CoderError,"MemoryAllocationFailed"); |
| } |
| events_wanted=JXL_DEC_BASIC_INFO; |
| if (image_info->ping == MagickFalse) |
| events_wanted|=JXL_DEC_FULL_IMAGE | JXL_DEC_COLOR_ENCODING; |
| if (JxlDecoderSubscribeEvents(decoder,events_wanted) != JXL_DEC_SUCCESS) |
| { |
| JxlThreadParallelRunnerDestroy(runner); |
| JxlDecoderDestroy(decoder); |
| ThrowReaderException(CoderError,"UnableToReadImageData"); |
| } |
| input_size=MagickMaxBufferExtent; |
| input_buffer=AcquireQuantumMemory(input_size,sizeof(*input_buffer)); |
| if (input_buffer == (unsigned char *) NULL) |
| { |
| JxlThreadParallelRunnerDestroy(runner); |
| JxlDecoderDestroy(decoder); |
| ThrowReaderException(CoderError,"MemoryAllocationFailed"); |
| } |
| output_buffer=(unsigned char *) NULL; |
| memset(&format,0,sizeof(format)); |
| decoder_status=JXL_DEC_NEED_MORE_INPUT; |
| while ((decoder_status != JXL_DEC_SUCCESS) && |
| (decoder_status != JXL_DEC_ERROR)) |
| { |
| decoder_status=JxlDecoderProcessInput(decoder); |
| switch (decoder_status) |
| { |
| case JXL_DEC_NEED_MORE_INPUT: |
| { |
| size_t |
| remaining; |
| |
| ssize_t |
| count; |
| |
| remaining=JxlDecoderReleaseInput(decoder); |
| if (remaining > 0) |
| memmove(input_buffer,input_buffer+input_size-remaining,remaining); |
| count=ReadBlob(image,input_size-remaining,input_buffer+remaining); |
| if (count <= 0) |
| { |
| decoder_status=JXL_DEC_SUCCESS; |
| ThrowMagickException(exception,GetMagickModule(),CoderError, |
| "InsufficientImageDataInFile","`%s'",image->filename); |
| break; |
| } |
| decoder_status=JxlDecoderSetInput(decoder,(const uint8_t *) input_buffer, |
| (size_t) count); |
| if (decoder_status == JXL_DEC_SUCCESS) |
| decoder_status=JXL_DEC_NEED_MORE_INPUT; |
| break; |
| } |
| case JXL_DEC_BASIC_INFO: |
| { |
| JxlBasicInfo |
| basic_info; |
| |
| decoder_status=JxlDecoderGetBasicInfo(decoder,&basic_info); |
| if (decoder_status != JXL_DEC_SUCCESS) |
| break; |
| /* For now we dont support images with an animation */ |
| if (basic_info.have_animation == 1) |
| { |
| ThrowMagickException(exception,GetMagickModule(), |
| MissingDelegateError,"NoDecodeDelegateForThisImageFormat", |
| "`%s'",image->filename); |
| break; |
| } |
| image->columns=basic_info.xsize; |
| image->rows=basic_info.ysize; |
| image->depth=basic_info.bits_per_sample; |
| if (basic_info.alpha_bits != 0) |
| image->alpha_trait=BlendPixelTrait; |
| image->orientation=JXLOrientationToOrientation(basic_info.orientation); |
| decoder_status=JXL_DEC_BASIC_INFO; |
| break; |
| } |
| case JXL_DEC_COLOR_ENCODING: |
| { |
| size_t |
| profile_size; |
| |
| StringInfo |
| *profile; |
| |
| decoder_status=JxlDecoderGetICCProfileSize(decoder,&format, |
| JXL_COLOR_PROFILE_TARGET_ORIGINAL,&profile_size); |
| if (decoder_status != JXL_DEC_SUCCESS) |
| break; |
| profile=AcquireStringInfo(profile_size); |
| decoder_status=JxlDecoderGetColorAsICCProfile(decoder,&format, |
| JXL_COLOR_PROFILE_TARGET_ORIGINAL,GetStringInfoDatum(profile), |
| profile_size); |
| if (decoder_status == JXL_DEC_SUCCESS) |
| decoder_status=JXL_DEC_COLOR_ENCODING; |
| break; |
| } |
| case JXL_DEC_NEED_IMAGE_OUT_BUFFER: |
| { |
| size_t |
| output_size; |
| |
| JXLSetFormat(image,&format); |
| decoder_status=JxlDecoderImageOutBufferSize(decoder,&format, |
| &output_size); |
| if (decoder_status != JXL_DEC_SUCCESS) |
| break; |
| status=SetImageExtent(image,image->columns,image->rows,exception); |
| if (status == MagickFalse) |
| break; |
| output_buffer=AcquireQuantumMemory(output_size,sizeof(*output_buffer)); |
| if (output_buffer == (unsigned char *) NULL) |
| { |
| ThrowMagickException(exception,GetMagickModule(),CoderError, |
| "MemoryAllocationFailed","`%s'",image->filename); |
| break; |
| } |
| decoder_status=JxlDecoderSetImageOutBuffer(decoder,&format, |
| output_buffer,output_size); |
| if (decoder_status == JXL_DEC_SUCCESS) |
| decoder_status=JXL_DEC_NEED_IMAGE_OUT_BUFFER; |
| } |
| case JXL_DEC_FULL_IMAGE: |
| { |
| if (output_buffer == (unsigned char *) NULL) |
| { |
| ThrowMagickException(exception,GetMagickModule(),CorruptImageError, |
| "UnableToReadImageData","`%s'",image->filename); |
| break; |
| } |
| status=ImportImagePixels(image,0,0,image->columns,image->rows, |
| image->alpha_trait == BlendPixelTrait ? "RGBA" : "RGB", |
| format.data_type == JXL_TYPE_FLOAT ? FloatPixel : CharPixel, |
| output_buffer,exception); |
| if (status == MagickFalse) |
| decoder_status=JXL_DEC_ERROR; |
| break; |
| } |
| case JXL_DEC_SUCCESS: |
| case JXL_DEC_ERROR: |
| break; |
| default: |
| decoder_status=JXL_DEC_ERROR; |
| break; |
| } |
| } |
| output_buffer=(unsigned char *) RelinquishMagickMemory(output_buffer); |
| input_buffer=(unsigned char *) RelinquishMagickMemory(input_buffer); |
| JxlThreadParallelRunnerDestroy(runner); |
| JxlDecoderDestroy(decoder); |
| if (decoder_status == JXL_DEC_ERROR) |
| ThrowReaderException(CorruptImageError,"UnableToReadImageData"); |
| return(image); |
| } |
| #endif |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % R e g i s t e r J X L I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % RegisterJXLImage() adds properties for the JXL image format to |
| % the list of supported formats. The properties include the image format |
| % tag, a method to read and/or write the format, whether the format |
| % supports the saving of more than one frame to the same file or blob, |
| % whether the format supports native in-memory I/O, and a brief |
| % description of the format. |
| % |
| % The format of the RegisterJXLImage method is: |
| % |
| % size_t RegisterJXLImage(void) |
| % |
| */ |
| ModuleExport size_t RegisterJXLImage(void) |
| { |
| MagickInfo |
| *entry; |
| |
| entry=AcquireMagickInfo("JXL", "JXL", "JPEG XL Lossless JPEG1 Recompression"); |
| #if defined(MAGICKCORE_JXL_DELEGATE) |
| entry->decoder=(DecodeImageHandler *) ReadJXLImage; |
| entry->encoder=(EncodeImageHandler *) WriteJXLImage; |
| #endif |
| entry->flags^=CoderAdjoinFlag; |
| (void) RegisterMagickInfo(entry); |
| return(MagickImageCoderSignature); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % U n r e g i s t e r J X L I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % UnregisterJXLImage() removes format registrations made by the |
| % JXL module from the list of supported formats. |
| % |
| % The format of the UnregisterJXLImage method is: |
| % |
| % UnregisterJXLImage(void) |
| % |
| */ |
| ModuleExport void UnregisterJXLImage(void) |
| { |
| (void) UnregisterMagickInfo("JXL"); |
| } |
| |
| #if defined(MAGICKCORE_JXL_DELEGATE) |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % W r i t e J X L I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % WriteJXLImage() writes a JXL image file and returns it. It |
| % allocates the memory necessary for the new Image structure and returns a |
| % pointer to the new image. |
| % |
| % The format of the WriteJXLImage method is: |
| % |
| % MagickBooleanType WriteJXLImage(const ImageInfo *image_info, |
| % Image *image) |
| % |
| % A description of each parameter follows: |
| % |
| % o image_info: the image info. |
| % |
| % o image: The image. |
| % |
| */ |
| static MagickBooleanType WriteJXLImage(const ImageInfo *image_info,Image *image, |
| ExceptionInfo *exception) |
| { |
| JxlBasicInfo |
| basic_info; |
| |
| JxlEncoder |
| *encoder; |
| |
| JxlEncoderOptions |
| *encoder_options; |
| |
| JxlEncoderStatus |
| encoder_status; |
| |
| JxlMemoryManager |
| memory_manager; |
| |
| JxlPixelFormat |
| format; |
| |
| MagickBooleanType |
| status; |
| |
| MemoryManagerInfo |
| memory_manager_info; |
| |
| size_t |
| bytes_per_row; |
| |
| unsigned char |
| *input_buffer; |
| |
| void |
| *runner; |
| |
| /* |
| Open output image file. |
| */ |
| assert(image_info != (const ImageInfo *) NULL); |
| assert(image_info->signature == MagickCoreSignature); |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| assert(exception != (ExceptionInfo *) NULL); |
| assert(exception->signature == MagickCoreSignature); |
| status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception); |
| if (status == MagickFalse) |
| return(status); |
| JXLSetMemoryManager(&memory_manager,&memory_manager_info,image,exception); |
| encoder=JxlEncoderCreate(&memory_manager); |
| if (encoder == (JxlEncoder *) NULL) |
| ThrowWriterException(CoderError,"MemoryAllocationFailed"); |
| runner=JxlThreadParallelRunnerCreate(NULL,(size_t) GetMagickResourceLimit( |
| ThreadResource)); |
| if (runner == (void *) NULL) |
| { |
| JxlEncoderDestroy(encoder); |
| ThrowWriterException(CoderError,"MemoryAllocationFailed"); |
| } |
| encoder_status=JxlEncoderSetParallelRunner(encoder,JxlThreadParallelRunner, |
| runner); |
| if (encoder_status != JXL_ENC_SUCCESS) |
| { |
| JxlThreadParallelRunnerDestroy(runner); |
| JxlEncoderDestroy(encoder); |
| return(MagickFalse); |
| } |
| memset(&format,0,sizeof(format)); |
| JXLSetFormat(image,&format); |
| memset(&basic_info,0,sizeof(basic_info)); |
| basic_info.xsize=(uint32_t) image->columns; |
| basic_info.ysize=(uint32_t) image->rows; |
| basic_info.bits_per_sample=8; |
| if (format.data_type == JXL_TYPE_FLOAT) |
| { |
| basic_info.bits_per_sample=32; |
| basic_info.exponent_bits_per_sample=8; |
| } |
| if (image->alpha_trait == BlendPixelTrait) |
| basic_info.alpha_bits=basic_info.bits_per_sample; |
| encoder_status=JxlEncoderSetBasicInfo(encoder,&basic_info); |
| if (encoder_status != JXL_ENC_SUCCESS) |
| { |
| JxlThreadParallelRunnerDestroy(runner); |
| JxlEncoderDestroy(encoder); |
| ThrowWriterException(CoderError,"UnableToWriteImageData"); |
| } |
| encoder_options=JxlEncoderOptionsCreate(encoder,(JxlEncoderOptions *) NULL); |
| if (encoder_options == (JxlEncoderOptions *) NULL) |
| { |
| JxlThreadParallelRunnerDestroy(runner); |
| JxlEncoderDestroy(encoder); |
| ThrowWriterException(CoderError,"MemoryAllocationFailed"); |
| } |
| if (image->quality == 100) |
| JxlEncoderOptionsSetLossless(encoder_options,JXL_TRUE); |
| bytes_per_row=image->columns* |
| ((image->alpha_trait == BlendPixelTrait) ? 4 : 3)* |
| ((format.data_type == JXL_TYPE_FLOAT) ? sizeof(float) : sizeof(char)); |
| input_buffer=AcquireQuantumMemory(bytes_per_row,image->rows* |
| sizeof(*input_buffer)); |
| if (input_buffer == (unsigned char *) NULL) |
| { |
| JxlThreadParallelRunnerDestroy(runner); |
| JxlEncoderDestroy(encoder); |
| ThrowWriterException(CoderError,"MemoryAllocationFailed"); |
| } |
| status=ExportImagePixels(image,0,0,image->columns,image->rows, |
| image->alpha_trait == BlendPixelTrait ? "RGBA" : "RGB", |
| format.data_type == JXL_TYPE_FLOAT ? FloatPixel : CharPixel, |
| input_buffer,exception); |
| if (status == MagickFalse) |
| { |
| input_buffer=(unsigned char *) RelinquishMagickMemory(input_buffer); |
| JxlThreadParallelRunnerDestroy(runner); |
| JxlEncoderDestroy(encoder); |
| ThrowWriterException(CoderError,"MemoryAllocationFailed"); |
| } |
| encoder_status=JxlEncoderAddImageFrame(encoder_options,&format,input_buffer, |
| bytes_per_row*image->rows); |
| if (encoder_status == JXL_ENC_SUCCESS) |
| { |
| unsigned char |
| *output_buffer; |
| |
| output_buffer=AcquireQuantumMemory(MagickMaxBufferExtent, |
| sizeof(*output_buffer)); |
| if (output_buffer == (unsigned char *) NULL) |
| { |
| input_buffer=(unsigned char *) RelinquishMagickMemory(input_buffer); |
| JxlThreadParallelRunnerDestroy(runner); |
| JxlEncoderDestroy(encoder); |
| ThrowWriterException(CoderError,"MemoryAllocationFailed"); |
| } |
| encoder_status=JXL_ENC_NEED_MORE_OUTPUT; |
| while (encoder_status == JXL_ENC_NEED_MORE_OUTPUT) |
| { |
| size_t |
| count; |
| |
| unsigned char |
| *p; |
| |
| count=MagickMaxBufferExtent; |
| p=output_buffer; |
| encoder_status=JxlEncoderProcessOutput(encoder,&p,&count); |
| (void) WriteBlob(image,MagickMaxBufferExtent-count,output_buffer); |
| } |
| output_buffer=(unsigned char *) RelinquishMagickMemory(output_buffer); |
| } |
| input_buffer=(unsigned char *) RelinquishMagickMemory(input_buffer); |
| JxlThreadParallelRunnerDestroy(runner); |
| JxlEncoderDestroy(encoder); |
| if (encoder_status != JXL_ENC_SUCCESS) |
| ThrowWriterException(CoderError,"UnableToWriteImageData"); |
| (void) CloseBlob(image); |
| return(status); |
| } |
| #endif |