xpm.c revision f1d8548abecaf5ca89d453fd9fc0cde77d20672b
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 ssize_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((ssize_t) (p-source));
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    {
366      xpm_buffer=DestroyString(xpm_buffer);
367      ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
368    }
369  /*
370    Read image colormap.
371  */
372  image->depth=1;
373  next=NextXPMLine(xpm_buffer);
374  for (j=0; (j < (ssize_t) image->colors) && (next != (char *) NULL); j++)
375  {
376    p=next;
377    next=NextXPMLine(p);
378    (void) CopyXPMColor(key,p,MagickMin((size_t) width,MaxTextExtent-1));
379    status=AddValueToSplayTree(xpm_colors,ConstantString(key),(void *) j);
380    /*
381      Parse color.
382    */
383    (void) CopyMagickString(target,"gray",MaxTextExtent);
384    q=ParseXPMColor(p+width,MagickTrue);
385    if (q != (char *) NULL)
386      {
387        while ((isspace((int) ((unsigned char) *q)) == 0) && (*q != '\0'))
388          q++;
389        if ((next-q) < 0)
390          break;
391        if (next != (char *) NULL)
392          (void) CopyXPMColor(target,q,MagickMin((size_t) (next-q),
393            MaxTextExtent-1));
394        else
395          (void) CopyMagickString(target,q,MaxTextExtent);
396        q=ParseXPMColor(target,MagickFalse);
397        if (q != (char *) NULL)
398          *q='\0';
399      }
400    StripString(target);
401    grey=strstr(target,"grey");
402    if (grey != (char *) NULL)
403      grey[2]='a';
404    if (LocaleCompare(target,"none") == 0)
405      {
406        image->storage_class=DirectClass;
407        image->alpha_trait=BlendPixelTrait;
408      }
409    status=QueryColorCompliance(target,XPMCompliance,&image->colormap[j],
410      exception);
411    if (status == MagickFalse)
412      break;
413    if (image->depth < image->colormap[j].depth)
414      image->depth=image->colormap[j].depth;
415  }
416  if (j < (ssize_t) image->colors)
417    {
418      xpm_colors=DestroySplayTree(xpm_colors);
419      xpm_buffer=DestroyString(xpm_buffer);
420      ThrowReaderException(CorruptImageError,"CorruptImage");
421    }
422  j=0;
423  if (image_info->ping == MagickFalse)
424    {
425      /*
426        Read image pixels.
427      */
428      status=SetImageExtent(image,image->columns,image->rows,exception);
429      if (status == MagickFalse)
430        return(DestroyImageList(image));
431      for (y=0; y < (ssize_t) image->rows; y++)
432      {
433        p=NextXPMLine(p);
434        if (p == (char *) NULL)
435          break;
436        r=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
437        if (r == (Quantum *) NULL)
438          break;
439        for (x=0; x < (ssize_t) image->columns; x++)
440        {
441          ssize_t count=CopyXPMColor(key,p,MagickMin(width,MaxTextExtent-1));
442          if (count != (ssize_t) width)
443            break;
444          j=(ssize_t) GetValueFromSplayTree(xpm_colors,key);
445          if (image->storage_class == PseudoClass)
446            SetPixelIndex(image,(Quantum) j,r);
447          SetPixelViaPixelInfo(image,image->colormap+j,r);
448          p+=count;
449          r+=GetPixelChannels(image);
450        }
451        if (x < (ssize_t) image->columns)
452          break;
453        if (SyncAuthenticPixels(image,exception) == MagickFalse)
454          break;
455      }
456      if (y < (ssize_t) image->rows)
457        {
458          xpm_colors=DestroySplayTree(xpm_colors);
459          xpm_buffer=DestroyString(xpm_buffer);
460          ThrowReaderException(CorruptImageError,"NotEnoughPixelData");
461        }
462    }
463  /*
464    Relinquish resources.
465  */
466  xpm_buffer=DestroyString(xpm_buffer);
467  xpm_colors=DestroySplayTree(xpm_colors);
468  (void) CloseBlob(image);
469  return(GetFirstImageInList(image));
470}
471
472/*
473%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
474%                                                                             %
475%                                                                             %
476%                                                                             %
477%   R e g i s t e r X P M I m a g e                                           %
478%                                                                             %
479%                                                                             %
480%                                                                             %
481%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
482%
483%  RegisterXPMImage() adds attributes for the XPM image format to
484%  the list of supported formats.  The attributes include the image format
485%  tag, a method to read and/or write the format, whether the format
486%  supports the saving of more than one frame to the same file or blob,
487%  whether the format supports native in-memory I/O, and a brief
488%  description of the format.
489%
490%  The format of the RegisterXPMImage method is:
491%
492%      size_t RegisterXPMImage(void)
493%
494*/
495ModuleExport size_t RegisterXPMImage(void)
496{
497  MagickInfo
498    *entry;
499
500  entry=SetMagickInfo("PICON");
501  entry->decoder=(DecodeImageHandler *) ReadXPMImage;
502  entry->encoder=(EncodeImageHandler *) WritePICONImage;
503  entry->flags^=CoderAdjoinFlag;
504  entry->description=ConstantString("Personal Icon");
505  entry->module=ConstantString("XPM");
506  (void) RegisterMagickInfo(entry);
507  entry=SetMagickInfo("PM");
508  entry->decoder=(DecodeImageHandler *) ReadXPMImage;
509  entry->encoder=(EncodeImageHandler *) WriteXPMImage;
510  entry->flags^=CoderAdjoinFlag;
511  entry->flags|=CoderStealthFlag;
512  entry->description=ConstantString("X Windows system pixmap (color)");
513  entry->module=ConstantString("XPM");
514  (void) RegisterMagickInfo(entry);
515  entry=SetMagickInfo("XPM");
516  entry->decoder=(DecodeImageHandler *) ReadXPMImage;
517  entry->encoder=(EncodeImageHandler *) WriteXPMImage;
518  entry->magick=(IsImageFormatHandler *) IsXPM;
519  entry->flags^=CoderAdjoinFlag;
520  entry->description=ConstantString("X Windows system pixmap (color)");
521  entry->module=ConstantString("XPM");
522  (void) RegisterMagickInfo(entry);
523  return(MagickImageCoderSignature);
524}
525
526/*
527%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
528%                                                                             %
529%                                                                             %
530%                                                                             %
531%   U n r e g i s t e r X P M I m a g e                                       %
532%                                                                             %
533%                                                                             %
534%                                                                             %
535%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
536%
537%  UnregisterXPMImage() removes format registrations made by the
538%  XPM module from the list of supported formats.
539%
540%  The format of the UnregisterXPMImage method is:
541%
542%      UnregisterXPMImage(void)
543%
544*/
545ModuleExport void UnregisterXPMImage(void)
546{
547  (void) UnregisterMagickInfo("PICON");
548  (void) UnregisterMagickInfo("PM");
549  (void) UnregisterMagickInfo("XPM");
550}
551
552/*
553%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
554%                                                                             %
555%                                                                             %
556%                                                                             %
557%   W r i t e P I C O N I m a g e                                             %
558%                                                                             %
559%                                                                             %
560%                                                                             %
561%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
562%
563%  WritePICONImage() writes an image to a file in the Personal Icon format.
564%
565%  The format of the WritePICONImage method is:
566%
567%      MagickBooleanType WritePICONImage(const ImageInfo *image_info,
568%        Image *image,ExceptionInfo *exception)
569%
570%  A description of each parameter follows.
571%
572%    o image_info: the image info.
573%
574%    o image:  The image.
575%
576%    o exception: return any errors or warnings in this structure.
577%
578*/
579static MagickBooleanType WritePICONImage(const ImageInfo *image_info,
580  Image *image,ExceptionInfo *exception)
581{
582#define ColormapExtent  155
583#define GraymapExtent  95
584#define PiconGeometry  "48x48>"
585
586  static unsigned char
587    Colormap[]=
588    {
589      0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x06, 0x00, 0x05, 0x00, 0xf4, 0x05,
590      0x00, 0x00, 0x00, 0x00, 0x2f, 0x4f, 0x4f, 0x70, 0x80, 0x90, 0x7e, 0x7e,
591      0x7e, 0xdc, 0xdc, 0xdc, 0xff, 0xff, 0xff, 0x00, 0x00, 0x80, 0x00, 0x00,
592      0xff, 0x1e, 0x90, 0xff, 0x87, 0xce, 0xeb, 0xe6, 0xe6, 0xfa, 0x00, 0xff,
593      0xff, 0x80, 0x00, 0x80, 0xb2, 0x22, 0x22, 0x2e, 0x8b, 0x57, 0x32, 0xcd,
594      0x32, 0x00, 0xff, 0x00, 0x98, 0xfb, 0x98, 0xff, 0x00, 0xff, 0xff, 0x00,
595      0x00, 0xff, 0x63, 0x47, 0xff, 0xa5, 0x00, 0xff, 0xd7, 0x00, 0xff, 0xff,
596      0x00, 0xee, 0x82, 0xee, 0xa0, 0x52, 0x2d, 0xcd, 0x85, 0x3f, 0xd2, 0xb4,
597      0x8c, 0xf5, 0xde, 0xb3, 0xff, 0xfa, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x00,
598      0x00, 0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00,
599      0x00, 0x00, 0x06, 0x00, 0x05, 0x00, 0x00, 0x05, 0x18, 0x20, 0x10, 0x08,
600      0x03, 0x51, 0x18, 0x07, 0x92, 0x28, 0x0b, 0xd3, 0x38, 0x0f, 0x14, 0x49,
601      0x13, 0x55, 0x59, 0x17, 0x96, 0x69, 0x1b, 0xd7, 0x85, 0x00, 0x3b,
602    },
603    Graymap[]=
604    {
605      0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x04, 0x00, 0x04, 0x00, 0xf3, 0x0f,
606      0x00, 0x00, 0x00, 0x00, 0x12, 0x12, 0x12, 0x21, 0x21, 0x21, 0x33, 0x33,
607      0x33, 0x45, 0x45, 0x45, 0x54, 0x54, 0x54, 0x66, 0x66, 0x66, 0x78, 0x78,
608      0x78, 0x87, 0x87, 0x87, 0x99, 0x99, 0x99, 0xab, 0xab, 0xab, 0xba, 0xba,
609      0xba, 0xcc, 0xcc, 0xcc, 0xde, 0xde, 0xde, 0xed, 0xed, 0xed, 0xff, 0xff,
610      0xff, 0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00,
611      0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x04, 0x0c, 0x10, 0x04, 0x31,
612      0x48, 0x31, 0x07, 0x25, 0xb5, 0x58, 0x73, 0x4f, 0x04, 0x00, 0x3b,
613    };
614
615#define MaxCixels  92
616
617  static const char
618    Cixel[MaxCixels+1] = " .XoO+@#$%&*=-;:>,<1234567890qwertyuipasdfghjk"
619                         "lzxcvbnmMNBVCZASDFGHJKLPIUYTREWQ!~^/()_`'][{}|";
620
621  char
622    buffer[MaxTextExtent],
623    basename[MaxTextExtent],
624    name[MaxTextExtent],
625    symbol[MaxTextExtent];
626
627  Image
628    *affinity_image,
629    *picon;
630
631  ImageInfo
632    *blob_info;
633
634  MagickBooleanType
635    status,
636    transparent;
637
638  PixelInfo
639    pixel;
640
641  QuantizeInfo
642    *quantize_info;
643
644  RectangleInfo
645    geometry;
646
647  register const Quantum
648    *p;
649
650  register ssize_t
651    i,
652    x;
653
654  register Quantum
655    *q;
656
657  size_t
658    characters_per_pixel,
659    colors;
660
661  ssize_t
662    j,
663    k,
664    y;
665
666  /*
667    Open output image file.
668  */
669  assert(image_info != (const ImageInfo *) NULL);
670  assert(image_info->signature == MagickSignature);
671  assert(image != (Image *) NULL);
672  assert(image->signature == MagickSignature);
673  if (image->debug != MagickFalse)
674    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
675  assert(exception != (ExceptionInfo *) NULL);
676  assert(exception->signature == MagickSignature);
677  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
678  if (status == MagickFalse)
679    return(status);
680  (void) TransformImageColorspace(image,sRGBColorspace,exception);
681  SetGeometry(image,&geometry);
682  (void) ParseMetaGeometry(PiconGeometry,&geometry.x,&geometry.y,
683    &geometry.width,&geometry.height);
684  picon=ResizeImage(image,geometry.width,geometry.height,TriangleFilter,
685    exception);
686  blob_info=CloneImageInfo(image_info);
687  (void) AcquireUniqueFilename(blob_info->filename);
688  if ((image_info->type != TrueColorType) &&
689      (SetImageGray(image,exception) != MagickFalse))
690    affinity_image=BlobToImage(blob_info,Graymap,GraymapExtent,exception);
691  else
692    affinity_image=BlobToImage(blob_info,Colormap,ColormapExtent,exception);
693  (void) RelinquishUniqueFileResource(blob_info->filename);
694  blob_info=DestroyImageInfo(blob_info);
695  if ((picon == (Image *) NULL) || (affinity_image == (Image *) NULL))
696    return(MagickFalse);
697  quantize_info=AcquireQuantizeInfo(image_info);
698  status=RemapImage(quantize_info,picon,affinity_image,exception);
699  quantize_info=DestroyQuantizeInfo(quantize_info);
700  affinity_image=DestroyImage(affinity_image);
701  transparent=MagickFalse;
702  if (picon->storage_class == PseudoClass)
703    {
704      (void) CompressImageColormap(picon,exception);
705      if (picon->alpha_trait != UndefinedPixelTrait)
706        transparent=MagickTrue;
707    }
708  else
709    {
710      /*
711        Convert DirectClass to PseudoClass picon.
712      */
713      if (picon->alpha_trait != UndefinedPixelTrait)
714        {
715          /*
716            Map all the transparent pixels.
717          */
718          for (y=0; y < (ssize_t) picon->rows; y++)
719          {
720            q=GetAuthenticPixels(picon,0,y,picon->columns,1,exception);
721            if (q == (Quantum *) NULL)
722              break;
723            for (x=0; x < (ssize_t) picon->columns; x++)
724            {
725              if (GetPixelAlpha(image,q) == (Quantum) TransparentAlpha)
726                transparent=MagickTrue;
727              else
728                SetPixelAlpha(picon,OpaqueAlpha,q);
729              q+=GetPixelChannels(picon);
730            }
731            if (SyncAuthenticPixels(picon,exception) == MagickFalse)
732              break;
733          }
734        }
735      (void) SetImageType(picon,PaletteType,exception);
736    }
737  colors=picon->colors;
738  if (transparent != MagickFalse)
739    {
740      colors++;
741      picon->colormap=(PixelInfo *) ResizeQuantumMemory((void **)
742        picon->colormap,(size_t) colors,sizeof(*picon->colormap));
743      if (picon->colormap == (PixelInfo *) NULL)
744        ThrowWriterException(ResourceLimitError,"MemoryAllocationError");
745      for (y=0; y < (ssize_t) picon->rows; y++)
746      {
747        q=GetAuthenticPixels(picon,0,y,picon->columns,1,exception);
748        if (q == (Quantum *) NULL)
749          break;
750        for (x=0; x < (ssize_t) picon->columns; x++)
751        {
752          if (GetPixelAlpha(image,q) == (Quantum) TransparentAlpha)
753            SetPixelIndex(picon,(Quantum) picon->colors,q);
754          q+=GetPixelChannels(picon);
755        }
756        if (SyncAuthenticPixels(picon,exception) == MagickFalse)
757          break;
758      }
759    }
760  /*
761    Compute the character per pixel.
762  */
763  characters_per_pixel=1;
764  for (k=MaxCixels; (ssize_t) colors > k; k*=MaxCixels)
765    characters_per_pixel++;
766  /*
767    XPM header.
768  */
769  (void) WriteBlobString(image,"/* XPM */\n");
770  GetPathComponent(picon->filename,BasePath,basename);
771  (void) FormatLocaleString(buffer,MaxTextExtent,
772    "static char *%s[] = {\n",basename);
773  (void) WriteBlobString(image,buffer);
774  (void) WriteBlobString(image,"/* columns rows colors chars-per-pixel */\n");
775  (void) FormatLocaleString(buffer,MaxTextExtent,
776    "\"%.20g %.20g %.20g %.20g\",\n",(double) picon->columns,(double)
777    picon->rows,(double) colors,(double) characters_per_pixel);
778  (void) WriteBlobString(image,buffer);
779  GetPixelInfo(image,&pixel);
780  for (i=0; i < (ssize_t) colors; i++)
781  {
782    /*
783      Define XPM color.
784    */
785    pixel=picon->colormap[i];
786    pixel.colorspace=sRGBColorspace;
787    pixel.depth=8;
788    pixel.alpha=(double) OpaqueAlpha;
789    (void) QueryColorname(image,&pixel,XPMCompliance,name,exception);
790    if (transparent != MagickFalse)
791      {
792        if (i == (ssize_t) (colors-1))
793          (void) CopyMagickString(name,"grey75",MaxTextExtent);
794      }
795    /*
796      Write XPM color.
797    */
798    k=i % MaxCixels;
799    symbol[0]=Cixel[k];
800    for (j=1; j < (ssize_t) characters_per_pixel; j++)
801    {
802      k=((i-k)/MaxCixels) % MaxCixels;
803      symbol[j]=Cixel[k];
804    }
805    symbol[j]='\0';
806    (void) FormatLocaleString(buffer,MaxTextExtent,"\"%s c %s\",\n",
807       symbol,name);
808    (void) WriteBlobString(image,buffer);
809  }
810  /*
811    Define XPM pixels.
812  */
813  (void) WriteBlobString(image,"/* pixels */\n");
814  for (y=0; y < (ssize_t) picon->rows; y++)
815  {
816    p=GetVirtualPixels(picon,0,y,picon->columns,1,exception);
817    if (p == (const Quantum *) NULL)
818      break;
819    (void) WriteBlobString(image,"\"");
820    for (x=0; x < (ssize_t) picon->columns; x++)
821    {
822      k=((ssize_t) GetPixelIndex(picon,p) % MaxCixels);
823      symbol[0]=Cixel[k];
824      for (j=1; j < (ssize_t) characters_per_pixel; j++)
825      {
826        k=(((int) GetPixelIndex(picon,p)-k)/MaxCixels) % MaxCixels;
827        symbol[j]=Cixel[k];
828      }
829      symbol[j]='\0';
830      (void) CopyMagickString(buffer,symbol,MaxTextExtent);
831      (void) WriteBlobString(image,buffer);
832      p+=GetPixelChannels(image);
833    }
834    (void) FormatLocaleString(buffer,MaxTextExtent,"\"%s\n",
835      y == (ssize_t) (picon->rows-1) ? "" : ",");
836    (void) WriteBlobString(image,buffer);
837    status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
838      picon->rows);
839    if (status == MagickFalse)
840      break;
841  }
842  picon=DestroyImage(picon);
843  (void) WriteBlobString(image,"};\n");
844  (void) CloseBlob(image);
845  return(MagickTrue);
846}
847
848/*
849%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
850%                                                                             %
851%                                                                             %
852%                                                                             %
853%   W r i t e X P M I m a g e                                                 %
854%                                                                             %
855%                                                                             %
856%                                                                             %
857%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
858%
859%  WriteXPMImage() writes an image to a file in the X pixmap format.
860%
861%  The format of the WriteXPMImage method is:
862%
863%      MagickBooleanType WriteXPMImage(const ImageInfo *image_info,
864%        Image *image,ExceptionInfo *exception)
865%
866%  A description of each parameter follows.
867%
868%    o image_info: the image info.
869%
870%    o image:  The image.
871%
872%    o exception: return any errors or warnings in this structure.
873%
874*/
875static MagickBooleanType WriteXPMImage(const ImageInfo *image_info,Image *image,
876  ExceptionInfo *exception)
877{
878#define MaxCixels  92
879
880  static const char
881    Cixel[MaxCixels+1] = " .XoO+@#$%&*=-;:>,<1234567890qwertyuipasdfghjk"
882                         "lzxcvbnmMNBVCZASDFGHJKLPIUYTREWQ!~^/()_`'][{}|";
883
884  char
885    buffer[MaxTextExtent],
886    basename[MaxTextExtent],
887    name[MaxTextExtent],
888    symbol[MaxTextExtent];
889
890  MagickBooleanType
891    status;
892
893  PixelInfo
894    pixel;
895
896  register const Quantum
897    *p;
898
899  register ssize_t
900    i,
901    x;
902
903  size_t
904    characters_per_pixel;
905
906  ssize_t
907    j,
908    k,
909    opacity,
910    y;
911
912  /*
913    Open output image file.
914  */
915  assert(image_info != (const ImageInfo *) NULL);
916  assert(image_info->signature == MagickSignature);
917  assert(image != (Image *) NULL);
918  assert(image->signature == MagickSignature);
919  if (image->debug != MagickFalse)
920    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
921  assert(exception != (ExceptionInfo *) NULL);
922  assert(exception->signature == MagickSignature);
923  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
924  if (status == MagickFalse)
925    return(status);
926  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
927    (void) TransformImageColorspace(image,sRGBColorspace,exception);
928  opacity=(-1);
929  if (image->alpha_trait == UndefinedPixelTrait)
930    {
931      if ((image->storage_class == DirectClass) || (image->colors > 256))
932        (void) SetImageType(image,PaletteType,exception);
933    }
934  else
935    {
936      double
937        alpha,
938        beta;
939
940      /*
941        Identify transparent colormap index.
942      */
943      if ((image->storage_class == DirectClass) || (image->colors > 256))
944        (void) SetImageType(image,PaletteBilevelAlphaType,exception);
945      for (i=0; i < (ssize_t) image->colors; i++)
946        if (image->colormap[i].alpha != OpaqueAlpha)
947          {
948            if (opacity < 0)
949              {
950                opacity=i;
951                continue;
952              }
953            alpha=(double) TransparentAlpha-(double)
954              image->colormap[i].alpha;
955            beta=(double) TransparentAlpha-(double)
956              image->colormap[opacity].alpha;
957            if (alpha < beta)
958              opacity=i;
959          }
960      if (opacity == -1)
961        {
962          (void) SetImageType(image,PaletteBilevelAlphaType,exception);
963          for (i=0; i < (ssize_t) image->colors; i++)
964            if (image->colormap[i].alpha != OpaqueAlpha)
965              {
966                if (opacity < 0)
967                  {
968                    opacity=i;
969                    continue;
970                  }
971                alpha=(Quantum) TransparentAlpha-(double)
972                  image->colormap[i].alpha;
973                beta=(Quantum) TransparentAlpha-(double)
974                  image->colormap[opacity].alpha;
975                if (alpha < beta)
976                  opacity=i;
977              }
978        }
979      if (opacity >= 0)
980        {
981          image->colormap[opacity].red=image->transparent_color.red;
982          image->colormap[opacity].green=image->transparent_color.green;
983          image->colormap[opacity].blue=image->transparent_color.blue;
984        }
985    }
986  /*
987    Compute the character per pixel.
988  */
989  characters_per_pixel=1;
990  for (k=MaxCixels; (ssize_t) image->colors > k; k*=MaxCixels)
991    characters_per_pixel++;
992  /*
993    XPM header.
994  */
995  (void) WriteBlobString(image,"/* XPM */\n");
996  GetPathComponent(image->filename,BasePath,basename);
997  if (isalnum((int) ((unsigned char) *basename)) == 0)
998    {
999      (void) FormatLocaleString(buffer,MaxTextExtent,"xpm_%s",basename);
1000      (void) CopyMagickString(basename,buffer,MaxTextExtent);
1001    }
1002  if (isalpha((int) ((unsigned char) basename[0])) == 0)
1003    basename[0]='_';
1004  for (i=1; basename[i] != '\0'; i++)
1005    if (isalnum((int) ((unsigned char) basename[i])) == 0)
1006      basename[i]='_';
1007  (void) FormatLocaleString(buffer,MaxTextExtent,
1008    "static char *%s[] = {\n",basename);
1009  (void) WriteBlobString(image,buffer);
1010  (void) WriteBlobString(image,"/* columns rows colors chars-per-pixel */\n");
1011  (void) FormatLocaleString(buffer,MaxTextExtent,
1012    "\"%.20g %.20g %.20g %.20g \",\n",(double) image->columns,(double)
1013    image->rows,(double) image->colors,(double) characters_per_pixel);
1014  (void) WriteBlobString(image,buffer);
1015  GetPixelInfo(image,&pixel);
1016  for (i=0; i < (ssize_t) image->colors; i++)
1017  {
1018    /*
1019      Define XPM color.
1020    */
1021    pixel=image->colormap[i];
1022    pixel.colorspace=sRGBColorspace;
1023    pixel.depth=8;
1024    pixel.alpha=(double) OpaqueAlpha;
1025    (void) QueryColorname(image,&pixel,XPMCompliance,name,exception);
1026    if (i == opacity)
1027      (void) CopyMagickString(name,"None",MaxTextExtent);
1028    /*
1029      Write XPM color.
1030    */
1031    k=i % MaxCixels;
1032    symbol[0]=Cixel[k];
1033    for (j=1; j < (ssize_t) characters_per_pixel; j++)
1034    {
1035      k=((i-k)/MaxCixels) % MaxCixels;
1036      symbol[j]=Cixel[k];
1037    }
1038    symbol[j]='\0';
1039    (void) FormatLocaleString(buffer,MaxTextExtent,"\"%s c %s\",\n",symbol,
1040      name);
1041    (void) WriteBlobString(image,buffer);
1042  }
1043  /*
1044    Define XPM pixels.
1045  */
1046  (void) WriteBlobString(image,"/* pixels */\n");
1047  for (y=0; y < (ssize_t) image->rows; y++)
1048  {
1049    p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1050    if (p == (const Quantum *) NULL)
1051      break;
1052    (void) WriteBlobString(image,"\"");
1053    for (x=0; x < (ssize_t) image->columns; x++)
1054    {
1055      k=((ssize_t) GetPixelIndex(image,p) % MaxCixels);
1056      symbol[0]=Cixel[k];
1057      for (j=1; j < (ssize_t) characters_per_pixel; j++)
1058      {
1059        k=(((int) GetPixelIndex(image,p)-k)/MaxCixels) % MaxCixels;
1060        symbol[j]=Cixel[k];
1061      }
1062      symbol[j]='\0';
1063      (void) CopyMagickString(buffer,symbol,MaxTextExtent);
1064      (void) WriteBlobString(image,buffer);
1065      p+=GetPixelChannels(image);
1066    }
1067    (void) FormatLocaleString(buffer,MaxTextExtent,"\"%s\n",
1068      (y == (ssize_t) (image->rows-1) ? "" : ","));
1069    (void) WriteBlobString(image,buffer);
1070    if (image->previous == (Image *) NULL)
1071      {
1072        status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
1073          image->rows);
1074        if (status == MagickFalse)
1075          break;
1076      }
1077  }
1078  (void) WriteBlobString(image,"};\n");
1079  (void) CloseBlob(image);
1080  return(MagickTrue);
1081}
1082