/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%                       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-private.h"
#include "MagickCore/string_.h"
#include "MagickCore/string-private.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),"%f",(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 SetHDRGMProperty3(name,value,value1,value2) \
  (void) FormatLocaleString(buffer,sizeof(buffer),"%f,%f,%f", \
     (value),(value1),(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;

  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 ((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);
    }
  }

  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 (exif != NULL)
  {
    StringInfo *exif_data = BlobToProfileStringInfo("exif",exif->data,
      exif->data_sz,exception);
    (void) SetImageProfilePrivate(image,exif_data,exception);
  }

  SetImageColorspace(image, RGBColorspace, exception);

  if (decoded_img_ct == UHDR_CT_LINEAR)
    image->gamma = 1.0;

  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: %.20g 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 MagickBooleanType WriteUHDRImage(const ImageInfo *image_info,
  Image *images,ExceptionInfo *exception)
{
#define GetHDRGMProperty(name,field) \
  do { \
    const char *v = GetImageProperty(image,"hdrgm:" name,exception); \
    if (v != (const char *) NULL) \
      gainmap_info.field=(float) atof(v); \
  } 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); \
    if (v != (const char *) NULL) \
      (void) sscanf(v,"%f,%f,%f",&gainmap_info.field0,&gainmap_info.field1, \
        &gainmap_info.field2); \
  } while (0)

  Image
    *image = images;

  MagickBooleanType
    status = MagickTrue;

  uhdr_raw_image_t
    hdrImgDescriptor = { 0 },
    sdrImgDescriptor = { 0 };

  uhdr_mem_block_t
    sdr_profile, hdr_profile;

  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);
  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
  if (status == MagickFalse)
    return (status);

  const StringInfo *gainmap_profile = GetImageProfile(image,"hdrgm");

  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.
      */
      uhdr_compressed_image_t gainmap_image = { 0 };
      uhdr_gainmap_metadata_t gainmap_info = { 0 };

      /*
        Compressed image descriptor.
      */
      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("GainMapMax",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);
      /*
        Set gainmap.
      */
      uhdr_codec_private_t *encoder = uhdr_create_encoder();
      if (encoder == NULL)
        {
          ThrowMagickException(exception,GetMagickModule(),CoderError,
            "FailedToCreateEncoder","`%s'",image->filename);
          return(MagickFalse);
        }
      uhdr_enc_set_gainmap_image(encoder,&gainmap_image,&gainmap_info);
    }

  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 ... ");
      return(MagickFalse);
    }

  /*
    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) GetImageListLength(image); 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 = AcquireMagickMemory(picSize);
    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) GetImageListLength(image) - 1)
    {
      if (GetNextImageInList(image) == (Image *) NULL)
      {
        status = MagickFalse;
        break;
      }
      image = SyncNextImageInList(image);
      if (image == (Image *) NULL)
        break;
    }

    status = SetImageProgress(image, SaveImageTag, (MagickOffsetType)i,
      GetImageListLength(image));
    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;                                                                   \
    }                                                                                         \
  }

    /* 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 (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 (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)
    {
      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)
      CHECK_IF_ERR(uhdr_enc_set_using_multi_channel_gainmap(handle, 1))

    if (status != MagickFalse)
      CHECK_IF_ERR(uhdr_enc_set_gainmap_scale_factor(handle, 1))

    if (status != MagickFalse)
      CHECK_IF_ERR(uhdr_enc_set_preset(handle, UHDR_USAGE_BEST_QUALITY))

    /* Configure gainmap metadata */
    if (status != 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)
    {
      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)
    {
      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);

      (void) WriteBlob(image, output->data_sz, output->data);

      uhdr_release_encoder(handle);
    }
  }
#undef CHECK_IF_ERR

  if (CloseBlob(image) == MagickFalse)
    status = MagickFalse;

  if (hdrImgDescriptor.planes[UHDR_PLANE_Y])
    RelinquishMagickMemory(hdrImgDescriptor.planes[UHDR_PLANE_Y]);

  if (sdrImgDescriptor.planes[UHDR_PLANE_Y])
    RelinquishMagickMemory(sdrImgDescriptor.planes[UHDR_PLANE_Y]);

  return status;
}
#endif
