GraphicsMagick: More work on the JXL writer. Still not working!
GraphicsMagick Commits <[email protected]>
| Newsgroups | gmane.comp.video.graphicsmagick.cvs |
|---|---|
| Message-ID | <mailman.38119.1646516590.1607.graphicsmagick-commit@lists.sourceforge.net> |
changeset 105031e1959c in /hg/GraphicsMagick details: http://hg.GraphicsMagick.org/hg/GraphicsMagick?cmd=changeset;node=105031e1959c summary: More work on the JXL writer. Still not working! diffstat: ChangeLog | 5 + PerlMagick/t/jxl/input.jxl | Bin README.txt | 3 + VisualMagick/installer/inc/version.isx | 4 +- coders/jxl.c | 323 +++++++++++++++++++++++++------- magick/version.h | 4 +- www/Changelog.html | 4 + www/INSTALL-unix.html | 58 ++++- www/INSTALL-unix.rst | 22 ++ www/README.html | 3 + 10 files changed, 327 insertions(+), 99 deletions(-) diffs (truncated from 753 to 500 lines): diff -r 1428b30e12b0 -r 105031e1959c ChangeLog --- a/ChangeLog Sat Feb 26 18:39:29 2022 -0600 +++ b/ChangeLog Sat Mar 05 15:42:50 2022 -0600 @@ -1,3 +1,8 @@ +2022-03-05 Bob Friesenhahn <[email protected]> + + * coders/jxl.c (WriteJXLImage): Work to update JXL writer code to + compile clean with libjxl v0.7.0. Still not working! + 2022-02-26 Bob Friesenhahn <[email protected]> * coders/heif.c (ReadHEIFImage): Pass decode options to diff -r 1428b30e12b0 -r 105031e1959c PerlMagick/t/jxl/input.jxl Binary file PerlMagick/t/jxl/input.jxl has changed diff -r 1428b30e12b0 -r 105031e1959c README.txt --- a/README.txt Sat Feb 26 18:39:29 2022 -0600 +++ b/README.txt Sat Mar 05 15:42:50 2022 -0600 @@ -394,3 +394,6 @@ software patents. The user is responsible for assuring that some entity has paid to allow use of any applicable HEVC patents on their device, or that the patents do not apply in their country. + +* GraphicsMagick requires the JPEG XL library from + https://github.com/libjxl/libjxl to read and write JPEG XL files. diff -r 1428b30e12b0 -r 105031e1959c VisualMagick/installer/inc/version.isx --- a/VisualMagick/installer/inc/version.isx Sat Feb 26 18:39:29 2022 -0600 +++ b/VisualMagick/installer/inc/version.isx Sat Mar 05 15:42:50 2022 -0600 @@ -10,5 +10,5 @@ #define public MagickPackageName "GraphicsMagick" #define public MagickPackageVersion "1.4" -#define public MagickPackageVersionAddendum ".020220226" -#define public MagickPackageReleaseDate "snapshot-20220226" +#define public MagickPackageVersionAddendum ".020220305" +#define public MagickPackageReleaseDate "snapshot-20220305" diff -r 1428b30e12b0 -r 105031e1959c coders/jxl.c --- a/coders/jxl.c Sat Feb 26 18:39:29 2022 -0600 +++ b/coders/jxl.c Sat Mar 05 15:42:50 2022 -0600 @@ -24,6 +24,7 @@ */ #include "magick/studio.h" +#include "magick/analyze.h" #include "magick/blob.h" #include "magick/colormap.h" #include "magick/log.h" @@ -42,6 +43,9 @@ #define MaxBufferExtent 16384 +/* Enable use of functions which produce deprecation warnings */ +#define ENABLE_DEPRECATED 0 + struct MyJXLMemoryManager { JxlMemoryManager super; ExceptionInfo *exception; @@ -128,17 +132,17 @@ } #define FOR_PIXEL_PACKETS \ - for (y=0; y < (long)image->rows; y++) \ - { \ - q=SetImagePixelsEx(image,0,y,image->columns,1,exception); \ - if (q == (PixelPacket *) NULL) \ - return MagickFail; \ - for (x=0; x < (long)image->columns; x++) + for (y=0; y < (long)image->rows; y++) \ + { \ + q=SetImagePixelsEx(image,0,y,image->columns,1,exception); \ + if (q == (PixelPacket *) NULL) \ + return MagickFail; \ + for (x=0; x < (long)image->columns; x++) #define END_FOR_PIXEL_PACKETS \ if (!SyncImagePixels(image)) \ - return MagickFail; \ - } \ + return MagickFail; \ + } \ static MagickBool fill_pixels_char(Image *image, ExceptionInfo *exception, @@ -347,18 +351,20 @@ return MagickTrue; } -#define JXLReadCleanup() \ - if (jxl_thread_runner) JxlThreadParallelRunnerDestroy(jxl_thread_runner); \ - if (jxl) JxlDecoderDestroy(jxl); \ - MagickFreeResourceLimitedMemory(in_buf); \ - MagickFreeResourceLimitedMemory(out_buf) \ +#define JXLReadCleanup() \ + MagickFreeResourceLimitedMemory(out_buf); \ + MagickFreeResourceLimitedMemory(in_buf); \ + if (jxl_thread_runner) \ + JxlThreadParallelRunnerDestroy(jxl_thread_runner); \ + if (jxl) \ + JxlDecoderDestroy(jxl); -#define ThrowJXLReaderException(code_,reason_,image_) \ -{ \ - JXLReadCleanup(); \ - ThrowReaderException(code_,reason_,image_); \ -} +#define ThrowJXLReaderException(code_,reason_,image_) \ + { \ + JXLReadCleanup(); \ + ThrowReaderException(code_,reason_,image_); \ + } static Image *ReadJXLImage(const ImageInfo *image_info, ExceptionInfo *exception) @@ -381,7 +387,7 @@ struct MyJXLMemoryManager mm; - size_t + const size_t in_len = MaxBufferExtent; unsigned char @@ -640,7 +646,9 @@ } } else if (format.data_type == JXL_TYPE_UINT8) - res=fill_pixels_char_grayscale(image, exception, out_buf); + { + res=fill_pixels_char_grayscale(image, exception, out_buf); + } if (!res) status=JXL_DEC_ERROR; @@ -695,7 +703,7 @@ #define JXLWriteCleanup() \ if (jxl_thread_runner) JxlThreadParallelRunnerDestroy(jxl_thread_runner); \ - if (jxl) JxlEncoderDestroy(jxl); \ + if (jxl_encoder) JxlEncoderDestroy(jxl_encoder); \ MagickFreeResourceLimitedMemory(in_buf); \ MagickFreeResourceLimitedMemory(out_buf); \ @@ -712,14 +720,19 @@ status; JxlEncoder - *jxl = NULL; + *jxl_encoder = NULL; +#if ENABLE_DEPRECATED JxlEncoderOptions *encoder_options; +#endif void *jxl_thread_runner = NULL; + JxlEncoderFrameSettings + *frame_settings = NULL; /* Deallocated when encoder is destroyed with JxlEncoderDestroy() */ + JxlEncoderStatus jxl_status; @@ -727,10 +740,10 @@ basic_info; struct MyJXLMemoryManager - mm; + memory_manager; - const char - *value; + ImageCharacteristics + characteristics; size_t size_row; @@ -739,16 +752,13 @@ *in_buf = NULL, *out_buf = NULL; - MagickBool - grayscale = MagickFalse; - - JxlPixelFormat - format; + JxlPixelFormat + pixel_format; JxlColorEncoding color_encoding = {}; - memset(&format,0,sizeof(format)); + memset(&pixel_format,0,sizeof(pixel_format)); assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickSignature); @@ -756,6 +766,35 @@ assert(image->signature == MagickSignature); /* + Ensure that image is in desired output space + */ + if ((image_info->type != UndefinedType) && + (image_info->type != OptimizeType)) + (void) SetImageType(image,image_info->type); + else if (!IsCMYKColorspace(image->colorspace) && + (!IsRGBColorspace(image->colorspace))) + (void) TransformColorspace(image,RGBColorspace); + + /* + Analyze image to be written. + */ + if (!GetImageCharacteristics(image,&characteristics, + (OptimizeType == image_info->type), + &image->exception)) + { + return MagickFail; + } + + (void) LogMagickEvent(CoderEvent,GetMagickModule(), + "Image characteristics: cmyk=%c, gray=%c, mono=%c," + " opaque=%c, palette=%c", + (characteristics.cmyk ? 'y' : 'n'), + (characteristics.grayscale ? 'y' : 'n'), + (characteristics.monochrome ? 'y' : 'n'), + (characteristics.opaque ? 'y' : 'n'), + (characteristics.palette ? 'y' : 'n')); + + /* Open output image file. */ status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception); @@ -763,68 +802,100 @@ ThrowWriterException(FileOpenError,UnableToOpenFile,image); /* Init JXL-Decoder handles */ - MyJxlMemoryManagerInit(&mm,image,&image->exception); - jxl=JxlEncoderCreate(&mm.super); - if (jxl == (JxlEncoder *) NULL) + MyJxlMemoryManagerInit(&memory_manager,image,&image->exception); + jxl_encoder=JxlEncoderCreate(&memory_manager.super); + if (jxl_encoder == (JxlEncoder *) NULL) ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image); - jxl_thread_runner=JxlThreadParallelRunnerCreate(NULL,(size_t) GetMagickResourceLimit( - ThreadsResource)); + jxl_thread_runner= + JxlThreadParallelRunnerCreate(NULL, + (size_t) GetMagickResourceLimit(ThreadsResource)); if (jxl_thread_runner == (void *) NULL) ThrowJXLWriterException(ResourceLimitError,MemoryAllocationFailed,image); - if (JxlEncoderSetParallelRunner(jxl, JxlThreadParallelRunner, jxl_thread_runner) - != JXL_ENC_SUCCESS) + if (JxlEncoderSetParallelRunner(jxl_encoder, JxlThreadParallelRunner, jxl_thread_runner) + != JXL_ENC_SUCCESS) ThrowJXLWriterException(ResourceLimitError,MemoryAllocationFailed,image); - grayscale = IsGrayColorspace(image_info->colorspace); - if (grayscale) - format.num_channels = 1; + if (characteristics.grayscale) + pixel_format.num_channels = 1; else { - (void) TransformColorspace(image,RGBColorspace); image->storage_class=DirectClass; - format.num_channels = image->matte ? 4 : 3; + pixel_format.num_channels = characteristics.opaque ? 3 : 4; } if (image->depth <= 8) - format.data_type = JXL_TYPE_UINT8; + pixel_format.data_type = JXL_TYPE_UINT8; else if (image->depth <= 16) - format.data_type = JXL_TYPE_UINT16; + pixel_format.data_type = JXL_TYPE_UINT16; else if (image->depth <= 32) - format.data_type = JXL_TYPE_FLOAT; + pixel_format.data_type = JXL_TYPE_UINT32; else ThrowJXLWriterException(CoderError,ColorspaceModelIsNotSupported,image); + + /* Initialize JxlBasicInfo struct to default values. */ JxlEncoderInitBasicInfo(&basic_info); + /* Width of the image in pixels, before applying orientation. */ basic_info.xsize = image->columns; + /* Height of the image in pixels, before applying orientation. */ basic_info.ysize = image->rows; + + /* JXL_TYPE_FLOAT requires a nominal range of 0 to 1 */ + + if (pixel_format.data_type == JXL_TYPE_UINT8) + basic_info.bits_per_sample = 8; + else if (pixel_format.data_type == JXL_TYPE_UINT16) + basic_info.bits_per_sample = 16; + else if ((pixel_format.data_type == JXL_TYPE_UINT32) || (pixel_format.data_type == JXL_TYPE_FLOAT)) + basic_info.bits_per_sample = 32; + + pixel_format.endianness = JXL_NATIVE_ENDIAN; + pixel_format.align = 0; + + if (pixel_format.data_type == JXL_TYPE_FLOAT) + basic_info.exponent_bits_per_sample = 8; + + (void) LogMagickEvent(CoderEvent,GetMagickModule(), + "Using %u bits per sample", basic_info.bits_per_sample); + +#if 0 if (image->depth == 8) + /* Original image color channel bit depth. */ basic_info.bits_per_sample = 8; else { + /* FIXME: It seems that JXL_TYPE_UINT16/JXL_TYPE_UINT32 should be used and no floating point */ + /* Original image color channel bit depth. */ basic_info.bits_per_sample=32; + /* Original image color channel floating point exponent bits, or + 0 if they are unsigned integer. */ basic_info.exponent_bits_per_sample=8; } - if (image->matte) +#endif + if (!characteristics.opaque) basic_info.alpha_bits=basic_info.bits_per_sample; - if ((jxl_status = JxlEncoderSetBasicInfo(jxl,&basic_info)) != JXL_ENC_SUCCESS) + /* Set the global metadata of the image encoded by this encoder. */ + if ((jxl_status = JxlEncoderSetBasicInfo(jxl_encoder,&basic_info)) != JXL_ENC_SUCCESS) { /* TODO better error codes */ if (jxl_status == JXL_ENC_ERROR) ThrowJXLWriterException(CoderError,NoDataReturned,image) - else if (jxl_status == JXL_ENC_NOT_SUPPORTED) - ThrowJXLWriterException(CoderError,UnsupportedBitsPerSample,image) - else - ThrowJXLWriterException(CoderFatalError,Default,image) - } + else if (jxl_status == JXL_ENC_NOT_SUPPORTED) + ThrowJXLWriterException(CoderError,UnsupportedBitsPerSample,image) + else + ThrowJXLWriterException(CoderFatalError,Default,image) + } /* Set expected input colorspace */ + /* FIXME: For RGB we want to set JXL_COLOR_SPACE_RGB and for gray we want JXL_COLOR_SPACE_GRAY */ basic_info.uses_original_profile = JXL_TRUE; - JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/format.num_channels < 3); - if (JxlEncoderSetColorEncoding(jxl, &color_encoding) != JXL_ENC_SUCCESS) - ThrowJXLWriterException(CoderFatalError,Default,image) + JxlColorEncodingSetToSRGB(&color_encoding, pixel_format.num_channels < 3); + if (JxlEncoderSetColorEncoding(jxl_encoder, &color_encoding) != JXL_ENC_SUCCESS) + ThrowJXLWriterException(CoderFatalError,Default,image) - encoder_options=JxlEncoderOptionsCreate(jxl,(JxlEncoderOptions *) NULL); +#if ENABLE_DEPRECATED + encoder_options=JxlEncoderOptionsCreate(jxl_encoder,(JxlEncoderOptions *) NULL); if (encoder_options == (JxlEncoderOptions *) NULL) ThrowJXLWriterException(ResourceLimitError,MemoryAllocationFailed,image); if (image_info->quality == 100) @@ -834,54 +905,152 @@ /* same as cjxl.c: roughly similar to jpeg-quality for range 1-99 */ if (image_info->quality >= 30) { JxlEncoderOptionsSetDistance(encoder_options, - 0.1 + (100 - image_info->quality) * 0.09); + 0.1 + (100 - image_info->quality) * 0.09); } else { JxlEncoderOptionsSetDistance(encoder_options, - 6.4 + pow(2.5, (30 - image_info->quality) / 5.0f) / 6.25f); + 6.4 + pow(2.5, (30 - image_info->quality) / 5.0f) / 6.25f); } } - if ((value=AccessDefinition(image_info,"jxl","effort"))) - JxlEncoderOptionsSetEffort(encoder_options, MagickAtoI(value)); - if ((value=AccessDefinition(image_info,"jxl","decodingspeed"))) - JxlEncoderOptionsSetDecodingSpeed(encoder_options, MagickAtoI(value)); + { + const char + *value; + + if ((value=AccessDefinition(image_info,"jxl","effort"))) + JxlEncoderOptionsSetEffort(encoder_options, MagickAtoI(value)); /* FIXME: deprecated */ + } + { + const char + *value; + + if ((value=AccessDefinition(image_info,"jxl","decodingspeed"))) + JxlEncoderOptionsSetDecodingSpeed(encoder_options, MagickAtoI(value)); /* FIXME: deprecated */ + } +#endif /* if ENABLE_DEPRECATED */ + + frame_settings = JxlEncoderFrameSettingsCreate(jxl_encoder, NULL); + if (image_info->quality == 100) + { + if (JxlEncoderSetFrameLossless(frame_settings,JXL_TRUE) != JXL_ENC_SUCCESS) + ThrowJXLWriterException(CoderFatalError,Default,image); + } + else + { + /* same as cjxl.c: roughly similar to jpeg-quality for range 1-99 */ + if (image_info->quality >= 30) + { + if (JxlEncoderSetFrameDistance(frame_settings, + 0.1 + (100 - image_info->quality) * 0.09) != JXL_ENC_SUCCESS) + ThrowJXLWriterException(CoderFatalError,Default,image); + } + else + { + if (JxlEncoderSetFrameDistance(frame_settings, + 6.4 + pow(2.5, (30 - image_info->quality) / 5.0f) / 6.25f) != JXL_ENC_SUCCESS) + ThrowJXLWriterException(CoderFatalError,Default,image); + } + } + /* + Handle key/value for settings handled by JxlEncoderFrameSettingsSetOption() + */ + { + static const struct + { + const char key[14]; + JxlEncoderFrameSettingId fs_id; + } int_frame_settings[] + = + { + { "effort", JXL_ENC_FRAME_SETTING_EFFORT }, + { "decodingspeed", JXL_ENC_FRAME_SETTING_DECODING_SPEED }, + }; + + unsigned int + index; + + for (index = 0; index < ArraySize(int_frame_settings); index++) + { + const char *key = int_frame_settings[index].key; + const char *value; + if ((value=AccessDefinition(image_info,"jxl",key))) + { + int int_value = MagickAtoI(value); + if (JxlEncoderFrameSettingsSetOption(frame_settings, int_frame_settings[index].fs_id, int_value) != JXL_ENC_SUCCESS) + (void) LogMagickEvent(CoderEvent,GetMagickModule(), + "JXL does not support \"%s\" frame setting!", key); + else + (void) LogMagickEvent(CoderEvent,GetMagickModule(), + "Set \"%s\" to %d", key, int_value); + } + } + } /* get & fill pixel buffer */ - size_row=image->columns * format.num_channels * - ((format.data_type == JXL_TYPE_FLOAT) ? sizeof(float) : sizeof(char)); + size_row=image->columns * pixel_format.num_channels * (basic_info.bits_per_sample/8); + (void) LogMagickEvent(CoderEvent,GetMagickModule(), + "size_row = %zu", size_row); in_buf=MagickAllocateResourceLimitedArray(unsigned char *,image->rows,size_row); if (in_buf == (unsigned char *) NULL) ThrowJXLWriterException(ResourceLimitError,MemoryAllocationFailed,image); + (void) LogMagickEvent(CoderEvent,GetMagickModule(), + "DispatchImage(0,0,%lu,%lu, %s)", + image->columns,image->rows, + characteristics.grayscale ? "I" : (characteristics.opaque ? "RGB" : "RGBA")); + status=DispatchImage(image,0,0,image->columns,image->rows, - grayscale ? "I" : (image->matte ? "RGBA" : "RGB"), - format.data_type == JXL_TYPE_UINT8 ? CharPixel : ShortPixel, - in_buf,&image->exception); + characteristics.grayscale ? "I" : (image->matte ? "RGBA" : "RGB"), + basic_info.bits_per_sample == 8 ? CharPixel : + (basic_info.bits_per_sample == 16 ? ShortPixel : + basic_info.bits_per_sample == LongPixel), + in_buf,&image->exception); if (status == MagickFail) ThrowJXLWriterException(ResourceLimitError,MemoryAllocationFailed,image); /* real encode */ - if (JxlEncoderAddImageFrame(encoder_options,&format,in_buf, - image->rows * size_row) != JXL_ENC_SUCCESS) + if (JxlEncoderAddImageFrame(frame_settings,&pixel_format,in_buf, + image->rows * size_row) != JXL_ENC_SUCCESS) /* TODO Better Error-code? */ ThrowJXLWriterException(CoderError,NoDataReturned,image); +#if 0 + /* From examples/encode_oneshot.cc */ + compressed->resize(64); + uint8_t* next_out = compressed->data(); + size_t avail_out = compressed->size() - (next_out - compressed->data()); + JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; + while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { + process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out); + if (process_result == JXL_ENC_NEED_MORE_OUTPUT) { + size_t offset = next_out - compressed->data(); + compressed->resize(compressed->size() * 2); + next_out = compressed->data() + offset; + avail_out = compressed->size() - offset; + } + } + compressed->resize(next_out - compressed->data()); +#endif