/*
% Copyright (C) 2003-2020 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 **StringToListMod(char *text)
{
  char
    **textlist;

  register char
    *p;

  register size_t
    i;

  size_t
    lines;

  if (text == (char *) NULL)
    return((char **) NULL);

  /*
    Convert string to an ASCII list, modifying the input string.
  */
  lines=1;
  for (p=text; *p != '\0'; p++)
    if (*p == '\n')
      lines++;
  textlist=MagickAllocateResourceLimitedMemory(char **,(lines+1)*sizeof(char *));
  if (textlist == (char **) NULL)
    MagickFatalError3(ResourceLimitFatalError,MemoryAllocationFailed,
                      UnableToConvertText);
  i=0;
  textlist[i]=text;
  for (p=text; *p != '\0'; p++)
    {
      if (*p == '\r')
        {
          *p='\0';
          continue;
        }
      if (*p == '\n')
        {
          *p='\0';
          if (*(p+1) != '\0')
            {
              i++;
              textlist[i]=p+1;
            }
        }
    }
  i++;
  textlist[i]=(char *) NULL;
  return(textlist);
}

static char *ParseColor(char *data) MAGICK_FUNC_PURE;

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

  static const char
    targets[NumberTargets][3] = { "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++) \
      MagickFreeResourceLimitedMemory(keys[i]); \
  MagickFreeResourceLimitedMemory(keys); \
  MagickFreeResourceLimitedMemory(textlist); \
  MagickFreeResourceLimitedMemory(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;

  MagickPassFail
    status;

  MagickBool
    colormap_initialized;

  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=MagickAllocateResourceLimitedMemory(char *,length);
  if (xpm_buffer != (char *) NULL)
    {
      xpm_buffer[0]='\0';
      p=xpm_buffer;
      while (ReadBlobString(image,p) != (char *) NULL)
        {
          char *new_xpm_buffer;
          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;
          new_xpm_buffer=MagickReallocateResourceLimitedMemory(char *,xpm_buffer,length);
          if (new_xpm_buffer == (char *) NULL)
            {
              MagickFreeResourceLimitedMemory(xpm_buffer);
              break;
            }
          xpm_buffer=new_xpm_buffer;
          p=xpm_buffer+strlen(xpm_buffer);
        }
    }
  if (xpm_buffer == (char *) NULL)
    ThrowXPMReaderException(ResourceLimitError,MemoryAllocationFailed,image);
  /*
    Parse image properties from file header while skipping over comment markers.
  */
  count=0;
  for (p=xpm_buffer; ((p - xpm_buffer) < 512) && (*p != '\0'); p++)
  {
    if ((*p != '"') || !isdigit((int) *(p+1)))
      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.
  */
  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=StringToListMod(xpm_buffer);
  if (textlist == (char **) NULL)
    ThrowXPMReaderException(ResourceLimitError,MemoryAllocationFailed,image);
#if 0
  if (image->logging)
    {
      for (i=0; textlist[i] != (char *) NULL; i++)
        { };
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "TextList has %lu entries", i);
      (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=MagickAllocateResourceLimitedArray(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...");
  colormap_initialized=MagickFalse;
  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]=MagickAllocateResourceLimitedMemory(char *,(size_t) 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))
      {
        /* Promote warning to error */
        exception->severity = CorruptImageError;
        break;
      }
    /* We are going to be done now */
    if (j+1 == image->colors)
      colormap_initialized=MagickTrue;
  }
  if (!colormap_initialized)
    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++)
    MagickFreeResourceLimitedMemory(keys[i]);
  MagickFreeResourceLimitedMemory(keys);
  MagickFreeResourceLimitedMemory(textlist);
  MagickFreeResourceLimitedMemory(xpm_buffer);
  CloseBlob(image);
  StopTimer(&image->timer);
  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));
      if (picon->colormap == (PixelPacket *) NULL)
        ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,picon);
      picon->colormap[colors-1].red=picon->colormap[colors-1].green=picon->colormap[colors-1].blue=0;
      picon->colormap[colors-1].opacity=TransparentOpacity;
      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));
      if (image->colormap == (PixelPacket *) NULL)
        ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image);
      image->colormap[colors-1].red=image->colormap[colors-1].green=image->colormap[colors-1].blue=0;
      image->colormap[colors-1].opacity=TransparentOpacity;
      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);
}
