GraphicsMagick: JXL: Support reading and writing ICC, EXIF, and ...
GraphicsMagick Commits <[email protected]>
| Newsgroups | gmane.comp.video.graphicsmagick.cvs |
|---|---|
| Message-ID | <mailman.36046.1671836730.1567.graphicsmagick-commit@lists.sourceforge.net> |
changeset 43529ba35611 in /hg/GraphicsMagick details: http://hg.GraphicsMagick.org/hg/GraphicsMagick?cmd=changeset;node=43529ba35611 summary: JXL: Support reading and writing ICC, EXIF, and XMP profiles. Lots of code cleanup. diffstat: ChangeLog | 6 + PerlMagick/t/jxl/input.jxl | Bin PerlMagick/t/jxl/input_gray_08.jxl | Bin PerlMagick/t/jxl/input_gray_16.jxl | Bin PerlMagick/t/jxl/input_gray_16f.jxl | Bin PerlMagick/t/jxl/input_gray_32f.jxl | Bin PerlMagick/t/jxl/input_truecolor_08.jxl | Bin PerlMagick/t/jxl/input_truecolor_16.jxl | Bin PerlMagick/t/jxl/input_truecolor_16f.jxl | Bin PerlMagick/t/jxl/input_truecolor_32f.jxl | Bin VisualMagick/installer/inc/version.isx | 4 +- coders/jxl.c | 464 +++++++++++++++++++++++------- magick/version.h | 4 +- www/Changelog.html | 14 + www/formats.rst | 1 + 15 files changed, 382 insertions(+), 111 deletions(-) diffs (truncated from 812 to 500 lines): diff -r fada78baed32 -r 43529ba35611 ChangeLog --- a/ChangeLog Fri Dec 23 00:57:51 2022 +0100 +++ b/ChangeLog Fri Dec 23 17:05:17 2022 -0600 @@ -1,3 +1,9 @@ +2022-12-23 Bob Friesenhahn <[email protected]> + + * coders/jxl.c: Support reading and writing ICC, EXIF, and XMP + profiles. Lots of code cleanup. + * PerlMagick/t/jxl: Created some JXL sanity-test files. + 2022-12-22 Fojtik Jaroslav <[email protected]> * VisualMagick/tests/runtest.bat Make binary directory configurable on one place. Report error when rwblob.exe does not exist. diff -r fada78baed32 -r 43529ba35611 PerlMagick/t/jxl/input.jxl Binary file PerlMagick/t/jxl/input.jxl has changed diff -r fada78baed32 -r 43529ba35611 PerlMagick/t/jxl/input_gray_08.jxl Binary file PerlMagick/t/jxl/input_gray_08.jxl has changed diff -r fada78baed32 -r 43529ba35611 PerlMagick/t/jxl/input_gray_16.jxl Binary file PerlMagick/t/jxl/input_gray_16.jxl has changed diff -r fada78baed32 -r 43529ba35611 PerlMagick/t/jxl/input_gray_16f.jxl Binary file PerlMagick/t/jxl/input_gray_16f.jxl has changed diff -r fada78baed32 -r 43529ba35611 PerlMagick/t/jxl/input_gray_32f.jxl Binary file PerlMagick/t/jxl/input_gray_32f.jxl has changed diff -r fada78baed32 -r 43529ba35611 PerlMagick/t/jxl/input_truecolor_08.jxl Binary file PerlMagick/t/jxl/input_truecolor_08.jxl has changed diff -r fada78baed32 -r 43529ba35611 PerlMagick/t/jxl/input_truecolor_16.jxl Binary file PerlMagick/t/jxl/input_truecolor_16.jxl has changed diff -r fada78baed32 -r 43529ba35611 PerlMagick/t/jxl/input_truecolor_16f.jxl Binary file PerlMagick/t/jxl/input_truecolor_16f.jxl has changed diff -r fada78baed32 -r 43529ba35611 PerlMagick/t/jxl/input_truecolor_32f.jxl Binary file PerlMagick/t/jxl/input_truecolor_32f.jxl has changed diff -r fada78baed32 -r 43529ba35611 VisualMagick/installer/inc/version.isx --- a/VisualMagick/installer/inc/version.isx Fri Dec 23 00:57:51 2022 +0100 +++ b/VisualMagick/installer/inc/version.isx Fri Dec 23 17:05:17 2022 -0600 @@ -10,5 +10,5 @@ #define public MagickPackageName "GraphicsMagick" #define public MagickPackageVersion "1.4" -#define public MagickPackageVersionAddendum ".020221221" -#define public MagickPackageReleaseDate "snapshot-20221221" +#define public MagickPackageVersionAddendum ".020221222" +#define public MagickPackageReleaseDate "snapshot-20221222" diff -r fada78baed32 -r 43529ba35611 coders/jxl.c --- a/coders/jxl.c Fri Dec 23 00:57:51 2022 +0100 +++ b/coders/jxl.c Fri Dec 23 17:05:17 2022 -0600 @@ -22,16 +22,26 @@ * Currently tested vs libjxl-0.7.0 on ubuntu only, likely will have build problems * on other platforms. Also note the amount of third-party-libs required! * +* Libjxl requires the full uncompressed image in memory in order to compress, +* so it requires a lot of memory when writing. +* +* Features which work: +* +* * Gray and RGB images +* * 8, and 16 bit integer samples +* * 16 and 32-bit float samples +* * Store/Read ICC, EXIF, and XMP profiles +* * Resource-limited memory allocator +* * Features still to be completed: * -* * Support premultiplied alpha -* * Support Alpha bits != RGB sample bits -* * Support CMYK layers -* * Support progressive -* * Support embedded profiles -* * Support 16-bit float ("Half") format -* * Support progress monitor -* * Use import/export functions (ImportImagePixelArea()/ExportImagePixelArea()) +* * Multiple frames / animations +* * Premultiplied alpha +* * Alpha bits != RGB sample bits +* * CMYK layers +* * Progressive images +* * Progress monitor +* * Linear images (needs improvement) */ #include "magick/studio.h" @@ -52,7 +62,7 @@ #include <jxl/encode.h> #include <jxl/thread_parallel_runner.h> -#define MaxBufferExtent 16384 +#define MaxBufferExtent 65536 struct MyJXLMemoryManager { JxlMemoryManager super; @@ -89,6 +99,74 @@ mm->super.alloc=MyJXLMalloc; mm->super.free=MyJXLFree; } +static const char *JxlDataTypeAsString(const JxlDataType data_type) +{ + const char *str = "Unknown"; + + switch (data_type) + { + case JXL_TYPE_FLOAT: + str = "Float"; + break; + case JXL_TYPE_UINT8: + str = "UINT8"; + break; + case JXL_TYPE_UINT16: + str = "UINT16"; + break; + case JXL_TYPE_FLOAT16: + str = "FLOAT16"; + break; + } + + return str; +} + +static QuantumSampleType JxlDataTypeToQuantumSampleType(const JxlDataType data_type) +{ + QuantumSampleType + sample_type = UndefinedQuantumSampleType; + + switch (data_type) + { + case JXL_TYPE_FLOAT: + sample_type = FloatQuantumSampleType; + break; + case JXL_TYPE_UINT8: + sample_type = UnsignedQuantumSampleType; + break; + case JXL_TYPE_UINT16: + sample_type = UnsignedQuantumSampleType; + break; + case JXL_TYPE_FLOAT16: + sample_type = FloatQuantumSampleType; + break; + } + return sample_type; +} + +static unsigned int JxlDataTypeToQuantumSize(const JxlDataType data_type) +{ + unsigned int + quantum_size = 0; + + switch (data_type) + { + case JXL_TYPE_FLOAT: + quantum_size = 32; + break; + case JXL_TYPE_UINT8: + quantum_size = 8; + break; + case JXL_TYPE_UINT16: + quantum_size = 16; + break; + case JXL_TYPE_FLOAT16: + quantum_size = 16; + break; + } + return quantum_size; +} /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -212,36 +290,16 @@ return str; } -static const char *JxlDataTypeAsString(const JxlDataType data_type) -{ - const char *str = "Unknown"; - - switch (data_type) - { - case JXL_TYPE_FLOAT: - str = "Float"; - break; - case JXL_TYPE_UINT8: - str = "UINT8"; - break; - case JXL_TYPE_UINT16: - str = "UINT16"; - break; - case JXL_TYPE_FLOAT16: - str = "FLOAT16"; - break; - } - - return str; -} #define JXLReadCleanup() \ MagickFreeResourceLimitedMemory(out_buf); \ MagickFreeResourceLimitedMemory(in_buf); \ + MagickFreeResourceLimitedMemory(exif_profile); \ + MagickFreeResourceLimitedMemory(xmp_profile); \ if (jxl_thread_runner) \ JxlThreadParallelRunnerDestroy(jxl_thread_runner); \ - if (jxl) \ - JxlDecoderDestroy(jxl); + if (jxl_decoder) \ + JxlDecoderDestroy(jxl_decoder); #define ThrowJXLReaderException(code_,reason_,image_) \ @@ -257,7 +315,7 @@ *image; JxlDecoder - *jxl = NULL; + *jxl_decoder = NULL; void *jxl_thread_runner = NULL; @@ -266,7 +324,7 @@ status; JxlPixelFormat - format; + pixel_format; struct MyJXLMemoryManager mm; @@ -287,12 +345,21 @@ magick_off_t blob_len = 0; + unsigned char + *exif_profile = NULL, + *xmp_profile = NULL; + + size_t + exif_size = 0, + exif_pad = 2, + xmp_size = 0; + assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickSignature); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); - memset(&format,0,sizeof(format)); + memset(&pixel_format,0,sizeof(pixel_format)); /* Open image file. @@ -306,12 +373,12 @@ /* Init JXL-Decoder handles */ MyJxlMemoryManagerInit(&mm,image,exception); - jxl=JxlDecoderCreate(&mm.super); - if (jxl == (JxlDecoder *) NULL) + jxl_decoder=JxlDecoderCreate(&mm.super); + if (jxl_decoder == (JxlDecoder *) NULL) ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image); /* Deliver image as-is. We provide autoOrient function if user requires it */ - if (JxlDecoderSetKeepOrientation(jxl, JXL_TRUE) != JXL_DEC_SUCCESS) + if (JxlDecoderSetKeepOrientation(jxl_decoder, JXL_TRUE) != JXL_DEC_SUCCESS) ThrowJXLReaderException(ResourceLimitError,MemoryAllocationFailed,image); if(!image_info->ping) @@ -319,17 +386,19 @@ jxl_thread_runner=JxlThreadParallelRunnerCreate(NULL,(size_t) GetMagickResourceLimit(ThreadsResource)); if (jxl_thread_runner == (void *) NULL) ThrowJXLReaderException(ResourceLimitError,MemoryAllocationFailed,image); - if (JxlDecoderSetParallelRunner(jxl, JxlThreadParallelRunner, jxl_thread_runner) + if (JxlDecoderSetParallelRunner(jxl_decoder, JxlThreadParallelRunner, jxl_thread_runner) != JXL_DEC_SUCCESS) ThrowJXLReaderException(ResourceLimitError,MemoryAllocationFailed,image); } - if (JxlDecoderSubscribeEvents(jxl, + if (JxlDecoderSubscribeEvents(jxl_decoder, (JxlDecoderStatus)(image_info->ping == MagickTrue - ? JXL_DEC_BASIC_INFO - : JXL_DEC_BASIC_INFO | - JXL_DEC_FULL_IMAGE | - JXL_DEC_COLOR_ENCODING) + ? (JXL_DEC_BASIC_INFO | + JXL_DEC_BOX) + : (JXL_DEC_BASIC_INFO | + JXL_DEC_FULL_IMAGE | + JXL_DEC_COLOR_ENCODING | + JXL_DEC_BOX)) ) != JXL_DEC_SUCCESS) ThrowJXLReaderException(ResourceLimitError,MemoryAllocationFailed,image); @@ -347,7 +416,7 @@ case JXL_DEC_NEED_MORE_INPUT: { /* read something from blob */ size_t - remaining = JxlDecoderReleaseInput(jxl), + remaining = JxlDecoderReleaseInput(jxl_decoder), count; if (remaining > 0) @@ -355,14 +424,14 @@ count=ReadBlob(image,in_len-remaining,in_buf+remaining); if (count == 0) ThrowJXLReaderException(CorruptImageError, UnexpectedEndOfFile, image); - status = JxlDecoderSetInput(jxl,(const uint8_t *) in_buf, (size_t) count); + status = JxlDecoderSetInput(jxl_decoder,(const uint8_t *) in_buf, (size_t) count); if (blob_len > 0) { /* If file size is known pass the info about the last block, to the decoder. Note that the call is currently optional */ blob_len -= count; if (blob_len == 0) - JxlDecoderCloseInput(jxl); + JxlDecoderCloseInput(jxl_decoder); } break; } @@ -373,7 +442,7 @@ JxlEncoderInitBasicInfo(&basic_info); - status=JxlDecoderGetBasicInfo(jxl,&basic_info); + status=JxlDecoderGetBasicInfo(jxl_decoder,&basic_info); if (status != JXL_DEC_SUCCESS) break; @@ -403,8 +472,8 @@ image->orientation=convert_orientation(basic_info.orientation); - format.endianness=JXL_NATIVE_ENDIAN; - format.align=0; + pixel_format.endianness=JXL_NATIVE_ENDIAN; + pixel_format.align=0; if (basic_info.num_color_channels == 1) { if ((basic_info.bits_per_sample <= 8) && (!image->matte)) @@ -420,15 +489,15 @@ ThrowJXLReaderException(ResourceLimitError,MemoryAllocationFailed,image); } grayscale=MagickTrue; - format.num_channels=1; - format.data_type=(basic_info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : + pixel_format.num_channels=1; + pixel_format.data_type=(basic_info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : (basic_info.bits_per_sample <= 16 ? JXL_TYPE_UINT16 : JXL_TYPE_FLOAT)); } else if (basic_info.num_color_channels == 3) { - format.num_channels=image->matte ? 4 : 3; - format.data_type=(basic_info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : + pixel_format.num_channels=image->matte ? 4 : 3; + pixel_format.data_type=(basic_info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : (basic_info.bits_per_sample <= 16 ? JXL_TYPE_UINT16 : JXL_TYPE_FLOAT)); } @@ -455,7 +524,7 @@ JxlColorEncoding color_encoding; - status=JxlDecoderGetColorAsEncodedProfile(jxl,&format, + status=JxlDecoderGetColorAsEncodedProfile(jxl_decoder,&pixel_format, JXL_COLOR_PROFILE_TARGET_DATA,&color_encoding); if (status == JXL_DEC_ERROR) { @@ -469,6 +538,9 @@ } else if (status == JXL_DEC_SUCCESS) { + /* + Transfer function if have_gamma is 0 + */ (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Color Transfer Function: %s", JxlTransferFunctionAsString(color_encoding.transfer_function)); @@ -506,6 +578,9 @@ "Color Space: %s", JxlColorSpaceAsString(color_encoding.color_space)); + /* + Color space of the image data. + */ switch (color_encoding.color_space) { case JXL_COLOR_SPACE_RGB: if (color_encoding.white_point == JXL_WHITE_POINT_D65 && @@ -544,7 +619,34 @@ break; } } - /*TODO: get ICC-profile and keep as metadata?*/ + /* + Get original ICC-profile and store as metadata + */ + { + size_t + profile_size; + + if (JxlDecoderGetICCProfileSize(jxl_decoder,&pixel_format, + JXL_COLOR_PROFILE_TARGET_ORIGINAL,&profile_size) + == JXL_DEC_SUCCESS) + { + unsigned char + *profile; + + if ((profile=MagickAllocateResourceLimitedMemory(unsigned char *,profile_size)) + != NULL) + { + if (JxlDecoderGetColorAsICCProfile(jxl_decoder,&pixel_format, + JXL_COLOR_PROFILE_TARGET_ORIGINAL, + profile, + profile_size) == JXL_DEC_SUCCESS) + { + (void) SetImageProfile(image,"ICM",profile,profile_size); + } + MagickFreeResourceLimitedMemory(profile); + } + } + } break; } case JXL_DEC_NEED_IMAGE_OUT_BUFFER: @@ -552,7 +654,7 @@ size_t out_len; - status=JxlDecoderImageOutBufferSize(jxl,&format,&out_len); + status=JxlDecoderImageOutBufferSize(jxl_decoder,&pixel_format,&out_len); if (status != JXL_DEC_SUCCESS) break; @@ -560,7 +662,7 @@ if (out_buf == (unsigned char *) NULL) ThrowJXLReaderException(ResourceLimitError,MemoryAllocationFailed,image); - status=JxlDecoderSetImageOutBuffer(jxl,&format,out_buf,out_len); + status=JxlDecoderSetImageOutBuffer(jxl_decoder,&pixel_format,out_buf,out_len); break; } case JXL_DEC_FULL_IMAGE: @@ -588,33 +690,15 @@ quantum_type; QuantumSampleType - sample_type = UndefinedQuantumSampleType; + sample_type; MagickPassFail res=MagickPass; assert(out_buf != (unsigned char *)NULL); - quantum_size = 0; - switch (format.data_type) - { - case JXL_TYPE_FLOAT: - quantum_size = 32; - sample_type = FloatQuantumSampleType; - break; - case JXL_TYPE_UINT8: - quantum_size = 8; - sample_type = UnsignedQuantumSampleType; - break; - case JXL_TYPE_UINT16: - quantum_size = 16; - sample_type = UnsignedQuantumSampleType; - break; - case JXL_TYPE_FLOAT16: - quantum_size = 16; - sample_type = FloatQuantumSampleType; - break; - } + quantum_size = JxlDataTypeToQuantumSize(pixel_format.data_type); + sample_type = JxlDataTypeToQuantumSampleType(pixel_format.data_type); if (grayscale) { @@ -692,6 +776,73 @@ status=JXL_DEC_ERROR; break; } + case JXL_DEC_BOX: + { + do + { + JxlBoxType + type; + + magick_uint64_t + profile_size = 0; + + unsigned char + *profile; + + /* Release buffer to get box data */ + (void) JxlDecoderReleaseBoxBuffer(jxl_decoder); + + /* Get the 4-character box typename */ + if (JxlDecoderGetBoxType(jxl_decoder,type,JXL_FALSE) != JXL_DEC_SUCCESS) + break; + + /* Get the size of the box as it appears in the container file, not decompressed. */ + if (JxlDecoderGetBoxSizeRaw(jxl_decoder, &profile_size) != JXL_DEC_SUCCESS)