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