file-gif-save.c 70.4 KB
Newer Older
1
/* GIF exporting file filter for GIMP
Elliot Lee's avatar
Elliot Lee committed
2
 *
3
 *    Copyright
4 5 6
 *    - Adam D. Moss
 *    - Peter Mattis
 *    - Spencer Kimball
Elliot Lee's avatar
Elliot Lee committed
7
 *
8
 *      Based around original GIF code by David Koblas.
Elliot Lee's avatar
Elliot Lee committed
9 10
 *
 *
11
 * Version 4.1.0 - 2003-06-16
12
 *                        Adam D. Moss - <adam@gimp.org> <adam@foxbox.org>
Elliot Lee's avatar
Elliot Lee committed
13 14 15 16 17 18 19 20
 */
/*
 * This filter uses code taken from the "giftopnm" and "ppmtogif" programs
 *    which are part of the "netpbm" package.
 */
/*
 *  "The Graphics Interchange Format(c) is the Copyright property of
 *  CompuServe Incorporated.  GIF(sm) is a Service Mark property of
21
 *  CompuServe Incorporated."
Elliot Lee's avatar
Elliot Lee committed
22
 */
23 24 25 26 27
/* Copyright notice for GIF code from which this plugin was long ago     */
/* derived (David Koblas has granted permission to relicense):           */
/* +-------------------------------------------------------------------+ */
/* | Copyright 1990, 1991, 1993, David Koblas.  (koblas@extra.com)     | */
/* +-------------------------------------------------------------------+ */
Elliot Lee's avatar
Elliot Lee committed
28

29
#include "config.h"
30

Elliot Lee's avatar
Elliot Lee committed
31
#include <string.h>
32

33 34
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
35

36
#include "libgimp/stdplugins-intl.h"
Elliot Lee's avatar
Elliot Lee committed
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 46 47 48
/* uncomment the line below for a little debugging info */
/* #define GIFDEBUG yesplease */


49 50 51 52 53 54
enum
{
  DISPOSE_STORE_VALUE_COLUMN,
  DISPOSE_STORE_LABEL_COLUMN
};

55 56 57 58 59 60
enum
{
  DISPOSE_UNSPECIFIED,
  DISPOSE_COMBINE,
  DISPOSE_REPLACE
};
61

Elliot Lee's avatar
Elliot Lee committed
62 63
typedef struct
{
64 65 66 67 68 69 70
  gint     interlace;
  gint     save_comment;
  gint     loop;
  gint     default_delay;
  gint     default_dispose;
  gboolean always_use_default_delay;
  gboolean always_use_default_dispose;
71
  gboolean as_animation;
Elliot Lee's avatar
Elliot Lee committed
72 73 74 75 76
} GIFSaveVals;


/* Declare some local functions.
 */
77 78
static void     query                  (void);
static void     run                    (const gchar      *name,
79 80 81 82
                                        gint              nparams,
                                        const GimpParam  *param,
                                        gint             *nreturn_vals,
                                        GimpParam       **return_vals);
83

84
static gboolean  save_image            (GFile            *file,
85 86
                                        gint32            image_ID,
                                        gint32            drawable_ID,
87 88
                                        gint32            orig_image_ID,
                                        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 97
static gboolean save_dialog            (gint32            image_ID);
static void     comment_entry_callback (GtkTextBuffer    *buffer);
Elliot Lee's avatar
Elliot Lee committed
98 99


100 101 102
static gboolean  comment_was_edited = FALSE;
static gchar    *globalcomment      = NULL;
static gint      Interlace; /* For compression code */
Elliot Lee's avatar
Elliot Lee committed
103 104


105
const GimpPlugInInfo PLUG_IN_INFO =
Elliot Lee's avatar
Elliot Lee committed
106
{
107 108 109 110
  NULL,  /* init_proc  */
  NULL,  /* quit_proc  */
  query, /* query_proc */
  run,   /* run_proc   */
Elliot Lee's avatar
Elliot Lee committed
111 112 113 114
};

static GIFSaveVals gsvals =
{
115 116 117
  FALSE,   /* interlace                            */
  TRUE,    /* save comment                         */
  TRUE,    /* loop infinitely                      */
Elliot Lee's avatar
Elliot Lee committed
118
  100,     /* default_delay between frames (100ms) */
119 120
  0,       /* default_dispose = "don't care"       */
  FALSE,   /* don't always use default_delay       */
121 122
  FALSE,   /* don't always use default_dispose     */
  FALSE    /* as_animation                         */
Elliot Lee's avatar
Elliot Lee committed
123 124 125 126 127
};


MAIN ()

128 129
#define COMMON_SAVE_ARGS \
    { GIMP_PDB_INT32,    "run-mode",        "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, \
130 131
    { GIMP_PDB_IMAGE,    "image",           "Image to export" }, \
    { GIMP_PDB_DRAWABLE, "drawable",        "Drawable to export" }, \
132 133
    { GIMP_PDB_STRING,   "uri",             "The name of the URI to export the image in" }, \
    { 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 138
    { GIMP_PDB_INT32,    "default-dispose", "(animated gif) Default disposal type (0=`don't care`, 1=combine, 2=replace)" }

Elliot Lee's avatar
Elliot Lee committed
139
static void
140
query (void)
Elliot Lee's avatar
Elliot Lee committed
141
{
142
  static const GimpParamDef save_args[] =
Elliot Lee's avatar
Elliot Lee committed
143
  {
144 145 146 147 148 149
    COMMON_SAVE_ARGS
  };

  static const GimpParamDef save2_args[] =
  {
    COMMON_SAVE_ARGS,
150
    { GIMP_PDB_INT32,    "as-animation", "Export GIF as animation?" },
151 152
    { GIMP_PDB_INT32,    "force-delay", "(animated gif) Use specified delay for all frames?" },
    { GIMP_PDB_INT32,    "force-dispose", "(animated gif) Use specified disposal for all frames?" }
Elliot Lee's avatar
Elliot Lee committed
153 154
  };

155
  gimp_install_procedure (SAVE_PROC,
156 157
                          "exports files in Compuserve GIF file format",
                          "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 162 163
                          "transparent.  When run non-interactively, the "
                          "value for the comment is taken from the "
                          "'gimp-comment' parasite.  ",
Elliot Lee's avatar
Elliot Lee committed
164 165 166
                          "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas",
                          "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas",
                          "1995-1997",
167
                          N_("GIF image"),
168
                          "INDEXED*, GRAY*",
Sven Neumann's avatar
Sven Neumann committed
169
                          GIMP_PLUGIN,
170
                          G_N_ELEMENTS (save_args), 0,
Elliot Lee's avatar
Elliot Lee committed
171 172
                          save_args, NULL);

173
  gimp_install_procedure (SAVE2_PROC,
174 175
                          "exports files in Compuserve GIF file format",
                          "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 179 180 181 182 183 184 185 186 187 188 189 190 191
                          "file and give the 'as-animation' parameter "
                          "as TRUE.  The plug-in will interpret <50% "
                          "alpha as transparent.  When run "
                          "non-interactively, the value for the comment "
                          "is taken from the 'gimp-comment' parasite.  ",
                          "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas",
                          "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas",
                          "1995-1997",
                          N_("GIF image"),
                          "INDEXED*, GRAY*",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (save2_args), 0,
                          save2_args, NULL);

192 193
  gimp_register_file_handler_mime (SAVE_PROC, "image/gif");
  gimp_register_save_handler (SAVE_PROC, "gif", "");
194 195 196
  gimp_register_file_handler_uri (SAVE_PROC);

  gimp_register_file_handler_uri (SAVE2_PROC);
Elliot Lee's avatar
Elliot Lee committed
197 198 199
}

static void
200 201 202 203 204
run (const gchar      *name,
     gint              nparams,
     const GimpParam  *param,
     gint             *nreturn_vals,
     GimpParam       **return_vals)
Elliot Lee's avatar
Elliot Lee committed
205
{
206 207 208 209 210
  static GimpParam   values[2];
  GimpRunMode        run_mode;
  GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
  GimpExportReturn   export = GIMP_EXPORT_CANCEL;
  GError            *error  = NULL;
Elliot Lee's avatar
Elliot Lee committed
211

212
  INIT_I18N ();
213 214
  gegl_init (NULL, NULL);

215 216
  run_mode = param[0].data.d_int32;

217 218
  *nreturn_vals = 1;
  *return_vals  = values;
219

Sven Neumann's avatar
Sven Neumann committed
220 221
  values[0].type          = GIMP_PDB_STATUS;
  values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
Elliot Lee's avatar
Elliot Lee committed
222

223 224
  if (strcmp (name, SAVE_PROC)  == 0 ||
      strcmp (name, SAVE2_PROC) == 0)
Elliot Lee's avatar
Elliot Lee committed
225
    {
226 227 228 229 230
      GFile  *file;
      gint32  image_ID;
      gint32  orig_image_ID;
      gint32  sanitized_image_ID = 0;
      gint32  drawable_ID;
231

232 233
      image_ID    = orig_image_ID = param[1].data.d_int32;
      drawable_ID = param[2].data.d_int32;
234
      file        = g_file_new_for_uri (param[3].data.d_string);
Elliot Lee's avatar
Elliot Lee committed
235

236 237
      if (run_mode == GIMP_RUN_INTERACTIVE ||
          run_mode == GIMP_RUN_WITH_LAST_VALS)
238 239
        gimp_ui_init (PLUG_IN_BINARY, FALSE);

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 247
           * a duplicate image to delete later.
           */
248 249
          sanitized_image_ID = image_ID;

250 251 252 253 254
          switch (run_mode)
            {
            case GIMP_RUN_INTERACTIVE:
              /*  Possibly retrieve data  */
              gimp_get_data (SAVE_PROC, &gsvals);
Elliot Lee's avatar
Elliot Lee committed
255

256 257
              /*  First acquire information with a dialog  */
              if (! save_dialog (image_ID))
258 259 260 261
                {
                  gimp_image_delete (sanitized_image_ID);
                  status = GIMP_PDB_CANCEL;
                }
262 263 264 265
              break;

            case GIMP_RUN_NONINTERACTIVE:
              /*  Make sure all the arguments are there!  */
266
              if (nparams != 9 && nparams != 12)
267 268 269 270 271 272 273 274 275 276
                {
                  status = GIMP_PDB_CALLING_ERROR;
                }
              else
                {
                  gsvals.interlace       = (param[5].data.d_int32) ? TRUE : FALSE;
                  gsvals.save_comment    = TRUE;  /*  no way to to specify that through the PDB  */
                  gsvals.loop            = (param[6].data.d_int32) ? TRUE : FALSE;
                  gsvals.default_delay   = param[7].data.d_int32;
                  gsvals.default_dispose = param[8].data.d_int32;
Ed J's avatar
Ed J committed
277 278
                  if (nparams == 12)
                    {
279 280
                      gsvals.as_animation               = (param[9].data.d_int32) ? TRUE : FALSE;
                      gsvals.always_use_default_delay   = (param[10].data.d_int32) ? TRUE : FALSE;
Ed J's avatar
Ed J committed
281 282
                      gsvals.always_use_default_dispose = (param[11].data.d_int32) ? TRUE : FALSE;
                    }
283 284
                }
              break;
Elliot Lee's avatar
Elliot Lee committed
285

286 287 288 289
            case GIMP_RUN_WITH_LAST_VALS:
              /*  Possibly retrieve data  */
              gimp_get_data (SAVE_PROC, &gsvals);
              break;
Elliot Lee's avatar
Elliot Lee committed
290

291
            default:
292
              break;
293
            }
294
        }
Elliot Lee's avatar
Elliot Lee committed
295

296 297
      if (status == GIMP_PDB_SUCCESS)
        {
298 299 300 301 302 303 304 305
          /* Create an exportable image based on the export options */
          switch (run_mode)
            {
            case GIMP_RUN_INTERACTIVE:
            case GIMP_RUN_WITH_LAST_VALS:
                {
                  GimpExportCapabilities capabilities =
                    GIMP_EXPORT_CAN_HANDLE_INDEXED |
306
                    GIMP_EXPORT_CAN_HANDLE_GRAY    |
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
                    GIMP_EXPORT_CAN_HANDLE_ALPHA;

                  if (gsvals.as_animation)
                    capabilities |= GIMP_EXPORT_CAN_HANDLE_LAYERS;

                  export = gimp_export_image (&image_ID, &drawable_ID, "GIF",
                                              capabilities);

                  if (export == GIMP_EXPORT_CANCEL)
                    {
                      values[0].data.d_status = GIMP_PDB_CANCEL;
                      if (sanitized_image_ID)
                        gimp_image_delete (sanitized_image_ID);
                      return;
                    }
                }
              break;
            default:
              break;
            }

          /* Write the image to file */
329
          if (save_image (file,
330 331
                          image_ID, drawable_ID, orig_image_ID,
                          &error))
332
            {
333 334 335 336 337 338
              /*  Store psvals data  */
              gimp_set_data (SAVE_PROC, &gsvals, sizeof (GIFSaveVals));
            }
          else
            {
              status = GIMP_PDB_EXECUTION_ERROR;
339
            }
340 341

          gimp_image_delete (sanitized_image_ID);
342
        }
Elliot Lee's avatar
Elliot Lee committed
343

344 345
      if (export == GIMP_EXPORT_EXPORT)
        gimp_image_delete (image_ID);
346 347

      g_object_unref (file);
348
    }
349

350 351 352 353 354
  if (status != GIMP_PDB_SUCCESS && error)
    {
      *nreturn_vals = 2;
      values[1].type          = GIMP_PDB_STRING;
      values[1].data.d_string = error->message;
355
    }
Elliot Lee's avatar
Elliot Lee committed
356

357 358
  values[0].data.d_status = status;
}
Elliot Lee's avatar
Elliot Lee committed
359 360 361 362


/* ppmtogif.c - read a portable pixmap and produce a GIF file
**
Marc Lehmann's avatar
Marc Lehmann committed
363 364
** Based on GIFENCOD by David Rowley <mgardi@watdscu.waterloo.edu>. A
** Lempel-Ziv compression based on "compress".
Elliot Lee's avatar
Elliot Lee committed
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
**
** Modified by Marcel Wijkstra <wijkstra@fwi.uva.nl>
**
**
** Copyright (C) 1989 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
**
** The Graphics Interchange Format(c) is the Copyright property of
** CompuServe Incorporated.  GIF(sm) is a Service Mark property of
** CompuServe Incorporated.
*/

#define MAXCOLORS 256

/*
 * Pointer to function returning an int
 */
388 389
typedef gint (* ifunptr) (gint x,
                          gint y);
Elliot Lee's avatar
Elliot Lee committed
390 391


392 393 394 395
static gint find_unused_ia_color           (const guchar  *pixels,
                                            gint           numpixels,
                                            gint           num_indices,
                                            gint          *colors);
396

397 398 399
static void special_flatten_indexed_alpha  (guchar        *pixels,
                                            gint           transparent,
                                            gint           numpixels);
400

401 402 403 404 405
static gint colors_to_bpp                  (gint           colors);
static gint bpp_to_colors                  (gint           bpp);
static gint get_pixel                      (gint           x,
                                            gint           y);
static gint gif_next_pixel                 (ifunptr        getpixel);
406 407
static void bump_pixel                     (void);

408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
static gboolean gif_encode_header              (GOutputStream  *output,
                                                gboolean        gif89,
                                                gint            width,
                                                gint            height,
                                                gint            background,
                                                gint            bpp,
                                                gint           *red,
                                                gint           *green,
                                                gint           *blue,
                                                ifunptr         get_pixel,
                                                GError        **error);
static gboolean gif_encode_graphic_control_ext (GOutputStream  *output,
                                                gint            disposal,
                                                gint            delay89,
                                                gint            n_frames,
                                                gint            width,
                                                gint            height,
                                                gint            transparent,
                                                gint            bpp,
                                                ifunptr         get_pixel,
                                                GError        **error);
static gboolean gif_encode_image_data          (GOutputStream  *output,
                                                gint            width,
                                                gint            height,
                                                gint            interlace,
                                                gint            bpp,
                                                ifunptr         get_pixel,
                                                gint            offset_x,
                                                gint            offset_y,
                                                GError        **error);
static gboolean gif_encode_close               (GOutputStream  *output,
                                                GError        **error);
static gboolean gif_encode_loop_ext            (GOutputStream  *output,
                                                guint           n_loops,
                                                GError        **error);
static gboolean gif_encode_comment_ext         (GOutputStream  *output,
                                                const gchar    *comment,
                                                GError        **error);
Elliot Lee's avatar
Elliot Lee committed
446

447
static gint     rowstride;
448
static guchar  *pixels;
449 450
static gint     cur_progress;
static gint     max_progress;
Elliot Lee's avatar
Elliot Lee committed
451

452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
static gboolean compress        (GOutputStream *output,
                                 gint           init_bits,
                                 ifunptr        ReadValue,
                                 GError        **error);
static gboolean no_compress     (GOutputStream *output,
                                 gint           init_bits,
                                 ifunptr        ReadValue,
                                 GError        **error);
static gboolean rle_compress    (GOutputStream *output,
                                 gint           init_bits,
                                 ifunptr        ReadValue,
                                 GError        **error);
static gboolean normal_compress (GOutputStream *output,
                                 gint           init_bits,
                                 ifunptr        ReadValue,
                                 GError        **error);

static gboolean put_byte        (GOutputStream  *output,
                                 guchar          b,
                                 GError        **error);
static gboolean put_word        (GOutputStream  *output,
                                 gint            w,
                                 GError        **error);
static gboolean put_string      (GOutputStream  *output,
                                 const gchar    *s,
                                 GError        **error);
static gboolean output_code     (GOutputStream  *output,
                                 gint            code,
                                 GError        **error);
static gboolean cl_block        (GOutputStream  *output,
                                 GError        **error);
static void     cl_hash         (glong           hsize);

static void     char_init       (void);
static gboolean char_out        (GOutputStream  *output,
                                 gint            c,
                                 GError        **error);
static gboolean char_flush      (GOutputStream  *output,
                                 GError        **error);
Elliot Lee's avatar
Elliot Lee committed
491 492


493
static gint
494
find_unused_ia_color (const guchar *pixels,
495 496 497
                      gint          numpixels,
                      gint          num_indices,
                      gint         *colors)
Elliot Lee's avatar
Elliot Lee committed
498 499
{
  gboolean ix_used[256];
500
  gint     i;
Elliot Lee's avatar
Elliot Lee committed
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
Elliot Lee's avatar
Elliot Lee committed
506

507
  for (i = 0; i < 256; i++)
508
    ix_used[i] = FALSE;
Elliot Lee's avatar
Elliot Lee committed
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 524
          return i;
        }
525
    }
Elliot Lee's avatar
Elliot Lee committed
526

527 528 529 530
  /* Couldn't find an unused color index within the number of bits per
   * pixel we wanted.  Will have to increment the number of colors in
   * the image and assign a transparent pixel there.
   */
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;
Elliot Lee's avatar
Elliot Lee committed
544 545 546
}


547
static void
Elliot Lee's avatar
Elliot Lee committed
548
special_flatten_indexed_alpha (guchar *pixels,
549 550
                               gint    transparent,
                               gint    numpixels)
Elliot Lee's avatar
Elliot Lee committed
551 552 553
{
  guint32 i;

554 555 556
  /* Each transparent pixel in the image is mapped to a uniform value
   * for encoding, if image already has <=255 colors
   */
557

558
  if (transparent == -1) /* tough, no indices left for the trans. index */
Elliot Lee's avatar
Elliot Lee committed
559
    {
560 561
      for (i = 0; i < numpixels; i++)
        pixels[i] = pixels[i * 2];
Elliot Lee's avatar
Elliot Lee committed
562 563 564
    }
  else  /* make transparent */
    {
565 566 567 568
      for (i = 0; i < numpixels; i++)
        {
          if (! (pixels[i * 2 + 1] & 128))
            {
569
              pixels[i] = (guchar) transparent;
570 571 572 573 574 575
            }
          else
            {
              pixels[i] = pixels[i * 2];
            }
        }
Elliot Lee's avatar
Elliot Lee committed
576 577 578 579
    }
}


580 581
static gint
parse_ms_tag (const gchar *str)
Elliot Lee's avatar
Elliot Lee committed
582
{
583
  gint sum    = 0;
Elliot Lee's avatar
Elliot Lee committed
584 585 586
  gint offset = 0;
  gint length;

587
  length = strlen (str);
Elliot Lee's avatar
Elliot Lee committed
588

589
 find_another_bra:
Manish Singh's avatar
Manish Singh committed
590

591
  while ((offset < length) && (str[offset] != '('))
Elliot Lee's avatar
Elliot Lee committed
592
    offset++;
593

594
  if (offset >= length)
Elliot Lee's avatar
Elliot Lee committed
595 596
    return(-1);

597
  if (! g_ascii_isdigit (str[++offset]))
Manish Singh's avatar
Manish Singh committed
598
    goto find_another_bra;
Elliot Lee's avatar
Elliot Lee committed
599 600 601 602 603 604 605

  do
    {
      sum *= 10;
      sum += str[offset] - '0';
      offset++;
    }
606
  while ((offset < length) && (g_ascii_isdigit (str[offset])));
Elliot Lee's avatar
Elliot Lee committed
607

608
  if (length - offset <= 2)
Elliot Lee's avatar
Elliot Lee committed
609 610
    return(-3);

611 612
  if ((g_ascii_toupper (str[offset])     != 'M') ||
      (g_ascii_toupper (str[offset + 1]) != 'S'))
613
    return -4;
Elliot Lee's avatar
Elliot Lee committed
614

615
  return sum;
Elliot Lee's avatar
Elliot Lee committed
616 617 618
}


619 620
static gint
parse_disposal_tag (const gchar *str)
Elliot Lee's avatar
Elliot Lee committed
621 622 623 624
{
  gint offset = 0;
  gint length;

625
  length = strlen (str);
Elliot Lee's avatar
Elliot Lee committed
626

627
  while ((offset + 9) <= length)
Elliot Lee's avatar
Elliot Lee committed
628
    {
629 630 631 632 633 634
      if (strncmp (&str[offset], "(combine)", 9) == 0)
        return 0x01;

      if (strncmp (&str[offset], "(replace)", 9) == 0)
        return 0x02 ;

Elliot Lee's avatar
Elliot Lee committed
635 636 637
      offset++;
    }

638
  return gsvals.default_dispose;
Elliot Lee's avatar
Elliot Lee committed
639 640 641
}


642
static GimpPDBStatusType
643
sanity_check (GFile        *file,
644
              gint32       *image_ID,
645
              GimpRunMode   run_mode,
646
              GError      **error)
Elliot Lee's avatar
Elliot Lee committed
647
{
648 649 650 651 652
  gint32 *layers;
  gint    nlayers;
  gint    image_width;
  gint    image_height;
  gint    i;
Elliot Lee's avatar
Elliot Lee committed
653

654 655
  image_width  = gimp_image_width (*image_ID);
  image_height = gimp_image_height (*image_ID);
Elliot Lee's avatar
Elliot Lee committed
656

657 658
  if (image_width > G_MAXUSHORT || image_height > G_MAXUSHORT)
    {
659
      g_set_error (error, 0, 0,
660
                   _("Unable to export '%s'.  "
661 662
                   "The GIF file format does not support images that are "
                   "more than %d pixels wide or tall."),
663
                   gimp_file_get_utf8_name (file), G_MAXUSHORT);
664 665

      return GIMP_PDB_EXECUTION_ERROR;
666
    }
Elliot Lee's avatar
Elliot Lee committed
667 668 669 670

  /*** Iterate through the layers to make sure they're all ***/
  /*** within the bounds of the image                      ***/

671 672
  *image_ID = gimp_image_duplicate (*image_ID);
  layers = gimp_image_get_layers (*image_ID, &nlayers);
673

674
  for (i = 0; i < nlayers; i++)
Elliot Lee's avatar
Elliot Lee committed
675
    {
676 677 678
      gint offset_x;
      gint offset_y;

Elliot Lee's avatar
Elliot Lee committed
679 680
      gimp_drawable_offsets (layers[i], &offset_x, &offset_y);

681 682 683 684
      if (offset_x < 0 ||
          offset_y < 0 ||
          offset_x + gimp_drawable_width (layers[i]) > image_width ||
          offset_y + gimp_drawable_height (layers[i]) > image_height)
685 686 687 688 689 690
        {
          g_free (layers);

          /* Image has illegal bounds - ask the user what it wants to do */

          /* Do the crop if we can't talk to the user, or if we asked
691 692
           * the user and they said yes.
           */
693 694
          if ((run_mode == GIMP_RUN_NONINTERACTIVE) || bad_bounds_dialog ())
            {
695
              gimp_image_crop (*image_ID, image_width, image_height, 0, 0);
696
              return GIMP_PDB_SUCCESS;
697 698 699
            }
          else
            {
700
              gimp_image_delete (*image_ID);
701
              return GIMP_PDB_CANCEL;
702 703
            }
        }
Elliot Lee's avatar
Elliot Lee committed
704 705 706 707
    }

  g_free (layers);

708
  return GIMP_PDB_SUCCESS;
Elliot Lee's avatar
Elliot Lee committed
709 710 711
}


712
static gboolean
713 714 715 716 717
save_image (GFile   *file,
            gint32   image_ID,
            gint32   drawable_ID,
            gint32   orig_image_ID,
            GError **error)
Elliot Lee's avatar
Elliot Lee committed
718
{
719 720 721
  GeglBuffer    *buffer;
  GimpImageType  drawable_type;
  const Babl    *format = NULL;
722
  GOutputStream *output;
723 724 725 726 727
  gint           Red[MAXCOLORS];
  gint           Green[MAXCOLORS];
  gint           Blue[MAXCOLORS];
  guchar        *cmap;
  guint          rows, cols;
728 729 730
  gint           BitsPerPixel;
  gint           liberalBPP = 0;
  gint           useBPP     = 0;
731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748
  gint           colors;
  gint           i;
  gint           transparent;
  gint           offset_x, offset_y;

  gint32        *layers;
  gint           nlayers;

  gboolean       is_gif89 = FALSE;

  gint           Delay89;
  gint           Disposal;
  gchar         *layer_name;

  GimpRGB        background;
  guchar         bgred, bggreen, bgblue;
  guchar         bgindex = 0;
  guint          best_error = 0xFFFFFFFF;
749

750
  /* Save the comment back to the ImageID, if appropriate */
751
  if (globalcomment != NULL && comment_was_edited)
752
    {
753 754 755 756 757
      GimpParasite *parasite;

      parasite = gimp_parasite_new ("gimp-comment",
                                    GIMP_PARASITE_PERSISTENT,
                                    strlen (globalcomment) + 1,
758
                                    (gpointer) globalcomment);
759 760
      gimp_image_attach_parasite (orig_image_ID, parasite);
      gimp_parasite_free (parasite);
761 762
    }

763 764 765
  /* The GIF spec says 7bit ASCII for the comment block. */
  if (gsvals.save_comment && globalcomment)
    {
766
      const gchar *c = globalcomment;
767 768 769 770
      gint         len;

      for (len = strlen (c); len; c++, len--)
        {
771
          if ((guchar) *c > 127)
772
            {
773
              g_message (_("The GIF format only supports comments in "
774 775 776 777 778 779 780 781 782
                           "7bit ASCII encoding. No comment is saved."));

              g_free (globalcomment);
              globalcomment = NULL;

              break;
            }
        }
    }
Elliot Lee's avatar
Elliot Lee committed
783 784

  /* get a list of layers for this image_ID */
785
  layers = gimp_image_get_layers (image_ID, &nlayers);
Elliot Lee's avatar
Elliot Lee committed
786 787 788

  drawable_type = gimp_drawable_type (layers[0]);

789 790 791 792
  /* If the image has multiple layers (i.e. will be animated), a
   * comment, or transparency, then it must be encoded as a GIF89a
   * file, not a vanilla GIF87a.
   */
793
  if (nlayers > 1)
Elliot Lee's avatar
Elliot Lee committed
794
    is_gif89 = TRUE;
795 796

  if (gsvals.save_comment)
Elliot Lee's avatar
Elliot Lee committed
797 798 799 800
    is_gif89 = TRUE;

  switch (drawable_type)
    {
Sven Neumann's avatar
Sven Neumann committed
801
    case GIMP_INDEXEDA_IMAGE:
Elliot Lee's avatar
Elliot Lee committed
802
      is_gif89 = TRUE;
Sven Neumann's avatar
Sven Neumann committed
803
    case GIMP_INDEXED_IMAGE:
804
      cmap = gimp_image_get_colormap (image_ID, &colors);
805

806
      gimp_context_get_background (&background);
807
      gimp_rgb_get_uchar (&background, &bgred, &bggreen, &bgblue);
808

Elliot Lee's avatar
Elliot Lee committed
809
      for (i = 0; i < colors; i++)
810 811 812 813 814
        {
          Red[i]   = *cmap++;
          Green[i] = *cmap++;
          Blue[i]  = *cmap++;
        }
Elliot Lee's avatar
Elliot Lee committed
815
      for ( ; i < 256; i++)
816 817 818 819 820
        {
          Red[i]   = bgred;
          Green[i] = bggreen;
          Blue[i]  = bgblue;
        }
Elliot Lee's avatar
Elliot Lee committed
821
      break;
Sven Neumann's avatar
Sven Neumann committed
822
    case GIMP_GRAYA_IMAGE:
Elliot Lee's avatar
Elliot Lee committed
823
      is_gif89 = TRUE;
Sven Neumann's avatar
Sven Neumann committed
824
    case GIMP_GRAY_IMAGE:
825
      colors = 256;                   /* FIXME: Not ideal. */
Elliot Lee's avatar
Elliot Lee committed
826
      for ( i = 0;  i < 256; i++)
827 828 829
        {
          Red[i] = Green[i] = Blue[i] = i;
        }
830 831 832 833 834

      if (drawable_type == GIMP_GRAYA_IMAGE)
        format = babl_format ("Y'A u8");
      else
        format = babl_format ("Y' u8");
Elliot Lee's avatar
Elliot Lee committed
835 836 837
      break;

    default:
838
      g_message (_("Cannot export RGB color images. Convert to "
839
                   "indexed color or grayscale first."));
Elliot Lee's avatar
Elliot Lee committed
840 841
      return FALSE;
    }
842

843 844

  /* find earliest index in palette which is closest to the background
845 846 847
   * color, and ATTEMPT to use that as the GIF's default background
   * color.
   */
848 849 850 851
  for (i = 255; i >= 0; --i)
    {
      guint local_error = 0;

852
      local_error += (Red[i]   - bgred)   * (Red[i]   - bgred);
853
      local_error += (Green[i] - bggreen) * (Green[i] - bggreen);
854
      local_error += (Blue[i]  - bgblue)  * (Blue[i]  - bgblue);
855 856 857 858 859 860

      if (local_error <= best_error)
        {
          bgindex = i;
          best_error = local_error;
        }
861 862
    }

Elliot Lee's avatar
Elliot Lee committed
863

864
  /* init the progress meter */
865
  gimp_progress_init_printf (_("Exporting '%s'"),
866
                             gimp_file_get_utf8_name (file));
867 868


Elliot Lee's avatar
Elliot Lee committed
869
  /* open the destination file for writing */
870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
  output = G_OUTPUT_STREAM (g_file_replace (file,
                                            NULL, FALSE, G_FILE_CREATE_NONE,
                                            NULL, error));
  if (output)
    {
      GDataOutputStream *data_output;

      data_output = g_data_output_stream_new (output);
      g_object_unref (output);

      g_data_output_stream_set_byte_order (data_output,
                                           G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN);

      output = G_OUTPUT_STREAM (data_output);
    }
  else
Elliot Lee's avatar
Elliot Lee committed
886 887 888 889 890 891 892 893
    {
      return FALSE;
    }


  /* write the GIFheader */

  if (colors < 256)
894
    {
895 896 897 898 899
      /* we keep track of how many bits we promised to have in
       * liberalBPP, so that we don't accidentally come under this
       * when doing clever transparency stuff where we can re-use
       * wasted indices.
       */
900
      liberalBPP = BitsPerPixel =
901
        colors_to_bpp (colors + ((drawable_type==GIMP_INDEXEDA_IMAGE) ? 1 : 0));
902
    }
Elliot Lee's avatar
Elliot Lee committed
903
  else
904
    {
905
      liberalBPP = BitsPerPixel =
906
        colors_to_bpp (256);
907

908 909
      if (drawable_type == GIMP_INDEXEDA_IMAGE)
        {
910
          g_printerr ("GIF: Too many colors?\n");
911
        }
912
    }
Elliot Lee's avatar
Elliot Lee committed
913

914 915
  cols = gimp_image_width (image_ID);
  rows = gimp_image_height (image_ID);
916
  Interlace = gsvals.interlace;
917 918 919 920
  if (! gif_encode_header (output, is_gif89, cols, rows, bgindex,
                           BitsPerPixel, Red, Green, Blue, get_pixel,
                           error))
    return FALSE;
Elliot Lee's avatar
Elliot Lee committed
921 922


923 924 925
  /* If the image has multiple layers it'll be made into an animated
   * GIF, so write out the infinite-looping extension
   */
Elliot Lee's avatar
Elliot Lee committed
926
  if ((nlayers > 1) && (gsvals.loop))
927 928
    if (! gif_encode_loop_ext (output, 0, error))
      return FALSE;
Elliot Lee's avatar
Elliot Lee committed
929 930

  /* Write comment extension - mustn't be written before the looping ext. */
931
  if (gsvals.save_comment && globalcomment)
Elliot Lee's avatar
Elliot Lee committed
932
    {
933 934
      if (! gif_encode_comment_ext (output, globalcomment, error))
        return FALSE;
Elliot Lee's avatar
Elliot Lee committed
935 936 937 938 939 940
    }


  /*** Now for each layer in the image, save an image in a compound GIF ***/
  /************************************************************************/

941 942
  cur_progress = 0;
  max_progress = nlayers * rows;
Elliot Lee's avatar
Elliot Lee committed
943

944
  for (i = nlayers - 1; i >= 0; i--, cur_progress = (nlayers - i) * rows)
Elliot Lee's avatar
Elliot Lee committed
945 946
    {
      drawable_type = gimp_drawable_type (layers[i]);
947
      buffer = gimp_drawable_get_buffer (layers[i]);
Elliot Lee's avatar
Elliot Lee committed
948
      gimp_drawable_offsets (layers[i], &offset_x, &offset_y);
949 950 951
      cols = gimp_drawable_width (layers[i]);
      rows = gimp_drawable_height (layers[i]);
      rowstride = cols;
952

953 954 955
      pixels = g_new (guchar, (cols * rows *
                               (((drawable_type == GIMP_INDEXEDA_IMAGE) ||
                                 (drawable_type == GIMP_GRAYA_IMAGE)) ? 2 : 1)));
Elliot Lee's avatar
Elliot Lee committed
956

957 958 959
      gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, cols, rows), 1.0,
                       format, pixels,
                       GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
Elliot Lee's avatar
Elliot Lee committed
960 961

      /* sort out whether we need to do transparency jiggery-pokery */
962 963
      if ((drawable_type == GIMP_INDEXEDA_IMAGE) ||
          (drawable_type == GIMP_GRAYA_IMAGE))
964 965
        {
          /* Try to find an entry which isn't actually used in the
966 967
           * image, for a transparency index.
           */
968 969

          transparent =
970
            find_unused_ia_color (pixels,
971
                                   cols * rows,
972 973
                                   bpp_to_colors (colors_to_bpp (colors)),
                                   &colors);
974 975

          special_flatten_indexed_alpha (pixels,
976
                                         transparent,
977
                                         cols * rows);
978
        }
Elliot Lee's avatar
Elliot Lee committed
979
      else
980 981 982
        {
          transparent = -1;
        }
983

984
      BitsPerPixel = colors_to_bpp (colors);
Elliot Lee's avatar
Elliot Lee committed
985

986
      if (BitsPerPixel != liberalBPP)
987
        {
988 989 990 991
          /* We were able to re-use an index within the existing
           * bitspace, whereas the estimate in the header was
           * pessimistic but still needs to be upheld...
           */
992
#ifdef GIFDEBUG
993
          static gboolean onceonly = FALSE;
994

995 996 997
          if (! onceonly)
            {
              g_warning ("Promised %d bpp, pondered writing chunk with %d bpp!",
998 999
                         liberalBPP, BitsPerPixel);
              onceonly = TRUE;
1000
            }
1001
#endif
1002
        }
1003

1004
      useBPP = (BitsPerPixel > liberalBPP) ? BitsPerPixel : liberalBPP;
1005

Elliot Lee's avatar
Elliot Lee committed
1006
      if (is_gif89)
1007 1008 1009
        {
          if (i > 0 && ! gsvals.always_use_default_dispose)
            {
1010
              layer_name = gimp_item_get_name (layers[i - 1]);
1011 1012 1013 1014
              Disposal = parse_disposal_tag (layer_name);
              g_free (layer_name);
            }
          else
1015 1016 1017
            {
              Disposal = gsvals.default_dispose;
            }
1018

1019
          layer_name = gimp_item_get_name (layers[i]);
1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
          Delay89 = parse_ms_tag (layer_name);
          g_free (layer_name);

          if (Delay89 < 0 || gsvals.always_use_default_delay)
            Delay89 = (gsvals.default_delay + 5) / 10;
          else
            Delay89 = (Delay89 + 5) / 10;

          /* don't allow a CPU-sucking completely 0-delay looping anim */
          if ((nlayers > 1) && gsvals.loop && (Delay89 == 0))
            {
              static gboolean onceonly = FALSE;

1033
              if (! onceonly)
1034 1035
                {
                  g_message (_("Delay inserted to prevent evil "
1036
                               "CPU-sucking animation."));
1037 1038
                  onceonly = TRUE;
                }
1039

1040 1041 1042
              Delay89 = 1;
            }

1043 1044 1045 1046 1047 1048 1049 1050
          if (! gif_encode_graphic_control_ext (output,
                                                Disposal, Delay89, nlayers,
                                                cols, rows,
                                                transparent,
                                                useBPP,
                                                get_pixel,
                                                error))
            return FALSE;
1051 1052
        }

1053 1054 1055 1056 1057 1058 1059 1060
      if (! gif_encode_image_data (output, cols, rows,
                                   (rows > 4) ? gsvals.interlace : 0,
                                   useBPP,
                                   get_pixel,
                                   offset_x, offset_y,
                                   error))
        return FALSE;

1061
      gimp_progress_update (1.0);
1062

1063
      g_object_unref (buffer);
Elliot Lee's avatar
Elliot Lee committed
1064

1065 1066
      g_free (pixels);
    }
1067

1068
  g_free (layers);
Elliot Lee's avatar
Elliot Lee committed
1069

1070 1071
  if (! gif_encode_close (output, error))
    return FALSE;
Elliot Lee's avatar
Elliot Lee committed
1072 1073 1074 1075 1076

  return TRUE;
}

static gboolean
1077
bad_bounds_dialog (void)
Elliot Lee's avatar
Elliot Lee committed
1078
{
1079
  GtkWidget *dialog;
1080
  gboolean   crop;
Elliot Lee's avatar
Elliot Lee committed
1081

Sven Neumann's avatar
Sven Neumann committed
1082 1083
  dialog = gtk_message_dialog_new (NULL, 0,
                                   GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE,
1084
                                   _("The image you are trying to export as a "
Sven Neumann's avatar
Sven Neumann committed
1085 1086
                                     "GIF contains layers which extend beyond "
                                     "the actual borders of the image."));
1087

Sven Neumann's avatar
Sven Neumann committed
1088
  gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1089 1090
                          _("_Cancel"), GTK_RESPONSE_CANCEL,
                          _("Cr_op"),   GTK_RESPONSE_OK,
Sven Neumann's avatar
Sven Neumann committed
1091
                          NULL);
Elliot Lee's avatar
Elliot Lee committed
1092

1093
  gimp_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
1094 1095 1096
                                           GTK_RESPONSE_OK,
                                           GTK_RESPONSE_CANCEL,
                                           -1);
1097

1098 1099
  gimp_window_set_transient (GTK_WINDOW (dialog));

Sven Neumann's avatar
Sven Neumann committed
1100 1101 1102 1103 1104
  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
                                            _("The GIF file format does not "
                                              "allow this.  You may choose "
                                              "whether to crop all of the "
                                              "layers to the image borders, "
1105
                                              "or cancel this export."));
Elliot Lee's avatar
Elliot Lee committed
1106

1107
  gtk_widget_show (dialog);
Elliot Lee's avatar
Elliot Lee committed
1108

Sven Neumann's avatar
Sven Neumann committed
1109
  crop = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK);
1110

1111
  gtk_widget_destroy (dialog);
Elliot Lee's avatar
Elliot Lee committed
1112

1113
  return crop;
Elliot Lee's avatar
Elliot Lee committed
1114 1115
}

1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147
static GtkWidget *
file_gif_toggle_button_init (GtkBuilder  *builder,
                             const gchar *name,
                             gboolean     initial_value,
                             gboolean    *value_pointer)
{
  GtkWidget *toggle = NULL;

  toggle = GTK_WIDGET (gtk_builder_get_object (builder, name));
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), initial_value);
  g_signal_connect (toggle, "toggled",
                    G_CALLBACK (gimp_toggle_button_update),
                    value_pointer);

  return toggle;
}

static GtkWidget *
file_gif_spin_button_int_init (GtkBuilder  *builder,
                               const gchar *name,
                               int          initial_value,
                               int         *value_pointer)
{
  GtkWidget     *spin_button = NULL;
  GtkAdjustment *adjustment  = NULL;

  spin_button = GTK_WIDGET (gtk_builder_get_object (builder, name));

  adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spin_button));
  gtk_adjustment_set_value (adjustment, initial_value);
  g_signal_connect (adjustment, "value-changed",
                    G_CALLBACK (gimp_int_adjustment_update),
1148
                    value_pointer);
1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181

  return spin_button;
}

static void
file_gif_combo_box_int_update_value (GtkComboBox *combo,
                                     gint        *value)
{
  GtkTreeIter iter;

  if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
    {
      gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (combo)),
                          &iter,
                          DISPOSE_STORE_VALUE_COLUMN, value,
                          -1);
    }
}

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,
                             ...)
{
  GtkWidget    *combo  = NULL;
  GtkListStore *store  = NULL;
  const gchar  *label  = NULL;
  gint          value  = 0;
  GtkTreeIter   iter   = { 0, };
1182
  va_list       values;
1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214

  combo = GTK_WIDGET (gtk_builder_get_object (builder, name));
  store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo)));

  /* Populate */
  va_start (values, first_value);
  for (label = first_label, value = first_value;
       label;
       label = va_arg (values, const gchar *), value = va_arg (values, gint))
    {
      gtk_list_store_append (store, &iter);
      gtk_list_store_set (store, &iter,
                          DISPOSE_STORE_VALUE_COLUMN, value,
                          DISPOSE_STORE_LABEL_COLUMN, label,
                          -1);
    }
  va_end (values);

  /* Set initial value */
  gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (store),
                                 &iter,
                                 NULL,
                                 initial_value);
  gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter);

  /* Arrange update of value */
  g_signal_connect (combo, "changed",
                    G_CALLBACK (file_gif_combo_box_int_update_value),
                    value_pointer);

  return combo;
}
Elliot Lee's avatar
Elliot Lee committed
1215

1216
static gboolean
1217
save_dialog (gint32 image_ID)
Elliot Lee's avatar
Elliot Lee committed
1218
{
1219 1220 1221
  GtkBuilder    *builder = NULL;
  gchar         *ui_file = NULL;
  GError        *error   = NULL;
1222
  GtkWidget     *dialog;
1223
  GtkWidget     *text_view;
1224
  GtkTextBuffer *text_buffer;
1225
  GtkWidget     *toggle;
1226 1227
  GtkWidget     *frame;
  GimpParasite  *GIF2_CMNT;
1228
  gint32         nlayers;
1229
  gboolean       animation_supported = FALSE;
1230
  gboolean       run;
Elliot Lee's avatar
Elliot Lee committed
1231

1232
  g_free (gimp_image_get_layers (image_ID, &nlayers));
1233
  animation_supported = nlayers > 1;
Elliot Lee's avatar
Elliot Lee committed
1234

1235
  dialog = gimp_export_dialog_new (_("GIF"), PLUG_IN_BINARY, SAVE_PROC);
1236

1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247
  /* GtkBuilder init */
  builder = gtk_builder_new ();
  ui_file = g_build_filename (gimp_data_directory (),
                              "ui/plug-ins/plug-in-file-gif.ui",
                              NULL);
  if (! gtk_builder_add_from_file (builder, ui_file, &error))
    g_printerr (_("Error loading UI file '%s':\n%s"),
                ui_file, error ? error->message : "???");
  g_free (ui_file);

  /* Main vbox */
1248 1249 1250
  gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
                      GTK_WIDGET (gtk_builder_get_object (builder, "main-vbox")),
                      TRUE, TRUE, 0);
1251

Elliot Lee's avatar
Elliot Lee committed
1252
  /*  regular gif parameter settings  */
1253 1254 1255 1256
  file_gif_toggle_button_init (builder, "interlace",
                               gsvals.interlace, &gsvals.interlace);
  file_gif_toggle_button_init (builder, "save-comment",
                               gsvals.save_comment, &gsvals.save_comment);
1257 1258
  file_gif_toggle_button_init (builder, "as-animation",
                               gsvals.as_animation, &gsvals.as_animation);
1259

1260 1261
  text_view   = GTK_WIDGET (gtk_builder_get_object (builder, "comment"));
  text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
1262

1263 1264 1265
  if (globalcomment)
    g_free (globalcomment);

1266
  GIF2_CMNT = gimp_image_get_parasite (image_ID, "gimp-comment");
1267
  if (GIF2_CMNT)
1268 1269 1270 1271 1272
    {
      globalcomment = g_strndup (gimp_parasite_data (GIF2_CMNT),
                                 gimp_parasite_data_size (GIF2_CMNT));
      gimp_parasite_free (GIF2_CMNT);
    }
1273
  else
1274 1275 1276
    {
      globalcomment = gimp_get_default_comment ();
    }
1277

1278
  if (globalcomment)
1279
    gtk_text_buffer_set_text (text_buffer, globalcomment, -1);
1280

1281
  g_signal_connect (text_buffer, "changed",
1282 1283
                    G_CALLBACK (comment_entry_callback),
                    NULL);
1284

Elliot Lee's avatar
Elliot Lee committed
1285
  /*  additional animated gif parameter settings  */