1 /* GIF exporting file filter for GIMP
2 *
3 * Copyright
4 * - Adam D. Moss
5 * - Peter Mattis
6 * - Spencer Kimball
7 *
8 * Based around original GIF code by David Koblas.
9 *
10 *
11 * Version 4.1.0 - 2003-06-16
12 * Adam D. Moss - <adam@gimp.org> <adam@foxbox.org>
13 */
14 /*
15 * This filter uses code taken from the "giftopnm" and "ppmtogif" programs
16 * which are part of the "netpbm" package.
17 */
18 /*
19 * "The Graphics Interchange Format(c) is the Copyright property of
20 * CompuServe Incorporated. GIF(sm) is a Service Mark property of
21 * CompuServe Incorporated."
22 */
23 /* Copyright notice for GIF code from which this plugin was long ago */
24 /* derived (David Koblas has granted permission to relicense): */
25 /* +-------------------------------------------------------------------+ */
26 /* | Copyright 1990, 1991, 1993, David Koblas. (koblas@extra.com) | */
27 /* +-------------------------------------------------------------------+ */
28
29 #include "config.h"
30
31 #include <string.h>
32
33 #include <libgimp/gimp.h>
34 #include <libgimp/gimpui.h>
35
36 #include "libgimp/stdplugins-intl.h"
37
38
39 #define SAVE_PROC "file-gif-save"
40 #define SAVE2_PROC "file-gif-save2"
41 #define PLUG_IN_BINARY "file-gif-save"
42 #define PLUG_IN_ROLE "gimp-file-gif-save"
43
44
45 /* uncomment the line below for a little debugging info */
46 /* #define GIFDEBUG yesplease */
47
48
49 enum
50 {
51 DISPOSE_STORE_VALUE_COLUMN,
52 DISPOSE_STORE_LABEL_COLUMN
53 };
54
55 enum
56 {
57 DISPOSE_UNSPECIFIED,
58 DISPOSE_COMBINE,
59 DISPOSE_REPLACE
60 };
61
62 typedef struct
63 {
64 gint interlace;
65 gint save_comment;
66 gint loop;
67 gint default_delay;
68 gint default_dispose;
69 gboolean always_use_default_delay;
70 gboolean always_use_default_dispose;
71 gboolean as_animation;
72 } GIFSaveVals;
73
74
75 /* Declare some local functions.
76 */
77 static void query (void);
78 static void run (const gchar *name,
79 gint nparams,
80 const GimpParam *param,
81 gint *nreturn_vals,
82 GimpParam **return_vals);
83
84 static gboolean save_image (GFile *file,
85 gint32 image_ID,
86 gint32 drawable_ID,
87 gint32 orig_image_ID,
88 GError **error);
89
90 static GimpPDBStatusType sanity_check (GFile *file,
91 gint32 *image_ID,
92 GimpRunMode run_mode,
93 GError **error);
94 static gboolean bad_bounds_dialog (void);
95
96 static gboolean save_dialog (gint32 image_ID);
97 static void comment_entry_callback (GtkTextBuffer *buffer);
98
99
100 static gboolean comment_was_edited = FALSE;
101 static gchar *globalcomment = NULL;
102 static gint Interlace; /* For compression code */
103
104
105 const GimpPlugInInfo PLUG_IN_INFO =
106 {
107 NULL, /* init_proc */
108 NULL, /* quit_proc */
109 query, /* query_proc */
110 run, /* run_proc */
111 };
112
113 static GIFSaveVals gsvals =
114 {
115 FALSE, /* interlace */
116 TRUE, /* save comment */
117 TRUE, /* loop infinitely */
118 100, /* default_delay between frames (100ms) */
119 0, /* default_dispose = "don't care" */
120 FALSE, /* don't always use default_delay */
121 FALSE, /* don't always use default_dispose */
122 FALSE /* as_animation */
123 };
124
125
MAIN()126 MAIN ()
127
128 #define COMMON_SAVE_ARGS \
129 { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, \
130 { GIMP_PDB_IMAGE, "image", "Image to export" }, \
131 { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, \
132 { GIMP_PDB_STRING, "uri", "The name of the URI to export the image in" }, \
133 { GIMP_PDB_STRING, "raw-uri", "The name of the URI to export the image in" }, \
134 { GIMP_PDB_INT32, "interlace", "Try to export as interlaced" }, \
135 { GIMP_PDB_INT32, "loop", "(animated gif) loop infinitely" }, \
136 { GIMP_PDB_INT32, "default-delay", "(animated gif) Default delay between frames in milliseconds" }, \
137 { GIMP_PDB_INT32, "default-dispose", "(animated gif) Default disposal type (0=`don't care`, 1=combine, 2=replace)" }
138
139 static void
140 query (void)
141 {
142 static const GimpParamDef save_args[] =
143 {
144 COMMON_SAVE_ARGS
145 };
146
147 static const GimpParamDef save2_args[] =
148 {
149 COMMON_SAVE_ARGS,
150 { GIMP_PDB_INT32, "as-animation", "Export GIF as animation?" },
151 { GIMP_PDB_INT32, "force-delay", "(animated gif) Use specified delay for all frames?" },
152 { GIMP_PDB_INT32, "force-dispose", "(animated gif) Use specified disposal for all frames?" }
153 };
154
155 gimp_install_procedure (SAVE_PROC,
156 "exports files in Compuserve GIF file format",
157 "Export a file in Compuserve GIF format, with "
158 "possible animation, transparency, and comment. "
159 "To export an animation, operate on a multi-layer "
160 "file. The plug-in will interpret <50% alpha as "
161 "transparent. When run non-interactively, the "
162 "value for the comment is taken from the "
163 "'gimp-comment' parasite. ",
164 "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas",
165 "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas",
166 "1995-1997",
167 N_("GIF image"),
168 "INDEXED*, GRAY*",
169 GIMP_PLUGIN,
170 G_N_ELEMENTS (save_args), 0,
171 save_args, NULL);
172
173 gimp_install_procedure (SAVE2_PROC,
174 "exports files in Compuserve GIF file format",
175 "Export a file in Compuserve GIF format, with "
176 "possible animation, transparency, and comment. "
177 "To export an animation, operate on a multi-layer "
178 "file and give the 'as-animation' parameter "
179 "as TRUE. The plug-in will interpret <50% "
180 "alpha as transparent. When run "
181 "non-interactively, the value for the comment "
182 "is taken from the 'gimp-comment' parasite. ",
183 "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas",
184 "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas",
185 "1995-1997",
186 N_("GIF image"),
187 "INDEXED*, GRAY*",
188 GIMP_PLUGIN,
189 G_N_ELEMENTS (save2_args), 0,
190 save2_args, NULL);
191
192 gimp_register_file_handler_mime (SAVE_PROC, "image/gif");
193 gimp_register_save_handler (SAVE_PROC, "gif", "");
194 gimp_register_file_handler_uri (SAVE_PROC);
195
196 gimp_register_file_handler_uri (SAVE2_PROC);
197 }
198
199 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)200 run (const gchar *name,
201 gint nparams,
202 const GimpParam *param,
203 gint *nreturn_vals,
204 GimpParam **return_vals)
205 {
206 static GimpParam values[2];
207 GimpRunMode run_mode;
208 GimpPDBStatusType status = GIMP_PDB_SUCCESS;
209 GimpExportReturn export = GIMP_EXPORT_CANCEL;
210 GError *error = NULL;
211
212 INIT_I18N ();
213 gegl_init (NULL, NULL);
214
215 run_mode = param[0].data.d_int32;
216
217 *nreturn_vals = 1;
218 *return_vals = values;
219
220 values[0].type = GIMP_PDB_STATUS;
221 values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
222
223 if (strcmp (name, SAVE_PROC) == 0 ||
224 strcmp (name, SAVE2_PROC) == 0)
225 {
226 GFile *file;
227 gint32 image_ID;
228 gint32 orig_image_ID;
229 gint32 sanitized_image_ID = 0;
230 gint32 drawable_ID;
231
232 image_ID = orig_image_ID = param[1].data.d_int32;
233 drawable_ID = param[2].data.d_int32;
234 file = g_file_new_for_uri (param[3].data.d_string);
235
236 if (run_mode == GIMP_RUN_INTERACTIVE ||
237 run_mode == GIMP_RUN_WITH_LAST_VALS)
238 gimp_ui_init (PLUG_IN_BINARY, FALSE);
239
240 status = sanity_check (file, &image_ID, run_mode, &error);
241
242 /* Get the export options */
243 if (status == GIMP_PDB_SUCCESS)
244 {
245 /* If the sanity check succeeded, the image_ID will point to
246 * a duplicate image to delete later.
247 */
248 sanitized_image_ID = image_ID;
249
250 switch (run_mode)
251 {
252 case GIMP_RUN_INTERACTIVE:
253 /* Possibly retrieve data */
254 gimp_get_data (SAVE_PROC, &gsvals);
255
256 /* First acquire information with a dialog */
257 if (! save_dialog (image_ID))
258 {
259 gimp_image_delete (sanitized_image_ID);
260 status = GIMP_PDB_CANCEL;
261 }
262 break;
263
264 case GIMP_RUN_NONINTERACTIVE:
265 /* Make sure all the arguments are there! */
266 if (nparams != 9 && nparams != 12)
267 {
268 status = GIMP_PDB_CALLING_ERROR;
269 }
270 else
271 {
272 gsvals.interlace = (param[5].data.d_int32) ? TRUE : FALSE;
273 gsvals.save_comment = TRUE; /* no way to to specify that through the PDB */
274 gsvals.loop = (param[6].data.d_int32) ? TRUE : FALSE;
275 gsvals.default_delay = param[7].data.d_int32;
276 gsvals.default_dispose = param[8].data.d_int32;
277 if (nparams == 12)
278 {
279 gsvals.as_animation = (param[9].data.d_int32) ? TRUE : FALSE;
280 gsvals.always_use_default_delay = (param[10].data.d_int32) ? TRUE : FALSE;
281 gsvals.always_use_default_dispose = (param[11].data.d_int32) ? TRUE : FALSE;
282 }
283 }
284 break;
285
286 case GIMP_RUN_WITH_LAST_VALS:
287 /* Possibly retrieve data */
288 gimp_get_data (SAVE_PROC, &gsvals);
289 break;
290
291 default:
292 break;
293 }
294 }
295
296 if (status == GIMP_PDB_SUCCESS)
297 {
298 /* Create an exportable image based on the export options */
299 switch (run_mode)
300 {
301 case GIMP_RUN_INTERACTIVE:
302 case GIMP_RUN_WITH_LAST_VALS:
303 {
304 GimpExportCapabilities capabilities =
305 GIMP_EXPORT_CAN_HANDLE_INDEXED |
306 GIMP_EXPORT_CAN_HANDLE_GRAY |
307 GIMP_EXPORT_CAN_HANDLE_ALPHA;
308
309 if (gsvals.as_animation)
310 capabilities |= GIMP_EXPORT_CAN_HANDLE_LAYERS;
311
312 export = gimp_export_image (&image_ID, &drawable_ID, "GIF",
313 capabilities);
314
315 if (export == GIMP_EXPORT_CANCEL)
316 {
317 values[0].data.d_status = GIMP_PDB_CANCEL;
318 if (sanitized_image_ID)
319 gimp_image_delete (sanitized_image_ID);
320 return;
321 }
322 }
323 break;
324 default:
325 break;
326 }
327
328 /* Write the image to file */
329 if (save_image (file,
330 image_ID, drawable_ID, orig_image_ID,
331 &error))
332 {
333 /* Store psvals data */
334 gimp_set_data (SAVE_PROC, &gsvals, sizeof (GIFSaveVals));
335 }
336 else
337 {
338 status = GIMP_PDB_EXECUTION_ERROR;
339 }
340
341 gimp_image_delete (sanitized_image_ID);
342 }
343
344 if (export == GIMP_EXPORT_EXPORT)
345 gimp_image_delete (image_ID);
346
347 g_object_unref (file);
348 }
349
350 if (status != GIMP_PDB_SUCCESS && error)
351 {
352 *nreturn_vals = 2;
353 values[1].type = GIMP_PDB_STRING;
354 values[1].data.d_string = error->message;
355 }
356
357 values[0].data.d_status = status;
358 }
359
360
361 /* ppmtogif.c - read a portable pixmap and produce a GIF file
362 **
363 ** Based on GIFENCOD by David Rowley <mgardi@watdscu.waterloo.edu>. A
364 ** Lempel-Ziv compression based on "compress".
365 **
366 ** Modified by Marcel Wijkstra <wijkstra@fwi.uva.nl>
367 **
368 **
369 ** Copyright (C) 1989 by Jef Poskanzer.
370 **
371 ** Permission to use, copy, modify, and distribute this software and its
372 ** documentation for any purpose and without fee is hereby granted, provided
373 ** that the above copyright notice appear in all copies and that both that
374 ** copyright notice and this permission notice appear in supporting
375 ** documentation. This software is provided "as is" without express or
376 ** implied warranty.
377 **
378 ** The Graphics Interchange Format(c) is the Copyright property of
379 ** CompuServe Incorporated. GIF(sm) is a Service Mark property of
380 ** CompuServe Incorporated.
381 */
382
383 #define MAXCOLORS 256
384
385 /*
386 * Pointer to function returning an int
387 */
388 typedef gint (* ifunptr) (gint x,
389 gint y);
390
391
392 static gint find_unused_ia_color (const guchar *pixels,
393 gint numpixels,
394 gint num_indices,
395 gint *colors);
396
397 static void special_flatten_indexed_alpha (guchar *pixels,
398 gint transparent,
399 gint numpixels);
400
401 static gint colors_to_bpp (gint colors);
402 static gint bpp_to_colors (gint bpp);
403 static gint get_pixel (gint x,
404 gint y);
405 static gint gif_next_pixel (ifunptr getpixel);
406 static void bump_pixel (void);
407
408 static gboolean gif_encode_header (GOutputStream *output,
409 gboolean gif89,
410 gint width,
411 gint height,
412 gint background,
413 gint bpp,
414 gint *red,
415 gint *green,
416 gint *blue,
417 ifunptr get_pixel,
418 GError **error);
419 static gboolean gif_encode_graphic_control_ext (GOutputStream *output,
420 gint disposal,
421 gint delay89,
422 gint n_frames,
423 gint width,
424 gint height,
425 gint transparent,
426 gint bpp,
427 ifunptr get_pixel,
428 GError **error);
429 static gboolean gif_encode_image_data (GOutputStream *output,
430 gint width,
431 gint height,
432 gint interlace,
433 gint bpp,
434 ifunptr get_pixel,
435 gint offset_x,
436 gint offset_y,
437 GError **error);
438 static gboolean gif_encode_close (GOutputStream *output,
439 GError **error);
440 static gboolean gif_encode_loop_ext (GOutputStream *output,
441 guint n_loops,
442 GError **error);
443 static gboolean gif_encode_comment_ext (GOutputStream *output,
444 const gchar *comment,
445 GError **error);
446
447 static gint rowstride;
448 static guchar *pixels;
449 static gint cur_progress;
450 static gint max_progress;
451
452 static gboolean compress (GOutputStream *output,
453 gint init_bits,
454 ifunptr ReadValue,
455 GError **error);
456 static gboolean no_compress (GOutputStream *output,
457 gint init_bits,
458 ifunptr ReadValue,
459 GError **error);
460 static gboolean rle_compress (GOutputStream *output,
461 gint init_bits,
462 ifunptr ReadValue,
463 GError **error);
464 static gboolean normal_compress (GOutputStream *output,
465 gint init_bits,
466 ifunptr ReadValue,
467 GError **error);
468
469 static gboolean put_byte (GOutputStream *output,
470 guchar b,
471 GError **error);
472 static gboolean put_word (GOutputStream *output,
473 gint w,
474 GError **error);
475 static gboolean put_string (GOutputStream *output,
476 const gchar *s,
477 GError **error);
478 static gboolean output_code (GOutputStream *output,
479 gint code,
480 GError **error);
481 static gboolean cl_block (GOutputStream *output,
482 GError **error);
483 static void cl_hash (glong hsize);
484
485 static void char_init (void);
486 static gboolean char_out (GOutputStream *output,
487 gint c,
488 GError **error);
489 static gboolean char_flush (GOutputStream *output,
490 GError **error);
491
492
493 static gint
find_unused_ia_color(const guchar * pixels,gint numpixels,gint num_indices,gint * colors)494 find_unused_ia_color (const guchar *pixels,
495 gint numpixels,
496 gint num_indices,
497 gint *colors)
498 {
499 gboolean ix_used[256];
500 gint i;
501
502 #ifdef GIFDEBUG
503 g_printerr ("GIF: fuiac: Image claims to use %d/%d indices - finding free "
504 "index...\n", *colors, num_indices);
505 #endif
506
507 for (i = 0; i < 256; i++)
508 ix_used[i] = FALSE;
509
510 for (i = 0; i < numpixels; i++)
511 {
512 if (pixels[i * 2 + 1])
513 ix_used[pixels[i * 2]] = TRUE;
514 }
515
516 for (i = num_indices - 1; i >= 0; i--)
517 {
518 if (! ix_used[i])
519 {
520 #ifdef GIFDEBUG
521 g_printerr ("GIF: Found unused color index %d.\n", (int) i);
522 #endif
523 return i;
524 }
525 }
526
527 /* Couldn't find an unused color index within the number of bits per
528 * pixel we wanted. Will have to increment the number of colors in
529 * the image and assign a transparent pixel there.
530 */
531 if (*colors < 256)
532 {
533 (*colors)++;
534
535 g_printerr ("GIF: 2nd pass "
536 "- Increasing bounds and using color index %d.\n",
537 *colors - 1);
538 return ((*colors) - 1);
539 }
540
541 g_message (_("Couldn't simply reduce colors further. Exporting as opaque."));
542
543 return -1;
544 }
545
546
547 static void
special_flatten_indexed_alpha(guchar * pixels,gint transparent,gint numpixels)548 special_flatten_indexed_alpha (guchar *pixels,
549 gint transparent,
550 gint numpixels)
551 {
552 guint32 i;
553
554 /* Each transparent pixel in the image is mapped to a uniform value
555 * for encoding, if image already has <=255 colors
556 */
557
558 if (transparent == -1) /* tough, no indices left for the trans. index */
559 {
560 for (i = 0; i < numpixels; i++)
561 pixels[i] = pixels[i * 2];
562 }
563 else /* make transparent */
564 {
565 for (i = 0; i < numpixels; i++)
566 {
567 if (! (pixels[i * 2 + 1] & 128))
568 {
569 pixels[i] = (guchar) transparent;
570 }
571 else
572 {
573 pixels[i] = pixels[i * 2];
574 }
575 }
576 }
577 }
578
579
580 static gint
parse_ms_tag(const gchar * str)581 parse_ms_tag (const gchar *str)
582 {
583 gint sum = 0;
584 gint offset = 0;
585 gint length;
586
587 length = strlen (str);
588
589 find_another_bra:
590
591 while ((offset < length) && (str[offset] != '('))
592 offset++;
593
594 if (offset >= length)
595 return(-1);
596
597 if (! g_ascii_isdigit (str[++offset]))
598 goto find_another_bra;
599
600 do
601 {
602 sum *= 10;
603 sum += str[offset] - '0';
604 offset++;
605 }
606 while ((offset < length) && (g_ascii_isdigit (str[offset])));
607
608 if (length - offset <= 2)
609 return(-3);
610
611 if ((g_ascii_toupper (str[offset]) != 'M') ||
612 (g_ascii_toupper (str[offset + 1]) != 'S'))
613 return -4;
614
615 return sum;
616 }
617
618
619 static gint
parse_disposal_tag(const gchar * str)620 parse_disposal_tag (const gchar *str)
621 {
622 gint offset = 0;
623 gint length;
624
625 length = strlen (str);
626
627 while ((offset + 9) <= length)
628 {
629 if (strncmp (&str[offset], "(combine)", 9) == 0)
630 return 0x01;
631
632 if (strncmp (&str[offset], "(replace)", 9) == 0)
633 return 0x02 ;
634
635 offset++;
636 }
637
638 return gsvals.default_dispose;
639 }
640
641
642 static GimpPDBStatusType
sanity_check(GFile * file,gint32 * image_ID,GimpRunMode run_mode,GError ** error)643 sanity_check (GFile *file,
644 gint32 *image_ID,
645 GimpRunMode run_mode,
646 GError **error)
647 {
648 gint32 *layers;
649 gint nlayers;
650 gint image_width;
651 gint image_height;
652 gint i;
653
654 image_width = gimp_image_width (*image_ID);
655 image_height = gimp_image_height (*image_ID);
656
657 if (image_width > G_MAXUSHORT || image_height > G_MAXUSHORT)
658 {
659 g_set_error (error, 0, 0,
660 _("Unable to export '%s'. "
661 "The GIF file format does not support images that are "
662 "more than %d pixels wide or tall."),
663 gimp_file_get_utf8_name (file), G_MAXUSHORT);
664
665 return GIMP_PDB_EXECUTION_ERROR;
666 }
667
668 /*** Iterate through the layers to make sure they're all ***/
669 /*** within the bounds of the image ***/
670
671 *image_ID = gimp_image_duplicate (*image_ID);
672 layers = gimp_image_get_layers (*image_ID, &nlayers);
673
674 for (i = 0; i < nlayers; i++)
675 {
676 gint offset_x;
677 gint offset_y;
678
679 gimp_drawable_offsets (layers[i], &offset_x, &offset_y);
680
681 if (offset_x < 0 ||
682 offset_y < 0 ||
683 offset_x + gimp_drawable_width (layers[i]) > image_width ||
684 offset_y + gimp_drawable_height (layers[i]) > image_height)
685 {
686 g_free (layers);
687
688 /* Image has illegal bounds - ask the user what it wants to do */
689
690 /* Do the crop if we can't talk to the user, or if we asked
691 * the user and they said yes.
692 */
693 if ((run_mode == GIMP_RUN_NONINTERACTIVE) || bad_bounds_dialog ())
694 {
695 gimp_image_crop (*image_ID, image_width, image_height, 0, 0);
696 return GIMP_PDB_SUCCESS;
697 }
698 else
699 {
700 gimp_image_delete (*image_ID);
701 return GIMP_PDB_CANCEL;
702 }
703 }
704 }
705
706 g_free (layers);
707
708 return GIMP_PDB_SUCCESS;
709 }
710
711
712 static gboolean
save_image(GFile * file,gint32 image_ID,gint32 drawable_ID,gint32 orig_image_ID,GError ** error)713 save_image (GFile *file,
714 gint32 image_ID,
715 gint32 drawable_ID,
716 gint32 orig_image_ID,
717 GError **error)
718 {
719 GeglBuffer *buffer;
720 GimpImageType drawable_type;
721 const Babl *format = NULL;
722 GOutputStream *output;
723 gint Red[MAXCOLORS];
724 gint Green[MAXCOLORS];
725 gint Blue[MAXCOLORS];
726 guchar *cmap;
727 guint rows, cols;
728 gint BitsPerPixel;
729 gint liberalBPP = 0;
730 gint useBPP = 0;
731 gint colors;
732 gint i;
733 gint transparent;
734 gint offset_x, offset_y;
735
736 gint32 *layers;
737 gint nlayers;
738
739 gboolean is_gif89 = FALSE;
740
741 gint Delay89;
742 gint Disposal;
743 gchar *layer_name;
744
745 GimpRGB background;
746 guchar bgred, bggreen, bgblue;
747 guchar bgindex = 0;
748 guint best_error = 0xFFFFFFFF;
749
750 /* Save the comment back to the ImageID, if appropriate */
751 if (globalcomment != NULL && comment_was_edited)
752 {
753 GimpParasite *parasite;
754
755 parasite = gimp_parasite_new ("gimp-comment",
756 GIMP_PARASITE_PERSISTENT,
757 strlen (globalcomment) + 1,
758 (gpointer) globalcomment);
759 gimp_image_attach_parasite (orig_image_ID, parasite);
760 gimp_parasite_free (parasite);
761 }
762
763 /* The GIF spec says 7bit ASCII for the comment block. */
764 if (gsvals.save_comment && globalcomment)
765 {
766 const gchar *c = globalcomment;
767 gint len;
768
769 for (len = strlen (c); len; c++, len--)
770 {
771 if ((guchar) *c > 127)
772 {
773 g_message (_("The GIF format only supports comments in "
774 "7bit ASCII encoding. No comment is saved."));
775
776 g_free (globalcomment);
777 globalcomment = NULL;
778
779 break;
780 }
781 }
782 }
783
784 /* get a list of layers for this image_ID */
785 layers = gimp_image_get_layers (image_ID, &nlayers);
786
787 drawable_type = gimp_drawable_type (layers[0]);
788
789 /* If the image has multiple layers (i.e. will be animated), a
790 * comment, or transparency, then it must be encoded as a GIF89a
791 * file, not a vanilla GIF87a.
792 */
793 if (nlayers > 1)
794 {
795 is_gif89 = TRUE;
796
797 /* Layers can be with or without alpha channel. Make sure we set
798 * alpha if there is at least one layer with alpha channel. */
799 if (drawable_type == GIMP_GRAY_IMAGE ||
800 drawable_type == GIMP_INDEXED_IMAGE)
801 {
802 for (i = nlayers - 1; i >= 0; i--)
803 {
804 GimpImageType dr_type = gimp_drawable_type (layers[i]);
805
806 if (dr_type == GIMP_GRAYA_IMAGE ||
807 dr_type == GIMP_INDEXEDA_IMAGE)
808 {
809 drawable_type = dr_type;
810 break;
811 }
812 }
813 }
814 }
815
816 if (gsvals.save_comment)
817 is_gif89 = TRUE;
818
819 switch (drawable_type)
820 {
821 case GIMP_INDEXEDA_IMAGE:
822 is_gif89 = TRUE;
823 case GIMP_INDEXED_IMAGE:
824 cmap = gimp_image_get_colormap (image_ID, &colors);
825
826 gimp_context_get_background (&background);
827 gimp_rgb_get_uchar (&background, &bgred, &bggreen, &bgblue);
828
829 for (i = 0; i < colors; i++)
830 {
831 Red[i] = *cmap++;
832 Green[i] = *cmap++;
833 Blue[i] = *cmap++;
834 }
835 for ( ; i < 256; i++)
836 {
837 Red[i] = bgred;
838 Green[i] = bggreen;
839 Blue[i] = bgblue;
840 }
841 break;
842 case GIMP_GRAYA_IMAGE:
843 is_gif89 = TRUE;
844 case GIMP_GRAY_IMAGE:
845 colors = 256; /* FIXME: Not ideal. */
846 for ( i = 0; i < 256; i++)
847 {
848 Red[i] = Green[i] = Blue[i] = i;
849 }
850 break;
851
852 default:
853 g_message (_("Cannot export RGB color images. Convert to "
854 "indexed color or grayscale first."));
855 return FALSE;
856 }
857
858
859 /* find earliest index in palette which is closest to the background
860 * color, and ATTEMPT to use that as the GIF's default background
861 * color.
862 */
863 for (i = 255; i >= 0; --i)
864 {
865 guint local_error = 0;
866
867 local_error += (Red[i] - bgred) * (Red[i] - bgred);
868 local_error += (Green[i] - bggreen) * (Green[i] - bggreen);
869 local_error += (Blue[i] - bgblue) * (Blue[i] - bgblue);
870
871 if (local_error <= best_error)
872 {
873 bgindex = i;
874 best_error = local_error;
875 }
876 }
877
878
879 /* init the progress meter */
880 gimp_progress_init_printf (_("Exporting '%s'"),
881 gimp_file_get_utf8_name (file));
882
883
884 /* open the destination file for writing */
885 output = G_OUTPUT_STREAM (g_file_replace (file,
886 NULL, FALSE, G_FILE_CREATE_NONE,
887 NULL, error));
888 if (output)
889 {
890 GDataOutputStream *data_output;
891
892 data_output = g_data_output_stream_new (output);
893 g_object_unref (output);
894
895 g_data_output_stream_set_byte_order (data_output,
896 G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN);
897
898 output = G_OUTPUT_STREAM (data_output);
899 }
900 else
901 {
902 return FALSE;
903 }
904
905
906 /* write the GIFheader */
907
908 if (colors < 256)
909 {
910 /* we keep track of how many bits we promised to have in
911 * liberalBPP, so that we don't accidentally come under this
912 * when doing clever transparency stuff where we can re-use
913 * wasted indices.
914 */
915 liberalBPP = BitsPerPixel =
916 colors_to_bpp (colors + ((drawable_type==GIMP_INDEXEDA_IMAGE) ? 1 : 0));
917 }
918 else
919 {
920 liberalBPP = BitsPerPixel =
921 colors_to_bpp (256);
922
923 if (drawable_type == GIMP_INDEXEDA_IMAGE)
924 {
925 g_printerr ("GIF: Too many colors?\n");
926 }
927 }
928
929 cols = gimp_image_width (image_ID);
930 rows = gimp_image_height (image_ID);
931 Interlace = gsvals.interlace;
932 if (! gif_encode_header (output, is_gif89, cols, rows, bgindex,
933 BitsPerPixel, Red, Green, Blue, get_pixel,
934 error))
935 return FALSE;
936
937
938 /* If the image has multiple layers it'll be made into an animated
939 * GIF, so write out the infinite-looping extension
940 */
941 if ((nlayers > 1) && (gsvals.loop))
942 if (! gif_encode_loop_ext (output, 0, error))
943 return FALSE;
944
945 /* Write comment extension - mustn't be written before the looping ext. */
946 if (gsvals.save_comment && globalcomment)
947 {
948 if (! gif_encode_comment_ext (output, globalcomment, error))
949 return FALSE;
950 }
951
952
953 /*** Now for each layer in the image, save an image in a compound GIF ***/
954 /************************************************************************/
955
956 cur_progress = 0;
957 max_progress = nlayers * rows;
958
959 for (i = nlayers - 1; i >= 0; i--, cur_progress = (nlayers - i) * rows)
960 {
961 drawable_type = gimp_drawable_type (layers[i]);
962 if (drawable_type == GIMP_GRAYA_IMAGE)
963 {
964 format = babl_format ("Y'A u8");
965 }
966 else if (drawable_type == GIMP_GRAY_IMAGE)
967 {
968 format = babl_format ("Y' u8");
969 }
970 buffer = gimp_drawable_get_buffer (layers[i]);
971 gimp_drawable_offsets (layers[i], &offset_x, &offset_y);
972 cols = gimp_drawable_width (layers[i]);
973 rows = gimp_drawable_height (layers[i]);
974 rowstride = cols;
975
976 pixels = g_new (guchar, (cols * rows *
977 (((drawable_type == GIMP_INDEXEDA_IMAGE) ||
978 (drawable_type == GIMP_GRAYA_IMAGE)) ? 2 : 1)));
979
980 gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, cols, rows), 1.0,
981 format, pixels,
982 GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
983
984 /* sort out whether we need to do transparency jiggery-pokery */
985 if ((drawable_type == GIMP_INDEXEDA_IMAGE) ||
986 (drawable_type == GIMP_GRAYA_IMAGE))
987 {
988 /* Try to find an entry which isn't actually used in the
989 * image, for a transparency index.
990 */
991
992 transparent =
993 find_unused_ia_color (pixels,
994 cols * rows,
995 bpp_to_colors (colors_to_bpp (colors)),
996 &colors);
997
998 special_flatten_indexed_alpha (pixels,
999 transparent,
1000 cols * rows);
1001 }
1002 else
1003 {
1004 transparent = -1;
1005 }
1006
1007 BitsPerPixel = colors_to_bpp (colors);
1008
1009 if (BitsPerPixel != liberalBPP)
1010 {
1011 /* We were able to re-use an index within the existing
1012 * bitspace, whereas the estimate in the header was
1013 * pessimistic but still needs to be upheld...
1014 */
1015 #ifdef GIFDEBUG
1016 static gboolean onceonly = FALSE;
1017
1018 if (! onceonly)
1019 {
1020 g_warning ("Promised %d bpp, pondered writing chunk with %d bpp!",
1021 liberalBPP, BitsPerPixel);
1022 onceonly = TRUE;
1023 }
1024 #endif
1025 }
1026
1027 useBPP = (BitsPerPixel > liberalBPP) ? BitsPerPixel : liberalBPP;
1028
1029 if (is_gif89)
1030 {
1031 if (i > 0 && ! gsvals.always_use_default_dispose)
1032 {
1033 layer_name = gimp_item_get_name (layers[i - 1]);
1034 Disposal = parse_disposal_tag (layer_name);
1035 g_free (layer_name);
1036 }
1037 else
1038 {
1039 Disposal = gsvals.default_dispose;
1040 }
1041
1042 layer_name = gimp_item_get_name (layers[i]);
1043 Delay89 = parse_ms_tag (layer_name);
1044 g_free (layer_name);
1045
1046 if (Delay89 < 0 || gsvals.always_use_default_delay)
1047 Delay89 = (gsvals.default_delay + 5) / 10;
1048 else
1049 Delay89 = (Delay89 + 5) / 10;
1050
1051 /* don't allow a CPU-sucking completely 0-delay looping anim */
1052 if ((nlayers > 1) && gsvals.loop && (Delay89 == 0))
1053 {
1054 static gboolean onceonly = FALSE;
1055
1056 if (! onceonly)
1057 {
1058 g_message (_("Delay inserted to prevent evil "
1059 "CPU-sucking animation."));
1060 onceonly = TRUE;
1061 }
1062
1063 Delay89 = 1;
1064 }
1065
1066 if (! gif_encode_graphic_control_ext (output,
1067 Disposal, Delay89, nlayers,
1068 cols, rows,
1069 transparent,
1070 useBPP,
1071 get_pixel,
1072 error))
1073 return FALSE;
1074 }
1075
1076 if (! gif_encode_image_data (output, cols, rows,
1077 (rows > 4) ? gsvals.interlace : 0,
1078 useBPP,
1079 get_pixel,
1080 offset_x, offset_y,
1081 error))
1082 return FALSE;
1083
1084 gimp_progress_update (1.0);
1085
1086 g_object_unref (buffer);
1087
1088 g_free (pixels);
1089 }
1090
1091 g_free (layers);
1092
1093 if (! gif_encode_close (output, error))
1094 return FALSE;
1095
1096 return TRUE;
1097 }
1098
1099 static gboolean
bad_bounds_dialog(void)1100 bad_bounds_dialog (void)
1101 {
1102 GtkWidget *dialog;
1103 gboolean crop;
1104
1105 dialog = gtk_message_dialog_new (NULL, 0,
1106 GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE,
1107 _("The image you are trying to export as a "
1108 "GIF contains layers which extend beyond "
1109 "the actual borders of the image."));
1110
1111 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1112 _("_Cancel"), GTK_RESPONSE_CANCEL,
1113 _("Cr_op"), GTK_RESPONSE_OK,
1114 NULL);
1115
1116 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
1117 GTK_RESPONSE_OK,
1118 GTK_RESPONSE_CANCEL,
1119 -1);
1120
1121 gimp_window_set_transient (GTK_WINDOW (dialog));
1122
1123 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1124 _("The GIF file format does not "
1125 "allow this. You may choose "
1126 "whether to crop all of the "
1127 "layers to the image borders, "
1128 "or cancel this export."));
1129
1130 gtk_widget_show (dialog);
1131
1132 crop = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK);
1133
1134 gtk_widget_destroy (dialog);
1135
1136 return crop;
1137 }
1138
1139 static GtkWidget *
file_gif_toggle_button_init(GtkBuilder * builder,const gchar * name,gboolean initial_value,gboolean * value_pointer)1140 file_gif_toggle_button_init (GtkBuilder *builder,
1141 const gchar *name,
1142 gboolean initial_value,
1143 gboolean *value_pointer)
1144 {
1145 GtkWidget *toggle = NULL;
1146
1147 toggle = GTK_WIDGET (gtk_builder_get_object (builder, name));
1148 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), initial_value);
1149 g_signal_connect (toggle, "toggled",
1150 G_CALLBACK (gimp_toggle_button_update),
1151 value_pointer);
1152
1153 return toggle;
1154 }
1155
1156 static GtkWidget *
file_gif_spin_button_int_init(GtkBuilder * builder,const gchar * name,int initial_value,int * value_pointer)1157 file_gif_spin_button_int_init (GtkBuilder *builder,
1158 const gchar *name,
1159 int initial_value,
1160 int *value_pointer)
1161 {
1162 GtkWidget *spin_button = NULL;
1163 GtkAdjustment *adjustment = NULL;
1164
1165 spin_button = GTK_WIDGET (gtk_builder_get_object (builder, name));
1166
1167 adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spin_button));
1168 gtk_adjustment_set_value (adjustment, initial_value);
1169 g_signal_connect (adjustment, "value-changed",
1170 G_CALLBACK (gimp_int_adjustment_update),
1171 value_pointer);
1172
1173 return spin_button;
1174 }
1175
1176 static void
file_gif_combo_box_int_update_value(GtkComboBox * combo,gint * value)1177 file_gif_combo_box_int_update_value (GtkComboBox *combo,
1178 gint *value)
1179 {
1180 GtkTreeIter iter;
1181
1182 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
1183 {
1184 gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (combo)),
1185 &iter,
1186 DISPOSE_STORE_VALUE_COLUMN, value,
1187 -1);
1188 }
1189 }
1190
1191 static GtkWidget *
file_gif_combo_box_int_init(GtkBuilder * builder,const gchar * name,int initial_value,int * value_pointer,const gchar * first_label,gint first_value,...)1192 file_gif_combo_box_int_init (GtkBuilder *builder,
1193 const gchar *name,
1194 int initial_value,
1195 int *value_pointer,
1196 const gchar *first_label,
1197 gint first_value,
1198 ...)
1199 {
1200 GtkWidget *combo = NULL;
1201 GtkListStore *store = NULL;
1202 const gchar *label = NULL;
1203 gint value = 0;
1204 GtkTreeIter iter = { 0, };
1205 va_list values;
1206
1207 combo = GTK_WIDGET (gtk_builder_get_object (builder, name));
1208 store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo)));
1209
1210 /* Populate */
1211 va_start (values, first_value);
1212 for (label = first_label, value = first_value;
1213 label;
1214 label = va_arg (values, const gchar *), value = va_arg (values, gint))
1215 {
1216 gtk_list_store_append (store, &iter);
1217 gtk_list_store_set (store, &iter,
1218 DISPOSE_STORE_VALUE_COLUMN, value,
1219 DISPOSE_STORE_LABEL_COLUMN, label,
1220 -1);
1221 }
1222 va_end (values);
1223
1224 /* Set initial value */
1225 gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (store),
1226 &iter,
1227 NULL,
1228 initial_value);
1229 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter);
1230
1231 /* Arrange update of value */
1232 g_signal_connect (combo, "changed",
1233 G_CALLBACK (file_gif_combo_box_int_update_value),
1234 value_pointer);
1235
1236 return combo;
1237 }
1238
1239 static gboolean
save_dialog(gint32 image_ID)1240 save_dialog (gint32 image_ID)
1241 {
1242 GtkBuilder *builder = NULL;
1243 gchar *ui_file = NULL;
1244 GError *error = NULL;
1245 GtkWidget *dialog;
1246 GtkWidget *text_view;
1247 GtkTextBuffer *text_buffer;
1248 GtkWidget *toggle;
1249 GtkWidget *frame;
1250 GimpParasite *GIF2_CMNT;
1251 gint32 nlayers;
1252 gboolean animation_supported = FALSE;
1253 gboolean run;
1254
1255 g_free (gimp_image_get_layers (image_ID, &nlayers));
1256 animation_supported = nlayers > 1;
1257
1258 dialog = gimp_export_dialog_new (_("GIF"), PLUG_IN_BINARY, SAVE_PROC);
1259
1260 /* GtkBuilder init */
1261 builder = gtk_builder_new ();
1262 ui_file = g_build_filename (gimp_data_directory (),
1263 "ui/plug-ins/plug-in-file-gif.ui",
1264 NULL);
1265 if (! gtk_builder_add_from_file (builder, ui_file, &error))
1266 g_printerr (_("Error loading UI file '%s':\n%s"),
1267 ui_file, error ? error->message : "???");
1268 g_free (ui_file);
1269
1270 /* Main vbox */
1271 gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
1272 GTK_WIDGET (gtk_builder_get_object (builder, "main-vbox")),
1273 TRUE, TRUE, 0);
1274
1275 /* regular gif parameter settings */
1276 file_gif_toggle_button_init (builder, "interlace",
1277 gsvals.interlace, &gsvals.interlace);
1278 file_gif_toggle_button_init (builder, "save-comment",
1279 gsvals.save_comment, &gsvals.save_comment);
1280 file_gif_toggle_button_init (builder, "as-animation",
1281 gsvals.as_animation, &gsvals.as_animation);
1282
1283 text_view = GTK_WIDGET (gtk_builder_get_object (builder, "comment"));
1284 text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
1285
1286 if (globalcomment)
1287 g_free (globalcomment);
1288
1289 GIF2_CMNT = gimp_image_get_parasite (image_ID, "gimp-comment");
1290 if (GIF2_CMNT)
1291 {
1292 globalcomment = g_strndup (gimp_parasite_data (GIF2_CMNT),
1293 gimp_parasite_data_size (GIF2_CMNT));
1294 gimp_parasite_free (GIF2_CMNT);
1295 }
1296 else
1297 {
1298 globalcomment = gimp_get_default_comment ();
1299 }
1300
1301 if (globalcomment)
1302 gtk_text_buffer_set_text (text_buffer, globalcomment, -1);
1303
1304 g_signal_connect (text_buffer, "changed",
1305 G_CALLBACK (comment_entry_callback),
1306 NULL);
1307
1308 /* additional animated gif parameter settings */
1309 file_gif_toggle_button_init (builder, "loop-forever",
1310 gsvals.loop, &gsvals.loop);
1311
1312 /* default_delay entry field */
1313 file_gif_spin_button_int_init (builder, "delay-spin",
1314 gsvals.default_delay, &gsvals.default_delay);
1315
1316 /* Disposal selector */
1317 file_gif_combo_box_int_init (builder, "dispose-combo",
1318 gsvals.default_dispose, &gsvals.default_dispose,
1319 _("I don't care"),
1320 DISPOSE_UNSPECIFIED,
1321 _("Cumulative layers (combine)"),
1322 DISPOSE_COMBINE,
1323 _("One frame per layer (replace)"),
1324 DISPOSE_REPLACE,
1325 NULL);
1326
1327 /* The "Always use default values" toggles */
1328 file_gif_toggle_button_init (builder, "use-default-delay",
1329 gsvals.always_use_default_delay,
1330 &gsvals.always_use_default_delay);
1331 file_gif_toggle_button_init (builder, "use-default-dispose",
1332 gsvals.always_use_default_dispose,
1333 &gsvals.always_use_default_dispose);
1334
1335 frame = GTK_WIDGET (gtk_builder_get_object (builder, "animation-frame"));
1336 toggle = GTK_WIDGET (gtk_builder_get_object (builder, "as-animation"));
1337 if (! animation_supported)
1338 {
1339 gimp_help_set_help_data (toggle,
1340 _("You can only export as animation when the "
1341 "image has more than one layer. The image "
1342 "you are trying to export only has one "
1343 "layer."),
1344 NULL);
1345 /* Make sure the checkbox is not checked from session data. */
1346 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE);
1347 }
1348 gtk_widget_set_sensitive (toggle, animation_supported);
1349
1350 g_object_bind_property (toggle, "active",
1351 frame, "sensitive",
1352 G_BINDING_SYNC_CREATE);
1353
1354 gtk_widget_show (dialog);
1355
1356 run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
1357
1358 gtk_widget_destroy (dialog);
1359
1360 return run;
1361 }
1362
1363 static int
colors_to_bpp(gint colors)1364 colors_to_bpp (gint colors)
1365 {
1366 gint bpp;
1367
1368 if (colors <= 2)
1369 bpp = 1;
1370 else if (colors <= 4)
1371 bpp = 2;
1372 else if (colors <= 8)
1373 bpp = 3;
1374 else if (colors <= 16)
1375 bpp = 4;
1376 else if (colors <= 32)
1377 bpp = 5;
1378 else if (colors <= 64)
1379 bpp = 6;
1380 else if (colors <= 128)
1381 bpp = 7;
1382 else if (colors <= 256)
1383 bpp = 8;
1384 else
1385 {
1386 g_warning ("GIF: colors_to_bpp - Eep! too many colors: %d\n", colors);
1387 return 8;
1388 }
1389
1390 return bpp;
1391 }
1392
1393 static int
bpp_to_colors(gint bpp)1394 bpp_to_colors (gint bpp)
1395 {
1396 gint colors;
1397
1398 if (bpp > 8)
1399 {
1400 g_warning ("GIF: bpp_to_colors - Eep! bpp==%d !\n", bpp);
1401 return 256;
1402 }
1403
1404 colors = 1 << bpp;
1405
1406 return colors;
1407 }
1408
1409
1410
1411 static gint
get_pixel(gint x,gint y)1412 get_pixel (gint x,
1413 gint y)
1414 {
1415 return *(pixels + (rowstride * (long) y) + (long) x);
1416 }
1417
1418
1419 /*****************************************************************************
1420 *
1421 * GIFENCODE.C - GIF Image compression interface
1422 *
1423 * GIFEncode( FName, GHeight, GWidth, GInterlace, Background, Transparent,
1424 * BitsPerPixel, Red, Green, Blue, get_pixel )
1425 *
1426 *****************************************************************************/
1427
1428 static gint Width, Height;
1429 static gint curx, cury;
1430 static glong CountDown;
1431 static gint Pass = 0;
1432
1433 /*
1434 * Bump the 'curx' and 'cury' to point to the next pixel
1435 */
1436 static void
bump_pixel(void)1437 bump_pixel (void)
1438 {
1439 /*
1440 * Bump the current X position
1441 */
1442 curx++;
1443
1444 /*
1445 * If we are at the end of a scan line, set curx back to the beginning
1446 * If we are interlaced, bump the cury to the appropriate spot,
1447 * otherwise, just increment it.
1448 */
1449 if (curx == Width)
1450 {
1451 cur_progress++;
1452
1453 if ((cur_progress % 20) == 0)
1454 gimp_progress_update ((gdouble) cur_progress / (gdouble) max_progress);
1455
1456 curx = 0;
1457
1458 if (! Interlace)
1459 ++cury;
1460 else
1461 {
1462 switch (Pass)
1463 {
1464
1465 case 0:
1466 cury += 8;
1467 if (cury >= Height)
1468 {
1469 Pass++;
1470 cury = 4;
1471 }
1472 break;
1473
1474 case 1:
1475 cury += 8;
1476 if (cury >= Height)
1477 {
1478 Pass++;
1479 cury = 2;
1480 }
1481 break;
1482
1483 case 2:
1484 cury += 4;
1485 if (cury >= Height)
1486 {
1487 Pass++;
1488 cury = 1;
1489 }
1490 break;
1491
1492 case 3:
1493 cury += 2;
1494 break;
1495 }
1496 }
1497 }
1498 }
1499
1500 /*
1501 * Return the next pixel from the image
1502 */
1503 static gint
gif_next_pixel(ifunptr getpixel)1504 gif_next_pixel (ifunptr getpixel)
1505 {
1506 gint r;
1507
1508 if (CountDown == 0)
1509 return EOF;
1510
1511 --CountDown;
1512
1513 r = (*getpixel) (curx, cury);
1514
1515 bump_pixel ();
1516
1517 return r;
1518 }
1519
1520 /* public */
1521
1522 static gboolean
gif_encode_header(GOutputStream * output,gboolean gif89,gint GWidth,gint GHeight,gint Background,gint BitsPerPixel,gint Red[],gint Green[],gint Blue[],ifunptr get_pixel,GError ** error)1523 gif_encode_header (GOutputStream *output,
1524 gboolean gif89,
1525 gint GWidth,
1526 gint GHeight,
1527 gint Background,
1528 gint BitsPerPixel,
1529 gint Red[],
1530 gint Green[],
1531 gint Blue[],
1532 ifunptr get_pixel,
1533 GError **error)
1534 {
1535 gint B;
1536 gint RWidth, RHeight;
1537 gint Resolution;
1538 gint ColorMapSize;
1539 gint i;
1540
1541 ColorMapSize = 1 << BitsPerPixel;
1542
1543 RWidth = Width = GWidth;
1544 RHeight = Height = GHeight;
1545
1546 Resolution = BitsPerPixel;
1547
1548 /*
1549 * Calculate number of bits we are expecting
1550 */
1551 CountDown = (long) Width *(long) Height;
1552
1553 /*
1554 * Indicate which pass we are on (if interlace)
1555 */
1556 Pass = 0;
1557
1558 /*
1559 * Set up the current x and y position
1560 */
1561 curx = cury = 0;
1562
1563 /*
1564 * Write the Magic header
1565 */
1566 if (! put_string (output, gif89 ? "GIF89a" : "GIF87a", error))
1567 return FALSE;
1568
1569 /*
1570 * Write out the screen width and height
1571 */
1572 if (! put_word (output, RWidth, error) ||
1573 ! put_word (output, RHeight, error))
1574 return FALSE;
1575
1576 /*
1577 * Indicate that there is a global color map
1578 */
1579 B = 0x80; /* Yes, there is a color map */
1580
1581 /*
1582 * OR in the resolution
1583 */
1584 B |= (Resolution - 1) << 5;
1585
1586 /*
1587 * OR in the Bits per Pixel
1588 */
1589 B |= (BitsPerPixel - 1);
1590
1591 /*
1592 * Write it out
1593 */
1594 if (! put_byte (output, B, error))
1595 return FALSE;
1596
1597 /*
1598 * Write out the Background color
1599 */
1600 if (! put_byte (output, Background, error))
1601 return FALSE;
1602
1603 /*
1604 * Byte of 0's (future expansion)
1605 */
1606 if (! put_byte (output, 0, error))
1607 return FALSE;
1608
1609 /*
1610 * Write out the Global Color Map
1611 */
1612 for (i = 0; i < ColorMapSize; i++)
1613 {
1614 if (! put_byte (output, Red[i], error) ||
1615 ! put_byte (output, Green[i], error) ||
1616 ! put_byte (output, Blue[i], error))
1617 return FALSE;
1618 }
1619
1620 return TRUE;
1621 }
1622
1623
1624 static gboolean
gif_encode_graphic_control_ext(GOutputStream * output,int Disposal,int Delay89,int NumFramesInImage,int GWidth,int GHeight,int Transparent,int BitsPerPixel,ifunptr get_pixel,GError ** error)1625 gif_encode_graphic_control_ext (GOutputStream *output,
1626 int Disposal,
1627 int Delay89,
1628 int NumFramesInImage,
1629 int GWidth,
1630 int GHeight,
1631 int Transparent,
1632 int BitsPerPixel,
1633 ifunptr get_pixel,
1634 GError **error)
1635 {
1636 Width = GWidth;
1637 Height = GHeight;
1638
1639 /*
1640 * Calculate number of bits we are expecting
1641 */
1642 CountDown = (long) Width *(long) Height;
1643
1644 /*
1645 * Indicate which pass we are on (if interlace)
1646 */
1647 Pass = 0;
1648
1649 /*
1650 * Set up the current x and y position
1651 */
1652 curx = cury = 0;
1653
1654 /*
1655 * Write out extension for transparent color index, if necessary.
1656 */
1657 if ( (Transparent >= 0) || (NumFramesInImage > 1) )
1658 {
1659 /* Extension Introducer - fixed. */
1660 if (! put_byte (output, '!', error))
1661 return FALSE;
1662
1663 /* Graphic Control Label - fixed. */
1664 if (! put_byte (output, 0xf9, error))
1665 return FALSE;
1666
1667 /* Block Size - fixed. */
1668 if (! put_byte (output, 4, error))
1669 return FALSE;
1670
1671 /* Packed Fields - XXXdddut (d=disposal, u=userInput, t=transFlag) */
1672 /* s8421 */
1673 if (! put_byte (output,
1674 ((Transparent >= 0) ? 0x01 : 0x00) /* TRANSPARENCY */
1675
1676 /* DISPOSAL */
1677 | ((NumFramesInImage > 1) ? (Disposal << 2) : 0x00 ),
1678 /* 0x03 or 0x01 build frames cumulatively */
1679 /* 0x02 clears frame before drawing */
1680 /* 0x00 'don't care' */
1681
1682 error))
1683 return FALSE;
1684
1685 if (! put_word (output, Delay89, error))
1686 return FALSE;
1687
1688 if (! put_byte (output, Transparent, error) ||
1689 ! put_byte (output, 0, error))
1690 return FALSE;
1691 }
1692
1693 return TRUE;
1694 }
1695
1696
1697 static gboolean
gif_encode_image_data(GOutputStream * output,int GWidth,int GHeight,int GInterlace,int BitsPerPixel,ifunptr get_pixel,gint offset_x,gint offset_y,GError ** error)1698 gif_encode_image_data (GOutputStream *output,
1699 int GWidth,
1700 int GHeight,
1701 int GInterlace,
1702 int BitsPerPixel,
1703 ifunptr get_pixel,
1704 gint offset_x,
1705 gint offset_y,
1706 GError **error)
1707 {
1708 gint LeftOfs, TopOfs;
1709 gint InitCodeSize;
1710
1711 Interlace = GInterlace;
1712
1713 Width = GWidth;
1714 Height = GHeight;
1715 LeftOfs = (gint) offset_x;
1716 TopOfs = (gint) offset_y;
1717
1718 /*
1719 * Calculate number of bits we are expecting
1720 */
1721 CountDown = (long) Width * (long) Height;
1722
1723 /*
1724 * Indicate which pass we are on (if interlace)
1725 */
1726 Pass = 0;
1727
1728 /*
1729 * The initial code size
1730 */
1731 if (BitsPerPixel <= 1)
1732 InitCodeSize = 2;
1733 else
1734 InitCodeSize = BitsPerPixel;
1735
1736 /*
1737 * Set up the current x and y position
1738 */
1739 curx = cury = 0;
1740
1741 /*
1742 * Write an Image separator
1743 */
1744 if (! put_byte (output, ',', error))
1745 return FALSE;
1746
1747 /*
1748 * Write the Image header
1749 */
1750
1751 if (! put_word (output, LeftOfs, error) ||
1752 ! put_word (output, TopOfs, error) ||
1753 ! put_word (output, Width, error) ||
1754 ! put_word (output, Height, error))
1755 return FALSE;
1756
1757 /*
1758 * Write out whether or not the image is interlaced
1759 */
1760 if (Interlace)
1761 {
1762 if (! put_byte (output, 0x40, error))
1763 return FALSE;
1764 }
1765 else
1766 {
1767 if (! put_byte (output, 0x00, error))
1768 return FALSE;
1769 }
1770
1771 /*
1772 * Write out the initial code size
1773 */
1774 if (! put_byte (output, InitCodeSize, error))
1775 return FALSE;
1776
1777 /*
1778 * Go and actually compress the data
1779 */
1780 if (! compress (output, InitCodeSize + 1, get_pixel, error))
1781 return FALSE;
1782
1783 /*
1784 * Write out a Zero-length packet (to end the series)
1785 */
1786 if (! put_byte (output, 0, error))
1787 return FALSE;
1788
1789 #if 0
1790 /***************************/
1791 Interlace = GInterlace;
1792 Width = GWidth;
1793 Height = GHeight;
1794 LeftOfs = TopOfs = 0;
1795
1796 CountDown = (long) Width *(long) Height;
1797 Pass = 0;
1798 /*
1799 * The initial code size
1800 */
1801 if (BitsPerPixel <= 1)
1802 InitCodeSize = 2;
1803 else
1804 InitCodeSize = BitsPerPixel;
1805 /*
1806 * Set up the current x and y position
1807 */
1808 curx = cury = 0;
1809 #endif
1810
1811 return TRUE;
1812 }
1813
1814
1815 static gboolean
gif_encode_close(GOutputStream * output,GError ** error)1816 gif_encode_close (GOutputStream *output,
1817 GError **error)
1818 {
1819 /*
1820 * Write the GIF file terminator
1821 */
1822 if (! put_byte (output, ';', error))
1823 return FALSE;
1824
1825 /*
1826 * And close the file
1827 */
1828 return g_output_stream_close (output, NULL, error);
1829 }
1830
1831
1832 static gboolean
gif_encode_loop_ext(GOutputStream * output,guint num_loops,GError ** error)1833 gif_encode_loop_ext (GOutputStream *output,
1834 guint num_loops,
1835 GError **error)
1836 {
1837 return (put_byte (output, 0x21, error) &&
1838 put_byte (output, 0xff, error) &&
1839 put_byte (output, 0x0b, error) &&
1840 put_string (output, "NETSCAPE2.0", error) &&
1841 put_byte (output, 0x03, error) &&
1842 put_byte (output, 0x01, error) &&
1843 put_word (output, num_loops, error) &&
1844 put_byte (output, 0x00, error));
1845
1846 /* NOTE: num_loops == 0 means 'loop infinitely' */
1847 }
1848
1849
1850 static gboolean
gif_encode_comment_ext(GOutputStream * output,const gchar * comment,GError ** error)1851 gif_encode_comment_ext (GOutputStream *output,
1852 const gchar *comment,
1853 GError **error)
1854 {
1855 if (!comment || !*comment)
1856 return TRUE;
1857
1858 if (strlen (comment) > 240)
1859 {
1860 g_printerr ("GIF: warning:"
1861 "comment too large - comment block not written.\n");
1862 return TRUE;
1863 }
1864
1865 return (put_byte (output, 0x21, error) &&
1866 put_byte (output, 0xfe, error) &&
1867 put_byte (output, strlen (comment), error) &&
1868 put_string (output, comment, error) &&
1869 put_byte (output, 0x00, error));
1870 }
1871
1872
1873 /*
1874 * Write stuff to the GIF file
1875 */
1876 static gboolean
put_byte(GOutputStream * output,guchar b,GError ** error)1877 put_byte (GOutputStream *output,
1878 guchar b,
1879 GError **error)
1880 {
1881 return g_data_output_stream_put_byte (G_DATA_OUTPUT_STREAM (output),
1882 b, NULL, error);
1883 }
1884
1885 static gboolean
put_word(GOutputStream * output,gint w,GError ** error)1886 put_word (GOutputStream *output,
1887 gint w,
1888 GError **error)
1889 {
1890 return g_data_output_stream_put_int16 (G_DATA_OUTPUT_STREAM (output),
1891 w, NULL, error);
1892 }
1893
1894 static gboolean
put_string(GOutputStream * output,const gchar * s,GError ** error)1895 put_string (GOutputStream *output,
1896 const gchar *s,
1897 GError **error)
1898 {
1899 return g_data_output_stream_put_string (G_DATA_OUTPUT_STREAM (output),
1900 s, NULL, error);
1901 }
1902
1903
1904 /***************************************************************************
1905 *
1906 * GIFCOMPR.C - GIF Image compression routines
1907 *
1908 * Lempel-Ziv compression based on 'compress'. GIF modifications by
1909 * David Rowley (mgardi@watdcsu.waterloo.edu)
1910 *
1911 ***************************************************************************/
1912
1913 /*
1914 * General DEFINEs
1915 */
1916
1917 #define GIF_BITS 12
1918
1919 #define HSIZE 5003 /* 80% occupancy */
1920
1921 /*
1922 * GIF Image compression - modified 'compress'
1923 *
1924 * Based on: compress.c - File compression ala IEEE Computer, June 1984.
1925 *
1926 * By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
1927 * Jim McKie (decvax!mcvax!jim)
1928 * Steve Davies (decvax!vax135!petsd!peora!srd)
1929 * Ken Turkowski (decvax!decwrl!turtlevax!ken)
1930 * James A. Woods (decvax!ihnp4!ames!jaw)
1931 * Joe Orost (decvax!vax135!petsd!joe)
1932 *
1933 */
1934
1935 static gint n_bits; /* number of bits/code */
1936 static gint maxbits = GIF_BITS; /* user settable max # bits/code */
1937 static gint maxcode; /* maximum code, given n_bits */
1938 static gint maxmaxcode = (gint) 1 << GIF_BITS; /* should NEVER generate this code */
1939 #ifdef COMPATIBLE /* But wrong! */
1940 #define MAXCODE(Mn_bits) ((gint) 1 << (Mn_bits) - 1)
1941 #else /*COMPATIBLE */
1942 #define MAXCODE(Mn_bits) (((gint) 1 << (Mn_bits)) - 1)
1943 #endif /*COMPATIBLE */
1944
1945 static glong htab[HSIZE];
1946 static gushort codetab[HSIZE];
1947 #define HashTabOf(i) htab[i]
1948 #define CodeTabOf(i) codetab[i]
1949
1950 static const gint hsize = HSIZE; /* the original reason for this being
1951 variable was "for dynamic table sizing",
1952 but since it was never actually changed
1953 I made it const --Adam. */
1954
1955 static gint free_ent = 0; /* first unused entry */
1956
1957 /*
1958 * block compression parameters -- after all codes are used up,
1959 * and compression rate changes, start over.
1960 */
1961 static gint clear_flg = 0;
1962
1963 static gint offset;
1964 static glong in_count = 1; /* length of input */
1965 static glong out_count = 0; /* # of codes output (for debugging) */
1966
1967 /*
1968 * compress stdin to stdout
1969 *
1970 * Algorithm: use open addressing double hashing (no chaining) on the
1971 * prefix code / next character combination. We do a variant of Knuth's
1972 * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
1973 * secondary probe. Here, the modular division first probe is gives way
1974 * to a faster exclusive-or manipulation. Also do block compression with
1975 * an adaptive reset, whereby the code table is cleared when the compression
1976 * ratio decreases, but after the table fills. The variable-length output
1977 * codes are re-sized at this point, and a special CLEAR code is generated
1978 * for the decompressor. Late addition: construct the table according to
1979 * file size for noticeable speed improvement on small files. Please direct
1980 * questions about this implementation to ames!jaw.
1981 */
1982
1983 static gint g_init_bits;
1984
1985 static gint ClearCode;
1986 static gint EOFCode;
1987
1988 static gulong cur_accum;
1989 static gint cur_bits;
1990
1991 static gulong masks[] =
1992 {
1993 0x0000, 0x0001, 0x0003, 0x0007,
1994 0x000F, 0x001F, 0x003F, 0x007F,
1995 0x00FF, 0x01FF, 0x03FF, 0x07FF,
1996 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF,
1997 0xFFFF
1998 };
1999
2000
2001 static gboolean
compress(GOutputStream * output,gint init_bits,ifunptr ReadValue,GError ** error)2002 compress (GOutputStream *output,
2003 gint init_bits,
2004 ifunptr ReadValue,
2005 GError **error)
2006 {
2007 if (FALSE)
2008 return no_compress (output, init_bits, ReadValue, error);
2009 else if (FALSE)
2010 return rle_compress (output, init_bits, ReadValue, error);
2011 else
2012 return normal_compress (output, init_bits, ReadValue, error);
2013 }
2014
2015 static gboolean
no_compress(GOutputStream * output,gint init_bits,ifunptr ReadValue,GError ** error)2016 no_compress (GOutputStream *output,
2017 gint init_bits,
2018 ifunptr ReadValue,
2019 GError **error)
2020 {
2021 glong fcode;
2022 gint i /* = 0 */ ;
2023 gint c;
2024 gint ent;
2025 gint hsize_reg;
2026 gint hshift;
2027
2028 /*
2029 * Set up the globals: g_init_bits - initial number of bits
2030 */
2031 g_init_bits = init_bits;
2032
2033 cur_bits = 0;
2034 cur_accum = 0;
2035
2036 /*
2037 * Set up the necessary values
2038 */
2039 offset = 0;
2040 out_count = 0;
2041 clear_flg = 0;
2042 in_count = 1;
2043
2044 ClearCode = (1 << (init_bits - 1));
2045 EOFCode = ClearCode + 1;
2046 free_ent = ClearCode + 2;
2047
2048
2049 /* Had some problems here... should be okay now. --Adam */
2050 n_bits = g_init_bits;
2051 maxcode = MAXCODE (n_bits);
2052
2053
2054 char_init();
2055
2056 ent = gif_next_pixel (ReadValue);
2057
2058 hshift = 0;
2059 for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L)
2060 ++hshift;
2061 hshift = 8 - hshift; /* set hash code range bound */
2062
2063 hsize_reg = hsize;
2064 cl_hash ((glong) hsize_reg); /* clear hash table */
2065
2066 if (! output_code (output, (gint) ClearCode, error))
2067 return FALSE;
2068
2069 while ((c = gif_next_pixel (ReadValue)) != EOF)
2070 {
2071 ++in_count;
2072
2073 fcode = (long) (((long) c << maxbits) + ent);
2074 i = (((gint) c << hshift) ^ ent); /* xor hashing */
2075
2076 if (! output_code (output, (gint) ent, error))
2077 return FALSE;
2078
2079 ++out_count;
2080 ent = c;
2081
2082 if (free_ent < maxmaxcode)
2083 {
2084 CodeTabOf (i) = free_ent++; /* code -> hashtable */
2085 HashTabOf (i) = fcode;
2086 }
2087 else
2088 {
2089 if (! cl_block (output, error))
2090 return FALSE;
2091 }
2092 }
2093
2094 /*
2095 * Put out the final code.
2096 */
2097 if (! output_code (output, (gint) ent, error))
2098 return FALSE;
2099
2100 ++out_count;
2101
2102 if (! output_code (output, (gint) EOFCode, error))
2103 return FALSE;
2104
2105 return TRUE;
2106 }
2107
2108 static gboolean
rle_compress(GOutputStream * output,gint init_bits,ifunptr ReadValue,GError ** error)2109 rle_compress (GOutputStream *output,
2110 gint init_bits,
2111 ifunptr ReadValue,
2112 GError **error)
2113 {
2114 glong fcode;
2115 gint i /* = 0 */ ;
2116 gint c, last;
2117 gint ent;
2118 gint disp;
2119 gint hsize_reg;
2120 gint hshift;
2121
2122 /*
2123 * Set up the globals: g_init_bits - initial number of bits
2124 */
2125 g_init_bits = init_bits;
2126
2127 cur_bits = 0;
2128 cur_accum = 0;
2129
2130 /*
2131 * Set up the necessary values
2132 */
2133 offset = 0;
2134 out_count = 0;
2135 clear_flg = 0;
2136 in_count = 1;
2137
2138 ClearCode = (1 << (init_bits - 1));
2139 EOFCode = ClearCode + 1;
2140 free_ent = ClearCode + 2;
2141
2142
2143 /* Had some problems here... should be okay now. --Adam */
2144 n_bits = g_init_bits;
2145 maxcode = MAXCODE (n_bits);
2146
2147
2148 char_init ();
2149
2150 last = ent = gif_next_pixel (ReadValue);
2151
2152 hshift = 0;
2153 for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L)
2154 ++hshift;
2155 hshift = 8 - hshift; /* set hash code range bound */
2156
2157 hsize_reg = hsize;
2158 cl_hash ((glong) hsize_reg); /* clear hash table */
2159
2160 if (! output_code (output, (gint) ClearCode, error))
2161 return FALSE;
2162
2163
2164 while ((c = gif_next_pixel (ReadValue)) != EOF)
2165 {
2166 ++in_count;
2167
2168 fcode = (long) (((long) c << maxbits) + ent);
2169 i = (((gint) c << hshift) ^ ent); /* xor hashing */
2170
2171
2172 if (last == c) {
2173 if (HashTabOf (i) == fcode)
2174 {
2175 ent = CodeTabOf (i);
2176 continue;
2177 }
2178 else if ((long) HashTabOf (i) < 0) /* empty slot */
2179 goto nomatch;
2180 disp = hsize_reg - i; /* secondary hash (after G. Knott) */
2181 if (i == 0)
2182 disp = 1;
2183 probe:
2184 if ((i -= disp) < 0)
2185 i += hsize_reg;
2186
2187 if (HashTabOf (i) == fcode)
2188 {
2189 ent = CodeTabOf (i);
2190 continue;
2191 }
2192 if ((long) HashTabOf (i) > 0)
2193 goto probe;
2194 }
2195 nomatch:
2196 if (! output_code (output, (gint) ent, error))
2197 return FALSE;
2198
2199 ++out_count;
2200 last = ent = c;
2201 if (free_ent < maxmaxcode)
2202 {
2203 CodeTabOf (i) = free_ent++; /* code -> hashtable */
2204 HashTabOf (i) = fcode;
2205 }
2206 else
2207 {
2208 if (! cl_block (output, error))
2209 return FALSE;
2210 }
2211 }
2212
2213 /*
2214 * Put out the final code.
2215 */
2216 if (! output_code (output, (gint) ent, error))
2217 return FALSE;
2218
2219 ++out_count;
2220
2221 if (! output_code (output, (gint) EOFCode, error))
2222 return FALSE;
2223
2224 return TRUE;
2225 }
2226
2227 static gboolean
normal_compress(GOutputStream * output,gint init_bits,ifunptr ReadValue,GError ** error)2228 normal_compress (GOutputStream *output,
2229 gint init_bits,
2230 ifunptr ReadValue,
2231 GError **error)
2232 {
2233 glong fcode;
2234 gint i /* = 0 */ ;
2235 gint c;
2236 gint ent;
2237 gint disp;
2238 gint hsize_reg;
2239 gint hshift;
2240
2241 /*
2242 * Set up the globals: g_init_bits - initial number of bits
2243 */
2244 g_init_bits = init_bits;
2245
2246 cur_bits = 0;
2247 cur_accum = 0;
2248
2249 /*
2250 * Set up the necessary values
2251 */
2252 offset = 0;
2253 out_count = 0;
2254 clear_flg = 0;
2255 in_count = 1;
2256
2257 ClearCode = (1 << (init_bits - 1));
2258 EOFCode = ClearCode + 1;
2259 free_ent = ClearCode + 2;
2260
2261
2262 /* Had some problems here... should be okay now. --Adam */
2263 n_bits = g_init_bits;
2264 maxcode = MAXCODE (n_bits);
2265
2266
2267 char_init();
2268
2269 ent = gif_next_pixel (ReadValue);
2270
2271 hshift = 0;
2272 for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L)
2273 ++hshift;
2274 hshift = 8 - hshift; /* set hash code range bound */
2275
2276 hsize_reg = hsize;
2277 cl_hash ((glong) hsize_reg); /* clear hash table */
2278
2279 if (! output_code (output, (gint) ClearCode, error))
2280 return FALSE;
2281
2282
2283 while ((c = gif_next_pixel (ReadValue)) != EOF)
2284 {
2285 ++in_count;
2286
2287 fcode = (long) (((long) c << maxbits) + ent);
2288 i = (((gint) c << hshift) ^ ent); /* xor hashing */
2289
2290 if (HashTabOf (i) == fcode)
2291 {
2292 ent = CodeTabOf (i);
2293 continue;
2294 }
2295 else if ((long) HashTabOf (i) < 0) /* empty slot */
2296 goto nomatch;
2297 disp = hsize_reg - i; /* secondary hash (after G. Knott) */
2298 if (i == 0)
2299 disp = 1;
2300 probe:
2301 if ((i -= disp) < 0)
2302 i += hsize_reg;
2303
2304 if (HashTabOf (i) == fcode)
2305 {
2306 ent = CodeTabOf (i);
2307 continue;
2308 }
2309 if ((long) HashTabOf (i) > 0)
2310 goto probe;
2311 nomatch:
2312 if (! output_code (output, (gint) ent, error))
2313 return FALSE;
2314
2315 ++out_count;
2316 ent = c;
2317 if (free_ent < maxmaxcode)
2318 {
2319 CodeTabOf (i) = free_ent++; /* code -> hashtable */
2320 HashTabOf (i) = fcode;
2321 }
2322 else
2323 {
2324 if (! cl_block (output, error))
2325 return FALSE;
2326 }
2327 }
2328
2329 /*
2330 * Put out the final code.
2331 */
2332 if (! output_code (output, (gint) ent, error))
2333 return FALSE;
2334
2335 ++out_count;
2336
2337 if (! output_code (output, (gint) EOFCode, error))
2338 return FALSE;
2339
2340 return TRUE;
2341 }
2342
2343
2344 /*****************************************************************
2345 * TAG( output )
2346 *
2347 * Output the given code.
2348 * Inputs:
2349 * code: A n_bits-bit integer. If == -1, then EOF. This assumes
2350 * that n_bits =< (long)wordsize - 1.
2351 * Outputs:
2352 * Outputs code to the file.
2353 * Assumptions:
2354 * Chars are 8 bits long.
2355 * Algorithm:
2356 * Maintain a GIF_BITS character long buffer (so that 8 codes will
2357 * fit in it exactly). Use the VAX insv instruction to insert each
2358 * code in turn. When the buffer fills up empty it and start over.
2359 */
2360
2361 static gboolean
output_code(GOutputStream * output,gint code,GError ** error)2362 output_code (GOutputStream *output,
2363 gint code,
2364 GError **error)
2365 {
2366 cur_accum &= masks[cur_bits];
2367
2368 if (cur_bits > 0)
2369 cur_accum |= ((long) code << cur_bits);
2370 else
2371 cur_accum = code;
2372
2373 cur_bits += n_bits;
2374
2375 while (cur_bits >= 8)
2376 {
2377 if (! char_out (output, (guchar) (cur_accum & 0xff), error))
2378 return FALSE;
2379
2380 cur_accum >>= 8;
2381 cur_bits -= 8;
2382 }
2383
2384 /*
2385 * If the next entry is going to be too big for the code size,
2386 * then increase it, if possible.
2387 */
2388 if (free_ent > maxcode || clear_flg)
2389 {
2390 if (clear_flg)
2391 {
2392 maxcode = MAXCODE (n_bits = g_init_bits);
2393 clear_flg = 0;
2394 }
2395 else
2396 {
2397 ++n_bits;
2398 if (n_bits == maxbits)
2399 maxcode = maxmaxcode;
2400 else
2401 maxcode = MAXCODE (n_bits);
2402 }
2403 }
2404
2405 if (code == EOFCode)
2406 {
2407 /*
2408 * At EOF, write the rest of the buffer.
2409 */
2410 while (cur_bits > 0)
2411 {
2412 if (! char_out (output, (guchar) (cur_accum & 0xff), error))
2413 return FALSE;
2414
2415 cur_accum >>= 8;
2416 cur_bits -= 8;
2417 }
2418
2419 if (! char_flush (output, error))
2420 return FALSE;
2421 }
2422
2423 return TRUE;
2424 }
2425
2426 /*
2427 * Clear out the hash table
2428 */
2429 static gboolean
cl_block(GOutputStream * output,GError ** error)2430 cl_block (GOutputStream *output,
2431 GError **error) /* table clear for block compress */
2432 {
2433 cl_hash ((glong) hsize);
2434 free_ent = ClearCode + 2;
2435 clear_flg = 1;
2436
2437 return output_code (output, (gint) ClearCode, error);
2438 }
2439
2440 static void
cl_hash(glong hsize)2441 cl_hash (glong hsize) /* reset code table */
2442 {
2443 glong *htab_p = htab + hsize;
2444
2445 long i;
2446 long m1 = -1;
2447
2448 i = hsize - 16;
2449 do
2450 { /* might use Sys V memset(3) here */
2451 *(htab_p - 16) = m1;
2452 *(htab_p - 15) = m1;
2453 *(htab_p - 14) = m1;
2454 *(htab_p - 13) = m1;
2455 *(htab_p - 12) = m1;
2456 *(htab_p - 11) = m1;
2457 *(htab_p - 10) = m1;
2458 *(htab_p - 9) = m1;
2459 *(htab_p - 8) = m1;
2460 *(htab_p - 7) = m1;
2461 *(htab_p - 6) = m1;
2462 *(htab_p - 5) = m1;
2463 *(htab_p - 4) = m1;
2464 *(htab_p - 3) = m1;
2465 *(htab_p - 2) = m1;
2466 *(htab_p - 1) = m1;
2467 htab_p -= 16;
2468 }
2469 while ((i -= 16) >= 0);
2470
2471 for (i += 16; i > 0; --i)
2472 *--htab_p = m1;
2473 }
2474
2475
2476 /******************************************************************************
2477 * GIF Specific routines
2478 ******************************************************************************/
2479
2480 /*
2481 * Number of characters so far in this 'packet'
2482 */
2483 static int a_count;
2484
2485 /*
2486 * Set up the 'byte output' routine
2487 */
2488 static void
char_init(void)2489 char_init (void)
2490 {
2491 a_count = 0;
2492 }
2493
2494 /*
2495 * Define the storage for the packet accumulator
2496 */
2497 static char accum[256];
2498
2499 /*
2500 * Add a character to the end of the current packet, and if it is 254
2501 * characters, flush the packet to disk.
2502 */
2503 static gboolean
char_out(GOutputStream * output,gint c,GError ** error)2504 char_out (GOutputStream *output,
2505 gint c,
2506 GError **error)
2507 {
2508 accum[a_count++] = c;
2509
2510 if (a_count >= 254)
2511 return char_flush (output, error);
2512
2513 return TRUE;
2514 }
2515
2516 /*
2517 * Flush the packet to disk, and reset the accumulator
2518 */
2519 static gboolean
char_flush(GOutputStream * output,GError ** error)2520 char_flush (GOutputStream *output,
2521 GError **error)
2522 {
2523 if (a_count > 0)
2524 {
2525 if (! put_byte (output, a_count, error))
2526 return FALSE;
2527
2528 if (! g_output_stream_write_all (output, accum, a_count,
2529 NULL, NULL, error))
2530 return FALSE;
2531
2532 a_count = 0;
2533 }
2534
2535 return TRUE;
2536 }
2537
2538
2539 /* Save interface functions */
2540
2541 static void
comment_entry_callback(GtkTextBuffer * buffer)2542 comment_entry_callback (GtkTextBuffer *buffer)
2543 {
2544 GtkTextIter start_iter;
2545 GtkTextIter end_iter;
2546 gchar *text;
2547
2548 gtk_text_buffer_get_bounds (buffer, &start_iter, &end_iter);
2549 text = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE);
2550
2551 #define MAX_COMMENT 240
2552
2553 if (strlen (text) > MAX_COMMENT)
2554 {
2555 /* translators: the %d is *always* 240 here */
2556 g_message (_("The default comment is limited to %d characters."),
2557 MAX_COMMENT);
2558
2559 gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, MAX_COMMENT - 1);
2560 gtk_text_buffer_get_end_iter (buffer, &end_iter);
2561
2562 /* this calls us recursivaly, but in the else branch
2563 */
2564 gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
2565 }
2566 else
2567 {
2568 g_free (globalcomment);
2569 globalcomment = g_strdup (text);
2570 comment_was_edited = TRUE;
2571 }
2572
2573 g_free (text);
2574 }
2575