1 /*
2  *   X11 Mouse Cursor (XMC) plug-in for GIMP
3  *
4  *   Copyright 2008-2009 Takeshi Matsuyama <tksmashiw@gmail.com>
5  *
6  *   Special thanks: Alexia Death, Sven Neumann, Martin Nordholts
7  *                   and all community members.
8  */
9 
10 /*
11  *   This program is free software: you can redistribute it and/or modify
12  *   it under the terms of the GNU General Public License as published by
13  *   the Free Software Foundation, either version 3 of the License, or
14  *   (at your option) any later version.
15  *
16  *   This program is distributed in the hope that it will be useful,
17  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *   GNU General Public License for more details.
20  *
21  *   You should have received a copy of the GNU General Public License
22  *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
23  */
24 
25 /*
26  * Todo: if drawable->bpp != 4 in save_image for GIMP-2.8?
27  * Todo: support for "gimp-metadata" parasite.
28  *       "xmc-copyright" and "xmc-license" may be deprecated in future?
29  */
30 
31 /*
32  * This plug-in use these four parasites.
33  * "hot-spot"       common with file-xbm plug-in
34  * "xmc-copyright"  original, store contents of type1 comment chunk of Xcursor
35  * "xmc-license"    original, store contents of type2 comment chunk of Xcursor
36  * "gimp-comment"   common, store contents of type3 comment chunk of Xcursor
37  */
38 
39 /* *** Caution: Size vs Dimension ***
40  *
41  * In this file, "size" and "dimension" are used in definitely
42  * different contexts.  "Size" means nominal size of Xcursor which is
43  * used to determine which frame depends on which animation sequence
44  * and which sequence is really used. (for more detail, please read
45  * Xcursor(3).)  On the other hand, "Dimension" simply means width
46  * and/or height.
47  */
48 
49 #include "config.h"
50 
51 #include <stdlib.h>
52 #include <string.h>
53 #include <errno.h>
54 
55 #include <glib/gstdio.h>
56 #include <glib/gprintf.h>
57 
58 #include <libgimp/gimp.h>
59 #include <libgimp/gimpui.h>
60 
61 #include <X11/Xlib.h>
62 #include <X11/Xcursor/Xcursor.h>
63 
64 #include "libgimp/stdplugins-intl.h"
65 
66 /* For debug */
67 /* #define XMC_DEBUG */
68 #ifdef XMC_DEBUG
69 #  define DM_XMC(...) g_fprintf(stderr, __VA_ARGS__)
70 #else
71 #  define DM_XMC(...)
72 #endif
73 
74 /*
75  * Constants...
76  */
77 
78 #define LOAD_PROC              "file-xmc-load"
79 #define LOAD_THUMB_PROC        "file-xmc-load-thumb"
80 #define SAVE_PROC              "file-xmc-save"
81 
82 #define PLUG_IN_BINARY         "file-xmc"
83 #define PLUG_IN_ROLE           "gimp-file-xmc"
84 
85 /* We use "xmc" as the file extension of X cursor for convenience */
86 #define XCURSOR_EXTENSION      "xmc"
87 #define XCURSOR_MIME_TYPE      "image/x-xcursor"
88 
89 /* The maximum dimension of Xcursor which is fully supported in any
90  * environments. This is defined on line 59 of xcursorint.h in
91  * libXcursor source code. Make sure this is about dimensions (width
92  * and height) not about nominal size despite of it's name.
93  *
94  * As of 2018, this macro still exists in libXCursor codebase, but I am
95  * unsure how far this restriction is enforced since this is a very low
96  * max dimension for today's displays. Therefore our code will not
97  * enforce this value anymore, but only warn about possible
98  * incompatibilities when using higher values.
99  */
100 #define MAX_BITMAP_CURSOR_SIZE  64
101 
102 /* The maximum dimension of each frame of X cursor we want to save
103  * should be MAX_BITMAP_CURSOR_SIZE but about loading, xhot(& yhot) of
104  * each frame varies from 0 to MAX_BITMAP_CURSOR_SIZE-1, so we need to
105  * set the maximum dimension of image no less than
106  * MAX_BITMAP_CURSOR_SIZE * 2( -1 to be precise) to remain hotspots on
107  * the same coordinates.
108  *
109  * We use four times value (256 for saving, 512 for loading) as a
110  * limitation because some cursors generated by CursorXP/FX to X11
111  * Mouse Theme Converter is very large.
112  *
113  * The biggest cursor I found is "watch" of OuterLimits which size is
114  * 213x208.  If you found bigger one, please tell me ;-)
115  */
116 #define MAX_LOAD_DIMENSION      512
117 #define MAX_SAVE_DIMENSION      256
118 
119 /* The maximum number of different nominal sizes in one cursor this
120  * plug-in can treat. This is based on the number of cursor size which
121  * gnome-appearance-properties supports.(12,16,24,32,36,40,48,64)
122  * ref. capplets/common/gnome-theme-info.c in source of
123  * gnome-control-center
124  */
125 #define MAX_SIZE_NUM            8
126 
127 /* cursor delay is guint32 defined in Xcursor.h */
128 #define CURSOR_MAX_DELAY        100000000
129 #define CURSOR_DEFAULT_DELAY    50
130 #define CURSOR_MINIMUM_DELAY    5
131 
132 #define div_255(x)       (((x) + 0x80 + (((x) + 0x80) >> 8)) >> 8)
133 #define READ32(f, e)     read32 ((f), (e)); if (*(e)) return -1;
134 #define DISPLAY_DIGIT(x) ((x) > 100) ? 3 : ((x) > 10) ? 2 : 1
135 
136 /*
137  * Structures...
138  */
139 
140 typedef struct
141 {
142   gboolean    crop;
143   gint        size;
144   gboolean    size_replace;
145   gint32      delay;
146   gboolean    delay_replace;
147 } XmcSaveVals;
148 
149 /*
150  * Local functions...
151  */
152 
153 static void      query                     (void);
154 
155 static void      run                       (const gchar      *name,
156                                             gint              nparams,
157                                             const GimpParam  *param,
158                                             gint             *nreturn_vals,
159                                             GimpParam       **return_vals);
160 
161 static gint32    load_image                (const gchar      *filename,
162                                             GError          **error);
163 
164 static gint32    load_thumbnail            (const gchar      *filename,
165                                             gint32            thumb_size,
166                                             gint32           *width,
167                                             gint32           *height,
168                                             gint32           *num_layers,
169                                             GError          **error);
170 
171 static guint32   read32                    (FILE             *f,
172                                             GError          **error);
173 
174 static gboolean  save_image                (const gchar      *filename,
175                                             gint32            image_ID,
176                                             gint32            drawable_ID,
177                                             gint32            orig_image_ID,
178                                             GError          **error);
179 
180 static gboolean  save_dialog               (const gint32      image_ID,
181                                             GeglRectangle    *hotspotRange);
182 
183 static void      comment_entry_callback    (GtkWidget        *widget,
184                                             gchar           **commentp);
185 
186 static void      text_view_callback        (GtkTextBuffer    *buffer,
187                                             gchar           **commentp);
188 
189 static gboolean  load_default_hotspot      (const gint32      image_ID,
190                                             GeglRectangle    *hotspotRange);
191 
192 static inline guint32   separate_alpha     (guint32           pixel);
193 
194 static inline guint32   premultiply_alpha  (guint32           pixel);
195 
196 static XcursorComments *set_cursor_comments (void);
197 
198 static void      load_comments             (const gint32      image_ID);
199 
200 static gboolean  set_comment_to_pname      (const gint32      image_ID,
201                                             const gchar      *content,
202                                             const gchar      *pname);
203 
204 static gchar    *get_comment_from_pname    (const gint32      image_ID,
205                                             const gchar       *pname);
206 
207 static gboolean  set_hotspot_to_parasite   (gint32            image_ID);
208 
209 static gboolean  get_hotspot_from_parasite (gint32            image_ID);
210 
211 static void      set_size_and_delay        (const gchar       *framename,
212                                             guint32           *sizep,
213                                             guint32           *delayp,
214                                             GRegex            *re,
215                                             gboolean          *size_warnp);
216 
217 static gchar    *make_framename            (guint32            size,
218                                             guint32            delay,
219                                             guint              indent,
220                                             GError           **errorp);
221 
222 static void      get_cropped_region        (GeglRectangle     *retrun_rgn,
223                                             GeglBuffer        *buffer);
224 
225 static inline gboolean pix_is_opaque       (guint32            pix);
226 
227 static GeglRectangle * get_intersection_of_frames (gint32      image_ID);
228 
229 static gboolean   pix_in_region            (gint32             x,
230                                             gint32             y,
231                                             GeglRectangle     *xmcrp);
232 
233 static void find_hotspots_and_dimensions   (XcursorImages     *xcIs,
234                                             gint32            *xhot,
235                                             gint32            *yhot,
236                                             gint32            *width,
237                                             gint32            *height);
238 
239 /*
240  * Globals...
241  */
242 
243 const GimpPlugInInfo PLUG_IN_INFO =
244 {
245   NULL,
246   NULL,
247   query,
248   run
249 };
250 
251 static XmcSaveVals xmcvals =
252 {
253   /* saved in pdb after this plug-in's process has gone. */
254   FALSE,                /* crop */
255   32,                   /* size */
256   FALSE,                /* size_replace */
257   CURSOR_DEFAULT_DELAY, /* delay */
258   FALSE                 /* delay_replace */
259 };
260 
261 static struct
262 {
263   /* saved as parasites of original image after this plug-in's process has gone.*/
264   gint32 x;                    /* hotspot x */
265   gint32 y;                    /* hotspot y */
266   gchar *comments[3]; /* copyright, license, other */
267 } xmcparas = {0,};
268 
269 /* parasites correspond to XcursorComment type */
270 static const gchar *parasiteName[3] = { "xmc-copyright",
271                                         "xmc-license",
272                                         "gimp-comment" };
273 
274 /*
275  * 'main()' - Main entry - just call gimp_main()...
276  */
277 
MAIN()278 MAIN ()
279 
280 
281 /*
282  * 'query()' - Respond to a plug-in query...
283  */
284 
285 static void
286 query (void)
287 {
288   static const GimpParamDef load_args[] =
289     {
290       { GIMP_PDB_INT32,  "run-mode",     "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
291       { GIMP_PDB_STRING, "filename",     "The name of the file to load" },
292       { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" }
293     };
294   static const GimpParamDef load_return_vals[] =
295     {
296       { GIMP_PDB_IMAGE, "image", "Output image" }
297     };
298 
299   static const GimpParamDef thumb_args[] =
300     {
301       { GIMP_PDB_STRING, "filename",     "The name of the file to load" },
302       { GIMP_PDB_INT32,  "thumb-size",   "Preferred thumbnail size"     }
303     };
304   static const GimpParamDef thumb_return_vals[] =
305     {
306       { GIMP_PDB_IMAGE,   "image",            "Thumbnail image"        },
307       { GIMP_PDB_INT32,   "image-width",      "The width of image"     },
308       { GIMP_PDB_INT32,   "image-height",     "The height of image"    },
309       { GIMP_PDB_INT32,   "image-type",       "The color type of image"},
310       { GIMP_PDB_INT32,   "image-num-layers", "The number of layeres"  }
311     };
312 
313   static const GimpParamDef save_args[] =
314     {
315       { GIMP_PDB_INT32,    "run-mode",     "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
316       { GIMP_PDB_IMAGE,    "image",        "Input image"                  },
317       { GIMP_PDB_DRAWABLE, "drawable",     "Drawable to export"           },
318       { GIMP_PDB_STRING,   "filename",     "The name of the file to export the image in" },
319       { GIMP_PDB_STRING,   "raw-filename", "The name entered" },
320       /* following elements are XMC specific options */
321       { GIMP_PDB_INT32,    "x_hot",        "X-coordinate of hot spot"       },
322       { GIMP_PDB_INT32,    "y_hot",        "Y-coordinate of hot spot\n"
323                                            "Use (-1, -1) to keep original hot spot."},
324       { GIMP_PDB_INT32,    "crop",         "Auto-crop or not"              },
325       { GIMP_PDB_INT32,    "size",         "Default nominal size"          },
326       { GIMP_PDB_INT32,    "size_replace", "Replace existent size or not." },
327       { GIMP_PDB_INT32,    "delay",        "Default delay"                 },
328       { GIMP_PDB_INT32,    "delay_replace","Replace existent delay or not."},
329       { GIMP_PDB_STRING,   "copyright",    "Copyright information."        },
330       { GIMP_PDB_STRING,   "license",      "License information."          },
331       { GIMP_PDB_STRING,   "other",        "Other comment.(taken from "
332                                            "\"gimp-comment\" parasite)"    }
333     };
334 
335   gimp_install_procedure (LOAD_PROC,
336                           "Loads files of X11 Mouse Cursor file format",
337                           "This plug-in loads X11 Mouse Cursor (XMC) files.",
338                           "Takeshi Matsuyama <tksmashiw@gmail.com>",
339                           "Takeshi Matsuyama",
340                           "26 May 2009",
341                           N_("X11 Mouse Cursor"),
342                           NULL,
343                           GIMP_PLUGIN,
344                           G_N_ELEMENTS (load_args),
345                           G_N_ELEMENTS (load_return_vals),
346                           load_args,
347                           load_return_vals);
348 
349   gimp_register_file_handler_mime (LOAD_PROC, XCURSOR_MIME_TYPE);
350   gimp_register_magic_load_handler (LOAD_PROC,
351                                     XCURSOR_EXTENSION,
352                                     "",
353                                     "0,string,Xcur");
354 
355   gimp_install_procedure (LOAD_THUMB_PROC,
356                           "Loads only first frame of X11 Mouse Cursor's "
357                           "animation sequence which nominal size is the closest "
358                           "of thumb-size to be used as a thumbnail",
359                           "",
360                           "Takeshi Matsuyama <tksmashiw@gmail.com>",
361                           "Takeshi Matsuyama",
362                           "26 May 2009",
363                           NULL,
364                           NULL,
365                           GIMP_PLUGIN,
366                           G_N_ELEMENTS (thumb_args),
367                           G_N_ELEMENTS (thumb_return_vals),
368                           thumb_args,
369                           thumb_return_vals);
370 
371   gimp_register_thumbnail_loader (LOAD_PROC, LOAD_THUMB_PROC);
372 
373   gimp_install_procedure (SAVE_PROC,
374                           "Exports files of X11 cursor file",
375                           "This plug-in exports X11 Mouse Cursor (XMC) files",
376                           "Takeshi Matsuyama <tksmashiw@gmail.com>",
377                           "Takeshi Matsuyama",
378                           "26 May 2009",
379                           N_("X11 Mouse Cursor"),
380                           "RGBA",
381                           GIMP_PLUGIN,
382                           G_N_ELEMENTS (save_args), 0,
383                           save_args, NULL);
384 
385   gimp_register_file_handler_mime (SAVE_PROC, XCURSOR_MIME_TYPE);
386   gimp_register_save_handler (SAVE_PROC, XCURSOR_EXTENSION, "");
387 }
388 
389 /*
390  * 'run()' - Run the plug-in...
391  */
392 
393 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)394 run (const gchar      *name,
395      gint              nparams,
396      const GimpParam  *param,
397      gint             *nreturn_vals,
398      GimpParam       **return_vals)
399 {
400   static GimpParam  values[6];
401   GimpRunMode       run_mode;
402   GimpPDBStatusType status = GIMP_PDB_SUCCESS;
403   gint32            image_ID;
404   gint32            drawable_ID;
405   gint32            orig_image_ID;
406   GimpExportReturn  export = GIMP_EXPORT_CANCEL;
407   GeglRectangle    *hotspotRange = NULL;
408   gint32            width, height;
409   gint32            num_layers;
410   GError           *error = NULL;
411   gint              i;
412 
413   INIT_I18N ();
414   gegl_init (NULL, NULL);
415 
416   DM_XMC ("run: start.\n");
417   *nreturn_vals = 1;
418   *return_vals  = values;
419 
420   values[0].type          = GIMP_PDB_STATUS;
421   values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
422 
423   if (strcmp (name, LOAD_PROC) == 0)
424     {
425       DM_XMC ("Starting to load file.\tparam.data=%s\n",
426               param[1].data.d_string);
427       image_ID = load_image (param[1].data.d_string, &error);
428 
429       if (image_ID != -1)
430         {
431           *nreturn_vals = 2;
432           values[1].type = GIMP_PDB_IMAGE;
433           values[1].data.d_image = image_ID;
434           DM_XMC ("LOAD_PROC successfully load image. image_ID=%i\n", image_ID);
435         }
436       else
437         {
438           status = GIMP_PDB_EXECUTION_ERROR;
439         }
440     }
441   else if (strcmp (name, LOAD_THUMB_PROC) == 0)
442     {
443       DM_XMC ("Starting to load thumbnail.\tfilename=%s\tthumb-size=%d\n",
444               param[0].data.d_string, param[1].data.d_int32);
445       image_ID = load_thumbnail (param[0].data.d_string,
446                                  param[1].data.d_int32,
447                                  &width,
448                                  &height,
449                                  &num_layers,
450                                  &error);
451 
452       if (image_ID != -1)
453         {
454           *nreturn_vals = 6;
455           values[1].type = GIMP_PDB_IMAGE;
456           values[1].data.d_image = image_ID;
457           values[2].type = GIMP_PDB_INT32;
458           values[2].data.d_int32 = width;            /* width */
459           values[3].type = GIMP_PDB_INT32;
460           values[3].data.d_int32 = height;           /* height */
461           /* This will not work on GIMP 2.6, but not harmful. */
462           values[4].type = GIMP_PDB_INT32;
463           values[4].data.d_int32 = GIMP_RGBA_IMAGE;  /* type */
464           values[5].type = GIMP_PDB_INT32;
465           values[5].data.d_int32 = num_layers;       /* num_layers */
466 
467           DM_XMC ("LOAD_THUMB_PROC successfully load image. image_ID=%i\n", image_ID);
468         }
469       else
470         {
471           status = GIMP_PDB_EXECUTION_ERROR;
472         }
473     }
474   else if (strcmp (name, SAVE_PROC) == 0)
475     {
476       DM_XMC ("run: export %s\n", name);
477       run_mode    = param[0].data.d_int32;
478       image_ID    = orig_image_ID = param[1].data.d_int32;
479       drawable_ID = param[2].data.d_int32;
480       hotspotRange = get_intersection_of_frames (image_ID);
481 
482       if (! hotspotRange)
483         {
484           g_set_error (&error, 0, 0,
485                        _("Cannot set the hot spot!\n"
486                          "You must arrange layers so that all of them have an intersection."));
487           *nreturn_vals = 2;
488           values[1].type          = GIMP_PDB_STRING;
489           values[1].data.d_string = error->message;
490           values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
491 
492           return;
493         }
494 
495       /*  eventually export the image */
496       switch (run_mode)
497         {
498         case GIMP_RUN_INTERACTIVE:
499         case GIMP_RUN_WITH_LAST_VALS:
500           gimp_ui_init (PLUG_IN_BINARY, FALSE);
501 
502           export = gimp_export_image (&image_ID, &drawable_ID, "XMC",
503                                       GIMP_EXPORT_CAN_HANDLE_RGB    |
504                                       GIMP_EXPORT_CAN_HANDLE_ALPHA  |
505                                       GIMP_EXPORT_CAN_HANDLE_LAYERS |
506                                       GIMP_EXPORT_NEEDS_ALPHA);
507 
508           if (export == GIMP_EXPORT_CANCEL)
509             {
510               *nreturn_vals = 1;
511               values[0].data.d_status = GIMP_PDB_CANCEL;
512 
513               return;
514             }
515           break;
516 
517         default:
518           break;
519         }
520       switch (run_mode)
521         {
522         case GIMP_RUN_INTERACTIVE:
523           /*
524            * Possibly retrieve data...
525            */
526           gimp_get_data (SAVE_PROC, &xmcvals);
527           load_comments (image_ID);
528 
529           load_default_hotspot (image_ID, hotspotRange);
530 
531           if (! save_dialog (image_ID, hotspotRange))
532               status = GIMP_PDB_CANCEL;
533           break;
534 
535         case GIMP_RUN_NONINTERACTIVE:
536           /*
537            * Make sure all the arguments are there!
538            */
539           if (nparams != 15)
540             {
541               status = GIMP_PDB_CALLING_ERROR;
542             }
543           else
544             {
545               if (pix_in_region (param[5].data.d_int32, param[6].data.d_int32,
546                                  hotspotRange))
547                 { /* if passed hotspot is acceptable, use that ones. */
548                   xmcparas.x = param[5].data.d_int32;
549                   xmcparas.y = param[6].data.d_int32;
550                 }
551               else
552                 {
553                   load_default_hotspot (image_ID, hotspotRange);
554                   /* you can purposely choose non acceptable values for hotspot
555                      to use cursor's original values. */
556                 }
557               xmcvals.crop          = param[7].data.d_int32;
558               xmcvals.size          = param[8].data.d_int32;
559               xmcvals.size_replace  = param[9].data.d_int32;
560               /* load delay */
561               if (param[10].data.d_int32 < CURSOR_MINIMUM_DELAY)
562                 {
563                   xmcvals.delay = CURSOR_DEFAULT_DELAY;
564                 }
565               else
566                 {
567                   xmcvals.delay = param[10].data.d_int32;
568                 }
569               xmcvals.delay_replace = param[11].data.d_int32;
570               load_comments (image_ID);
571               for (i = 0; i < 3; ++i)
572                 {
573                   if (param[i + 12].data.d_string &&
574                       g_utf8_validate (param[i + 12].data.d_string, -1, NULL))
575                     {
576                       xmcparas.comments[i] = g_strdup (param[i + 12].data.d_string);
577                     }
578                 }
579             }
580           break;
581 
582         case GIMP_RUN_WITH_LAST_VALS:
583           /* Possibly retrieve data... */
584           gimp_get_data (SAVE_PROC, &xmcvals);
585           load_comments (image_ID);
586           load_default_hotspot (image_ID, hotspotRange);
587           break;
588 
589         default:
590           break;
591         }
592       if (status == GIMP_PDB_SUCCESS)
593         {
594           if (save_image (param[3].data.d_string, image_ID,
595                           drawable_ID, orig_image_ID, &error))
596             {
597               gimp_set_data (SAVE_PROC, &xmcvals, sizeof (XmcSaveVals));
598             }
599           else
600             {
601               status = GIMP_PDB_EXECUTION_ERROR;
602             }
603         }
604 
605       if (export == GIMP_EXPORT_EXPORT)
606         gimp_image_delete (image_ID);
607 
608       g_free (hotspotRange);
609 
610       for (i = 0; i < 3 ; ++i)
611         {
612           g_free (xmcparas.comments[i]);
613           xmcparas.comments[i] = NULL;
614         }
615     }
616   else
617     {
618       DM_XMC ("name=%s\n", name);
619       status = GIMP_PDB_CALLING_ERROR;
620     }
621 
622    if (status != GIMP_PDB_SUCCESS && error)
623      {
624        *nreturn_vals = 2;
625        values[1].type          = GIMP_PDB_STRING;
626        values[1].data.d_string = error->message;
627      }
628 
629   values[0].data.d_status = status;
630   DM_XMC ("run: finish\n");
631 }
632 
633 
634 /*
635  * 'load_image()' - Load a X cursor image into a new image window.
636  */
637 
638 static gint32
load_image(const gchar * filename,GError ** error)639 load_image (const gchar  *filename,
640             GError      **error)
641 {
642   FILE            *fp;
643   gint32           image_ID;
644   gint32           layer_ID;
645   GeglBuffer      *buffer;
646   XcursorComments *commentsp; /* pointer to comments */
647   XcursorImages   *imagesp;   /* pointer to images*/
648   guint32          delay;     /* use guint32 instead CARD32(in X11/Xmd.h) */
649   gchar           *framename; /* name of layer */
650   guint32         *tmppixel;  /* pixel data (guchar * bpp = guint32) */
651   gint             img_width;
652   gint             img_height;
653   gint             i, j;
654 
655   gimp_progress_init_printf (_("Opening '%s'"),
656                              gimp_filename_to_utf8 (filename));
657 
658   /* Open the file and check it is a valid X cursor */
659 
660   fp = g_fopen (filename, "rb");
661 
662   if (fp == NULL)
663     {
664       g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
665                    _("Could not open '%s' for reading: %s"),
666                    gimp_filename_to_utf8 (filename), g_strerror (errno));
667       return -1;
668     }
669 
670   if (! XcursorFileLoad (fp, &commentsp, &imagesp))
671     {
672       g_set_error (error, 0, 0, _("'%s' is not a valid X cursor."),
673                    gimp_filename_to_utf8 (filename));
674       fclose (fp);
675       return -1;
676     }
677 
678   /* check dimension is valid. */
679 
680   for (i = 0; i < imagesp->nimage; i++)
681     {
682       if (imagesp->images[i]->width > MAX_LOAD_DIMENSION)
683         {
684           g_set_error (error, 0, 0,
685                        _("Frame %d of '%s' is too wide for an X cursor."),
686                        i + 1, gimp_filename_to_utf8 (filename));
687           fclose (fp);
688           return -1;
689         }
690       if (imagesp->images[i]->height > MAX_LOAD_DIMENSION)
691         {
692           g_set_error (error, 0, 0,
693                        _("Frame %d of '%s' is too high for an X cursor."),
694                        i + 1, gimp_filename_to_utf8 (filename));
695           fclose (fp);
696           return -1;
697         }
698     }
699 
700   find_hotspots_and_dimensions (imagesp,
701                                 &xmcparas.x, &xmcparas.y,
702                                 &img_width, &img_height);
703 
704   DM_XMC ("xhot=%i,\tyhot=%i,\timg_width=%i,\timg_height=%i\n",
705           xmcparas.x, xmcparas.y, img_width, img_height);
706 
707   image_ID = gimp_image_new (img_width, img_height, GIMP_RGB);
708 
709   gimp_image_set_filename (image_ID, filename);
710 
711   if (! set_hotspot_to_parasite (image_ID))
712     {
713       fclose (fp);
714       return -1;
715     }
716 
717   /* Temporary buffer */
718   tmppixel = g_new (guint32, img_width * img_height);
719 
720   /* load each frame to each layer one by one */
721   for (i = 0; i < imagesp->nimage; i++)
722     {
723       gint width  = imagesp->images[i]->width;
724       gint height = imagesp->images[i]->height;
725 
726       delay = imagesp->images[i]->delay;
727 
728       if (delay < CURSOR_MINIMUM_DELAY)
729         {
730           delay = CURSOR_DEFAULT_DELAY;
731         }
732 
733       DM_XMC ("images[%i]->delay=%i\twidth=%d\theight=%d\n",
734               i ,delay, imagesp->images[i]->width, imagesp->images[i]->height);
735 
736       framename = make_framename (imagesp->images[i]->size, delay,
737                                   DISPLAY_DIGIT (imagesp->nimage), error);
738       if (! framename)
739         {
740           fclose (fp);
741           return -1;
742         }
743 
744       layer_ID = gimp_layer_new (image_ID, framename, width, height,
745                                  GIMP_RGBA_IMAGE,
746                                  100,
747                                  gimp_image_get_default_new_layer_mode (image_ID));
748       gimp_image_insert_layer (image_ID, layer_ID, -1, 0);
749 
750       /* Adjust layer position to let hotspot sit on the same point. */
751       gimp_item_transform_translate (layer_ID,
752                                      xmcparas.x - imagesp->images[i]->xhot,
753                                      xmcparas.y - imagesp->images[i]->yhot);
754 
755       g_free (framename);
756 
757       /* Get the buffer for our load... */
758 
759       buffer = gimp_drawable_get_buffer (layer_ID);
760 
761       /* set color to each pixel */
762       for (j = 0; j < width * height; j++)
763         {
764           tmppixel[j] = separate_alpha (imagesp->images[i]->pixels[j]);
765         }
766 
767       /* set pixel */
768       gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0,
769                        NULL, tmppixel, GEGL_AUTO_ROWSTRIDE);
770 
771       gimp_progress_update ((i + 1) / imagesp->nimage);
772 
773       g_object_unref (buffer);
774     }
775 
776   g_free (tmppixel);
777 
778   gimp_progress_update (1.0);
779 
780   /* Comment parsing */
781 
782   if (commentsp)
783     {
784       for (i = 0; i < commentsp->ncomment; ++i)
785         {
786           DM_XMC ("comment type=%d\tcomment=%s\n",
787                   commentsp->comments[i]->comment_type,
788                   commentsp->comments[i]->comment);
789           if (! set_comment_to_pname (image_ID,
790                                       commentsp->comments[i]->comment,
791                                       parasiteName[commentsp->comments[i]->comment_type -1]))
792             {
793               DM_XMC ("Failed to write %ith comment.\n", i);
794               fclose (fp);
795               return -1;
796             }
797         }
798     }
799 
800   DM_XMC ("Comment parsing done.\n");
801   XcursorImagesDestroy (imagesp);
802   XcursorCommentsDestroy (commentsp);
803   fclose (fp);
804 
805   gimp_progress_end ();
806 
807   return image_ID;
808 }
809 
810 /*
811  * load_thumbnail
812  */
813 
814 static gint32
load_thumbnail(const gchar * filename,gint32 thumb_size,gint32 * thumb_width,gint32 * thumb_height,gint32 * thumb_num_layers,GError ** error)815 load_thumbnail (const gchar *filename,
816                 gint32       thumb_size,
817                 gint32      *thumb_width,
818                 gint32      *thumb_height,
819                 gint32      *thumb_num_layers,
820                 GError     **error)
821 {
822   /* Return only one frame for thumbnail.
823    * We select first frame of an animation sequence which nominal size is the
824    * closest of thumb_size.
825    */
826 
827   XcursorImages *xcIs = NULL;  /* use to find the dimensions of thumbnail */
828   XcursorImage  *xcI;          /* temporary pointer to XcursorImage */
829   guint32       *positions;    /* array of the offsets of image chunks */
830   guint32        size;         /* nominal size */
831   guint32        diff;         /* difference between thumb_size and current size */
832   guint32        min_diff = XCURSOR_IMAGE_MAX_SIZE; /* minimum value of diff */
833   guint32        type;         /* chunk type */
834   FILE          *fp = NULL;
835   gint32         image_ID = -1;
836   gint32         layer_ID;
837   GeglBuffer    *buffer;
838   guint32       *tmppixel;     /* pixel data (guchar * bpp = guint32) */
839   guint32        ntoc = 0;     /* the number of table of contents */
840   gint           sel_num = -1; /* the index of selected image chunk */
841   gint           width;
842   gint           height;
843   gint           i;
844 
845   g_return_val_if_fail (thumb_width, -1);
846   g_return_val_if_fail (thumb_height, -1);
847   g_return_val_if_fail (thumb_num_layers, -1);
848 
849   *thumb_width      = 0;
850   *thumb_height     = 0;
851   *thumb_num_layers = 0;
852 
853   fp = g_fopen (filename, "rb");
854 
855   if (fp == NULL)
856     {
857       g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
858                    _("Could not open '%s' for reading: %s"),
859                    gimp_filename_to_utf8 (filename), g_strerror (errno));
860       return -1;
861     }
862 
863   /* From this line, we make a XcursorImages struct so that we can find out the
864    * width and height of entire image.
865    * We can use XcursorFileLoadImages (fp, thumb_size) from libXcursor instead
866    * of this ugly code but XcursorFileLoadImages loads all pixel data of the
867    * image chunks on memory thus we should not use it.
868    */
869 
870   /* find which image chunk is preferred to load. */
871 
872   /* skip magic, headersize, version */
873   fseek (fp, 12, SEEK_SET);
874   /* read the number of chunks */
875   ntoc = READ32 (fp, error)
876   if (ntoc > (G_MAXUINT32 / sizeof (guint32)))
877     {
878       g_set_error (error, 0, 0,
879                    "'%s' seems to have an incorrect toc size.",
880                    gimp_filename_to_utf8 (filename));
881       fclose (fp);
882       return -1;
883     }
884   positions = g_malloc (ntoc * sizeof (guint32));
885 
886   /* enter list of toc(table of contents) */
887   for (; ntoc > 0; --ntoc)
888     {
889       /* read entry type */
890       type = READ32 (fp, error)
891       if (type != XCURSOR_IMAGE_TYPE)
892         {
893           /* not a image */
894 
895           /* skip rest of this content */
896           fseek (fp, 8, SEEK_CUR);
897         }
898       else
899         {
900           /* this content is image */
901 
902           size = READ32 (fp, error)
903           positions[*thumb_num_layers] = READ32 (fp, error)
904           /* is this image is more preferred than selected before? */
905           diff = MAX (thumb_size, size) - MIN (thumb_size, size);
906           if (diff < min_diff)
907             {/* the image size is closer than current selected image */
908               min_diff = diff;
909               sel_num = *thumb_num_layers;
910             }
911           ++*thumb_num_layers;
912         }
913     }
914 
915   if (sel_num < 0)
916     {
917       g_set_error (error, 0, 0,
918                    _("there is no image chunk in \"%s\"."),
919                    gimp_filename_to_utf8 (filename));
920       fclose (fp);
921       return -1;
922     }
923 
924   /* get width and height of entire image */
925 
926   /* Let's make XcursorImages */
927   xcIs = XcursorImagesCreate (*thumb_num_layers);
928   xcIs->nimage = *thumb_num_layers;
929   for (i = 0; i < xcIs->nimage; ++i)
930     {
931       /* make XcursorImage with no pixel buffer */
932       xcI = XcursorImageCreate (0, 0);
933       /* go to the image chunk header */
934       fseek (fp, positions[i], SEEK_SET);
935       /* skip chunk header */
936       fseek (fp, 16, SEEK_CUR);
937       /* read properties of this image to determine entire image dimensions */
938       xcI->width = READ32 (fp, error)
939       xcI->height = READ32 (fp, error)
940       xcI->xhot = READ32 (fp, error)
941       xcI->yhot = READ32 (fp, error)
942 
943       xcIs->images[i] = xcI;
944     }
945 
946   DM_XMC ("selected size is %i or %i\n",
947           thumb_size - min_diff, thumb_size + min_diff);
948 
949   /* get entire image dimensions */
950   find_hotspots_and_dimensions (xcIs, NULL, NULL, thumb_width, thumb_height);
951 
952   DM_XMC ("width=%i\theight=%i\tnum-layers=%i\n",
953           *thumb_width, *thumb_height, xcIs->nimage);
954 
955   /* dimension check */
956   if (*thumb_width > MAX_LOAD_DIMENSION)
957     {
958       g_set_error (error, 0, 0,
959                    _("'%s' is too wide for an X cursor."),
960                    gimp_filename_to_utf8 (filename));
961       fclose (fp);
962       return -1;
963     }
964 
965   if (*thumb_height > MAX_LOAD_DIMENSION)
966     {
967       g_set_error (error, 0, 0,
968                    _("'%s' is too high for an X cursor."),
969                    gimp_filename_to_utf8 (filename));
970       fclose (fp);
971       return -1;
972     }
973 
974   /*  create new image! */
975 
976   width  = xcIs->images[sel_num]->width;
977   height = xcIs->images[sel_num]->height;
978 
979   image_ID = gimp_image_new (width, height, GIMP_RGB);
980 
981   layer_ID = gimp_layer_new (image_ID, NULL, width, height,
982                              GIMP_RGBA_IMAGE,
983                              100,
984                              gimp_image_get_default_new_layer_mode (image_ID));
985 
986   gimp_image_insert_layer (image_ID, layer_ID, -1, 0);
987 
988   /*
989    * Get the drawable and set the pixel region for our load...
990    */
991 
992   buffer = gimp_drawable_get_buffer (layer_ID);
993 
994   /* Temporary buffer */
995   tmppixel = g_new (guint32, width * height);
996 
997   /* copy the chunk data to tmppixel */
998   fseek (fp, positions[sel_num], SEEK_SET);
999   fseek (fp, 36, SEEK_CUR); /* skip chunk header(16bytes), xhot, yhot, width, height, delay */
1000 
1001   for (i = 0; i < width * height; i++)
1002     {
1003       tmppixel[i] = READ32 (fp, error)
1004       /* get back separate alpha */
1005       tmppixel[i] = separate_alpha (tmppixel[i]);
1006     }
1007 
1008   /* set pixel */
1009   gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0,
1010                    NULL, tmppixel, GEGL_AUTO_ROWSTRIDE);
1011 
1012   /* free tmppixel */
1013   g_free(tmppixel);
1014   g_free (positions);
1015   fclose (fp);
1016 
1017   g_object_unref (buffer);
1018 
1019   return image_ID;
1020 }
1021 
1022 /* read guint32 value from f despite of host's byte order. */
1023 static guint32
read32(FILE * f,GError ** error)1024 read32 (FILE    *f,
1025         GError **error)
1026 {
1027   guchar  p[4];
1028   guint32 ret;
1029 
1030   if (fread (p, 1, 4, f) != 4)
1031     {
1032       g_set_error (error, 0, 0, _("A read error occurred."));
1033       return 0;
1034     }
1035 
1036 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
1037   ret = p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24);
1038 #elif G_BYTE_ORDER == G_BIG_ENDIAN
1039   ret = p[3] + (p[2]<<8) + (p[1]<<16) + (p[0]<<24);
1040 #elif G_BYTE_ORDER == G_PDP_ENDIAN
1041   ret = p[2] + (p[3]<<8) + (p[0]<<16) + (p[1]<<24);
1042 #else
1043   g_return_val_if_rearched ();
1044 #endif
1045 
1046   return ret;
1047 }
1048 
1049 /* 'save_dialog ()'
1050  */
1051 static gboolean
save_dialog(const gint32 image_ID,GeglRectangle * hotspotRange)1052 save_dialog (const gint32   image_ID,
1053              GeglRectangle *hotspotRange)
1054 {
1055   GtkWidget      *dialog;
1056   GtkWidget      *frame;
1057   GtkWidget      *table;
1058   GtkWidget      *box;
1059   GtkAdjustment  *adjustment;
1060   GtkWidget      *alignment;
1061   GtkWidget      *tmpwidget;
1062   GtkWidget      *label;
1063   GtkTextBuffer  *textbuffer;
1064   GValue          val = G_VALUE_INIT;
1065   gint            x1, x2, y1, y2;
1066   gboolean        run;
1067 
1068   g_value_init (&val, G_TYPE_DOUBLE);
1069   dialog = gimp_export_dialog_new (_("X11 Mouse Cursor"),
1070                                    PLUG_IN_BINARY, SAVE_PROC);
1071 
1072   /*
1073    * parameter settings
1074    */
1075   frame = gimp_frame_new (_("XMC Options"));
1076   gtk_container_set_border_width (GTK_CONTAINER (frame), 12);
1077   gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
1078                       frame, TRUE, TRUE, 0);
1079   gtk_widget_show (frame);
1080 
1081   table = gtk_table_new (9, 3, FALSE);
1082   gtk_widget_show (table);
1083   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
1084   gtk_table_set_row_spacings (GTK_TABLE (table), 6);
1085   gtk_container_set_border_width (GTK_CONTAINER (table), 12);
1086   gtk_container_add (GTK_CONTAINER (frame), table);
1087 
1088   /*
1089    *  Hotspot
1090    */
1091   /* label "Hot spot  _X:" + spinbox */
1092   x1 = hotspotRange->x;
1093   x2 = hotspotRange->width + hotspotRange->x - 1;
1094 
1095   adjustment = (GtkAdjustment *)
1096     gtk_adjustment_new (xmcparas.x, x1, x2, 1, 5, 0);
1097   tmpwidget = gimp_spin_button_new (adjustment, 1.0, 0);
1098   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (tmpwidget), TRUE);
1099   g_value_set_double (&val, 1.0);
1100   g_object_set_property (G_OBJECT (tmpwidget), "xalign", &val);/* align right*/
1101   gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
1102                              _("Hot spot _X:"), 0, 0.5, tmpwidget, 1, TRUE);
1103   gtk_widget_show (tmpwidget);
1104 
1105   g_signal_connect (adjustment, "value-changed",
1106                     G_CALLBACK (gimp_int_adjustment_update),
1107                     &xmcparas.x);
1108 
1109   gimp_help_set_help_data (tmpwidget,
1110                            _("Enter the X coordinate of the hot spot. "
1111                              "The origin is top left corner."),
1112                            NULL);
1113 
1114   /* label "Y:" + spinbox */
1115   y1 = hotspotRange->y;
1116   y2 = hotspotRange->height + hotspotRange->y - 1;
1117 
1118   adjustment = (GtkAdjustment *)
1119     gtk_adjustment_new (xmcparas.y, y1, y2, 1, 5, 0);
1120   tmpwidget = gimp_spin_button_new (adjustment, 1.0, 0);
1121   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (tmpwidget), TRUE);
1122   g_value_set_double (&val, 1.0);
1123   g_object_set_property (G_OBJECT (tmpwidget), "xalign", &val);/* align right*/
1124   gimp_table_attach_aligned (GTK_TABLE (table), 1, 0,
1125                              "_Y:", 1.0, 0.5, tmpwidget, 1, TRUE);
1126   gtk_widget_show (tmpwidget);
1127 
1128   g_signal_connect (adjustment, "value-changed",
1129                     G_CALLBACK (gimp_int_adjustment_update),
1130                     &xmcparas.y);
1131 
1132   gimp_help_set_help_data (tmpwidget,
1133                            _("Enter the Y coordinate of the hot spot. "
1134                              "The origin is top left corner."),
1135                            NULL);
1136 
1137   /*
1138    *  Auto-crop
1139    */
1140   /* check button */
1141   tmpwidget =
1142   gtk_check_button_new_with_mnemonic (_("_Auto-Crop all frames."));
1143   gtk_table_attach (GTK_TABLE (table),
1144                     tmpwidget, 0, 3, 1, 2, GTK_FILL, 0, 0, 10);
1145   gtk_widget_show (tmpwidget);
1146 
1147   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tmpwidget),
1148                                 xmcvals.crop);
1149   gtk_widget_show (tmpwidget);
1150   g_signal_connect (tmpwidget, "toggled",
1151                     G_CALLBACK (gimp_toggle_button_update),
1152                     &xmcvals.crop);
1153   /* tooltip */
1154   gimp_help_set_help_data (tmpwidget,
1155                            _("Remove the empty borders of all frames.\n"
1156                              "This reduces the file size and may fix "
1157                              "the problem that some large cursors disorder "
1158                              "the screen.\n"
1159                              "Uncheck if you plan to edit the exported "
1160                              "cursor using other programs."),
1161                            NULL);
1162 
1163   /*
1164    *  size
1165    */
1166   tmpwidget =
1167     gimp_int_combo_box_new ("12px", 12, "16px", 16,
1168                             "24px", 24, "32px", 32,
1169                             "36px", 36, "40px", 40,
1170                             "48px", 48, "64px", 64, NULL);
1171   gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (tmpwidget),
1172                               32,
1173                               G_CALLBACK (gimp_int_combo_box_get_active),
1174                               &xmcvals.size);
1175   gtk_widget_show (tmpwidget);
1176   /* tooltip */
1177   gimp_help_set_help_data (tmpwidget,
1178                            _("Choose the nominal size of frames.\n"
1179                              "If you don't have plans to make multi-sized "
1180                              "cursor, or you have no idea, leave it \"32px\".\n"
1181                              "Nominal size has no relation with the actual "
1182                              "size (width or height).\n"
1183                              "It is only used to determine which frame depends "
1184                              "on which animation sequence, and which sequence "
1185                              "is used based on the value of "
1186                              "\"gtk-cursor-theme-size\"."),
1187                            NULL);
1188 
1189   gimp_table_attach_aligned (GTK_TABLE (table), 0, 2,
1190                              _("_Size:"), 0, 0.5, tmpwidget, 3, TRUE);
1191   /* Replace size ? */
1192   tmpwidget =
1193     gimp_int_radio_group_new (FALSE, NULL, G_CALLBACK (gimp_radio_button_update),
1194                              &xmcvals.size_replace, xmcvals.size_replace,
1195                              _("_Use this value only for a frame which size "
1196                                "is not specified."),
1197                              FALSE, NULL,
1198                              _("_Replace the size of all frames even if it "
1199                                "is specified."),
1200                              TRUE,  NULL,
1201                              NULL);
1202   alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
1203   gtk_widget_show (alignment);
1204   gtk_table_attach (GTK_TABLE (table), alignment, 0, 3, 3, 4, 0, 0, 0, 0);
1205   gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 6, 20, 0); /*padding left*/
1206   gtk_container_add (GTK_CONTAINER (alignment), tmpwidget);
1207   gtk_widget_show (tmpwidget);
1208 
1209   /*
1210    * delay
1211    */
1212   /* spin button */
1213   box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
1214   gimp_table_attach_aligned (GTK_TABLE (table), 0, 4, _("_Delay:"),
1215                              0, 0.5, box, 3, TRUE);
1216   gtk_widget_show (box);
1217 
1218   gimp_help_set_help_data (box,
1219                            _("Enter time span in milliseconds in which "
1220                              "each frame is rendered."),
1221                            NULL);
1222 
1223   adjustment = (GtkAdjustment *)
1224     gtk_adjustment_new (xmcvals.delay, CURSOR_MINIMUM_DELAY,
1225                         CURSOR_MAX_DELAY, 1, 5, 0);
1226   tmpwidget = gimp_spin_button_new (adjustment, 1.0, 0);
1227   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (tmpwidget), TRUE);
1228   g_value_set_double (&val, 1.0);
1229   g_object_set_property (G_OBJECT (tmpwidget), "xalign", &val);/* align right*/
1230   gtk_box_pack_start (GTK_BOX (box), tmpwidget, TRUE, TRUE, 0);
1231   gtk_widget_show (tmpwidget);
1232 
1233   g_signal_connect (adjustment, "value-changed",
1234                     G_CALLBACK (gimp_int_adjustment_update),
1235                     &xmcvals.delay);
1236 
1237   /* appended "ms" */
1238   tmpwidget = gtk_label_new ("ms");
1239   gtk_label_set_xalign (GTK_LABEL (tmpwidget), 0.0); /*align left*/
1240   gtk_box_pack_start (GTK_BOX (box), tmpwidget, TRUE, TRUE, 0);
1241   gtk_widget_show (tmpwidget);
1242 
1243   /* Replace delay? */
1244   tmpwidget =
1245     gimp_int_radio_group_new (FALSE, NULL, G_CALLBACK (gimp_radio_button_update),
1246                              &xmcvals.delay_replace, xmcvals.delay_replace,
1247                              _("_Use this value only for a frame which delay "
1248                                "is not specified."),
1249                              FALSE, NULL,
1250                              _("_Replace the delay of all frames even if it "
1251                                "is specified."),
1252                              TRUE,  NULL,
1253                              NULL);
1254   alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
1255   gtk_widget_show (alignment);
1256   gtk_table_attach (GTK_TABLE (table), alignment, 0, 3, 5, 6, 0, 0, 0, 0);
1257   gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 6, 20, 0); /*padding left*/
1258   gtk_container_add (GTK_CONTAINER (alignment), tmpwidget);
1259   gtk_widget_show (tmpwidget);
1260 
1261   /*
1262    *  Copyright
1263    */
1264   tmpwidget = gtk_entry_new ();
1265   /* Maximum length will be clamped to 65536 */
1266   gtk_entry_set_max_length (GTK_ENTRY (tmpwidget), XCURSOR_COMMENT_MAX_LEN);
1267 
1268   if (xmcparas.comments[0])
1269     {
1270       gtk_entry_set_text (GTK_ENTRY (tmpwidget),
1271                           gimp_any_to_utf8 (xmcparas.comments[0], - 1, NULL));
1272       /* show warning if comment is over 65535 characters
1273        * because gtk_entry can hold only that. */
1274       if (strlen (gtk_entry_get_text (GTK_ENTRY (tmpwidget))) >= 65535)
1275         g_message (_("The part of copyright information "
1276                      "that exceeded 65535 characters was removed."));
1277     }
1278 
1279   g_signal_connect (tmpwidget, "changed",
1280                      G_CALLBACK (comment_entry_callback),
1281                      xmcparas.comments);
1282   gtk_widget_show (tmpwidget);
1283   /* tooltip */
1284   gimp_help_set_help_data (tmpwidget,
1285                         _("Enter copyright information."),
1286                         NULL);
1287   gimp_table_attach_aligned (GTK_TABLE (table), 0, 6, _("_Copyright:"),
1288                              0, 0.5, tmpwidget, 3, FALSE);
1289   /*
1290    *  License
1291    */
1292   tmpwidget = gtk_entry_new ();
1293   /* Maximum length will be clamped to 65536 */
1294   gtk_entry_set_max_length (GTK_ENTRY (tmpwidget), XCURSOR_COMMENT_MAX_LEN);
1295 
1296   if (xmcparas.comments[1])
1297     {
1298       gtk_entry_set_text (GTK_ENTRY (tmpwidget),
1299                           gimp_any_to_utf8 (xmcparas.comments[1], - 1, NULL));
1300       /* show warning if comment is over 65535 characters
1301        * because gtk_entry can hold only that. */
1302       if (strlen (gtk_entry_get_text (GTK_ENTRY (tmpwidget))) >= 65535)
1303         g_message (_("The part of license information "
1304                      "that exceeded 65535 characters was removed."));
1305     }
1306 
1307   g_signal_connect (tmpwidget, "changed",
1308                      G_CALLBACK (comment_entry_callback),
1309                      xmcparas.comments + 1);
1310   gtk_widget_show (tmpwidget);
1311   /* tooltip */
1312   gimp_help_set_help_data (tmpwidget,
1313                         _("Enter license information."),
1314                         NULL);
1315   gimp_table_attach_aligned (GTK_TABLE (table), 0, 7, _("_License:"),
1316                              0, 0.5, tmpwidget, 3, FALSE);
1317   /*
1318    *  Other
1319    */
1320   /* We use gtk_text_view for "Other" while "Copyright" & "License" is entered
1321    * in gtk_entry because We want allow '\n' for "Other". */
1322   label = gtk_label_new_with_mnemonic (_("_Other:"));
1323   gtk_widget_show (label);
1324   gtk_label_set_xalign (GTK_LABEL (label), 0.0); /*align top-left*/
1325   gtk_label_set_yalign (GTK_LABEL (label), 0.0); /*align top-left*/
1326   gtk_table_attach (GTK_TABLE (table), label, 0, 1, 8, 9, GTK_FILL, 0, 0, 0);
1327   /* content of Other */
1328   /* scrolled window */
1329   box = gtk_scrolled_window_new (NULL, NULL);
1330   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box),
1331                                        GTK_SHADOW_IN);
1332   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box),
1333                                   GTK_POLICY_AUTOMATIC,
1334                                   GTK_POLICY_AUTOMATIC);
1335   gtk_table_attach (GTK_TABLE (table), box, 1, 3, 8, 9, GTK_FILL, 0, 0, 0);
1336   gtk_widget_show (box);
1337   /* textbuffer */
1338   textbuffer = gtk_text_buffer_new (NULL);
1339   if (xmcparas.comments[2])
1340     gtk_text_buffer_set_text (textbuffer,
1341                               gimp_any_to_utf8 (xmcparas.comments[2], -1, NULL),
1342                               -1);
1343   g_signal_connect (textbuffer, "changed",
1344                      G_CALLBACK (text_view_callback),
1345                      xmcparas.comments + 2);
1346   /* textview */
1347   tmpwidget =
1348     gtk_text_view_new_with_buffer (GTK_TEXT_BUFFER (textbuffer));
1349   gtk_text_view_set_accepts_tab (GTK_TEXT_VIEW (tmpwidget), FALSE);
1350   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tmpwidget), GTK_WRAP_WORD);
1351   g_object_unref (textbuffer);
1352   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tmpwidget), GTK_WRAP_WORD);
1353   gtk_container_add (GTK_CONTAINER (box), tmpwidget);
1354   gtk_widget_show (tmpwidget);
1355   /* tooltip */
1356   gimp_help_set_help_data (tmpwidget,
1357                         _("Enter other comment if you want."),
1358                         NULL);
1359   gtk_label_set_mnemonic_widget (GTK_LABEL (label), tmpwidget);
1360 
1361   /*
1362    *  all widget is prepared. Let's show dialog.
1363    */
1364   gtk_widget_show (dialog);
1365 
1366   run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
1367 
1368   gtk_widget_destroy (dialog);
1369 
1370   return run;
1371 }
1372 
1373 /*
1374  * callback function of gtk_entry for "copyright" and "license".
1375  * "other" is processed by text_view_callback
1376  */
1377 static void
comment_entry_callback(GtkWidget * widget,gchar ** commentp)1378 comment_entry_callback (GtkWidget  *widget,
1379                         gchar     **commentp)
1380 {
1381   const gchar *text;
1382 
1383   g_return_if_fail (commentp);
1384 
1385   text = gtk_entry_get_text (GTK_ENTRY (widget));
1386 
1387   /* This will not happen because sizeof(gtk_entry) < XCURSOR_COMMENT_MAX_LEN */
1388   g_return_if_fail (strlen (text) <= XCURSOR_COMMENT_MAX_LEN);
1389 
1390   g_free (*commentp);
1391   *commentp = g_strdup (text);
1392 }
1393 
1394 static void
text_view_callback(GtkTextBuffer * buffer,gchar ** commentp)1395 text_view_callback (GtkTextBuffer  *buffer,
1396                     gchar         **commentp)
1397 {
1398   GtkTextIter  start_iter;
1399   GtkTextIter  end_iter;
1400   gchar       *text;
1401 
1402   g_return_if_fail (commentp != NULL);
1403 
1404   gtk_text_buffer_get_bounds (buffer, &start_iter, &end_iter);
1405   text = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE);
1406 
1407   if (strlen (text) > XCURSOR_COMMENT_MAX_LEN)
1408     {
1409       g_message (_("Comment is limited to %d characters."),
1410                  XCURSOR_COMMENT_MAX_LEN);
1411 
1412       gtk_text_buffer_get_iter_at_offset (buffer, &start_iter,
1413                                           XCURSOR_COMMENT_MAX_LEN - 1);
1414       gtk_text_buffer_get_end_iter (buffer, &end_iter);
1415 
1416       gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
1417     }
1418   else
1419     {
1420       g_free (*commentp);
1421       *commentp = g_strdup (text);
1422     }
1423 }
1424 
1425 /**
1426  * Set default hotspot based on hotspotRange.
1427 **/
1428 static gboolean
load_default_hotspot(const gint32 image_ID,GeglRectangle * hotspotRange)1429 load_default_hotspot (const gint32   image_ID,
1430                       GeglRectangle *hotspotRange)
1431 {
1432 
1433   g_return_val_if_fail (hotspotRange, FALSE);
1434 
1435   if ( /* if we cannot load hotspot correctly */
1436       ! get_hotspot_from_parasite (image_ID) ||
1437      /* ,or hostspot is out of range */
1438       ! pix_in_region (xmcparas.x, xmcparas.y, hotspotRange))
1439     {
1440       /* then use top left point of hotspotRange as fallback. */
1441       xmcparas.x = hotspotRange->x;
1442       xmcparas.y = hotspotRange->y;
1443     }
1444 
1445   return TRUE;
1446 }
1447 
1448 /*
1449  * 'save_image ()' - Save the specified image to X cursor file.
1450  */
1451 
1452 static gboolean
save_image(const gchar * filename,gint32 image_ID,gint32 drawable_ID,gint32 orig_image_ID,GError ** error)1453 save_image (const gchar *filename,
1454             gint32       image_ID,
1455             gint32       drawable_ID,
1456             gint32       orig_image_ID,
1457             GError     **error)
1458 {
1459   FILE            *fp;                     /* File pointer */
1460   gboolean         dimension_warn = FALSE; /* become TRUE if even one
1461                                             * of the dimensions of the
1462                                             * frames of the cursor is
1463                                             * over
1464                                             * MAX_BITMAP_CURSOR_SIZE */
1465   gboolean         size_warn = FALSE;      /* become TRUE if even one
1466                                             * of the nominal size of
1467                                             * the frames is not
1468                                             * supported by
1469                                             * gnome-appearance-properties */
1470   GRegex          *re;                     /* used to get size and delay from
1471                                             * framename */
1472   XcursorComments *commentsp;              /* pointer to comments */
1473   XcursorImages   *imagesp;                /* pointer to images */
1474   gint32          *layers;                 /* Array of layer */
1475   gint32          *orig_layers;            /* Array of layer of orig_image */
1476   gint             nlayers;                /* Number of layers */
1477   gchar           *framename;              /* framename of a layer */
1478   GeglRectangle    save_rgn;               /* region to save */
1479   gint             layer_xoffset, layer_yoffset;
1480   /* temporary buffer which store pixel data (guchar * bpp = guint32) */
1481   guint32          pixelbuf[SQR (MAX_SAVE_DIMENSION)];
1482   gint             i, j;                   /* Looping vars */
1483 
1484   /* This will be used in set_size_and_delay function later.  To
1485    * define this in that function is easy to read but place here to
1486    * reduce overheads.
1487    */
1488   re = g_regex_new ("[(][ 0]*(\\d+)[ ]*(px|ms)[ ]*[)]",
1489                     G_REGEX_CASELESS | G_REGEX_OPTIMIZE,
1490                     0,
1491                     NULL);
1492 
1493   gimp_progress_init_printf (_("Saving '%s'"),
1494                              gimp_filename_to_utf8 (filename));
1495 
1496   /*
1497    * Open the file pointer.
1498    */
1499   DM_XMC ("Open the file pointer.\n");
1500   fp = g_fopen (filename, "wb");
1501   if (fp == NULL)
1502     {
1503       g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
1504                    _("Could not open '%s' for writing: %s"),
1505                    gimp_filename_to_utf8 (filename), g_strerror (errno));
1506       return FALSE;
1507     }
1508 
1509   /* get layers */
1510   orig_layers = gimp_image_get_layers (orig_image_ID, &nlayers);
1511   layers = gimp_image_get_layers (image_ID, &nlayers);
1512 
1513   /* create new XcursorImages. */
1514   imagesp = XcursorImagesCreate (nlayers);
1515   if (!imagesp)
1516     {
1517       DM_XMC ("Failed to XcursorImagesCreate!\n");
1518       fclose (fp);
1519       return FALSE;
1520     }
1521   imagesp->nimage = nlayers;
1522 
1523   /* XcursorImages also have `name' member but it is not used as long as I know.
1524      We leave it NULL here. */
1525 
1526   /*
1527    *  Now we start to convert each layer to a XcurosrImage one by one.
1528    */
1529   for (i = 0; i < nlayers; i++)
1530     {
1531       GeglBuffer *buffer;
1532       const Babl *format;
1533       gint        width;
1534       gint        height;
1535 
1536       buffer = gimp_drawable_get_buffer (layers[nlayers - 1 - i]);
1537 
1538       width  = gegl_buffer_get_width  (buffer);
1539       height = gegl_buffer_get_height (buffer);
1540 
1541       format = babl_format ("R'G'B'A u8");
1542 
1543       /* get framename of this layer */
1544       framename = gimp_item_get_name (layers[nlayers - 1 - i]);
1545       /* get offset of this layer. */
1546       gimp_drawable_offsets (layers[nlayers - 1 - i], &layer_xoffset, &layer_yoffset);
1547 
1548       /*
1549        * layer dimension check.
1550        */
1551       DM_XMC ("layer size check.\n");
1552       /* We allow to save a cursor which dimensions are no more than
1553        * MAX_SAVE_DIMENSION but after auto-cropping, we warn (only
1554        * warn, don't stop) if dimension is over
1555        * MAX_BITMAP_CURSOR_SIZE.
1556        */
1557       if (width > MAX_SAVE_DIMENSION)
1558         {
1559           g_set_error (error, 0, 0,
1560                        _("Frame '%s' is too wide. Please reduce to no more than %dpx."),
1561                        gimp_any_to_utf8 (framename, -1, NULL),
1562                        MAX_SAVE_DIMENSION);
1563           fclose (fp);
1564           return FALSE;
1565         }
1566 
1567       if (height > MAX_SAVE_DIMENSION)
1568         {
1569           g_set_error (error, 0, 0,
1570                        _("Frame '%s' is too high. Please reduce to no more than %dpx."),
1571                        gimp_any_to_utf8 (framename, -1, NULL),
1572                        MAX_SAVE_DIMENSION);
1573           fclose (fp);
1574           return FALSE;
1575         }
1576 
1577       if (height == 0 || width == 0)
1578         {
1579           g_set_error (error, 0, 0,
1580                        _("Width and/or height of frame '%s' is zero!"),
1581                        gimp_any_to_utf8 (framename, -1, NULL));
1582           fclose (fp);
1583           return FALSE;
1584         }
1585 
1586       if (xmcvals.crop) /* with auto-cropping */
1587         {
1588           /* get the region of auto-cropped area. */
1589           DM_XMC ("get_cropped_region\n");
1590           get_cropped_region (&save_rgn, buffer);
1591 
1592           /* don't forget save_rgn's origin is not a entire image
1593            * but a layer which we are doing on.
1594           */
1595 
1596           if (save_rgn.width == 0 || save_rgn.height == 0)
1597             {/* perfectly transparent frames become 1x1px transparent pixel. */
1598               DM_XMC ("get_cropped_region return 0.\n");
1599               imagesp->images[i] = XcursorImageCreate (1, 1);
1600               if (!imagesp->images[i])
1601                 {
1602                   DM_XMC ("Failed to XcursorImageCreate.\n");
1603                   fclose (fp);
1604                   return FALSE;
1605                 }
1606               imagesp->images[i]->pixels[0] = 0x0;
1607               imagesp->images[i]->xhot = 0;
1608               imagesp->images[i]->yhot = 0;
1609               set_size_and_delay (framename, &(imagesp->images[i]->size),
1610                                   &(imagesp->images[i]->delay), re,
1611                                   &size_warn);
1612               continue;
1613             }
1614           /* OK save_rgn is not 0x0 */
1615           /* is hotspot in save_rgn ? */
1616           if (! pix_in_region (xmcparas.x - layer_xoffset,
1617                                xmcparas.y - layer_yoffset,
1618                                &save_rgn))
1619             { /* if hotspot is not on save_rgn */
1620               g_set_error (error, 0, 0,
1621                            _("Cannot export the cursor because the hot spot "
1622                              "is not on frame '%s'.\n"
1623                              "Try to change the hot spot position, "
1624                              "layer geometry or export without auto-crop."),
1625                            gimp_any_to_utf8 (framename, -1, NULL));
1626               fclose (fp);
1627               return FALSE;
1628             }
1629         }
1630       else /* if without auto-cropping... */
1631         {
1632           /* set save_rgn for the case not to auto-crop */
1633           save_rgn.width  = width;
1634           save_rgn.height = height;
1635           save_rgn.x      = 0;
1636           save_rgn.y      = 0;
1637         }
1638 
1639       /* We warn if the dimension of the layer is over MAX_BITMAP_CURSOR_SIZE. */
1640       if (! dimension_warn)
1641         {
1642           if (save_rgn.width > MAX_BITMAP_CURSOR_SIZE ||
1643               save_rgn.height > MAX_BITMAP_CURSOR_SIZE)
1644             {
1645               dimension_warn = TRUE;
1646               /* actual warning is done after the cursor is successfully saved.*/
1647             }
1648          }
1649       /*
1650        * Create new XcursorImage.
1651        */
1652       DM_XMC ("create new xcursorimage.\twidth=%i\theight=%i\n",
1653               save_rgn.width, save_rgn.height);
1654       imagesp->images[i] = XcursorImageCreate (save_rgn.width, save_rgn.height);
1655       /* Cursor width & height is automatically set by function */
1656       /* XcursorImageCreate, so no need to set manually. */
1657       if (!imagesp->images[i])
1658         {
1659           DM_XMC ("Failed to XcursorImageCreate.\n");
1660           fclose (fp);
1661           return FALSE;
1662         }
1663       /*
1664        ** set images[i]'s xhot & yhot.
1665        */
1666       /* [Cropped layer's hotspot] =
1667                    [image's hotspot] - [layer's offset] - [save_rgn's offset]. */
1668       DM_XMC ("xhot=%i\tsave_rgn->xoffset=%i\tlayer_xoffset=%i\n",
1669               xmcparas.x, layer_xoffset, save_rgn.x);
1670       DM_XMC ("yhot=%i\tsave_rgn->yoffset=%i\tlayer_yoffset=%i\n",
1671               xmcparas.y, layer_yoffset, save_rgn.y);
1672       imagesp->images[i]->xhot = xmcparas.x - layer_xoffset - save_rgn.x;
1673       imagesp->images[i]->yhot = xmcparas.y - layer_yoffset - save_rgn.y;
1674       DM_XMC ("images[%i]->xhot=%i\tyhot=%i\n", i,
1675               imagesp->images[i]->xhot, imagesp->images[i]->yhot);
1676 
1677       /*
1678        * set images[i]->pixels
1679        */
1680       /* get image data to pixelbuf. */
1681       gegl_buffer_get (buffer,
1682                        GEGL_RECTANGLE (save_rgn.x, save_rgn.y,
1683                                        save_rgn.width, save_rgn.height), 1.0,
1684                        format, pixelbuf,
1685                        GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
1686 
1687       /*convert pixel date to XcursorPixel. */
1688       g_assert (save_rgn.width * save_rgn.height < SQR (MAX_SAVE_DIMENSION));
1689       for (j = 0; j < save_rgn.width * save_rgn.height; j++)
1690         {
1691           imagesp->images[i]->pixels[j] = premultiply_alpha (pixelbuf[j]);
1692         }
1693 
1694       /*
1695        * get back size & delay from framename.
1696        */
1697       set_size_and_delay (framename, &(imagesp->images[i]->size),
1698                           &(imagesp->images[i]->delay), re, &size_warn);
1699 
1700       /*
1701        * All property of this XcursorImage is loaded.
1702        */
1703 
1704       /* set the layer name of original image with the saved value */
1705       g_free (framename);
1706       framename = make_framename (imagesp->images[i]->size,
1707                                   imagesp->images[i]->delay,
1708                                   DISPLAY_DIGIT (imagesp->nimage),
1709                                   error);
1710       if (! framename)
1711         {
1712           fclose (fp);
1713           return FALSE;
1714         }
1715 
1716       gimp_item_set_name (orig_layers[nlayers - 1 - i], framename);
1717       g_free (framename);
1718 
1719       g_object_unref (buffer);
1720 
1721       gimp_progress_update ((i + 1) / imagesp->nimage);
1722     }
1723 
1724   gimp_progress_update (1.0);
1725 
1726   /*
1727    * comment parsing
1728    */
1729   commentsp = set_cursor_comments ();
1730 
1731 #ifdef XMC_DEBUG
1732   DM_XMC ("imagesp->nimage=%i\tname=%s\n", imagesp->nimage, imagesp->name);
1733   for (i = 0; i < imagesp->nimage; ++i)
1734     {
1735       DM_XMC ("\timages[%i]->size=%i\n\
1736                \twidth=%i\n\
1737                \theight=%i\n\
1738                \txhot=%i\n\
1739                \tyhot=%i\n\
1740                \tdelay=%i\n\
1741                \t*pixels=%p\n",
1742                i,
1743                imagesp->images[i]->size,
1744                imagesp->images[i]->width,
1745                imagesp->images[i]->height,
1746                imagesp->images[i]->xhot,
1747                imagesp->images[i]->yhot,
1748                imagesp->images[i]->delay,
1749                imagesp->images[i]->pixels);
1750     }
1751 
1752   if (commentsp)
1753     {
1754       for (i = 0; i < commentsp->ncomment; ++i)
1755         {
1756           DM_XMC ("comment type=%d\tcomment=%s\n",
1757                   commentsp->comments[i]->comment_type,
1758                   commentsp->comments[i]->comment);
1759         }
1760     }
1761 #endif
1762 
1763   /*
1764    *  save cursor to file *fp.
1765    */
1766 
1767   if (commentsp)
1768     {
1769       if (! XcursorFileSave (fp, commentsp, imagesp))
1770         {
1771           DM_XMC ("Failed to XcursorFileSave.\t%p\t%p\t%p\n",
1772                   fp, commentsp, imagesp);
1773           fclose (fp);
1774           return FALSE;
1775         }
1776 
1777     }
1778   else /* if no comments exist */
1779     {
1780       if (! XcursorFileSaveImages (fp, imagesp))
1781         {
1782           DM_XMC ("Failed to XcursorFileSaveImages.\t%p\t%p\n", fp, imagesp);
1783           fclose (fp);
1784           return FALSE;
1785         }
1786     }
1787 
1788   /* actual warning about dimensions */
1789   if (dimension_warn)
1790     {
1791       g_message (_("Your cursor was successfully exported but it contains one or "
1792                    "more frames whose width or height is more than %ipx, "
1793                    "a historical max dimension value for X bitmap cursors.\n"
1794                    "It might be unsupported by some environments."),
1795                    MAX_BITMAP_CURSOR_SIZE);
1796     }
1797   if (size_warn)
1798     {
1799       g_message (_("Your cursor was successfully exported but it contains one "
1800                    "or more frames whose nominal size is not supported by "
1801                    "GNOME settings.\n"
1802                    "You can satisfy it by checking \"Replace the size of all "
1803                    "frames...\" in the export dialog, or your cursor may not "
1804                    "appear in GNOME settings."));
1805     }
1806   /*
1807    * Done with the file...
1808    */
1809   g_regex_unref (re);
1810   DM_XMC ("fp=%p\n", fp);
1811   fclose (fp);
1812   DM_XMC ("%i frames written.\n", imagesp->nimage);
1813   XcursorImagesDestroy (imagesp);
1814   DM_XMC ("Xcursor destroyed.\n");
1815   XcursorCommentsDestroy (commentsp); /* this is safe even if commentsp is NULL. */
1816   gimp_progress_end ();
1817 
1818   /* Save the comment back to the original image */
1819   for (i = 0; i < 3; i++)
1820     {
1821       gimp_image_detach_parasite (orig_image_ID, parasiteName[i]);
1822 
1823       if (xmcparas.comments[i])
1824         {
1825           if (! set_comment_to_pname (orig_image_ID,
1826                                       xmcparas.comments[i], parasiteName[i]))
1827             {
1828               DM_XMC ("Failed to write back %ith comment to orig_image.\n", i);
1829             }
1830         }
1831     }
1832   /* Save hotspot back to the original image */
1833   set_hotspot_to_parasite (orig_image_ID);
1834 
1835   return TRUE;
1836 }
1837 
1838 static inline guint32
separate_alpha(guint32 pixel)1839 separate_alpha (guint32 pixel)
1840 {
1841   guint   alpha, red, green, blue;
1842   guint32 retval;
1843 
1844 #if G_BYTE_ORDER != G_LITTLE_ENDIAN
1845   pixel = GUINT32_TO_LE (pixel);
1846 #endif
1847 
1848   blue = pixel & 0xff;
1849   green = (pixel>>8) & 0xff;
1850   red = (pixel>>16) & 0xff;
1851   alpha = (pixel>>24) & 0xff;
1852 
1853   if (alpha == 0)
1854     return 0;
1855 
1856   /* resume separate alpha data. */
1857   red   = MIN (red   * 255 / alpha, 255);
1858   blue  = MIN (blue  * 255 / alpha, 255);
1859   green = MIN (green * 255 / alpha, 255);
1860 
1861   retval = red + (green<<8) + (blue<<16) + (alpha<<24);
1862 
1863 #if G_BYTE_ORDER != G_LITTLE_ENDIAN
1864   pixel = GUINT32_FROM_LE (pixel);
1865 #endif
1866 
1867   return retval;
1868 }
1869 
1870 static inline guint32
premultiply_alpha(guint32 pixel)1871 premultiply_alpha (guint32 pixel)
1872 {
1873   guint   alpha, red, green, blue;
1874   guint32 retval;
1875 
1876 #if G_BYTE_ORDER != G_LITTLE_ENDIAN
1877   pixel = GUINT32_TO_LE (pixel);
1878 #endif
1879 
1880   red   = pixel         & 0xff;
1881   green = (pixel >>  8) & 0xff;
1882   blue  = (pixel >> 16) & 0xff;
1883   alpha = (pixel >> 24) & 0xff;
1884 
1885   /* premultiply alpha
1886      (see "premultiply_data" function at line 154 of xcursorgen.c) */
1887   red   = div_255 (red   * alpha);
1888   green = div_255 (green * alpha);
1889   blue  = div_255 (blue  * alpha);
1890 
1891   retval = blue + (green << 8) + (red << 16) + (alpha << 24);
1892 
1893 #if G_BYTE_ORDER != G_LITTLE_ENDIAN
1894   pixel = GUINT32_FROM_LE (pixel);
1895 #endif
1896 
1897   return retval;
1898 }
1899 
1900 /* set comments to cursor from xmcparas.comments.
1901  * don't forget to XcursorCommentsDestroy returned pointer later.
1902  */
1903 static XcursorComments *
set_cursor_comments(void)1904 set_cursor_comments (void)
1905 {
1906   gint             i;
1907   guint            gcomlen, arraylen;
1908   GArray          *xcCommentsArray;
1909   XcursorComment  *(xcCommentp[3]) = {NULL,};
1910   XcursorComments *xcCommentsp;
1911 
1912   xcCommentsArray = g_array_new (FALSE, FALSE, sizeof (XcursorComment *));
1913 
1914   for (i = 0; i < 3; ++i)
1915     {
1916       if (xmcparas.comments[i])
1917         {
1918           gcomlen = strlen (xmcparas.comments[i]);
1919           if (gcomlen > 0)
1920             {
1921               xcCommentp[i] = XcursorCommentCreate (i + 1, gcomlen);
1922               /* first argument of XcursorCommentCreate is comment_type
1923                  defined in Xcursor.h as enumerator.
1924                  i + 1 is appropriate when we dispose parasiteName before MAIN(). */
1925               if (!xcCommentp[i])
1926                 {
1927                   g_warning ("Cannot create xcCommentp[%i]\n", i);
1928                   return NULL;
1929                 }
1930               else
1931                 {
1932                   g_stpcpy (xcCommentp[i]->comment, xmcparas.comments[i]);
1933                   g_array_append_val (xcCommentsArray, xcCommentp[i]);
1934                 }
1935             }
1936         }
1937     }
1938 
1939   arraylen = xcCommentsArray->len;
1940 
1941   if (arraylen == 0)
1942     return NULL;
1943 
1944   xcCommentsp = XcursorCommentsCreate (arraylen);
1945   xcCommentsp->ncomment = arraylen;
1946 
1947   for (i = 0; i < arraylen; ++i)
1948     {
1949       xcCommentsp->comments[i] =
1950         g_array_index (xcCommentsArray, XcursorComment* ,i);
1951     }
1952 
1953   return xcCommentsp;
1954 }
1955 
1956 /* Load xmcparas.comments from three parasites named as "xmc-copyright",
1957  * "xmc-license","gimp-comment".
1958  * This alignment sequence is depends on the definition of comment_type
1959  * in Xcursor.h .
1960  * Don't forget to g_free each element of xmcparas.comments later.
1961  */
1962 static void
load_comments(const gint32 image_ID)1963 load_comments (const gint32 image_ID)
1964 {
1965   gint i;
1966 
1967   g_return_if_fail (image_ID != -1);
1968 
1969   for (i = 0; i < 3; ++i)
1970     xmcparas.comments[i] = get_comment_from_pname (image_ID, parasiteName[i]);
1971 }
1972 
1973 /* Set content to a parasite named as pname. if parasite already
1974  * exists, append the new one to the old one with "\n"
1975  */
1976 static gboolean
set_comment_to_pname(const gint32 image_ID,const gchar * content,const gchar * pname)1977 set_comment_to_pname (const gint32  image_ID,
1978                       const gchar  *content,
1979                       const gchar  *pname)
1980 {
1981   gboolean      ret = FALSE;
1982   gchar        *tmpstring, *joind;
1983   GimpParasite *parasite;
1984 
1985   g_return_val_if_fail (image_ID != -1, FALSE);
1986   g_return_val_if_fail (content, FALSE);
1987 
1988   parasite = gimp_image_get_parasite (image_ID, pname);
1989   if (! parasite)
1990     {
1991       parasite = gimp_parasite_new (pname, GIMP_PARASITE_PERSISTENT,
1992                                     strlen (content) + 1, content);
1993     }
1994   else
1995     {
1996       tmpstring = g_strndup (gimp_parasite_data (parasite),
1997                              gimp_parasite_data_size (parasite));
1998       gimp_parasite_free (parasite);
1999       joind = g_strjoin ("\n", tmpstring, content, NULL);
2000       g_free (tmpstring);
2001       parasite = gimp_parasite_new (pname, GIMP_PARASITE_PERSISTENT,
2002                          strlen (joind) + 1, joind);
2003       g_free (joind);
2004     }
2005 
2006   if (parasite)
2007     {
2008       ret = gimp_image_attach_parasite (image_ID, parasite);
2009       gimp_parasite_free (parasite);
2010     }
2011 
2012   return ret;
2013 }
2014 
2015 /* get back comment from parasite name, don't forget to call
2016  * g_free(returned pointer) later
2017  */
2018 static gchar *
get_comment_from_pname(const gint32 image_ID,const gchar * pname)2019 get_comment_from_pname (const gint32  image_ID,
2020                         const gchar  *pname)
2021 {
2022   gchar        *string = NULL;
2023   GimpParasite *parasite;
2024   glong         length;
2025 
2026   g_return_val_if_fail (image_ID != -1, NULL);
2027 
2028   parasite = gimp_image_get_parasite (image_ID, pname);
2029   length = gimp_parasite_data_size (parasite);
2030 
2031   if (parasite)
2032     {
2033       if (length > XCURSOR_COMMENT_MAX_LEN)
2034         {
2035           length = XCURSOR_COMMENT_MAX_LEN;
2036           g_message (_("The parasite \"%s\" is too long for an X cursor "
2037                        "comment. It was cut off to fit."),
2038                      gimp_any_to_utf8 (pname, -1,NULL));
2039         }
2040 
2041       string = g_strndup (gimp_parasite_data (parasite), length);
2042       gimp_parasite_free (parasite);
2043     }
2044 
2045   return string;
2046 }
2047 
2048 /* Set hotspot to "hot-spot" parasite which format is common with that
2049  * of file-xbm.
2050  */
2051 static gboolean
set_hotspot_to_parasite(gint32 image_ID)2052 set_hotspot_to_parasite (gint32 image_ID)
2053 {
2054   gboolean      ret = FALSE;
2055   gchar        *tmpstr;
2056   GimpParasite *parasite;
2057 
2058   g_return_val_if_fail (image_ID != -1, FALSE);
2059 
2060   tmpstr = g_strdup_printf ("%d %d", xmcparas.x, xmcparas.y);
2061   parasite = gimp_parasite_new ("hot-spot",
2062                                 GIMP_PARASITE_PERSISTENT,
2063                                 strlen (tmpstr) + 1,
2064                                 tmpstr);
2065   g_free (tmpstr);
2066 
2067   if (parasite)
2068     {
2069       ret = gimp_image_attach_parasite (image_ID, parasite);
2070       gimp_parasite_free (parasite);
2071     }
2072 
2073   return ret;
2074 }
2075 
2076 /* Get back xhot & yhot from "hot-spot" parasite.
2077  * If succeed, hotspot coordinate is set to xmcparas.x, xmcparas.y and
2078  * return TRUE.
2079  * If "hot-spot" is not found or broken, return FALSE.
2080  */
2081 static gboolean
get_hotspot_from_parasite(gint32 image_ID)2082 get_hotspot_from_parasite (gint32 image_ID)
2083 {
2084   GimpParasite *parasite;
2085 
2086   g_return_val_if_fail (image_ID != -1, FALSE);
2087 
2088   DM_XMC ("function: getHotsopt\n");
2089 
2090   parasite = gimp_image_get_parasite (image_ID, "hot-spot");
2091   if (!parasite)  /* cannot find a parasite named "hot-spot". */
2092     {
2093       return FALSE;
2094     }
2095 
2096   if (sscanf (gimp_parasite_data (parasite),
2097               "%i %i", &xmcparas.x, &xmcparas.y) < 2)
2098     {  /*cannot load hotspot.(parasite is broken?) */
2099       return FALSE;
2100     }
2101 
2102   /*OK, hotspot is set to *xhotp & *yhotp. */
2103   return TRUE;
2104 }
2105 
2106 /* Set size to sizep, delay to delayp from drawable's framename.
2107  */
2108 static void
set_size_and_delay(const gchar * framename,guint32 * sizep,guint32 * delayp,GRegex * re,gboolean * size_warnp)2109 set_size_and_delay (const gchar *framename,
2110                     guint32     *sizep,
2111                     guint32     *delayp,
2112                     GRegex      *re,
2113                     gboolean    *size_warnp)
2114 {
2115   guint32     size   = 0;
2116   guint32     delay  = 0;
2117   gchar      *digits = NULL;
2118   gchar      *suffix = NULL;
2119   GMatchInfo *info   = NULL;
2120 
2121   g_return_if_fail (framename);
2122   g_return_if_fail (sizep);
2123   g_return_if_fail (delayp);
2124   g_return_if_fail (re);
2125 
2126   DM_XMC ("function: set_size_and_delay\tframename=%s\n", framename);
2127 
2128   /* re is defined at the start of save_image() as
2129         [(]                : open parenthesis
2130         [ ]*               : ignore zero or more spaces
2131         (\\d+)             : the number we want to get out
2132         [ ]*               : ignore zero or more spaces
2133         (px|ms)            : whether "px"(size) or "ms"(delay)
2134         [ ]*               : ignore zero or more spaces
2135         [)]                : close parenthesis
2136      This is intended to match for the animation-play plug-in. */
2137 
2138   g_regex_match (re, framename, 0, &info);
2139 
2140   while (g_match_info_matches (info))
2141     {
2142       digits = g_match_info_fetch (info, 1);
2143       suffix = g_match_info_fetch (info, 2);
2144 
2145       if (g_ascii_strcasecmp (suffix, "px") == 0)
2146         {
2147           if (!size) /* substitute it only for the first time */
2148             {
2149               if (strlen (digits) > 8) /* too large number should be clamped */
2150                 {
2151                   g_message (_("Your cursor was successfully exported but it contains one or "
2152                                "more frames whose size is over 8 digits.\n"
2153                                "We clamped it to %dpx. You should check the exported cursor."),
2154                              MAX_BITMAP_CURSOR_SIZE);
2155                   size = MAX_BITMAP_CURSOR_SIZE;
2156                 }
2157               else
2158                 {
2159                   size = atoi (digits);
2160                 }
2161             }
2162         }
2163       else /* suffix is "ms" */
2164         {
2165           if (!delay) /* substitute it only for the first time */
2166             {
2167               if (strlen (digits) > 8) /* too large number should be clamped */
2168                 delay = CURSOR_MAX_DELAY;
2169               else
2170                 delay = MIN (CURSOR_MAX_DELAY, atoi (digits));
2171             }
2172         }
2173 
2174       g_free (digits);
2175       g_free (suffix);
2176 
2177       g_match_info_next (info, NULL);
2178     }
2179 
2180   g_match_info_free (info);
2181 
2182   /* if size is not set, or size_replace is TRUE, set default size
2183    * (which was chosen in save dialog) */
2184   if (size == 0 || xmcvals.size_replace == TRUE)
2185     {
2186       size = xmcvals.size;
2187     }
2188   else if (! *size_warnp &&
2189            size != 12 && size != 16 && size != 24 && size != 32 &&
2190            size != 36 && size != 40 && size != 48 && size != 64 &&
2191            size != 96)
2192     { /* if the size is different from these values, we warn about it after
2193          successfully saving because gnome-appearance-properties only support
2194          them. */
2195       *size_warnp = TRUE;
2196     }
2197 
2198   *sizep = size;
2199 
2200   /* if delay is not set, or delay_replace is TRUE, set default delay
2201    * (which was chosen in save dialog) */
2202   if (delay == 0 ||  xmcvals.delay_replace == TRUE)
2203     {
2204       delay = xmcvals.delay;
2205     }
2206 
2207   *delayp = delay;
2208 
2209   DM_XMC ("set_size_and_delay return\tsize=%i\tdelay=%i\n", size, delay);
2210 }
2211 
2212 /* Return framename as format: "([x]px)_[i] ([t]ms) (replace)"
2213  * where [x] is nominal size, [t] is delay passed as argument respectively,
2214  * and [i] is an index separately counted by [x].
2215  * This format is compatible with "animation-play" plug-in.
2216  * Don't forget to g_free returned framename later.
2217  */
2218 static gchar *
make_framename(guint32 size,guint32 delay,guint indent,GError ** errorp)2219 make_framename (guint32   size,
2220                 guint32   delay,
2221                 guint     indent,
2222                 GError  **errorp)
2223 {
2224   static struct
2225   {
2226     guint32 size;
2227     guint count;
2228   } Counter[MAX_SIZE_NUM + 1] = {{0,}};
2229 
2230   int i;  /* loop index */
2231 
2232   /* don't pass 0 for size. */
2233   g_return_val_if_fail (size > 0, NULL);
2234 
2235   /* "count" member of Counter's element means how many time corresponding
2236      "size" is passed to this function. The size member of the last element
2237      of Counter must be 0, so Counter can have MAX_SIZE_NUM elements at most.
2238      This is not a smart way but rather simple than using dynamic method. */
2239 
2240   for (i = 0; Counter[i].size != size; ++i)
2241     {
2242       if (Counter[i].size == 0) /* the end of Counter elements */
2243         {
2244           if (i > MAX_SIZE_NUM)
2245             {
2246               g_set_error (errorp, 0, 0,
2247                            /* translators: the %i is *always* 8 here */
2248                            _("Sorry, this plug-in cannot handle a cursor "
2249                              "which contains over %i different nominal sizes."),
2250                            MAX_SIZE_NUM);
2251               return NULL;
2252             }
2253           else /* append new element which "size" is given value. */
2254             {
2255               Counter[i].size = size;
2256               break;
2257             }
2258         }
2259     }
2260 
2261   Counter[i].count += 1;
2262 
2263   return g_strdup_printf ("(%dpx)_%0*d (%dms) (replace)", size, indent,
2264                           Counter[i].count, delay);
2265 }
2266 
2267 /* Get the region which is maintained when auto-crop.
2268  */
2269 static void
get_cropped_region(GeglRectangle * return_rgn,GeglBuffer * buffer)2270 get_cropped_region (GeglRectangle *return_rgn,
2271                     GeglBuffer    *buffer)
2272 {
2273   gint        width  = gegl_buffer_get_width  (buffer);
2274   gint        height = gegl_buffer_get_height (buffer);
2275   guint32    *buf    = g_malloc (MAX (width, height) * sizeof (guint32));
2276   const Babl *format = babl_format ("R'G'B'A u8");
2277   guint       i, j;
2278 
2279   g_return_if_fail (GEGL_IS_BUFFER (buffer));
2280 
2281   DM_XMC ("function:get_cropped_region\n");
2282 
2283   DM_XMC ("getTrim:\tMAX=%i\tpr->w=%i\tpr->h=%i\n", sizeof (buf)/4, pr->w, pr->h);
2284 
2285   /* find left border. */
2286   for (i = 0; i < width; ++i)
2287     {
2288       DM_XMC ("i=%i  width=%i\n", i, width);
2289 
2290       gegl_buffer_get (buffer, GEGL_RECTANGLE (i, 0, 1, height), 1.0,
2291                        format, buf,
2292                        GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
2293 
2294       for (j = 0; j < height; ++j)
2295         {
2296           if (pix_is_opaque (buf[j])) /* if a opaque pixel exist. */
2297             {
2298               return_rgn->x = i;
2299               goto find_right;
2300             }
2301         }
2302     }
2303 
2304   /* pr has no opaque pixel. */
2305   return_rgn->width = 0;
2306   return;
2307 
2308   /* find right border. */
2309  find_right:
2310   for (i = 0; i < width ; ++i)
2311     {
2312       DM_XMC ("width-1-i=%i  height=%i\n", width - 1 - i, height);
2313 
2314       gegl_buffer_get (buffer, GEGL_RECTANGLE (width - 1 - i, 0, 1, height), 1.0,
2315                        format, buf,
2316                        GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
2317 
2318       for (j = 0; j < height; ++j)
2319         {
2320           if (pix_is_opaque (buf[j])) /* if a opaque pixel exist. */
2321             {
2322               return_rgn->width = width - i - return_rgn->x;
2323               goto find_top;
2324             }
2325         }
2326     }
2327   g_return_if_reached ();
2328 
2329   /* find top border. */
2330  find_top:
2331   for (j = 0; j < height; ++j)
2332     {
2333       DM_XMC ("j=%i  width=%i\n", j, width);
2334 
2335       gegl_buffer_get (buffer, GEGL_RECTANGLE (0, j, width, 1), 1.0,
2336                        format, buf,
2337                        GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
2338 
2339       for (i = 0; i < width; ++i)
2340         {
2341           if (pix_is_opaque (buf[i])) /* if a opaque pixel exist. */
2342             {
2343               return_rgn->y = j;
2344               goto find_bottom;
2345             }
2346         }
2347     }
2348 
2349   g_return_if_reached ();
2350 
2351   /* find bottom border. */
2352  find_bottom:
2353   for (j = 0; j < height; ++j)
2354     {
2355       DM_XMC ("height-1-j=%i  width=%i\n", height - 1 - j, width);
2356 
2357       gegl_buffer_get (buffer, GEGL_RECTANGLE (0, height - 1 - j, width, 1), 1.0,
2358                        format, buf,
2359                        GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
2360 
2361       for (i = 0; i < width; ++i)
2362         {
2363           if (pix_is_opaque (buf[i])) /* if a opaque pixel exist. */
2364             {
2365               return_rgn->height = height  - j - return_rgn->y;
2366               goto end_trim;
2367             }
2368         }
2369     }
2370 
2371   g_return_if_reached ();
2372 
2373  end_trim:
2374   DM_XMC ("width=%i\theight=%i\txoffset=%i\tyoffset=%i\n",
2375           return_rgn->width, return_rgn->height,
2376           return_rgn->x, return_rgn->y);
2377 
2378   g_free (buf);
2379 }
2380 
2381 /* Return true if alpha of pix is not 0.
2382  */
2383 static inline gboolean
pix_is_opaque(guint32 pix)2384 pix_is_opaque (guint32 pix)
2385 {
2386 #if G_BYTE_ORDER != G_LITTLE_ENDIAN
2387   pix = GUINT32_TO_LE (pix);
2388 #endif
2389 
2390   return ((pix >> 24) != 0);
2391 }
2392 
2393 /* Get the intersection of the all layers of the image specified by image_ID.
2394  * if the intersection is empty return NULL.
2395  * don't forget to g_free returned pointer later.
2396  */
2397 static GeglRectangle *
get_intersection_of_frames(gint32 image_ID)2398 get_intersection_of_frames (gint32 image_ID)
2399 {
2400   GeglRectangle *iregion;
2401   gint           i;
2402   gint32         x1 = G_MININT32, x2 = G_MAXINT32;
2403   gint32         y1 = G_MININT32, y2 = G_MAXINT32;
2404   gint32         x_off, y_off;
2405   gint           nlayers;
2406   gint          *layers;
2407 
2408   g_return_val_if_fail (image_ID != -1, FALSE);
2409 
2410   layers = gimp_image_get_layers (image_ID, &nlayers);
2411 
2412   for (i = 0; i < nlayers; ++i)
2413     {
2414       if (! gimp_drawable_offsets (layers[i], &x_off, &y_off))
2415         return NULL;
2416 
2417       x1 = MAX (x1, x_off);
2418       y1 = MAX (y1, y_off);
2419       x2 = MIN (x2, x_off + gimp_drawable_width  (layers[i]) - 1);
2420       y2 = MIN (y2, y_off + gimp_drawable_height (layers[i]) - 1);
2421     }
2422 
2423   if (x1 > x2 || y1 > y2)
2424     return NULL;
2425 
2426   /* OK intersection exists. */
2427   iregion = g_new (GeglRectangle, 1);
2428   iregion->x = x1;
2429   iregion->y = y1;
2430   iregion->width = x2 - x1 + 1;
2431   iregion->height = y2 - y1 + 1;
2432 
2433   return iregion;
2434 }
2435 
2436 /* If (x,y) is in xmcrp, return TRUE.
2437  */
2438 static gboolean
pix_in_region(gint32 x,gint32 y,GeglRectangle * xmcrp)2439 pix_in_region (gint32         x,
2440                gint32         y,
2441                GeglRectangle *xmcrp)
2442 {
2443   g_return_val_if_fail (xmcrp, FALSE);
2444 
2445   if (x < xmcrp->x || y < xmcrp->y ||
2446       x >= xmcrp->x + xmcrp->width || y >= xmcrp->y + xmcrp->height)
2447     return FALSE;
2448   else
2449     return TRUE;
2450 }
2451 
2452 /**
2453  * Find out xhot, yhot, width and height of the Xcursor specified by xcIs.
2454  * Use NULL for the value you don't want to return.
2455 **/
2456 static void
find_hotspots_and_dimensions(XcursorImages * xcIs,gint32 * xhotp,gint32 * yhotp,gint32 * widthp,gint32 * heightp)2457 find_hotspots_and_dimensions (XcursorImages *xcIs,
2458                               gint32        *xhotp,
2459                               gint32        *yhotp,
2460                               gint32        *widthp,
2461                               gint32        *heightp)
2462 {
2463   gint32 dw, dh;   /* the distance between hotspot and right(bottom) border */
2464   gint32 max_xhot;
2465   gint32 max_yhot; /* the maximum value of xhot(yhot) */
2466   gint   i;
2467 
2468   g_return_if_fail (xcIs);
2469 
2470   max_xhot = max_yhot = dw = dh = 0;
2471 
2472   for (i = 0; i < xcIs->nimage; ++i)
2473     {
2474       /* xhot of entire image is the maximum value of xhot of all frames */
2475       max_xhot = MAX (xcIs->images[i]->xhot, max_xhot);
2476       /* same for yhot */
2477       max_yhot = MAX (xcIs->images[i]->yhot, max_yhot);
2478       /* the maximum distance between right border and xhot */
2479       dw = MAX (dw, xcIs->images[i]->width - xcIs->images[i]->xhot);
2480       /* the maximum distance between bottom border and yhot */
2481       dh = MAX (dh, xcIs->images[i]->height - xcIs->images[i]->yhot);
2482     }
2483 
2484   if (xhotp)
2485     *xhotp = max_xhot;
2486   if (yhotp)
2487     *yhotp = max_yhot;
2488   if (widthp)
2489     *widthp = dw + max_xhot;
2490   if (heightp)
2491     *heightp = dh + max_yhot;
2492 }
2493