/*
% Copyright (C) 2003 - 2016 GraphicsMagick Group
% Copyright (C) 2002 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.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%                            X   X  PPPP   M   M                              %
%                             X X   P   P  MM MM                              %
%                              X    PPPP   M M M                              %
%                             X X   P      M   M                              %
%                            X   X  P      M   M                              %
%                                                                             %
%                                                                             %
%                 Read/Write X Windows system Pixmap Format.                  %
%                                                                             %
%                                                                             %
%                              Software Design                                %
%                                John Cristy                                  %
%                                 July 1992                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%
*/

/*
  Include declarations.
*/
#include "magick/studio.h"
#include "magick/analyze.h"
#include "magick/blob.h"
#include "magick/color.h"
#include "magick/color_lookup.h"
#include "magick/colormap.h"
#include "magick/log.h"
#include "magick/magick.h"
#include "magick/monitor.h"
#include "magick/pixel_cache.h"
#include "magick/quantize.h"
#include "magick/resize.h"
#include "magick/utility.h"


/*
  Forward declarations.
*/
static unsigned int
  WritePICONImage(const ImageInfo *,Image *),
  WriteXPMImage(const ImageInfo *,Image *);

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   I s X P M                                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method IsXPM returns True if the image format type, identified by the
%  magick string, is XPM.
%
%  The format of the IsXPM method is:
%
%      unsigned int IsXPM(const unsigned char *magick,const size_t length)
%
%  A description of each parameter follows:
%
%    o status:  Method IsXPM returns True if the image format type is XPM.
%
%    o magick: This string is generally the first few bytes of an image file
%      or blob.
%
%    o length: Specifies the length of the magick string.
%
%
*/
static unsigned int IsXPM(const unsigned char *magick,const size_t length)
{
  if (length < 9)
    return(False);
  if (LocaleNCompare((char *) magick,"/* XPM */",9) == 0)
    return(True);
  return(False);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e a d X P M I m a g e                                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method ReadXPMImage reads an X11 pixmap image file 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 ReadXPMImage method is:
%
%      Image *ReadXPMImage(const ImageInfo *image_info,ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image:  Method ReadXPMImage returns a pointer to the image after
%      creating it. A null image is returned if there is a memory shortage
%      or if the image cannot be read.
%
%    o image_info: Specifies a pointer to a ImageInfo structure.
%
%    o exception: return any errors or warnings in this structure.
%
%
*/

static char *ParseColor(char *data)
{
#define NumberTargets  6

  static const char
    * const targets[NumberTargets] = { "c ", "g ", "g4 ", "m ", "b ", "s " };

  register char
    *p,
    *r;

  register const char
    *q;

  register long
    i;

  for (i=0; i < NumberTargets; i++)
  {
    r=data;
    for (q=targets[i]; *r != '\0'; r++)
    {
      if (*r != *q)
        continue;
      if (!isspace((int) (*(r-1))))
        continue;
      p=r;
      for ( ; ; )
      {
        if (*q == '\0')
          return(r);
        if (*p++ != *q++)
          break;
      }
      q=targets[i];
    }
  }
  return((char *) NULL);
}
#define ThrowXPMReaderException(code_,reason_,image_) \
do { \
  if (keys) \
    for (i=0; i < (long) image->colors; i++) \
      MagickFreeMemory(keys[i]); \
  MagickFreeMemory(keys); \
  if (textlist) \
    for (i=0; textlist[i] != (char *) NULL; i++) \
      MagickFreeMemory(textlist[i]); \
  MagickFreeMemory(textlist); \
  MagickFreeMemory(xpm_buffer); \
  ThrowReaderException(code_,reason_,image_); \
} while (0);

static Image *ReadXPMImage(const ImageInfo *image_info,ExceptionInfo *exception)
{
  char
    key[MaxTextExtent],
    **keys = (char **) NULL,
    target[MaxTextExtent],
    **textlist = (char **) NULL,
    *xpm_buffer = (char *) NULL;

  Image
    *image;

  int
    count;

  unsigned long
    j,
    none;

  long
    k,
    y;

  register char
    *p,
    *q;

  register IndexPacket
    *indexes;

  register long
    x;

  register PixelPacket
    *r;

  register long
    i;

  unsigned int
    status;

  size_t
    length;

  unsigned long
    width; /* characters per pixel */

  /*
    Open image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickSignature);
  image=AllocateImage(image_info);
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
  if (status == MagickFail)
    ThrowXPMReaderException(FileOpenError,UnableToOpenFile,image);
  /*
    Read XPM file.
  */
  length=MaxTextExtent;
  xpm_buffer=MagickAllocateMemory(char *,length);
  if (xpm_buffer != (char *) NULL)
    {
      xpm_buffer[0]='\0';
      p=xpm_buffer;
      while (ReadBlobString(image,p) != (char *) NULL)
        {
          if (*p == '#')
            if ((p == xpm_buffer) || (*(p-1) == '\n'))
              continue;
          if ((*p == '}') && (*(p+1) == ';'))
            break;
          p+=strlen(p);
          if (((size_t) (p-xpm_buffer)+MaxTextExtent+1) < length)
            continue;
          length<<=1;
          MagickReallocMemory(char *,xpm_buffer,length);
          if (xpm_buffer == (char *) NULL)
            break;
          p=xpm_buffer+strlen(xpm_buffer);
        }
    }
  if (xpm_buffer == (char *) NULL)
    ThrowXPMReaderException(ResourceLimitError,MemoryAllocationFailed,image);
  /*
    Remove comments.
  */
  count=0;
  for (p=xpm_buffer; *p != '\0'; p++)
  {
    if (*p != '"')
      continue;
    count=sscanf(p+1,"%lu %lu %u %lu",&image->columns,&image->rows,
      &image->colors,&width);
    if (count == 4)
      {
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Columns: %lu, Rows: %lu, Colors: %u, Char Per Pixel: %lu",
                              image->columns, image->rows, image->colors, width);
        break;
      }
  }
  if ((count != 4) || (width == 0) || (width > 2) ||
      (image->columns == 0) || (image->rows == 0) ||
      (image->colors == 0) || (image->colors > MaxColormapSize))
    ThrowXPMReaderException(CorruptImageError,ImproperImageHeader,image);
  image->depth=16;
  /*
    Remove unquoted characters.
  */
  {
    MagickBool inquote = MagickFalse;
    q=xpm_buffer;
    while (*p != '\0')
      {
        if (*p++ == '"')
          {
            if (inquote)
              *q++='\n';
            inquote = !inquote;
          }
        if (inquote)
          *q++=*p;
      }
    *q='\0';
    if (inquote)
      {
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Imbalanced quotes error");
        ThrowXPMReaderException(CorruptImageError,CorruptImage,image);
      }
  }
  /*
    Scan for non-white space binary control codes and reject file if
    they are present.  This is done because StringToList() converts
    such text content to hex and because it is not good XPM data.
  */
  for (p=xpm_buffer; *p != '\0'; p++)
    if (((unsigned char) *p < 32) && !isspace((int)(unsigned char) (*p)))
      break;
  if (*p != '\0')
    {
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Binary control codes error");
        ThrowXPMReaderException(CorruptImageError,CorruptImage,image);
    }
  textlist=StringToList(xpm_buffer);
  MagickFreeMemory(xpm_buffer);
  if (textlist == (char **) NULL)
    ThrowXPMReaderException(ResourceLimitError,MemoryAllocationFailed,image);
#if 0
  if (image->logging)
    {
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "TextList");
      for (i=0; textlist[i] != (char *) NULL; i++)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "    %lu: %s", i, textlist[i]);
    }
#endif

  /*
    Initialize image structure.
  */
  keys=MagickAllocateArray(char **,image->colors,sizeof(char *));
  if (keys == (char **) NULL)
    ThrowXPMReaderException(ResourceLimitError,MemoryAllocationFailed,image);
  for (i=0; i < (long) image->colors; i++)
    keys[i]=(char *) NULL;
  if (!AllocateImageColormap(image,image->colors))
    ThrowXPMReaderException(ResourceLimitError,MemoryAllocationFailed,image);

  /*
    Read image colormap.
  */
  i=1;
  none=(~0U);
  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                        "Parsing colormap...");
  for (j=0; j < image->colors; j++)
  {
    p=textlist[i++];
    if ((p == (char *) NULL) || (p[0] == '\0'))
      break;
    if (image->logging)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "    %lu: %s", i-1, textlist[i-1]);
    if (strlen(p) < width)
      break;
    keys[j]=MagickAllocateMemory(char *,width+1);
    if (keys[j] == (char *) NULL)
      ThrowXPMReaderException(ResourceLimitError,MemoryAllocationFailed,image);
    keys[j][width]='\0';
    (void) strncpy(keys[j],p,width);
    /*
      Parse color.
    */
    (void) strcpy(target,"gray");
    q=ParseColor(p+width);
    if (q != (char *) NULL)
      {
        while (!isspace((int) (*q)) && (*q != '\0'))
          q++;
        (void) strlcpy(target,q,MaxTextExtent);
        q=ParseColor(target);
        if (q != (char *) NULL)
          *q='\0';
      }
    Strip(target);
    if (LocaleCompare(target,"none") == 0)
      {
        image->storage_class=DirectClass;
        image->matte=True;
        none=j;
        (void) strcpy(target,"black");
      }
    if (!QueryColorDatabase(target,&image->colormap[j],exception))
      break;
  }
  if (j < image->colors)
    ThrowXPMReaderException(CorruptImageError,CorruptImage,image);
  image->depth=GetImageDepth(image,&image->exception);
  image->depth=NormalizeDepthToOctet(image->depth);
  j=0;
  key[width]='\0';
  if (!image_info->ping)
    {
      /*
        Read image pixels.
      */
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                        "Parsing pixels...");
      for (y=0; y < (long) image->rows; y++)
      {
        p=textlist[i++];
        if ((p == (char *) NULL) || (p[0] == '\0'))
          break;
        if (image->logging)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "    %lu: %s", i-1, textlist[i-1]);
        r=SetImagePixelsEx(image,0,y,image->columns,1,exception);
        if (r == (PixelPacket *) NULL)
          break;
        indexes=AccessMutableIndexes(image);
        for (x=0; x < (long) image->columns; x++)
        {
          /* (void) strncpy(key,p,width); */
          for (k=0; k < (long) width; k++)
            {
              key[k]=p[k];
              if (p[k] == '\0')
                {
                  status=MagickFail;
                  break;
                }
            }
          if (MagickFail == status)
            break;
          key[k]='\0';
          if (strcmp(key,keys[j]) != 0)
            for (j=0; j < Max(image->colors-1,1); j++)
              if (strcmp(key,keys[j]) == 0)
                break;
          VerifyColormapIndex(image,j);
          if (image->storage_class == PseudoClass)
            indexes[x]=(IndexPacket) j;
          *r=image->colormap[j];
          r->opacity=(Quantum)
            (j == none ? TransparentOpacity : OpaqueOpacity);
          r++;
          p+=width;
        }
        if (MagickFail == status)
          break;
        if (!SyncImagePixelsEx(image,exception))
          break;
      }
      if (y < (long) image->rows)
        ThrowXPMReaderException(CorruptImageError,InsufficientImageDataInFile,image);
    }
  /*
    Free resources.
  */
  for (i=0; i < (long) image->colors; i++)
    MagickFreeMemory(keys[i]);
  MagickFreeMemory(keys);
  for (i=0; textlist[i] != (char *) NULL; i++)
    MagickFreeMemory(textlist[i]);
  MagickFreeMemory(textlist);
  CloseBlob(image);
  return(image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e g i s t e r X P M I m a g e                                           %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method RegisterXPMImage adds attributes for the XPM 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 RegisterXPMImage method is:
%
%      RegisterXPMImage(void)
%
*/
ModuleExport void RegisterXPMImage(void)
{
  MagickInfo
    *entry;

  entry=SetMagickInfo("PICON");
  entry->decoder=(DecoderHandler) ReadXPMImage;
  entry->encoder=(EncoderHandler) WritePICONImage;
  entry->adjoin=False;
  entry->description="Personal Icon";
  entry->module="XPM";
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("PM");
  entry->decoder=(DecoderHandler) ReadXPMImage;
  entry->encoder=(EncoderHandler) WriteXPMImage;
  entry->adjoin=False;
  entry->stealth=True;
  entry->description="X Windows system pixmap (color)";
  entry->module="XPM";
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("XPM");
  entry->decoder=(DecoderHandler) ReadXPMImage;
  entry->encoder=(EncoderHandler) WriteXPMImage;
  entry->magick=(MagickHandler) IsXPM;
  entry->adjoin=False;
  entry->description="X Windows system pixmap (color)";
  entry->module="XPM";
  (void) RegisterMagickInfo(entry);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   U n r e g i s t e r X P M I m a g e                                       %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method UnregisterXPMImage removes format registrations made by the
%  XPM module from the list of supported formats.
%
%  The format of the UnregisterXPMImage method is:
%
%      UnregisterXPMImage(void)
%
*/
ModuleExport void UnregisterXPMImage(void)
{
  (void) UnregisterMagickInfo("PICON");
  (void) UnregisterMagickInfo("PM");
  (void) UnregisterMagickInfo("XPM");
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   W r i t e P I C O N I m a g e                                             %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Procedure WritePICONImage writes an image to a file in the Personal Icon
%  format.
%
%  The format of the WritePICONImage method is:
%
%      unsigned int WritePICONImage(const ImageInfo *image_info,Image *image)
%
%  A description of each parameter follows.
%
%    o status: Method WritePICONImage return True if the image is written.
%      False is returned is there is a memory shortage or if the image file
%      fails to write.
%
%    o image_info: Specifies a pointer to a ImageInfo structure.
%
%    o image:  A pointer to an Image structure.
%
%
*/
static unsigned int WritePICONImage(const ImageInfo *image_info,Image *image)
{
#define ColormapExtent  155
#define GraymapExtent  95
#define PiconGeometry  "48x48>"

  static const unsigned char
    Colormap[]=
    {
      0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x06, 0x00, 0x05, 0x00, 0xf4, 0x05,
      0x00, 0x00, 0x00, 0x00, 0x2f, 0x4f, 0x4f, 0x70, 0x80, 0x90, 0x7e, 0x7e,
      0x7e, 0xdc, 0xdc, 0xdc, 0xff, 0xff, 0xff, 0x00, 0x00, 0x80, 0x00, 0x00,
      0xff, 0x1e, 0x90, 0xff, 0x87, 0xce, 0xeb, 0xe6, 0xe6, 0xfa, 0x00, 0xff,
      0xff, 0x80, 0x00, 0x80, 0xb2, 0x22, 0x22, 0x2e, 0x8b, 0x57, 0x32, 0xcd,
      0x32, 0x00, 0xff, 0x00, 0x98, 0xfb, 0x98, 0xff, 0x00, 0xff, 0xff, 0x00,
      0x00, 0xff, 0x63, 0x47, 0xff, 0xa5, 0x00, 0xff, 0xd7, 0x00, 0xff, 0xff,
      0x00, 0xee, 0x82, 0xee, 0xa0, 0x52, 0x2d, 0xcd, 0x85, 0x3f, 0xd2, 0xb4,
      0x8c, 0xf5, 0xde, 0xb3, 0xff, 0xfa, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00,
      0x00, 0x00, 0x06, 0x00, 0x05, 0x00, 0x00, 0x05, 0x18, 0x20, 0x10, 0x08,
      0x03, 0x51, 0x18, 0x07, 0x92, 0x28, 0x0b, 0xd3, 0x38, 0x0f, 0x14, 0x49,
      0x13, 0x55, 0x59, 0x17, 0x96, 0x69, 0x1b, 0xd7, 0x85, 0x00, 0x3b,
    },
    Graymap[]=
    {
      0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x04, 0x00, 0x04, 0x00, 0xf3, 0x0f,
      0x00, 0x00, 0x00, 0x00, 0x12, 0x12, 0x12, 0x21, 0x21, 0x21, 0x33, 0x33,
      0x33, 0x45, 0x45, 0x45, 0x54, 0x54, 0x54, 0x66, 0x66, 0x66, 0x78, 0x78,
      0x78, 0x87, 0x87, 0x87, 0x99, 0x99, 0x99, 0xab, 0xab, 0xab, 0xba, 0xba,
      0xba, 0xcc, 0xcc, 0xcc, 0xde, 0xde, 0xde, 0xed, 0xed, 0xed, 0xff, 0xff,
      0xff, 0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00,
      0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x04, 0x0c, 0x10, 0x04, 0x31,
      0x48, 0x31, 0x07, 0x25, 0xb5, 0x58, 0x73, 0x4f, 0x04, 0x00, 0x3b,
    };

#define MaxCixels  92

  static const char
    Cixel[MaxCixels+1] = " .XoO+@#$%&*=-;:>,<1234567890qwertyuipasdfghjk"
                         "lzxcvbnmMNBVCZASDFGHJKLPIUYTREWQ!~^/()_`'][{}|";

  char
    buffer[MaxTextExtent],
    basename[MaxTextExtent],
    name[MaxTextExtent],
    symbol[MaxTextExtent];

  Image
    *picon,
    *map;

  int
    j;

  long
    k,
    y;

  RectangleInfo
    geometry;

  register const PixelPacket
    *p;

  register long
    i,
    x;

  register PixelPacket
    *q;

  unsigned int
    status,
    transparent;

  unsigned long
    characters_per_pixel,
    colors;

    ImageCharacteristics
      characteristics;

  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  /*
    Open output image file.
  */
  status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
  if (status == False)
    ThrowWriterException(FileOpenError,UnableToOpenFile,image);
  /*
    Ensure that image is in an RGB space.
  */
  (void) TransformColorspace(image,RGBColorspace);
  /*
    Analyze image to be written.
  */
  if (!GetImageCharacteristics(image,&characteristics,
                               (OptimizeType == image_info->type),
                               &image->exception))
    {
      CloseBlob(image);
      return MagickFail;
    }
  SetGeometry(image,&geometry);
  (void) GetMagickGeometry(PiconGeometry,&geometry.x,&geometry.y,
    &geometry.width,&geometry.height);
  picon=ThumbnailImage(image,geometry.width,geometry.height,&image->exception);
  if ((image_info->type != TrueColorType) &&
      (characteristics.grayscale))
    map=BlobToImage(image_info,Graymap,GraymapExtent,&image->exception);
  else
    map=BlobToImage(image_info,Colormap,ColormapExtent,&image->exception);
  if ((picon == (Image *) NULL) || (map == (Image *) NULL))
    return(False);
  status=MapImage(picon,map,image_info->dither);
  DestroyImage(map);
  transparent=False;
  if (picon->storage_class == PseudoClass)
    {
      CompressImageColormap(picon);
      if (picon->matte)
        transparent=True;
    }
  else
    {
      /*
        Convert DirectClass to PseudoClass picon.
      */
      if (picon->matte)
        {
          /*
            Map all the transparent pixels.
          */
          for (y=0; y < (long) picon->rows; y++)
          {
            q=GetImagePixels(picon,0,y,picon->columns,1);
            if (q == (PixelPacket *) NULL)
              break;
            for (x=0; x < (long) picon->columns; x++)
            {
              if (q->opacity == TransparentOpacity)
                transparent=True;
              else
                q->opacity=OpaqueOpacity;
              q++;
            }
            if (!SyncImagePixels(picon))
              break;
          }
        }
      (void) SetImageType(picon,PaletteType);
    }
  colors=picon->colors;
  if (transparent)
    {
      colors++;
      MagickReallocMemory(PixelPacket *,picon->colormap,colors*sizeof(PixelPacket));
      for (y=0; y < (long) picon->rows; y++)
      {
        register IndexPacket
          *indexes;

        q=GetImagePixels(picon,0,y,picon->columns,1);
        if (q == (PixelPacket *) NULL)
          break;
        indexes=AccessMutableIndexes(picon);
        for (x=0; x < (long) picon->columns; x++)
        {
          if (q->opacity == TransparentOpacity)
            indexes[x]=(IndexPacket) picon->colors;
          q++;
        }
        if (!SyncImagePixels(picon))
          break;
      }
    }
  /*
    Compute the character per pixel.
  */
  characters_per_pixel=1;
  for (k=MaxCixels; (long) colors > k; k*=MaxCixels)
    characters_per_pixel++;
  /*
    XPM header.
  */
  (void) WriteBlobString(image,"/* XPM */\n");
  GetPathComponent(picon->filename,BasePath,basename);
  (void) FormatString(buffer,"static char *%.1024s[] = {\n",basename);
  (void) WriteBlobString(image,buffer);
  (void) WriteBlobString(image,"/* columns rows colors chars-per-pixel */\n");
  FormatString(buffer,"\"%lu %lu %lu %ld\",\n",picon->columns,picon->rows,
    colors,characters_per_pixel);
  (void) WriteBlobString(image,buffer);
  for (i=0; i < (long) colors; i++)
  {
    /*
      Define XPM color.
    */
    picon->colormap[i].opacity=OpaqueOpacity;
    (void) QueryColorname(picon,picon->colormap+i,XPMCompliance,name,
      &picon->exception);
    if (transparent)
      {
        if (i == (long) (colors-1))
          (void) strcpy(name,"grey75");
      }
    /*
      Write XPM color.
    */
    k=i % MaxCixels;
    symbol[0]=Cixel[k];
    for (j=1; j < (long) characters_per_pixel; j++)
    {
      k=((i-k)/MaxCixels) % MaxCixels;
      symbol[j]=Cixel[k];
    }
    symbol[j]='\0';
    FormatString(buffer,"\"%.1024s c %.1024s\",\n",symbol,name);
    (void) WriteBlobString(image,buffer);
  }
  /*
    Define XPM pixels.
  */
  (void) WriteBlobString(image,"/* pixels */\n");
  for (y=0; y < (long) picon->rows; y++)
  {
    register const IndexPacket
      *indexes;

    p=AcquireImagePixels(picon,0,y,picon->columns,1,&picon->exception);
    if (p == (const PixelPacket *) NULL)
      break;
    indexes=AccessImmutableIndexes(picon);
    (void) WriteBlobString(image,"\"");
    for (x=0; x < (long) picon->columns; x++)
    {
      k=indexes[x] % MaxCixels;
      symbol[0]=Cixel[k];
      for (j=1; j < (long) characters_per_pixel; j++)
      {
        k=(((int) indexes[x]-k)/MaxCixels) % MaxCixels;
        symbol[j]=Cixel[k];
      }
      symbol[j]='\0';
      (void) strlcpy(buffer,symbol,MaxTextExtent);
      (void) WriteBlobString(image,buffer);
    }
    FormatString(buffer,"\"%.1024s\n",
      (y == (long) (picon->rows-1) ? "" : ","));
    (void) WriteBlobString(image,buffer);
    if (QuantumTick(y,picon->rows))
      if (!MagickMonitorFormatted(y,picon->rows,&image->exception,
                                  SaveImageText,image->filename,
                                  image->columns,image->rows))
        break;
  }
  DestroyImage(picon);
  (void) WriteBlobString(image,"};\n");
  CloseBlob(image);
  return(True);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   W r i t e X P M I m a g e                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Procedure WriteXPMImage writes an image to a file in the X pixmap format.
%
%  The format of the WriteXPMImage method is:
%
%      unsigned int WriteXPMImage(const ImageInfo *image_info,Image *image)
%
%  A description of each parameter follows.
%
%    o status: Method WriteXPMImage return True if the image is written.
%      False is returned is there is a memory shortage or if the image file
%      fails to write.
%
%    o image_info: Specifies a pointer to a ImageInfo structure.
%
%    o image:  A pointer to an Image structure.
%
%
*/
static unsigned int WriteXPMImage(const ImageInfo *image_info,Image *image)
{
#define MaxCixels  92

  static const char
    Cixel[MaxCixels+1] = " .XoO+@#$%&*=-;:>,<1234567890qwertyuipasdfghjk"
                         "lzxcvbnmMNBVCZASDFGHJKLPIUYTREWQ!~^/()_`'][{}|";

  char
    buffer[MaxTextExtent],
    basename[MaxTextExtent],
    name[MaxTextExtent],
    symbol[MaxTextExtent];

  int
    j;

  long
    k,
    y;

  register const PixelPacket
    *p;

  register long
    i,
    x;

  register PixelPacket
    *q;

  unsigned int
    status,
    transparent;

  unsigned long
    characters_per_pixel,
    colors;

  /*
    Open output image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
  if (status == False)
    ThrowWriterException(FileOpenError,UnableToOpenFile,image);
  (void) TransformColorspace(image,RGBColorspace);
  /*
    Limit color resolution to what XPM can traditionally handle
  */
  if (image->depth > 16)
    image->depth=16;
  transparent=False;
  if (image->storage_class == PseudoClass)
    {
      CompressImageColormap(image);
      if (image->matte)
        transparent=True;
    }
  else
    {
      /*
        Convert DirectClass to PseudoClass image.
      */
      if (image->matte)
        {
          /*
            Map all the transparent pixels.
          */
          for (y=0; y < (long) image->rows; y++)
          {
            q=GetImagePixels(image,0,y,image->columns,1);
            if (q == (PixelPacket *) NULL)
              break;
            for (x=0; x < (long) image->columns; x++)
            {
              if (q->opacity == TransparentOpacity)
                transparent=True;
              else
                q->opacity=OpaqueOpacity;
              q++;
            }
            if (!SyncImagePixels(image))
              break;
          }
        }
      (void) SetImageType(image,PaletteType);
    }
  colors=image->colors;
  if (transparent)
    {
      colors++;
      MagickReallocMemory(PixelPacket *,image->colormap,colors*sizeof(PixelPacket));
      for (y=0; y < (long) image->rows; y++)
      {
        register IndexPacket
          *indexes;

        q=GetImagePixels(image,0,y,image->columns,1);
        if (q == (PixelPacket *) NULL)
          break;
        indexes=AccessMutableIndexes(image);
        for (x=0; x < (long) image->columns; x++)
        {
          if (q->opacity == TransparentOpacity)
            indexes[x]=(IndexPacket) image->colors;
          q++;
        }
        if (!SyncImagePixels(image))
          break;
      }
    }
  /*
    Compute the character per pixel.
  */
  characters_per_pixel=1;
  for (k=MaxCixels; (long) colors > k; k*=MaxCixels)
    characters_per_pixel++;
  /*
    XPM header.
  */
  (void) WriteBlobString(image,"/* XPM */\n");
  GetPathComponent(image->filename,BasePath,basename);
  (void) FormatString(buffer,"static char *%.1024s[] = {\n",basename);
  (void) WriteBlobString(image,buffer);
  (void) WriteBlobString(image,"/* columns rows colors chars-per-pixel */\n");
  FormatString(buffer,"\"%lu %lu %lu %ld\",\n",image->columns,
    image->rows,colors,characters_per_pixel);
  (void) WriteBlobString(image,buffer);
  for (i=0; i < (long) colors; i++)
  {
    /*
      Define XPM color.
    */
    image->colormap[i].opacity=OpaqueOpacity;
    (void) QueryColorname(image,image->colormap+i,XPMCompliance,name,
      &image->exception);
    if (transparent)
      {
        if (i == (long) (colors-1))
          (void) strcpy(name,"None");
      }
    /*
      Write XPM color.
    */
    k=i % MaxCixels;
    symbol[0]=Cixel[k];
    for (j=1; j < (long) characters_per_pixel; j++)
    {
      k=((i-k)/MaxCixels) % MaxCixels;
      symbol[j]=Cixel[k];
    }
    symbol[j]='\0';
    FormatString(buffer,"\"%.1024s c %.1024s\",\n",symbol,name);
    (void) WriteBlobString(image,buffer);
  }
  /*
    Define XPM pixels.
  */
  (void) WriteBlobString(image,"/* pixels */\n");
  for (y=0; y < (long) image->rows; y++)
  {
    register const IndexPacket
      *indexes;

    p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
    if (p == (const PixelPacket *) NULL)
      break;
    indexes=AccessImmutableIndexes(image);
    (void) WriteBlobString(image,"\"");
    for (x=0; x < (long) image->columns; x++)
    {
      k=indexes[x] % MaxCixels;
      symbol[0]=Cixel[k];
      for (j=1; j < (long) characters_per_pixel; j++)
      {
        k=(((int) indexes[x]-k)/MaxCixels) % MaxCixels;
        symbol[j]=Cixel[k];
      }
      symbol[j]='\0';
      (void) strlcpy(buffer,symbol,MaxTextExtent);
      (void) WriteBlobString(image,buffer);
    }
    FormatString(buffer,"\"%.1024s\n",
      (y == (long) (image->rows-1) ? "" : ","));
    (void) WriteBlobString(image,buffer);
    if (QuantumTick(y,image->rows))
      if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                  SaveImageText,image->filename,
                                  image->columns,image->rows))
        break;
  }
  (void) WriteBlobString(image,"};\n");
  CloseBlob(image);
  return(True);
}
