1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 /* XPM plugin version 1.2.7 */
19 
20 /*
21 1.2.7 fixes saving unused transparency (bug #4560)
22 
23 1.2.6 fixes crash when saving indexed images (bug #109567)
24 
25 1.2.5 only creates a "None" color entry if the image has alpha (bug #108034)
26 
27 1.2.4 displays an error message if saving fails (bug #87588)
28 
29 1.2.3 fixes bug when running in noninteractive mode
30 changes alpha_threshold range from [0, 1] to [0,255] for consistency with
31 the threshold_alpha plugin
32 
33 1.2.2 fixes bug that generated bad digits on images with more than 20000
34 colors. (thanks, yanele)
35 parses gtkrc (thanks, yosh)
36 doesn't load parameter screen on images that don't have alpha
37 
38 1.2.1 fixes some minor bugs -- spaces in #XXXXXX strings, small typos in code.
39 
40 1.2 compute color indexes so that we don't have to use XpmSaveXImage*
41 
42 Previous...Inherited code from Ray Lehtiniemi, who inherited it from S & P.
43 */
44 
45 #include "config.h"
46 
47 #include <string.h>
48 
49 #include <glib/gstdio.h>
50 
51 #include <gdk/gdk.h>          /* For GDK_WINDOWING_WIN32 */
52 
53 #ifndef GDK_WINDOWING_X11
54 #ifndef XPM_NO_X
55 #define XPM_NO_X
56 #endif
57 #else
58 #include <X11/Xlib.h>
59 #endif
60 
61 #include <X11/xpm.h>
62 
63 #include <libgimp/gimp.h>
64 #include <libgimp/gimpui.h>
65 
66 #include "libgimp/stdplugins-intl.h"
67 
68 
69 static const gchar linenoise [] =
70 " .+@#$%&*=-;>,')!~{]^/(_:<[}|1234567890abcdefghijklmnopqrstuvwxyz\
71 ABCDEFGHIJKLMNOPQRSTUVWXYZ`";
72 
73 #define LOAD_PROC      "file-xpm-load"
74 #define SAVE_PROC      "file-xpm-save"
75 #define PLUG_IN_BINARY "file-xpm"
76 #define PLUG_IN_ROLE   "gimp-file-xpm"
77 #define SCALE_WIDTH    125
78 
79 /* Structs for the save dialog */
80 typedef struct
81 {
82   gint threshold;
83 } XpmSaveVals;
84 
85 typedef struct
86 {
87   guchar r;
88   guchar g;
89   guchar b;
90 } rgbkey;
91 
92 /*  whether the image is color or not.  global so I only have to pass
93  *  one user value to the GHFunc
94  */
95 static gboolean   color;
96 
97 /*  bytes per pixel.  global so I only have to pass one user value
98  *  to the GHFunc
99  */
100 static gint       cpp;
101 
102 /* Declare local functions */
103 static void       query               (void);
104 static void       run                 (const gchar      *name,
105                                        gint              nparams,
106                                        const GimpParam  *param,
107                                        gint             *nreturn_vals,
108                                        GimpParam       **return_vals);
109 
110 static gint32     load_image          (const gchar      *filename,
111                                        GError          **error);
112 static guchar   * parse_colors        (XpmImage         *xpm_image);
113 static void       parse_image         (gint32            image_ID,
114                                        XpmImage         *xpm_image,
115                                        guchar           *cmap);
116 static gboolean   save_image          (const gchar      *filename,
117                                        gint32            image_ID,
118                                        gint32            drawable_ID,
119                                        GError          **error);
120 static gboolean   save_dialog         (void);
121 
122 
123 const GimpPlugInInfo PLUG_IN_INFO =
124 {
125   NULL,  /* init_proc  */
126   NULL,  /* quit_proc  */
127   query, /* query_proc */
128   run,   /* run_proc   */
129 };
130 
131 static XpmSaveVals xpmvals =
132 {
133   127  /* alpha threshold */
134 };
135 
136 
MAIN()137 MAIN ()
138 
139 static void
140 query (void)
141 {
142   static const GimpParamDef load_args[] =
143   {
144     { GIMP_PDB_INT32,     "run-mode",     "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
145     { GIMP_PDB_STRING,    "filename",     "The name of the file to load" },
146     { GIMP_PDB_STRING,    "raw-filename", "The name entered"             }
147   };
148 
149   static const GimpParamDef load_return_vals[] =
150   {
151     { GIMP_PDB_IMAGE,    "image",         "Output image" }
152   };
153 
154   static const GimpParamDef save_args[] =
155   {
156     { GIMP_PDB_INT32,    "run-mode",      "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
157     { GIMP_PDB_IMAGE,    "image",         "Input image" },
158     { GIMP_PDB_DRAWABLE, "drawable",      "Drawable to export" },
159     { GIMP_PDB_STRING,   "filename",      "The name of the file to export the image in" },
160     { GIMP_PDB_STRING,   "raw-filename",  "The name of the file to export the image in" },
161     { GIMP_PDB_INT32,    "threshold",     "Alpha threshold (0-255)" }
162   };
163 
164   gimp_install_procedure (LOAD_PROC,
165                           "Load files in XPM (X11 Pixmap) format.",
166                           "Load files in XPM (X11 Pixmap) format. "
167                           "XPM is a portable image format designed to be "
168                           "included in C source code. XLib provides utility "
169                           "functions to read this format. Newer code should "
170                           "however be using gdk-pixbuf-csource instead. "
171                           "XPM supports colored images, unlike the XBM "
172                           "format which XPM was designed to replace.",
173                           "Spencer Kimball & Peter Mattis & Ray Lehtiniemi",
174                           "Spencer Kimball & Peter Mattis",
175                           "1997",
176                           N_("X PixMap image"),
177                           NULL,
178                           GIMP_PLUGIN,
179                           G_N_ELEMENTS (load_args),
180                           G_N_ELEMENTS (load_return_vals),
181                           load_args, load_return_vals);
182 
183   gimp_register_file_handler_mime (LOAD_PROC, "image/x-xpixmap");
184   gimp_register_magic_load_handler (LOAD_PROC,
185                                     "xpm",
186                                     "",
187                                     "0, string,/*\\040XPM\\040*/");
188 
189   gimp_install_procedure (SAVE_PROC,
190                           "Export files in XPM (X11 Pixmap) format.",
191                           "Export files in XPM (X11 Pixmap) format. "
192                           "XPM is a portable image format designed to be "
193                           "included in C source code. XLib provides utility "
194                           "functions to read this format. Newer code should "
195                           "however be using gdk-pixbuf-csource instead. "
196                           "XPM supports colored images, unlike the XBM "
197                           "format which XPM was designed to replace.",
198                           "Spencer Kimball & Peter Mattis & Ray Lehtiniemi & Nathan Summers",
199                           "Spencer Kimball & Peter Mattis",
200                           "1997",
201                           N_("X PixMap image"),
202                           "RGB*, GRAY*, INDEXED*",
203                           GIMP_PLUGIN,
204                           G_N_ELEMENTS (save_args), 0,
205                           save_args, NULL);
206 
207   gimp_register_file_handler_mime (SAVE_PROC, "image/x-xpixmap");
208   gimp_register_save_handler (SAVE_PROC, "xpm", "");
209 }
210 
211 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)212 run (const gchar      *name,
213      gint              nparams,
214      const GimpParam  *param,
215      gint             *nreturn_vals,
216      GimpParam       **return_vals)
217 {
218   static GimpParam   values[2];
219   GimpRunMode        run_mode;
220   GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
221   gint32             image_ID;
222   gint32             drawable_ID;
223   GimpExportReturn   export = GIMP_EXPORT_CANCEL;
224   GError            *error  = NULL;
225 
226   INIT_I18N ();
227   gegl_init (NULL, NULL);
228 
229   run_mode = param[0].data.d_int32;
230 
231   *nreturn_vals = 1;
232   *return_vals  = values;
233 
234   values[0].type          = GIMP_PDB_STATUS;
235   values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
236 
237   if (strcmp (name, LOAD_PROC) == 0)
238     {
239       image_ID = load_image (param[1].data.d_string, &error);
240 
241       if (image_ID != -1)
242         {
243           *nreturn_vals = 2;
244           values[1].type         = GIMP_PDB_IMAGE;
245           values[1].data.d_image = image_ID;
246         }
247       else
248         {
249           status = GIMP_PDB_EXECUTION_ERROR;
250         }
251     }
252   else if (strcmp (name, SAVE_PROC) == 0)
253     {
254       gimp_ui_init (PLUG_IN_BINARY, FALSE);
255 
256       image_ID    = param[1].data.d_int32;
257       drawable_ID = param[2].data.d_int32;
258 
259       /*  eventually export the image */
260       switch (run_mode)
261         {
262         case GIMP_RUN_INTERACTIVE:
263         case GIMP_RUN_WITH_LAST_VALS:
264           export = gimp_export_image (&image_ID, &drawable_ID, "XPM",
265                                       GIMP_EXPORT_CAN_HANDLE_RGB     |
266                                       GIMP_EXPORT_CAN_HANDLE_GRAY    |
267                                       GIMP_EXPORT_CAN_HANDLE_INDEXED |
268                                       GIMP_EXPORT_CAN_HANDLE_ALPHA);
269 
270           if (export == GIMP_EXPORT_CANCEL)
271             {
272               values[0].data.d_status = GIMP_PDB_CANCEL;
273               return;
274             }
275           break;
276         default:
277           break;
278         }
279 
280       switch (run_mode)
281         {
282         case GIMP_RUN_INTERACTIVE:
283           /*  Possibly retrieve data  */
284           gimp_get_data ("file_xpm_save", &xpmvals);
285 
286           /*  First acquire information with a dialog  */
287           if (gimp_drawable_has_alpha (drawable_ID))
288             if (! save_dialog ())
289               status = GIMP_PDB_CANCEL;
290           break;
291 
292         case GIMP_RUN_NONINTERACTIVE:
293           /*  Make sure all the arguments are there!  */
294           if (nparams != 6)
295             {
296               status = GIMP_PDB_CALLING_ERROR;
297             }
298           else
299             {
300               xpmvals.threshold = param[5].data.d_int32;
301 
302               if (xpmvals.threshold < 0 ||
303                   xpmvals.threshold > 255)
304                 status = GIMP_PDB_CALLING_ERROR;
305             }
306           break;
307 
308         case GIMP_RUN_WITH_LAST_VALS:
309           /*  Possibly retrieve data  */
310           gimp_get_data ("file_xpm_save", &xpmvals);
311           break;
312 
313         default:
314           break;
315         }
316 
317       if (status == GIMP_PDB_SUCCESS)
318         {
319           if (save_image (param[3].data.d_string,
320                           image_ID, drawable_ID, &error))
321             {
322               gimp_set_data ("file_xpm_save", &xpmvals, sizeof (XpmSaveVals));
323             }
324           else
325             {
326               status = GIMP_PDB_EXECUTION_ERROR;
327             }
328         }
329 
330       if (export == GIMP_EXPORT_EXPORT)
331         gimp_image_delete (image_ID);
332     }
333   else
334     {
335       status = GIMP_PDB_CALLING_ERROR;
336     }
337 
338   if (status != GIMP_PDB_SUCCESS && error)
339     {
340       *nreturn_vals = 2;
341       values[1].type          = GIMP_PDB_STRING;
342       values[1].data.d_string = error->message;
343     }
344 
345   values[0].data.d_status = status;
346 }
347 
348 static gint32
load_image(const gchar * filename,GError ** error)349 load_image (const gchar  *filename,
350             GError      **error)
351 {
352   XpmImage  xpm_image;
353   guchar   *cmap;
354   gint32    image_ID;
355 
356   gimp_progress_init_printf (_("Opening '%s'"),
357                              gimp_filename_to_utf8 (filename));
358 
359   /* read the raw file */
360   switch (XpmReadFileToXpmImage ((char *) filename, &xpm_image, NULL))
361     {
362     case XpmSuccess:
363       break;
364 
365     case XpmOpenFailed:
366       g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
367                    _("Error opening file '%s'"),
368                    gimp_filename_to_utf8 (filename));
369       return -1;
370 
371     case XpmFileInvalid:
372       g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
373                    "%s", _("XPM file invalid"));
374       return -1;
375 
376     default:
377       return -1;
378     }
379 
380   /* parse out the colors into a cmap */
381   cmap = parse_colors (&xpm_image);
382 
383   /* create the new image */
384   image_ID = gimp_image_new (xpm_image.width,
385                              xpm_image.height,
386                              GIMP_RGB);
387 
388   /* name it */
389   gimp_image_set_filename (image_ID, filename);
390 
391   /* fill it */
392   parse_image (image_ID, &xpm_image, cmap);
393 
394   /* clean up and exit */
395   g_free (cmap);
396 
397   return image_ID;
398 }
399 
400 static guchar *
parse_colors(XpmImage * xpm_image)401 parse_colors (XpmImage  *xpm_image)
402 {
403 #ifndef XPM_NO_X
404   Display  *display;
405   Colormap  colormap;
406 #endif
407   gint      i, j;
408   guchar   *cmap;
409 
410 #ifndef XPM_NO_X
411   /* open the display and get the default color map */
412   display  = XOpenDisplay (NULL);
413   if (display == NULL)
414     g_printerr ("Could not open display\n");
415 
416   colormap = DefaultColormap (display, DefaultScreen (display));
417 #endif
418 
419   /* alloc a buffer to hold the parsed colors */
420   cmap = g_new0 (guchar, 4 * xpm_image->ncolors);
421 
422   /* parse each color in the file */
423   for (i = 0, j = 0; i < xpm_image->ncolors; i++)
424     {
425       gchar     *colorspec = "None";
426       XpmColor *xpm_color;
427 #ifndef XPM_NO_X
428       XColor    xcolor;
429 #else
430       GdkColor  xcolor;
431 #endif
432 
433       xpm_color = &(xpm_image->colorTable[i]);
434 
435       /* pick the best spec available */
436       if (xpm_color->c_color)
437         colorspec = xpm_color->c_color;
438       else if (xpm_color->g_color)
439         colorspec = xpm_color->g_color;
440       else if (xpm_color->g4_color)
441         colorspec = xpm_color->g4_color;
442       else if (xpm_color->m_color)
443         colorspec = xpm_color->m_color;
444 
445       /* parse if it's not transparent */
446       if (strcmp (colorspec, "None") != 0)
447         {
448 #ifndef XPM_NO_X
449           XParseColor (display, colormap, colorspec, &xcolor);
450 #else
451           gdk_color_parse (colorspec, &xcolor);
452 #endif
453           cmap[j++] = xcolor.red >> 8;
454           cmap[j++] = xcolor.green >> 8;
455           cmap[j++] = xcolor.blue >> 8;
456           cmap[j++] = ~0;
457         }
458       else
459         {
460           j += 4;
461         }
462     }
463 #ifndef XPM_NO_X
464   XCloseDisplay (display);
465 #endif
466   return cmap;
467 }
468 
469 static void
parse_image(gint32 image_ID,XpmImage * xpm_image,guchar * cmap)470 parse_image (gint32    image_ID,
471              XpmImage *xpm_image,
472              guchar   *cmap)
473 {
474   GeglBuffer *buffer;
475   gint        tile_height;
476   gint        scanlines;
477   gint        val;
478   guchar     *buf;
479   guchar     *dest;
480   guint      *src;
481   gint32      layer_ID;
482   gint        i;
483 
484   layer_ID = gimp_layer_new (image_ID,
485                              _("Color"),
486                              xpm_image->width,
487                              xpm_image->height,
488                              GIMP_RGBA_IMAGE,
489                              100,
490                              gimp_image_get_default_new_layer_mode (image_ID));
491 
492   gimp_image_insert_layer (image_ID, layer_ID, -1, 0);
493 
494   buffer = gimp_drawable_get_buffer (layer_ID);
495 
496   tile_height = gimp_tile_height ();
497 
498   buf  = g_new (guchar, tile_height * xpm_image->width * 4);
499 
500   src  = xpm_image->data;
501   for (i = 0; i < xpm_image->height; i += tile_height)
502     {
503       gint j;
504 
505       dest = buf;
506       scanlines = MIN (tile_height, xpm_image->height - i);
507       j = scanlines * xpm_image->width;
508       while (j--)
509         {
510           {
511             val = *(src++) * 4;
512             *(dest)   = cmap[val];
513             *(dest+1) = cmap[val+1];
514             *(dest+2) = cmap[val+2];
515             *(dest+3) = cmap[val+3];
516             dest += 4;
517           }
518 
519           if ((j % 100) == 0)
520             gimp_progress_update ((double) i / (double) xpm_image->height);
521         }
522 
523       gegl_buffer_set (buffer,
524                        GEGL_RECTANGLE (0, i, xpm_image->width, scanlines), 0,
525                        NULL, buf, GEGL_AUTO_ROWSTRIDE);
526     }
527 
528   g_free (buf);
529   g_object_unref (buffer);
530 
531   gimp_progress_update (1.0);
532 }
533 
534 static guint
rgbhash(rgbkey * c)535 rgbhash (rgbkey *c)
536 {
537   return ((guint)c->r) ^ ((guint)c->g) ^ ((guint)c->b);
538 }
539 
540 static guint
compare(rgbkey * c1,rgbkey * c2)541 compare (rgbkey *c1,
542          rgbkey *c2)
543 {
544   return (c1->r == c2->r) && (c1->g == c2->g) && (c1->b == c2->b);
545 }
546 
547 static void
set_XpmImage(XpmColor * array,guint index,gchar * colorstring)548 set_XpmImage (XpmColor *array,
549               guint     index,
550               gchar    *colorstring)
551 {
552   gchar *p;
553   gint   i, charnum, indtemp;
554 
555   indtemp=index;
556   array[index].string = p = g_new (gchar, cpp+1);
557 
558   /*convert the index number to base sizeof(linenoise)-1 */
559   for (i=0; i<cpp; ++i)
560     {
561       charnum = indtemp % (sizeof (linenoise) - 1);
562       indtemp = indtemp / (sizeof (linenoise) - 1);
563       *p++ = linenoise[charnum];
564     }
565   /* *p++=linenoise[indtemp]; */
566 
567   *p = '\0'; /* C and its stupid null-terminated strings... */
568 
569   array[index].symbolic = NULL;
570   array[index].m_color  = NULL;
571   array[index].g4_color = NULL;
572 
573   if (color)
574     {
575       array[index].g_color = NULL;
576       array[index].c_color = colorstring;
577     }
578   else
579     {
580       array[index].c_color = NULL;
581       array[index].g_color = colorstring;
582     }
583 }
584 
585 static void
create_colormap_from_hash(gpointer gkey,gpointer value,gpointer user_data)586 create_colormap_from_hash (gpointer gkey,
587                            gpointer value,
588                            gpointer user_data)
589 {
590   rgbkey *key    = gkey;
591   gchar  *string = g_new (gchar, 8);
592 
593   sprintf (string, "#%02X%02X%02X", (int)key->r, (int)key->g, (int)key->b);
594   set_XpmImage (user_data, *((int *) value), string);
595 }
596 
597 static void
decrement_hash_values(gpointer gkey,gpointer value,gpointer user_data)598 decrement_hash_values (gpointer gkey,
599                        gpointer value,
600                        gpointer user_data)
601 {
602   --(*((guint*) value));
603 }
604 
605 static gboolean
save_image(const gchar * filename,gint32 image_ID,gint32 drawable_ID,GError ** error)606 save_image (const gchar  *filename,
607             gint32        image_ID,
608             gint32        drawable_ID,
609             GError      **error)
610 {
611   GeglBuffer *buffer;
612   const Babl *format;
613   gint        width;
614   gint        height;
615   gint        ncolors = 1;
616   gint       *indexno;
617   gboolean    indexed;
618   gboolean    alpha;
619   gboolean    alpha_used = FALSE;
620   XpmColor   *colormap;
621   XpmImage   *image;
622   guint      *ibuff   = NULL;
623   guchar     *buf;
624   guchar     *data;
625   GHashTable *hash = NULL;
626   gint        i, j, k;
627   gint        threshold = xpmvals.threshold;
628   gboolean    success = FALSE;
629 
630   buffer = gimp_drawable_get_buffer (drawable_ID);
631 
632   width  = gegl_buffer_get_width  (buffer);
633   height = gegl_buffer_get_height (buffer);
634 
635   alpha   = gimp_drawable_has_alpha (drawable_ID);
636   color   = !gimp_drawable_is_gray (drawable_ID);
637   indexed = gimp_drawable_is_indexed (drawable_ID);
638 
639   switch (gimp_drawable_type (drawable_ID))
640     {
641     case GIMP_RGB_IMAGE:
642       format = babl_format ("R'G'B' u8");
643       break;
644 
645     case GIMP_RGBA_IMAGE:
646       format = babl_format ("R'G'B'A u8");
647       break;
648 
649     case GIMP_GRAY_IMAGE:
650       format = babl_format ("Y' u8");
651       break;
652 
653     case GIMP_GRAYA_IMAGE:
654       format = babl_format ("Y'A u8");
655       break;
656 
657     case GIMP_INDEXED_IMAGE:
658     case GIMP_INDEXEDA_IMAGE:
659       format = gegl_buffer_get_format (buffer);
660       break;
661 
662     default:
663       g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
664                    _("Unsupported drawable type"));
665       g_object_unref (buffer);
666       return FALSE;
667     }
668 
669   /* allocate buffer making the assumption that ibuff is 32 bit aligned... */
670   ibuff = g_new (guint, width * height);
671 
672   hash = g_hash_table_new ((GHashFunc) rgbhash, (GCompareFunc) compare);
673 
674   gimp_progress_init_printf (_("Exporting '%s'"),
675                              gimp_filename_to_utf8 (filename));
676 
677   ncolors = alpha ? 1 : 0;
678 
679   /* allocate a pixel region to work with */
680   buf = g_new (guchar,
681                gimp_tile_height () * width *
682                babl_format_get_bytes_per_pixel (format));
683 
684   /* process each row of tiles */
685   for (i = 0; i < height; i += gimp_tile_height ())
686     {
687       gint scanlines;
688 
689       /* read the next row of tiles */
690       scanlines = MIN (gimp_tile_height(), height - i);
691 
692       gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scanlines), 1.0,
693                        format, buf,
694                        GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
695 
696       data = buf;
697 
698       /* process each pixel row */
699       for (j = 0; j < scanlines; j++)
700         {
701           /* go to the start of this row in each image */
702           guint *idata = ibuff + (i+j) * width;
703 
704           /* do each pixel in the row */
705           for (k = 0; k < width; k++)
706             {
707               rgbkey *key = g_new (rgbkey, 1);
708               guchar  a;
709 
710               /* get pixel data */
711               key->r = *(data++);
712               key->g = color && !indexed ? *(data++) : key->r;
713               key->b = color && !indexed ? *(data++) : key->r;
714               a = alpha ? *(data++) : 255;
715 
716               if (a < threshold)
717                 {
718                   *(idata++) = 0;
719                   alpha_used = TRUE;
720                 }
721               else
722                 {
723                   if (indexed)
724                     {
725                       *(idata++) = (key->r) + (alpha ? 1 : 0);
726                     }
727                   else
728                     {
729                       indexno = g_hash_table_lookup (hash, key);
730                       if (!indexno)
731                         {
732                           indexno = g_new (gint, 1);
733                           *indexno = ncolors++;
734                           g_hash_table_insert (hash, key, indexno);
735                           key = g_new (rgbkey, 1);
736                         }
737                       *(idata++) = *indexno;
738                     }
739                 }
740             }
741 
742           /* kick the progress bar */
743           gimp_progress_update ((gdouble) (i+j) / (gdouble) height);
744         }
745     }
746 
747   g_free (buf);
748 
749   /* remove alpha if not actually used */
750   if (alpha && !alpha_used)
751     {
752       gint i;
753       --ncolors;
754       for (i = 0; i < width * height; ++i)
755         --ibuff[i];
756 
757       g_hash_table_foreach (hash, decrement_hash_values, NULL);
758     }
759 
760   if (indexed)
761     {
762       guchar *cmap = gimp_image_get_colormap (image_ID, &ncolors);
763       guchar *c;
764 
765       c = cmap;
766 
767       if (alpha_used)
768         ncolors++;
769 
770       colormap = g_new (XpmColor, ncolors);
771       cpp =
772         1 + (gdouble) log (ncolors) / (gdouble) log (sizeof (linenoise) - 1.0);
773 
774       if (alpha_used)
775         set_XpmImage (colormap, 0, "None");
776 
777       for (i = alpha_used ? 1 : 0; i < ncolors; i++)
778         {
779           gchar *string;
780           guchar r, g, b;
781 
782           r = *c++;
783           g = *c++;
784           b = *c++;
785 
786           string = g_new (gchar, 8);
787           sprintf (string, "#%02X%02X%02X", (int)r, (int)g, (int)b);
788           set_XpmImage (colormap, i, string);
789         }
790 
791       g_free (cmap);
792     }
793   else
794     {
795       colormap = g_new (XpmColor, ncolors);
796       cpp =
797         1 + (gdouble) log (ncolors) / (gdouble) log (sizeof (linenoise) - 1.0);
798 
799       if (alpha_used)
800         set_XpmImage (colormap, 0, "None");
801 
802       g_hash_table_foreach (hash, create_colormap_from_hash, colormap);
803     }
804 
805   image = g_new (XpmImage, 1);
806 
807   image->width      = width;
808   image->height     = height;
809   image->ncolors    = ncolors;
810   image->cpp        = cpp;
811   image->colorTable = colormap;
812   image->data       = ibuff;
813 
814   /* do the save */
815   switch (XpmWriteFileFromXpmImage ((char *) filename, image, NULL))
816     {
817     case XpmSuccess:
818       success = TRUE;
819       break;
820 
821     case XpmOpenFailed:
822       g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
823                    _("Error opening file '%s'"),
824                    gimp_filename_to_utf8 (filename));
825       break;
826 
827     case XpmFileInvalid:
828       g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
829                    "%s", _("XPM file invalid"));
830       break;
831 
832     default:
833       break;
834     }
835 
836   g_object_unref (buffer);
837   g_free (ibuff);
838 
839   if (hash)
840     g_hash_table_destroy (hash);
841 
842   gimp_progress_update (1.0);
843 
844   return success;
845 }
846 
847 static gboolean
save_dialog(void)848 save_dialog (void)
849 {
850   GtkWidget *dialog;
851   GtkWidget *table;
852   GtkObject *scale_data;
853   gboolean   run;
854 
855   dialog = gimp_export_dialog_new (_("XPM"), PLUG_IN_BINARY, SAVE_PROC);
856 
857   table = gtk_table_new (1, 3, FALSE);
858   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
859   gtk_container_set_border_width (GTK_CONTAINER (table), 12);
860   gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
861                       table, TRUE, TRUE, 0);
862   gtk_widget_show (table);
863 
864   scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
865                                      _("_Alpha threshold:"), SCALE_WIDTH, 0,
866                                      xpmvals.threshold, 0, 255, 1, 8, 0,
867                                      TRUE, 0, 0,
868                                      NULL, NULL);
869 
870   g_signal_connect (scale_data, "value-changed",
871                     G_CALLBACK (gimp_int_adjustment_update),
872                     &xpmvals.threshold);
873 
874   gtk_widget_show (dialog);
875 
876   run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
877 
878   gtk_widget_destroy (dialog);
879 
880   return run;
881 }
882