/*
 * GFLI 1.3
 *
 * A pika plug-in to read and write FLI and FLC movies.
 *
 * Copyright (C) 1998 Jens Ch. Restemeier <jchrr@hrz.uni-bielefeld.de>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * This is a first loader for FLI and FLC movies. It uses as the same method as
 * the gif plug-in to store the animation (i.e. 1 layer/frame).
 *
 * Current disadvantages:
 * - Generates A LOT OF warnings.
 * - Consumes a lot of memory (See wish-list: use the original data or
 *   compression).
 * - doesn't support palette changes between two frames.
 *
 * Wish-List:
 * - I'd like to have a different format for storing animations, so I can use
 *   Layers and Alpha-Channels for effects. An older version of
 *   this plug-in created one image per frame, and went real soon out of
 *   memory.
 * - I'd like a method that requests unmodified frames from the original
 *   image, and stores modified without destroying the original file.
 * - I'd like a way to store additional information about a image to it, for
 *   example copyright stuff or a timecode.
 * - I've thought about a small utility to mix MIDI events as custom chunks
 *   between the FLI chunks. Anyone interested in implementing this ?
 */

/*
 * History:
 * 1.0 first release
 * 1.1 first support for FLI saving (BRUN and LC chunks)
 * 1.2 support for load/save ranges, fixed SGI & SUN problems (I hope...), fixed FLC
 * 1.3 made saving actually work, alpha channel is silently ignored;
       loading was broken too, fixed it  --Sven
 */

#include <config.h>

#include <errno.h>
#include <string.h>

#include <glib/gstdio.h>

#include <libpika/pika.h>
#include <libpika/pikaui.h>

#include "fli.h"

#include "libpika/stdplugins-intl.h"


#define LOAD_PROC      "file-fli-load"
#define SAVE_PROC      "file-fli-save"
#define INFO_PROC      "file-fli-info"
#define PLUG_IN_BINARY "file-fli"
#define PLUG_IN_ROLE   "pika-file-fli"


typedef struct _Fli      Fli;
typedef struct _FliClass FliClass;

struct _Fli
{
  PikaPlugIn      parent_instance;
};

struct _FliClass
{
  PikaPlugInClass parent_class;
};


#define FLI_TYPE  (fli_get_type ())
#define FLI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLI_TYPE, Fli))

GType                   fli_get_type         (void) G_GNUC_CONST;

static GList          * fli_query_procedures (PikaPlugIn            *plug_in);
static PikaProcedure  * fli_create_procedure (PikaPlugIn            *plug_in,
                                              const gchar           *name);

static PikaValueArray * fli_load             (PikaProcedure         *procedure,
                                              PikaRunMode            run_mode,
                                              GFile                 *file,
                                              PikaMetadata          *metadata,
                                              PikaMetadataLoadFlags *flags,
                                              PikaProcedureConfig   *config,
                                              gpointer               run_data);
static PikaValueArray * fli_save             (PikaProcedure         *procedure,
                                              PikaRunMode            run_mode,
                                              PikaImage             *image,
                                              gint                   n_drawables,
                                              PikaDrawable         **drawables,
                                              GFile                 *file,
                                              PikaMetadata          *metadata,
                                              PikaProcedureConfig   *config,
                                              gpointer               run_data);
static PikaValueArray * fli_info             (PikaProcedure         *procedure,
                                              PikaProcedureConfig   *config,
                                              gpointer               run_data);

static PikaImage      * load_image           (GFile                 *file,
                                              GObject               *config,
                                              GError               **error);
static gboolean         load_dialog          (GFile                 *file,
                                              PikaProcedure         *procedure,
                                              GObject               *config);

static gboolean         save_image           (GFile                 *file,
                                              PikaImage             *image,
                                              GObject               *config,
                                              GError               **error);
static gboolean         save_dialog          (PikaImage             *image,
                                              PikaProcedure         *procedure,
                                              GObject               *config);

static gboolean         get_info             (GFile                 *file,
                                              gint32                *width,
                                              gint32                *height,
                                              gint32                *frames,
                                              GError               **error);


G_DEFINE_TYPE (Fli, fli, PIKA_TYPE_PLUG_IN)

PIKA_MAIN (FLI_TYPE)
DEFINE_STD_SET_I18N


static void
fli_class_init (FliClass *klass)
{
  PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass);

  plug_in_class->query_procedures = fli_query_procedures;
  plug_in_class->create_procedure = fli_create_procedure;
  plug_in_class->set_i18n         = STD_SET_I18N;
}

static void
fli_init (Fli *fli)
{
}

static GList *
fli_query_procedures (PikaPlugIn *plug_in)
{
  GList *list = NULL;

  list = g_list_append (list, g_strdup (LOAD_PROC));
  list = g_list_append (list, g_strdup (SAVE_PROC));
  list = g_list_append (list, g_strdup (INFO_PROC));

  return list;
}

static PikaProcedure *
fli_create_procedure (PikaPlugIn  *plug_in,
                      const gchar *name)
{
  PikaProcedure *procedure = NULL;

  if (! strcmp (name, LOAD_PROC))
    {
      procedure = pika_load_procedure_new (plug_in, name,
                                           PIKA_PDB_PROC_TYPE_PLUGIN,
                                           fli_load, NULL, NULL);

      pika_procedure_set_menu_label (procedure, _("AutoDesk FLIC animation"));

      pika_procedure_set_documentation (procedure,
                                        _("Load FLI-movies"),
                                        _("This is an experimental plug-in to "
                                          "handle FLI movies"),
                                        name);
      pika_procedure_set_attribution (procedure,
                                      "Jens Ch. Restemeier",
                                      "Jens Ch. Restemeier",
                                      "1997");

      pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
                                          "image/x-flic");
      pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
                                          "fli,flc");

      PIKA_PROC_ARG_INT (procedure, "from-frame",
                         _("_From frame"),
                         _("Load beginning from this frame"),
                         -1, G_MAXINT, -1,
                         G_PARAM_READWRITE);

      PIKA_PROC_ARG_INT (procedure, "to-frame",
                         _("_To frame"),
                         _("End loading with this frame"),
                         -1, G_MAXINT, -1,
                         G_PARAM_READWRITE);
    }
  else if (! strcmp (name, SAVE_PROC))
    {
      procedure = pika_save_procedure_new (plug_in, name,
                                           PIKA_PDB_PROC_TYPE_PLUGIN,
                                           FALSE, fli_save, NULL, NULL);

      pika_procedure_set_image_types (procedure, "INDEXED, GRAY");

      pika_procedure_set_menu_label (procedure, _("AutoDesk FLIC animation"));
      pika_file_procedure_set_format_name (PIKA_FILE_PROCEDURE (procedure),
                                           _("FLI Animation"));
      pika_procedure_set_documentation (procedure,
                                        _("Export FLI-movies"),
                                        _("This is an experimental plug-in to "
                                          "handle FLI movies"),
                                        name);
      pika_procedure_set_attribution (procedure,
                                      "Jens Ch. Restemeier",
                                      "Jens Ch. Restemeier",
                                      "1997");

      pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
                                          "image/x-flic");
      pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
                                          "fli,flc");

      PIKA_PROC_ARG_INT (procedure, "from-frame",
                         _("_From frame"),
                         _("Export beginning from this frame"),
                         -1, G_MAXINT, -1,
                         G_PARAM_READWRITE);

      PIKA_PROC_ARG_INT (procedure, "to-frame",
                         _("_To frame"),
                         _("End exporting with this frame "
                           "(or -1 for all frames)"),
                         -1, G_MAXINT, -1,
                         G_PARAM_READWRITE);
    }
  else if (! strcmp (name, INFO_PROC))
    {
      procedure = pika_procedure_new (plug_in, name,
                                      PIKA_PDB_PROC_TYPE_PLUGIN,
                                      fli_info, NULL, NULL);

      pika_procedure_set_documentation (procedure,
                                        "Get information about a Fli movie",
                                        "This is an experimental plug-in to "
                                        "handle FLI movies",
                                        name);
      pika_procedure_set_attribution (procedure,
                                      "Jens Ch. Restemeier",
                                      "Jens Ch. Restemeier",
                                      "1997");

      PIKA_PROC_ARG_FILE (procedure, "file",
                          "File",
                          "The local file to get info about",
                          G_PARAM_READWRITE);

      PIKA_PROC_VAL_INT (procedure, "width",
                         "Width",
                         "Width of one frame",
                         0, PIKA_MAX_IMAGE_SIZE, 0,
                         G_PARAM_READWRITE);

      PIKA_PROC_VAL_INT (procedure, "height",
                         "Height",
                         "Height of one frame",
                         0, PIKA_MAX_IMAGE_SIZE, 0,
                         G_PARAM_READWRITE);

      PIKA_PROC_VAL_INT (procedure, "frames",
                         "Frames",
                         "Number of frames",
                         0, G_MAXINT, 0,
                         G_PARAM_READWRITE);
    }

  return procedure;
}

static PikaValueArray *
fli_load (PikaProcedure         *procedure,
          PikaRunMode            run_mode,
          GFile                 *file,
          PikaMetadata          *metadata,
          PikaMetadataLoadFlags *flags,
          PikaProcedureConfig   *config,
          gpointer               run_data)
{
  PikaValueArray *return_vals;
  PikaImage      *image;
  GError         *error = NULL;

  gegl_init (NULL, NULL);

  if (run_mode == PIKA_RUN_INTERACTIVE)
    {
      if (! load_dialog (file, procedure, G_OBJECT (config)))
        return pika_procedure_new_return_values (procedure,
                                                 PIKA_PDB_CANCEL,
                                                 NULL);
    }

  image = load_image (file, G_OBJECT (config), &error);

  if (! image)
    return pika_procedure_new_return_values (procedure,
                                             PIKA_PDB_EXECUTION_ERROR,
                                             error);

  return_vals = pika_procedure_new_return_values (procedure,
                                                  PIKA_PDB_SUCCESS,
                                                  NULL);

  PIKA_VALUES_SET_IMAGE (return_vals, 1, image);

  return return_vals;
}

static PikaValueArray *
fli_save (PikaProcedure        *procedure,
          PikaRunMode           run_mode,
          PikaImage            *image,
          gint                  n_drawables,
          PikaDrawable        **drawables,
          GFile                *file,
          PikaMetadata         *metadata,
          PikaProcedureConfig  *config,
          gpointer              run_data)
{
  PikaPDBStatusType  status = PIKA_PDB_SUCCESS;
  PikaExportReturn   export = PIKA_EXPORT_CANCEL;
  GError            *error  = NULL;

  gegl_init (NULL, NULL);

  switch (run_mode)
    {
    case PIKA_RUN_INTERACTIVE:
    case PIKA_RUN_WITH_LAST_VALS:
      pika_ui_init (PLUG_IN_BINARY);

      export = pika_export_image (&image, &n_drawables, &drawables, "FLI",
                                  PIKA_EXPORT_CAN_HANDLE_INDEXED |
                                  PIKA_EXPORT_CAN_HANDLE_GRAY    |
                                  PIKA_EXPORT_CAN_HANDLE_ALPHA   |
                                  PIKA_EXPORT_CAN_HANDLE_LAYERS);

      if (export == PIKA_EXPORT_CANCEL)
        return pika_procedure_new_return_values (procedure,
                                                 PIKA_PDB_CANCEL,
                                                 NULL);
      break;

    default:
      break;
    }

  if (run_mode == PIKA_RUN_INTERACTIVE)
    {
      if (! save_dialog (image, procedure, G_OBJECT (config)))
        status = PIKA_PDB_CANCEL;
    }

  if (status == PIKA_PDB_SUCCESS)
    {
      if (! save_image (file, image, G_OBJECT (config),
                        &error))
        {
          status = PIKA_PDB_EXECUTION_ERROR;
        }
    }

  if (export == PIKA_EXPORT_EXPORT)
    {
      pika_image_delete (image);
      g_free (drawables);
    }

  return pika_procedure_new_return_values (procedure, status, error);
}

static PikaValueArray *
fli_info (PikaProcedure        *procedure,
          PikaProcedureConfig  *config,
          gpointer              run_data)
{
  PikaValueArray *return_vals;
  GFile          *file;
  gint32          width;
  gint32          height;
  gint32          frames;
  GError         *error = NULL;

  g_object_get (config, "file", &file, NULL);

  if (! get_info (file, &width, &height, &frames,
                  &error))
    {
      return pika_procedure_new_return_values (procedure,
                                               PIKA_PDB_EXECUTION_ERROR,
                                               error);
    }

  return_vals = pika_procedure_new_return_values (procedure,
                                                  PIKA_PDB_SUCCESS,
                                                  NULL);

  PIKA_VALUES_SET_INT (return_vals, 1, width);
  PIKA_VALUES_SET_INT (return_vals, 2, height);
  PIKA_VALUES_SET_INT (return_vals, 3, frames);

  return return_vals;
}

/*
 * Open FLI animation and return header-info
 */
static gboolean
get_info (GFile   *file,
          gint32  *width,
          gint32  *height,
          gint32  *frames,
          GError **error)
{
  FILE         *fp;
  s_fli_header  fli_header;

  *width = 0; *height = 0; *frames = 0;

  fp = g_fopen (g_file_peek_path (file),"rb");

  if (! fp)
    {
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Could not open '%s' for reading: %s"),
                   pika_file_get_utf8_name (file), g_strerror (errno));
      return FALSE;
    }

  if (! fli_read_header (fp, &fli_header, error))
    {
      fclose (fp);
      return FALSE;
    }
  fclose (fp);

  *width  = fli_header.width;
  *height = fli_header.height;
  *frames = fli_header.frames;

  return TRUE;
}

/*
 * load fli animation and store as framestack
 */
static PikaImage *
load_image (GFile    *file,
            GObject  *config,
            GError  **error)
{
  FILE         *fp;
  GeglBuffer   *buffer;
  PikaImage    *image;
  PikaLayer    *layer;
  guchar       *fb, *ofb, *fb_x;
  guchar        cm[768], ocm[768];
  s_fli_header  fli_header;
  gint          cnt;
  gint          from_frame;
  gint          to_frame;

  g_object_get (config,
                "from-frame", &from_frame,
                "to-frame",   &to_frame,
                NULL);

  pika_progress_init_printf (_("Opening '%s'"),
                             pika_file_get_utf8_name (file));

  fp = g_fopen (g_file_peek_path (file) ,"rb");

  if (! fp)
    {
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Could not open '%s' for reading: %s"),
                   pika_file_get_utf8_name (file), g_strerror (errno));
      return NULL;
    }

  if (! fli_read_header (fp, &fli_header, error))
    {
      fclose (fp);
      return NULL;
    }

  fseek (fp, 128, SEEK_SET);

  /*
   * Fix parameters
   */
  if ((from_frame == -1) && (to_frame == -1))
    {
      /* to make scripting easier: */
      from_frame = 1;
      to_frame   = fli_header.frames;
    }

  if (to_frame < from_frame)
    {
      to_frame = fli_header.frames;
    }

  if (from_frame < 1)
    {
      from_frame = 1;
    }

  if (to_frame < 1)
    {
      /* nothing to do ... */
      fclose (fp);
      return NULL;
    }

  if (from_frame > fli_header.frames)
    {
      /* nothing to do ... */
      fclose (fp);
      return NULL;
    }

  if (to_frame > fli_header.frames)
    {
      to_frame = fli_header.frames;
    }

  image = pika_image_new (fli_header.width, fli_header.height, PIKA_INDEXED);

  fb  = g_malloc (fli_header.width * fli_header.height);
  ofb = g_malloc (fli_header.width * fli_header.height);

  /*
   * Skip to the beginning of requested frames:
   */
  for (cnt = 1; cnt < from_frame; cnt++)
    {
      if (! fli_read_frame (fp, &fli_header, ofb, ocm, fb, cm, error))
        {
          fclose (fp);
          g_free (fb);
          g_free (ofb);
          return FALSE;
        }
      memcpy (ocm, cm, 768);
      fb_x = fb; fb = ofb; ofb = fb_x;
    }
  /*
   * Load range
   */
  for (cnt = from_frame; cnt <= to_frame; cnt++)
    {
      gchar *name_buf = g_strdup_printf (_("Frame %d (%ums)"), cnt, fli_header.speed);

      g_debug ("Loading frame %d", cnt);

      layer = pika_layer_new (image, name_buf,
                              fli_header.width, fli_header.height,
                              PIKA_INDEXED_IMAGE,
                              100,
                              pika_image_get_default_new_layer_mode (image));
      g_free (name_buf);

      if (! fli_read_frame (fp, &fli_header, ofb, ocm, fb, cm, error))
        {
          /* Since some of the frames could have been read, let's not make
           * this fatal, unless it's the first frame. */
          if (error && *error)
            {
              pika_item_delete (PIKA_ITEM(layer));
              if (cnt > from_frame)
                {
                  g_warning ("Failed to read frame %d. Possibly corrupt animation.\n%s",
                              cnt, (*error)->message);
                  g_clear_error (error);
                }
              else
                {
                  pika_image_delete (image);
                  g_prefix_error (error, _("Failed to read frame %d. Possibly corrupt animation.\n"), cnt);
                  fclose (fp);
                  g_free (fb);
                  g_free (ofb);
                  return FALSE;
                }
            }

          break;
        }

      buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (layer));

      gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0,
                                               fli_header.width,
                                               fli_header.height), 0,
                       NULL, fb, GEGL_AUTO_ROWSTRIDE);

      g_object_unref (buffer);

      if (cnt > 0)
        pika_layer_add_alpha (layer);

      pika_image_insert_layer (image, layer, NULL, 0);

      if (cnt < to_frame)
        {
          memcpy (ocm, cm, 768);
          fb_x = fb; fb = ofb; ofb = fb_x;
        }

      if (to_frame > from_frame)
        pika_progress_update ((double) cnt + 1 / (double)(to_frame - from_frame));
    }

  pika_image_set_colormap (image, cm, 256);

  fclose (fp);

  g_free (fb);
  g_free (ofb);

  pika_progress_update (1.0);

  return image;
}


#define MAXDIFF 195075    /*  3 * SQR (255) + 1  */

/*
 * get framestack and store as fli animation
 * (some code was taken from the GIF plugin.)
 */
static gboolean
save_image (GFile      *file,
            PikaImage  *image,
            GObject    *config,
            GError    **error)
{
  FILE         *fp;
  GList        *framelist;
  GList        *iter;
  gint          n_frames;
  gint          colors, i;
  guchar       *cmap;
  guchar        bg;
  guchar        red, green, blue;
  gint          diff, sum, max;
  gint          offset_x, offset_y, xc, yc, xx, yy;
  guint         rows, cols, bytes;
  guchar       *src_row;
  guchar       *fb, *ofb;
  guchar        cm[768];
  PikaRGB       background;
  s_fli_header  fli_header;
  gint          cnt;
  gint          from_frame;
  gint          to_frame;
  gboolean      write_ok = FALSE;

  g_object_get (config,
                "from-frame", &from_frame,
                "to-frame",   &to_frame,
                NULL);

  framelist = pika_image_list_layers (image);
  framelist = g_list_reverse (framelist);
  n_frames  = g_list_length (framelist);

  if ((from_frame == -1) && (to_frame == -1))
    {
      /* to make scripting easier: */
      from_frame = 1;
      to_frame   = n_frames;
    }
  if (to_frame < from_frame)
    {
      to_frame = n_frames;
    }
  if (from_frame < 1)
    {
      from_frame = 1;
    }
  if (to_frame < 1)
    {
      /* nothing to do ... */
      return FALSE;
    }
  if (from_frame > n_frames)
    {
      /* nothing to do ... */
      return FALSE;
    }
  if (to_frame > n_frames)
    {
      to_frame = n_frames;
    }

  pika_context_get_background (&background);
  pika_rgb_get_uchar (&background, &red, &green, &blue);

  switch (pika_image_get_base_type (image))
    {
    case PIKA_GRAY:
      /* build grayscale palette */
      for (i = 0; i < 256; i++)
        {
          cm[i*3+0] = cm[i*3+1] = cm[i*3+2] = i;
        }
      bg = PIKA_RGB_LUMINANCE (red, green, blue) + 0.5;
      break;

    case PIKA_INDEXED:
      max = MAXDIFF;
      bg = 0;
      cmap = pika_image_get_colormap (image, NULL, &colors);
      for (i = 0; i < MIN (colors, 256); i++)
        {
          cm[i*3+0] = cmap[i*3+0];
          cm[i*3+1] = cmap[i*3+1];
          cm[i*3+2] = cmap[i*3+2];

          diff = red - cm[i*3+0];
          sum = SQR (diff);
          diff = green - cm[i*3+1];
          sum +=  SQR (diff);
          diff = blue - cm[i*3+2];
          sum += SQR (diff);

          if (sum < max)
            {
              bg = i;
              max = sum;
            }
        }
      for (i = colors; i < 256; i++)
        {
          cm[i*3+0] = cm[i*3+1] = cm[i*3+2] = i;
        }
      break;

    default:
      /* Not translating this, since we should never get this error, unless
       * someone messed up setting supported image types. */
      g_set_error (error, PIKA_PLUG_IN_ERROR, 0,
                   "Exporting of RGB images is not supported!");
      return FALSE;
    }

  pika_progress_init_printf (_("Exporting '%s'"),
                             pika_file_get_utf8_name (file));

  /*
   * First build the fli header.
   */
  fli_header.filesize = 0;  /* will be fixed when writing the header */
  fli_header.frames   = 0;  /* will be fixed during the write */
  fli_header.width    = pika_image_get_width (image);
  fli_header.height   = pika_image_get_height (image);

  if ((fli_header.width == 320) && (fli_header.height == 200))
    {
      fli_header.magic = HEADER_FLI;
    }
  else
    {
      fli_header.magic = HEADER_FLC;
    }
  fli_header.depth    = 8;  /* I've never seen a depth != 8 */
  fli_header.flags    = 3;
  fli_header.speed    = 1000 / 25;
  fli_header.created  = 0;  /* program ID. not necessary... */
  fli_header.updated  = 0;  /* date in MS-DOS format. ignore...*/
  fli_header.aspect_x = 1;  /* aspect ratio. Will be added as soon.. */
  fli_header.aspect_y = 1;  /* ... as PIKA supports it. */
  fli_header.oframe1  = fli_header.oframe2 = 0; /* will be fixed during the write */

  fp = g_fopen (g_file_peek_path (file) , "wb");

  if (! fp)
    {
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Could not open '%s' for writing: %s"),
                   pika_file_get_utf8_name (file), g_strerror (errno));
      return FALSE;
    }
  fseek (fp, 128, SEEK_SET);

  fb = g_malloc (fli_header.width * fli_header.height);
  ofb = g_malloc (fli_header.width * fli_header.height);

  /* initialize with bg color */
  memset (fb, bg, fli_header.width * fli_header.height);

  /*
   * Now write all frames
   */
  for (iter = g_list_nth (framelist, from_frame - 1), cnt = from_frame;
       iter && cnt <= to_frame;
       iter = g_list_next (iter), cnt++)
    {
      PikaDrawable *drawable = iter->data;
      GeglBuffer   *buffer;
      const Babl   *format = NULL;

      buffer = pika_drawable_get_buffer (drawable);

      g_debug ("Writing frame: %d", cnt);

      if (pika_drawable_is_gray (drawable))
        {
          if (pika_drawable_has_alpha (drawable))
            format = babl_format ("Y' u8");
          else
            format = babl_format ("Y'A u8");
        }
      else
        {
          format = gegl_buffer_get_format (buffer);
        }

      cols = gegl_buffer_get_width  (buffer);
      rows = gegl_buffer_get_height (buffer);

      pika_drawable_get_offsets (drawable, &offset_x, &offset_y);

      bytes = babl_format_get_bytes_per_pixel (format);

      src_row = g_malloc (cols * bytes);

      /* now paste it into the framebuffer, with the necessary offset */
      for (yc = 0, yy = offset_y; yc < rows; yc++, yy++)
        {
          if (yy >= 0 && yy < fli_header.height)
            {
              gegl_buffer_get (buffer, GEGL_RECTANGLE (0, yc, cols, 1), 1.0,
                               format, src_row,
                               GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);

              for (xc = 0, xx = offset_x; xc < cols; xc++, xx++)
                {
                  if (xx >= 0 && xx < fli_header.width)
                    fb[yy * fli_header.width + xx] = src_row[xc * bytes];
                }
            }
        }

      g_free (src_row);
      g_object_unref (buffer);

      /* save the frame */
      if (cnt > from_frame)
        {
          /* save frame, allow all codecs */
          write_ok = fli_write_frame (fp, &fli_header, ofb, cm, fb, cm, W_ALL, error);
        }
      else
        {
          /* save first frame, no delta information, allow all codecs */
          write_ok = fli_write_frame (fp, &fli_header, NULL, NULL, fb, cm, W_ALL, error);
        }
      if (! write_ok)
        break;

      if (cnt < to_frame)
        memcpy (ofb, fb, fli_header.width * fli_header.height);

      pika_progress_update ((double) cnt + 1 / (double)(to_frame - from_frame));
    }

  /*
   * finish fli
   */
  if (write_ok)
    write_ok = fli_write_header (fp, &fli_header, error);
  fclose (fp);

  g_free (fb);
  g_free (ofb);
  g_list_free (framelist);

  pika_progress_update (1.0);

  return write_ok;
}

/*
 * Dialogs for interactive usage
 */
static gboolean
load_dialog (GFile         *file,
             PikaProcedure *procedure,
             GObject       *config)
{
  GtkWidget *dialog;
  GtkWidget *vbox;
  gint       width, height, n_frames;
  gboolean   run;

  get_info (file, &width, &height, &n_frames, NULL);

  g_object_set (config,
                "from-frame", 1,
                "to-frame",   n_frames,
                NULL);

  pika_ui_init (PLUG_IN_BINARY);

  dialog = pika_procedure_dialog_new (procedure,
                                      PIKA_PROCEDURE_CONFIG (config),
                                      _("Open FLIC Animation"));

  vbox = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog),
                                         "fli-vbox", "from-frame", "to-frame",
                                         NULL);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);

  pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog), "fli-vbox",
                              NULL);
  gtk_widget_show (dialog);

  run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog));

  gtk_widget_destroy (dialog);

  return run;
}

static gboolean
save_dialog (PikaImage     *image,
             PikaProcedure *procedure,
             GObject       *config)
{
  GtkWidget *dialog;
  gint       n_frames;
  gboolean   run;

  g_free (pika_image_get_layers (image, &n_frames));

  g_object_set (config,
                "from-frame", 1,
                "to-frame",   n_frames,
                NULL);

  dialog = pika_save_procedure_dialog_new (PIKA_SAVE_PROCEDURE (procedure),
                                           PIKA_PROCEDURE_CONFIG (config),
                                           image);
  /*
   * Maybe I add on-the-fly RGB conversion, to keep palettechanges...
   * But for now you can set a start- and a end-frame:
   */

  pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog), NULL);

  gtk_widget_show (dialog);

  run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog));

  gtk_widget_destroy (dialog);

  return run;
}
