summaryrefslogtreecommitdiff
path: root/coders/jxl.c
diff options
context:
space:
mode:
Diffstat (limited to 'coders/jxl.c')
-rw-r--r--coders/jxl.c1099
1 files changed, 792 insertions, 307 deletions
diff --git a/coders/jxl.c b/coders/jxl.c
index 9967723..a785414 100644
--- a/coders/jxl.c
+++ b/coders/jxl.c
@@ -1,5 +1,5 @@
/*
-% Copyright (C) 2022 GraphicsMagick Group
+% Copyright (C) 2023 GraphicsMagick Group
%
% This program is covered by multiple licenses, which are described in
% Copyright.txt. You should have received a copy of Copyright.txt with this
@@ -19,8 +19,29 @@
% Status: Only support basic images (no animations) with grayscale/SRGB colorspace
* Note that JXL is a C++ library so does require linking with a c++ compiler.
*
-* Currently tested vs libjxl-0.6.1 on ubuntu only, likely will have build problems
+* 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:
+*
+* * Multiple frames / animations
+* * Premultiplied alpha
+* * Alpha bits != RGB sample bits
+* * CMYK layers
+* * Progressive images
+* * Progress monitor
+* * Linear images (needs improvement)
*/
#include "magick/studio.h"
@@ -41,7 +62,7 @@
#include <jxl/encode.h>
#include <jxl/thread_parallel_runner.h>
-#define MaxBufferExtent 16384
+#define MaxBufferExtent 65536
struct MyJXLMemoryManager {
JxlMemoryManager super;
@@ -78,6 +99,137 @@ static void MyJxlMemoryManagerInit(struct MyJXLMemoryManager *mm,
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;
+}
+
+static const char *JxlExtraChannelTypeAsString(const JxlExtraChannelType extra_channel_type)
+{
+ const char *str = "Unknown";
+
+ /* Defined in jxl/codestream_header.h */
+ switch (extra_channel_type)
+ {
+ case JXL_CHANNEL_ALPHA:
+ str = "Alpha";
+ break;
+ case JXL_CHANNEL_DEPTH:
+ str = "Depth";
+ break;
+ case JXL_CHANNEL_SPOT_COLOR:
+ str = "SpotColor";
+ break;
+ case JXL_CHANNEL_SELECTION_MASK:
+ str = "SelectionMask";
+ break;
+ case JXL_CHANNEL_BLACK:
+ str = "Black";
+ break;
+ case JXL_CHANNEL_CFA:
+ str = "CFA";
+ break;
+ case JXL_CHANNEL_THERMAL:
+ str = "Thermal";
+ break;
+ case JXL_CHANNEL_RESERVED0:
+ str = "RESERVED0";
+ break;
+ case JXL_CHANNEL_RESERVED1:
+ str = "RESERVED1";
+ break;
+ case JXL_CHANNEL_RESERVED2:
+ str = "RESERVED2";
+ break;
+ case JXL_CHANNEL_RESERVED3:
+ str = "RESERVED3";
+ break;
+ case JXL_CHANNEL_RESERVED4:
+ str = "RESERVED4";
+ break;
+ case JXL_CHANNEL_RESERVED5:
+ str = "RESERVED5";
+ break;
+ case JXL_CHANNEL_RESERVED6:
+ str = "RESERVED6";
+ break;
+ case JXL_CHANNEL_RESERVED7:
+ str = "RESERVED7";
+ break;
+ case JXL_CHANNEL_UNKNOWN:
+ str = "Unknown";
+ break;
+ case JXL_CHANNEL_OPTIONAL:
+ str = "Optional";
+ break;
+ }
+
+ return str;
+}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -128,224 +280,19 @@ static inline OrientationType convert_orientation(JxlOrientation orientation)
}
}
-#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++)
-
-#define END_FOR_PIXEL_PACKETS \
- if (!SyncImagePixels(image)) \
- return MagickFail; \
- } \
-
-static MagickBool fill_pixels_char(Image *image,
- ExceptionInfo *exception,
- unsigned char *p)
-{
- long
- x,
- y;
-
- PixelPacket
- *q;
-
- if (image->matte) {
- FOR_PIXEL_PACKETS
- {
- SetRedSample(q,ScaleCharToQuantum(*p)); p++;
- SetGreenSample(q,ScaleCharToQuantum(*p)); p++;
- SetBlueSample(q,ScaleCharToQuantum(*p)); p++;
- SetOpacitySample(q,MaxRGB-ScaleCharToQuantum(*p)); p++;
- q++;
- }
- END_FOR_PIXEL_PACKETS
- } else {
- FOR_PIXEL_PACKETS
- {
- SetRedSample(q,ScaleCharToQuantum(*p)); p++;
- SetGreenSample(q,ScaleCharToQuantum(*p)); p++;
- SetBlueSample(q,ScaleCharToQuantum(*p)); p++;
- SetOpacitySample(q,OpaqueOpacity);
- q++;
- }
- END_FOR_PIXEL_PACKETS
- }
-
- return MagickTrue;
-}
-
-static MagickBool fill_pixels_float(Image *image,
- ExceptionInfo *exception,
- float *p)
-{
- long
- x,
- y;
-
- PixelPacket
- *q;
-
- if (image->matte) {
- FOR_PIXEL_PACKETS
- {
- SetRedSample(q,RoundFloatToQuantum(*p * MaxRGBFloat)); p++;
- SetGreenSample(q,RoundFloatToQuantum(*p * MaxRGBFloat)); p++;
- SetBlueSample(q,RoundFloatToQuantum(*p * MaxRGBFloat)); p++;
- SetOpacitySample(q,MaxRGB-RoundFloatToQuantum(*p * MaxRGBFloat)); p++;
- q++;
- }
- END_FOR_PIXEL_PACKETS
- } else {
- FOR_PIXEL_PACKETS
- {
- SetRedSample(q,RoundFloatToQuantum(*p * MaxRGBFloat)); p++;
- SetGreenSample(q,RoundFloatToQuantum(*p * MaxRGBFloat)); p++;
- SetBlueSample(q,RoundFloatToQuantum(*p * MaxRGBFloat)); p++;
- SetOpacitySample(q,OpaqueOpacity);
- q++;
- }
- END_FOR_PIXEL_PACKETS
- }
-
- return MagickTrue;
-}
-
-static MagickBool fill_pixels_char_grayscale(Image *image, ExceptionInfo *exception,
- unsigned char *p)
-{
- long
- x,
- y;
-
- PixelPacket
- *q;
-
- IndexPacket
- index;
-
- for (y=0; y < (long)image->rows; y++)
- {
- register IndexPacket
- *indexes;
-
- q=SetImagePixelsEx(image,0,y,image->columns,1,exception);
- if (q == (PixelPacket *) NULL)
- return MagickFail;
-
- indexes=AccessMutableIndexes(image);
- if (indexes == NULL)
- return MagickFail;
-
- for (x=0; x < (long)image->columns; x++) {
- index=(IndexPacket)(*p++);
- VerifyColormapIndex(image,index);
- indexes[x]=index;
- *q++=image->colormap[index];
- }
- if (!SyncImagePixels(image))
- return MagickFail;
- }
- return MagickTrue;
-}
-
/** Convert any linear RGB to SRGB
* Formula from wikipedia:
* https://en.wikipedia.org/wiki/SRGB
*/
-static Quantum linear2nonlinear(float p)
-{
- if (p < 0.0031308) {
- p=p * 12.92;
- } else {
- p=1.055 * powf(p, 1.0/2.4) - 0.055;
- }
- return RoundFloatToQuantum(p * MaxRGBFloat);
-}
-
-static MagickBool fill_pixels_float_linear(Image *image,
- ExceptionInfo *exception,
- float *p)
-{
- long
- x,
- y;
-
- PixelPacket
- *q;
-
- if (image->matte) {
- FOR_PIXEL_PACKETS
- {
- SetRedSample(q,linear2nonlinear(*p++));
- SetGreenSample(q,linear2nonlinear(*p++));
- SetBlueSample(q,linear2nonlinear(*p++));
- SetOpacitySample(q,MaxRGB-linear2nonlinear(*p++));
- q++;
- }
- END_FOR_PIXEL_PACKETS
- } else {
- FOR_PIXEL_PACKETS
- {
- SetRedSample(q,linear2nonlinear(*p++));
- SetGreenSample(q,linear2nonlinear(*p++));
- SetBlueSample(q,linear2nonlinear(*p++));
- SetOpacitySample(q,OpaqueOpacity);
- q++;
- }
- END_FOR_PIXEL_PACKETS
- }
-
- return MagickTrue;
-}
-
-static Quantum linear2nonlinear_char(unsigned char c)
+static void linear2nonlinear_quantum(Quantum *q)
{
- float p = c * (1.0f/256.0f);
+ double p = *q * (1.0/256.0);
if (p < 0.0031308) {
p=p * 12.92;
} else {
- p=1.055 * powf(p, 1.0/2.4) - 0.055;
+ p=1.055 * pow(p, 1.0/2.4) - 0.055;
}
- return RoundFloatToQuantum(p * MaxRGBFloat);
-}
-
-static MagickBool fill_pixels_char_linear(Image *image,
- ExceptionInfo *exception,
- unsigned char *p)
-{
- long
- x,
- y;
-
- PixelPacket
- *q;
-
- if (image->matte) {
- FOR_PIXEL_PACKETS
- {
- SetRedSample(q,linear2nonlinear_char(*p++));
- SetGreenSample(q,linear2nonlinear_char(*p++));
- SetBlueSample(q,linear2nonlinear_char(*p++));
- SetOpacitySample(q,MaxRGB-linear2nonlinear_char(*p++));
- q++;
- }
- END_FOR_PIXEL_PACKETS
- } else {
- FOR_PIXEL_PACKETS
- {
- SetRedSample(q,linear2nonlinear_char(*p++));
- SetGreenSample(q,linear2nonlinear_char(*p++));
- SetBlueSample(q,linear2nonlinear_char(*p++));
- SetOpacitySample(q,OpaqueOpacity);
- q++;
- }
- END_FOR_PIXEL_PACKETS
- }
-
- return MagickTrue;
+ *q = RoundDoubleToQuantum(p * MaxRGBDouble);
}
static const char *JxlTransferFunctionAsString(const JxlTransferFunction fn)
@@ -383,13 +330,39 @@ static const char *JxlTransferFunctionAsString(const JxlTransferFunction fn)
return str;
}
+static const char *JxlColorSpaceAsString(const JxlColorSpace color_space)
+{
+ const char *str = "Unknown";
+
+ switch (color_space)
+ {
+ case JXL_COLOR_SPACE_RGB:
+ str = "Tristimulus RGB";
+ break;
+ case JXL_COLOR_SPACE_GRAY:
+ str = "Luminance based (Gray)";
+ break;
+ case JXL_COLOR_SPACE_XYB:
+ str = "XYB (opsin)";
+ break;
+ case JXL_COLOR_SPACE_UNKNOWN:
+ str = "Unknown";
+ 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_) \
@@ -405,7 +378,7 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
*image;
JxlDecoder
- *jxl = NULL;
+ *jxl_decoder = NULL;
void
*jxl_thread_runner = NULL;
@@ -414,7 +387,10 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
status;
JxlPixelFormat
- format;
+ pixel_format;
+
+ JxlExtraChannelInfo
+ extra_channel_info[5];
struct MyJXLMemoryManager
mm;
@@ -435,12 +411,21 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
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.
@@ -454,30 +439,35 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
/* 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);
+ /* Apply any pre-multiplied alpha for us so we don't need to do it. */
+ (void) JxlDecoderSetUnpremultiplyAlpha(jxl_decoder, JXL_TRUE);
+
if(!image_info->ping)
{
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);
@@ -495,7 +485,7 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
case JXL_DEC_NEED_MORE_INPUT:
{ /* read something from blob */
size_t
- remaining = JxlDecoderReleaseInput(jxl),
+ remaining = JxlDecoderReleaseInput(jxl_decoder),
count;
if (remaining > 0)
@@ -503,14 +493,14 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
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;
}
@@ -519,12 +509,9 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
JxlBasicInfo
basic_info;
- unsigned long
- max_value_given_bits;
-
JxlEncoderInitBasicInfo(&basic_info);
- status=JxlDecoderGetBasicInfo(jxl,&basic_info);
+ status=JxlDecoderGetBasicInfo(jxl_decoder,&basic_info);
if (status != JXL_DEC_SUCCESS)
break;
@@ -537,10 +524,48 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
" bits_per_sample=%u\n"
" exponent_bits_per_sample=%u\n"
" alpha_bits=%u\n"
- " num_color_channels=%u",
+ " num_color_channels=%u\n"
+ " have_animation=%s",
basic_info.xsize, basic_info.ysize,
basic_info.bits_per_sample, basic_info.exponent_bits_per_sample,
- basic_info.alpha_bits, basic_info.num_color_channels);
+ basic_info.alpha_bits, basic_info.num_color_channels,
+ basic_info.have_animation == JXL_FALSE ? "False" : "True");
+ }
+ if (basic_info.num_extra_channels)
+ {
+ size_t index;
+ (void) memset(extra_channel_info,0,sizeof(extra_channel_info));
+ for (index = 0 ;index < Min(basic_info.num_extra_channels,
+ ArraySize(extra_channel_info));
+ index++)
+ {
+ JxlExtraChannelInfo* ecip=&extra_channel_info[index];
+ status=JxlDecoderGetExtraChannelInfo(jxl_decoder,
+ index,
+ ecip);
+ if (JXL_DEC_SUCCESS == status)
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "Extra Channel Info[%lu]:\n"
+ " type=%s\n"
+ " bits_per_sample=%u\n"
+ " exponent_bits_per_sample=%u\n"
+ " dim_shift=%u\n"
+ " name_length=%u\n"
+ " alpha_premultiplied=%s\n"
+ " spot_color=%f,%f,%f,%f\n"
+ " cfa_channel=%u"
+ ,
+ (unsigned long) index,
+ JxlExtraChannelTypeAsString(ecip->type),
+ ecip->bits_per_sample,
+ ecip->exponent_bits_per_sample,
+ ecip->dim_shift,
+ ecip->name_length,
+ ecip->alpha_premultiplied == JXL_FALSE ? "False" : "True",
+ ecip->spot_color[0],ecip->spot_color[1],
+ ecip->spot_color[2],ecip->spot_color[3],
+ ecip->cfa_channel);
+ }
}
if (basic_info.have_animation == 1)
@@ -553,35 +578,40 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
image->matte=MagickTrue;
image->orientation=convert_orientation(basic_info.orientation);
- max_value_given_bits=MaxValueGivenBits(basic_info.bits_per_sample);
- (void) LogMagickEvent(CoderEvent,GetMagickModule(),
- "max_value_given_bits=%lu",max_value_given_bits);
- if ((basic_info.num_color_channels == 1) && (max_value_given_bits < MaxColormapSize))
+ pixel_format.endianness=JXL_NATIVE_ENDIAN;
+ pixel_format.align=0;
+ if (basic_info.num_color_channels == 1)
{
- if (!AllocateImageColormap(image,max_value_given_bits+1))
- ThrowJXLReaderException(ResourceLimitError,MemoryAllocationFailed,image);
+ if ((basic_info.bits_per_sample <= 8) && (!image->matte))
+ {
+ unsigned long
+ max_value_given_bits;
+
+ max_value_given_bits=MaxValueGivenBits(basic_info.bits_per_sample);
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "max_value_given_bits=%lu",max_value_given_bits);
+
+ if (!AllocateImageColormap(image,max_value_given_bits+1))
+ ThrowJXLReaderException(ResourceLimitError,MemoryAllocationFailed,image);
+ }
grayscale=MagickTrue;
- format.num_channels=1;
- format.data_type=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)
+ else if (basic_info.num_color_channels == 3)
{
- ThrowJXLReaderException(CoderError, ImageTypeNotSupported, image);
+ 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));
}
else
{
- /* use encoder suggested pixel format if possible */
- if ((JxlDecoderDefaultPixelFormat(jxl, &format) != JXL_DEC_SUCCESS)
- || (format.data_type != JXL_TYPE_FLOAT && JXL_TYPE_FLOAT != JXL_TYPE_UINT8))
- {
- format.data_type=(image->depth > 8) ? JXL_TYPE_FLOAT : JXL_TYPE_UINT8;
- }
- format.endianness=JXL_NATIVE_ENDIAN;
- format.num_channels=image->matte ? 4 : 3;
- format.align=0;
+ ThrowJXLReaderException(CoderError, ImageTypeNotSupported, image);
}
-
break;
}
@@ -601,8 +631,12 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
JxlColorEncoding
color_encoding;
- status=JxlDecoderGetColorAsEncodedProfile(jxl,&format,
- JXL_COLOR_PROFILE_TARGET_DATA,&color_encoding);
+ status=JxlDecoderGetColorAsEncodedProfile(jxl_decoder,
+#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
+ &pixel_format,
+#endif /* if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0) */
+ JXL_COLOR_PROFILE_TARGET_DATA,
+ &color_encoding);
if (status == JXL_DEC_ERROR)
{
status=JXL_DEC_SUCCESS;
@@ -615,6 +649,9 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
}
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));
@@ -648,6 +685,13 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
ThrowJXLReaderException(CoderError, ImageTypeNotSupported, image);
}
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "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 &&
@@ -671,7 +715,7 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
}
break;
case JXL_COLOR_SPACE_GRAY:
- if(!grayscale || isLinear) /* FIXME: Can't read linear gray */
+ if (!grayscale)
ThrowJXLReaderException(CoderError, ImageTypeNotSupported, image);
break;
case JXL_COLOR_SPACE_XYB:
@@ -686,7 +730,42 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
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,
+#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
+ &pixel_format,
+#endif /* if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0) */
+ 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,
+#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
+ &pixel_format,
+#endif /* if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0) */
+ 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:
@@ -694,7 +773,7 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
size_t
out_len;
- status=JxlDecoderImageOutBufferSize(jxl,&format,&out_len);
+ status=JxlDecoderImageOutBufferSize(jxl_decoder,&pixel_format,&out_len);
if (status != JXL_DEC_SUCCESS)
break;
@@ -702,41 +781,190 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
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:
{ /* got image */
- MagickBool
- res=MagickFail;
+ long
+ x,
+ y;
+
+ PixelPacket
+ *q;
+
+ unsigned char
+ *p;
+
+ ImportPixelAreaOptions
+ import_options;
+
+ ImportPixelAreaInfo
+ import_area_info;
+
+ unsigned int
+ quantum_size;
+
+ QuantumType
+ quantum_type;
+
+ QuantumSampleType
+ sample_type;
+
+ MagickPassFail
+ res=MagickPass;
assert(out_buf != (unsigned char *)NULL);
- if (!grayscale)
+
+ quantum_size = JxlDataTypeToQuantumSize(pixel_format.data_type);
+ sample_type = JxlDataTypeToQuantumSampleType(pixel_format.data_type);
+
+ if (grayscale)
+ {
+ if (image->matte)
+ quantum_type = GrayAlphaQuantum;
+ else
+ quantum_type = GrayQuantum;
+ }
+ #if 0
+ else if (cmyk)
+ {
+ if (image->matte)
+ quantum_type = CMYKAQuantum;
+ else
+ quantum_type = CMYKQuantum;
+ }
+ #endif
+ else
+ {
+ if (image->matte)
+ quantum_type = RGBAQuantum;
+ else
+ quantum_type = RGBQuantum;
+ }
+
+ ImportPixelAreaOptionsInit(&import_options);
+ import_options.sample_type = sample_type;
+ import_options.endian = NativeEndian;
+
+ p = out_buf;
+ for (y=0; y < (long) image->rows; y++)
{
- if (format.data_type == JXL_TYPE_UINT8)
+ q=SetImagePixelsEx(image,0,y,image->columns,1,exception);
+ if (q == (PixelPacket *) NULL)
{
- if (isLinear)
- res=fill_pixels_char_linear(image, exception, out_buf);
- else
- res=fill_pixels_char(image, exception, out_buf);
+ res = MagickFail;
+ break;
}
- else
+
+ if ((res = ImportImagePixelArea(image,quantum_type,quantum_size,
+ p,
+ &import_options,&import_area_info))
+ != MagickPass)
+ break;
+ // Promote linear image to sRGB (2.4 gamma).
+ // We could also set image->gamma and return the original image.
+#if 1
+ if (isLinear)
{
- if (isLinear)
- res=fill_pixels_float_linear(image, exception, (float*)out_buf);
- else
- res=fill_pixels_float(image, exception, (float*)out_buf);
+ for (x = 0 ; x < (long) image->columns ; x++)
+ {
+ linear2nonlinear_quantum(&q[x].red);
+ if (grayscale)
+ {
+ q[x].green = q[x].blue = q[x].red;
+ }
+ else
+ {
+ linear2nonlinear_quantum(&q[x].green);
+ linear2nonlinear_quantum(&q[x].blue);
+ }
+ }
+ }
+#endif
+ p += import_area_info.bytes_imported;
+
+ if (!SyncImagePixels(image))
+ {
+ res = MagickFail;
+ break;
}
- }
- else if (format.data_type == JXL_TYPE_UINT8)
- {
- res=fill_pixels_char_grayscale(image, exception, out_buf);
}
if (!res)
status=JXL_DEC_ERROR;
break;
}
+ case JXL_DEC_BOX:
+ {
+ do
+ {
+ JxlBoxType
+ type; /* A 4 character string which is not null terminated! */
+
+ 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)
+ break;
+
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "JXL Box of type \"%c%c%c%c\" and %lu bytes",
+ type[0],type[1],type[2],type[3], (unsigned long) profile_size);
+
+ /* Ignore tiny profiles */
+ if (profile_size < 12)
+ break;
+
+ /* Discard raw box size and type bytes */
+ profile_size -= 8;
+
+ if (LocaleNCompare(type,"Exif",sizeof(type)) == 0)
+ {
+ /*
+ Allocate EXIF profile box buffer (plus a bit more)
+ */
+ if ((profile=MagickAllocateResourceLimitedClearedMemory(unsigned char *,
+ profile_size+exif_pad))
+ != NULL)
+ {
+ if (JxlDecoderSetBoxBuffer(jxl_decoder,profile+exif_pad,profile_size)
+ == JXL_DEC_SUCCESS)
+ {
+ exif_profile=profile;
+ exif_size=profile_size;
+ }
+ }
+ }
+ if (LocaleNCompare(type,"xml ",sizeof(type)) == 0)
+ {
+ /*
+ Allocate XMP profile box buffer
+ */
+ if ((profile=MagickAllocateResourceLimitedMemory(unsigned char *,profile_size))
+ != NULL)
+ {
+ if (JxlDecoderSetBoxBuffer(jxl_decoder,profile,profile_size) == JXL_DEC_SUCCESS)
+ {
+ xmp_profile=profile;
+ xmp_size=profile_size;
+ }
+ }
+ }
+ } while(0);
+ break;
+ }
default:
/* unexpected status is error.
* - JXL_DEC_SUCCESS should never happen here so it's also an error
@@ -746,7 +974,84 @@ static Image *ReadJXLImage(const ImageInfo *image_info,
}
if (status == JXL_DEC_ERROR)
break;
- status = JxlDecoderProcessInput(jxl);
+ status = JxlDecoderProcessInput(jxl_decoder);
+ }
+ /* Release buffer to get box data in the buffers which were passed */
+ (void) JxlDecoderReleaseBoxBuffer(jxl_decoder);
+ if (exif_profile != NULL)
+ {
+ /*
+ Read Exif profile
+
+ The EXIF box starts with a 4-byte offset to
+ the TIFF header (and may be 0).
+
+ The EXIF profile blob needs to be prefixed
+ with "Exif\0\0" prior to the TIFF header.
+
+ The buffer provided to libjxl is offset to allow adding our
+ header.
+ */
+ unsigned char *p = exif_profile;
+ magick_uint32_t exif_profile_offset;
+
+ /* Big-endian offset decoding */
+ exif_profile_offset = p[exif_pad+0] << 24 |
+ p[exif_pad+1] << 16 |
+ p[exif_pad+2] << 8 |
+ p[exif_pad+3];
+
+#if 0
+ fprintf(stderr,
+ "BOX-1: %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x\n",
+ p[0], p[1],p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11]);
+#endif
+
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "EXIF Box: Size %lu, Offset %u",
+ (unsigned long) exif_size, exif_profile_offset);
+
+ /*
+ If the TIFF header offset is not zero, then need to
+ move the TIFF data forward to the correct offset.
+ */
+ exif_size -= 4;
+ if (exif_profile_offset > 0 && exif_profile_offset < exif_size)
+ {
+ exif_size -= exif_profile_offset;
+
+ /* Strip any EOI marker if payload starts with a JPEG marker */
+ if (exif_size > 2 &&
+ (memcmp(p+exif_pad+4,"\xff\xd8",2) == 0 ||
+ memcmp(p+exif_pad+4,"\xff\xe1",2) == 0) &&
+ memcmp(p+exif_pad+4+exif_size-2,"\xff\xd9",2) == 0)
+ exif_size -= 2;
+
+ (void) memmove(p+exif_pad+4,p+exif_pad+4+exif_profile_offset,exif_size);
+ }
+
+ p[0]='E';
+ p[1]='x';
+ p[2]='i';
+ p[3]='f';
+ p[4]='\0';
+ p[5]='\0';
+
+#if 0
+ fprintf(stderr,
+ "BOX-2: %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x\n",
+ p[0], p[1],p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11]);
+#endif
+ (void) SetImageProfile(image,"EXIF",exif_profile,exif_size+exif_pad+4);
+
+ MagickFreeResourceLimitedMemory(exif_profile);
+ }
+ if (xmp_profile != NULL)
+ {
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "XMP Box: Size %lu", (unsigned long) exif_size);
+ (void) SetImageProfile(image,"XMP",xmp_profile,xmp_size);
+ MagickFreeResourceLimitedMemory(xmp_profile);
}
/* every break outside of success is some kind of error */
if (status != JXL_DEC_SUCCESS) {
@@ -796,7 +1101,6 @@ do { \
ThrowWriterException(code_,reason_,image_); \
} while(1)
-
static unsigned int WriteJXLImage(const ImageInfo *image_info,Image *image)
{
MagickPassFail
@@ -885,6 +1189,7 @@ static unsigned int WriteJXLImage(const ImageInfo *image_info,Image *image)
if (jxl_encoder == (JxlEncoder *) NULL)
ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image);
+ /* Use the same number of threads as used for OpenMP */
jxl_thread_runner=
JxlThreadParallelRunnerCreate(NULL,
(size_t) GetMagickResourceLimit(ThreadsResource));
@@ -894,6 +1199,7 @@ static unsigned int WriteJXLImage(const ImageInfo *image_info,Image *image)
!= JXL_ENC_SUCCESS)
ThrowJXLWriterException(ResourceLimitError,MemoryAllocationFailed,image);
+ /* Use one color channel for grayscale image */
if (characteristics.grayscale)
pixel_format.num_channels = 1;
else
@@ -901,15 +1207,23 @@ static unsigned int WriteJXLImage(const ImageInfo *image_info,Image *image)
image->storage_class=DirectClass;
pixel_format.num_channels = characteristics.opaque ? 3 : 4;
}
+
+ /* Support writing integer depths 8, 16, and 32 */
+ /* FIXME: Provide an option to write JXL_TYPE_FLOAT16 or really any
+ desired type here */
if (image->depth <= 8)
pixel_format.data_type = JXL_TYPE_UINT8;
else if (image->depth <= 16)
- pixel_format.data_type = JXL_TYPE_UINT16;
+ pixel_format.data_type = JXL_TYPE_UINT16; /* or JXL_TYPE_FLOAT16 JXL_TYPE_UINT16 */
else if (image->depth <= 32)
- pixel_format.data_type = JXL_TYPE_UINT16; /* JXL_TYPE_UINT32; */
+ pixel_format.data_type = JXL_TYPE_FLOAT; /* or JXL_TYPE_UINT16 */
else
ThrowJXLWriterException(CoderError,ColorspaceModelIsNotSupported,image);
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "Using JXL '%s' data type",
+ JxlDataTypeAsString(pixel_format.data_type));
+
/* Initialize JxlBasicInfo struct to default values. */
JxlEncoderInitBasicInfo(&basic_info);
/* Width of the image in pixels, before applying orientation. */
@@ -919,33 +1233,80 @@ static unsigned int WriteJXLImage(const ImageInfo *image_info,Image *image)
/* 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;
+ switch (pixel_format.data_type)
+ {
+ case JXL_TYPE_FLOAT:
+ basic_info.bits_per_sample = 32;
+ break;
+ case JXL_TYPE_UINT8:
+ basic_info.bits_per_sample = 8;
+ break;
+ case JXL_TYPE_UINT16:
+ basic_info.bits_per_sample = 16;
+ break;
+ case JXL_TYPE_FLOAT16:
+ basic_info.bits_per_sample = 16;
+ break;
+ default:
+ ThrowJXLWriterException(CoderError,DataStorageTypeIsNotSupported,image);
+ }
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;
+ else if (pixel_format.data_type == JXL_TYPE_FLOAT16)
+ basic_info.exponent_bits_per_sample = 5;
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Using %u bits per sample", basic_info.bits_per_sample);
+ basic_info.num_color_channels = characteristics.grayscale ? 1 : 3;
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "Using %u color channel%s", basic_info.num_color_channels,
+ basic_info.num_color_channels > 1 ? "s" : "");
+
if (!characteristics.opaque)
basic_info.alpha_bits=basic_info.bits_per_sample;
+ /*
+ Set the feature level of the JPEG XL codestream. Valid values are 5 and
+ 10, or -1 (to choose automatically). Using the minimum required level, or
+ level 5 in most cases, is recommended for compatibility with all decoders.
+
+ See <jxl/encode.h> for more details.
+ */
+ if ((jxl_status = JxlEncoderSetCodestreamLevel(jxl_encoder,-1)) != JXL_ENC_SUCCESS)
+ {
+ };
+ if (image->logging)
+ {
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "Basic Info:\n"
+ " xsize=%u\n"
+ " ysize=%u \n"
+ " bits_per_sample=%u\n"
+ " exponent_bits_per_sample=%u\n"
+ " alpha_bits=%u\n"
+ " num_color_channels=%u\n"
+ " have_animation=%s",
+ basic_info.xsize, basic_info.ysize,
+ basic_info.bits_per_sample, basic_info.exponent_bits_per_sample,
+ basic_info.alpha_bits, basic_info.num_color_channels,
+ basic_info.have_animation == JXL_FALSE ? "False" : "True");
+ }
/* 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);
+#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
+ /* JXL_ENC_NOT_SUPPORTED was removed for 0.9.0, although API docs still mention it. */
else if (jxl_status == JXL_ENC_NOT_SUPPORTED)
ThrowJXLWriterException(CoderError,UnsupportedBitsPerSample,image);
+#endif /* if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0) */
else
ThrowJXLWriterException(CoderFatalError,Default,image);
}
@@ -1014,26 +1375,148 @@ static unsigned int WriteJXLImage(const ImageInfo *image_info,Image *image)
}
}
+ /* FIXME: Add metadata boxes */
+ do
+ {
+ const unsigned char
+ *exif,
+ *xmp;
+
+ size_t
+ exif_length,
+ xmp_length;
+
+ exif=GetImageProfile(image,"EXIF",&exif_length);
+ xmp=GetImageProfile(image,"XMP",&xmp_length);
+
+ if (!((exif != (const unsigned char *) NULL) ||
+ (xmp != (const unsigned char *) NULL)))
+ break;
+
+ (void) JxlEncoderUseBoxes(jxl_encoder);
+
+ #define ExifNamespace "Exif\0\0"
+ if ((exif != (const unsigned char *) NULL) && (exif_length > 6) &&
+ (exif[0] == 'E' && exif[1] == 'x' && exif[2] == 'i' &&
+ exif[3] == 'f' && exif[4] == '\0' && exif[5] == '\0'))
+ {
+ /*
+ The contents of this box must be prepended by a 4-byte tiff
+ header offset, which may be 4 zero bytes in case the tiff
+ header follows immediately. We will make the header follow
+ immediately.
+
+ The EXIF profile blob is prefixed with "Exif\0\0" but the
+ EXIF box needs to start with "\0\0\0\0" to indicate that the
+ TIFF header follows immediately.
+ */
+ unsigned char *exif_b = MagickAllocateResourceLimitedMemory(unsigned char *,
+ exif_length-2U);
+ if (exif_b != (unsigned char *) NULL)
+ {
+ (void) memset(exif_b,0,4U);
+ (void) memcpy(exif_b+4U,exif+6U,exif_length-6U);
+
+ (void) JxlEncoderAddBox(jxl_encoder,"Exif",exif_b,exif_length-2U,0);
+ MagickFreeResourceLimitedMemory(exif_b);
+ }
+ }
+
+ if (xmp != (const unsigned char *) NULL)
+ (void) JxlEncoderAddBox(jxl_encoder,"xml ",xmp,xmp_length,0);
+
+ (void) JxlEncoderCloseBoxes(jxl_encoder);
+
+ } while(0);
+
/* get & fill pixel buffer */
- size_row=image->columns * pixel_format.num_channels * (basic_info.bits_per_sample/8);
+ size_row=MagickArraySize(MagickArraySize(image->columns,pixel_format.num_channels),
+ (basic_info.bits_per_sample/8));
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
- "size_row = %zu", size_row);
+ "size_row = %" MAGICK_SIZE_T_F "u", (MAGICK_SIZE_T) 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,
- 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);
+
+ /*
+ Export image pixels to allocated buffer.
+ */
+ {
+ long
+ y;
+
+ const PixelPacket
+ *q;
+
+ unsigned char
+ *p;
+
+ unsigned int
+ quantum_size;
+
+ QuantumType
+ quantum_type;
+
+ QuantumSampleType
+ sample_type;
+
+ ExportPixelAreaOptions
+ export_options;
+
+ ExportPixelAreaInfo
+ export_info;
+
+ quantum_size = JxlDataTypeToQuantumSize(pixel_format.data_type);
+ sample_type = JxlDataTypeToQuantumSampleType(pixel_format.data_type);
+
+ if (pixel_format.num_channels == 1)
+ {
+ if (image->matte)
+ quantum_type = GrayAlphaQuantum;
+ else
+ quantum_type = GrayQuantum;
+ }
+#if 0
+ else if (cmyk)
+ {
+ if (image->matte)
+ quantum_type = CMYKAQuantum;
+ else
+ quantum_type = CMYKQuantum;
+ }
+#endif
+ else
+ {
+ if (image->matte)
+ quantum_type = RGBAQuantum;
+ else
+ quantum_type = RGBQuantum;
+ }
+
+ ExportPixelAreaOptionsInit(&export_options);
+ export_options.sample_type = sample_type;
+ export_options.endian = NativeEndian;
+
+ p = in_buf;
+ for (y=0; y < (long) image->rows; y++)
+ {
+ q=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
+ if (q == (const PixelPacket *) NULL)
+ {
+ status = MagickFail;
+ break;
+ }
+
+ if ((status = ExportImagePixelArea(image,quantum_type,quantum_size,
+ p,&export_options,&export_info))
+ != MagickPass)
+ break;
+
+ p += export_info.bytes_exported;
+ }
+ if (status == MagickFail)
+ ThrowJXLWriterException(ResourceLimitError,MemoryAllocationFailed,image);
+ }
/* real encode */
if (JxlEncoderAddImageFrame(frame_settings,&pixel_format,in_buf,
@@ -1080,7 +1563,7 @@ static unsigned int WriteJXLImage(const ImageInfo *image_info,Image *image)
return MagickPass;
}
-#endif
+#endif /* HasJXL */
/*
@@ -1106,6 +1589,7 @@ static unsigned int WriteJXLImage(const ImageInfo *image_info,Image *image)
*/
ModuleExport void RegisterJXLImage(void)
{
+#if defined(HasJXL)
static const char
description[] = "JXL Image Format";
@@ -1125,15 +1609,13 @@ ModuleExport void RegisterJXLImage(void)
jxl_minor=(encoder_version >> 8) & 0xff;
jxl_revision=encoder_version & 0xff;
*version='\0';
- (void) sprintf(version,
+ (void) snprintf(version,sizeof(version),
"jxl v%u.%u.%u", jxl_major,
jxl_minor, jxl_revision);
entry=SetMagickInfo("JXL");
-#if defined(HasJXL)
entry->decoder=(DecoderHandler) ReadJXLImage;
entry->encoder=(EncoderHandler) WriteJXLImage;
-#endif
entry->description=description;
entry->adjoin=False;
entry->seekable_stream=MagickTrue;
@@ -1142,6 +1624,7 @@ ModuleExport void RegisterJXLImage(void)
entry->module="JXL";
entry->coder_class=PrimaryCoderClass;
(void) RegisterMagickInfo(entry);
+#endif /* HasJXL */
}
/*
@@ -1165,5 +1648,7 @@ ModuleExport void RegisterJXLImage(void)
*/
ModuleExport void UnregisterJXLImage(void)
{
+#if defined(HasJXL)
(void) UnregisterMagickInfo("JXL");
+#endif /* HasJXL */
}