Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Gimp/plug-ins/common/   (Gimp Bildeditor Version 3.2.0©)  Datei vom 14.2.2026 mit Größe 75 kB image not shown  

Quelle  file-gif-export.c   Sprache: C

 

/* GIF exporting file filter for GIMP
 *
 *    Copyright
 *    - Adam D. Moss
 *    - Peter Mattis
 *    - Spencer Kimball
 *
 *      Based around original GIF code by David Koblas.
 *
 *
 * Version 4.1.0 - 2003-06-16
 *                        Adam D. Moss - <adam@gimp.org> <adam@foxbox.org>
 */

/*
 * 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
 *  CompuServe Incorporated."
 */

/* 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)     | */
/* +-------------------------------------------------------------------+ */

#include "config.h"

#include <string.h>

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#include "libgimp/stdplugins-intl.h"


#define EXPORT_PROC    "file-gif-export"
#define PLUG_IN_BINARY "file-gif-export"


/* uncomment the line below for a little debugging info */
/* #define GIFDEBUG yesplease */


enum
{
  DISPOSE_UNSPECIFIED,
  DISPOSE_COMBINE,
  DISPOSE_REPLACE
};


typedef struct _Gif      Gif;
typedef struct _GifClass GifClass;

struct _Gif
{
  GimpPlugIn      parent_instance;
};

struct _GifClass
{
  GimpPlugInClass parent_class;
};


#define GIF_TYPE  (gif_get_type ())
#define GIF(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIF_TYPE, Gif))

GType                           gif_get_type         (void) G_GNUC_CONST;

static GList                  * gif_query_procedures (GimpPlugIn           *plug_in);
static GimpProcedure          * gif_create_procedure (GimpPlugIn           *plug_in,
                                                      const gchar          *name);

static GimpValueArray         * gif_export           (GimpProcedure        *procedure,
                                                      GimpRunMode           run_mode,
                                                      GimpImage            *image,
                                                      GFile                *file,
                                                      GimpExportOptions    *options,
                                                      GimpMetadata         *metadata,
                                                      GimpProcedureConfig  *config,
                                                      gpointer              run_data);

static gboolean                 export_image         (GFile                *file,
                                                      GimpImage            *image,
                                                      GimpDrawable         *drawable,
                                                      GimpImage            *orig_image,
                                                      GObject              *config,
                                                      GError              **error);

static GimpExportCapabilities   export_edit_options  (GimpProcedure        *procedure,
                                                      GimpProcedureConfig  *config,
                                                      GimpExportOptions    *options,
                                                      gpointer              create_data);

static GimpPDBStatusType        sanity_check         (GFile                *file,
                                                      GimpImage           **image,
                                                      GimpRunMode           run_mode,
                                                      GError              **error);
static gboolean                 bad_bounds_dialog    (void);

static gboolean                 save_dialog          (GimpImage            *image,
                                                      GimpImage            *orig_image,
                                                      GimpProcedure        *procedure,
                                                      GObject              *config);


G_DEFINE_TYPE (Gif, gif, GIMP_TYPE_PLUG_IN)

GIMP_MAIN (GIF_TYPE)
DEFINE_STD_SET_I18N


static gint Interlace; /* For compression code */


static void
gif_class_init (GifClass *klass)
{
  GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);

  plug_in_class->query_procedures = gif_query_procedures;
  plug_in_class->create_procedure = gif_create_procedure;
  plug_in_class->set_i18n         = STD_SET_I18N;
}

static void
gif_init (Gif *gif)
{
}

static GList *
gif_query_procedures (GimpPlugIn *plug_in)
{
  return  g_list_append (NULL, g_strdup (EXPORT_PROC));
}

static GimpProcedure *
gif_create_procedure (GimpPlugIn  *plug_in,
                      const gchar *name)
{
  GimpProcedure *procedure = NULL;

  if (! strcmp (name, EXPORT_PROC))
    {
      procedure = gimp_export_procedure_new (plug_in, name,
                                             GIMP_PDB_PROC_TYPE_PLUGIN,
                                             FALSE, gif_export, NULL, NULL);

      gimp_procedure_set_image_types (procedure, "INDEXED*, GRAY*");

      gimp_procedure_set_menu_label (procedure, _("GIF image"));
      gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
                                           _("GIF"));

      gimp_procedure_set_documentation (procedure,
                                        _("exports files in GIF "
                                          "file format"),
                                        /* xgettext: no-c-format */
                                        _("Export a file in GIF "
                                          "format, with possible animation, "
                                          "transparency, and comment. To export "
                                          "an animation, operate on a multi-layer "
                                          "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."),
                                        name);
      gimp_procedure_set_attribution (procedure,
                                      "Spencer Kimball, Peter Mattis, "
                                      "Adam Moss, David Koblas",
                                      "Spencer Kimball, Peter Mattis, "
                                      "Adam Moss, David Koblas",
                                      "1995-1997");

      gimp_file_procedure_set_handles_remote (GIMP_FILE_PROCEDURE (procedure),
                                              TRUE);
      gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
                                          "image/gif");
      gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
                                          "gif");

      gimp_export_procedure_set_capabilities (GIMP_EXPORT_PROCEDURE (procedure),
                                              0, export_edit_options, NULL, NULL);

      gimp_procedure_add_boolean_argument (procedure, "interlace",
                                           _("_Interlace"),
                                           _("Try to export as interlaced"),
                                           FALSE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "loop",
                                           _("Loop _Forever"),
                                           _("(animated gif) Loop infinitely"),
                                           TRUE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_int_argument (procedure, "number-of-repeats",
                                       _("_Number of repeats"),
                                       _("(animated gif) Number of repeats "
                                         "(Ignored if 'loop' is TRUE)"),
                                       0, G_MAXSHORT - 1, 0,
                                       G_PARAM_READWRITE);

      gimp_procedure_add_int_argument (procedure, "default-delay",
                                       _("_Delay between frames when unspecified"),
                                       _("(animated gif) Default delay between frames "
                                         "in milliseconds"),
                                       0, G_MAXINT, 100,
                                       G_PARAM_READWRITE);

      gimp_procedure_add_choice_argument (procedure, "default-dispose",
                                          _("Frame disposal _when unspecified"),
                                          _("(animated gif) Default disposal type"),
                                          gimp_choice_new_with_values ("unspecified", DISPOSE_UNSPECIFIED, _("I don't care"),                  NULL,
                                                                       "combine",     DISPOSE_COMBINE,     _("Cumulative layers (combine)"),   NULL,
                                                                       "replace",     DISPOSE_REPLACE,     _("One frame per layer (replace)"), NULL,
                                                                       NULL),
                                          "unspecified",
                                          G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "as-animation",
                                           _("_As animation"),
                                           _("Export GIF as animation?"),
                                           FALSE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "force-delay",
                                           _("_Use delay entered above for all frames"),
                                           _("(animated gif) Use specified delay for all frames"),
                                           FALSE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "force-dispose",
                                           _("Use dis_posal entered above "
                                             "for all frames"),
                                           _("(animated gif) Use specified disposal for all frames"),
                                           FALSE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_boolean_aux_argument (procedure, "include-comment",
                                               _("Sa_ve comment"),
                                               _("Save the image comment in the GIF file"),
                                               gimp_export_comment (),
                                               G_PARAM_READWRITE);

      gimp_procedure_add_string_aux_argument (procedure, "gimp-comment",
                                              _("Commen_t"),
                                              _("Image comment"),
                                              gimp_get_default_comment (),
                                              G_PARAM_READWRITE);

      gimp_procedure_set_argument_sync (procedure, "gimp-comment",
                                        GIMP_ARGUMENT_SYNC_PARASITE);
    }

  return procedure;
}

static GimpValueArray *
gif_export (GimpProcedure        *procedure,
            GimpRunMode           run_mode,
            GimpImage            *image,
            GFile                *file,
            GimpExportOptions    *options,
            GimpMetadata         *metadata,
            GimpProcedureConfig  *config,
            gpointer              run_data)
{
  GimpPDBStatusType  status          = GIMP_PDB_SUCCESS;
  GimpExportReturn   export          = GIMP_EXPORT_IGNORE;
  GimpImage         *orig_image;
  GimpImage         *sanitized_image = NULL;
  GimpParasite      *parasite        = NULL;
  GError            *error           = NULL;

  gegl_init (NULL, NULL);

  orig_image = image;

  if (run_mode == GIMP_RUN_INTERACTIVE ||
      run_mode == GIMP_RUN_WITH_LAST_VALS)
    gimp_ui_init (PLUG_IN_BINARY);

  status = sanity_check (file, &image, run_mode, &error);

  /* Get the export options */
  if (status == GIMP_PDB_SUCCESS)
    {
      /* If the sanity check succeeded, the image_ID will point to a
       * duplicate image to delete later.
       */

      sanitized_image = image;

      /* If imported as an animation, set the animation configurations
       * when overwriting the file */

      parasite = gimp_image_get_parasite (image, "gif/animated");
      if (parasite)
        {
          gint     num_loops;
          gchar   *parasite_data;
          guint32  parasite_size;

          parasite_data = (gchar *) gimp_parasite_get_data (parasite, ¶site_size);
          parasite_data = g_strndup (parasite_data, parasite_size);

          if (sscanf (parasite_data, "%i", &num_loops) == 1)
            {
              gboolean loop = (num_loops == 0);

              g_object_set (config,
                            "as-animation",      TRUE,
                            "loop",              loop,
                            "number-of-repeats", num_loops,
                            NULL);
            }

          gimp_image_detach_parasite (image, "gif/animated");
          g_free (parasite);
          g_free (parasite_data);
        }

      if (run_mode == GIMP_RUN_INTERACTIVE)
        {
          if (! save_dialog (image, orig_image, procedure, G_OBJECT (config)))
            {
              gimp_image_delete (sanitized_image);

              return gimp_procedure_new_return_values (procedure,
                                                       GIMP_PDB_CANCEL,
                                                       NULL);
            }
        }
    }

  if (status == GIMP_PDB_SUCCESS)
    {
      GList                  *drawables;

      export = gimp_export_options_get_image (options, &image);
      drawables = gimp_image_list_layers (image);

      if (! export_image (file, image, drawables->data, orig_image,
                          G_OBJECT (config), &error))
        {
          status = GIMP_PDB_EXECUTION_ERROR;
        }

      gimp_image_delete (sanitized_image);
      g_list_free (drawables);
    }

  if (export == GIMP_EXPORT_EXPORT)
    gimp_image_delete (image);


  return gimp_procedure_new_return_values (procedure, status, error);
}

/* ppmtogif.c - read a portable pixmap and produce a GIF file
**
** Based on GIFENCOD by David Rowley <mgardi@watdscu.waterloo.edu>. A
** Lempel-Ziv compression based on "compress".
**
** 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 gint (* ifunptr) (gint x,
                          gint y);


static gint find_unused_ia_color           (const guchar  *pixels,
                                            gint           numpixels,
                                            gint           num_indices,
                                            gint          *colors);

static void special_flatten_indexed_alpha  (guchar        *pixels,
                                            gint           transparent,
                                            gint           numpixels);

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);
static void bump_pixel                     (void);

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);

static gint     rowstride;
static guchar  *pixels;
static gint     cur_progress;
static gint     max_progress;

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);


static gint
find_unused_ia_color (const guchar *pixels,
                      gint          numpixels,
                      gint          num_indices,
                      gint         *colors)
{
  gboolean ix_used[256];
  gint     i;

#ifdef GIFDEBUG
  g_printerr ("GIF: fuiac: Image claims to use %d/%d indices - finding free "
              "index...\n", *colors, num_indices);
#endif

  for (i = 0; i < 256; i++)
    ix_used[i] = FALSE;

  for (i = 0; i < numpixels; i++)
    {
      if (pixels[i * 2 + 1])
        ix_used[pixels[i * 2]] = TRUE;
    }

  for (i = num_indices - 1; i >= 0; i--)
    {
      if (! ix_used[i])
        {
#ifdef GIFDEBUG
          g_printerr ("GIF: Found unused color index %d.\n", (int) i);
#endif
          return i;
        }
    }

  /* 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.
   */

  if (*colors < 256)
    {
      (*colors)++;

      g_printerr ("GIF: 2nd pass "
                  "- Increasing bounds and using color index %d.\n",
                  *colors - 1);
      return ((*colors) - 1);
    }

  g_message (_("Couldn't simply reduce colors further. Exporting as opaque."));

  return -1;
}


static void
special_flatten_indexed_alpha (guchar *pixels,
                               gint    transparent,
                               gint    numpixels)
{
  gint i;

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


  if (transparent == -1) /* tough, no indices left for the trans. index */
    {
      for (i = 0; i < numpixels; i++)
        pixels[i] = pixels[i * 2];
    }
  else  /* make transparent */
    {
      for (i = 0; i < numpixels; i++)
        {
          if (! (pixels[i * 2 + 1] & 128))
            {
              pixels[i] = (guchar) transparent;
            }
          else
            {
              pixels[i] = pixels[i * 2];
            }
        }
    }
}


static gint
parse_ms_tag (const gchar *str)
{
  gint sum    = 0;
  gint offset = 0;
  gint length;

  length = strlen (str);

 find_another_bra:

  while ((offset < length) && (str[offset] != '('))
    offset++;

  if (offset >= length)
    return(-1);

  if (! g_ascii_isdigit (str[++offset]))
    goto find_another_bra;

  do
    {
      sum *= 10;
      sum += str[offset] - '0';
      offset++;
    }
  while ((offset < length) && (g_ascii_isdigit (str[offset])));

  if (length - offset <= 2)
    return(-3);

  if ((g_ascii_toupper (str[offset])     != 'M') ||
      (g_ascii_toupper (str[offset + 1]) != 'S'))
    return -4;

  return sum;
}


static gint
parse_disposal_tag (const gchar *str,
                    gint         default_dispose)
{
  gint offset = 0;
  gint length;

  length = strlen (str);

  while ((offset + 9) <= length)
    {
      if (strncmp (&str[offset], "(combine)", 9) == 0)
        return 0x01;

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

      offset++;
    }

  return default_dispose;
}


static GimpPDBStatusType
sanity_check (GFile        *file,
              GimpImage   **image,
              GimpRunMode   run_mode,
              GError      **error)
{
  GList *layers;
  GList *list;
  gint   image_width;
  gint   image_height;

  image_width  = gimp_image_get_width (*image);
  image_height = gimp_image_get_height (*image);

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

      return GIMP_PDB_EXECUTION_ERROR;
    }

  *image = gimp_image_duplicate (*image);

  /* Convert image to 8 bit precision if necessary */
  if (gimp_image_get_precision (*image) != GIMP_PRECISION_U8_NON_LINEAR &&
      gimp_image_get_precision (*image) != GIMP_PRECISION_U8_LINEAR     &&
      gimp_image_get_precision (*image) != GIMP_PRECISION_U8_PERCEPTUAL)
    {
      gboolean converted = FALSE;

      converted = gimp_image_convert_precision (*image,
                                                GIMP_PRECISION_U8_NON_LINEAR);

      if (! converted)
        {
          g_set_error (error, 0, 0,
                       _("Unable to export '%s'.  "
                         "Please convert your image to 8 bit integer "
                         "precision, as the GIF file format does not "
                         "support higher precisions."),
                       gimp_file_get_utf8_name (file));

          return GIMP_PDB_EXECUTION_ERROR;
        }
    }

  /*** Iterate through the layers to make sure they're all ***/
  /*** within the bounds of the image                      ***/
  layers = gimp_image_list_layers (*image);

  for (list = layers; list; list = g_list_next (list))
    {
      GimpDrawable *drawable = list->data;
      gint          offset_x;
      gint          offset_y;

      gimp_drawable_get_offsets (drawable, &offset_x, &offset_y);

      if (offset_x < 0 ||
          offset_y < 0 ||
          offset_x + gimp_drawable_get_width (drawable) > image_width ||
          offset_y + gimp_drawable_get_height (drawable) > image_height)
        {
          g_list_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
           * the user and they said yes.
           */

          if ((run_mode == GIMP_RUN_NONINTERACTIVE) || bad_bounds_dialog ())
            {
              gimp_image_crop (*image, image_width, image_height, 0, 0);
              return GIMP_PDB_SUCCESS;
            }
          else
            {
              gimp_image_delete (*image);
              return GIMP_PDB_CANCEL;
            }
        }
    }

  g_list_free (layers);

  return GIMP_PDB_SUCCESS;
}

static gboolean
export_image (GFile         *file,
              GimpImage     *image,
              GimpDrawable  *drawable,
              GimpImage     *orig_image,
              GObject       *config,
              GError       **error)
{
  GeglBuffer    *buffer;
  GimpImageType  drawable_type;
  const Babl    *format = NULL;
  GOutputStream *output;
  gint           Red[MAXCOLORS];
  gint           Green[MAXCOLORS];
  gint           Blue[MAXCOLORS];
  guchar        *cmap;
  guint          rows, cols;
  gint           BitsPerPixel;
  gint           liberalBPP = 0;
  gint           useBPP     = 0;
  gint           colors;
  gint           i;
  gint           transparent;
  gint           offset_x, offset_y;

  GList         *layers;
  GList         *list;
  gint           nlayers;

  gboolean       is_gif89 = FALSE;

  gint           Delay89;
  gint           Disposal;
  gchar         *layer_name;

  guchar         bgred   = 255;
  guchar         bggreen = 255;
  guchar         bgblue  = 255;
  guchar         bgindex = 0;
  guint          best_error = 0xFFFFFFFF;

  gboolean       config_interlace;
  gboolean       config_loop;
  gint           config_number_of_repeats;
  gint           config_default_delay;
  gint           config_default_dispose;
  gboolean       config_use_default_delay;
  gboolean       config_use_default_dispose;
  gboolean       config_as_animation;
  gboolean       config_save_comment;
  gchar         *config_comment;

  g_object_get (config,
                "interlace",         &config_interlace,
                "loop",              &config_loop,
                "number-of-repeats", &config_number_of_repeats,
                "default-delay",     &config_default_delay,
                "force-delay",       &config_use_default_delay,
                "force-dispose",     &config_use_default_dispose,
                "as-animation",      &config_as_animation,
                "include-comment",   &config_save_comment,
                "gimp-comment",      &config_comment,
                NULL);
  config_default_dispose =
    gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config),
                                         "default-dispose");

  /* The GIF spec says 7bit ASCII for the comment block. */
  if (config_save_comment && config_comment && strlen (config_comment))
    {
      const gchar *c = config_comment;
      gint         len;

      for (len = strlen (c); len; c++, len--)
        {
          if ((guchar) *c > 127)
            {
              g_message (_("The GIF format only supports comments in "
                           "7bit ASCII encoding. No comment is saved."));

              g_free (config_comment);
              config_comment = NULL;

              config_save_comment = FALSE;

              break;
            }
        }
    }
  else
    {
      config_save_comment = FALSE;
    }

  /* get a list of layers for this image */
  layers = gimp_image_list_layers (image);
  nlayers = g_list_length (layers);

  drawable_type = gimp_drawable_type (layers->data);

  /* 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.
   */

  if (nlayers > 1)
    {
      is_gif89 = TRUE;

      /* Layers can be with or without alpha channel. Make sure we set
       * alpha if there is at least one layer with alpha channel. */

      if (drawable_type == GIMP_GRAY_IMAGE ||
          drawable_type == GIMP_INDEXED_IMAGE)
        {
          for (list = layers; list; list = g_list_next (list))
            {
              GimpImageType dr_type = gimp_drawable_type (list->data);

              if (dr_type == GIMP_GRAYA_IMAGE ||
                  dr_type == GIMP_INDEXEDA_IMAGE)
                {
                  drawable_type = dr_type;
                  break;
                }
            }
        }
    }

  if (config_save_comment)
    is_gif89 = TRUE;

  switch (drawable_type)
    {
    case GIMP_INDEXEDA_IMAGE:
      is_gif89 = TRUE;
    case GIMP_INDEXED_IMAGE:
        {
          GeglColor *background;
          guchar     bg[3];

          cmap = gimp_palette_get_colormap (gimp_image_get_palette (image), babl_format ("R'G'B' u8"), &colors, NULL);

          background = gimp_context_get_background ();
          gegl_color_get_pixel (background, babl_format_with_space ("R'G'B' u8", NULL), bg);
          g_object_unref (background);
          bgred   = bg[0];
          bggreen = bg[1];
          bgblue  = bg[2];

          for (i = 0; i < colors; i++)
            {
              Red[i]   = *cmap++;
              Green[i] = *cmap++;
              Blue[i]  = *cmap++;
            }
          for ( ; i < 256; i++)
            {
              Red[i]   = bgred;
              Green[i] = bggreen;
              Blue[i]  = bgblue;
            }
        }
      break;
    case GIMP_GRAYA_IMAGE:
      is_gif89 = TRUE;
    case GIMP_GRAY_IMAGE:
      colors = 256;                   /* FIXME: Not ideal. */
      for ( i = 0;  i < 256; i++)
        {
          Red[i] = Green[i] = Blue[i] = i;
        }
      break;

    default:
      g_message (_("Cannot export RGB color images. Convert to "
                   "indexed color or grayscale first."));
      return FALSE;
    }


  /* find earliest index in palette which is closest to the background
   * color, and ATTEMPT to use that as the GIF's default background
   * color.
   */

  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;
        }
    }


  /* init the progress meter */
  gimp_progress_init_printf (_("Exporting '%s'"),
                             gimp_file_get_utf8_name (file));


  /* open the destination file for writing */
  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
    {
      return FALSE;
    }


  /* write the GIFheader */

  if (colors < 256)
    {
      /* 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.
       */

      liberalBPP = BitsPerPixel =
        colors_to_bpp (colors + ((drawable_type==GIMP_INDEXEDA_IMAGE) ? 1 : 0));
    }
  else
    {
      liberalBPP = BitsPerPixel =
        colors_to_bpp (256);

      if (drawable_type == GIMP_INDEXEDA_IMAGE)
        {
          g_printerr ("GIF: Too many colors?\n");
        }
    }

  cols = gimp_image_get_width (image);
  rows = gimp_image_get_height (image);
  Interlace = config_interlace;
  if (! gif_encode_header (output, is_gif89, cols, rows, bgindex,
                           BitsPerPixel, Red, Green, Blue, get_pixel,
                           error))
    return FALSE;


  /* If the image has multiple layers it'll be made into an animated
   * GIF, so write out the looping extension
   */

  if (nlayers > 1)
    {
      /* If 'Forever' toggle was checked, override number_of_repeats */
      if (config_loop)
        config_number_of_repeats = 0;

      if (config_loop || config_number_of_repeats > 0)
        if (! gif_encode_loop_ext (output, config_number_of_repeats, error))
          return FALSE;
    }

  /* Write comment extension - mustn't be written before the looping ext. */
  if (config_save_comment && config_comment && strlen (config_comment))
    {
      if (! gif_encode_comment_ext (output, config_comment, error))
        return FALSE;
    }


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

  cur_progress = 0;
  max_progress = nlayers * rows;

  layers = g_list_reverse (layers);

  for (list = layers, i = nlayers - 1;
       list && i >= 0;
       list = g_list_next (list), i--, cur_progress = (nlayers - i) * rows)
    {
      GimpDrawable *drawable = list->data;

      drawable_type = gimp_drawable_type (drawable);
      if (drawable_type == GIMP_GRAYA_IMAGE)
        {
          format = babl_format ("Y'A u8");
        }
      else if (drawable_type == GIMP_GRAY_IMAGE)
        {
          format = babl_format ("Y' u8");
        }

      buffer = gimp_drawable_get_buffer (drawable);
      gimp_drawable_get_offsets (drawable, &offset_x, &offset_y);
      cols = gimp_drawable_get_width (drawable);
      rows = gimp_drawable_get_height (drawable);
      rowstride = cols;

      pixels = g_new (guchar, (cols * rows *
                               (((drawable_type == GIMP_INDEXEDA_IMAGE) ||
                                 (drawable_type == GIMP_GRAYA_IMAGE)) ? 2 : 1)));

      gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, cols, rows), 1.0,
                       format, pixels,
                       GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);

      /* sort out whether we need to do transparency jiggery-pokery */
      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 =
            find_unused_ia_color (pixels,
                                   cols * rows,
                                   bpp_to_colors (colors_to_bpp (colors)),
                                   &colors);

          special_flatten_indexed_alpha (pixels,
                                         transparent,
                                         cols * rows);
        }
      else
        {
          transparent = -1;
        }

      BitsPerPixel = colors_to_bpp (colors);

      if (BitsPerPixel != liberalBPP)
        {
          /* 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...
           */

#ifdef GIFDEBUG
          static gboolean onceonly = FALSE;

          if (! onceonly)
            {
              g_warning ("Promised %d bpp, pondered writing chunk with %d bpp!",
                         liberalBPP, BitsPerPixel);
              onceonly = TRUE;
            }
#endif
        }

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

      if (is_gif89)
        {
          if (i > 0 && ! config_use_default_dispose)
            {
              layer_name = gimp_item_get_name (list->next->data);
              Disposal = parse_disposal_tag (layer_name,
                                             config_default_dispose);
              g_free (layer_name);
            }
          else
            {
              Disposal = config_default_dispose;
            }

          layer_name = gimp_item_get_name (GIMP_ITEM (drawable));
          Delay89 = parse_ms_tag (layer_name);
          g_free (layer_name);

          if (Delay89 < 0 || config_use_default_delay)
            Delay89 = (config_default_delay + 5) / 10;
          else
            Delay89 = (Delay89 + 5) / 10;

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

              if (! onceonly)
                {
                  g_message (_("Delay inserted to prevent evil "
                               "CPU-sucking animation."));
                  onceonly = TRUE;
                }

              Delay89 = 1;
            }

          if (! gif_encode_graphic_control_ext (output,
                                                Disposal, Delay89, nlayers,
                                                cols, rows,
                                                transparent,
                                                useBPP,
                                                get_pixel,
                                                error))
            return FALSE;
        }

      if (! gif_encode_image_data (output, cols, rows,
                                   (rows > 4) ? config_interlace : 0,
                                   useBPP,
                                   get_pixel,
                                   offset_x, offset_y,
                                   error))
        return FALSE;

      gimp_progress_update (1.0);

      g_object_unref (buffer);

      g_free (pixels);
    }

  g_list_free (layers);

  if (! gif_encode_close (output, error))
    return FALSE;

  return TRUE;
}

static GimpExportCapabilities
export_edit_options (GimpProcedure        *procedure,
                     GimpProcedureConfig  *config,
                     GimpExportOptions    *options,
                     gpointer              create_data)
{
  GimpExportCapabilities capabilities;
  gboolean               as_animation;

  g_object_get (G_OBJECT (config),
                "as-animation", &as_animation,
                NULL);

  capabilities = (GIMP_EXPORT_CAN_HANDLE_INDEXED |
                  GIMP_EXPORT_CAN_HANDLE_GRAY    |
                  GIMP_EXPORT_CAN_HANDLE_ALPHA);

  if (as_animation)
    capabilities |= GIMP_EXPORT_CAN_HANDLE_LAYERS;

  return capabilities;
}

static gboolean
bad_bounds_dialog (void)
{
  GtkWidget *dialog;
  gboolean   crop;

  dialog = gtk_message_dialog_new (NULL, 0,
                                   GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE,
                                   _("The image you are trying to export as a "
                                     "GIF contains layers which extend beyond "
                                     "the actual borders of the image."));

  gtk_dialog_add_buttons (GTK_DIALOG (dialog),
                          _("_Cancel"), GTK_RESPONSE_CANCEL,
                          _("Cr_op"),   GTK_RESPONSE_OK,
                          NULL);

  gimp_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
                                           GTK_RESPONSE_OK,
                                           GTK_RESPONSE_CANCEL,
                                           -1);

  gimp_window_set_transient (GTK_WINDOW (dialog));

  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 export."));

  gtk_widget_show (dialog);

  crop = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK);

  gtk_widget_destroy (dialog);

  return crop;
}

static gboolean
save_dialog (GimpImage     *image,
             GimpImage     *orig_image,
             GimpProcedure *procedure,
             GObject       *config)
{
  GtkWidget     *dialog;
  GtkWidget     *vbox;
  GtkWidget     *text_view;
  GtkTextBuffer *text_buffer;
  GimpLayer    **layers;
  gint32         n_layers;
  gboolean       animation_supported;
  gboolean       run;

  layers   = gimp_image_get_layers (image);
  n_layers = gimp_core_object_array_get_length ((GObject **) layers);
  g_free (layers);

  animation_supported = n_layers > 1;

  dialog = gimp_export_procedure_dialog_new (GIMP_EXPORT_PROCEDURE (procedure),
                                             GIMP_PROCEDURE_CONFIG (config),
                                             image);

  /* Warn user that image will be converted to 8 bits if not already */
  if (gimp_image_get_precision (orig_image) != GIMP_PRECISION_U8_NON_LINEAR &&
      gimp_image_get_precision (orig_image) != GIMP_PRECISION_U8_LINEAR     &&
      gimp_image_get_precision (orig_image) != GIMP_PRECISION_U8_PERCEPTUAL)
    {
      GtkWidget *hint;

      /* Used to create vbox to store hintbox in */
      gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dialog),
                                       "spacer-8-bit", " ",
                                       FALSE, FALSE);

      vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dialog),
                                             "8-bit-warning-vbox", "spacer-8-bit",
                                             NULL);

      hint = g_object_new (GIMP_TYPE_HINT_BOX,
                           "icon-name", GIMP_ICON_DIALOG_WARNING,
                           "hint",      _("The GIF format only supports 8 bit "
                                          "integer precision.\n"
                                          "Your image will be converted on "
                                          "export, which may change the pixel "
                                          "values."),
                           NULL);
      gtk_widget_set_visible (hint, TRUE);

      gtk_box_pack_start (GTK_BOX (vbox), hint, FALSE, FALSE, 0);
      gtk_box_reorder_child (GTK_BOX (vbox), hint, 0);

      gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
                                  "8-bit-warning-vbox", NULL);
    }

#define MAX_COMMENT 240

  text_view = gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
                                                "gimp-comment",
                                                GTK_TYPE_TEXT_VIEW);
  text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
  g_object_set_data (G_OBJECT (text_buffer), "max-len",
                     GINT_TO_POINTER (MAX_COMMENT));

  gimp_procedure_dialog_fill_scrolled_window (GIMP_PROCEDURE_DIALOG (dialog),
                                              "comment-scrolled",
                                              "gimp-comment");

  gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog),
                                    "comment-frame", "include-comment", FALSE,
                                    "comment-scrolled");

  gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
                              "interlace", "comment-frame", NULL);

  if (animation_supported)
    {
      GtkWidget    *grid;
      GtkWidget    *widget;

      grid = gtk_grid_new ();
      gtk_grid_set_column_spacing (GTK_GRID (grid), 1);
      gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
      gtk_widget_show (grid);

      widget = gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
                                                 "number-of-repeats",
                                                 GIMP_TYPE_LABEL_SPIN);
      gtk_grid_attach (GTK_GRID (grid), widget, 0, 0, 1, 1);
      gtk_widget_set_visible (widget, TRUE);

      widget = gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
                                                 "loop", GTK_TYPE_CHECK_BUTTON);
      gtk_grid_attach (GTK_GRID (grid), widget, 1, 0, 1, 1);

      gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog),
                                           "number-of-repeats", TRUE,
                                           config, "loop",
                                           TRUE);

      widget = gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
                                                 "default-delay",
                                                 GIMP_TYPE_LABEL_SPIN);
      gtk_grid_attach (GTK_GRID (grid), widget, 0, 1, 1, 1);
      gtk_widget_set_visible (widget, TRUE);

      widget = gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dialog),
                                                "milliseconds",
                                                _("milliseconds"),
                                                FALSE, FALSE);
      gtk_label_set_xalign (GTK_LABEL (widget), 0.1);
      gtk_grid_attach (GTK_GRID (grid), widget, 1, 1, 1, 1);
      gtk_widget_set_visible (widget, TRUE);

      widget = gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
                                                 "default-dispose",
                                                 G_TYPE_NONE);
      gtk_grid_attach (GTK_GRID (grid), widget, 0, 2, 2, 1);
      gtk_widget_set_visible (widget, TRUE);

      vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dialog),
                                             "animation-vbox", "force-delay",
                                             "force-dispose", NULL);

      gtk_box_pack_start (GTK_BOX (vbox), grid, FALSE, FALSE, 0);
      gtk_box_reorder_child (GTK_BOX (vbox), grid, 0);

      gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog),
                                        "ani-frame", "as-animation", FALSE,
                                        "animation-vbox");

      gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
                                  "ani-frame", NULL);
    }
  else
    {
      GtkWidget *hint;

      /* Used to create vbox to store hintbox in */
      gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dialog),
                                       "spacer", " ",
                                       FALSE, FALSE);
      gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dialog),
                                       "no-ani-title", _("Animated GIF"),
                                       FALSE, FALSE);

      vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dialog),
                                             "no-animation-vbox", "spacer",
                                             NULL);

      hint = gimp_hint_box_new (_("You can only export as animation when the "
                                  "image has more than one layer.\n"
                                  "The image you are trying to export only "
                                  "has one layer."));
      gtk_box_pack_start (GTK_BOX (vbox), hint, FALSE, FALSE, 0);
      gtk_widget_set_margin_bottom (vbox, 12);
      gtk_widget_show (hint);
      gtk_box_reorder_child (GTK_BOX (vbox), hint, 0);

      gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog),
                                        "no-ani-frame", "no-ani-title", FALSE,
                                        "no-animation-vbox");

      gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
                                  "no-ani-frame", NULL);
    }

  gtk_widget_show (dialog);

  run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog));

  gtk_widget_destroy (dialog);

  return run;
}

static int
colors_to_bpp (gint colors)
{
  gint bpp;

  if (colors <= 2)
    bpp = 1;
  else if (colors <= 4)
    bpp = 2;
  else if (colors <= 8)
    bpp = 3;
  else if (colors <= 16)
    bpp = 4;
  else if (colors <= 32)
    bpp = 5;
  else if (colors <= 64)
    bpp = 6;
  else if (colors <= 128)
    bpp = 7;
  else if (colors <= 256)
    bpp = 8;
  else
    {
      g_warning ("GIF: colors_to_bpp - Eep! too many colors: %d\n", colors);
      return 8;
    }

  return bpp;
}

static int
bpp_to_colors (gint bpp)
{
  gint colors;

  if (bpp > 8)
    {
      g_warning ("GIF: bpp_to_colors - Eep! bpp==%d !\n", bpp);
      return 256;
    }

  colors = 1 << bpp;

  return colors;
}



static gint
get_pixel (gint x,
           gint y)
{
  return *(pixels + (rowstride * (long) y) + (long) x);
}


/*****************************************************************************
 *
 * GIFENCODE.C    - GIF Image compression interface
 *
 * GIFEncode( FName, GHeight, GWidth, GInterlace, Background, Transparent,
 *            BitsPerPixel, Red, Green, Blue, get_pixel )
 *
 *****************************************************************************/


static gint  Width, Height;
static gint  curx, cury;
static glong CountDown;
static gint  Pass = 0;

/*
 * Bump the 'curx' and 'cury' to point to the next pixel
 */

static void
bump_pixel (void)
{
  /*
   * Bump the current X position
   */

  curx++;

  /*
   * If we are at the end of a scan line, set curx back to the beginning
   * If we are interlaced, bump the cury to the appropriate spot,
   * otherwise, just increment it.
   */

  if (curx == Width)
    {
      cur_progress++;

      if ((cur_progress % 20) == 0)
        gimp_progress_update ((gdouble) cur_progress / (gdouble) max_progress);

      curx = 0;

      if (! Interlace)
        ++cury;
      else
        {
          switch (Pass)
            {

            case 0:
              cury += 8;
              if (cury >= Height)
                {
                  Pass++;
                  cury = 4;
                }
              break;

            case 1:
              cury += 8;
              if (cury >= Height)
                {
                  Pass++;
                  cury = 2;
                }
              break;

            case 2:
              cury += 4;
              if (cury >= Height)
                {
                  Pass++;
                  cury = 1;
                }
              break;

            case 3:
              cury += 2;
              break;
            }
        }
    }
}

/*
 * Return the next pixel from the image
 */

static gint
gif_next_pixel (ifunptr getpixel)
{
  gint r;

  if (CountDown == 0)
    return EOF;

  --CountDown;

  r = (*getpixel) (curx, cury);

  bump_pixel ();

  return r;
}

/* public */

static gboolean
gif_encode_header (GOutputStream *output,
                   gboolean       gif89,
                   gint           GWidth,
                   gint           GHeight,
                   gint           Background,
                   gint           BitsPerPixel,
                   gint           Red[],
                   gint           Green[],
                   gint           Blue[],
                   ifunptr        get_pixel,
                   GError       **error)
{
  gint B;
  gint RWidth, RHeight;
  gint Resolution;
  gint ColorMapSize;
  gint i;

  ColorMapSize = 1 << BitsPerPixel;

  RWidth = Width = GWidth;
  RHeight = Height = GHeight;

  Resolution = BitsPerPixel;

  /*
   * Calculate number of bits we are expecting
   */

  CountDown = (long) Width *(long) Height;

  /*
   * Indicate which pass we are on (if interlace)
   */

  Pass = 0;

  /*
   * Set up the current x and y position
   */

  curx = cury = 0;

  /*
   * Write the Magic header
   */

  if (! put_string (output, gif89 ? "GIF89a" : "GIF87a", error))
    return FALSE;

  /*
   * Write out the screen width and height
   */

  if (! put_word (output, RWidth,  error) ||
      ! put_word (output, RHeight, error))
    return FALSE;

  /*
   * Indicate that there is a global color map
   */

  B = 0x80;                        /* Yes, there is a color map */

  /*
   * OR in the resolution
   */

  B |= (Resolution - 1) << 5;

  /*
   * OR in the Bits per Pixel
   */

  B |= (BitsPerPixel - 1);

  /*
   * Write it out
   */

  if (! put_byte (output, B, error))
    return FALSE;

  /*
   * Write out the Background color
   */

  if (! put_byte (output, Background, error))
    return FALSE;

  /*
   * Byte of 0's (future expansion)
   */

  if (! put_byte (output, 0, error))
    return FALSE;

  /*
   * Write out the Global Color Map
   */

  for (i = 0; i < ColorMapSize; i++)
    {
      if (! put_byte (output, Red[i],   error) ||
          ! put_byte (output, Green[i], error) ||
          ! put_byte (output, Blue[i],  error))
        return FALSE;
    }

  return TRUE;
}


static gboolean
gif_encode_graphic_control_ext (GOutputStream *output,
                                int            Disposal,
                                int            Delay89,
                                int            NumFramesInImage,
                                int            GWidth,
                                int            GHeight,
                                int            Transparent,
                                int            BitsPerPixel,
                                ifunptr        get_pixel,
                                GError       **error)
{
  Width = GWidth;
  Height = GHeight;

  /*
   * Calculate number of bits we are expecting
   */

  CountDown = (long) Width *(long) Height;

  /*
   * Indicate which pass we are on (if interlace)
   */

  Pass = 0;

  /*
   * Set up the current x and y position
   */

  curx = cury = 0;

  /*
   * Write out extension for transparent color index, if necessary.
   */

  if ( (Transparent >= 0) || (NumFramesInImage > 1) )
    {
      /* Extension Introducer - fixed. */
      if (! put_byte (output, '!', error))
        return FALSE;

      /* Graphic Control Label - fixed. */
      if (! put_byte (output, 0xf9, error))
        return FALSE;

      /* Block Size - fixed. */
      if (! put_byte (output, 4, error))
        return FALSE;

      /* Packed Fields - XXXdddut (d=disposal, u=userInput, t=transFlag) */
      /*                    s8421                                        */
      if (! put_byte (output,
                      ((Transparent >= 0) ? 0x01 : 0x00) /* TRANSPARENCY */

                      /* DISPOSAL */
                      | ((NumFramesInImage > 1) ? (Disposal << 2) : 0x00 ),
                      /* 0x03 or 0x01 build frames cumulatively */
                      /* 0x02 clears frame before drawing */
                      /* 0x00 'don't care' */

                      error))
        return FALSE;

      if (! put_word (output, Delay89, error))
        return FALSE;

      if (! put_byte (output, Transparent, error) ||
          ! put_byte (output, 0, error))
        return FALSE;
    }

  return TRUE;
}


static gboolean
gif_encode_image_data (GOutputStream *output,
                       int            GWidth,
                       int            GHeight,
                       int            GInterlace,
                       int            BitsPerPixel,
                       ifunptr        get_pixel,
                       gint           offset_x,
                       gint           offset_y,
                       GError       **error)
{
  gint LeftOfs, TopOfs;
  gint InitCodeSize;

  Interlace = GInterlace;

  Width   = GWidth;
  Height  = GHeight;
  LeftOfs = (gint) offset_x;
  TopOfs  = (gint) offset_y;

  /*
   * Calculate number of bits we are expecting
   */

  CountDown = (long) Width * (long) Height;

  /*
   * Indicate which pass we are on (if interlace)
   */

  Pass = 0;

  /*
   * The initial code size
   */

  if (BitsPerPixel <= 1)
    InitCodeSize = 2;
  else
    InitCodeSize = BitsPerPixel;

  /*
   * Set up the current x and y position
   */

  curx = cury = 0;

  /*
   * Write an Image separator
   */

  if (! put_byte (output, ',', error))
    return FALSE;

  /*
   * Write the Image header
   */


  if (! put_word (output, LeftOfs, error) ||
      ! put_word (output, TopOfs,  error) ||
      ! put_word (output, Width,   error) ||
      ! put_word (output, Height,  error))
    return FALSE;

  /*
   * Write out whether or not the image is interlaced
   */

  if (Interlace)
    {
      if (! put_byte (output, 0x40, error))
        return FALSE;
    }
  else
    {
      if (! put_byte (output, 0x00, error))
        return FALSE;
    }

  /*
   * Write out the initial code size
   */

  if (! put_byte (output, InitCodeSize, error))
    return FALSE;

  /*
   * Go and actually compress the data
   */

  if (! compress (output, InitCodeSize + 1, get_pixel, error))
    return FALSE;

  /*
   * Write out a Zero-length packet (to end the series)
   */

  if (! put_byte (output, 0, error))
    return FALSE;

#if 0
  /***************************/
  Interlace = GInterlace;
  Width = GWidth;
  Height = GHeight;
  LeftOfs = TopOfs = 0;

  CountDown = (long) Width *(long) Height;
  Pass = 0;
  /*
   * The initial code size
   */

  if (BitsPerPixel <= 1)
    InitCodeSize = 2;
  else
    InitCodeSize = BitsPerPixel;
  /*
   * Set up the current x and y position
   */

  curx = cury = 0;
#endif

  return TRUE;
}


static gboolean
gif_encode_close (GOutputStream  *output,
                  GError        **error)
{
  /*
   * Write the GIF file terminator
   */

  if (! put_byte (output, ';', error))
    return FALSE;

  /*
   * And close the file
   */

  return g_output_stream_close (output, NULL, error);
}


static gboolean
gif_encode_loop_ext (GOutputStream  *output,
                     guint           num_loops,
                     GError        **error)
{
  return (put_byte   (output, 0x21, error) &&
          put_byte   (output, 0xff, error) &&
          put_byte   (output, 0x0b, error) &&
          put_string (output, "NETSCAPE2.0", error) &&
          put_byte   (output, 0x03, error) &&
          put_byte   (output, 0x01, error) &&
          put_word   (output, num_loops, error) &&
          put_byte   (output, 0x00, error));

  /* NOTE: num_loops == 0 means 'loop infinitely' */
}


static gboolean
gif_encode_comment_ext (GOutputStream  *output,
                        const gchar    *comment,
                        GError        **error)
{
  if (!comment || !*comment)
    return TRUE;

  if (strlen (comment) > 240)
    {
      g_printerr ("GIF: warning:"
                  "comment too large - comment block not written.\n");
      return TRUE;
    }

  return (put_byte   (output, 0x21, error) &&
          put_byte   (output, 0xfe, error) &&
          put_byte   (output, strlen (comment), error) &&
          put_string (output, comment, error) &&
          put_byte   (output, 0x00, error));
}


/*
 * Write stuff to the GIF file
 */

static gboolean
put_byte (GOutputStream  *output,
          guchar          b,
          GError        **error)
{
  return g_data_output_stream_put_byte (G_DATA_OUTPUT_STREAM (output),
                                        b, NULL, error);
}

static gboolean
put_word (GOutputStream  *output,
          gint            w,
          GError        **error)
{
  return g_data_output_stream_put_int16 (G_DATA_OUTPUT_STREAM (output),
                                         w, NULL, error);
}

static gboolean
put_string (GOutputStream  *output,
            const gchar    *s,
            GError        **error)
{
  return g_data_output_stream_put_string (G_DATA_OUTPUT_STREAM (output),
                                          s, NULL, error);
}


/***************************************************************************
 *
 *  GIFCOMPR.C       - GIF Image compression routines
 *
 *  Lempel-Ziv compression based on 'compress'.  GIF modifications by
 *  David Rowley (mgardi@watdcsu.waterloo.edu)
 *
 ***************************************************************************/


/*
 * General DEFINEs
 */


#define GIF_BITS   12

#define HSIZE    5003                /* 80% occupancy */

/*
 * GIF Image compression - modified 'compress'
 *
 * Based on: compress.c - File compression ala IEEE Computer, June 1984.
 *
 * By Authors:  Spencer W. Thomas       (decvax!harpo!utah-cs!utah-gr!thomas)
 *              Jim McKie               (decvax!mcvax!jim)
 *              Steve Davies            (decvax!vax135!petsd!peora!srd)
 *              Ken Turkowski           (decvax!decwrl!turtlevax!ken)
 *              James A. Woods          (decvax!ihnp4!ames!jaw)
 *              Joe Orost               (decvax!vax135!petsd!joe)
 *
 */


static gint n_bits;                /* number of bits/code */
static gint maxbits = GIF_BITS;    /* user settable max # bits/code */
static gint maxcode;               /* maximum code, given n_bits */
static gint maxmaxcode = (gint) 1 << GIF_BITS;        /* should NEVER generate this code */
#ifdef COMPATIBLE                /* But wrong! */
#define MAXCODE(Mn_bits)        ((gint) 1 << (Mn_bits) - 1)
#else /*COMPATIBLE */
#define MAXCODE(Mn_bits)        (((gint) 1 << (Mn_bits)) - 1)
#endif /*COMPATIBLE */

static glong   htab[HSIZE];
static gushort codetab[HSIZE];
#define HashTabOf(i)       htab[i]
#define CodeTabOf(i)    codetab[i]

static const gint hsize = HSIZE; /* the original reason for this being
                                    variable was "for dynamic table sizing",
                                    but since it was never actually changed
                                    I made it const   --Adam. */


static gint free_ent = 0;        /* first unused entry */

/*
 * block compression parameters -- after all codes are used up,
 * and compression rate changes, start over.
 */

static gint  clear_flg = 0;

static gint  offset;
static glong in_count  = 1;        /* length of input */
static glong out_count = 0;        /* # of codes output (for debugging) */

/*
 * compress stdin to stdout
 *
 * Algorithm:  use open addressing double hashing (no chaining) on the
 * prefix code / next character combination.  We do a variant of Knuth's
 * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
 * secondary probe.  Here, the modular division first probe is gives way
 * to a faster exclusive-or manipulation.  Also do block compression with
 * an adaptive reset, whereby the code table is cleared when the compression
 * ratio decreases, but after the table fills.  The variable-length output
 * codes are re-sized at this point, and a special CLEAR code is generated
 * for the decompressor.  Late addition:  construct the table according to
 * file size for noticeable speed improvement on small files.  Please direct
 * questions about this implementation to ames!jaw.
 */


static gint g_init_bits;

static gint ClearCode;
static gint EOFCode;

static gulong cur_accum;
static gint   cur_bits;

static gulong masks[] =
{
  0x0000, 0x0001, 0x0003, 0x0007,
  0x000F, 0x001F, 0x003F, 0x007F,
  0x00FF, 0x01FF, 0x03FF, 0x07FF,
  0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF,
  0xFFFF
};


static gboolean
compress (GOutputStream  *output,
          gint            init_bits,
          ifunptr         ReadValue,
          GError        **error)
{
  if (FALSE)
    return no_compress (output, init_bits, ReadValue, error);
  else if (FALSE)
    return rle_compress (output, init_bits, ReadValue, error);
  else
    return normal_compress (output, init_bits, ReadValue, error);
}

static gboolean
no_compress (GOutputStream  *output,
             gint            init_bits,
             ifunptr         ReadValue,
             GError        **error)
{
  glong fcode;
  gint  i /* = 0 */ ;
  gint  c;
  gint  ent;
  gint  hsize_reg;
  gint  hshift;

  /*
   * Set up the globals:  g_init_bits - initial number of bits
   */

  g_init_bits = init_bits;

  cur_bits = 0;
  cur_accum = 0;

  /*
   * Set up the necessary values
   */

  offset = 0;
  out_count = 0;
  clear_flg = 0;
  in_count = 1;

  ClearCode = (1 << (init_bits - 1));
  EOFCode = ClearCode + 1;
  free_ent = ClearCode + 2;


  /* Had some problems here... should be okay now.  --Adam */
  n_bits = g_init_bits;
  maxcode = MAXCODE (n_bits);


  char_init();

  ent = gif_next_pixel (ReadValue);

  hshift = 0;
  for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L)
    ++hshift;
  hshift = 8 - hshift;                /* set hash code range bound */

  hsize_reg = hsize;
  cl_hash ((glong) hsize_reg);        /* clear hash table */

  if (! output_code (output, (gint) ClearCode, error))
    return FALSE;

  while ((c = gif_next_pixel (ReadValue)) != EOF)
    {
      ++in_count;

      fcode = (long) (((long) c << maxbits) + ent);
      i = (((gint) c << hshift) ^ ent);        /* xor hashing */

      if (! output_code (output, (gint) ent, error))
        return FALSE;

      ++out_count;
      ent = c;

      if (free_ent < maxmaxcode)
        {
          CodeTabOf (i) = free_ent++;        /* code -> hashtable */
          HashTabOf (i) = fcode;
        }
      else
        {
          if (! cl_block (output, error))
            return FALSE;
        }
    }

  /*
   * Put out the final code.
   */

  if (! output_code (output, (gint) ent, error))
    return FALSE;

  ++out_count;

  if (! output_code (output, (gint) EOFCode, error))
    return FALSE;

  return TRUE;
}

static gboolean
rle_compress (GOutputStream  *output,
              gint            init_bits,
              ifunptr         ReadValue,
              GError        **error)
{
  glong fcode;
  gint  i /* = 0 */ ;
  gint  c, last;
  gint  ent;
  gint  disp;
  gint  hsize_reg;
  gint  hshift;

  /*
   * Set up the globals:  g_init_bits - initial number of bits
   */

  g_init_bits = init_bits;

  cur_bits = 0;
  cur_accum = 0;

  /*
   * Set up the necessary values
   */

  offset = 0;
  out_count = 0;
  clear_flg = 0;
  in_count = 1;

  ClearCode = (1 << (init_bits - 1));
  EOFCode = ClearCode + 1;
  free_ent = ClearCode + 2;


  /* Had some problems here... should be okay now.  --Adam */
  n_bits = g_init_bits;
  maxcode = MAXCODE (n_bits);


  char_init ();

  last = ent = gif_next_pixel (ReadValue);

  hshift = 0;
  for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L)
    ++hshift;
  hshift = 8 - hshift;                /* set hash code range bound */

  hsize_reg = hsize;
  cl_hash ((glong) hsize_reg);        /* clear hash table */

  if (! output_code (output, (gint) ClearCode, error))
    return FALSE;


  while ((c = gif_next_pixel (ReadValue)) != EOF)
    {
      ++in_count;

      fcode = (long) (((long) c << maxbits) + ent);
      i = (((gint) c << hshift) ^ ent);        /* xor hashing */


      if (last == c) {
        if (HashTabOf (i) == fcode)
          {
            ent = CodeTabOf (i);
            continue;
          }
        else if ((long) HashTabOf (i) < 0)        /* empty slot */
          goto nomatch;
        disp = hsize_reg - i;        /* secondary hash (after G. Knott) */
        if (i == 0)
          disp = 1;
      probe:
        if ((i -= disp) < 0)
          i += hsize_reg;

        if (HashTabOf (i) == fcode)
          {
            ent = CodeTabOf (i);
            continue;
          }
        if ((long) HashTabOf (i) > 0)
          goto probe;
        }
    nomatch:
      if (! output_code (output, (gint) ent, error))
        return FALSE;

      ++out_count;
      last = ent = c;
      if (free_ent < maxmaxcode)
        {
          CodeTabOf (i) = free_ent++;        /* code -> hashtable */
          HashTabOf (i) = fcode;
        }
      else
        {
          if (! cl_block (output, error))
            return FALSE;
        }
    }

  /*
   * Put out the final code.
   */

  if (! output_code (output, (gint) ent, error))
    return FALSE;

  ++out_count;

  if (! output_code (output, (gint) EOFCode, error))
    return FALSE;

  return TRUE;
}

static gboolean
normal_compress (GOutputStream  *output,
                 gint            init_bits,
                 ifunptr         ReadValue,
                 GError        **error)
{
  glong fcode;
  gint  i /* = 0 */ ;
  gint  c;
  gint  ent;
  gint  disp;
  gint  hsize_reg;
  gint  hshift;

  /*
   * Set up the globals:  g_init_bits - initial number of bits
   */

  g_init_bits = init_bits;

  cur_bits = 0;
  cur_accum = 0;

  /*
   * Set up the necessary values
   */

  offset = 0;
  out_count = 0;
  clear_flg = 0;
  in_count = 1;

  ClearCode = (1 << (init_bits - 1));
  EOFCode = ClearCode + 1;
  free_ent = ClearCode + 2;


  /* Had some problems here... should be okay now.  --Adam */
  n_bits = g_init_bits;
  maxcode = MAXCODE (n_bits);


  char_init();

  ent = gif_next_pixel (ReadValue);

  hshift = 0;
  for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L)
    ++hshift;
  hshift = 8 - hshift;                /* set hash code range bound */

  hsize_reg = hsize;
  cl_hash ((glong) hsize_reg);        /* clear hash table */

  if (! output_code (output, (gint) ClearCode, error))
    return FALSE;


  while ((c = gif_next_pixel (ReadValue)) != EOF)
    {
      ++in_count;

      fcode = (long) (((long) c << maxbits) + ent);
      i = (((gint) c << hshift) ^ ent);        /* xor hashing */

      if (HashTabOf (i) == fcode)
        {
          ent = CodeTabOf (i);
          continue;
        }
      else if ((long) HashTabOf (i) < 0)        /* empty slot */
        goto nomatch;
      disp = hsize_reg - i;        /* secondary hash (after G. Knott) */
      if (i == 0)
        disp = 1;
    probe:
      if ((i -= disp) < 0)
        i += hsize_reg;

      if (HashTabOf (i) == fcode)
        {
          ent = CodeTabOf (i);
          continue;
        }
      if ((long) HashTabOf (i) > 0)
        goto probe;
    nomatch:
      if (! output_code (output, (gint) ent, error))
        return FALSE;

      ++out_count;
      ent = c;
      if (free_ent < maxmaxcode)
        {
          CodeTabOf (i) = free_ent++;        /* code -> hashtable */
          HashTabOf (i) = fcode;
        }
      else
        {
          if (! cl_block (output, error))
            return FALSE;
        }
    }

  /*
   * Put out the final code.
   */

  if (! output_code (output, (gint) ent, error))
    return FALSE;

  ++out_count;

  if (! output_code (output, (gint) EOFCode, error))
    return FALSE;

  return TRUE;
}


/*****************************************************************
 * TAG( output )
 *
 * Output the given code.
 * Inputs:
 *      code:   A n_bits-bit integer.  If == -1, then EOF.  This assumes
 *              that n_bits =< (long)wordsize - 1.
 * Outputs:
 *      Outputs code to the file.
 * Assumptions:
 *      Chars are 8 bits long.
 * Algorithm:
 *      Maintain a GIF_BITS character long buffer (so that 8 codes will
 * fit in it exactly).  Use the VAX insv instruction to insert each
 * code in turn.  When the buffer fills up empty it and start over.
 */


static gboolean
output_code (GOutputStream  *output,
             gint            code,
             GError        **error)
{
  cur_accum &= masks[cur_bits];

  if (cur_bits > 0)
    cur_accum |= ((long) code << cur_bits);
  else
    cur_accum = code;

  cur_bits += n_bits;

  while (cur_bits >= 8)
    {
      if (! char_out (output, (guchar) (cur_accum & 0xff), error))
        return FALSE;

      cur_accum >>= 8;
      cur_bits -= 8;
    }

  /*
   * If the next entry is going to be too big for the code size,
   * then increase it, if possible.
   */

  if (free_ent > maxcode || clear_flg)
    {
      if (clear_flg)
        {
          maxcode = MAXCODE (n_bits = g_init_bits);
          clear_flg = 0;
        }
      else
        {
          ++n_bits;
          if (n_bits == maxbits)
            maxcode = maxmaxcode;
          else
            maxcode = MAXCODE (n_bits);
        }
    }

  if (code == EOFCode)
    {
      /*
       * At EOF, write the rest of the buffer.
       */

      while (cur_bits > 0)
        {
          if (! char_out (output, (guchar) (cur_accum & 0xff), error))
            return FALSE;

          cur_accum >>= 8;
          cur_bits -= 8;
        }

      if (! char_flush (output, error))
        return FALSE;
    }

  return TRUE;
}

/*
 * Clear out the hash table
 */

static gboolean
cl_block (GOutputStream  *output,
          GError        **error) /* table clear for block compress */
{
  cl_hash ((glong) hsize);
  free_ent = ClearCode + 2;
  clear_flg = 1;

  return output_code (output, (gint) ClearCode, error);
}

static void
cl_hash (glong hsize)        /* reset code table */
{
  glong *htab_p = htab + hsize;

  long i;
  long m1 = -1;

  i = hsize - 16;
  do
    {                                /* might use Sys V memset(3) here */
      *(htab_p - 16) = m1;
      *(htab_p - 15) = m1;
      *(htab_p - 14) = m1;
      *(htab_p - 13) = m1;
      *(htab_p - 12) = m1;
      *(htab_p - 11) = m1;
      *(htab_p - 10) = m1;
      *(htab_p - 9) = m1;
      *(htab_p - 8) = m1;
      *(htab_p - 7) = m1;
      *(htab_p - 6) = m1;
      *(htab_p - 5) = m1;
      *(htab_p - 4) = m1;
      *(htab_p - 3) = m1;
      *(htab_p - 2) = m1;
      *(htab_p - 1) = m1;
      htab_p -= 16;
    }
  while ((i -= 16) >= 0);

  for (i += 16; i > 0; --i)
    *--htab_p = m1;
}


/******************************************************************************
 * GIF Specific routines
 ******************************************************************************/


/*
 * Number of characters so far in this 'packet'
 */

static int a_count;

/*
 * Set up the 'byte output' routine
 */

static void
char_init (void)
{
  a_count = 0;
}

/*
 * Define the storage for the packet accumulator
 */

static char accum[256];

/*
 * Add a character to the end of the current packet, and if it is 254
 * characters, flush the packet to disk.
 */

static gboolean
char_out (GOutputStream  *output,
          gint            c,
          GError        **error)
{
  accum[a_count++] = c;

  if (a_count >= 254)
    return char_flush (output, error);

  return TRUE;
}

/*
 * Flush the packet to disk, and reset the accumulator
 */

static gboolean
char_flush (GOutputStream  *output,
            GError        **error)
{
  if (a_count > 0)
    {
      if (! put_byte (output, a_count, error))
        return FALSE;

      if (! g_output_stream_write_all (output, accum, a_count,
                                       NULL, NULL, error))
        return FALSE;

      a_count = 0;
    }

  return TRUE;
}

Messung
CHZG
84959290

¤ Dauer der Verarbeitung: 0.52 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.