xpm.c revision 6f1d4197adb5c45d716ce2957826101e8da5f136
1/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3%                                                                             %
4%                                                                             %
5%                                                                             %
6%                            X   X  PPPP   M   M                              %
7%                             X X   P   P  MM MM                              %
8%                              X    PPPP   M M M                              %
9%                             X X   P      M   M                              %
10%                            X   X  P      M   M                              %
11%                                                                             %
12%                                                                             %
13%                  Read/Write X Windows system Pixmap Format                  %
14%                                                                             %
15%                              Software Design                                %
16%                                   Cristy                                    %
17%                                 July 1992                                   %
18%                                                                             %
19%                                                                             %
20%  Copyright 1999-2015 ImageMagick Studio LLC, a non-profit organization      %
21%  dedicated to making software imaging solutions freely available.           %
22%                                                                             %
23%  You may not use this file except in compliance with the License.  You may  %
24%  obtain a copy of the License at                                            %
25%                                                                             %
26%    http://www.imagemagick.org/script/license.php                            %
27%                                                                             %
28%  Unless required by applicable law or agreed to in writing, software        %
29%  distributed under the License is distributed on an "AS IS" BASIS,          %
30%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
31%  See the License for the specific language governing permissions and        %
32%  limitations under the License.                                             %
33%                                                                             %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37*/
38
39/*
40  Include declarations.
41*/
42#include "MagickCore/studio.h"
43#include "MagickCore/attribute.h"
44#include "MagickCore/blob.h"
45#include "MagickCore/blob-private.h"
46#include "MagickCore/cache.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colormap.h"
50#include "MagickCore/colorspace.h"
51#include "MagickCore/colorspace-private.h"
52#include "MagickCore/exception.h"
53#include "MagickCore/exception-private.h"
54#include "MagickCore/geometry.h"
55#include "MagickCore/image.h"
56#include "MagickCore/image-private.h"
57#include "MagickCore/list.h"
58#include "MagickCore/magick.h"
59#include "MagickCore/memory_.h"
60#include "MagickCore/monitor.h"
61#include "MagickCore/monitor-private.h"
62#include "MagickCore/pixel-accessor.h"
63#include "MagickCore/quantize.h"
64#include "MagickCore/quantum-private.h"
65#include "MagickCore/resize.h"
66#include "MagickCore/resource_.h"
67#include "MagickCore/splay-tree.h"
68#include "MagickCore/static.h"
69#include "MagickCore/string_.h"
70#include "MagickCore/module.h"
71#include "MagickCore/threshold.h"
72#include "MagickCore/utility.h"
73
74/*
75  Forward declarations.
76*/
77static MagickBooleanType
78  WritePICONImage(const ImageInfo *,Image *,ExceptionInfo *),
79  WriteXPMImage(const ImageInfo *,Image *,ExceptionInfo *);
80
81/*
82%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83%                                                                             %
84%                                                                             %
85%                                                                             %
86%   I s X P M                                                                 %
87%                                                                             %
88%                                                                             %
89%                                                                             %
90%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91%
92%  IsXPM() returns MagickTrue if the image format type, identified by the
93%  magick string, is XPM.
94%
95%  The format of the IsXPM method is:
96%
97%      MagickBooleanType IsXPM(const unsigned char *magick,const size_t length)
98%
99%  A description of each parameter follows:
100%
101%    o magick: compare image format pattern against these bytes. or
102%      blob.
103%
104%    o length: Specifies the length of the magick string.
105%
106*/
107static MagickBooleanType IsXPM(const unsigned char *magick,const size_t length)
108{
109  if (length < 9)
110    return(MagickFalse);
111  if (LocaleNCompare((char *) magick+1,"* XPM *",7) == 0)
112    return(MagickTrue);
113  return(MagickFalse);
114}
115
116/*
117%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
118%                                                                             %
119%                                                                             %
120%                                                                             %
121%   R e a d X P M I m a g e                                                   %
122%                                                                             %
123%                                                                             %
124%                                                                             %
125%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
126%
127%  ReadXPMImage() reads an X11 pixmap image file and returns it.  It
128%  allocates the memory necessary for the new Image structure and returns a
129%  pointer to the new image.
130%
131%  The format of the ReadXPMImage method is:
132%
133%      Image *ReadXPMImage(const ImageInfo *image_info,ExceptionInfo *exception)
134%
135%  A description of each parameter follows:
136%
137%    o image_info: the image info.
138%
139%    o exception: return any errors or warnings in this structure.
140%
141*/
142
143static int CompareXPMColor(const void *target,const void *source)
144{
145  const char
146    *p,
147    *q;
148
149  p=(const char *) target;
150  q=(const char *) source;
151  return(strcmp(p,q));
152}
153
154static size_t CopyXPMColor(char *destination,const char *source,size_t length)
155{
156  register const char
157    *p;
158
159  p=source;
160  while (length-- && (*p != '\0'))
161    *destination++=(*p++);
162  if (length != 0)
163    *destination='\0';
164  return((size_t) (p-source-1));
165}
166
167static char *NextXPMLine(char *p)
168{
169  assert(p != (char *) NULL);
170  p=strchr(p,'\n');
171  if (p != (char *) NULL)
172    p++;
173  return(p);
174}
175
176static char *ParseXPMColor(char *color,MagickBooleanType search_start)
177{
178#define NumberTargets  6
179
180  register char
181    *p,
182    *r;
183
184  register const char
185    *q;
186
187  register ssize_t
188    i;
189
190  static const char
191    *targets[NumberTargets] = { "c ", "g ", "g4 ", "m ", "b ", "s " };
192
193  if (search_start != MagickFalse)
194    {
195      for (i=0; i < NumberTargets; i++)
196      {
197        p=color;
198        for (q=targets[i]; *p != '\0'; p++)
199        {
200          if (*p == '\n')
201            break;
202          if (*p != *q)
203            continue;
204          if (isspace((int) ((unsigned char) (*(p-1)))) == 0)
205            continue;
206          r=p;
207          for ( ; ; )
208          {
209            if (*q == '\0')
210              return(p);
211            if (*r++ != *q++)
212              break;
213          }
214          q=targets[i];
215        }
216      }
217      return((char *) NULL);
218    }
219  for (p=color+1; *p != '\0'; p++)
220  {
221    if (*p == '\n')
222      break;
223    if (isspace((int) ((unsigned char) (*(p-1)))) == 0)
224      continue;
225    if (isspace((int) ((unsigned char) (*p))) != 0)
226      continue;
227    for (i=0; i < NumberTargets; i++)
228    {
229      if ((*p == *targets[i]) && (*(p+1) == *(targets[i]+1)))
230        return(p);
231    }
232  }
233  return(p);
234}
235
236static Image *ReadXPMImage(const ImageInfo *image_info,ExceptionInfo *exception)
237{
238  char
239    *grey,
240    key[MaxTextExtent],
241    target[MaxTextExtent],
242    *xpm_buffer;
243
244  Image
245    *image;
246
247  MagickBooleanType
248    active,
249    status;
250
251  register char
252    *next,
253    *p,
254    *q;
255
256  register ssize_t
257    x;
258
259  register Quantum
260    *r;
261
262  size_t
263    length;
264
265  SplayTreeInfo
266    *xpm_colors;
267
268  ssize_t
269    count,
270    j,
271    y;
272
273  unsigned long
274    colors,
275    columns,
276    rows,
277    width;
278
279  /*
280    Open image file.
281  */
282  assert(image_info != (const ImageInfo *) NULL);
283  assert(image_info->signature == MagickSignature);
284  if (image_info->debug != MagickFalse)
285    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
286      image_info->filename);
287  assert(exception != (ExceptionInfo *) NULL);
288  assert(exception->signature == MagickSignature);
289  image=AcquireImage(image_info,exception);
290  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
291  if (status == MagickFalse)
292    {
293      image=DestroyImageList(image);
294      return((Image *) NULL);
295    }
296  /*
297    Read XPM file.
298  */
299  length=MaxTextExtent;
300  xpm_buffer=(char *) AcquireQuantumMemory((size_t) length,sizeof(*xpm_buffer));
301  if (xpm_buffer == (char *) NULL)
302    ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
303  *xpm_buffer='\0';
304  p=xpm_buffer;
305  while (ReadBlobString(image,p) != (char *) NULL)
306  {
307    if ((*p == '#') && ((p == xpm_buffer) || (*(p-1) == '\n')))
308      continue;
309    if ((*p == '}') && (*(p+1) == ';'))
310      break;
311    p+=strlen(p);
312    if ((size_t) (p-xpm_buffer+MaxTextExtent) < length)
313      continue;
314    length<<=1;
315    xpm_buffer=(char *) ResizeQuantumMemory(xpm_buffer,length+MaxTextExtent,
316      sizeof(*xpm_buffer));
317    if (xpm_buffer == (char *) NULL)
318      break;
319    p=xpm_buffer+strlen(xpm_buffer);
320  }
321  if (xpm_buffer == (char *) NULL)
322    ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
323  /*
324    Remove comments.
325  */
326  count=0;
327  width=0;
328  for (p=xpm_buffer; *p != '\0'; p++)
329  {
330    if (*p != '"')
331      continue;
332    count=(ssize_t) sscanf(p+1,"%lu %lu %lu %lu",&columns,&rows,&colors,&width);
333    image->columns=columns;
334    image->rows=rows;
335    image->colors=colors;
336    if (count == 4)
337      break;
338  }
339  if ((count != 4) || (width > 10) || (image->columns == 0) ||
340      (image->rows == 0) || (image->colors == 0))
341    ThrowReaderException(CorruptImageError,"ImproperImageHeader");
342  /*
343    Remove unquoted characters.
344  */
345  active=MagickFalse;
346  q=xpm_buffer;
347  while (*p != '\0')
348  {
349    if (*p++ == '"')
350      {
351        if (active != MagickFalse)
352          *q++='\n';
353        active=active != MagickFalse ? MagickFalse : MagickTrue;
354      }
355    if (active != MagickFalse)
356      *q++=(*p);
357  }
358  *q='\0';
359  /*
360    Initialize image structure.
361  */
362  xpm_colors=NewSplayTree(CompareXPMColor,RelinquishMagickMemory,
363    (void *(*)(void *)) NULL);
364  if (AcquireImageColormap(image,image->colors,exception) == MagickFalse)
365    ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
366  /*
367    Read image colormap.
368  */
369  image->depth=1;
370  next=NextXPMLine(xpm_buffer);
371  for (j=0; (j < (ssize_t) image->colors) && (next != (char *) NULL); j++)
372  {
373    p=next;
374    next=NextXPMLine(p);
375    (void) CopyXPMColor(key,p,MagickMin((size_t) width,MaxTextExtent-1));
376    status=AddValueToSplayTree(xpm_colors,ConstantString(key),(void *) j);
377    /*
378      Parse color.
379    */
380    (void) CopyMagickString(target,"gray",MaxTextExtent);
381    q=ParseXPMColor(p+width,MagickTrue);
382    if (q != (char *) NULL)
383      {
384        while ((isspace((int) ((unsigned char) *q)) == 0) && (*q != '\0'))
385          q++;
386        if ((next-q) < 0)
387          break;
388        if (next != (char *) NULL)
389          (void) CopyXPMColor(target,q,MagickMin((size_t) (next-q),
390            MaxTextExtent-1));
391        else
392          (void) CopyMagickString(target,q,MaxTextExtent);
393        q=ParseXPMColor(target,MagickFalse);
394        if (q != (char *) NULL)
395          *q='\0';
396      }
397    StripString(target);
398    grey=strstr(target,"grey");
399    if (grey != (char *) NULL)
400      grey[2]='a';
401    if (LocaleCompare(target,"none") == 0)
402      {
403        image->storage_class=DirectClass;
404        image->alpha_trait=BlendPixelTrait;
405      }
406    status=QueryColorCompliance(target,XPMCompliance,&image->colormap[j],
407      exception);
408    if (status == MagickFalse)
409      break;
410    if (image->depth < image->colormap[j].depth)
411      image->depth=image->colormap[j].depth;
412  }
413  if (j < (ssize_t) image->colors)
414    ThrowReaderException(CorruptImageError,"CorruptImage");
415  j=0;
416  if (image_info->ping == MagickFalse)
417    {
418      /*
419        Read image pixels.
420      */
421      status=SetImageExtent(image,image->columns,image->rows,exception);
422      if (status == MagickFalse)
423        return(DestroyImageList(image));
424      for (y=0; y < (ssize_t) image->rows; y++)
425      {
426        p=NextXPMLine(p);
427        if (p == (char *) NULL)
428          break;
429        r=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
430        if (r == (Quantum *) NULL)
431          break;
432        for (x=0; x < (ssize_t) image->columns; x++)
433        {
434          p+=CopyXPMColor(key,p,MagickMin(width,MaxTextExtent-1));
435          j=(ssize_t) GetValueFromSplayTree(xpm_colors,key);
436          if (image->storage_class == PseudoClass)
437            SetPixelIndex(image,(Quantum) j,r);
438          SetPixelInfoPixel(image,image->colormap+j,r);
439          r+=GetPixelChannels(image);
440        }
441        if (SyncAuthenticPixels(image,exception) == MagickFalse)
442          break;
443      }
444      if (y < (ssize_t) image->rows)
445        ThrowReaderException(CorruptImageError,"NotEnoughPixelData");
446    }
447  /*
448    Relinquish resources.
449  */
450  xpm_colors=DestroySplayTree(xpm_colors);
451  (void) CloseBlob(image);
452  return(GetFirstImageInList(image));
453}
454
455/*
456%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
457%                                                                             %
458%                                                                             %
459%                                                                             %
460%   R e g i s t e r X P M I m a g e                                           %
461%                                                                             %
462%                                                                             %
463%                                                                             %
464%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
465%
466%  RegisterXPMImage() adds attributes for the XPM image format to
467%  the list of supported formats.  The attributes include the image format
468%  tag, a method to read and/or write the format, whether the format
469%  supports the saving of more than one frame to the same file or blob,
470%  whether the format supports native in-memory I/O, and a brief
471%  description of the format.
472%
473%  The format of the RegisterXPMImage method is:
474%
475%      size_t RegisterXPMImage(void)
476%
477*/
478ModuleExport size_t RegisterXPMImage(void)
479{
480  MagickInfo
481    *entry;
482
483  entry=SetMagickInfo("PICON");
484  entry->decoder=(DecodeImageHandler *) ReadXPMImage;
485  entry->encoder=(EncodeImageHandler *) WritePICONImage;
486  entry->adjoin=MagickFalse;
487  entry->description=ConstantString("Personal Icon");
488  entry->module=ConstantString("XPM");
489  (void) RegisterMagickInfo(entry);
490  entry=SetMagickInfo("PM");
491  entry->decoder=(DecodeImageHandler *) ReadXPMImage;
492  entry->encoder=(EncodeImageHandler *) WriteXPMImage;
493  entry->adjoin=MagickFalse;
494  entry->stealth=MagickTrue;
495  entry->description=ConstantString("X Windows system pixmap (color)");
496  entry->module=ConstantString("XPM");
497  (void) RegisterMagickInfo(entry);
498  entry=SetMagickInfo("XPM");
499  entry->decoder=(DecodeImageHandler *) ReadXPMImage;
500  entry->encoder=(EncodeImageHandler *) WriteXPMImage;
501  entry->magick=(IsImageFormatHandler *) IsXPM;
502  entry->adjoin=MagickFalse;
503  entry->description=ConstantString("X Windows system pixmap (color)");
504  entry->module=ConstantString("XPM");
505  (void) RegisterMagickInfo(entry);
506  return(MagickImageCoderSignature);
507}
508
509/*
510%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
511%                                                                             %
512%                                                                             %
513%                                                                             %
514%   U n r e g i s t e r X P M I m a g e                                       %
515%                                                                             %
516%                                                                             %
517%                                                                             %
518%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
519%
520%  UnregisterXPMImage() removes format registrations made by the
521%  XPM module from the list of supported formats.
522%
523%  The format of the UnregisterXPMImage method is:
524%
525%      UnregisterXPMImage(void)
526%
527*/
528ModuleExport void UnregisterXPMImage(void)
529{
530  (void) UnregisterMagickInfo("PICON");
531  (void) UnregisterMagickInfo("PM");
532  (void) UnregisterMagickInfo("XPM");
533}
534
535/*
536%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
537%                                                                             %
538%                                                                             %
539%                                                                             %
540%   W r i t e P I C O N I m a g e                                             %
541%                                                                             %
542%                                                                             %
543%                                                                             %
544%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
545%
546%  WritePICONImage() writes an image to a file in the Personal Icon format.
547%
548%  The format of the WritePICONImage method is:
549%
550%      MagickBooleanType WritePICONImage(const ImageInfo *image_info,
551%        Image *image,ExceptionInfo *exception)
552%
553%  A description of each parameter follows.
554%
555%    o image_info: the image info.
556%
557%    o image:  The image.
558%
559%    o exception: return any errors or warnings in this structure.
560%
561*/
562static MagickBooleanType WritePICONImage(const ImageInfo *image_info,
563  Image *image,ExceptionInfo *exception)
564{
565#define ColormapExtent  155
566#define GraymapExtent  95
567#define PiconGeometry  "48x48>"
568
569  static unsigned char
570    Colormap[]=
571    {
572      0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x06, 0x00, 0x05, 0x00, 0xf4, 0x05,
573      0x00, 0x00, 0x00, 0x00, 0x2f, 0x4f, 0x4f, 0x70, 0x80, 0x90, 0x7e, 0x7e,
574      0x7e, 0xdc, 0xdc, 0xdc, 0xff, 0xff, 0xff, 0x00, 0x00, 0x80, 0x00, 0x00,
575      0xff, 0x1e, 0x90, 0xff, 0x87, 0xce, 0xeb, 0xe6, 0xe6, 0xfa, 0x00, 0xff,
576      0xff, 0x80, 0x00, 0x80, 0xb2, 0x22, 0x22, 0x2e, 0x8b, 0x57, 0x32, 0xcd,
577      0x32, 0x00, 0xff, 0x00, 0x98, 0xfb, 0x98, 0xff, 0x00, 0xff, 0xff, 0x00,
578      0x00, 0xff, 0x63, 0x47, 0xff, 0xa5, 0x00, 0xff, 0xd7, 0x00, 0xff, 0xff,
579      0x00, 0xee, 0x82, 0xee, 0xa0, 0x52, 0x2d, 0xcd, 0x85, 0x3f, 0xd2, 0xb4,
580      0x8c, 0xf5, 0xde, 0xb3, 0xff, 0xfa, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x00,
581      0x00, 0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00,
582      0x00, 0x00, 0x06, 0x00, 0x05, 0x00, 0x00, 0x05, 0x18, 0x20, 0x10, 0x08,
583      0x03, 0x51, 0x18, 0x07, 0x92, 0x28, 0x0b, 0xd3, 0x38, 0x0f, 0x14, 0x49,
584      0x13, 0x55, 0x59, 0x17, 0x96, 0x69, 0x1b, 0xd7, 0x85, 0x00, 0x3b,
585    },
586    Graymap[]=
587    {
588      0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x04, 0x00, 0x04, 0x00, 0xf3, 0x0f,
589      0x00, 0x00, 0x00, 0x00, 0x12, 0x12, 0x12, 0x21, 0x21, 0x21, 0x33, 0x33,
590      0x33, 0x45, 0x45, 0x45, 0x54, 0x54, 0x54, 0x66, 0x66, 0x66, 0x78, 0x78,
591      0x78, 0x87, 0x87, 0x87, 0x99, 0x99, 0x99, 0xab, 0xab, 0xab, 0xba, 0xba,
592      0xba, 0xcc, 0xcc, 0xcc, 0xde, 0xde, 0xde, 0xed, 0xed, 0xed, 0xff, 0xff,
593      0xff, 0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00,
594      0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x04, 0x0c, 0x10, 0x04, 0x31,
595      0x48, 0x31, 0x07, 0x25, 0xb5, 0x58, 0x73, 0x4f, 0x04, 0x00, 0x3b,
596    };
597
598#define MaxCixels  92
599
600  static const char
601    Cixel[MaxCixels+1] = " .XoO+@#$%&*=-;:>,<1234567890qwertyuipasdfghjk"
602                         "lzxcvbnmMNBVCZASDFGHJKLPIUYTREWQ!~^/()_`'][{}|";
603
604  char
605    buffer[MaxTextExtent],
606    basename[MaxTextExtent],
607    name[MaxTextExtent],
608    symbol[MaxTextExtent];
609
610  Image
611    *affinity_image,
612    *picon;
613
614  ImageInfo
615    *blob_info;
616
617  MagickBooleanType
618    status,
619    transparent;
620
621  PixelInfo
622    pixel;
623
624  QuantizeInfo
625    *quantize_info;
626
627  RectangleInfo
628    geometry;
629
630  register const Quantum
631    *p;
632
633  register ssize_t
634    i,
635    x;
636
637  register Quantum
638    *q;
639
640  size_t
641    characters_per_pixel,
642    colors;
643
644  ssize_t
645    j,
646    k,
647    y;
648
649  /*
650    Open output image file.
651  */
652  assert(image_info != (const ImageInfo *) NULL);
653  assert(image_info->signature == MagickSignature);
654  assert(image != (Image *) NULL);
655  assert(image->signature == MagickSignature);
656  if (image->debug != MagickFalse)
657    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
658  assert(exception != (ExceptionInfo *) NULL);
659  assert(exception->signature == MagickSignature);
660  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
661  if (status == MagickFalse)
662    return(status);
663  (void) TransformImageColorspace(image,sRGBColorspace,exception);
664  SetGeometry(image,&geometry);
665  (void) ParseMetaGeometry(PiconGeometry,&geometry.x,&geometry.y,
666    &geometry.width,&geometry.height);
667  picon=ResizeImage(image,geometry.width,geometry.height,TriangleFilter,
668    exception);
669  blob_info=CloneImageInfo(image_info);
670  (void) AcquireUniqueFilename(blob_info->filename);
671  if ((image_info->type != TrueColorType) &&
672      (IsImageGray(image,exception) != MagickFalse))
673    affinity_image=BlobToImage(blob_info,Graymap,GraymapExtent,exception);
674  else
675    affinity_image=BlobToImage(blob_info,Colormap,ColormapExtent,exception);
676  (void) RelinquishUniqueFileResource(blob_info->filename);
677  blob_info=DestroyImageInfo(blob_info);
678  if ((picon == (Image *) NULL) || (affinity_image == (Image *) NULL))
679    return(MagickFalse);
680  quantize_info=AcquireQuantizeInfo(image_info);
681  status=RemapImage(quantize_info,picon,affinity_image,exception);
682  quantize_info=DestroyQuantizeInfo(quantize_info);
683  affinity_image=DestroyImage(affinity_image);
684  transparent=MagickFalse;
685  if (picon->storage_class == PseudoClass)
686    {
687      (void) CompressImageColormap(picon,exception);
688      if (picon->alpha_trait != UndefinedPixelTrait)
689        transparent=MagickTrue;
690    }
691  else
692    {
693      /*
694        Convert DirectClass to PseudoClass picon.
695      */
696      if (picon->alpha_trait != UndefinedPixelTrait)
697        {
698          /*
699            Map all the transparent pixels.
700          */
701          for (y=0; y < (ssize_t) picon->rows; y++)
702          {
703            q=GetAuthenticPixels(picon,0,y,picon->columns,1,exception);
704            if (q == (Quantum *) NULL)
705              break;
706            for (x=0; x < (ssize_t) picon->columns; x++)
707            {
708              if (GetPixelAlpha(image,q) == (Quantum) TransparentAlpha)
709                transparent=MagickTrue;
710              else
711                SetPixelAlpha(picon,OpaqueAlpha,q);
712              q+=GetPixelChannels(picon);
713            }
714            if (SyncAuthenticPixels(picon,exception) == MagickFalse)
715              break;
716          }
717        }
718      (void) SetImageType(picon,PaletteType,exception);
719    }
720  colors=picon->colors;
721  if (transparent != MagickFalse)
722    {
723      colors++;
724      picon->colormap=(PixelInfo *) ResizeQuantumMemory((void **)
725        picon->colormap,(size_t) colors,sizeof(*picon->colormap));
726      if (picon->colormap == (PixelInfo *) NULL)
727        ThrowWriterException(ResourceLimitError,"MemoryAllocationError");
728      for (y=0; y < (ssize_t) picon->rows; y++)
729      {
730        q=GetAuthenticPixels(picon,0,y,picon->columns,1,exception);
731        if (q == (Quantum *) NULL)
732          break;
733        for (x=0; x < (ssize_t) picon->columns; x++)
734        {
735          if (GetPixelAlpha(image,q) == (Quantum) TransparentAlpha)
736            SetPixelIndex(picon,(Quantum) picon->colors,q);
737          q+=GetPixelChannels(picon);
738        }
739        if (SyncAuthenticPixels(picon,exception) == MagickFalse)
740          break;
741      }
742    }
743  /*
744    Compute the character per pixel.
745  */
746  characters_per_pixel=1;
747  for (k=MaxCixels; (ssize_t) colors > k; k*=MaxCixels)
748    characters_per_pixel++;
749  /*
750    XPM header.
751  */
752  (void) WriteBlobString(image,"/* XPM */\n");
753  GetPathComponent(picon->filename,BasePath,basename);
754  (void) FormatLocaleString(buffer,MaxTextExtent,
755    "static char *%s[] = {\n",basename);
756  (void) WriteBlobString(image,buffer);
757  (void) WriteBlobString(image,"/* columns rows colors chars-per-pixel */\n");
758  (void) FormatLocaleString(buffer,MaxTextExtent,
759    "\"%.20g %.20g %.20g %.20g\",\n",(double) picon->columns,(double)
760    picon->rows,(double) colors,(double) characters_per_pixel);
761  (void) WriteBlobString(image,buffer);
762  GetPixelInfo(image,&pixel);
763  for (i=0; i < (ssize_t) colors; i++)
764  {
765    /*
766      Define XPM color.
767    */
768    pixel=picon->colormap[i];
769    pixel.colorspace=sRGBColorspace;
770    pixel.depth=8;
771    pixel.alpha=(double) OpaqueAlpha;
772    (void) QueryColorname(image,&pixel,XPMCompliance,name,exception);
773    if (transparent != MagickFalse)
774      {
775        if (i == (ssize_t) (colors-1))
776          (void) CopyMagickString(name,"grey75",MaxTextExtent);
777      }
778    /*
779      Write XPM color.
780    */
781    k=i % MaxCixels;
782    symbol[0]=Cixel[k];
783    for (j=1; j < (ssize_t) characters_per_pixel; j++)
784    {
785      k=((i-k)/MaxCixels) % MaxCixels;
786      symbol[j]=Cixel[k];
787    }
788    symbol[j]='\0';
789    (void) FormatLocaleString(buffer,MaxTextExtent,"\"%s c %s\",\n",
790       symbol,name);
791    (void) WriteBlobString(image,buffer);
792  }
793  /*
794    Define XPM pixels.
795  */
796  (void) WriteBlobString(image,"/* pixels */\n");
797  for (y=0; y < (ssize_t) picon->rows; y++)
798  {
799    p=GetVirtualPixels(picon,0,y,picon->columns,1,exception);
800    if (p == (const Quantum *) NULL)
801      break;
802    (void) WriteBlobString(image,"\"");
803    for (x=0; x < (ssize_t) picon->columns; x++)
804    {
805      k=((ssize_t) GetPixelIndex(picon,p) % MaxCixels);
806      symbol[0]=Cixel[k];
807      for (j=1; j < (ssize_t) characters_per_pixel; j++)
808      {
809        k=(((int) GetPixelIndex(picon,p)-k)/MaxCixels) % MaxCixels;
810        symbol[j]=Cixel[k];
811      }
812      symbol[j]='\0';
813      (void) CopyMagickString(buffer,symbol,MaxTextExtent);
814      (void) WriteBlobString(image,buffer);
815      p+=GetPixelChannels(image);
816    }
817    (void) FormatLocaleString(buffer,MaxTextExtent,"\"%s\n",
818      y == (ssize_t) (picon->rows-1) ? "" : ",");
819    (void) WriteBlobString(image,buffer);
820    status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
821      picon->rows);
822    if (status == MagickFalse)
823      break;
824  }
825  picon=DestroyImage(picon);
826  (void) WriteBlobString(image,"};\n");
827  (void) CloseBlob(image);
828  return(MagickTrue);
829}
830
831/*
832%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
833%                                                                             %
834%                                                                             %
835%                                                                             %
836%   W r i t e X P M I m a g e                                                 %
837%                                                                             %
838%                                                                             %
839%                                                                             %
840%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
841%
842%  WriteXPMImage() writes an image to a file in the X pixmap format.
843%
844%  The format of the WriteXPMImage method is:
845%
846%      MagickBooleanType WriteXPMImage(const ImageInfo *image_info,
847%        Image *image,ExceptionInfo *exception)
848%
849%  A description of each parameter follows.
850%
851%    o image_info: the image info.
852%
853%    o image:  The image.
854%
855%    o exception: return any errors or warnings in this structure.
856%
857*/
858static MagickBooleanType WriteXPMImage(const ImageInfo *image_info,Image *image,
859  ExceptionInfo *exception)
860{
861#define MaxCixels  92
862
863  static const char
864    Cixel[MaxCixels+1] = " .XoO+@#$%&*=-;:>,<1234567890qwertyuipasdfghjk"
865                         "lzxcvbnmMNBVCZASDFGHJKLPIUYTREWQ!~^/()_`'][{}|";
866
867  char
868    buffer[MaxTextExtent],
869    basename[MaxTextExtent],
870    name[MaxTextExtent],
871    symbol[MaxTextExtent];
872
873  MagickBooleanType
874    status;
875
876  PixelInfo
877    pixel;
878
879  register const Quantum
880    *p;
881
882  register ssize_t
883    i,
884    x;
885
886  size_t
887    characters_per_pixel;
888
889  ssize_t
890    j,
891    k,
892    opacity,
893    y;
894
895  /*
896    Open output image file.
897  */
898  assert(image_info != (const ImageInfo *) NULL);
899  assert(image_info->signature == MagickSignature);
900  assert(image != (Image *) NULL);
901  assert(image->signature == MagickSignature);
902  if (image->debug != MagickFalse)
903    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
904  assert(exception != (ExceptionInfo *) NULL);
905  assert(exception->signature == MagickSignature);
906  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
907  if (status == MagickFalse)
908    return(status);
909  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
910    (void) TransformImageColorspace(image,sRGBColorspace,exception);
911  opacity=(-1);
912  if (image->alpha_trait == UndefinedPixelTrait)
913    {
914      if ((image->storage_class == DirectClass) || (image->colors > 256))
915        (void) SetImageType(image,PaletteType,exception);
916    }
917  else
918    {
919      double
920        alpha,
921        beta;
922
923      /*
924        Identify transparent colormap index.
925      */
926      if ((image->storage_class == DirectClass) || (image->colors > 256))
927        (void) SetImageType(image,PaletteBilevelMatteType,exception);
928      for (i=0; i < (ssize_t) image->colors; i++)
929        if (image->colormap[i].alpha != OpaqueAlpha)
930          {
931            if (opacity < 0)
932              {
933                opacity=i;
934                continue;
935              }
936            alpha=(double) TransparentAlpha-(double)
937              image->colormap[i].alpha;
938            beta=(double) TransparentAlpha-(double)
939              image->colormap[opacity].alpha;
940            if (alpha < beta)
941              opacity=i;
942          }
943      if (opacity == -1)
944        {
945          (void) SetImageType(image,PaletteBilevelMatteType,exception);
946          for (i=0; i < (ssize_t) image->colors; i++)
947            if (image->colormap[i].alpha != OpaqueAlpha)
948              {
949                if (opacity < 0)
950                  {
951                    opacity=i;
952                    continue;
953                  }
954                alpha=(Quantum) TransparentAlpha-(double)
955                  image->colormap[i].alpha;
956                beta=(Quantum) TransparentAlpha-(double)
957                  image->colormap[opacity].alpha;
958                if (alpha < beta)
959                  opacity=i;
960              }
961        }
962      if (opacity >= 0)
963        {
964          image->colormap[opacity].red=image->transparent_color.red;
965          image->colormap[opacity].green=image->transparent_color.green;
966          image->colormap[opacity].blue=image->transparent_color.blue;
967        }
968    }
969  /*
970    Compute the character per pixel.
971  */
972  characters_per_pixel=1;
973  for (k=MaxCixels; (ssize_t) image->colors > k; k*=MaxCixels)
974    characters_per_pixel++;
975  /*
976    XPM header.
977  */
978  (void) WriteBlobString(image,"/* XPM */\n");
979  GetPathComponent(image->filename,BasePath,basename);
980  if (isalnum((int) ((unsigned char) *basename)) == 0)
981    {
982      (void) FormatLocaleString(buffer,MaxTextExtent,"xpm_%s",basename);
983      (void) CopyMagickString(basename,buffer,MaxTextExtent);
984    }
985  if (isalpha((int) ((unsigned char) basename[0])) == 0)
986    basename[0]='_';
987  for (i=1; basename[i] != '\0'; i++)
988    if (isalnum((int) ((unsigned char) basename[i])) == 0)
989      basename[i]='_';
990  (void) FormatLocaleString(buffer,MaxTextExtent,
991    "static char *%s[] = {\n",basename);
992  (void) WriteBlobString(image,buffer);
993  (void) WriteBlobString(image,"/* columns rows colors chars-per-pixel */\n");
994  (void) FormatLocaleString(buffer,MaxTextExtent,
995    "\"%.20g %.20g %.20g %.20g \",\n",(double) image->columns,(double)
996    image->rows,(double) image->colors,(double) characters_per_pixel);
997  (void) WriteBlobString(image,buffer);
998  GetPixelInfo(image,&pixel);
999  for (i=0; i < (ssize_t) image->colors; i++)
1000  {
1001    /*
1002      Define XPM color.
1003    */
1004    pixel=image->colormap[i];
1005    pixel.colorspace=sRGBColorspace;
1006    pixel.depth=8;
1007    pixel.alpha=(double) OpaqueAlpha;
1008    (void) QueryColorname(image,&pixel,XPMCompliance,name,exception);
1009    if (i == opacity)
1010      (void) CopyMagickString(name,"None",MaxTextExtent);
1011    /*
1012      Write XPM color.
1013    */
1014    k=i % MaxCixels;
1015    symbol[0]=Cixel[k];
1016    for (j=1; j < (ssize_t) characters_per_pixel; j++)
1017    {
1018      k=((i-k)/MaxCixels) % MaxCixels;
1019      symbol[j]=Cixel[k];
1020    }
1021    symbol[j]='\0';
1022    (void) FormatLocaleString(buffer,MaxTextExtent,"\"%s c %s\",\n",symbol,
1023      name);
1024    (void) WriteBlobString(image,buffer);
1025  }
1026  /*
1027    Define XPM pixels.
1028  */
1029  (void) WriteBlobString(image,"/* pixels */\n");
1030  for (y=0; y < (ssize_t) image->rows; y++)
1031  {
1032    p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1033    if (p == (const Quantum *) NULL)
1034      break;
1035    (void) WriteBlobString(image,"\"");
1036    for (x=0; x < (ssize_t) image->columns; x++)
1037    {
1038      k=((ssize_t) GetPixelIndex(image,p) % MaxCixels);
1039      symbol[0]=Cixel[k];
1040      for (j=1; j < (ssize_t) characters_per_pixel; j++)
1041      {
1042        k=(((int) GetPixelIndex(image,p)-k)/MaxCixels) % MaxCixels;
1043        symbol[j]=Cixel[k];
1044      }
1045      symbol[j]='\0';
1046      (void) CopyMagickString(buffer,symbol,MaxTextExtent);
1047      (void) WriteBlobString(image,buffer);
1048      p+=GetPixelChannels(image);
1049    }
1050    (void) FormatLocaleString(buffer,MaxTextExtent,"\"%s\n",
1051      (y == (ssize_t) (image->rows-1) ? "" : ","));
1052    (void) WriteBlobString(image,buffer);
1053    if (image->previous == (Image *) NULL)
1054      {
1055        status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
1056          image->rows);
1057        if (status == MagickFalse)
1058          break;
1059      }
1060  }
1061  (void) WriteBlobString(image,"};\n");
1062  (void) CloseBlob(image);
1063  return(MagickTrue);
1064}
1065