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)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.