file-gif-save.c 62.3 KB
Newer Older
1
/* GIF saving 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

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

34 35
#include <glib/gstdio.h>

36 37
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
38

39
#include "libgimp/stdplugins-intl.h"
Elliot Lee's avatar
Elliot Lee committed
40

41

42
#define SAVE_PROC      "file-gif-save"
43
#define PLUG_IN_BINARY "file-gif-save"
44 45


46 47 48 49 50 51 52 53 54
/* Define only one of these to determine which kind of gif's you would like.
 * GIF_UN means use uncompressed gifs.  These will be large, but no
 * patent problems.
 * GIF_RLE uses Run-length-encoding, which should not be covered by the
 * patent, but this is not legal advice.
 */
/* #define GIF_UN */
/* #define GIF_RLE */

55 56 57 58 59 60 61
/* uncomment the line below for a little debugging info */
/* #define GIFDEBUG yesplease */

/* Does the version of GIMP we're compiling for support
   data attachments to images?  ('Parasites') */
#define FACEHUGGERS aieee
/* PS: I know that technically facehuggers aren't parasites,
62
   the pupal-forms are.  But facehuggers are ky00te. */
63

64 65 66 67 68 69
enum
{
  DISPOSE_STORE_VALUE_COLUMN,
  DISPOSE_STORE_LABEL_COLUMN
};

70 71 72 73 74 75
enum
{
  DISPOSE_UNSPECIFIED,
  DISPOSE_COMBINE,
  DISPOSE_REPLACE
};
76

Elliot Lee's avatar
Elliot Lee committed
77 78
typedef struct
{
79 80 81 82 83 84 85
  gint     interlace;
  gint     save_comment;
  gint     loop;
  gint     default_delay;
  gint     default_dispose;
  gboolean always_use_default_delay;
  gboolean always_use_default_dispose;
86
  gboolean as_animation;
Elliot Lee's avatar
Elliot Lee committed
87 88 89 90 91
} GIFSaveVals;


/* Declare some local functions.
 */
92 93
static void     query                  (void);
static void     run                    (const gchar      *name,
94 95 96 97
                                        gint              nparams,
                                        const GimpParam  *param,
                                        gint             *nreturn_vals,
                                        GimpParam       **return_vals);
98 99

static gboolean  save_image            (const gchar      *filename,
100 101
                                        gint32            image_ID,
                                        gint32            drawable_ID,
102 103
                                        gint32            orig_image_ID,
                                        GError          **error);
104

105 106 107
static GimpPDBStatusType sanity_check  (const gchar      *filename,
                                        gint32            image_ID,
                                        GError          **error);
108
static gboolean bad_bounds_dialog      (void);
Sven Neumann's avatar
Sven Neumann committed
109

110 111
static gboolean save_dialog            (gint32            image_ID);
static void     comment_entry_callback (GtkTextBuffer    *buffer);
Elliot Lee's avatar
Elliot Lee committed
112 113


114 115
static gboolean comment_was_edited = FALSE;

116
static GimpRunMode run_mode;
117
#ifdef FACEHUGGERS
118
static GimpParasite * comment_parasite = NULL;
119
#endif
120

121
/* For compression code */
122
static gint Interlace;
Elliot Lee's avatar
Elliot Lee committed
123 124


125
const GimpPlugInInfo PLUG_IN_INFO =
Elliot Lee's avatar
Elliot Lee committed
126
{
127 128 129 130
  NULL,  /* init_proc  */
  NULL,  /* quit_proc  */
  query, /* query_proc */
  run,   /* run_proc   */
Elliot Lee's avatar
Elliot Lee committed
131 132 133 134
};

static GIFSaveVals gsvals =
{
135 136 137
  FALSE,   /* interlace                            */
  TRUE,    /* save comment                         */
  TRUE,    /* loop infinitely                      */
Elliot Lee's avatar
Elliot Lee committed
138
  100,     /* default_delay between frames (100ms) */
139 140
  0,       /* default_dispose = "don't care"       */
  FALSE,   /* don't always use default_delay       */
141 142
  FALSE,   /* don't always use default_dispose     */
  FALSE    /* as_animation                         */
Elliot Lee's avatar
Elliot Lee committed
143 144 145 146 147 148
};


MAIN ()

static void
149
query (void)
Elliot Lee's avatar
Elliot Lee committed
150
{
151
  static const GimpParamDef save_args[] =
Elliot Lee's avatar
Elliot Lee committed
152
  {
153
    { GIMP_PDB_INT32,    "run-mode",        "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
Sven Neumann's avatar
Sven Neumann committed
154 155 156
    { GIMP_PDB_IMAGE,    "image",           "Image to save" },
    { GIMP_PDB_DRAWABLE, "drawable",        "Drawable to save" },
    { GIMP_PDB_STRING,   "filename",        "The name of the file to save the image in" },
157
    { GIMP_PDB_STRING,   "raw-filename",    "The name entered" },
Sven Neumann's avatar
Sven Neumann committed
158 159
    { GIMP_PDB_INT32,    "interlace",       "Try to save as interlaced" },
    { GIMP_PDB_INT32,    "loop",            "(animated gif) loop infinitely" },
160 161
    { GIMP_PDB_INT32,    "default-delay",   "(animated gif) Default delay between framese in milliseconds" },
    { 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
162 163
  };

164
  gimp_install_procedure (SAVE_PROC,
Marc Lehmann's avatar
Marc Lehmann committed
165
                          "saves files in Compuserve GIF file format",
166
                          "Save a file in Compuserve GIF format, with "
167 168 169 170 171 172
                          "possible animation, transparency, and comment.  "
                          "To save an animation, operate on a multi-layer "
                          "file.  The plug-in will intrepret <50% alpha as "
                          "transparent.  When run non-interactively, the "
                          "value for the comment is taken from the "
                          "'gimp-comment' parasite.  ",
Elliot Lee's avatar
Elliot Lee committed
173 174 175
                          "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas",
                          "Spencer Kimball, Peter Mattis, Adam Moss, David Koblas",
                          "1995-1997",
176
                          N_("GIF image"),
177
                          "INDEXED*, GRAY*",
Sven Neumann's avatar
Sven Neumann committed
178
                          GIMP_PLUGIN,
179
                          G_N_ELEMENTS (save_args), 0,
Elliot Lee's avatar
Elliot Lee committed
180 181
                          save_args, NULL);

182 183
  gimp_register_file_handler_mime (SAVE_PROC, "image/gif");
  gimp_register_save_handler (SAVE_PROC, "gif", "");
Elliot Lee's avatar
Elliot Lee committed
184 185 186
}

static void
187 188 189 190 191
run (const gchar      *name,
     gint              nparams,
     const GimpParam  *param,
     gint             *nreturn_vals,
     GimpParam       **return_vals)
Elliot Lee's avatar
Elliot Lee committed
192
{
193 194 195
  static GimpParam  values[2];
  GimpPDBStatusType status = GIMP_PDB_SUCCESS;
  GimpExportReturn  export = GIMP_EXPORT_CANCEL;
196
  GError           *error  = NULL;
Elliot Lee's avatar
Elliot Lee committed
197 198 199

  run_mode = param[0].data.d_int32;

200 201
  INIT_I18N ();

202 203
  *nreturn_vals = 1;
  *return_vals  = values;
204

Sven Neumann's avatar
Sven Neumann committed
205 206
  values[0].type          = GIMP_PDB_STATUS;
  values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
Elliot Lee's avatar
Elliot Lee committed
207

208
  if (strcmp (name, SAVE_PROC) == 0)
Elliot Lee's avatar
Elliot Lee committed
209
    {
210 211 212 213 214
      const gchar *filename;
      gint32       image_ID;
      gint32       drawable_ID;
      gint32       orig_image_ID;

215 216
      image_ID    = orig_image_ID = param[1].data.d_int32;
      drawable_ID = param[2].data.d_int32;
217
      filename    = param[3].data.d_string;
Elliot Lee's avatar
Elliot Lee committed
218

219 220
      if (run_mode == GIMP_RUN_INTERACTIVE ||
          run_mode == GIMP_RUN_WITH_LAST_VALS)
221 222
        gimp_ui_init (PLUG_IN_BINARY, FALSE);

223 224
      status = sanity_check (filename, image_ID, &error);

225
      /* Get the export options */
226
      if (status == GIMP_PDB_SUCCESS)
227 228 229 230 231 232
        {
          switch (run_mode)
            {
            case GIMP_RUN_INTERACTIVE:
              /*  Possibly retrieve data  */
              gimp_get_data (SAVE_PROC, &gsvals);
Elliot Lee's avatar
Elliot Lee committed
233

234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
              /*  First acquire information with a dialog  */
              if (! save_dialog (image_ID))
                status = GIMP_PDB_CANCEL;
              break;

            case GIMP_RUN_NONINTERACTIVE:
              /*  Make sure all the arguments are there!  */
              if (nparams != 9)
                {
                  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;
                }
              break;
Elliot Lee's avatar
Elliot Lee committed
254

255 256 257 258
            case GIMP_RUN_WITH_LAST_VALS:
              /*  Possibly retrieve data  */
              gimp_get_data (SAVE_PROC, &gsvals);
              break;
Elliot Lee's avatar
Elliot Lee committed
259

260 261 262
            default:
              break;
            }
263
        }
Elliot Lee's avatar
Elliot Lee committed
264

265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
      /* 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 |
              GIMP_EXPORT_CAN_HANDLE_GRAY |
              GIMP_EXPORT_CAN_HANDLE_ALPHA;

            if (gsvals.as_animation)
              capabilities |= GIMP_EXPORT_CAN_HANDLE_LAYERS;

            export = gimp_export_image (&image_ID, &drawable_ID, NULL,
                                        capabilities);

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

      /* Write the image to file */
294 295 296 297 298
      if (status == GIMP_PDB_SUCCESS)
        {
          if (save_image (param[3].data.d_string,
                          image_ID, drawable_ID, orig_image_ID,
                          &error))
299
            {
300 301 302 303 304 305
              /*  Store psvals data  */
              gimp_set_data (SAVE_PROC, &gsvals, sizeof (GIFSaveVals));
            }
          else
            {
              status = GIMP_PDB_EXECUTION_ERROR;
306 307
            }
        }
Elliot Lee's avatar
Elliot Lee committed
308

309 310
      if (export == GIMP_EXPORT_EXPORT)
        gimp_image_delete (image_ID);
311
    }
312

313 314 315 316 317
  if (status != GIMP_PDB_SUCCESS && error)
    {
      *nreturn_vals = 2;
      values[1].type          = GIMP_PDB_STRING;
      values[1].data.d_string = error->message;
318
    }
Elliot Lee's avatar
Elliot Lee committed
319

320 321
  values[0].data.d_status = status;
}
Elliot Lee's avatar
Elliot Lee committed
322

323
static gchar * globalcomment = NULL;
Elliot Lee's avatar
Elliot Lee committed
324 325 326 327 328



/* ppmtogif.c - read a portable pixmap and produce a GIF file
**
Marc Lehmann's avatar
Marc Lehmann committed
329 330
** Based on GIFENCOD by David Rowley <mgardi@watdscu.waterloo.edu>. A
** Lempel-Ziv compression based on "compress".
Elliot Lee's avatar
Elliot Lee committed
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
**
** 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
 */
typedef int (*ifunptr) (int, int);

/*
 * a code_int must be able to hold 2**BITS values of type int, and also -1
 */
typedef int code_int;

#ifdef SIGNED_COMPARE_SLOW
typedef unsigned long int count_int;
typedef unsigned short int count_short;
#else /*SIGNED_COMPARE_SLOW */
typedef long int count_int;
#endif /*SIGNED_COMPARE_SLOW */



370 371 372 373
static gint find_unused_ia_colour   (const guchar *pixels,
                                     gint          numpixels,
                                     gint          num_indices,
                                     gint         *colors);
Elliot Lee's avatar
Elliot Lee committed
374

375
static void special_flatten_indexed_alpha (guchar *pixels,
376
                                           gint    transparent,
377
                                           gint    numpixels);
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
static int colors_to_bpp  (int);
static int bpp_to_colors  (int);
static int get_pixel      (int, int);
static int gif_next_pixel (ifunptr);
static void bump_pixel    (void);

static void gif_encode_header              (FILE *, gboolean, int, int, int, int,
                                            int *, int *, int *, ifunptr);
static void gif_encode_graphic_control_ext (FILE *, int, int, int, int,
                                            int, int, int, ifunptr);
static void gif_encode_image_data          (FILE *, int, int, int, int,
                                            ifunptr, gint, gint);
static void gif_encode_close               (FILE *);
static void gif_encode_loop_ext            (FILE *, guint);
static void gif_encode_comment_ext         (FILE *, const gchar *comment);
Elliot Lee's avatar
Elliot Lee committed
393

394
static gint     rowstride;
395
static guchar  *pixels;
396 397
static gint     cur_progress;
static gint     max_progress;
Elliot Lee's avatar
Elliot Lee committed
398

399
#ifdef GIF_UN
400
static void no_compress     (int, FILE *, ifunptr);
401 402
#else
#ifdef GIF_RLE
403
static void rle_compress    (int, FILE *, ifunptr);
404
#else
405
static void normal_compress (int, FILE *, ifunptr);
406 407
#endif
#endif
408 409 410 411 412 413 414 415
static void put_word   (int, FILE *);
static void compress   (int, FILE *, ifunptr);
static void output     (code_int);
static void cl_block   (void);
static void cl_hash    (count_int);
static void write_err  (void);
static void char_init  (void);
static void char_out   (int);
Elliot Lee's avatar
Elliot Lee committed
416 417 418 419
static void flush_char (void);



420
static gint
421 422 423 424
find_unused_ia_colour (const guchar *pixels,
                       gint          numpixels,
                       gint          num_indices,
                       gint         *colors)
Elliot Lee's avatar
Elliot Lee committed
425 426
{
  gboolean ix_used[256];
427
  gint i;
Elliot Lee's avatar
Elliot Lee committed
428

429
#ifdef GIFDEBUG
430
  g_printerr ("GIF: fuiac: Image claims to use %d/%d indices - finding free "
431
              "index...\n", *colors, num_indices);
432
#endif
Elliot Lee's avatar
Elliot Lee committed
433

434
  for (i = 0; i < 256; i++)
435
    ix_used[i] = FALSE;
Elliot Lee's avatar
Elliot Lee committed
436

437
  for (i = 0; i < numpixels; i++)
438
    {
439
      if (pixels[i * 2 + 1])
440
        ix_used[pixels[i * 2]] = TRUE;
441
    }
442

443
  for (i = num_indices - 1; i >= 0; i--)
444
    {
445
      if (! ix_used[i])
446
        {
447
#ifdef GIFDEBUG
448
          g_printerr ("GIF: Found unused colour index %d.\n", (int) i);
449
#endif
450 451
          return i;
        }
452
    }
Elliot Lee's avatar
Elliot Lee committed
453

454 455 456
  /* Couldn't find an unused colour index within the number of
     bits per pixel we wanted.  Will have to increment the number
     of colours in the image and assign a transparent pixel there. */
457
  if (*colors < 256)
458 459
    {
      (*colors)++;
460

461 462
      g_printerr ("GIF: 2nd pass "
                  "- Increasing bounds and using colour index %d.\n",
463
                  *colors - 1);
464
      return ((*colors) - 1);
465
    }
466

467
  g_message (_("Couldn't simply reduce colors further. Saving as opaque."));
468 469

  return -1;
Elliot Lee's avatar
Elliot Lee committed
470 471 472
}


473
static void
Elliot Lee's avatar
Elliot Lee committed
474
special_flatten_indexed_alpha (guchar *pixels,
475 476
                               gint    transparent,
                               gint    numpixels)
Elliot Lee's avatar
Elliot Lee committed
477 478 479 480 481
{
  guint32 i;

  /* Each transparent pixel in the image is mapped to a uniform value for
     encoding, if image already has <=255 colours */
482

483
  if (transparent == -1) /* tough, no indices left for the trans. index */
Elliot Lee's avatar
Elliot Lee committed
484
    {
485 486
      for (i = 0; i < numpixels; i++)
        pixels[i] = pixels[i * 2];
Elliot Lee's avatar
Elliot Lee committed
487 488 489
    }
  else  /* make transparent */
    {
490 491 492 493
      for (i = 0; i < numpixels; i++)
        {
          if (! (pixels[i * 2 + 1] & 128))
            {
494
              pixels[i] = (guchar) transparent;
495 496 497 498 499 500
            }
          else
            {
              pixels[i] = pixels[i * 2];
            }
        }
Elliot Lee's avatar
Elliot Lee committed
501 502 503 504
    }
}


505 506
static gint
parse_ms_tag (const gchar *str)
Elliot Lee's avatar
Elliot Lee committed
507 508 509 510 511 512 513
{
  gint sum = 0;
  gint offset = 0;
  gint length;

  length = strlen(str);

Manish Singh's avatar
Manish Singh committed
514 515
find_another_bra:

516
  while ((offset < length) && (str[offset] != '('))
Elliot Lee's avatar
Elliot Lee committed
517
    offset++;
518

519
  if (offset >= length)
Elliot Lee's avatar
Elliot Lee committed
520 521
    return(-1);

522
  if (! g_ascii_isdigit (str[++offset]))
Manish Singh's avatar
Manish Singh committed
523
    goto find_another_bra;
Elliot Lee's avatar
Elliot Lee committed
524 525 526 527 528 529 530

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

533
  if (length - offset <= 2)
Elliot Lee's avatar
Elliot Lee committed
534 535
    return(-3);

536 537
  if ((g_ascii_toupper (str[offset]) != 'M')
      || (g_ascii_toupper (str[offset+1]) != 'S'))
538
    return -4;
Elliot Lee's avatar
Elliot Lee committed
539

540
  return sum;
Elliot Lee's avatar
Elliot Lee committed
541 542 543
}


544 545
static gint
parse_disposal_tag (const gchar *str)
Elliot Lee's avatar
Elliot Lee committed
546 547 548 549 550 551
{
  gint offset = 0;
  gint length;

  length = strlen(str);

552
  while ((offset + 9) <= length)
Elliot Lee's avatar
Elliot Lee committed
553
    {
554 555 556 557
      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
558 559 560 561 562 563 564
      offset++;
    }

  return (gsvals.default_dispose);
}


565 566 567 568
static GimpPDBStatusType
sanity_check (const gchar  *filename,
              gint32        image_ID,
              GError      **error)
Elliot Lee's avatar
Elliot Lee committed
569
{
570 571 572 573 574
  gint32 *layers;
  gint    nlayers;
  gint    image_width;
  gint    image_height;
  gint    i;
Elliot Lee's avatar
Elliot Lee committed
575

576 577
  image_width  = gimp_image_width (image_ID);
  image_height = gimp_image_height (image_ID);
Elliot Lee's avatar
Elliot Lee committed
578

579 580
  if (image_width > G_MAXUSHORT || image_height > G_MAXUSHORT)
    {
581 582 583 584 585 586 587
      g_set_error (error, 0, 0,
                   _("Unable to save '%s'.  "
                   "The GIF file format does not support images that are "
                   "more than %d pixels wide or tall."),
                   gimp_filename_to_utf8 (filename), G_MAXUSHORT);

      return GIMP_PDB_EXECUTION_ERROR;
588
    }
Elliot Lee's avatar
Elliot Lee committed
589 590 591 592

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

593 594
  layers = gimp_image_get_layers (image_ID, &nlayers);

595
  for (i = 0; i < nlayers; i++)
Elliot Lee's avatar
Elliot Lee committed
596
    {
597 598 599
      gint offset_x;
      gint offset_y;

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

602 603 604 605
      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)
606 607 608 609 610 611
        {
          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
612 613
           * the user and they said yes.
           */
614 615
          if ((run_mode == GIMP_RUN_NONINTERACTIVE) || bad_bounds_dialog ())
            {
616
              gimp_image_crop (image_ID, image_width, image_height, 0, 0);
617
              return GIMP_PDB_SUCCESS;
618 619 620
            }
          else
            {
621
              return GIMP_PDB_CANCEL;
622 623
            }
        }
Elliot Lee's avatar
Elliot Lee committed
624 625 626 627
    }

  g_free (layers);

628
  return GIMP_PDB_SUCCESS;
Elliot Lee's avatar
Elliot Lee committed
629 630 631
}


632
static gboolean
633
save_image (const gchar *filename,
634 635
            gint32       image_ID,
            gint32       drawable_ID,
636 637
            gint32       orig_image_ID,
            GError     **error)
Elliot Lee's avatar
Elliot Lee committed
638
{
Sven Neumann's avatar
Sven Neumann committed
639 640 641
  GimpPixelRgn pixel_rgn;
  GimpDrawable *drawable;
  GimpImageType drawable_type;
Elliot Lee's avatar
Elliot Lee committed
642
  FILE *outfile;
643 644 645
  gint Red[MAXCOLORS];
  gint Green[MAXCOLORS];
  gint Blue[MAXCOLORS];
Elliot Lee's avatar
Elliot Lee committed
646 647
  guchar *cmap;
  guint rows, cols;
648
  gint BitsPerPixel, liberalBPP = 0, useBPP = 0;
649 650 651
  gint colors;
  gint i;
  gint transparent;
Elliot Lee's avatar
Elliot Lee committed
652 653
  gint offset_x, offset_y;

654
  gint32 *layers;
655
  gint    nlayers;
Elliot Lee's avatar
Elliot Lee committed
656 657 658

  gboolean is_gif89 = FALSE;

659 660 661
  gint   Delay89;
  gint   Disposal;
  gchar *layer_name;
Elliot Lee's avatar
Elliot Lee committed
662

663 664
  GimpRGB background;
  guchar  bgred, bggreen, bgblue;
665 666
  guchar  bgindex = 0;
  guint   best_error = 0xFFFFFFFF;
667

Elliot Lee's avatar
Elliot Lee committed
668

669 670
#ifdef FACEHUGGERS
  /* Save the comment back to the ImageID, if appropriate */
Sven Neumann's avatar
Sven Neumann committed
671
  if (globalcomment != NULL && comment_was_edited)
672
    {
673
      comment_parasite = gimp_parasite_new ("gimp-comment",
674 675 676
                                            GIMP_PARASITE_PERSISTENT,
                                            strlen (globalcomment) + 1,
                                            (void*) globalcomment);
Marc Lehmann's avatar
Marc Lehmann committed
677
      gimp_image_parasite_attach (orig_image_ID, comment_parasite);
678
      gimp_parasite_free (comment_parasite);
679 680 681 682
      comment_parasite = NULL;
    }
#endif

683 684 685 686 687 688 689 690
  /* The GIF spec says 7bit ASCII for the comment block. */
  if (gsvals.save_comment && globalcomment)
    {
      const gchar *c   = globalcomment;
      gint         len;

      for (len = strlen (c); len; c++, len--)
        {
691
          if ((guchar) *c > 127)
692
            {
693
              g_message (_("The GIF format only supports comments in "
694 695 696 697 698 699 700 701 702
                           "7bit ASCII encoding. No comment is saved."));

              g_free (globalcomment);
              globalcomment = NULL;

              break;
            }
        }
    }
Elliot Lee's avatar
Elliot Lee committed
703 704

  /* get a list of layers for this image_ID */
705
  layers = gimp_image_get_layers (image_ID, &nlayers);
Elliot Lee's avatar
Elliot Lee committed
706 707 708 709 710 711

  drawable_type = gimp_drawable_type (layers[0]);

  /* 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. */
Sven Neumann's avatar
Sven Neumann committed
712
  if (nlayers > 1)
Elliot Lee's avatar
Elliot Lee committed
713
    is_gif89 = TRUE;
714 715

  if (gsvals.save_comment)
Elliot Lee's avatar
Elliot Lee committed
716 717 718 719
    is_gif89 = TRUE;

  switch (drawable_type)
    {
Sven Neumann's avatar
Sven Neumann committed
720
    case GIMP_INDEXEDA_IMAGE:
Elliot Lee's avatar
Elliot Lee committed
721
      is_gif89 = TRUE;
Sven Neumann's avatar
Sven Neumann committed
722
    case GIMP_INDEXED_IMAGE:
723
      cmap = gimp_image_get_colormap (image_ID, &colors);
724

725
      gimp_context_get_background (&background);
726
      gimp_rgb_get_uchar (&background, &bgred, &bggreen, &bgblue);
727

Elliot Lee's avatar
Elliot Lee committed
728
      for (i = 0; i < colors; i++)
729 730 731 732 733
        {
          Red[i]   = *cmap++;
          Green[i] = *cmap++;
          Blue[i]  = *cmap++;
        }
Elliot Lee's avatar
Elliot Lee committed
734
      for ( ; i < 256; i++)
735 736 737 738 739
        {
          Red[i]   = bgred;
          Green[i] = bggreen;
          Blue[i]  = bgblue;
        }
Elliot Lee's avatar
Elliot Lee committed
740
      break;
Sven Neumann's avatar
Sven Neumann committed
741
    case GIMP_GRAYA_IMAGE:
Elliot Lee's avatar
Elliot Lee committed
742
      is_gif89 = TRUE;
Sven Neumann's avatar
Sven Neumann committed
743
    case GIMP_GRAY_IMAGE:
744
      colors = 256;                   /* FIXME: Not ideal. */
Elliot Lee's avatar
Elliot Lee committed
745
      for ( i = 0;  i < 256; i++)
746 747 748
        {
          Red[i] = Green[i] = Blue[i] = i;
        }
Elliot Lee's avatar
Elliot Lee committed
749 750 751
      break;

    default:
752 753
      g_message (_("Cannot save RGB color images. Convert to "
                   "indexed color or grayscale first."));
Elliot Lee's avatar
Elliot Lee committed
754 755
      return FALSE;
    }
756

757 758 759

  /* find earliest index in palette which is closest to the background
     colour, and ATTEMPT to use that as the GIF's default background colour. */
760 761 762 763 764 765 766 767 768 769 770 771 772
  for (i = 255; i >= 0; --i)
    {
      guint local_error = 0;

      local_error += (Red[i] - bgred)     * (Red[i] - bgred);
      local_error += (Green[i] - bggreen) * (Green[i] - bggreen);
      local_error += (Blue[i] - bgblue)   * (Blue[i] - bgblue);

      if (local_error <= best_error)
        {
          bgindex = i;
          best_error = local_error;
        }
773 774
    }

Elliot Lee's avatar
Elliot Lee committed
775 776

  /* open the destination file for writing */
777
  outfile = g_fopen (filename, "wb");
Elliot Lee's avatar
Elliot Lee committed
778 779
  if (!outfile)
    {
780 781 782
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Could not open '%s' for writing: %s"),
                   gimp_filename_to_utf8 (filename), g_strerror (errno));
Elliot Lee's avatar
Elliot Lee committed
783 784 785 786
      return FALSE;
    }


787
  /* init the progress meter */
788
  gimp_progress_init_printf (_("Saving '%s'"),
789
                             gimp_filename_to_utf8 (filename));
790

791

Elliot Lee's avatar
Elliot Lee committed
792 793 794
  /* write the GIFheader */

  if (colors < 256)
795
    {
796
      /* we keep track of how many bits we promised to have in liberalBPP,
797 798
         so that we don't accidentally come under this when doing
         clever transparency stuff where we can re-use wasted indices. */
799
      liberalBPP = BitsPerPixel =
800
        colors_to_bpp (colors + ((drawable_type==GIMP_INDEXEDA_IMAGE) ? 1 : 0));
801
    }
Elliot Lee's avatar
Elliot Lee committed
802
  else
803
    {
804
      liberalBPP = BitsPerPixel =
805
        colors_to_bpp (256);
806

807 808 809 810
      if (drawable_type == GIMP_INDEXEDA_IMAGE)
        {
          g_printerr ("GIF: Too many colours?\n");
        }
811
    }
Elliot Lee's avatar
Elliot Lee committed
812

Sven Neumann's avatar
Sven Neumann committed
813 814
  cols = gimp_image_width (image_ID);
  rows = gimp_image_height (image_ID);
815
  Interlace = gsvals.interlace;
816 817
  gif_encode_header (outfile, is_gif89, cols, rows, bgindex,
                     BitsPerPixel, Red, Green, Blue, get_pixel);
Elliot Lee's avatar
Elliot Lee committed
818 819 820 821 822


  /* If the image has multiple layers it'll be made into an
     animated GIF, so write out the infinite-looping extension */
  if ((nlayers > 1) && (gsvals.loop))
823
    gif_encode_loop_ext (outfile, 0);
Elliot Lee's avatar
Elliot Lee committed
824 825

  /* Write comment extension - mustn't be written before the looping ext. */
826
  if (gsvals.save_comment && globalcomment)
Elliot Lee's avatar
Elliot Lee committed
827
    {
828
      gif_encode_comment_ext (outfile, globalcomment);
Elliot Lee's avatar
Elliot Lee committed
829 830 831 832 833 834
    }


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

835 836
  cur_progress = 0;
  max_progress = nlayers * rows;
Elliot Lee's avatar
Elliot Lee committed
837

838
  for (i = nlayers - 1; i >= 0; i--, cur_progress = (nlayers - i) * rows)
Elliot Lee's avatar
Elliot Lee committed
839 840 841 842 843 844 845
    {
      drawable_type = gimp_drawable_type (layers[i]);
      drawable = gimp_drawable_get (layers[i]);
      gimp_drawable_offsets (layers[i], &offset_x, &offset_y);
      cols = drawable->width;
      rows = drawable->height;
      rowstride = drawable->width;
846

847
      gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0,
848
                           drawable->width, drawable->height, FALSE, FALSE);
Elliot Lee's avatar
Elliot Lee committed
849

850 851 852
      pixels = g_new (guchar, (drawable->width * drawable->height
                               * (((drawable_type == GIMP_INDEXEDA_IMAGE)
                                   || (drawable_type == GIMP_GRAYA_IMAGE)) ? 2 : 1)));
853

854
      gimp_pixel_rgn_get_rect (&pixel_rgn, pixels, 0, 0,
855
                               drawable->width, drawable->height);
Elliot Lee's avatar
Elliot Lee committed
856 857 858


      /* sort out whether we need to do transparency jiggery-pokery */
859 860 861 862 863 864 865
      if ((drawable_type == GIMP_INDEXEDA_IMAGE)
          || (drawable_type == GIMP_GRAYA_IMAGE))
        {
          /* Try to find an entry which isn't actually used in the
             image, for a transparency index. */

          transparent =
866 867 868 869
            find_unused_ia_colour (pixels,
                                   drawable->width * drawable->height,
                                   bpp_to_colors (colors_to_bpp (colors)),
                                   &colors);
870 871

          special_flatten_indexed_alpha (pixels,
872
                                         transparent,
873 874
                                         drawable->width * drawable->height);
        }
Elliot Lee's avatar
Elliot Lee committed
875
      else
876 877 878
        {
          transparent = -1;
        }
879

880
      BitsPerPixel = colors_to_bpp (colors);
Elliot Lee's avatar
Elliot Lee committed
881

882
      if (BitsPerPixel != liberalBPP)
883 884 885 886
        {
          /* 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... */
887
#ifdef GIFDEBUG
888
          static gboolean onceonly = FALSE;
889

890 891 892
          if (! onceonly)
            {
              g_warning ("Promised %d bpp, pondered writing chunk with %d bpp!",
893 894
                         liberalBPP, BitsPerPixel);
              onceonly = TRUE;
895
            }
896
#endif
897
        }
898

899
      useBPP = (BitsPerPixel > liberalBPP) ? BitsPerPixel : liberalBPP;
900

Elliot Lee's avatar
Elliot Lee committed
901
      if (is_gif89)
902 903 904
        {
          if (i > 0 && ! gsvals.always_use_default_dispose)
            {
905
              layer_name = gimp_item_get_name (layers[i - 1]);
906 907 908 909
              Disposal = parse_disposal_tag (layer_name);
              g_free (layer_name);
            }
          else
910 911 912
            {
              Disposal = gsvals.default_dispose;
            }
913

914
          layer_name = gimp_item_get_name (layers[i]);
915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930
          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;

              if (!onceonly)
                {
                  g_message (_("Delay inserted to prevent evil "
931
                               "CPU-sucking animation."));
932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
                  onceonly = TRUE;
                }
              Delay89 = 1;
            }

          gif_encode_graphic_control_ext (outfile, Disposal, Delay89, nlayers,
                                          cols, rows,
                                          transparent,
                                          useBPP,
                                          get_pixel);
        }

     gif_encode_image_data (outfile, cols, rows,
                            (rows > 4) ? gsvals.interlace : 0,
                            useBPP,
                            get_pixel,
                            offset_x, offset_y);
949

950
     gimp_drawable_detach (drawable);
Elliot Lee's avatar
Elliot Lee committed
951

952 953
     g_free (pixels);
  }
954

Elliot Lee's avatar
Elliot Lee committed
955 956
  g_free(layers);

957
  gif_encode_close (outfile);
Elliot Lee's avatar
Elliot Lee committed
958 959 960 961 962

  return TRUE;
}

static gboolean
963
bad_bounds_dialog (void)
Elliot Lee's avatar
Elliot Lee committed
964
{
965
  GtkWidget *dialog;
966
  gboolean   crop;
Elliot Lee's avatar
Elliot Lee committed
967

Sven Neumann's avatar
Sven Neumann committed
968 969 970 971 972
  dialog = gtk_message_dialog_new (NULL, 0,
                                   GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE,
                                   _("The image you are trying to save as a "
                                     "GIF contains layers which extend beyond "
                                     "the actual borders of the image."));
973

Sven Neumann's avatar
Sven Neumann committed
974 975 976 977
  gtk_dialog_add_buttons (GTK_DIALOG (dialog),
                          GTK_STOCK_CANCEL,     GTK_RESPONSE_CANCEL,
                          GIMP_STOCK_TOOL_CROP, GTK_RESPONSE_OK,
                          NULL);
Elliot Lee's avatar
Elliot Lee committed
978

979
  gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
980 981 982
                                           GTK_RESPONSE_OK,
                                           GTK_RESPONSE_CANCEL,
                                           -1);
983

984 985
  gimp_window_set_transient (GTK_WINDOW (dialog));

Sven Neumann's avatar
Sven Neumann committed
986 987 988 989 990 991
  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, "
                                              "or cancel this save."));
Elliot Lee's avatar
Elliot Lee committed
992

993
  gtk_widget_show (dialog);
Elliot Lee's avatar
Elliot Lee committed
994

Sven Neumann's avatar
Sven Neumann committed
995
  crop = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK);
996

997
  gtk_widget_destroy (dialog);
Elliot Lee's avatar
Elliot Lee committed
998

999
  return crop;
Elliot Lee's avatar
Elliot Lee committed
1000 1001
}

1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067
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),
                    &gsvals.default_delay);

  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, };
1068
  va_list       values;
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100

  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
1101 1102

static gint
1103
save_dialog (gint32 image_ID)
Elliot Lee's avatar
Elliot Lee committed
1104
{
1105 1106 1107
  GtkBuilder    *builder = NULL;
  gchar         *ui_file = NULL;
  GError        *error   = NULL;
1108
  GtkWidget     *dialog;
1109
  GtkWidget     *text_view;
1110
  GtkTextBuffer *text_buffer;
1111
  GtkWidget     *toggle;
1112
  GtkWidget     *frame;
1113
#ifdef FACEHUGGERS
1114
  GimpParasite  *GIF2_CMNT;
1115
#endif
1116
  gint32         nlayers;
1117
  gboolean       animation_supported = FALSE;
1118
  gboolean       run;
Elliot Lee's avatar
Elliot Lee committed
1119 1120

  gimp_image_get_layers (image_ID, &nlayers);
1121
  animation_supported = nlayers > 1;
Elliot Lee's avatar
Elliot Lee committed
1122

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