mirror of
https://github.com/ImageMagick/ImageMagick.git
synced 2026-10-04 16:57:14 +02:00
* eliminate dead code if the else case is hit, transform is not resize and applying the transform will always fail * add scale support for uhdr
2224 lines
76 KiB
C
2224 lines
76 KiB
C
/*
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
% %
|
||
% %
|
||
% %
|
||
% Read/Write UHDR Image Format %
|
||
% %
|
||
% Software Design %
|
||
% A S %
|
||
% Jan 2024 %
|
||
% %
|
||
% %
|
||
% Copyright @ 1999 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/license/ %
|
||
% %
|
||
% 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/blob.h"
|
||
#include "MagickCore/blob-private.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/module.h"
|
||
#include "MagickCore/option.h"
|
||
#include "MagickCore/profile.h"
|
||
#include "MagickCore/profile-private.h"
|
||
#include "MagickCore/resize.h"
|
||
#include "MagickCore/shear.h"
|
||
#include "MagickCore/static.h"
|
||
#include "MagickCore/string_.h"
|
||
#include "MagickCore/string-private.h"
|
||
#include "MagickCore/transform.h"
|
||
#if defined(MAGICKCORE_UHDR_DELEGATE)
|
||
#include "ultrahdr_api.h"
|
||
#include "uhdr.h"
|
||
#endif
|
||
|
||
/*
|
||
Forward declarations.
|
||
*/
|
||
#if defined(MAGICKCORE_UHDR_DELEGATE)
|
||
static MagickBooleanType
|
||
WriteUHDRImage(const ImageInfo *,Image *,ExceptionInfo *);
|
||
#endif
|
||
|
||
/*
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
% %
|
||
% %
|
||
% %
|
||
% I s U H D R %
|
||
% %
|
||
% %
|
||
% %
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
%
|
||
% IsUHDR() returns MagickTrue if the input stream is a valid ultra hdr file,
|
||
% MagickFalse otherwise
|
||
%
|
||
% The format of the IsUHDR method is:
|
||
%
|
||
% MagickBooleanType IsUHDR(const unsigned char *magick,const size_t length)
|
||
%
|
||
% A description of each parameter follows:
|
||
%
|
||
% o image_info: the image info.
|
||
%
|
||
% o exception: return any errors or warnings in this structure.
|
||
%
|
||
*/
|
||
static MagickBooleanType IsUHDR(const unsigned char *magick,
|
||
const size_t length)
|
||
{
|
||
#if defined(MAGICKCORE_UHDR_DELEGATE)
|
||
if (is_uhdr_image((void *) magick,(int) length))
|
||
return(MagickTrue);
|
||
#else
|
||
magick_unreferenced(magick);
|
||
magick_unreferenced(length);
|
||
#endif
|
||
return(MagickFalse);
|
||
}
|
||
|
||
#if defined(MAGICKCORE_UHDR_DELEGATE)
|
||
/*
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
% %
|
||
% %
|
||
% %
|
||
% R e a d U H D R I m a g e %
|
||
% %
|
||
% %
|
||
% %
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
%
|
||
% ReadUHDRImage retrieves an image via a file descriptor, decodes the image,
|
||
% 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 ReadUHDRImage method is:
|
||
%
|
||
% Image *ReadUHDRImage(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 uhdr_color_transfer_t map_ct_to_uhdr_ct(const char *input_ct)
|
||
{
|
||
if (!strcmp(input_ct, "hlg"))
|
||
return UHDR_CT_HLG;
|
||
if (!strcmp(input_ct, "pq"))
|
||
return UHDR_CT_PQ;
|
||
if (!strcmp(input_ct, "linear"))
|
||
return UHDR_CT_LINEAR;
|
||
if (!strcmp(input_ct, "srgb"))
|
||
return UHDR_CT_SRGB;
|
||
else
|
||
return UHDR_CT_UNSPECIFIED;
|
||
}
|
||
|
||
static Image *ReadUHDRImage(const ImageInfo *image_info,
|
||
ExceptionInfo *exception)
|
||
{
|
||
#define SetHDRGMProperty(name,value) \
|
||
(void) FormatLocaleString(buffer,sizeof(buffer),"%.9g",(double) (value)); \
|
||
(void) SetImageProperty(image,"hdrgm:" name,buffer,exception)
|
||
#define SetHDRGMPropertyInt(name,value) \
|
||
(void) FormatLocaleString(buffer,sizeof(buffer),"%d",(value)); \
|
||
(void) SetImageProperty(image,"hdrgm:" name,buffer,exception)
|
||
#define SetHDRGMPropertySize(name,value) \
|
||
(void) FormatLocaleString(buffer,sizeof(buffer),"%.17g",(double) (value)); \
|
||
(void) SetImageProperty(image,"hdrgm:" name,buffer,exception)
|
||
#define SetHDRGMProperty3(name,value,value1,value2) \
|
||
(void) FormatLocaleString(buffer,sizeof(buffer),"%.9g,%.9g,%.9g", \
|
||
(double) (value),(double) (value1),(double) (value2)); \
|
||
(void) SetImageProperty(image,"hdrgm:" name,buffer,exception)
|
||
|
||
Image
|
||
*image;
|
||
|
||
MagickBooleanType
|
||
status;
|
||
|
||
image = AcquireImage(image_info, exception);
|
||
status = OpenBlob(image_info, image, ReadBinaryBlobMode, exception);
|
||
if (status == MagickFalse)
|
||
return DestroyImageList(image);
|
||
|
||
uhdr_compressed_image_t
|
||
img;
|
||
|
||
img.data = GetBlobStreamData(image);
|
||
img.data_sz = GetBlobSize(image);
|
||
img.capacity = GetBlobSize(image);
|
||
img.cg = UHDR_CG_UNSPECIFIED;
|
||
img.ct = UHDR_CT_UNSPECIFIED;
|
||
img.range = UHDR_CR_UNSPECIFIED;
|
||
|
||
uhdr_codec_private_t
|
||
*handle = uhdr_create_decoder();
|
||
|
||
const char *option = GetImageOption(image_info, "uhdr:output-color-transfer");
|
||
uhdr_color_transfer_t decoded_img_ct =
|
||
(option != (const char *)NULL) ? map_ct_to_uhdr_ct(option) : UHDR_CT_SRGB;
|
||
|
||
const char *profile_skip = GetImageOption(image_info, "profile:skip");
|
||
|
||
MagickBooleanType
|
||
skip_app_profiles = IsOptionMember("APP",profile_skip),
|
||
skip_exif_profile = IsOptionMember("EXIF",profile_skip),
|
||
skip_gainmap_profile = IsOptionMember("HDRGM",profile_skip),
|
||
skip_icc_profile = IsOptionMember("ICC",profile_skip);
|
||
|
||
if (skip_app_profiles != MagickFalse)
|
||
{
|
||
skip_exif_profile=MagickTrue;
|
||
skip_gainmap_profile=MagickTrue;
|
||
}
|
||
|
||
uhdr_img_fmt_t
|
||
decoded_img_fmt = UHDR_CT_SRGB;
|
||
|
||
if (decoded_img_ct == UHDR_CT_LINEAR)
|
||
decoded_img_fmt = UHDR_IMG_FMT_64bppRGBAHalfFloat;
|
||
else if (decoded_img_ct == UHDR_CT_HLG || decoded_img_ct == UHDR_CT_PQ)
|
||
decoded_img_fmt = UHDR_IMG_FMT_32bppRGBA1010102;
|
||
else if (decoded_img_ct == UHDR_CT_SRGB)
|
||
decoded_img_fmt = UHDR_IMG_FMT_32bppRGBA8888;
|
||
else
|
||
decoded_img_fmt = UHDR_IMG_FMT_UNSPECIFIED;
|
||
|
||
#define CHECK_IF_ERR(x) \
|
||
{ \
|
||
uhdr_error_info_t retval = (x); \
|
||
if (retval.error_code != UHDR_CODEC_OK) \
|
||
{ \
|
||
(void)ThrowMagickException(exception, GetMagickModule(), CoderError, \
|
||
retval.has_detail ? retval.detail : "unknown error", "`%s'", \
|
||
image->filename); \
|
||
uhdr_release_decoder(handle); \
|
||
CloseBlob(image); \
|
||
return DestroyImageList(image); \
|
||
} \
|
||
}
|
||
|
||
CHECK_IF_ERR(uhdr_dec_set_image(handle, &img))
|
||
CHECK_IF_ERR(uhdr_dec_set_out_color_transfer(handle, decoded_img_ct))
|
||
CHECK_IF_ERR(uhdr_dec_set_out_img_format(handle, decoded_img_fmt))
|
||
CHECK_IF_ERR(uhdr_dec_probe(handle))
|
||
|
||
image->columns = uhdr_dec_get_image_width(handle);
|
||
image->rows = uhdr_dec_get_image_height(handle);
|
||
|
||
if (image_info->ping != MagickFalse)
|
||
{
|
||
uhdr_release_decoder(handle);
|
||
(void) CloseBlob(image);
|
||
return(GetFirstImageInList(image));
|
||
}
|
||
|
||
CHECK_IF_ERR(uhdr_decode(handle))
|
||
|
||
uhdr_raw_image_t
|
||
*dst = uhdr_get_decoded_image(handle);
|
||
|
||
/*
|
||
Preserve compressed gain‑map + metadata for transcoding.
|
||
*/
|
||
{
|
||
uhdr_mem_block_t
|
||
*gainmap_image = uhdr_dec_get_gainmap_image(handle);
|
||
|
||
uhdr_gainmap_metadata_t
|
||
*gainmap_info = uhdr_dec_get_gainmap_metadata(handle);
|
||
|
||
if ((skip_gainmap_profile == MagickFalse) &&
|
||
(gainmap_image != (uhdr_mem_block_t *) NULL) &&
|
||
(gainmap_info != (uhdr_gainmap_metadata_t *) NULL))
|
||
{
|
||
char
|
||
buffer[MagickPathExtent];
|
||
|
||
/*
|
||
Set gainmap as a binary profile.
|
||
*/
|
||
StringInfo *gainmap_profile = BlobToProfileStringInfo("hdrgm",
|
||
gainmap_image->data,gainmap_image->data_sz,exception);
|
||
(void) SetImageProfilePrivate(image,gainmap_profile,exception);
|
||
|
||
/*
|
||
Set metadata as properties.
|
||
*/
|
||
SetHDRGMProperty3("GainMapMax",
|
||
gainmap_info->max_content_boost[0],gainmap_info->max_content_boost[1],
|
||
gainmap_info->max_content_boost[2]);
|
||
SetHDRGMProperty3("GainMapMin",
|
||
gainmap_info->min_content_boost[0],gainmap_info->min_content_boost[1],
|
||
gainmap_info->min_content_boost[2]);
|
||
SetHDRGMProperty3("Gamma",gainmap_info->gamma[0],
|
||
gainmap_info->gamma[1],gainmap_info->gamma[2]);
|
||
SetHDRGMProperty3("OffsetSDR",gainmap_info->offset_sdr[0],
|
||
gainmap_info->offset_sdr[1],gainmap_info->offset_sdr[2]);
|
||
SetHDRGMProperty3("OffsetHDR",gainmap_info->offset_hdr[0],
|
||
gainmap_info->offset_hdr[1],gainmap_info->offset_hdr[2]);
|
||
SetHDRGMProperty("HDRCapacityMin",gainmap_info->hdr_capacity_min);
|
||
SetHDRGMProperty("HDRCapacityMax",gainmap_info->hdr_capacity_max);
|
||
SetHDRGMPropertyInt("UseBaseColorGrade",gainmap_info->use_base_cg);
|
||
SetHDRGMPropertySize("BaseWidth",image->columns);
|
||
SetHDRGMPropertySize("BaseHeight",image->rows);
|
||
}
|
||
}
|
||
|
||
status=SetImageExtent(image,image->columns,image->rows,exception);
|
||
if (status == MagickFalse)
|
||
{
|
||
uhdr_release_decoder(handle);
|
||
CloseBlob(image);
|
||
return(DestroyImageList(image));
|
||
}
|
||
|
||
#undef CHECK_IF_ERR
|
||
|
||
uhdr_mem_block_t *exif = uhdr_dec_get_exif(handle);
|
||
if ((skip_exif_profile == MagickFalse) && (exif != NULL))
|
||
{
|
||
StringInfo *exif_data = BlobToProfileStringInfo("exif",exif->data,
|
||
exif->data_sz,exception);
|
||
(void) SetImageProfilePrivate(image,exif_data,exception);
|
||
}
|
||
|
||
uhdr_mem_block_t *icc = uhdr_dec_get_icc(handle);
|
||
if ((skip_icc_profile == MagickFalse) && (icc != NULL) &&
|
||
(icc->data != NULL) && (icc->data_sz != 0))
|
||
{
|
||
const unsigned char
|
||
*icc_data_start = (const unsigned char *) icc->data;
|
||
|
||
size_t
|
||
icc_data_size = icc->data_sz;
|
||
|
||
/*
|
||
libultrahdr returns JPEG APP2 ICC chunk data. ImageMagick stores the
|
||
raw ICC payload and adds the APP2 wrapper itself when writing JPEG.
|
||
*/
|
||
if ((icc_data_size > 14) &&
|
||
(memcmp(icc_data_start,"ICC_PROFILE\0",12) == 0))
|
||
{
|
||
icc_data_start+=14;
|
||
icc_data_size-=14;
|
||
}
|
||
StringInfo *icc_data = BlobToProfileStringInfo("icc",icc_data_start,
|
||
icc_data_size,exception);
|
||
(void) SetImageProfilePrivate(image,icc_data,exception);
|
||
}
|
||
|
||
if (decoded_img_ct == UHDR_CT_LINEAR)
|
||
{
|
||
SetImageColorspace(image,RGBColorspace,exception);
|
||
image->gamma=1.0;
|
||
}
|
||
else if ((decoded_img_ct == UHDR_CT_SRGB) && (dst->cg == UHDR_CG_DISPLAY_P3))
|
||
SetImageColorspace(image,DisplayP3Colorspace,exception);
|
||
else if (decoded_img_ct == UHDR_CT_SRGB)
|
||
SetImageColorspace(image,sRGBColorspace,exception);
|
||
else
|
||
SetImageColorspace(image,RGBColorspace,exception);
|
||
|
||
image->compression = JPEGCompression;
|
||
if (decoded_img_fmt == UHDR_IMG_FMT_32bppRGBA8888)
|
||
image->depth = 8;
|
||
else if (decoded_img_fmt == UHDR_IMG_FMT_32bppRGBA1010102)
|
||
image->depth = 10;
|
||
else if (decoded_img_fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat)
|
||
image->depth = 16;
|
||
switch (dst->cg)
|
||
{
|
||
case UHDR_CG_BT_709:
|
||
image->chromaticity.red_primary.x = 0.64;
|
||
image->chromaticity.red_primary.y = 0.33;
|
||
image->chromaticity.green_primary.x = 0.30;
|
||
image->chromaticity.green_primary.y = 0.60;
|
||
image->chromaticity.blue_primary.x = 0.15;
|
||
image->chromaticity.blue_primary.y = 0.06;
|
||
image->chromaticity.white_point.x = 0.3127;
|
||
image->chromaticity.white_point.y = 0.3290;
|
||
break;
|
||
|
||
case UHDR_CG_DISPLAY_P3:
|
||
image->chromaticity.red_primary.x = 0.680;
|
||
image->chromaticity.red_primary.y = 0.320;
|
||
image->chromaticity.green_primary.x = 0.265;
|
||
image->chromaticity.green_primary.y = 0.690;
|
||
image->chromaticity.blue_primary.x = 0.150;
|
||
image->chromaticity.blue_primary.y = 0.060;
|
||
image->chromaticity.white_point.x = 0.3127;
|
||
image->chromaticity.white_point.y = 0.3290;
|
||
break;
|
||
|
||
case UHDR_CG_BT_2100:
|
||
image->chromaticity.red_primary.x = 0.708;
|
||
image->chromaticity.red_primary.y = 0.292;
|
||
image->chromaticity.green_primary.x = 0.170;
|
||
image->chromaticity.green_primary.y = 0.797;
|
||
image->chromaticity.blue_primary.x = 0.131;
|
||
image->chromaticity.blue_primary.y = 0.046;
|
||
image->chromaticity.white_point.x = 0.3127;
|
||
image->chromaticity.white_point.y = 0.3290;
|
||
break;
|
||
|
||
case UHDR_CG_UNSPECIFIED:
|
||
break;
|
||
}
|
||
|
||
for (ssize_t y = 0; y < (ssize_t) image->rows; y++)
|
||
{
|
||
Quantum
|
||
*q;
|
||
|
||
q = QueueAuthenticPixels(image, 0, y, image->columns, 1, exception);
|
||
if (q == (Quantum *) NULL)
|
||
{
|
||
status = MagickFalse;
|
||
break;
|
||
}
|
||
if (decoded_img_fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat)
|
||
{
|
||
uint16_t *row =
|
||
(uint16_t *)dst->planes[UHDR_PLANE_PACKED] + (y * dst->stride[UHDR_PLANE_PACKED] * 4);
|
||
for (ssize_t x = 0; x < (ssize_t)image->columns; x++)
|
||
{
|
||
SetPixelRed(image, ClampToQuantum(QuantumRange * (HalfToSinglePrecision(*row++))), q);
|
||
SetPixelGreen(image, ClampToQuantum(QuantumRange * (HalfToSinglePrecision(*row++))), q);
|
||
SetPixelBlue(image, ClampToQuantum(QuantumRange * (HalfToSinglePrecision(*row++))), q);
|
||
row++;
|
||
q += GetPixelChannels(image);
|
||
}
|
||
}
|
||
else if (decoded_img_fmt == UHDR_IMG_FMT_32bppRGBA1010102)
|
||
{
|
||
uint32_t *row =
|
||
(uint32_t *)dst->planes[UHDR_PLANE_PACKED] + (y * dst->stride[UHDR_PLANE_PACKED]);
|
||
for (ssize_t x = 0; x < (ssize_t)image->columns; x++)
|
||
{
|
||
SetPixelRed(image, ScaleShortToQuantum((unsigned short)((*(row + x) & 0x000003FF) << 6)),
|
||
q);
|
||
SetPixelGreen(image, ScaleShortToQuantum((unsigned short)((*(row + x) & 0x000FFC00) >> 4)),
|
||
q);
|
||
SetPixelBlue(image, ScaleShortToQuantum((unsigned short)((*(row + x) & 0x3FF00000) >> 14)),
|
||
q);
|
||
q += GetPixelChannels(image);
|
||
}
|
||
}
|
||
else if (decoded_img_fmt == UHDR_IMG_FMT_32bppRGBA8888) {
|
||
uint8_t *row =
|
||
(uint8_t *)dst->planes[UHDR_PLANE_PACKED] + (y * dst->stride[UHDR_PLANE_PACKED] * 4);
|
||
for (ssize_t x = 0; x < (ssize_t)image->columns; x++)
|
||
{
|
||
SetPixelRed(image, ScaleCharToQuantum((unsigned char)(*row++)), q);
|
||
SetPixelGreen(image, ScaleCharToQuantum((unsigned char)(*row++)), q);
|
||
SetPixelBlue(image, ScaleCharToQuantum((unsigned char)(*row++)), q);
|
||
row++;
|
||
q += GetPixelChannels(image);
|
||
}
|
||
}
|
||
|
||
if (SyncAuthenticPixels(image, exception) == MagickFalse)
|
||
{
|
||
status = MagickFalse;
|
||
break;
|
||
}
|
||
}
|
||
uhdr_release_decoder(handle);
|
||
CloseBlob(image);
|
||
if (status == MagickFalse)
|
||
return (DestroyImageList(image));
|
||
return (GetFirstImageInList(image));
|
||
}
|
||
#endif
|
||
|
||
/*
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
% %
|
||
% %
|
||
% %
|
||
% R e g i s t e r U H D R I m a g e %
|
||
% %
|
||
% %
|
||
% %
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
%
|
||
% RegisterUHDRImage() adds attributes for the Uhdr image format to the list
|
||
% of supported formats. The attributes 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 RegisterUHDRImage method is:
|
||
%
|
||
% size_t RegisterUHDRImage(void)
|
||
%
|
||
*/
|
||
ModuleExport size_t RegisterUHDRImage(void)
|
||
{
|
||
char
|
||
version[MagickPathExtent];
|
||
|
||
MagickInfo
|
||
*entry;
|
||
|
||
*version='\0';
|
||
entry=AcquireMagickInfo("UHDR","UHDR","Ultra HDR Image Format");
|
||
#if defined(MAGICKCORE_UHDR_DELEGATE)
|
||
entry->decoder=(DecodeImageHandler *) ReadUHDRImage;
|
||
entry->encoder=(EncodeImageHandler *) WriteUHDRImage;
|
||
#endif
|
||
entry->flags|=CoderDecoderSeekableStreamFlag;
|
||
entry->magick=(IsImageFormatHandler *) IsUHDR;
|
||
if (*version != '\0')
|
||
entry->version=ConstantString(version);
|
||
(void) RegisterMagickInfo(entry);
|
||
return(MagickImageCoderSignature);
|
||
}
|
||
|
||
/*
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
% %
|
||
% %
|
||
% %
|
||
% U n r e g i s t e r U H D R I m a g e %
|
||
% %
|
||
% %
|
||
% %
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
%
|
||
% UnregisterUHDRImage() removes format registrations made by the UHDR module
|
||
% from the list of supported formats.
|
||
%
|
||
% The format of the UnregisterUHDRImage method is:
|
||
%
|
||
% UnregisterUHDRImage(void)
|
||
%
|
||
*/
|
||
ModuleExport void UnregisterUHDRImage(void)
|
||
{
|
||
(void) UnregisterMagickInfo("UHDR");
|
||
}
|
||
|
||
#if defined(MAGICKCORE_UHDR_DELEGATE)
|
||
/*
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
% %
|
||
% %
|
||
% %
|
||
% W r i t e U H D R I m a g e %
|
||
% %
|
||
% %
|
||
% %
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
%
|
||
% WriteUHDRImage() writes an input HDR intent and SDR intent of an image in
|
||
% UltraHDR format.
|
||
%
|
||
% The format of the WriteUHDRImage method is:
|
||
%
|
||
% MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,
|
||
% Image *image,ExceptionInfo *exception)
|
||
%
|
||
% A description of each parameter follows.
|
||
%
|
||
% o image_info: the image info.
|
||
%
|
||
% o image: The image.
|
||
%
|
||
% o exception: return any errors or warnings in this structure.
|
||
%
|
||
*/
|
||
|
||
static MagickBooleanType IsFloatEqual(float fa, float fb)
|
||
{
|
||
float diff = fa - fb;
|
||
|
||
return (diff < 0.0001 && diff > -0.0001) ? MagickTrue : MagickFalse;
|
||
}
|
||
|
||
static uhdr_color_gamut_t getImageColorGamut(ChromaticityInfo *info)
|
||
{
|
||
if (!(IsFloatEqual(info->white_point.x, 0.3127f) &&
|
||
IsFloatEqual(info->white_point.y, 0.3290f)))
|
||
return UHDR_CG_UNSPECIFIED;
|
||
|
||
if (IsFloatEqual(info->blue_primary.x, 0.150f) &&
|
||
IsFloatEqual(info->blue_primary.y, 0.060f))
|
||
{
|
||
if (IsFloatEqual(info->red_primary.x, 0.64f) &&
|
||
IsFloatEqual(info->red_primary.y, 0.33f) &&
|
||
IsFloatEqual(info->green_primary.x, 0.30f) &&
|
||
IsFloatEqual(info->green_primary.y, 0.60f))
|
||
{
|
||
return UHDR_CG_BT_709;
|
||
}
|
||
if (IsFloatEqual(info->red_primary.x, 0.680f) &&
|
||
IsFloatEqual(info->red_primary.y, 0.320f) &&
|
||
IsFloatEqual(info->green_primary.x, 0.265f) &&
|
||
IsFloatEqual(info->green_primary.y, 0.690f))
|
||
{
|
||
return UHDR_CG_DISPLAY_P3;
|
||
}
|
||
}
|
||
else if (IsFloatEqual(info->blue_primary.x, 0.131f) &&
|
||
IsFloatEqual(info->blue_primary.y, 0.046f) &&
|
||
IsFloatEqual(info->red_primary.x, 0.708f) &&
|
||
IsFloatEqual(info->red_primary.y, 0.292f) &&
|
||
IsFloatEqual(info->green_primary.x, 0.170f) &&
|
||
IsFloatEqual(info->green_primary.y, 0.797f))
|
||
{
|
||
return UHDR_CG_BT_2100;
|
||
}
|
||
|
||
return UHDR_CG_UNSPECIFIED;
|
||
}
|
||
|
||
static uhdr_color_gamut_t map_cg_to_uhdr_cg(const char *input_cg)
|
||
{
|
||
if (!strcmp(input_cg, "display_p3"))
|
||
return UHDR_CG_DISPLAY_P3;
|
||
if (!strcmp(input_cg, "bt709"))
|
||
return UHDR_CG_BT_709;
|
||
if (!strcmp(input_cg, "bt2100"))
|
||
return UHDR_CG_BT_2100;
|
||
else
|
||
return UHDR_CG_UNSPECIFIED;
|
||
}
|
||
|
||
static uhdr_mem_block_t GetExifProfile(Image *image, ExceptionInfo *exception)
|
||
{
|
||
const char
|
||
*name;
|
||
|
||
const StringInfo
|
||
*profile;
|
||
|
||
uhdr_mem_block_t
|
||
uhdr_profile = { 0 };
|
||
|
||
ResetImageProfileIterator(image);
|
||
for (name = GetNextImageProfile(image); name != (const char *)NULL;)
|
||
{
|
||
profile = GetImageProfile(image, name);
|
||
if (LocaleCompare(name, "EXIF") == 0)
|
||
{
|
||
uhdr_profile.data_sz = (unsigned int)GetStringInfoLength(profile);
|
||
uhdr_profile.capacity = uhdr_profile.data_sz;
|
||
if (uhdr_profile.data_sz > 65533L)
|
||
(void)ThrowMagickException(exception, GetMagickModule(), CoderWarning,
|
||
"ExifProfileSizeExceedsLimit", "`%s'", image->filename);
|
||
|
||
uhdr_profile.data = GetStringInfoDatum(profile);
|
||
return uhdr_profile;
|
||
}
|
||
if (image->debug != MagickFalse)
|
||
(void)LogMagickEvent(CoderEvent, GetMagickModule(), "%s profile: %.17g bytes", name,
|
||
(double)GetStringInfoLength(profile));
|
||
name = GetNextImageProfile(image);
|
||
}
|
||
return uhdr_profile;
|
||
}
|
||
|
||
static void fillRawImageDescriptor(uhdr_raw_image_t *imgDescriptor, const ImageInfo *image_info,
|
||
Image *image, uhdr_img_fmt_t fmt, uhdr_color_transfer_t ct)
|
||
{
|
||
const char
|
||
*option;
|
||
|
||
imgDescriptor->fmt = fmt;
|
||
|
||
if (image->depth >= 10)
|
||
{
|
||
option = GetImageOption(image_info, "uhdr:hdr-color-gamut");
|
||
imgDescriptor->cg = (option != (const char *)NULL) ? map_cg_to_uhdr_cg(option)
|
||
: getImageColorGamut(&image->chromaticity);
|
||
}
|
||
else if (image->depth == 8)
|
||
{
|
||
option = GetImageOption(image_info, "uhdr:sdr-color-gamut");
|
||
imgDescriptor->cg = (option != (const char *)NULL) ? map_cg_to_uhdr_cg(option)
|
||
: getImageColorGamut(&image->chromaticity);
|
||
}
|
||
|
||
imgDescriptor->range = imgDescriptor->fmt == UHDR_IMG_FMT_24bppYCbCrP010 ? UHDR_CR_LIMITED_RANGE
|
||
: UHDR_CR_FULL_RANGE;
|
||
imgDescriptor->ct = ct;
|
||
imgDescriptor->w = image->columns;
|
||
imgDescriptor->h = image->rows;
|
||
}
|
||
|
||
static size_t GetHDRGMPropertySize(const Image *image,const char *name,
|
||
ExceptionInfo *exception)
|
||
{
|
||
char
|
||
property[MagickPathExtent];
|
||
|
||
const char
|
||
*value;
|
||
|
||
(void) FormatLocaleString(property,MagickPathExtent,"hdrgm:%s",name);
|
||
value=GetImageProperty(image,property,exception);
|
||
if (value == (const char *) NULL)
|
||
return(0);
|
||
return((size_t) StringToUnsignedLong(value));
|
||
}
|
||
|
||
static size_t ScaleGainMapExtent(const size_t extent,
|
||
const size_t scaled_extent,const size_t base_extent)
|
||
{
|
||
double
|
||
scale;
|
||
|
||
if ((extent == 0) || (scaled_extent == 0) || (base_extent == 0))
|
||
return(0);
|
||
scale=((double) extent*(double) scaled_extent)/(double) base_extent;
|
||
if (scale < 1.0)
|
||
return(1);
|
||
if (scale > (double) MAGICK_SSIZE_MAX)
|
||
return(0);
|
||
return(CastDoubleToSizeT(scale+0.5));
|
||
}
|
||
|
||
static ssize_t ScaleGainMapCoordinate(const double coordinate,
|
||
const size_t extent,const size_t base_extent)
|
||
{
|
||
double
|
||
scale;
|
||
|
||
if ((extent == 0) || (base_extent == 0))
|
||
return(0);
|
||
scale=((double) extent*coordinate)/(double) base_extent;
|
||
if (scale < 0.0)
|
||
return(0);
|
||
if (scale > (double) MAGICK_SSIZE_MAX)
|
||
return(MAGICK_SSIZE_MAX);
|
||
return(CastDoubleToSsizeT(floor(scale+0.5)));
|
||
}
|
||
|
||
static MagickBooleanType IsGainMapBaseGeometry(const size_t base_columns,
|
||
const size_t base_rows,const double columns,const double rows)
|
||
{
|
||
if ((columns <= 0.0) || (rows <= 0.0))
|
||
return(MagickFalse);
|
||
if ((base_columns != CastDoubleToSizeT(columns)) ||
|
||
(base_rows != CastDoubleToSizeT(rows)))
|
||
return(MagickFalse);
|
||
return(MagickTrue);
|
||
}
|
||
|
||
typedef struct _UltraHDRGainMapTransformState
|
||
{
|
||
size_t
|
||
columns,
|
||
rows,
|
||
source_columns,
|
||
source_rows;
|
||
|
||
ssize_t
|
||
origin_x,
|
||
origin_y;
|
||
|
||
int
|
||
basis_x_x,
|
||
basis_x_y,
|
||
basis_y_x,
|
||
basis_y_y;
|
||
} UltraHDRGainMapTransformState;
|
||
|
||
typedef struct _UltraHDRGainMapOrientation
|
||
{
|
||
int
|
||
origin_columns,
|
||
origin_rows,
|
||
basis_x_columns,
|
||
basis_x_rows,
|
||
basis_y_columns,
|
||
basis_y_rows,
|
||
swaps;
|
||
} UltraHDRGainMapOrientation;
|
||
|
||
static const UltraHDRGainMapOrientation
|
||
gainmap_orientations[7] =
|
||
{
|
||
{ 0, 1, 0,-1, 1, 0, 1 }, /* rotate 90 degrees clockwise */
|
||
{ 1, 1,-1, 0, 0,-1, 0 }, /* rotate 180 degrees */
|
||
{ 1, 0, 0, 1,-1, 0, 1 }, /* rotate 270 degrees clockwise */
|
||
{ 0, 1, 1, 0, 0,-1, 0 }, /* flip */
|
||
{ 1, 0,-1, 0, 0, 1, 0 }, /* flop */
|
||
{ 0, 0, 0, 1, 1, 0, 1 }, /* transpose */
|
||
{ 1, 1, 0,-1,-1, 0, 1 } /* transverse */
|
||
};
|
||
|
||
static void InitializeGainMapTransformState(
|
||
UltraHDRGainMapTransformState *state,const size_t columns,
|
||
const size_t rows)
|
||
{
|
||
state->columns=columns;
|
||
state->rows=rows;
|
||
state->source_columns=columns;
|
||
state->source_rows=rows;
|
||
state->origin_x=0;
|
||
state->origin_y=0;
|
||
state->basis_x_x=1;
|
||
state->basis_x_y=0;
|
||
state->basis_y_x=0;
|
||
state->basis_y_y=1;
|
||
}
|
||
|
||
static MagickBooleanType AddGainMapTransformOffset(const ssize_t origin,
|
||
const size_t offset,const int direction,ssize_t *result)
|
||
{
|
||
if (offset > (size_t) MAGICK_SSIZE_MAX)
|
||
return(MagickFalse);
|
||
if (direction == 0)
|
||
{
|
||
*result=origin;
|
||
return(MagickTrue);
|
||
}
|
||
if (direction > 0)
|
||
{
|
||
if (origin > (MAGICK_SSIZE_MAX-(ssize_t) offset))
|
||
return(MagickFalse);
|
||
*result=origin+(ssize_t) offset;
|
||
}
|
||
else
|
||
{
|
||
if (origin < (MAGICK_SSIZE_MIN+(ssize_t) offset))
|
||
return(MagickFalse);
|
||
*result=origin-(ssize_t) offset;
|
||
}
|
||
return(MagickTrue);
|
||
}
|
||
|
||
static MagickBooleanType AddGainMapTransformTerm(const ssize_t origin,
|
||
const size_t x,const int x_direction,const size_t y,const int y_direction,
|
||
ssize_t *result)
|
||
{
|
||
ssize_t
|
||
value;
|
||
|
||
if (AddGainMapTransformOffset(origin,x,x_direction,&value) == MagickFalse)
|
||
return(MagickFalse);
|
||
return(AddGainMapTransformOffset(value,y,y_direction,result));
|
||
}
|
||
|
||
static MagickBooleanType UpdateGainMapTransformCrop(
|
||
UltraHDRGainMapTransformState *state,const size_t columns,
|
||
const size_t rows,const ssize_t x,const ssize_t y)
|
||
{
|
||
ssize_t
|
||
origin_x,
|
||
origin_y;
|
||
|
||
if ((columns == 0) || (rows == 0) || (columns > state->columns) ||
|
||
(rows > state->rows) || (x < 0) || (y < 0) ||
|
||
((size_t) x > state->columns-columns) ||
|
||
((size_t) y > state->rows-rows))
|
||
return(MagickFalse);
|
||
if (AddGainMapTransformTerm(state->origin_x,(size_t) x,
|
||
state->basis_x_x,(size_t) y,state->basis_y_x,&origin_x) == MagickFalse)
|
||
return(MagickFalse);
|
||
if (AddGainMapTransformTerm(state->origin_y,(size_t) x,
|
||
state->basis_x_y,(size_t) y,state->basis_y_y,&origin_y) == MagickFalse)
|
||
return(MagickFalse);
|
||
state->origin_x=origin_x;
|
||
state->origin_y=origin_y;
|
||
state->columns=columns;
|
||
state->rows=rows;
|
||
return(MagickTrue);
|
||
}
|
||
|
||
static MagickBooleanType UpdateGainMapTransformOrientation(
|
||
UltraHDRGainMapTransformState *state,const size_t orientation)
|
||
{
|
||
const UltraHDRGainMapOrientation
|
||
*transform;
|
||
|
||
ssize_t
|
||
origin_x,
|
||
origin_y;
|
||
|
||
int
|
||
basis_x_x,
|
||
basis_x_y,
|
||
basis_y_x,
|
||
basis_y_y;
|
||
|
||
size_t
|
||
columns,
|
||
rows;
|
||
|
||
if (orientation >= (sizeof(gainmap_orientations)/
|
||
sizeof(*gainmap_orientations)))
|
||
return(MagickFalse);
|
||
transform=gainmap_orientations+orientation;
|
||
columns=state->columns;
|
||
rows=state->rows;
|
||
if (AddGainMapTransformTerm(state->origin_x,
|
||
transform->origin_columns > 0 ? columns : 0,
|
||
transform->origin_columns*state->basis_x_x,
|
||
transform->origin_rows > 0 ? rows : 0,
|
||
transform->origin_rows*state->basis_y_x,
|
||
&origin_x) == MagickFalse ||
|
||
AddGainMapTransformTerm(state->origin_y,
|
||
transform->origin_columns > 0 ? columns : 0,
|
||
transform->origin_columns*state->basis_x_y,
|
||
transform->origin_rows > 0 ? rows : 0,
|
||
transform->origin_rows*state->basis_y_y,
|
||
&origin_y) == MagickFalse)
|
||
return(MagickFalse);
|
||
basis_x_x=transform->basis_x_columns*state->basis_x_x+
|
||
transform->basis_x_rows*state->basis_y_x;
|
||
basis_x_y=transform->basis_x_columns*state->basis_x_y+
|
||
transform->basis_x_rows*state->basis_y_y;
|
||
basis_y_x=transform->basis_y_columns*state->basis_x_x+
|
||
transform->basis_y_rows*state->basis_y_x;
|
||
basis_y_y=transform->basis_y_columns*state->basis_x_y+
|
||
transform->basis_y_rows*state->basis_y_y;
|
||
if (transform->swaps != 0)
|
||
Swap(columns,rows);
|
||
state->origin_x=origin_x;
|
||
state->origin_y=origin_y;
|
||
state->basis_x_x=basis_x_x;
|
||
state->basis_x_y=basis_x_y;
|
||
state->basis_y_x=basis_y_x;
|
||
state->basis_y_y=basis_y_y;
|
||
state->columns=columns;
|
||
state->rows=rows;
|
||
return(MagickTrue);
|
||
}
|
||
|
||
static MagickBooleanType UpdateGainMapTransformStateFromRecord(
|
||
UltraHDRGainMapTransformState *state,const char *transform)
|
||
{
|
||
const char
|
||
*geometry;
|
||
|
||
double
|
||
rotations,
|
||
source_columns,
|
||
source_rows;
|
||
|
||
size_t
|
||
orientation;
|
||
|
||
int
|
||
fields;
|
||
|
||
if (LocaleNCompare(transform,"rotate ",7) == 0)
|
||
{
|
||
geometry=transform+7;
|
||
fields=sscanf(geometry,"%lfx%lf %lf",&source_columns,
|
||
&source_rows,&rotations);
|
||
if (fields != 3)
|
||
return(MagickFalse);
|
||
if ((IsNaN(source_columns) != 0) ||
|
||
(IsNaN(source_rows) != 0) || (IsNaN(rotations) != 0) ||
|
||
(source_columns > (double) MAGICK_SSIZE_MAX) ||
|
||
(source_rows > (double) MAGICK_SSIZE_MAX) ||
|
||
(rotations < 0.0) || (rotations > (double) MAGICK_SSIZE_MAX) ||
|
||
(rotations != floor(rotations)) ||
|
||
(IsGainMapBaseGeometry(state->columns,state->rows,source_columns,
|
||
source_rows) == MagickFalse))
|
||
return(MagickFalse);
|
||
orientation=CastDoubleToSizeT(rotations)%4;
|
||
if (orientation == 0)
|
||
return(MagickTrue);
|
||
return(UpdateGainMapTransformOrientation(state,orientation-1));
|
||
}
|
||
if (LocaleNCompare(transform,"flip ",5) == 0)
|
||
orientation=3;
|
||
else if (LocaleNCompare(transform,"flop ",5) == 0)
|
||
orientation=4;
|
||
else if (LocaleNCompare(transform,"transpose ",10) == 0)
|
||
orientation=5;
|
||
else if (LocaleNCompare(transform,"transverse ",11) == 0)
|
||
orientation=6;
|
||
else
|
||
return(MagickFalse);
|
||
geometry=transform+(orientation < 5 ? 5 : orientation == 5 ? 10 : 11);
|
||
fields=sscanf(geometry,"%lfx%lf",&source_columns,&source_rows);
|
||
if (fields != 2)
|
||
return(MagickFalse);
|
||
if ((IsNaN(source_columns) != 0) || (IsNaN(source_rows) != 0) ||
|
||
(source_columns > (double) MAGICK_SSIZE_MAX) ||
|
||
(source_rows > (double) MAGICK_SSIZE_MAX) ||
|
||
(IsGainMapBaseGeometry(state->columns,state->rows,source_columns,
|
||
source_rows) == MagickFalse))
|
||
return(MagickFalse);
|
||
return(UpdateGainMapTransformOrientation(state,orientation));
|
||
}
|
||
|
||
static MagickBooleanType ReplaceGainMapImage(Image **gainmap_image,
|
||
Image *transform_image)
|
||
{
|
||
if (transform_image == (Image *) NULL)
|
||
return(MagickFalse);
|
||
*gainmap_image=DestroyImageList(*gainmap_image);
|
||
*gainmap_image=transform_image;
|
||
return(MagickTrue);
|
||
}
|
||
|
||
static MagickBooleanType CropGainMapImage(Image **gainmap_image,
|
||
const size_t base_columns,const size_t base_rows,const double columns,
|
||
const double rows,const double x,const double y,ExceptionInfo *exception)
|
||
{
|
||
Image
|
||
*crop_image;
|
||
|
||
RectangleInfo
|
||
geometry;
|
||
|
||
ssize_t
|
||
x0,
|
||
x1,
|
||
y0,
|
||
y1;
|
||
|
||
if ((base_columns == 0) || (base_rows == 0) || (columns <= 0.0) ||
|
||
(rows <= 0.0))
|
||
return(MagickFalse);
|
||
if ((x < 0.0) || (y < 0.0) || ((x+columns) > (double) base_columns) ||
|
||
((y+rows) > (double) base_rows))
|
||
return(MagickFalse);
|
||
x0=ScaleGainMapCoordinate(x,(*gainmap_image)->columns,base_columns);
|
||
y0=ScaleGainMapCoordinate(y,(*gainmap_image)->rows,base_rows);
|
||
x1=ScaleGainMapCoordinate(x+columns,(*gainmap_image)->columns,base_columns);
|
||
y1=ScaleGainMapCoordinate(y+rows,(*gainmap_image)->rows,base_rows);
|
||
if (x0 >= (ssize_t) (*gainmap_image)->columns)
|
||
x0=(ssize_t) (*gainmap_image)->columns-1;
|
||
if (y0 >= (ssize_t) (*gainmap_image)->rows)
|
||
y0=(ssize_t) (*gainmap_image)->rows-1;
|
||
if (x1 > (ssize_t) (*gainmap_image)->columns)
|
||
x1=(ssize_t) (*gainmap_image)->columns;
|
||
if (y1 > (ssize_t) (*gainmap_image)->rows)
|
||
y1=(ssize_t) (*gainmap_image)->rows;
|
||
if (x1 <= x0)
|
||
x1=x0+1;
|
||
if (y1 <= y0)
|
||
y1=y0+1;
|
||
geometry.x=x0;
|
||
geometry.y=y0;
|
||
geometry.width=(size_t) (x1-x0);
|
||
geometry.height=(size_t) (y1-y0);
|
||
(void) ResetImagePage(*gainmap_image,"0x0+0+0");
|
||
crop_image=CropImage(*gainmap_image,&geometry,exception);
|
||
return(ReplaceGainMapImage(gainmap_image,crop_image));
|
||
}
|
||
|
||
static MagickBooleanType ResizeGainMapImage(Image **gainmap_image,
|
||
size_t *base_columns,size_t *base_rows,const size_t columns,
|
||
const size_t rows,const FilterType filter_type,ExceptionInfo *exception)
|
||
{
|
||
Image
|
||
*resize_image;
|
||
|
||
size_t
|
||
target_columns,
|
||
target_rows;
|
||
|
||
target_columns=ScaleGainMapExtent((*gainmap_image)->columns,columns,
|
||
*base_columns);
|
||
target_rows=ScaleGainMapExtent((*gainmap_image)->rows,rows,*base_rows);
|
||
if ((target_columns == 0) || (target_rows == 0))
|
||
return(MagickFalse);
|
||
if ((target_columns != (*gainmap_image)->columns) ||
|
||
(target_rows != (*gainmap_image)->rows))
|
||
{
|
||
resize_image=ResizeImage(*gainmap_image,target_columns,target_rows,
|
||
filter_type,exception);
|
||
if (ReplaceGainMapImage(gainmap_image,resize_image) == MagickFalse)
|
||
return(MagickFalse);
|
||
}
|
||
*base_columns=columns;
|
||
*base_rows=rows;
|
||
return(MagickTrue);
|
||
}
|
||
|
||
static MagickBooleanType ScaleGainMapImage(Image **gainmap_image,
|
||
size_t *base_columns,size_t *base_rows,const size_t columns,
|
||
const size_t rows,ExceptionInfo *exception)
|
||
{
|
||
Image
|
||
*scale_image;
|
||
|
||
size_t
|
||
target_columns,
|
||
target_rows;
|
||
|
||
target_columns=ScaleGainMapExtent((*gainmap_image)->columns,columns,
|
||
*base_columns);
|
||
target_rows=ScaleGainMapExtent((*gainmap_image)->rows,rows,*base_rows);
|
||
if ((target_columns == 0) || (target_rows == 0))
|
||
return(MagickFalse);
|
||
if ((target_columns != (*gainmap_image)->columns) ||
|
||
(target_rows != (*gainmap_image)->rows))
|
||
{
|
||
scale_image=ScaleImage(*gainmap_image,target_columns,target_rows,
|
||
exception);
|
||
if (ReplaceGainMapImage(gainmap_image,scale_image) == MagickFalse)
|
||
return(MagickFalse);
|
||
}
|
||
*base_columns=columns;
|
||
*base_rows=rows;
|
||
return(MagickTrue);
|
||
}
|
||
|
||
static MagickBooleanType ApplyGainMapTransform(Image **gainmap_image,
|
||
size_t *base_columns,size_t *base_rows,const Image *image,
|
||
const char *transform,ExceptionInfo *exception)
|
||
{
|
||
double
|
||
columns,
|
||
rows,
|
||
source_columns,
|
||
source_rows;
|
||
|
||
FilterType
|
||
filter_type;
|
||
|
||
int
|
||
fields,
|
||
filter_value;
|
||
|
||
if (LocaleNCompare(transform,"resize ",7) == 0)
|
||
{
|
||
fields=sscanf(transform+7,"%lfx%lf %lfx%lf %d",&source_columns,
|
||
&source_rows,&columns,&rows,&filter_value);
|
||
if ((fields != 4) && (fields != 5))
|
||
return(MagickFalse);
|
||
if ((IsNaN(source_columns) != 0) || (IsNaN(source_rows) != 0) ||
|
||
(IsNaN(columns) != 0) || (IsNaN(rows) != 0) ||
|
||
(source_columns > (double) MAGICK_SSIZE_MAX) ||
|
||
(source_rows > (double) MAGICK_SSIZE_MAX) ||
|
||
(columns <= 0.0) || (rows <= 0.0) ||
|
||
(columns > (double) MAGICK_SSIZE_MAX) ||
|
||
(rows > (double) MAGICK_SSIZE_MAX) ||
|
||
(columns != floor(columns)) || (rows != floor(rows)))
|
||
return(MagickFalse);
|
||
filter_type=UndefinedFilter;
|
||
if (fields == 5)
|
||
{
|
||
if ((filter_value <= (int) UndefinedFilter) ||
|
||
(filter_value >= (int) SentinelFilter))
|
||
return(MagickFalse);
|
||
filter_type=(FilterType) filter_value;
|
||
}
|
||
else
|
||
filter_type=image->filter;
|
||
if (IsGainMapBaseGeometry(*base_columns,*base_rows,source_columns,
|
||
source_rows) == MagickFalse)
|
||
return(MagickFalse);
|
||
return(ResizeGainMapImage(gainmap_image,base_columns,base_rows,
|
||
CastDoubleToSizeT(columns),CastDoubleToSizeT(rows),filter_type,
|
||
exception));
|
||
}
|
||
else if (LocaleNCompare(transform,"scale ",6) == 0)
|
||
{
|
||
fields=sscanf(transform+6,"%lfx%lf %lfx%lf",&source_columns,
|
||
&source_rows,&columns,&rows);
|
||
if (fields != 4)
|
||
return(MagickFalse);
|
||
if ((IsNaN(source_columns) != 0) || (IsNaN(source_rows) != 0) ||
|
||
(IsNaN(columns) != 0) || (IsNaN(rows) != 0) ||
|
||
(source_columns > (double) MAGICK_SSIZE_MAX) ||
|
||
(source_rows > (double) MAGICK_SSIZE_MAX) ||
|
||
(columns <= 0.0) || (rows <= 0.0) ||
|
||
(columns > (double) MAGICK_SSIZE_MAX) ||
|
||
(rows > (double) MAGICK_SSIZE_MAX) ||
|
||
(columns != floor(columns)) || (rows != floor(rows)))
|
||
return(MagickFalse);
|
||
if (IsGainMapBaseGeometry(*base_columns,*base_rows,source_columns,
|
||
source_rows) == MagickFalse)
|
||
return(MagickFalse);
|
||
return(ScaleGainMapImage(gainmap_image,base_columns,base_rows,
|
||
CastDoubleToSizeT(columns),CastDoubleToSizeT(rows),exception));
|
||
}
|
||
return(MagickFalse);
|
||
}
|
||
|
||
static MagickBooleanType ApplyGainMapTransformOrientation(
|
||
Image **gainmap_image,const UltraHDRGainMapTransformState *state,
|
||
ExceptionInfo *exception)
|
||
{
|
||
Image
|
||
*transform_image;
|
||
|
||
size_t
|
||
orientation,
|
||
rotations;
|
||
|
||
if ((state->basis_x_x == 1) && (state->basis_x_y == 0) &&
|
||
(state->basis_y_x == 0) && (state->basis_y_y == 1))
|
||
return(MagickTrue);
|
||
for (orientation=0; orientation < (sizeof(gainmap_orientations)/
|
||
sizeof(*gainmap_orientations)); orientation++)
|
||
if ((state->basis_x_x == gainmap_orientations[orientation].basis_x_columns) &&
|
||
(state->basis_x_y == gainmap_orientations[orientation].basis_x_rows) &&
|
||
(state->basis_y_x == gainmap_orientations[orientation].basis_y_columns) &&
|
||
(state->basis_y_y == gainmap_orientations[orientation].basis_y_rows))
|
||
break;
|
||
if (orientation >= (sizeof(gainmap_orientations)/
|
||
sizeof(*gainmap_orientations)))
|
||
return(MagickFalse);
|
||
if (orientation < 3)
|
||
rotations=orientation+1;
|
||
else if (orientation == 3)
|
||
transform_image=FlipImage(*gainmap_image,exception);
|
||
else if (orientation == 4)
|
||
transform_image=FlopImage(*gainmap_image,exception);
|
||
else if (orientation == 5)
|
||
transform_image=TransposeImage(*gainmap_image,exception);
|
||
else
|
||
transform_image=TransverseImage(*gainmap_image,exception);
|
||
if (orientation >= 3)
|
||
return(ReplaceGainMapImage(gainmap_image,transform_image));
|
||
transform_image=IntegralRotateImage(*gainmap_image,rotations,exception);
|
||
return(ReplaceGainMapImage(gainmap_image,transform_image));
|
||
}
|
||
|
||
static MagickBooleanType FlushGainMapTransform(
|
||
Image **gainmap_image,const UltraHDRGainMapTransformState *state,
|
||
ExceptionInfo *exception)
|
||
{
|
||
ssize_t
|
||
origin_x,
|
||
origin_y,
|
||
max_x,
|
||
max_y,
|
||
min_x,
|
||
min_y,
|
||
opposite_x,
|
||
opposite_y;
|
||
|
||
size_t
|
||
crop_height,
|
||
crop_width;
|
||
|
||
if ((state->source_columns == 0) || (state->source_rows == 0) ||
|
||
(state->source_columns > (size_t) MAGICK_SSIZE_MAX) ||
|
||
(state->source_rows > (size_t) MAGICK_SSIZE_MAX))
|
||
return(MagickFalse);
|
||
origin_x=state->origin_x;
|
||
origin_y=state->origin_y;
|
||
if (AddGainMapTransformTerm(state->origin_x,state->columns,
|
||
state->basis_x_x,state->rows,state->basis_y_x,&opposite_x) ==
|
||
MagickFalse ||
|
||
AddGainMapTransformTerm(state->origin_y,state->columns,
|
||
state->basis_x_y,state->rows,state->basis_y_y,&opposite_y) ==
|
||
MagickFalse)
|
||
return(MagickFalse);
|
||
min_x=MagickMin(origin_x,opposite_x);
|
||
min_y=MagickMin(origin_y,opposite_y);
|
||
max_x=MagickMax(origin_x,opposite_x);
|
||
max_y=MagickMax(origin_y,opposite_y);
|
||
if ((min_x < 0) || (min_y < 0) ||
|
||
(max_x > (ssize_t) state->source_columns) ||
|
||
(max_y > (ssize_t) state->source_rows) || (max_x <= min_x) ||
|
||
(max_y <= min_y))
|
||
return(MagickFalse);
|
||
crop_width=(size_t) (max_x-min_x);
|
||
crop_height=(size_t) (max_y-min_y);
|
||
if ((min_x != 0) || (min_y != 0) ||
|
||
(max_x != (ssize_t) state->source_columns) ||
|
||
(max_y != (ssize_t) state->source_rows))
|
||
if (CropGainMapImage(gainmap_image,state->source_columns,
|
||
state->source_rows,(double) crop_width,(double) crop_height,
|
||
(double) min_x,(double) min_y,exception) == MagickFalse)
|
||
return(MagickFalse);
|
||
if (ApplyGainMapTransformOrientation(gainmap_image,state,exception) ==
|
||
MagickFalse)
|
||
return(MagickFalse);
|
||
return(MagickTrue);
|
||
}
|
||
|
||
static StringInfo *EncodeBaseImageProfile(const ImageInfo *image_info,
|
||
Image *image,ExceptionInfo *exception)
|
||
{
|
||
Image
|
||
*base_image;
|
||
|
||
ImageInfo
|
||
*base_info;
|
||
|
||
size_t
|
||
length;
|
||
|
||
StringInfo
|
||
*profile,
|
||
*removed_profile;
|
||
|
||
void
|
||
*blob;
|
||
|
||
base_image=CloneImage(image,0,0,MagickTrue,exception);
|
||
if (base_image == (Image *) NULL)
|
||
return((StringInfo *) NULL);
|
||
removed_profile=RemoveImageProfile(base_image,"hdrgm");
|
||
if (removed_profile != (StringInfo *) NULL)
|
||
removed_profile=DestroyStringInfo(removed_profile);
|
||
base_info=CloneImageInfo(image_info);
|
||
(void) CopyMagickString(base_info->filename,"JPEG:uhdr-base.jpg",
|
||
MagickPathExtent);
|
||
(void) CopyMagickString(base_info->magick,"JPEG",MagickPathExtent);
|
||
base_info->type=TrueColorType;
|
||
if (image->quality > 0)
|
||
base_info->quality=image->quality;
|
||
(void) CopyMagickString(base_image->magick,"JPEG",MagickPathExtent);
|
||
blob=ImageToBlob(base_info,base_image,&length,exception);
|
||
base_image=DestroyImage(base_image);
|
||
base_info=DestroyImageInfo(base_info);
|
||
if (blob == (void *) NULL)
|
||
{
|
||
(void) ThrowMagickException(exception,GetMagickModule(),CoderError,
|
||
"UnableToEncodeBaseImage","`%s'",image->filename);
|
||
return((StringInfo *) NULL);
|
||
}
|
||
profile=BlobToProfileStringInfo("uhdr-base",blob,length,exception);
|
||
blob=RelinquishMagickMemory(blob);
|
||
return(profile);
|
||
}
|
||
|
||
static StringInfo *TransformGainMapProfile(const ImageInfo *image_info,
|
||
const Image *image,const StringInfo *gainmap_profile,
|
||
MagickBooleanType *transform_status,ExceptionInfo *exception)
|
||
{
|
||
const char
|
||
*option,
|
||
*transforms;
|
||
|
||
Image
|
||
*gainmap_images;
|
||
|
||
ImageInfo
|
||
*gainmap_info;
|
||
|
||
MagickBooleanType
|
||
status,
|
||
transformed,
|
||
transform_required,
|
||
transform_pending;
|
||
|
||
UltraHDRGainMapTransformState
|
||
transform_state;
|
||
|
||
size_t
|
||
base_columns,
|
||
base_rows,
|
||
length;
|
||
|
||
StringInfo
|
||
*profile;
|
||
|
||
void
|
||
*blob;
|
||
|
||
assert(transform_status != (MagickBooleanType *) NULL);
|
||
*transform_status=MagickTrue;
|
||
base_columns=GetHDRGMPropertySize(image,"BaseWidth",exception);
|
||
base_rows=GetHDRGMPropertySize(image,"BaseHeight",exception);
|
||
transforms=GetImageProperty(image,"hdrgm:Transform",exception);
|
||
transform_required=((transforms != (const char *) NULL) &&
|
||
(*transforms != '\0')) ? MagickTrue : MagickFalse;
|
||
if ((base_columns == 0) || (base_rows == 0))
|
||
{
|
||
if (transform_required != MagickFalse)
|
||
{
|
||
*transform_status=MagickFalse;
|
||
(void) ThrowMagickException(exception,GetMagickModule(),CoderError,
|
||
"UnableToTransformGainMap","`%s'",image->filename);
|
||
}
|
||
return((StringInfo *) NULL);
|
||
}
|
||
if ((base_columns != image->columns) || (base_rows != image->rows))
|
||
{
|
||
if (transform_required == MagickFalse)
|
||
{
|
||
*transform_status=MagickFalse;
|
||
(void) ThrowMagickException(exception,GetMagickModule(),CoderError,
|
||
"UnableToTransformGainMap","`%s'",image->filename);
|
||
return((StringInfo *) NULL);
|
||
}
|
||
}
|
||
if (transform_required == MagickFalse)
|
||
return((StringInfo *) NULL);
|
||
gainmap_info=CloneImageInfo(image_info);
|
||
(void) CopyMagickString(gainmap_info->filename,"JPEG:hdrgm.jpg",
|
||
MagickPathExtent);
|
||
(void) CopyMagickString(gainmap_info->magick,"JPEG",MagickPathExtent);
|
||
(void) SetImageOption(gainmap_info,"jpeg:detect-uhdr","false");
|
||
gainmap_images=BlobToImage(gainmap_info,GetStringInfoDatum(gainmap_profile),
|
||
GetStringInfoLength(gainmap_profile),exception);
|
||
if (gainmap_images == (Image *) NULL)
|
||
{
|
||
gainmap_info=DestroyImageInfo(gainmap_info);
|
||
*transform_status=MagickFalse;
|
||
(void) ThrowMagickException(exception,GetMagickModule(),CoderError,
|
||
"UnableToDecodeGainMap","`%s'",image->filename);
|
||
return((StringInfo *) NULL);
|
||
}
|
||
status=MagickTrue;
|
||
transformed=MagickFalse;
|
||
transform_pending=MagickFalse;
|
||
InitializeGainMapTransformState(&transform_state,base_columns,base_rows);
|
||
if ((transforms != (const char *) NULL) && (*transforms != '\0'))
|
||
{
|
||
char
|
||
*next,
|
||
*transform,
|
||
*transform_list;
|
||
|
||
transform_list=AcquireString(transforms);
|
||
for (transform=transform_list; transform != (char *) NULL; )
|
||
{
|
||
next=strchr(transform,';');
|
||
if (next != (char *) NULL)
|
||
*next++='\0';
|
||
if (*transform != '\0')
|
||
{
|
||
if (LocaleNCompare(transform,"crop ",5) == 0)
|
||
{
|
||
double
|
||
columns,
|
||
rows,
|
||
source_columns,
|
||
source_rows,
|
||
x,
|
||
y;
|
||
|
||
size_t
|
||
crop_columns,
|
||
crop_rows;
|
||
|
||
ssize_t
|
||
crop_x,
|
||
crop_y;
|
||
|
||
if (sscanf(transform+5,"%lfx%lf %lfx%lf%lf%lf",
|
||
&source_columns,&source_rows,&columns,&rows,&x,&y) != 6)
|
||
status=MagickFalse;
|
||
else if ((IsNaN(source_columns) != 0) ||
|
||
(IsNaN(source_rows) != 0) || (IsNaN(columns) != 0) ||
|
||
(IsNaN(rows) != 0) || (IsNaN(x) != 0) ||
|
||
(IsNaN(y) != 0) ||
|
||
(source_columns > (double) MAGICK_SSIZE_MAX) ||
|
||
(source_rows > (double) MAGICK_SSIZE_MAX) ||
|
||
(columns > (double) MAGICK_SSIZE_MAX) ||
|
||
(rows > (double) MAGICK_SSIZE_MAX) ||
|
||
(x > (double) MAGICK_SSIZE_MAX) ||
|
||
(y > (double) MAGICK_SSIZE_MAX) ||
|
||
(columns != floor(columns)) ||
|
||
(rows != floor(rows)) || (x != floor(x)) ||
|
||
(y != floor(y)))
|
||
status=MagickFalse;
|
||
else if (IsGainMapBaseGeometry(transform_state.columns,
|
||
transform_state.rows,
|
||
source_columns,source_rows) == MagickFalse)
|
||
status=MagickFalse;
|
||
else if ((columns <= 0.0) || (rows <= 0.0) ||
|
||
(x < 0.0) || (y < 0.0) ||
|
||
(columns > (double) transform_state.columns) ||
|
||
(rows > (double) transform_state.rows) ||
|
||
(x > ((double) transform_state.columns-columns)) ||
|
||
(y > ((double) transform_state.rows-rows)))
|
||
status=MagickFalse;
|
||
if (status != MagickFalse)
|
||
{
|
||
crop_columns=CastDoubleToSizeT(columns);
|
||
crop_rows=CastDoubleToSizeT(rows);
|
||
crop_x=CastDoubleToSsizeT(x);
|
||
crop_y=CastDoubleToSsizeT(y);
|
||
if (transform_pending == MagickFalse)
|
||
{
|
||
InitializeGainMapTransformState(&transform_state,
|
||
base_columns,base_rows);
|
||
transform_pending=MagickTrue;
|
||
}
|
||
status=UpdateGainMapTransformCrop(&transform_state,
|
||
crop_columns,crop_rows,crop_x,crop_y);
|
||
if (status != MagickFalse)
|
||
{
|
||
base_columns=transform_state.columns;
|
||
base_rows=transform_state.rows;
|
||
transformed=MagickTrue;
|
||
}
|
||
}
|
||
}
|
||
else if ((LocaleNCompare(transform,"resize ",7) == 0) ||
|
||
(LocaleNCompare(transform,"scale ", 6) == 0))
|
||
{
|
||
if (transform_pending != MagickFalse)
|
||
{
|
||
status=FlushGainMapTransform(&gainmap_images,
|
||
&transform_state,exception);
|
||
transform_pending=MagickFalse;
|
||
}
|
||
if (status != MagickFalse)
|
||
status=ApplyGainMapTransform(&gainmap_images,&base_columns,
|
||
&base_rows,image,transform,exception);
|
||
if (status != MagickFalse)
|
||
{
|
||
InitializeGainMapTransformState(&transform_state,
|
||
base_columns,base_rows);
|
||
transformed=MagickTrue;
|
||
}
|
||
}
|
||
else if ((LocaleNCompare(transform,"flip ",5) == 0) ||
|
||
(LocaleNCompare(transform,"flop ",5) == 0) ||
|
||
(LocaleNCompare(transform,"rotate ",7) == 0) ||
|
||
(LocaleNCompare(transform,"transpose ",10) == 0) ||
|
||
(LocaleNCompare(transform,"transverse ",11) == 0))
|
||
{
|
||
if (transform_pending == MagickFalse)
|
||
{
|
||
InitializeGainMapTransformState(&transform_state,
|
||
base_columns,base_rows);
|
||
transform_pending=MagickTrue;
|
||
}
|
||
status=UpdateGainMapTransformStateFromRecord(
|
||
&transform_state,transform);
|
||
if (status != MagickFalse)
|
||
{
|
||
base_columns=transform_state.columns;
|
||
base_rows=transform_state.rows;
|
||
transformed=MagickTrue;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
status=MagickFalse;
|
||
}
|
||
if (status == MagickFalse)
|
||
break;
|
||
}
|
||
transform=next;
|
||
}
|
||
transform_list=DestroyString(transform_list);
|
||
}
|
||
if ((status != MagickFalse) && (transform_pending != MagickFalse))
|
||
{
|
||
status=FlushGainMapTransform(&gainmap_images,&transform_state,exception);
|
||
}
|
||
if ((status != MagickFalse) &&
|
||
((base_columns != image->columns) || (base_rows != image->rows)))
|
||
status=MagickFalse;
|
||
if (status == MagickFalse)
|
||
{
|
||
gainmap_info=DestroyImageInfo(gainmap_info);
|
||
gainmap_images=DestroyImageList(gainmap_images);
|
||
*transform_status=MagickFalse;
|
||
(void) ThrowMagickException(exception,GetMagickModule(),CoderError,
|
||
"UnableToTransformGainMap","`%s'",image->filename);
|
||
return((StringInfo *) NULL);
|
||
}
|
||
if (transformed == MagickFalse)
|
||
{
|
||
gainmap_images=DestroyImageList(gainmap_images);
|
||
gainmap_info=DestroyImageInfo(gainmap_info);
|
||
return((StringInfo *) NULL);
|
||
}
|
||
option=GetImageOption(image_info,"uhdr:gainmap-quality");
|
||
if (option != (const char *) NULL)
|
||
gainmap_images->quality=StringToUnsignedLong(option);
|
||
else if (image->quality > 0)
|
||
gainmap_images->quality=image->quality;
|
||
(void) CopyMagickString(gainmap_images->magick,"JPEG",MagickPathExtent);
|
||
blob=ImageToBlob(gainmap_info,gainmap_images,&length,exception);
|
||
gainmap_images=DestroyImageList(gainmap_images);
|
||
gainmap_info=DestroyImageInfo(gainmap_info);
|
||
if (blob == (void *) NULL)
|
||
{
|
||
*transform_status=MagickFalse;
|
||
(void) ThrowMagickException(exception,GetMagickModule(),CoderError,
|
||
"UnableToEncodeGainMap","`%s'",image->filename);
|
||
return((StringInfo *) NULL);
|
||
}
|
||
profile=BlobToProfileStringInfo("hdrgm",blob,length,exception);
|
||
blob=RelinquishMagickMemory(blob);
|
||
if (profile == (StringInfo *) NULL)
|
||
{
|
||
*transform_status=MagickFalse;
|
||
(void) ThrowMagickException(exception,GetMagickModule(),CoderError,
|
||
"UnableToEncodeGainMap","`%s'",image->filename);
|
||
}
|
||
return(profile);
|
||
}
|
||
|
||
static const char *SkipHDRGMWhitespace(const char *value)
|
||
{
|
||
while ((*value != '\0') &&
|
||
(strchr(" \f\n\r\t\v",(int) ((unsigned char) *value)) !=
|
||
(char *) NULL))
|
||
value++;
|
||
return(value);
|
||
}
|
||
|
||
static MagickBooleanType ParseHDRGMProperty(const char *value,float *result)
|
||
{
|
||
char
|
||
*q;
|
||
|
||
value=SkipHDRGMWhitespace(value);
|
||
*result=StringToFloat(value,&q);
|
||
if ((q == value) || (IsNaN((double) *result) != 0) ||
|
||
(*result > FLT_MAX) || (*result < -FLT_MAX))
|
||
return(MagickFalse);
|
||
q=(char *) SkipHDRGMWhitespace(q);
|
||
return(*q == '\0' ? MagickTrue : MagickFalse);
|
||
}
|
||
|
||
static MagickBooleanType ParseHDRGMProperty3(const char *value,
|
||
float values[3])
|
||
{
|
||
char
|
||
*q;
|
||
|
||
ssize_t
|
||
i;
|
||
|
||
for (i=0; i < 3; i++)
|
||
{
|
||
value=SkipHDRGMWhitespace(value);
|
||
values[i]=StringToFloat(value,&q);
|
||
if ((q == value) || (IsNaN((double) values[i]) != 0) ||
|
||
(values[i] > FLT_MAX) || (values[i] < -FLT_MAX))
|
||
return(MagickFalse);
|
||
q=(char *) SkipHDRGMWhitespace(q);
|
||
if (i == 2)
|
||
return(*q == '\0' ? MagickTrue : MagickFalse);
|
||
if (*q != ',')
|
||
return(MagickFalse);
|
||
value=q+1;
|
||
}
|
||
return(MagickFalse);
|
||
}
|
||
|
||
static MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,
|
||
Image *images,ExceptionInfo *exception)
|
||
{
|
||
#define GetHDRGMProperty(name,field) \
|
||
do { \
|
||
const char *v = GetImageProperty(image,"hdrgm:" name,exception); \
|
||
float value; \
|
||
if ((v != (const char *) NULL) && \
|
||
(ParseHDRGMProperty(v,&value) == MagickFalse)) \
|
||
{ \
|
||
(void) ThrowMagickException(exception,GetMagickModule(),OptionError, \
|
||
"InvalidArgument","`hdrgm:%s'",name); \
|
||
status=MagickFalse; \
|
||
} \
|
||
else if (v != (const char *) NULL) \
|
||
gainmap_info.field=value; \
|
||
} while (0)
|
||
#define GetHDRGMPropertyInt(name,field) \
|
||
do { \
|
||
const char *v = GetImageProperty(image,"hdrgm:" name,exception); \
|
||
if (v != (const char *) NULL) \
|
||
gainmap_info.field=atoi(v); \
|
||
} while (0)
|
||
#define GetHDRGMProperty3(name,field0,field1,field2) \
|
||
do { \
|
||
const char *v = GetImageProperty(image,"hdrgm:" name,exception); \
|
||
float values[3]; \
|
||
if ((v != (const char *) NULL) && \
|
||
(ParseHDRGMProperty3(v,values) == MagickFalse)) \
|
||
{ \
|
||
(void) ThrowMagickException(exception,GetMagickModule(),OptionError, \
|
||
"InvalidArgument","`hdrgm:%s'",name); \
|
||
status=MagickFalse; \
|
||
} \
|
||
else if (v != (const char *) NULL) \
|
||
{ \
|
||
gainmap_info.field0=values[0]; \
|
||
gainmap_info.field1=values[1]; \
|
||
gainmap_info.field2=values[2]; \
|
||
} \
|
||
} while (0)
|
||
|
||
Image
|
||
*image = images;
|
||
|
||
MagickBooleanType
|
||
status = MagickTrue;
|
||
|
||
uhdr_raw_image_t
|
||
hdrImgDescriptor = { 0 },
|
||
sdrImgDescriptor = { 0 };
|
||
|
||
uhdr_mem_block_t
|
||
sdr_profile = { 0 },
|
||
hdr_profile = { 0 };
|
||
|
||
StringInfo
|
||
*base_image_profile = (StringInfo *) NULL,
|
||
*resized_gainmap_profile = (StringInfo *) NULL;
|
||
|
||
uhdr_compressed_image_t
|
||
base_image = { 0 },
|
||
gainmap_image = { 0 };
|
||
|
||
uhdr_gainmap_metadata_t
|
||
gainmap_info = { 0 };
|
||
|
||
MagickBooleanType
|
||
gainmap_transform_status = MagickTrue,
|
||
preserve_gainmap = MagickFalse;
|
||
|
||
assert(image_info != (const ImageInfo *) NULL);
|
||
assert(image_info->signature == MagickCoreSignature);
|
||
assert(image != (Image *) NULL);
|
||
assert(image->signature == MagickCoreSignature);
|
||
if (IsEventLogging() != MagickFalse)
|
||
(void) LogMagickEvent(TraceEvent, GetMagickModule(), "%s", image->filename);
|
||
const StringInfo *gainmap_profile = GetImageProfile(image,"hdrgm");
|
||
|
||
const size_t
|
||
image_count = GetImageListLength(image);
|
||
|
||
if (gainmap_profile != (const StringInfo *) NULL)
|
||
{
|
||
/*
|
||
Preserve gainmap+metadata (for transcoding). If these exist, we do not
|
||
regenerate the gainmap. Instead, we pass them directly to the encoder.
|
||
*/
|
||
/*
|
||
Compressed image descriptor.
|
||
*/
|
||
resized_gainmap_profile=TransformGainMapProfile(image_info,image,
|
||
gainmap_profile,&gainmap_transform_status,exception);
|
||
if (gainmap_transform_status == MagickFalse)
|
||
{
|
||
status=MagickFalse;
|
||
goto cleanup;
|
||
}
|
||
if (resized_gainmap_profile != (StringInfo *) NULL)
|
||
gainmap_profile=(const StringInfo *) resized_gainmap_profile;
|
||
base_image_profile=EncodeBaseImageProfile(image_info,image,exception);
|
||
if (base_image_profile == (StringInfo *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
goto cleanup;
|
||
}
|
||
base_image.data=(void *) GetStringInfoDatum(base_image_profile);
|
||
base_image.data_sz=GetStringInfoLength(base_image_profile);
|
||
base_image.capacity=base_image.data_sz;
|
||
base_image.cg=getImageColorGamut(&image->chromaticity);
|
||
base_image.ct=UHDR_CT_SRGB;
|
||
base_image.range=UHDR_CR_FULL_RANGE;
|
||
gainmap_image.data=(void *) GetStringInfoDatum(gainmap_profile);
|
||
gainmap_image.data_sz=GetStringInfoLength(gainmap_profile);
|
||
gainmap_image.capacity=gainmap_image.data_sz;
|
||
gainmap_image.cg=UHDR_CG_UNSPECIFIED;
|
||
gainmap_image.ct=UHDR_CT_UNSPECIFIED;
|
||
gainmap_image.range=UHDR_CR_UNSPECIFIED;
|
||
/*
|
||
Gainmap metadata descriptor.
|
||
*/
|
||
GetHDRGMProperty3("GainMapMax",max_content_boost[0],max_content_boost[1],
|
||
max_content_boost[2]);
|
||
GetHDRGMProperty3("GainMapMin",min_content_boost[0],min_content_boost[1],
|
||
min_content_boost[2]);
|
||
GetHDRGMProperty3("Gamma",gamma[0],gamma[1],gamma[2]);
|
||
GetHDRGMProperty3("OffsetSDR",offset_sdr[0],offset_sdr[1],offset_sdr[2]);
|
||
GetHDRGMProperty3("OffsetHDR",offset_hdr[0],offset_hdr[1],offset_hdr[2]);
|
||
GetHDRGMProperty("HDRCapacityMin",hdr_capacity_min);
|
||
GetHDRGMProperty("HDRCapacityMax",hdr_capacity_max);
|
||
GetHDRGMPropertyInt("UseBaseColorGrade",use_base_cg);
|
||
if (status == MagickFalse)
|
||
goto cleanup;
|
||
preserve_gainmap=MagickTrue;
|
||
}
|
||
|
||
const char
|
||
*option = GetImageOption(image_info,"uhdr:hdr-color-transfer");
|
||
|
||
uhdr_color_transfer_t
|
||
hdr_ct = (option != (const char *) NULL) ? map_ct_to_uhdr_ct(option) : UHDR_CT_SRGB;
|
||
|
||
if (hdr_ct == UHDR_CT_UNSPECIFIED)
|
||
{
|
||
(void) ThrowMagickException(exception,GetMagickModule(),ConfigureWarning,
|
||
"invalid hdr color transfer received, ","%s","exiting ... ");
|
||
status=MagickFalse;
|
||
goto cleanup;
|
||
}
|
||
|
||
/*
|
||
HDR intent:
|
||
color transfer linear, MUST be rgba half float - bitdepth is ATLEAST 16
|
||
color transfer hlg or pq, MUST be rgba1010102/p010-bitdepth is ATLEAST 10
|
||
|
||
SDR intent
|
||
color transfer sRGB MUST be rgba8888/yuv420 - bitdepth MUST be 8
|
||
*/
|
||
int
|
||
hdrIntentMinDepth = hdr_ct == UHDR_CT_LINEAR ? 16 : 10;
|
||
|
||
for (int i = 0; i < (ssize_t) image_count; i++)
|
||
{
|
||
/* Classify image as hdr/sdr intent basing on depth */
|
||
int
|
||
bpp;
|
||
|
||
ssize_t
|
||
aligned_height,
|
||
aligned_width;
|
||
|
||
size_t
|
||
picSize;
|
||
|
||
void
|
||
*crBuffer = NULL, *cbBuffer = NULL, *yBuffer = NULL;
|
||
|
||
if (((double) image->columns > sqrt(MAGICK_SSIZE_MAX/3.0)) ||
|
||
((double) image->rows > sqrt(MAGICK_SSIZE_MAX/3.0)))
|
||
{
|
||
(void) ThrowMagickException(exception,GetMagickModule(),ImageError,
|
||
"WidthOrHeightExceedsLimit","%s",image->filename);
|
||
goto next_image;
|
||
}
|
||
bpp = (int) image->depth >= hdrIntentMinDepth ? 2 : 1;
|
||
if (IssRGBCompatibleColorspace(image->colorspace) && !IsGrayColorspace(image->colorspace))
|
||
{
|
||
if ((int) image->depth >= hdrIntentMinDepth && hdr_ct == UHDR_CT_LINEAR)
|
||
bpp = 8; /* rgbahalf float */
|
||
else
|
||
bpp = 4; /* rgba1010102 or rgba8888 */
|
||
}
|
||
else if (IsYCbCrCompatibleColorspace(image->colorspace))
|
||
{
|
||
if ((int) image->depth >= hdrIntentMinDepth && hdr_ct == UHDR_CT_LINEAR)
|
||
{
|
||
(void) ThrowMagickException(exception, GetMagickModule(), ConfigureWarning,
|
||
"linear color transfer inputs MUST be compatible with RGB Colorspace, ", "%s",
|
||
"ignoring ...");
|
||
goto next_image;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
(void) ThrowMagickException(exception, GetMagickModule(), ConfigureWarning,
|
||
"Received image with color space incompatible with RGB/YCbCr, ","%s","ignoring ...");
|
||
goto next_image;
|
||
}
|
||
|
||
aligned_width = image->columns + (image->columns & 1);
|
||
aligned_height = image->rows + (image->rows & 1);
|
||
if (HeapOverflowSanityCheckGetSize(aligned_width,aligned_height,&picSize) != MagickFalse)
|
||
{
|
||
(void) ThrowMagickException(exception,GetMagickModule(),
|
||
CorruptImageError,"ImproperImageHeader","%s",image->filename);
|
||
goto next_image;
|
||
}
|
||
if (HeapOverflowSanityCheckGetSize(picSize,bpp,&picSize) != MagickFalse)
|
||
{
|
||
(void) ThrowMagickException(exception,GetMagickModule(),
|
||
CorruptImageError,"ImproperImageHeader","%s",image->filename);
|
||
goto next_image;
|
||
}
|
||
if (bpp < 4)
|
||
{
|
||
if (HeapOverflowSanityCheckGetSize(picSize,3,&picSize) != MagickFalse)
|
||
{
|
||
(void) ThrowMagickException(exception,GetMagickModule(),
|
||
CorruptImageError,"ImproperImageHeader","%s",image->filename);
|
||
goto next_image;
|
||
}
|
||
picSize/=2;
|
||
}
|
||
|
||
if (((int) image->depth < hdrIntentMinDepth) && (image->depth != 8))
|
||
{
|
||
(void) ThrowMagickException(exception, GetMagickModule(), ConfigureWarning,
|
||
"Received image with unexpected bit depth","%s","ignoring ...");
|
||
goto next_image;
|
||
}
|
||
|
||
if (((int) image->depth >= hdrIntentMinDepth) &&
|
||
(hdrImgDescriptor.planes[UHDR_PLANE_Y] != NULL))
|
||
{
|
||
(void) ThrowMagickException(exception, GetMagickModule(), ConfigureWarning,
|
||
"Received multiple hdr intent resources, ","%s","overwriting ...");
|
||
RelinquishMagickMemory(hdrImgDescriptor.planes[UHDR_PLANE_Y]);
|
||
hdrImgDescriptor.planes[UHDR_PLANE_Y] = NULL;
|
||
}
|
||
else if ((image->depth == 8) &&
|
||
(sdrImgDescriptor.planes[UHDR_PLANE_Y] != NULL))
|
||
{
|
||
(void) ThrowMagickException(exception,GetMagickModule(),ConfigureWarning,
|
||
"Received multiple sdr intent resources, ","%s","overwriting ...");
|
||
RelinquishMagickMemory(sdrImgDescriptor.planes[UHDR_PLANE_Y]);
|
||
sdrImgDescriptor.planes[UHDR_PLANE_Y] = NULL;
|
||
}
|
||
|
||
yBuffer = AcquireQuantumMemory(picSize,1);
|
||
if (yBuffer == NULL)
|
||
{
|
||
status = MagickFalse;
|
||
break;
|
||
}
|
||
|
||
if ((int) image->depth >= hdrIntentMinDepth)
|
||
{
|
||
if (IsYCbCrCompatibleColorspace(image->colorspace))
|
||
{
|
||
cbBuffer = ((uint16_t *) yBuffer) + aligned_width * aligned_height;
|
||
crBuffer = ((uint16_t *) cbBuffer) + 1;
|
||
|
||
fillRawImageDescriptor(&hdrImgDescriptor, image_info, image, UHDR_IMG_FMT_24bppYCbCrP010,
|
||
hdr_ct);
|
||
|
||
hdrImgDescriptor.planes[UHDR_PLANE_Y] = yBuffer;
|
||
hdrImgDescriptor.planes[UHDR_PLANE_UV] = cbBuffer;
|
||
hdrImgDescriptor.planes[UHDR_PLANE_V] = NULL;
|
||
|
||
hdrImgDescriptor.stride[UHDR_PLANE_Y] = aligned_width;
|
||
hdrImgDescriptor.stride[UHDR_PLANE_UV] = aligned_width;
|
||
hdrImgDescriptor.stride[UHDR_PLANE_V] = 0;
|
||
}
|
||
else
|
||
{
|
||
fillRawImageDescriptor(&hdrImgDescriptor, image_info, image,
|
||
hdr_ct == UHDR_CT_LINEAR ? UHDR_IMG_FMT_64bppRGBAHalfFloat
|
||
: UHDR_IMG_FMT_32bppRGBA1010102,
|
||
hdr_ct);
|
||
|
||
hdrImgDescriptor.planes[UHDR_PLANE_PACKED] = yBuffer;
|
||
hdrImgDescriptor.planes[UHDR_PLANE_U] = NULL;
|
||
hdrImgDescriptor.planes[UHDR_PLANE_V] = NULL;
|
||
|
||
hdrImgDescriptor.stride[UHDR_PLANE_PACKED] = aligned_width;
|
||
hdrImgDescriptor.stride[UHDR_PLANE_U] = 0;
|
||
hdrImgDescriptor.stride[UHDR_PLANE_V] = 0;
|
||
}
|
||
|
||
hdr_profile = GetExifProfile(image, exception);
|
||
}
|
||
else if (image->depth == 8)
|
||
{
|
||
if (IsYCbCrCompatibleColorspace(image->colorspace))
|
||
{
|
||
cbBuffer = ((uint8_t *) yBuffer) + aligned_width * aligned_height;
|
||
crBuffer = ((uint8_t *) cbBuffer) + ((aligned_width / 2) * (aligned_height / 2));
|
||
|
||
fillRawImageDescriptor(&sdrImgDescriptor, image_info, image, UHDR_IMG_FMT_12bppYCbCr420,
|
||
UHDR_CT_SRGB);
|
||
|
||
sdrImgDescriptor.planes[UHDR_PLANE_Y] = yBuffer;
|
||
sdrImgDescriptor.planes[UHDR_PLANE_U] = cbBuffer;
|
||
sdrImgDescriptor.planes[UHDR_PLANE_V] = crBuffer;
|
||
|
||
sdrImgDescriptor.stride[UHDR_PLANE_Y] = aligned_width;
|
||
sdrImgDescriptor.stride[UHDR_PLANE_U] = aligned_width / 2;
|
||
sdrImgDescriptor.stride[UHDR_PLANE_V] = aligned_width / 2;
|
||
}
|
||
else
|
||
{
|
||
fillRawImageDescriptor(&sdrImgDescriptor, image_info, image, UHDR_IMG_FMT_32bppRGBA8888,
|
||
UHDR_CT_SRGB);
|
||
|
||
sdrImgDescriptor.planes[UHDR_PLANE_PACKED] = yBuffer;
|
||
sdrImgDescriptor.planes[UHDR_PLANE_U] = NULL;
|
||
sdrImgDescriptor.planes[UHDR_PLANE_V] = NULL;
|
||
|
||
sdrImgDescriptor.stride[UHDR_PLANE_PACKED] = aligned_width;
|
||
sdrImgDescriptor.stride[UHDR_PLANE_U] = 0;
|
||
sdrImgDescriptor.stride[UHDR_PLANE_V] = 0;
|
||
}
|
||
|
||
sdr_profile = GetExifProfile(image, exception);
|
||
}
|
||
|
||
for (int y = 0; y < (ssize_t) image->rows; y++)
|
||
{
|
||
const Quantum
|
||
*p;
|
||
|
||
ssize_t
|
||
x;
|
||
|
||
p = GetVirtualPixels(image, 0, y, image->columns, 1, exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status = MagickFalse;
|
||
break;
|
||
}
|
||
|
||
for (x = 0; x < (ssize_t) image->columns; x++)
|
||
{
|
||
if ((int) image->depth >= hdrIntentMinDepth)
|
||
{
|
||
if (hdrImgDescriptor.fmt == UHDR_IMG_FMT_24bppYCbCrP010)
|
||
{
|
||
uint16_t
|
||
*crBase = crBuffer, *cbBase = cbBuffer, *yBase = yBuffer;
|
||
|
||
yBase[y * hdrImgDescriptor.stride[UHDR_PLANE_Y] + x] =
|
||
ScaleQuantumToShort(GetPixelY(image, p)) & 0xFFC0;
|
||
if ((y % 2 == 0) && (x % 2 == 0))
|
||
{
|
||
cbBase[y / 2 * hdrImgDescriptor.stride[UHDR_PLANE_UV] + x] =
|
||
ScaleQuantumToShort(GetPixelCb(image, p)) & 0xFFC0;
|
||
crBase[y / 2 * hdrImgDescriptor.stride[UHDR_PLANE_UV] + x] =
|
||
ScaleQuantumToShort(GetPixelCr(image, p)) & 0xFFC0;
|
||
}
|
||
}
|
||
else if (hdrImgDescriptor.fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat)
|
||
{
|
||
uint64_t
|
||
*rgbaBase = yBuffer;
|
||
|
||
unsigned short
|
||
r, g, b, a;
|
||
|
||
r = SinglePrecisionToHalf(QuantumScale * GetPixelRed(image, p));
|
||
g = SinglePrecisionToHalf(QuantumScale * GetPixelGreen(image, p));
|
||
b = SinglePrecisionToHalf(QuantumScale * GetPixelBlue(image, p));
|
||
a = SinglePrecisionToHalf(QuantumScale * GetPixelAlpha(image, p));
|
||
|
||
rgbaBase[y * hdrImgDescriptor.stride[UHDR_PLANE_PACKED] + x] =
|
||
((uint64_t)a << 48) | ((uint64_t)b << 32) | (g << 16) | (r);
|
||
}
|
||
else
|
||
{
|
||
uint32_t
|
||
*rgbBase = yBuffer;
|
||
|
||
unsigned short
|
||
r, g, b;
|
||
|
||
r = ScaleQuantumToShort(GetPixelRed(image, p)) & 0xFFC0;
|
||
g = ScaleQuantumToShort(GetPixelGreen(image, p)) & 0xFFC0;
|
||
b = ScaleQuantumToShort(GetPixelBlue(image, p)) & 0xFFC0;
|
||
|
||
rgbBase[y * hdrImgDescriptor.stride[UHDR_PLANE_PACKED] + x] =
|
||
(0x3U << 30) | (b << 14) | (g << 4) | (r >> 6);
|
||
}
|
||
}
|
||
else if (image->depth == 8)
|
||
{
|
||
if (sdrImgDescriptor.fmt == UHDR_IMG_FMT_12bppYCbCr420)
|
||
{
|
||
uint8_t
|
||
*crBase = crBuffer, *cbBase = cbBuffer, *yBase = yBuffer;
|
||
|
||
yBase[y * sdrImgDescriptor.stride[UHDR_PLANE_Y] + x] =
|
||
ScaleQuantumToChar(GetPixelY(image, p));
|
||
if ((y % 2 == 0) && (x % 2 == 0))
|
||
{
|
||
cbBase[y / 2 * sdrImgDescriptor.stride[UHDR_PLANE_U] + x / 2] =
|
||
ScaleQuantumToChar(GetPixelCb(image, p));
|
||
crBase[y / 2 * sdrImgDescriptor.stride[UHDR_PLANE_V] + x / 2] =
|
||
ScaleQuantumToChar(GetPixelCr(image, p));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
uint32_t
|
||
*rgbBase = yBuffer;
|
||
|
||
unsigned char
|
||
r, g, b;
|
||
|
||
r = ScaleQuantumToChar(GetPixelRed(image, p));
|
||
g = ScaleQuantumToChar(GetPixelGreen(image, p));
|
||
b = ScaleQuantumToChar(GetPixelBlue(image, p));
|
||
|
||
rgbBase[y * sdrImgDescriptor.stride[UHDR_PLANE_PACKED] + x] = (b << 16) | (g << 8) | r;
|
||
}
|
||
}
|
||
p += GetPixelChannels(image);
|
||
}
|
||
}
|
||
|
||
next_image:
|
||
if (i != (ssize_t) image_count - 1)
|
||
{
|
||
if (GetNextImageInList(image) == (Image *) NULL)
|
||
{
|
||
status = MagickFalse;
|
||
break;
|
||
}
|
||
image = SyncNextImageInList(image);
|
||
if (image == (Image *) NULL)
|
||
break;
|
||
}
|
||
|
||
status = SetImageProgress(image, SaveImageTag, (MagickOffsetType)i,
|
||
image_count);
|
||
if (status == MagickFalse)
|
||
break;
|
||
}
|
||
|
||
if (status != MagickFalse)
|
||
{
|
||
uhdr_codec_private_t *handle = uhdr_create_encoder();
|
||
|
||
#define CHECK_IF_ERR(x) \
|
||
{ \
|
||
uhdr_error_info_t retval = (x); \
|
||
if (retval.error_code != UHDR_CODEC_OK) \
|
||
{ \
|
||
(void)ThrowMagickException(exception, GetMagickModule(), CoderError, \
|
||
retval.has_detail ? retval.detail : "unknown error", "`%s'", \
|
||
image->filename); \
|
||
uhdr_release_encoder(handle); \
|
||
status = MagickFalse; \
|
||
} \
|
||
}
|
||
|
||
if (handle == NULL)
|
||
{
|
||
(void) ThrowMagickException(exception,GetMagickModule(),CoderError,
|
||
"FailedToCreateEncoder","`%s'",image->filename);
|
||
status=MagickFalse;
|
||
}
|
||
|
||
if ((status != MagickFalse) && (preserve_gainmap != MagickFalse))
|
||
{
|
||
CHECK_IF_ERR(uhdr_enc_set_compressed_image(handle,&base_image,
|
||
UHDR_BASE_IMG))
|
||
if (status != MagickFalse)
|
||
CHECK_IF_ERR(uhdr_enc_set_gainmap_image(handle,&gainmap_image,
|
||
&gainmap_info))
|
||
}
|
||
else
|
||
{
|
||
/* Configure hdr and sdr intents */
|
||
if (status != MagickFalse && hdrImgDescriptor.planes[UHDR_PLANE_Y])
|
||
{
|
||
CHECK_IF_ERR(uhdr_enc_set_raw_image(handle, &hdrImgDescriptor, UHDR_HDR_IMG))
|
||
if ((status != MagickFalse) && (hdr_profile.data_sz != 0))
|
||
CHECK_IF_ERR(uhdr_enc_set_exif_data(handle, &hdr_profile))
|
||
}
|
||
|
||
if (status != MagickFalse && sdrImgDescriptor.planes[UHDR_PLANE_Y])
|
||
{
|
||
CHECK_IF_ERR(uhdr_enc_set_raw_image(handle, &sdrImgDescriptor, UHDR_SDR_IMG))
|
||
if ((status != MagickFalse) && (sdr_profile.data_sz != 0))
|
||
CHECK_IF_ERR(uhdr_enc_set_exif_data(handle, &sdr_profile))
|
||
}
|
||
}
|
||
|
||
/* Configure encoding settings */
|
||
if (status != MagickFalse && image->quality > 0 && image->quality <= 100)
|
||
CHECK_IF_ERR(uhdr_enc_set_quality(handle, image->quality, UHDR_BASE_IMG))
|
||
|
||
const char
|
||
*option;
|
||
|
||
if ((status != MagickFalse) && (preserve_gainmap == MagickFalse))
|
||
{
|
||
option = GetImageOption(image_info, "uhdr:gainmap-quality");
|
||
if (option != (const char *)NULL)
|
||
CHECK_IF_ERR(uhdr_enc_set_quality(handle, atoi(option), UHDR_GAIN_MAP_IMG))
|
||
}
|
||
|
||
if ((status != MagickFalse) && (preserve_gainmap == MagickFalse))
|
||
CHECK_IF_ERR(uhdr_enc_set_using_multi_channel_gainmap(handle, 1))
|
||
|
||
if ((status != MagickFalse) && (preserve_gainmap == MagickFalse))
|
||
CHECK_IF_ERR(uhdr_enc_set_gainmap_scale_factor(handle, 1))
|
||
|
||
if ((status != MagickFalse) && (preserve_gainmap == MagickFalse))
|
||
CHECK_IF_ERR(uhdr_enc_set_preset(handle, UHDR_USAGE_BEST_QUALITY))
|
||
|
||
/* Configure gainmap metadata */
|
||
if ((status != MagickFalse) && (preserve_gainmap == MagickFalse))
|
||
{
|
||
option = GetImageOption(image_info, "uhdr:gainmap-gamma");
|
||
if (option != (const char *)NULL)
|
||
CHECK_IF_ERR(uhdr_enc_set_gainmap_gamma(handle, atof(option)))
|
||
}
|
||
|
||
if ((status != MagickFalse) && (preserve_gainmap == MagickFalse))
|
||
{
|
||
option = GetImageOption(image_info, "uhdr:gainmap-min-content-boost");
|
||
float minContentBoost = option != (const char *)NULL ? atof(option) : FLT_MIN;
|
||
|
||
option = GetImageOption(image_info, "uhdr:gainmap-max-content-boost");
|
||
float maxContentBoost = option != (const char *)NULL ? atof(option) : FLT_MAX;
|
||
|
||
if (minContentBoost != FLT_MIN || maxContentBoost != FLT_MAX)
|
||
CHECK_IF_ERR(uhdr_enc_set_min_max_content_boost(handle, minContentBoost, maxContentBoost))
|
||
}
|
||
|
||
if ((status != MagickFalse) && (preserve_gainmap == MagickFalse))
|
||
{
|
||
option = GetImageOption(image_info, "uhdr:target-display-peak-brightness");
|
||
float targetDispPeakBrightness = option != (const char *)NULL ? atof(option) : -1.0f;
|
||
|
||
if (targetDispPeakBrightness > 0.0f)
|
||
|
||
CHECK_IF_ERR(uhdr_enc_set_target_display_peak_brightness(handle, targetDispPeakBrightness))
|
||
}
|
||
|
||
if (status != MagickFalse)
|
||
CHECK_IF_ERR(uhdr_encode(handle))
|
||
|
||
if (status != MagickFalse)
|
||
{
|
||
uhdr_compressed_image_t *output = uhdr_get_encoded_stream(handle);
|
||
|
||
status=OpenBlob(image_info,images,WriteBinaryBlobMode,exception);
|
||
if (status != MagickFalse)
|
||
{
|
||
if (WriteBlob(images,output->data_sz,output->data) !=
|
||
(ssize_t) output->data_sz)
|
||
status=MagickFalse;
|
||
if (CloseBlob(images) == MagickFalse)
|
||
status=MagickFalse;
|
||
}
|
||
|
||
uhdr_release_encoder(handle);
|
||
}
|
||
}
|
||
#undef CHECK_IF_ERR
|
||
|
||
cleanup:
|
||
if (hdrImgDescriptor.planes[UHDR_PLANE_Y])
|
||
RelinquishMagickMemory(hdrImgDescriptor.planes[UHDR_PLANE_Y]);
|
||
|
||
if (sdrImgDescriptor.planes[UHDR_PLANE_Y])
|
||
RelinquishMagickMemory(sdrImgDescriptor.planes[UHDR_PLANE_Y]);
|
||
|
||
if (resized_gainmap_profile != (StringInfo *) NULL)
|
||
resized_gainmap_profile=DestroyStringInfo(resized_gainmap_profile);
|
||
|
||
if (base_image_profile != (StringInfo *) NULL)
|
||
base_image_profile=DestroyStringInfo(base_image_profile);
|
||
|
||
return status;
|
||
}
|
||
#endif
|