/*
% Copyright (C) 2003 - 2019 GraphicsMagick Group
% Copyright (C) 2003 ImageMagick Studio
% Copyright 1991-1999 E. I. du Pont de Nemours and Company
%
% This program is covered by multiple licenses, which are described in
% Copyright.txt. You should have received a copy of Copyright.txt with this
% package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                  Methods to transform the image colorspace                  %
%                                                                             %
%                                                                             %
%                              Software Design                                %
%                                John Cristy                                  %
%                                 July 1992                                   %
%                              Bob Friesenhahn                                %
%                                 March 2003                                  %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%
*/

/*
  Include declarations.
*/
#include "magick/studio.h"
#include "magick/attribute.h"
#include "magick/color.h"
#include "magick/colorspace.h"
#include "magick/enum_strings.h"
#include "magick/gem.h"
#include "magick/log.h"
#include "magick/monitor.h"
#include "magick/pixel_iterator.h"
#include "magick/utility.h"

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+     R G B T r a n s f o r m I m a g e                                       %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method RGBTransformImage converts the reference image from RGB to
%  an alternate colorspace.  The transformation matrices are not the standard
%  ones: the weights are rescaled to normalize the range of the transformed
%  values to be [0..MaxRGB].
%
%  The format of the RGBTransformImage method is:
%
%      unsigned int RGBTransformImage(Image *image,
%        const ColorspaceType colorspace)
%
%  A description of each parameter follows:
%
%    o image: the image
%
%    o colorspace: the colorspace to transform the image to.
%
%
*/

static MagickPassFail
RGBToCMYKTransform(void *mutable_data,          /* User provided mutable data */
                   const void *immutable_data,  /* User provided immutable data */
                   Image * restrict image,                /* Modify image */
                   PixelPacket * restrict pixels,         /* Pixel row */
                   IndexPacket * restrict indexes,        /* Pixel row indexes */
                   const long npixels,          /* Number of pixels in row */
                   ExceptionInfo *exception)    /* Exception report */
{
  /*
    Transform RGB to CMYK(A) pixels.
  */
  register long
    i;

  Quantum
    black,
    cyan,
    magenta,
    yellow;

  ARG_NOT_USED(mutable_data);
  ARG_NOT_USED(immutable_data);
  ARG_NOT_USED(image);
  ARG_NOT_USED(exception);

  for (i=0; i < npixels; i++)
    {
      cyan=(Quantum) (MaxRGB-pixels[i].red);
      magenta=(Quantum) (MaxRGB-pixels[i].green);
      yellow=(Quantum) (MaxRGB-pixels[i].blue);
      black=(cyan < magenta ? Min(cyan,yellow) : Min(magenta,yellow));
      pixels[i].red=cyan;
      pixels[i].green=magenta;
      pixels[i].blue=yellow;
      indexes[i]=pixels[i].opacity;
      pixels[i].opacity=black;
    }

  return MagickPass;
}

static MagickPassFail
RGBToCineonLogTransform(void *mutable_data,          /* User provided mutable data */
                        const void *immutable_data,  /* User provided immutable data */
                        Image * restrict image,                /* Modify image */
                        PixelPacket * restrict pixels,         /* Pixel row */
                        IndexPacket * restrict indexes,        /* Pixel row indexes */
                        const long npixels,          /* Number of pixels in row */
                        ExceptionInfo *exception)    /* Exception report */
{
  /*
    Transform RGB pixels to CineonLog based on an existing lookup
    table.
  */
  const unsigned int
    *logmap = (const unsigned int *) immutable_data;

  register long
    i;

  ARG_NOT_USED(mutable_data);
  ARG_NOT_USED(image);
  ARG_NOT_USED(indexes);
  ARG_NOT_USED(exception);

  for (i=0; i < npixels; i++)
    {
      pixels[i].red   = logmap[ScaleQuantumToMap(pixels[i].red)];
      pixels[i].green = logmap[ScaleQuantumToMap(pixels[i].green)];
      pixels[i].blue  = logmap[ScaleQuantumToMap(pixels[i].blue)];
    }

  return MagickPass;
}

static MagickPassFail
RGBToHSLTransform(void *mutable_data,          /* User provided mutable data */
                  const void *immutable_data,  /* User provided immutable data */
                  Image * restrict image,                /* Modify image */
                  PixelPacket * restrict pixels,         /* Pixel row */
                  IndexPacket * restrict indexes,        /* Pixel row indexes */
                  const long npixels,          /* Number of pixels in row */
                  ExceptionInfo *exception)    /* Exception report */
{
  /*
    Transform pixels from RGB space to HSL space.
  */
  double
    h,
    s,
    l;

  register long
    i;

  ARG_NOT_USED(mutable_data);
  ARG_NOT_USED(immutable_data);
  ARG_NOT_USED(image);
  ARG_NOT_USED(indexes);
  ARG_NOT_USED(exception);

  for (i=0; i < npixels; i++)
    {
      TransformHSL(pixels[i].red,pixels[i].green,pixels[i].blue,&h,&s,&l);
      h *= MaxRGB;
      s *= MaxRGB;
      l *= MaxRGB;
      pixels[i].red=RoundDoubleToQuantum(h);
      pixels[i].green=RoundDoubleToQuantum(s);
      pixels[i].blue=RoundDoubleToQuantum(l);
    }

  return MagickPass;
}

static MagickPassFail
RGBToHWBTransform(void *mutable_data,          /* User provided mutable data */
                  const void *immutable_data,  /* User provided immutable data */
                  Image * restrict image,                /* Modify image */
                  PixelPacket * restrict pixels,         /* Pixel row */
                  IndexPacket * restrict indexes,        /* Pixel row indexes */
                  const long npixels,          /* Number of pixels in row */
                  ExceptionInfo *exception)    /* Exception report */
{
  /*
    Transform pixels from RGB space to HWB space.
  */
  double
    h,
    w,
    b;

  register long
    i;

  ARG_NOT_USED(mutable_data);
  ARG_NOT_USED(immutable_data);
  ARG_NOT_USED(image);
  ARG_NOT_USED(indexes);
  ARG_NOT_USED(exception);

  for (i=0; i < npixels; i++)
    {
      TransformHWB(pixels[i].red,pixels[i].green,pixels[i].blue,&h,&w,&b);
      h *= MaxRGB;
      w *= MaxRGB;
      b *= MaxRGB;
      pixels[i].red=RoundDoubleToQuantum(h);
      pixels[i].green=RoundDoubleToQuantum(w);
      pixels[i].blue=RoundDoubleToQuantum(b);
    }

  return MagickPass;
}

typedef struct _XYZColorTransformPacket
{
  float
    x,
    y,
    z;
} XYZColorTransformPacket;

typedef struct _XYZColorTransformInfo_t
{
  XYZColorTransformPacket *x;
  XYZColorTransformPacket *y;
  XYZColorTransformPacket *z;
  XYZColorTransformPacket primary_info;
} XYZColorTransformInfo_t;

static const size_t
  XYZMapAllocSize=(MaxMap+1)*sizeof(XYZColorTransformPacket);


static MagickPassFail
XYZTransformPackets(void *mutable_data,          /* User provided mutable data */
                    const void *immutable_data,  /* User provided immutable data */
                    Image * restrict image,                /* Modify image */
                    PixelPacket * restrict pixels,         /* Pixel row */
                    IndexPacket * restrict indexes,        /* Pixel row indexes */
                    const long npixels,          /* Number of pixels in row */
                    ExceptionInfo *exception)    /* Exception report */
{
  /*
    3D transform pixels from RGB to alternate colorspace.
  */
  float
    b,
    g,
    r;

  const XYZColorTransformInfo_t
    *xform = (const XYZColorTransformInfo_t *) immutable_data;

  register long
    i;

  ARG_NOT_USED(mutable_data);
  ARG_NOT_USED(image);
  ARG_NOT_USED(indexes);
  ARG_NOT_USED(exception);

  for (i=0; i < npixels; i++)
    {
      register unsigned int
        x_index,
        y_index,
        z_index;

      x_index = ScaleQuantumToMap(pixels[i].red);
      y_index = ScaleQuantumToMap(pixels[i].green);
      z_index = ScaleQuantumToMap(pixels[i].blue);

      r = (xform->x[x_index].x + xform->y[y_index].x + xform->z[z_index].x + xform->primary_info.x);
      g = (xform->x[x_index].y + xform->y[y_index].y + xform->z[z_index].y + xform->primary_info.y);
      b = (xform->x[x_index].z + xform->y[y_index].z + xform->z[z_index].z + xform->primary_info.z);

      r = r < 0.0f ? 0.0f : r > MaxMapFloat ? MaxMapFloat : (r + 0.5f);
      g = g < 0.0f ? 0.0f : g > MaxMapFloat ? MaxMapFloat : (g + 0.5f);
      b = b < 0.0f ? 0.0f : b > MaxMapFloat ? MaxMapFloat : (b + 0.5f);

      pixels[i].red   = ScaleMapToQuantum((Quantum) r);
      pixels[i].green = ScaleMapToQuantum((Quantum) g);
      pixels[i].blue  = ScaleMapToQuantum((Quantum) b);
    }

  return MagickPass;
}

MagickExport MagickPassFail RGBTransformImage(Image *image,
                                              const ColorspaceType colorspace)
{
  char
    progress_message[MaxTextExtent];

  register long
    i;

  MagickPassFail
    status=MagickPass;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);

  /* Detect bogus request to convert to RGB */
  assert(colorspace != RGBColorspace);
  assert(colorspace != TransparentColorspace);
  assert(colorspace != UndefinedColorspace);

  /*
    Ensure that image is an RGB-compatible colorspace prior to
    transforming to an alternate colorspace.
  */
  if (!IsRGBColorspace(image->colorspace))
    (void) TransformRGBImage(image,image->colorspace);

  /*
    Log colorspace transform event
  */
  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                        "Transform colorspace from %s to %s",
                        ColorspaceTypeToString(image->colorspace),
                        ColorspaceTypeToString(colorspace));
  FormatString(progress_message,"[%%s] Transform colorspace from %s to %s...",
               ColorspaceTypeToString(image->colorspace),
               ColorspaceTypeToString(colorspace));

  /*
    Store colorspace in image.
  */
  image->colorspace=colorspace;

  if (colorspace == CMYKColorspace)
    {
      /*
        Transform RGB to CMYK(A) pixels.
      */
      if (image->storage_class == PseudoClass)
        {

          status=SyncImage(image);
          image->storage_class=DirectClass;
        }
      if (status != MagickFail)
        status=PixelIterateMonoModify(RGBToCMYKTransform,
                                      NULL,
                                      progress_message,
                                      NULL,NULL,
                                      0,0,image->columns,image->rows,
                                      image,
                                      &image->exception);
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                            "Colorspace transform completed");
      return(status);
    }

  if (colorspace == CineonLogRGBColorspace)
    {
      /*
        Transform from linear RGB to Cineon Log RGB.
      */
      double
        DisplayGamma,
        Gain,
        MaxLinearValue,
        NegativeFilmGamma,
        Offset,
        ReferenceBlack,
        ReferenceWhite;

      unsigned int
        *logmap,
        scale_to_short;

      /*
        Establish defaults.
      */
      MaxLinearValue=MaxRGB; /* Maximum linear value output */
      ReferenceWhite=685;    /* 90% white card (default 685) */
      ReferenceBlack=95;     /* 1% black card  (default 95) */
      DisplayGamma=1.7;      /* Typical display gamma (Kodak recommends 1.7) */
      NegativeFilmGamma=0.6; /* Typical gamma for a film negative */

      /*
        Allow image attributes to override defaults.
      */
      MagickAttributeToDouble(image,"reference-white",ReferenceWhite);
      MagickAttributeToDouble(image,"reference-black",ReferenceBlack);
      MagickAttributeToDouble(image,"display-gamma",DisplayGamma);
      MagickAttributeToDouble(image,"film-gamma",NegativeFilmGamma);

      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                            "Log Transform: ReferenceWhite=%g ReferenceBlack=%g DisplayGamma=%g FilmGamma=%g",
                            ReferenceWhite,ReferenceBlack,DisplayGamma,NegativeFilmGamma);

      /*
        FIXME: Math seems to be producing data with gamma 1.0 rather than 1.7.
      */

#if 1
      Gain=MaxLinearValue/(1.0 - pow(pow(10,((ReferenceBlack-ReferenceWhite)
                                             *0.002/NegativeFilmGamma)),
                                     (DisplayGamma/1.7)));
#else
      Gain=MaxLinearValue/(1.0 - pow(pow(10,((ReferenceBlack-ReferenceWhite)
                                             *0.002/NegativeFilmGamma)),
                                     (1.0/DisplayGamma)));
#endif
      Offset=Gain-MaxLinearValue;

      /*
        Build LUT
      */
      logmap=MagickAllocateMemory(unsigned int *,(MaxMap+1)*sizeof(unsigned int));
      if (logmap == 0)
        ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed,
                              UnableToTransformColorspace);
      scale_to_short=(65535U / (65535U >> (16-10)));
      for (i=0; i <= (long) MaxMap; i++)
        {
          double
            linearval,
            logval;

          linearval=i*(double) MaxRGB/MaxMap;

          /*
            FIXME: Math seems to be expecting data with gamma 1.0 rather than 1.7.
            Also, quantizing to 64K levels does not do justice to the Q32 build at all.
          */
          logval=685+log10(pow((((double) linearval+Offset)/Gain),
                               (1.7/DisplayGamma)))/(0.002/NegativeFilmGamma);

          /*           logval=685+log10(pow((((double) linearval+Offset)/Gain), */
          /*                                (DisplayGamma/1.0)))/(0.002/NegativeFilmGamma); */

          logval *= scale_to_short;
          logmap[i]=ScaleShortToQuantum((unsigned int) (logval + 0.5));
          /* printf("logmap[%u]=%u\n",i,(unsigned int) logmap[i]); */
        }
      /*
        Transform pixels.
      */
      if (image->storage_class == PseudoClass)
        {
          /*
            Convert PseudoClass image colormap.
          */
          (void) RGBToCineonLogTransform(0,
                                         logmap,
                                         image,
                                         image->colormap,
                                         (IndexPacket *) NULL,
                                         image->colors,
                                         &image->exception);
          status=SyncImage(image);
        }
      else
        {
          /*
            Convert DirectClass image.
          */
          status=PixelIterateMonoModify(RGBToCineonLogTransform,
                                        NULL,
                                        progress_message,
                                        NULL,logmap,
                                        0,0,image->columns,image->rows,
                                        image,
                                        &image->exception);
        }

      MagickFreeMemory(logmap);
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                            "Transform to colorspace %s completed",
                            ColorspaceTypeToString(colorspace));
      return(status);
    }


  if (colorspace == HSLColorspace)
    {
      if (image->storage_class == PseudoClass)
        {
          /*
            Convert PseudoClass image colormap.
          */
          RGBToHSLTransform(NULL,
                            NULL,
                            image,
                            image->colormap,
                            (IndexPacket *) NULL,
                            image->colors,
                            &image->exception);
          status=SyncImage(image);
        }
      else
        {
          /*
            Convert DirectClass image.
          */
          status=PixelIterateMonoModify(RGBToHSLTransform,
                                        NULL,
                                        progress_message,
                                        NULL,NULL,
                                        0,0,image->columns,image->rows,
                                        image,
                                        &image->exception);
        }
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                            "Colorspace transform completed");
      return(status);
    }

  if (colorspace == HWBColorspace)
    {
      if (image->storage_class == PseudoClass)
        {
          /*
            Convert PseudoClass image colormap.
          */
          RGBToHWBTransform(NULL,
                            NULL,
                            image,
                            image->colormap,
                            (IndexPacket *) NULL,
                            image->colors,
                            &image->exception);
          status=SyncImage(image);
        }
      else
        {
          status=PixelIterateMonoModify(RGBToHWBTransform,
                                        NULL,
                                        progress_message,
                                        NULL,NULL,
                                        0,0,image->columns,image->rows,
                                        image,
                                        &image->exception);
        }
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                            "Colorspace transform completed");
      return(status);
    }

  {
    /*
      3D Transform.
    */

    XYZColorTransformInfo_t
      xform;

    /*
      Allocate the tables.
    */
    xform.x=MagickAllocateMemory(XYZColorTransformPacket *,XYZMapAllocSize);
    xform.y=MagickAllocateMemory(XYZColorTransformPacket *,XYZMapAllocSize);
    xform.z=MagickAllocateMemory(XYZColorTransformPacket *,XYZMapAllocSize);
    if ((xform.x == 0) || (xform.y == 0) || (xform.z == 0))
      {
        MagickFreeMemory(xform.x);
        MagickFreeMemory(xform.y);
        MagickFreeMemory(xform.z);
        ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed,
                              UnableToTransformColorspace);
      }
    xform.primary_info.x=xform.primary_info.y=xform.primary_info.z=0;
    switch (colorspace)
      {
      case GRAYColorspace:
      case Rec601LumaColorspace:
        {
          /*
            Initialize Rec. 601 Luma tables:

            G = 0.29900*R+0.58700*G+0.11400*B
          */
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.x[i].x=xform.x[i].y=xform.x[i].z=(0.299f*(float) i);
              xform.y[i].x=xform.y[i].y=xform.y[i].z=(0.587f*(float) i);
              xform.z[i].x=xform.z[i].y=xform.z[i].z=(0.114f*(float) i);
            }
          break;
        }
      case Rec709LumaColorspace:
        {
          /*
            Initialize Rec. 709 Luma tables:

            G = 0.2126*R+0.7152*G+0.0722*B
          */
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.x[i].x=xform.x[i].y=xform.x[i].z=(0.2126f*(float) i);
              xform.y[i].x=xform.y[i].y=xform.y[i].z=(0.7152f*(float) i);
              xform.z[i].x=xform.z[i].y=xform.z[i].z=(0.0722f*(float) i);
            }
          break;
        }
      case OHTAColorspace:
        {
          /*
            Initialize OHTA tables:

            I1 = 0.33333*R+0.33334*G+0.33333*B
            I2 = 0.50000*R+0.00000*G-0.50000*B
            I3 =-0.25000*R+0.50000*G-0.25000*B

            I and Q, normally -0.5 through 0.5, are normalized to the range 0
            through MaxRGB.
          */
          xform.primary_info.y=((MaxMap+1)/2);
          xform.primary_info.z=((MaxMap+1)/2);
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.x[i].x=(0.33333f*(float) i);
              xform.x[i].y=(0.5f*(float) i);
              xform.x[i].z=((-0.25f)*(float) i);
              xform.y[i].x=(0.33334f*(float) i);
              xform.y[i].y=(0.0f);
              xform.y[i].z=(0.5f*(float) i);
              xform.z[i].x=(0.33333f*(float) i);
              xform.z[i].y=((-0.5f)*(float) i);
              xform.z[i].z=((-0.25f)*(float) i);
            }
          break;
        }
      case sRGBColorspace:
        {
          /*
            Kodak PhotoYCC Color Space

            Initialize sRGB tables:

            Y =  0.29900*R+0.58700*G+0.11400*B
            C1= -0.29900*R-0.58700*G+0.88600*B
            C2=  0.70100*R-0.58700*G-0.11400*B

            sRGB is scaled by 1.3584.  C1 zero is 156 and C2 is at 137.
          */
          /* FIXME! The scaling factors for this transform look bizarre,
             and in fact, the results are not correct. */
          xform.primary_info.y=(ScaleCharToMap(156));
          xform.primary_info.z=(ScaleCharToMap(137));
          for (i=0; i <= (long) (0.018*MaxMap); i++)
            {
              xform.x[i].x=(0.003962014134275617*i);
              xform.x[i].y=((-0.002426619775463276)*i);
              xform.x[i].z=(0.006927257754597858*i);
              xform.y[i].x=(0.007778268551236748*i);
              xform.y[i].y=((-0.004763965913702149)*i);
              xform.y[i].z=((-0.005800713697502058)*i);
              xform.z[i].x=(0.001510600706713781*i);
              xform.z[i].y=(0.007190585689165425*i);
              xform.z[i].z=((-0.0011265440570958)*i);
            }
          for ( ; i <= (long) MaxMap; i++)
            {
              xform.x[i].x=(0.2201118963486454*(1.099*i-0.099));
              xform.x[i].y=((-0.1348122097479598)*(1.099*i-0.099));
              xform.x[i].z=(0.3848476530332144*(1.099*i-0.099));
              xform.y[i].x=(0.4321260306242638*(1.099*i-0.099));
              xform.y[i].y=((-0.2646647729834528)*(1.099*i-0.099));
              xform.y[i].z=((-0.3222618720834477)*(1.099*i-0.099));
              xform.z[i].x=(0.08392226148409894*(1.099*i-0.099));
              xform.z[i].y=(0.3994769827314126*(1.099*i-0.099));
              xform.z[i].z=((-0.06258578094976668)*(1.099*i-0.099));
            }
          break;
        }
      case XYZColorspace:
        {
          /*
            Initialize CIE XYZ tables (from ITU-R 709 RGB):

            X = 0.412453*X+0.357580*Y+0.180423*Z
            Y = 0.212671*X+0.715160*Y+0.072169*Z
            Z = 0.019334*X+0.119193*Y+0.950227*Z
          */
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.x[i].x=(0.412453f*(float) i);
              xform.x[i].y=(0.212671f*(float) i);
              xform.x[i].z=(0.019334f*(float) i);
              xform.y[i].x=(0.35758f*(float) i);
              xform.y[i].y=(0.71516f*(float) i);
              xform.y[i].z=(0.119193f*(float) i);
              xform.z[i].x=(0.180423f*(float) i);
              xform.z[i].y=(0.072169f*(float) i);
              xform.z[i].z=(0.950227f*(float) i);
            }
          break;
        }
      case Rec601YCbCrColorspace:
        {
          /*
            Initialize YCbCr tables (using ITU-R BT.601 luma):

            Y =  0.299000*R+0.587000*G+0.114000*B
            Cb= -0.168736*R-0.331264*G+0.500000*B
            Cr=  0.500000*R-0.418688*G-0.081312*B

            Cb and Cr, normally -0.5 through 0.5, are normalized to the range 0
            through MaxRGB.
          */
          xform.primary_info.y=((MaxMap+1)/2);
          xform.primary_info.z=((MaxMap+1)/2);
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.x[i].x=(0.299f*(float) i); /* Red */
              xform.x[i].y=((-0.16873f)*(float) i); /* Green */
              xform.x[i].z=(0.500000f*(float) i); /* Blue */
              xform.y[i].x=(0.587f*(float) i); /* Red */
              xform.y[i].y=((-0.331264f)*(float) i); /* Green */
              xform.y[i].z=((-0.418688f)*(float) i); /* Blue */
              xform.z[i].x=(0.114f*(float) i); /* Red */
              xform.z[i].y=(0.500000f*(float) i); /* Green */
              xform.z[i].z=((-0.081312f)*(float) i); /* Blue */
            }
          break;
        }
      case Rec709YCbCrColorspace:
        {
          /*
            Initialize YCbCr tables (using ITU-R BT.709 luma):

            Y =  0.212600*R+0.715200*G+0.072200*B
            Cb= -0.114572*R-0.385428*G+0.500000*B
            Cr=  0.500000*R-0.454153*G-0.045847*B

            Cb and Cr, normally -0.5 through 0.5, are normalized to the range 0
            through MaxRGB.
          */
          xform.primary_info.y=((MaxMap+1)/2);
          xform.primary_info.z=((MaxMap+1)/2);
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.x[i].x=(0.212600f*(float) i); /* Red */
              xform.x[i].y=((-0.114572f)*(float) i); /* Green */
              xform.x[i].z=(0.500000f*(float) i); /* Blue */
              xform.y[i].x=(0.715200f*(float) i); /* Red */
              xform.y[i].y=((-0.385428f)*(float) i); /* Green */
              xform.y[i].z=((-0.454153f)*(float) i); /* Blue */
              xform.z[i].x=(0.072200f*(float) i); /* Red */
              xform.z[i].y=(0.500000f*(float) i); /* Green */
              xform.z[i].z=((-0.045847f)*(float) i); /* Blue */
            }
          break;
        }
      case YCCColorspace:
        {
          /*
            Kodak PhotoYCC Color Space.

            Initialize YCC tables:

            Y =  0.29900*R+0.58700*G+0.11400*B
            C1= -0.29900*R-0.58700*G+0.88600*B
            C2=  0.70100*R-0.58700*G-0.11400*B

            YCC is scaled by 1.3584.  C1 zero is 156 and C2 is at 137.
          */
          /* FIXME! The scaling factors for this transform look bizarre,
             and in fact, the results are not correct. */
          xform.primary_info.y=(ScaleCharToMap(156));
          xform.primary_info.z=(ScaleCharToMap(137));
          for (i=0; i <= (long) (0.018*MaxMap); i++)
            {
              xform.x[i].x=(0.003962014134275617*i);
              xform.x[i].y=((-0.002426619775463276)*i);
              xform.x[i].z=(0.006927257754597858*i);
              xform.y[i].x=(0.007778268551236748*i);
              xform.y[i].y=((-0.004763965913702149)*i);
              xform.y[i].z=((-0.005800713697502058)*i);
              xform.z[i].x=(0.001510600706713781*i);
              xform.z[i].y=(0.007190585689165425*i);
              xform.z[i].z=((-0.0011265440570958)*i);
            }
          for ( ; i <= (long) MaxMap; i++)
            {
              xform.x[i].x=(0.2201118963486454*(1.099*i-0.099));
              xform.x[i].y=((-0.1348122097479598)*(1.099*i-0.099));
              xform.x[i].z=(0.3848476530332144*(1.099*i-0.099));
              xform.y[i].x=(0.4321260306242638*(1.099*i-0.099));
              xform.y[i].y=((-0.2646647729834528)*(1.099*i-0.099));
              xform.y[i].z=((-0.3222618720834477)*(1.099*i-0.099));
              xform.z[i].x=(0.08392226148409894*(1.099*i-0.099));
              xform.z[i].y=(0.3994769827314126*(1.099*i-0.099));
              xform.z[i].z=((-0.06258578094976668)*(1.099*i-0.099));
            }
          break;
        }
      case YIQColorspace:
        {
          /*
            Initialize YIQ tables:

            Y = 0.29900*R+0.58700*G+0.11400*B
            I = 0.59600*R-0.27400*G-0.32200*B
            Q = 0.21100*R-0.52300*G+0.31200*B

            I and Q, normally -0.5 through 0.5, are normalized to the range 0
            through MaxRGB.
          */
          xform.primary_info.y=((MaxMap+1)/2);
          xform.primary_info.z=((MaxMap+1)/2);
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.x[i].x=(0.299f*(float) i);
              xform.x[i].y=(0.596f*(float) i);
              xform.x[i].z=(0.211f*(float) i);
              xform.y[i].x=(0.587f*(float) i);
              xform.y[i].y=((-0.274f)*(float) i);
              xform.y[i].z=((-0.523f)*(float) i);
              xform.z[i].x=(0.114f*(float) i);
              xform.z[i].y=((-0.322f)*(float) i);
              xform.z[i].z=(0.312f*(float) i);
            }
          break;
        }
      case YPbPrColorspace:
        {
          /*
            Initialize YPbPr tables (according to ITU-R BT.601):

            Y =  0.299000*R+0.587000*G+0.114000*B
            Pb= -0.168736*R-0.331264*G+0.500000*B
            Pr=  0.500000*R-0.418688*G-0.081312*B

            Pb and Pr, normally -0.5 through 0.5, are normalized to the range 0
            through MaxRGB.
          */
          xform.primary_info.y=((MaxMap+1)/2);
          xform.primary_info.z=((MaxMap+1)/2);
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.x[i].x=(0.299f*(float) i);
              xform.x[i].y=((-0.168736f)*(float) i);
              xform.x[i].z=(0.5f*(float) i);
              xform.y[i].x=(0.587f*(float) i);
              xform.y[i].y=((-0.331264f)*(float) i);
              xform.y[i].z=((-0.418688f)*(float) i);
              xform.z[i].x=(0.114f*(float) i);
              xform.z[i].y=(0.5f*(float) i);
              xform.z[i].z=((-0.081312f)*(float) i);
            }
          break;
        }
      case YUVColorspace:
      default:
        {
          /*
            Initialize YUV tables:

            Y =  0.29900*R+0.58700*G+0.11400*B
            U = -0.14740*R-0.28950*G+0.43690*B
            V =  0.61500*R-0.51500*G-0.10000*B

            U and V, normally -0.5 through 0.5, are normalized to the range 0
            through MaxRGB.  Note that U = 0.493*(B-Y), V = 0.877*(R-Y).
          */
          xform.primary_info.y=((MaxMap+1)/2);
          xform.primary_info.z=((MaxMap+1)/2);
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.x[i].x=(0.299f*(float) i);
              xform.x[i].y=((-0.1474f)*(float) i);
              xform.x[i].z=(0.615f*(float) i);
              xform.y[i].x=(0.587f*(float) i);
              xform.y[i].y=((-0.2895f)*(float) i);
              xform.y[i].z=((-0.515f)*(float) i);
              xform.z[i].x=(0.114f*(float) i);
              xform.z[i].y=(0.4369f*(float) i);
              xform.z[i].z=((-0.1f)*(float) i);
            }
          break;
        }
      }

#if 0
    /*
      Dump tables
    */
    for (i=0; i <= (long) MaxMap; i++)
      {
        printf("%5ld: xform.x(%g,%g,%g) xform.y(%g,%g,%g) xform.z(%g,%g,%g)\n",
               i,
               ((xform.x[i].x)),
               ((xform.x[i].y)),
               ((xform.x[i].z)),

               ((xform.y[i].x)),
               ((xform.y[i].y)),
               ((xform.y[i].z)),

               ((xform.z[i].x)),
               ((xform.z[i].y)),
               ((xform.z[i].z)));
      }
#endif

    /*
      Convert from RGB.
    */
    if (image->storage_class == PseudoClass)
      {
        /*
          Convert PseudoClass image colormap.
        */
        (void) XYZTransformPackets(NULL,
                                   &xform,
                                   image,
                                   image->colormap,
                                   (IndexPacket *) NULL,
                                   image->colors,
                                   &image->exception);
        status=SyncImage(image);
      }
    else
      {
        /*
          Convert DirectClass image.
        */
        status=PixelIterateMonoModify(XYZTransformPackets,
                                      NULL,
                                      progress_message,
                                      NULL,&xform,
                                      0,0,image->columns,image->rows,
                                      image,
                                      &image->exception);
      }

    /*
      Free allocated memory.
    */
    MagickFreeMemory(xform.x);
    MagickFreeMemory(xform.y);
    MagickFreeMemory(xform.z);
  }

  image->is_grayscale=IsGrayColorspace(colorspace);
  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                        "Transform to colorspace %s completed",
                        ColorspaceTypeToString(colorspace));
  return(status);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+     T r a n s f o r m C o l o r s p a c e                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method (void) TransformColorspace converts the image to a specified colorspace.
%  If the image is already in the requested colorspace, no work is performed.
%  Note that the current colorspace is stored in the image colorspace member.
%  The transformation matrices are not necessarily the standard ones: the
%  weights are rescaled to normalize the range of the transformed values to
%  be [0..MaxRGB].
%
%  The format of the (void) TransformColorspace method is:
%
%      unsigned int (void) TransformColorspace(Image *image,
%        const ColorspaceType colorspace)
%
%  A description of each parameter follows:
%
%    o image: the image to transform
%
%    o colorspace: the desired colorspace.
%
*/
MagickExport MagickPassFail TransformColorspace(Image *image,
  const ColorspaceType colorspace)
{
  MagickPassFail
    status=MagickPass;

  assert(image != (Image *) NULL);
  assert(colorspace != UndefinedColorspace);
  assert(image->colorspace != UndefinedColorspace);
  /*
    If the image colorspace is the same as requested, do nothing.
  */
  if (image->colorspace == colorspace)
     return (status);

  /*
    If the requested colorspace is RGB or Transparent, then convert
    via TransformRGBImage.
  */
  if ((colorspace == RGBColorspace) ||
      (colorspace == TransparentColorspace))
      {
        status &= TransformRGBImage(image,image->colorspace);
        image->colorspace=colorspace;
        return  (status);
      }

  /*
    If the image is not already in an RGB-compatible colorspace, then
    convert it to RGB via TransformRGBImage, and then to the target
    colorspace via RGBTransformImage, otherwise just convert to the
    target colorspace via RGBTransformImage.
  */
  if (!IsRGBColorspace(image->colorspace))
    status=TransformRGBImage(image,image->colorspace);

  status &= RGBTransformImage(image,colorspace);
  return (status);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+     T r a n s f o r m R G B I m a g e                                       %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method TransformRGBImage converts the reference image from an alternate
%  colorspace to RGB.  The transformation matrices are not the standard ones:
%  the weights are rescaled to normalize the range of the transformed values
%  to be [0..MaxRGB].
%
%  The format of the TransformRGBImage method is:
%
%      unsigned int TransformRGBImage(Image *image,
%        const ColorspaceType colorspace)
%
%  A description of each parameter follows:
%
%    o image: the image
%
%    o colorspace: the colorspace to transform the image to.
%
*/
static MagickPassFail
CMYKToRGBTransform(void *mutable_data,          /* User provided mutable data */
                   const void *immutable_data,  /* User provided immutable data */
                   Image * restrict image,                /* Modify image */
                   PixelPacket * restrict pixels,         /* Pixel row */
                   IndexPacket * restrict indexes,        /* Pixel row indexes */
                   const long npixels,          /* Number of pixels in row */
                   ExceptionInfo *exception)    /* Exception report */
{
  /*
    Transform CMYK(A) pixels to RGB.
  */
  register long
    i;

  ARG_NOT_USED(mutable_data);
  ARG_NOT_USED(immutable_data);
  ARG_NOT_USED(exception);

  for (i=0; i < npixels; i++)
    {
      double
        black_factor;

      black_factor=MaxRGBDouble-GetBlackSample(&pixels[i]);
      SetRedSample(&pixels[i],
                   (Quantum) (((MaxRGBDouble-GetCyanSample(&pixels[i]))*
                               black_factor)/MaxRGBDouble+0.5));
      SetGreenSample(&pixels[i],
                     (Quantum) (((MaxRGBDouble-GetMagentaSample(&pixels[i]))*
                                 black_factor)/MaxRGBDouble+0.5));
      SetBlueSample(&pixels[i],
                    (Quantum) (((MaxRGBDouble-GetYellowSample(&pixels[i]))*
                                black_factor)/ MaxRGBDouble+0.5));
      SetOpacitySample(&pixels[i],(image->matte ? indexes[i] : OpaqueOpacity));
    }

  return MagickPass;
}

static MagickPassFail
CineonLogToRGBTransform(void *mutable_data,         /* User provided mutable data */
                        const void *immutable_data, /* User provided immutable data */
                        Image * restrict image,               /* Modify image */
                        PixelPacket * restrict pixels,        /* Pixel row */
                        IndexPacket * restrict indexes,       /* Pixel row indexes */
                        const long npixels,         /* Number of pixels in row */
                        ExceptionInfo *exception)   /* Exception report */
{
  /*
    Transform CineonLog pixels to RGB based on an existing lookup
    table.
  */
  const Quantum
    *linearmap = (const Quantum *) immutable_data;

  register long
    i;

  ARG_NOT_USED(mutable_data);
  ARG_NOT_USED(image);
  ARG_NOT_USED(indexes);
  ARG_NOT_USED(exception);

  for (i=0; i < npixels; i++)
    {
      pixels[i].red   = linearmap[ScaleQuantumToShort(pixels[i].red)/64U];
      pixels[i].green = linearmap[ScaleQuantumToShort(pixels[i].green)/64U];
      pixels[i].blue  = linearmap[ScaleQuantumToShort(pixels[i].blue)/64U];
    }

  return MagickPass;
}

static MagickPassFail
HSLToRGBTransform(void *mutable_data,         /* User provided mutable data */
                  const void *immutable_data, /* User provided immutable data */
                  Image *image,               /* Modify image */
                  PixelPacket * restrict pixels,        /* Pixel row */
                  IndexPacket * restrict indexes,       /* Pixel row indexes */
                  const long npixels,         /* Number of pixels in row */
                  ExceptionInfo *exception)   /* Exception report */
{
  /*
    Transform pixels from HSL space to RGB space.
  */
  register long
    i;

  ARG_NOT_USED(mutable_data);
  ARG_NOT_USED(immutable_data);
  ARG_NOT_USED(image);
  ARG_NOT_USED(indexes);
  ARG_NOT_USED(exception);

  for (i=0; i < npixels; i++)
    {
      HSLTransform((double) pixels[i].red/MaxRGB,(double) pixels[i].green/MaxRGB,
                   (double) pixels[i].blue/MaxRGB,&pixels[i].red,&pixels[i].green,&pixels[i].blue);
    }

  return MagickPass;
}

static MagickPassFail
HWBToRGBTransform(void *mutable_data,         /* User provided mutable data */
                  const void *immutable_data, /* User provided immutable data */
                  Image * restrict image,               /* Modify image */
                  PixelPacket * restrict pixels,        /* Pixel row */
                  IndexPacket * restrict indexes,       /* Pixel row indexes */
                  const long npixels,         /* Number of pixels in row */
                  ExceptionInfo *exception)   /* Exception report */
{
  /*
    Transform pixels from HWB space to RGB space.
  */
  register long
    i;

  ARG_NOT_USED(mutable_data);
  ARG_NOT_USED(immutable_data);
  ARG_NOT_USED(image);
  ARG_NOT_USED(indexes);
  ARG_NOT_USED(exception);

  for (i=0; i < npixels; i++)
    {
      HWBTransform((double) pixels[i].red/MaxRGB,(double) pixels[i].green/MaxRGB,
                   (double) pixels[i].blue/MaxRGB,&pixels[i].red,&pixels[i].green,&pixels[i].blue);
    }

  return MagickPass;
}

typedef struct _RGBColorTransformPacket
{
  float
    r,
    g,
    b;
} RGBColorTransformPacket;

typedef struct _RGBTransformInfo_t
{
  RGBColorTransformPacket *r;
  RGBColorTransformPacket *g;
  RGBColorTransformPacket *b;
  const unsigned char *rgb_map;
  unsigned int rgb_map_max_index;
} RGBTransformInfo_t;

static MagickPassFail
RGBTransformPackets(void *mutable_data,         /* User provided mutable data */
                    const void *immutable_data, /* User provided immutable data */
                    Image * restrict image,               /* Modify image */
                    PixelPacket * restrict pixels,        /* Pixel row */
                    IndexPacket * restrict indexes,       /* Pixel row indexes */
                    const long npixels,         /* Number of pixels in row */
                    ExceptionInfo *exception)   /* Exception report */
{
  /*
    3D transform pixels to RGB.
  */
  float
    b,
    g,
    r;

  const RGBTransformInfo_t
    *xform = (const RGBTransformInfo_t *) immutable_data;

  register long
    i;

  unsigned int
    r_index,
    g_index,
    b_index;

  ARG_NOT_USED(mutable_data);
  ARG_NOT_USED(image);
  ARG_NOT_USED(indexes);
  ARG_NOT_USED(exception);

  for (i=0; i < npixels; i++)
    {
      r_index = ScaleQuantumToMap(pixels[i].red);
      g_index = ScaleQuantumToMap(pixels[i].green);
      b_index = ScaleQuantumToMap(pixels[i].blue);

      r = (xform->r[r_index].r + xform->g[g_index].r + xform->b[b_index].r);
      g = (xform->r[r_index].g + xform->g[g_index].g + xform->b[b_index].g);
      b = (xform->r[r_index].b + xform->g[g_index].b + xform->b[b_index].b);

      r = r < 0.0f ? 0.0f : r > MaxMapFloat ? MaxMapFloat : (r + 0.5f);
      g = g < 0.0f ? 0.0f : g > MaxMapFloat ? MaxMapFloat : (g + 0.5f);
      b = b < 0.0f ? 0.0f : b > MaxMapFloat ? MaxMapFloat : (b + 0.5f);

      if ( xform->rgb_map != 0 )
        {
          r_index = ScaleMapToChar(r);
          g_index = ScaleMapToChar(g);
          b_index = ScaleMapToChar(b);

          if (r_index > xform->rgb_map_max_index) r_index = xform->rgb_map_max_index;
          if (g_index > xform->rgb_map_max_index) g_index = xform->rgb_map_max_index;
          if (b_index > xform->rgb_map_max_index) b_index = xform->rgb_map_max_index;

          pixels[i].red   = ScaleCharToQuantum(xform->rgb_map[r_index]);
          pixels[i].green = ScaleCharToQuantum(xform->rgb_map[g_index]);
          pixels[i].blue  = ScaleCharToQuantum(xform->rgb_map[b_index]);
        }
      else
        {
          pixels[i].red   = ScaleMapToQuantum(r);
          pixels[i].green = ScaleMapToQuantum(g);
          pixels[i].blue  = ScaleMapToQuantum(b);
        }
    }

  return MagickPass;
}

MagickExport MagickPassFail TransformRGBImage(Image *image,
                                              const ColorspaceType colorspace)
{
  static const unsigned char
    sRGBMap[351] =
    {
      0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
      19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 29, 30, 31, 32, 33,
      34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
      50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 65, 66,
      67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
      83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 95, 96, 97, 98, 99,
      100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
      114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126,
      127, 128, 129, 130, 131, 132, 133, 135, 136, 137, 138, 139, 140,
      141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153,
      154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166,
      167, 168, 169, 170, 171, 172, 173, 174, 175, 175, 176, 177, 178,
      179, 180, 181, 182, 183, 184, 185, 186, 187, 187, 188, 189, 190,
      191, 192, 193, 194, 194, 195, 196, 197, 198, 199, 199, 200, 201,
      202, 203, 203, 204, 205, 206, 207, 207, 208, 209, 210, 210, 211,
      212, 213, 213, 214, 215, 215, 216, 217, 218, 218, 219, 220, 220,
      221, 222, 222, 223, 223, 224, 225, 225, 226, 227, 227, 228, 228,
      229, 229, 230, 230, 231, 232, 232, 233, 233, 234, 234, 235, 235,
      235, 236, 236, 237, 237, 238, 238, 238, 239, 239, 240, 240, 240,
      241, 241, 242, 242, 242, 243, 243, 243, 243, 244, 244, 244, 245,
      245, 245, 245, 246, 246, 246, 247, 247, 247, 247, 247, 248, 248,
      248, 248, 249, 249, 249, 249, 249, 249, 250, 250, 250, 250, 250,
      250, 251, 251, 251, 251, 251, 251, 252, 252, 252, 252, 252, 252,
      252, 252, 252, 253, 253, 253, 253, 253, 253, 253, 253, 253, 254,
      254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 255, 255,
      255, 255, 255, 255, 255
    };

  static const unsigned char
    YCCMap[351] =  /* Photo CD information beyond 100% white, Gamma 2.2 */
    {
      0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
      19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35,
      36, 37, 38, 39, 40, 41, 42, 43, 45, 46, 47, 48, 49, 50, 51, 52,
      53, 54, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 69, 70,
      71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 82, 83, 84, 86, 87, 88,
      89, 90, 91, 92, 93, 94, 95, 97, 98, 99, 100, 101, 102, 103, 104,
      105, 106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118,
      119, 120, 121, 122, 123, 124, 125, 126, 127, 129, 130, 131, 132,
      133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145,
      146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158,
      159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171,
      172, 173, 174, 175, 176, 176, 177, 178, 179, 180, 181, 182, 183,
      184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 193, 194, 195,
      196, 197, 198, 199, 200, 201, 201, 202, 203, 204, 205, 206, 207,
      207, 208, 209, 210, 211, 211, 212, 213, 214, 215, 215, 216, 217,
      218, 218, 219, 220, 221, 221, 222, 223, 224, 224, 225, 226, 226,
      227, 228, 228, 229, 230, 230, 231, 232, 232, 233, 234, 234, 235,
      236, 236, 237, 237, 238, 238, 239, 240, 240, 241, 241, 242, 242,
      243, 243, 244, 244, 245, 245, 245, 246, 246, 247, 247, 247, 248,
      248, 248, 249, 249, 249, 249, 250, 250, 250, 250, 251, 251, 251,
      251, 251, 252, 252, 252, 252, 252, 253, 253, 253, 253, 253, 253,
      253, 253, 253, 253, 253, 253, 253, 254, 254, 254, 254, 254, 254,
      254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
      255, 255, 255, 255, 255, 255
    };

  char
    progress_message[MaxTextExtent];

  register long
    i;

  unsigned int
    is_grayscale;

  MagickPassFail
    status=MagickPass;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  assert(image->colorspace != UndefinedColorspace);

  is_grayscale=((image->is_grayscale) || IsGrayColorspace(image->colorspace));

  /*
    If colorspace is already an RGB type then simply return.
  */
  if (IsRGBColorspace(image->colorspace))
    return(status);

  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                        "Transform colorspace from %s to RGB",
                        ColorspaceTypeToString(image->colorspace));

  FormatString(progress_message,"[%%s] Transform colorspace from %s to RGB...",
               ColorspaceTypeToString(image->colorspace));

  if (image->colorspace == CMYKColorspace)
    {
      if (image->storage_class == PseudoClass)
        {
          status=SyncImage(image);
          image->storage_class=DirectClass;
        }
      if (status != MagickFail)
        status=PixelIterateMonoModify(CMYKToRGBTransform,
                                      NULL,
                                      progress_message,
                                      NULL,NULL,
                                      0,0,image->columns,image->rows,
                                      image,
                                      &image->exception);
      image->colorspace=RGBColorspace;
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                            "Colorspace transform completed");
      return(status);
    }

  if (colorspace == CineonLogRGBColorspace)
    {
      /*
        Transform from Cineon Log RGB to Linear RGB
      */
      double
        BreakPoint,
        DisplayGamma,
        Gain,
        KneeGain,
        KneeOffset,
        MaxLinearValue,
        NegativeFilmGamma,
        Offset,
        ReferenceBlack,
        ReferenceWhite,
        SoftClip;

      Quantum
        *linearmap;

      /*
        Establish defaults.
      */
      MaxLinearValue=MaxRGB; /* Maximum linear value output */
      ReferenceWhite=685.0;  /* 90% white card (default 685) */
      ReferenceBlack=95.0;   /* 1% black card  (default 95) */
      DisplayGamma=1.7;      /* Typical display gamma (Kodak recommended 1.7) */
      NegativeFilmGamma=0.6; /* Typical gamma for a film negative */
      SoftClip=0.0;          /* Soft clip offset */

      /*
        Allow image attributes to override defaults.
      */
      MagickAttributeToDouble(image,"reference-white",ReferenceWhite);
      MagickAttributeToDouble(image,"reference-black",ReferenceBlack);
      MagickAttributeToDouble(image,"display-gamma",DisplayGamma);
      MagickAttributeToDouble(image,"film-gamma",NegativeFilmGamma);
      MagickAttributeToDouble(image,"soft-clip-offset",SoftClip);

      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                            "Log Transform: ReferenceWhite=%g ReferenceBlack=%g DisplayGamma=%g FilmGamma=%g SoftClip=%g",
                            ReferenceWhite,ReferenceBlack,DisplayGamma,NegativeFilmGamma,SoftClip);

      BreakPoint=ReferenceWhite-SoftClip;
      Gain=MaxLinearValue/(1.0 - pow(pow(10,((ReferenceBlack-ReferenceWhite)
                                             *0.002/NegativeFilmGamma)),
                                     (DisplayGamma/1.7)));
      Offset=Gain-MaxLinearValue;
      KneeOffset=pow(pow(10,((BreakPoint-ReferenceWhite)*0.002/NegativeFilmGamma)),
                     (DisplayGamma/1.7))*Gain-Offset;
      KneeGain=(MaxLinearValue-KneeOffset)/pow((5*SoftClip),(SoftClip/100));


      linearmap=MagickAllocateMemory(Quantum *,1024*sizeof(Quantum));
      if (linearmap == 0)
        ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed,
                              UnableToTransformColorspace);

      for (i=0; i < 1023; i++)
        {
          double
            linearval,
            logval;

          logval=i;
          if (logval < ReferenceBlack)
            {
              /* Values below reference black are clipped to zero */
              linearval=0.0;
            }
          else if (logval > BreakPoint)
            {
              /* Values above the breakpoint are soft-clipped. */
              linearval=pow((logval-BreakPoint),(SoftClip/100))*KneeGain+KneeOffset;
            }
          else
            {
              /* Otherwise, normal values */
              linearval=pow(pow(10,((logval-ReferenceWhite)*0.002/NegativeFilmGamma)),
                            (DisplayGamma/1.7))*Gain-Offset;
            }

          linearmap[i]=(unsigned int) (linearval + 0.5);
        }
      /*
        Transform pixels.
      */
      if (image->storage_class == PseudoClass)
        {
          /*
            Convert PseudoClass image colormap.
          */
          CineonLogToRGBTransform(NULL,
                                  linearmap,
                                  image,
                                  image->colormap,
                                  (IndexPacket *) NULL,
                                  image->colors,
                                  &image->exception);
          status=SyncImage(image);
        }
      else
        {
          /*
            Convert DirectClass image.
          */
          status=PixelIterateMonoModify(CineonLogToRGBTransform,
                                        NULL,
                                        progress_message,
                                        NULL,linearmap,
                                        0,0,image->columns,image->rows,
                                        image,
                                        &image->exception);
        }
      MagickFreeMemory(linearmap);
      image->colorspace=RGBColorspace;
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                            "Transform to colorspace %s completed",
                            ColorspaceTypeToString(colorspace));
      return(status);
    }

  if (image->colorspace == HSLColorspace)
    {
      if (image->storage_class == PseudoClass)
        {
          /*
            Convert PseudoClass image colormap.
          */
          HSLToRGBTransform(NULL,
                            NULL,
                            image,
                            image->colormap,
                            (IndexPacket *) NULL,
                            image->colors,
                            &image->exception);
          status=SyncImage(image);
        }
      else
        {
          status=PixelIterateMonoModify(HSLToRGBTransform,
                                        NULL,
                                        progress_message,
                                        NULL,NULL,
                                        0,0,image->columns,image->rows,
                                        image,
                                        &image->exception);
        }
      image->colorspace=RGBColorspace;
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                            "Colorspace transform completed");
      return(status);
    }

  if (image->colorspace == HWBColorspace)
    {
      if (image->storage_class == PseudoClass)
        {
          /*
            Convert PseudoClass image colormap.
          */
          HWBToRGBTransform(NULL,
                            NULL,
                            image,
                            image->colormap,
                            (IndexPacket *) NULL,
                            image->colors,
                            &image->exception);
          status=SyncImage(image);
        }
      else
        {
          status=PixelIterateMonoModify(HWBToRGBTransform,
                                        NULL,
                                        progress_message,
                                        NULL,NULL,
                                        0,0,image->columns,image->rows,
                                        image,
                                        &image->exception);
        }
      image->colorspace=RGBColorspace;
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                            "Colorspace transform completed");
      return(status);
    }

  {
    /*
      3D Transform.
    */

    RGBTransformInfo_t
      xform;

    /*
      Allocate the tables.
    */
    xform.rgb_map = (const unsigned char *) NULL;
    xform.rgb_map_max_index = 0;
    xform.r=MagickAllocateMemory(RGBColorTransformPacket *,
                                 (MaxMap+1)*sizeof(RGBColorTransformPacket));
    xform.g=MagickAllocateMemory(RGBColorTransformPacket *,
                                 (MaxMap+1)*sizeof(RGBColorTransformPacket));
    xform.b=MagickAllocateMemory(RGBColorTransformPacket *,
                                 (MaxMap+1)*sizeof(RGBColorTransformPacket));
    if ((xform.r == (RGBColorTransformPacket *) NULL) ||
        (xform.g == (RGBColorTransformPacket *) NULL) ||
        (xform.b == (RGBColorTransformPacket *) NULL))
      {
        MagickFreeMemory(xform.r);
        MagickFreeMemory(xform.g);
        MagickFreeMemory(xform.b);
        ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed,
                              UnableToTransformColorspace);
      }

    switch (image->colorspace)
      {
      case OHTAColorspace:
        {
          /*
            Initialize OHTA tables:

            R = I1+1.00000*I2-0.66668*I3
            G = I1+0.00000*I2+1.33333*I3
            B = I1-1.00000*I2-0.66668*I3

            I and Q, normally -0.5 through 0.5, must be normalized to the range 0
            through MaxMap.
          */
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.b[i].b=((-0.33334f)*(2.0f*(float) i-MaxMapFloat));
              xform.b[i].g=(0.666665f*(2.0f*(float) i-MaxMapFloat));
              xform.b[i].r=((-0.33334f)*(2.0f*(float) i-MaxMapFloat));
              xform.g[i].b=((-0.5f)*(2.0f*(float) i-MaxMapFloat));
              xform.g[i].g=(0.0f);
              xform.g[i].r=(0.5f*(2.0f*(float) i-MaxMapFloat));
              xform.r[i].b=((float) i);
              xform.r[i].g=((float) i);
              xform.r[i].r=((float) i);
            }
          break;
        }
      case sRGBColorspace:
        {
          /*
            Initialize sRGB tables:

            R = Y            +1.032096*C2
            G = Y-0.326904*C1-0.704445*C2
            B = Y+1.685070*C1

            sRGB is scaled by 1.3584.  C1 zero is 156 and C2 is at 137.
          */
          xform.rgb_map=sRGBMap;
          xform.rgb_map_max_index=350;
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.b[i].b=(0.0f);
              xform.b[i].g=((-0.95692f)*((float) i-ScaleCharToMap(137)));
              xform.b[i].r=(1.88000f*((float) i-ScaleCharToMap(137)));
              xform.g[i].b=(2.28900f*((float) i-ScaleCharToMap(156)));
              xform.g[i].g=((-0.444066f)*((float) i-ScaleCharToMap(156)));
              xform.g[i].r=(0.0f);
              xform.r[i].b=(1.40200f*(float) i);
              xform.r[i].g=(1.40200f*(float) i);
              xform.r[i].r=(1.40200f*(float) i);
            }
          break;
        }
      case XYZColorspace:
        {
          /*
            Initialize CIE XYZ tables (to ITU R-709 RGB):

            R =  3.240479*R-1.537150*G-0.498535*B
            G = -0.969256*R+1.875992*G+0.041556*B
            B =  0.055648*R-0.204043*G+1.057311*B
          */
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.b[i].b=(1.057311f*(float) i);
              xform.b[i].g=(0.041556f*(float) i);
              xform.b[i].r=((-0.498535f)*(float) i);
              xform.g[i].b=((-0.204043f)*(float) i);
              xform.g[i].g=(1.875992f*(float) i);
              xform.g[i].r=((-1.537150f)*(float) i);
              xform.r[i].b=(0.055648f*(float) i);
              xform.r[i].g=((-0.969256f)*(float) i);
              xform.r[i].r=(3.240479f*(float) i);
            }
          break;
        }
      case Rec601YCbCrColorspace:
        {
          /*
            Y'CbCr based on ITU-R 601 Luma

            Initialize Y'CbCr tables:

            R' = Y'            +1.402000*Cr
            G' = Y'-0.344136*Cb-0.714136*Cr
            B' = Y'+1.772000*Cb

            Cb and Cr, normally -0.5 through 0.5, must be normalized to the range 0
            through MaxMap.
          */
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.b[i].b=(0.0f); /* Pr */
              xform.b[i].g=((-0.714136f*0.5f)*(2.0f*(float) i-MaxMapFloat)); /* Pb */
              xform.b[i].r=((1.402000f*0.5f)*(2.0f*(float) i-MaxMapFloat)); /* Y */
              xform.g[i].b=((1.772000f*0.5f)*(2.0f*(float) i-MaxMapFloat)); /* Pr */
              xform.g[i].g=((-0.344136f*0.5f)*(2.0f*(float) i-MaxMapFloat)); /* Pb */
              xform.g[i].r=(0.0f); /* Y */
              xform.r[i].b=((float) i); /* Pr */
              xform.r[i].g=((float) i); /* Pb */
              xform.r[i].r=((float) i); /* Y */
            }
          break;
        }
      case Rec709YCbCrColorspace:
        {
          /*
            Y'CbCr based on ITU-R 709 Luma.

            R' = Y'            +1.574800*Cr
            G' = Y'-0.187324*Cb-0.468124*Cr
            B' = Y'+1.855600*Cb

            Cb and Cr, normally -0.5 through 0.5, must be normalized to the range 0
            through MaxMap.
          */
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.b[i].b=(0.0f); /* Pr */
              xform.b[i].g=((-0.468124f*0.5f)*(2.0f*(float) i-MaxMapFloat)); /* Pb */
              xform.b[i].r=((1.5748f*0.5f)*(2.0f*(float) i-MaxMapFloat)); /* Y */
              xform.g[i].b=((1.8556f*0.5f)*(2.0f*(float) i- MaxMapFloat)); /* Pr */
              xform.g[i].g=((-0.187324f*0.5f)*(2.0f*(float) i-MaxMapFloat)); /* Pb */
              xform.g[i].r=(0.0f); /* Y */
              xform.r[i].b=((float) i); /* Pr */
              xform.r[i].g=((float) i); /* Pb */
              xform.r[i].r=((float) i); /* Y */
            }
          break;
        }
      case YCCColorspace:
        {
          /*
            Kodak PhotoYCC Color Space.

            Initialize YCC tables:

            R = Y            +1.340762*C2
            G = Y-0.317038*C1-0.682243*C2
            B = Y+1.632639*C1

            YCC is scaled by 1.3584.  C1 zero is 156 and C2 is at 137.
          */
          xform.rgb_map=YCCMap;
          xform.rgb_map_max_index=350;
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.b[i].b=(0.0f);
              xform.b[i].g=((-0.9271435f)*((float) i-ScaleCharToMap(137)));
              xform.b[i].r=(1.8215f*((float) i-ScaleCharToMap(137)));
              xform.g[i].b=(2.2179f*((float) i-ScaleCharToMap(156)));
              xform.g[i].g=((-0.4302726f)*((float) i-ScaleCharToMap(156)));
              xform.g[i].r=(0.0f);
              xform.r[i].b=(1.3584f*i);
              xform.r[i].g=(1.3584f*(float) i);
              xform.r[i].r=(1.3584f*(float) i);
            }
          break;
        }
      case YIQColorspace:
        {
          /*
            Initialize YIQ tables:

            R = Y+0.95620*I+0.62140*Q
            G = Y-0.27270*I-0.64680*Q
            B = Y-1.10370*I+1.70060*Q

            I and Q, normally -0.5 through 0.5, must be normalized to the range 0
            through MaxMap.
          */
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.b[i].b=(0.8503f*(2.0f*(float) i-MaxMapFloat));
              xform.b[i].g=((-0.3234f)*(2.0f*(float) i-MaxMapFloat));
              xform.b[i].r=(0.3107f*(2.0f*(float) i-MaxMapFloat));
              xform.g[i].b=((-0.55185f)*(2.0f*(float) i-MaxMapFloat));
              xform.g[i].g=((-0.13635f)*(2.0f*(float) i-MaxMapFloat));
              xform.g[i].r=(0.4781f*(2.0f*(float) i-MaxMapFloat));
              xform.r[i].b=((float) i);
              xform.r[i].g=((float) i);
              xform.r[i].r=((float) i);
            }
          break;
        }
      case YPbPrColorspace:
        {
          /*
            Initialize Y'PbPr tables using ITU-R 601 luma:

            R = Y            +1.402000*C2
            G = Y-0.344136*C1+0.714136*C2
            B = Y+1.772000*C1

            Pb and Pr, normally -0.5 through 0.5, must be normalized to the range 0
            through MaxMap.
          */
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.b[i].b=(0.0f);
              xform.b[i].g=(0.357068f*(2.0f*(float) i-MaxMapFloat));
              xform.b[i].r=(0.701f*(2.0f*(float) i-MaxMapFloat));
              xform.g[i].b=(0.886f*(2.0f*(float) i-MaxMapFloat));
              xform.g[i].g=((-0.172068f)*(2.0f*(float) i-MaxMapFloat));
              xform.g[i].r=(0.0f);
              xform.r[i].b=((float) i);
              xform.r[i].g=((float) i);
              xform.r[i].r=((float) i);
            }
          break;
        }
      case YUVColorspace:
      default:
        {
          /*
            Initialize YUV tables:

            R = Y          +1.13980*V
            G = Y-0.39380*U-0.58050*V
            B = Y+2.02790*U

            U and V, normally -0.5 through 0.5, must be normalized to the range 0
            through MaxMap.
          */
#if MaxMap > 255
#  if defined(HAVE_OPENMP)
#    pragma omp parallel for schedule(static,64)
#  endif
#endif
          for (i=0; i <= (long) MaxMap; i++)
            {
              xform.b[i].b=(0.0f);
              xform.b[i].g=((-0.29025f)*(2.0f*(float) i-MaxMapFloat));
              xform.b[i].r=(0.5699f*(2.0f*(float) i-MaxMapFloat));
              xform.g[i].b=(1.01395f*(2.0f*(float) i-MaxMapFloat));
              xform.g[i].g=((-0.1969f)*(2.0f*(float) i-MaxMapFloat));
              xform.g[i].r=(0.0f);
              xform.r[i].b=((float) i);
              xform.r[i].g=((float) i);
              xform.r[i].r=((float) i);
            }
          break;
        }
      }

#if 0
    /*
      Dump tables
    */
    for (i=0; i <= (long) MaxMap; i++)
      {
        printf("%5ld: xform.r(%g,%g,%g) xform.g(%g,%g,%g) xform.b(%g,%g,%g)\n",
               i,
               ((xform.r[i].r)),
               ((xform.r[i].g)),
               ((xform.r[i].b)),

               ((xform.g[i].r)),
               ((xform.g[i].g)),
               ((xform.g[i].b)),

               ((xform.b[i].r)),
               ((xform.b[i].g)),
               ((xform.b[i].b)));
      }
#endif

    /*
      Convert to RGB.
    */
    if (image->storage_class == PseudoClass)
      {
        /*
          Convert PseudoClass image colormap.
        */
        (void) RGBTransformPackets(NULL,
                                   &xform,
                                   image,
                                   image->colormap,
                                   (IndexPacket *) NULL,
                                   image->colors,
                                   &image->exception);
        status=SyncImage(image);
      }
    else
      {
        /*
          Convert DirectClass image.
        */
        status=PixelIterateMonoModify(RGBTransformPackets,
                                      NULL,
                                      progress_message,
                                      NULL,&xform,
                                      0,0,image->columns,image->rows,
                                      image,
                                      &image->exception);
      }


    /*
      Free allocated memory.
    */
    MagickFreeMemory(xform.b);
    MagickFreeMemory(xform.g);
    MagickFreeMemory(xform.r);
  }
  image->is_grayscale=is_grayscale;
  image->colorspace=RGBColorspace;
  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                        "Colorspace transform completed");
  return(status);
}
