/*
% Copyright (C) 2003 - 2024 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.
%
% GraphicsMagick Colormap Methods
%
*/

/*
  Include declarations.
*/
#include "magick/studio.h"
#include "magick/analyze.h"
#include "magick/color.h"
#include "magick/colormap.h"
#include "magick/pixel_iterator.h"
#include "magick/utility.h"

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   A l l o c a t e I m a g e C o l o r m a p                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  AllocateImageColormap() allocates an image colormap and initializes
%  it to a linear gray colorspace with increasing intensity.  If the image
%  already has a colormap, it is replaced.  AllocateImageColormap() returns
%  MagickPass if successful, otherwise MagickFail if there is not enough memory.
%
%  The format of the AllocateImageColormap method is:
%
%      MagickPassFail AllocateImageColormap(Image *image,
%        const unsigned long colors)
%
%  A description of each parameter follows:
%
%    o image: The image.
%
%    o colors: The number of colors in the image colormap.
%
%
*/
MagickExport MagickPassFail AllocateImageColormap(Image *image,
  const unsigned long colors)
{
  register unsigned int
    i;

  size_t
    length;

  Quantum
    quantum;

  /*
    Allocate image colormap.
  */
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  if (colors > MaxColormapSize)
    return (MagickFail);
  image->storage_class=PseudoClass;
  image->colors=colors;
  length=MagickArraySize((size_t) image->colors,sizeof(PixelPacket));
  if (image->colormap == (PixelPacket *) NULL)
    image->colormap=MagickAllocateMemory(PixelPacket *,length);
  else
    MagickReallocMemory(PixelPacket *,image->colormap,length);
  if (image->colormap == (PixelPacket *) NULL)
    {
      image->colors=0;
      image->storage_class=DirectClass;
      return(MagickFail);
    }
  for (i=0; i < image->colors; i++)
  {
    quantum=(Quantum) (i*(MaxRGB/Max(colors-1,1)));
    image->colormap[i].red=quantum;
    image->colormap[i].green=quantum;
    image->colormap[i].blue=quantum;
    image->colormap[i].opacity=OpaqueOpacity;
  }
  return(MagickPass);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%     C y c l e C o l o r m a p I m a g e                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  CycleColormap() displaces an image's colormap by a given number of
%  positions.  If you cycle the colormap a number of times you can produce
%  a psychodelic effect.
%
%  The format of the CycleColormapImage method is:
%
%      MagickPassFail CycleColormapImage(Image *image,const int amount)
%
%  A description of each parameter follows:
%
%    o image: The image.
%
%    o amount:  Offset the colormap this much.
%
%
*/
#define CycleColormapImageText "[%s] Cycle colormap..."
static MagickPassFail
CycleColormapCallBack(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 */
{
  /*
    Cycle colormap by amount.
  */
  const int
    amount = *((const int *) immutable_data);


  long
    colormap_index;

  register const PixelPacket
    *colormap = image->colormap;

  const unsigned long
    colors = image->colors;

  register long
    i;

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

  for (i=0; i < npixels; i++)
    {
      colormap_index=(long) ((indexes[i]+amount) % colors);
      if (colormap_index < 0L)
        colormap_index+=colors;
      indexes[i]=(IndexPacket) colormap_index;
      pixels[i].red=colormap[colormap_index].red;
      pixels[i].green=colormap[colormap_index].green;
      pixels[i].blue=colormap[colormap_index].blue;
    }

  return MagickPass;
}
MagickExport MagickPassFail  CycleColormapImage(Image *image,const int amount)
{
  unsigned int
    is_grayscale,
    is_monochrome,
    status = MagickPass;

  PixelIteratorOptions
    options;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  is_grayscale=image->is_grayscale;
  is_monochrome=image->is_monochrome;
  if (image->storage_class == DirectClass)
    (void) SetImageType(image,PaletteType);

  InitializePixelIteratorOptions(&options,&image->exception);
#if defined(HAVE_OPENMP) && defined(DisableSlowOpenMP)
  options.max_threads=1;
#endif
  status=PixelIterateMonoModify(CycleColormapCallBack,&options,
                                CycleColormapImageText,
                                NULL,&amount,0,0,image->columns,image->rows,
                                image,&image->exception);
  image->is_grayscale=is_grayscale;
  image->is_monochrome=is_monochrome;
  return(status);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   M a g i c k C o n s t r a i n C o l o r m a p I n d e x                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  MagickConstrainColormapIndex() is a subordinate function for use by
%  the VerifyColormapIndex macro.  If the colormap index is outside the
%  bounds of the image colormap then zero is returned and an
%  InvalidColormapIndex exception is thrown, otherwise the colormap
%  index is returned.  The use of this function is DEPRECATED.
%
%  The format of the MagickConstrainColormapIndex method is:
%
%      unsigned int MagickConstrainColormapIndex(Image *image,
%                                                unsigned int index)
%
%  A description of each parameter follows:
%
%    o image: The image.
%
%    o index: The colormap index
%
%
*/
MagickExport unsigned int
MagickConstrainColormapIndex(Image *image, unsigned int index)
{
  if (index < image->colors)
    return index;

  if (image->exception.severity < CorruptImageError )
    {
      char
        colormapIndexBuffer[MaxTextExtent];

      FormatString(colormapIndexBuffer,"index %u >= %u colors, %.1024s",
                   index, image->colors, image->filename);
      errno=0;
      ThrowException(&image->exception,CorruptImageError,
                     InvalidColormapIndex,colormapIndexBuffer);
    }
  return 0U;
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   R e a l l o c a t e I m a g e C o l o r m a p                             %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  ReallocateImageColormap() reallocates an image colormap (or allocates it
%  if is not already allocated) and clears any added colormap entries
%  while preserving existing entries. ReallocateImageColormap() returns
%  MagickPass if successful, otherwise MagickFail if there is not enough
%  memory or the number of colormap entries is not supported.
%
%  The image storage class is not modified by this function.
%
%  The format of the ReallocateImageColormap method is:
%
%      MagickPassFail ReallocateImageColormap(Image *image,
%        const unsigned int colors)
%
%  A description of each parameter follows:
%
%    o image: The image.
%
%    o colors: The number of colors in the image colormap.
%
%
*/
MagickExport MagickPassFail ReallocateImageColormap(Image *image,
  const unsigned int colors)
{
  register unsigned int
    i;

  unsigned int
    prev_colors;

  size_t
    length;

  /*
    Allocate image colormap.
  */
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  if (colors > MaxColormapSize)
    return (MagickFail);
  prev_colors=image->colors;
  length=MagickArraySize((size_t) colors,sizeof(PixelPacket));
  MagickReallocMemory(PixelPacket *,image->colormap,length);
  if (image->colormap == (PixelPacket *) NULL)
    {
      image->colors=0;
      return(MagickFail);
    }
  image->colors=colors;
  for (i=prev_colors; i < image->colors; i++)
  {
    image->colormap[i].red=0;
    image->colormap[i].green=0;
    image->colormap[i].blue=0;
    image->colormap[i].opacity=OpaqueOpacity;
  }
  return(MagickPass);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e p l a c e I m a g e C o l o r m a p                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  ReplaceImageColormap() replaces an existing image colormap with a new
%  image colormap. The new image colormap is expected to contain all of the
%  colors from the existing colormap. The existing colormap indexes are
%  adjusted to conform with positions in the new colormap. If the new
%  colormap contains duplicate entries, then the associated colormap index
%  will point to the first entry found in the colormap and other matching
%  entries will not be used. MagickPass is returned if the operation is
%  successful, otherwise MagickFail is returned, and image->exception is
%  updated with the cause of the failure.
%
%  This function is useful in case colormap entries need to match across
%  multiple images or otherwise occupy specific locations.
%
%  The format of the ReplaceImageColormap method is:
%
%      MagickPassFail ReplaceImageColormap(Image *image,
%                                          const PixelPacket *colormap,
%                                          const unsigned int colors)
%
%  A description of each parameter follows:
%
%    o image: image in which to replace colormap.
%
%    o colormap: new colormap.
%
%    o colors: number of colors in new colormap.
%
*/
#define ReplaceImageColormapText "[%s] Replacing image colormap..."
static MagickPassFail
ReplaceImageColormapCallBack(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 */
{
  /*
    Replace image colormap
  */

  const unsigned int
    *colormap_index=(const unsigned int *) immutable_data;

  register long
    i;

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

  for (i=0; i < npixels; i++)
    indexes[i]=colormap_index[indexes[i]];

  return MagickPass;
}
MagickExport MagickPassFail
ReplaceImageColormap(Image *image,
                     const PixelPacket *colormap,
                     const unsigned int colors)
{
  unsigned int
    *colormap_index=(unsigned int *) NULL;

  PixelPacket
    *new_colormap;

  register unsigned int
    i,
    j;

  MagickPassFail
    status=MagickPass;

  assert(image != (Image *) NULL);
  assert(colormap != (const PixelPacket *) NULL);
  assert(colors != 0);

  /*
    Input image is required to have a colormap
  */
  if ((image->storage_class != PseudoClass) ||
      (image->colormap == (PixelPacket *) NULL) ||
      (image->colors == 0U))
    {
      ThrowException(&image->exception,ImageError,
                     ImageIsNotColormapped,image->filename);
      return MagickFail;
    }

  /*
    Allocate memory for colormap index
  */
  colormap_index=MagickAllocateArray(unsigned int *,
                                     MaxColormapSize,sizeof(unsigned int));
  if (colormap_index == (unsigned int *) NULL)
    {
      ThrowException3(&image->exception,ResourceLimitError,
                      MemoryAllocationFailed,UnableToAllocateColormap);
      return MagickFail;
    }

  /*
    Allocate replacement colormap
  */
  new_colormap=MagickAllocateArray(PixelPacket *,
                                   colors,sizeof(PixelPacket));
  if (new_colormap == (PixelPacket *) NULL)
    {
      MagickFreeMemory(colormap_index);
      ThrowException3(&image->exception,ResourceLimitError,
                      MemoryAllocationFailed,UnableToAllocateColormap);
      return MagickFail;
    }

  /*
    Build a map between the new colormap and the old colormap.
  */
  (void) memset(colormap_index,0,MaxColormapSize*sizeof(unsigned int));
  for (i=0; i < image->colors ; i++)
    {
      for (j=0; j < colors; j++)
        {
          if (ColorMatch(&colormap[j],&image->colormap[i]))
            {
              colormap_index[i]=j;
              break;
            }
        }
    }

  if (status == MagickPass)
    {
      /*
        Reassign image colormap indexes
      */
      status=PixelIterateMonoModify(ReplaceImageColormapCallBack,NULL,
                                    ReplaceImageColormapText,
                                    NULL,colormap_index,0,0,
                                    image->columns,image->rows,
                                    image,&image->exception);
      /*
        Replace existing colormap.
      */
      if (status == MagickPass)
        {
          (void) memcpy(new_colormap,colormap,sizeof(PixelPacket)*colors);
          MagickFreeMemory(image->colormap);
          image->colormap=new_colormap;
          new_colormap=(PixelPacket *) NULL;

        }
    }

  MagickFreeMemory(new_colormap);
  MagickFreeMemory(colormap_index);

  image->is_grayscale=IsGrayImage(image,&image->exception);
  image->is_monochrome=IsMonochromeImage(image,&image->exception);

  return status;
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   S o r t C o l o r m a p B y I n t e n t s i t y                           %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  SortColormapByIntensity() sorts the colormap of a PseudoClass image by
%  decreasing color intensity.
%
%  The format of the SortColormapByIntensity method is:
%
%      MagickPassFail SortColormapByIntensity(Image *image)
%
%  A description of each parameter follows:
%
%    o image: A pointer to an Image structure.
%
%
*/

#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif

static int InverseIntensityCompare(const void *x,const void *y)
{
  long
    intensity;

  PixelPacket
    *color_1,
    *color_2;

  color_1=(PixelPacket *) x;
  color_2=(PixelPacket *) y;
  /*
    y - x results in decreasing order
  */
  intensity=PixelIntensityToQuantum(color_2)-
    (long) PixelIntensityToQuantum(color_1);
  return(intensity);
}

#if defined(__cplusplus) || defined(c_plusplus)
}
#endif

#define SortColormapByIntensityText "[%s] Sorting colormap by intensity...  "
static MagickPassFail
SortColormapByIntensityCallBack(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 */
{
  /*
    Update image colormap indexes to reflect new ordering indicated by
    new_indexes array.
  */
  const unsigned short
    *new_indexes = (const unsigned short *) immutable_data;

  register long
    i;

  ARG_NOT_USED(mutable_data);
  ARG_NOT_USED(image);
  ARG_NOT_USED(pixels);
  ARG_NOT_USED(exception);
  for (i=0; i < npixels; i++)
    indexes[i]=new_indexes[indexes[i]];

  return MagickPass;
}
MagickExport MagickPassFail SortColormapByIntensity(Image *image)
{
  register long
    i;

  unsigned int
    is_grayscale,
    is_monochrome;

  unsigned short
    *new_indexes;

  MagickPassFail
    status=MagickPass;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  if (image->storage_class != PseudoClass)
    return(MagickFail);
  is_grayscale=image->is_grayscale;
  is_monochrome=image->is_monochrome;
  /*
    Allocate memory for pixel indexes.
  */
  new_indexes=MagickAllocateMemory(unsigned short *,image->colors*sizeof(unsigned short));
  if (new_indexes == (unsigned short *) NULL)
    ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed,
                          UnableToSortImageColormap);
  /*
    Assign index values to colormap entries.
  */
  for (i=0; i < (long) image->colors; i++)
    image->colormap[i].opacity=(unsigned short) i;
  /*
    Sort image colormap by decreasing intensity.
  */
  qsort((void *) image->colormap,image->colors,sizeof(PixelPacket),
        InverseIntensityCompare);
  /*
    Update image colormap indexes to new order.
  */
  for (i=0; i < (long) image->colors; i++)
    new_indexes[image->colormap[i].opacity]=(unsigned short) i;
  status=PixelIterateMonoModify(SortColormapByIntensityCallBack,NULL,
                                SortColormapByIntensityText,
                                NULL,new_indexes,0,0,image->columns,image->rows,
                                image,&image->exception);
  MagickFreeMemory(new_indexes);
  image->is_grayscale=is_grayscale;
  image->is_monochrome=is_monochrome;
  return(status);
}
