/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 * SUN raster reading and writing code Copyright (C) 1996 Peter Kirchgessner
 * (email: peter@kirchgessner.net, WWW: http://www.kirchgessner.net)
 *
 * 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 program was written using pages 625-629 of the book
 * "Encyclopedia of Graphics File Formats", Murray/van Ryper,
 * O'Reilly & Associates Inc.
 * Bug reports or suggestions should be e-mailed to peter@kirchgessner.net
 */

/* Event history:
 * V 1.00, PK, 25-Jul-96: First try
 * V 1.90, PK, 15-Mar-97: Upgrade to work with GIMP V0.99
 * V 1.91, PK, 05-Apr-97: Return all arguments, even in case of an error
 * V 1.92, PK, 18-May-97: Ignore EOF-error on reading image data
 * V 1.93, PK, 05-Oct-97: Parse rc file
 * V 1.94, PK, 12-Oct-97: No progress bars for non-interactive mode
 * V 1.95, nn, 20-Dec-97: Initialize some variable
 * V 1.96, PK, 21-Nov-99: Internationalization
 * V 1.97, PK, 20-Dec-00: Recognize extensions .rs and .ras too
 */

#include "config.h"

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

#include <glib/gstdio.h>

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

#include "libgimp/stdplugins-intl.h"


#define LOAD_PROC      "file-sunras-load"
#define SAVE_PROC      "file-sunras-save"
#define PLUG_IN_BINARY "file-sunras"
#define PLUG_IN_ROLE   "gimp-file-sunras"


typedef int WRITE_FUN(void*,size_t,size_t,FILE*);

typedef gulong  L_CARD32;
typedef gushort L_CARD16;
typedef guchar  L_CARD8;

/* Fileheader of SunRaster files */
typedef struct
{
  L_CARD32 l_ras_magic;    /* Magic Number */
  L_CARD32 l_ras_width;    /* Width */
  L_CARD32 l_ras_height;   /* Height */
  L_CARD32 l_ras_depth;    /* Number of bits per pixel (1,8,24,32) */
  L_CARD32 l_ras_length;   /* Length of image data (but may also be 0) */
  L_CARD32 l_ras_type;     /* Encoding */
  L_CARD32 l_ras_maptype;  /* Type of colormap */
  L_CARD32 l_ras_maplength;/* Number of bytes for colormap */
} L_SUNFILEHEADER;

/* Sun-raster magic */
#define RAS_MAGIC 0x59a66a95

#define RAS_TYPE_STD 1    /* Standard uncompressed format */
#define RAS_TYPE_RLE 2    /* Runlength compression format */

typedef struct
{
  gint val;   /* The value that is to be repeated */
  gint n;     /* How many times it is repeated */
} RLEBUF;


/* Declare some local functions.
 */
static void      query            (void);
static void      run              (const gchar       *name,
                                   gint               nparams,
                                   const GimpParam   *param,
                                   gint              *nreturn_vals,
                                   GimpParam        **return_vals);

static gint32    load_image       (const gchar       *filename,
                                   GError           **error);
static gboolean  save_image       (const gchar       *filename,
                                   gint32             image_ID,
                                   gint32             drawable_ID,
                                   GError           **error);

static void      set_color_table  (gint32             image_ID,
                                   L_SUNFILEHEADER   *sunhdr,
                                   const guchar      *suncolmap);
static gint32    create_new_image (const gchar       *filename,
                                   guint              width,
                                   guint              height,
                                   GimpImageBaseType  type,
                                   gint32            *layer_ID,
                                   GeglBuffer       **buffer);

static gint32    load_sun_d1      (const gchar       *filename,
                                   FILE              *ifp,
                                   L_SUNFILEHEADER   *sunhdr,
                                   guchar            *suncolmap);
static gint32    load_sun_d8      (const gchar       *filename,
                                   FILE              *ifp,
                                   L_SUNFILEHEADER   *sunhdr,
                                   guchar            *suncolmap);
static gint32    load_sun_d24     (const gchar       *filename,
                                   FILE              *ifp,
                                   L_SUNFILEHEADER   *sunhdr,
                                   guchar            *suncolmap);
static gint32    load_sun_d32     (const gchar       *filename,
                                   FILE              *ifp,
                                   L_SUNFILEHEADER   *sunhdr,
                                   guchar            *suncolmap);

static L_CARD32  read_card32      (FILE              *ifp,
                                   int               *err);

static void      write_card32     (FILE              *ofp,
                                   L_CARD32           c);

static void      byte2bit         (guchar            *byteline,
                                   int                width,
                                   guchar            *bitline,
                                   gboolean           invert);

static void      rle_startread    (FILE              *ifp);
static int       rle_fread        (char              *ptr,
                                   int                sz,
                                   int                nelem,
                                   FILE              *ifp);
static int       rle_fgetc        (FILE              *ifp);
#define rle_getc(fp) ((rlebuf.n > 0) ? (rlebuf.n)--,rlebuf.val : rle_fgetc (fp))

static void      rle_startwrite   (FILE              *ofp);
static int       rle_fwrite       (char              *ptr,
                                   int                sz,
                                   int                nelem,
                                   FILE              *ofp);
static int       rle_fputc        (int                val,
                                   FILE              *ofp);
static int       rle_putrun       (int                n,
                                   int                val,
                                   FILE              *ofp);
static void      rle_endwrite     (FILE              *ofp);
#define rle_putc rle_fputc

static void      read_sun_header  (FILE              *ifp,
                                   L_SUNFILEHEADER   *sunhdr);
static void      write_sun_header (FILE              *ofp,
                                   L_SUNFILEHEADER   *sunhdr);
static void      read_sun_cols    (FILE              *ifp,
                                   L_SUNFILEHEADER   *sunhdr,
                                   guchar            *colormap);
static void      write_sun_cols   (FILE              *ofp,
                                   L_SUNFILEHEADER   *sunhdr,
                                   guchar            *colormap);

static gint      save_index       (FILE             *ofp,
                                   gint32            image_ID,
                                   gint32            drawable_ID,
                                   gboolean          grey,
                                   gboolean          rle);
static gint      save_rgb         (FILE             *ofp,
                                   gint32            image_ID,
                                   gint32            drawable_ID,
                                   gboolean          rle);

static gboolean  save_dialog      (void);

/* Portability kludge */
static int       my_fwrite        (void             *ptr,
                                   int               size,
                                   int               nmemb,
                                   FILE             *stream);


static int    read_msb_first = 1;
static RLEBUF rlebuf;


const GimpPlugInInfo PLUG_IN_INFO =
{
  NULL,  /* init_proc  */
  NULL,  /* quit_proc  */
  query, /* query_proc */
  run,   /* run_proc   */
};


/* Export info  */
typedef struct
{
  gboolean  rle;  /*  rle or standard */
} SUNRASSaveVals;


static SUNRASSaveVals psvals =
{
  TRUE     /* rle */
};


MAIN ()


static void
query (void)
{
  static const GimpParamDef load_args[] =
  {
    { GIMP_PDB_INT32,  "run-mode",      "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
    { GIMP_PDB_STRING, "filename",      "The name of the file to load" },
    { GIMP_PDB_STRING, "raw-filename",  "The name of the file to load" }
  };

  static const GimpParamDef load_return_vals[] =
  {
    { GIMP_PDB_IMAGE,  "image",         "Output image" }
  };

  static const GimpParamDef save_args[] =
  {
    { GIMP_PDB_INT32,    "run-mode",     "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
    { GIMP_PDB_IMAGE,    "image",        "Input image" },
    { GIMP_PDB_DRAWABLE, "drawable",     "Drawable to export" },
    { GIMP_PDB_STRING,   "filename",     "The name of the file to export the image in" },
    { GIMP_PDB_STRING,   "raw-filename", "The name of the file to export the image in" },
    { GIMP_PDB_INT32,    "rle",          "Specify non-zero for rle output, zero for standard output" }
  };

  gimp_install_procedure (LOAD_PROC,
                          "load file of the SunRaster file format",
                          "load file of the SunRaster file format",
                          "Peter Kirchgessner",
                          "Peter Kirchgessner",
                          "1996",
                          N_("SUN Rasterfile image"),
                          NULL,
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (load_args),
                          G_N_ELEMENTS (load_return_vals),
                          load_args, load_return_vals);

  gimp_register_file_handler_mime (LOAD_PROC, "image/x-sun-raster");
  gimp_register_magic_load_handler (LOAD_PROC,
                                    "im1,im8,im24,im32,rs,ras",
                                    "",
                                    "0,long,0x59a66a95");

  gimp_install_procedure (SAVE_PROC,
                          "export file in the SunRaster file format",
                          "SUNRAS exporting handles all image types except "
                          "those with alpha channels.",
                          "Peter Kirchgessner",
                          "Peter Kirchgessner",
                          "1996",
                          N_("SUN Rasterfile image"),
                          "RGB, GRAY, INDEXED",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (save_args), 0,
                          save_args, NULL);

  gimp_register_file_handler_mime (SAVE_PROC, "image/x-sun-raster");
  gimp_register_save_handler (SAVE_PROC,
                              "im1,im8,im24,im32,rs,ras", "");
}


static void
run (const gchar      *name,
     gint              nparams,
     const GimpParam  *param,
     gint             *nreturn_vals,
     GimpParam       **return_vals)
{
  static GimpParam   values[2];
  GimpRunMode        run_mode;
  GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
  gint32             image_ID;
  gint32             drawable_ID;
  GimpExportReturn   export = GIMP_EXPORT_CANCEL;
  GError            *error  = NULL;

  run_mode = param[0].data.d_int32;

  INIT_I18N ();
  gegl_init (NULL, NULL);

  *nreturn_vals = 1;
  *return_vals  = values;

  values[0].type          = GIMP_PDB_STATUS;
  values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;

  if (strcmp (name, LOAD_PROC) == 0)
    {
      image_ID = load_image (param[1].data.d_string, &error);

      if (image_ID != -1)
        {
          *nreturn_vals = 2;
          values[1].type         = GIMP_PDB_IMAGE;
          values[1].data.d_image = image_ID;
        }
      else
        {
          status = GIMP_PDB_EXECUTION_ERROR;
        }
    }
  else if (strcmp (name, SAVE_PROC) == 0)
    {
      image_ID = param[1].data.d_int32;
      drawable_ID = param[2].data.d_int32;

      /*  eventually export the image */
      switch (run_mode)
        {
        case GIMP_RUN_INTERACTIVE:
        case GIMP_RUN_WITH_LAST_VALS:
          gimp_ui_init (PLUG_IN_BINARY, FALSE);

          export = gimp_export_image (&image_ID, &drawable_ID, "SUNRAS",
                                      GIMP_EXPORT_CAN_HANDLE_RGB  |
                                      GIMP_EXPORT_CAN_HANDLE_GRAY |
                                      GIMP_EXPORT_CAN_HANDLE_INDEXED);

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

      switch (run_mode)
        {
        case GIMP_RUN_INTERACTIVE:
          /*  Possibly retrieve data  */
          gimp_get_data (SAVE_PROC, &psvals);

          /*  First acquire information with a dialog  */
          if (! save_dialog ())
            status = GIMP_PDB_CANCEL;
          break;

        case GIMP_RUN_NONINTERACTIVE:
          /*  Make sure all the arguments are there!  */
          if (nparams != 6)
            {
              status = GIMP_PDB_CALLING_ERROR;
            }
          else
            {
              psvals.rle = (param[5].data.d_int32) ? TRUE : FALSE;
            }
          break;

        case GIMP_RUN_WITH_LAST_VALS:
          /*  Possibly retrieve data  */
          gimp_get_data (SAVE_PROC, &psvals);
          break;

        default:
          break;
        }

      if (status == GIMP_PDB_SUCCESS)
        {
          if (save_image (param[3].data.d_string, image_ID, drawable_ID,
                          &error))
            {
              /*  Store psvals data  */
              gimp_set_data (SAVE_PROC, &psvals, sizeof (SUNRASSaveVals));
            }
          else
            {
              status = GIMP_PDB_EXECUTION_ERROR;
            }
        }

      if (export == GIMP_EXPORT_EXPORT)
        gimp_image_delete (image_ID);
    }
  else
    {
      status = GIMP_PDB_CALLING_ERROR;
    }

  if (status != GIMP_PDB_SUCCESS && error)
    {
      *nreturn_vals = 2;
      values[1].type          = GIMP_PDB_STRING;
      values[1].data.d_string = error->message;
    }

  values[0].data.d_status = status;
}


static gint32
load_image (const gchar  *filename,
            GError      **error)
{
  gint32           image_ID;
  FILE            *ifp;
  L_SUNFILEHEADER  sunhdr;
  guchar          *suncolmap = NULL;

  gimp_progress_init_printf (_("Opening '%s'"),
                             gimp_filename_to_utf8 (filename));

  ifp = g_fopen (filename, "rb");
  if (!ifp)
    {
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Could not open '%s' for reading: %s"),
                   gimp_filename_to_utf8 (filename), g_strerror (errno));
      return -1;
    }

  read_msb_first = 1;   /* SUN raster is always most significant byte first */

  read_sun_header (ifp, &sunhdr);
  if (sunhdr.l_ras_magic != RAS_MAGIC)
    {
      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                   _("Could not open '%s' as SUN-raster-file"),
                   gimp_filename_to_utf8 (filename));
      fclose (ifp);
      return -1;
    }

  if (sunhdr.l_ras_type > 5)
    {
      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                   "%s",
                   _("The type of this SUN-rasterfile is not supported"));
      fclose (ifp);
      return -1;
    }

  if (sunhdr.l_ras_maplength > (256 * 3))
    {
      g_message ("Map lengths greater than 256 entries are unsupported by GIMP.");
      gimp_quit ();
    }

  /* Is there a RGB colormap ? */
  if ((sunhdr.l_ras_maptype == 1) && (sunhdr.l_ras_maplength > 0))
    {
      suncolmap = g_new (guchar, sunhdr.l_ras_maplength);

      read_sun_cols (ifp, &sunhdr, suncolmap);
#ifdef DEBUG
      {
        int j, ncols;
        printf ("File %s\n",filename);
        ncols = sunhdr.l_ras_maplength/3;
        for (j=0; j < ncols; j++)
          printf ("Entry 0x%08x: 0x%04x,  0x%04x, 0x%04x\n",
                  j,suncolmap[j],suncolmap[j+ncols],suncolmap[j+2*ncols]);
      }
#endif
      if (sunhdr.l_ras_magic != RAS_MAGIC)
        {
          g_message (_("Could not read color entries from '%s'"),
                     gimp_filename_to_utf8 (filename));
          fclose (ifp);
          g_free (suncolmap);
          return -1;
        }
    }
  else if (sunhdr.l_ras_maplength > 0)
    {
      g_message (_("Type of colormap not supported"));
      fseek (ifp, (sizeof (L_SUNFILEHEADER)/sizeof (L_CARD32))
             *4 + sunhdr.l_ras_maplength, SEEK_SET);
    }

  if (sunhdr.l_ras_width <= 0)
    {
      g_message (_("'%s':\nNo image width specified"),
                 gimp_filename_to_utf8 (filename));
      fclose (ifp);
      return -1;
    }

  if (sunhdr.l_ras_width > GIMP_MAX_IMAGE_SIZE)
    {
      g_message (_("'%s':\nImage width is larger than GIMP can handle"),
                 gimp_filename_to_utf8 (filename));
      fclose (ifp);
      return -1;
    }

  if (sunhdr.l_ras_height <= 0)
    {
      g_message (_("'%s':\nNo image height specified"),
                 gimp_filename_to_utf8 (filename));
      fclose (ifp);
      return -1;
    }

  if (sunhdr.l_ras_height > GIMP_MAX_IMAGE_SIZE)
    {
      g_message (_("'%s':\nImage height is larger than GIMP can handle"),
                 gimp_filename_to_utf8 (filename));
      fclose (ifp);
      return -1;
    }

  switch (sunhdr.l_ras_depth)
    {
    case 1:    /* bitmap */
      image_ID = load_sun_d1 (filename, ifp, &sunhdr, suncolmap);
      break;

    case 8:    /* 256 colors */
      image_ID = load_sun_d8 (filename, ifp, &sunhdr, suncolmap);
      break;

    case 24:   /* True color */
      image_ID = load_sun_d24 (filename, ifp, &sunhdr, suncolmap);
      break;

    case 32:   /* True color with extra byte */
      image_ID = load_sun_d32 (filename, ifp, &sunhdr, suncolmap);
      break;

    default:
      image_ID = -1;
      break;
    }
  gimp_progress_update (1.0);

  fclose (ifp);

  g_free (suncolmap);

  if (image_ID == -1)
    {
      g_message (_("This image depth is not supported"));
      return -1;
    }

  return image_ID;
}


static gboolean
save_image (const gchar  *filename,
            gint32        image_ID,
            gint32        drawable_ID,
            GError      **error)
{
  FILE          *ofp;
  GimpImageType  drawable_type;
  gboolean       retval;

  drawable_type = gimp_drawable_type (drawable_ID);

  /*  Make sure we're not exporting an image with an alpha channel  */
  if (gimp_drawable_has_alpha (drawable_ID))
    {
      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                   _("SUNRAS export cannot handle images with alpha channels"));
      return FALSE;
    }

  switch (drawable_type)
    {
    case GIMP_INDEXED_IMAGE:
    case GIMP_GRAY_IMAGE:
    case GIMP_RGB_IMAGE:
      break;
    default:
      g_message (_("Can't operate on unknown image types"));
      return FALSE;
      break;
    }

  gimp_progress_init_printf (_("Exporting '%s'"),
                             gimp_filename_to_utf8 (filename));

  /* Open the output file. */
  ofp = g_fopen (filename, "wb");
  if (! ofp)
    {
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Could not open '%s' for writing: %s"),
                   gimp_filename_to_utf8 (filename), g_strerror (errno));
      return FALSE;
    }

  if (drawable_type == GIMP_INDEXED_IMAGE)
    retval = save_index (ofp,image_ID, drawable_ID, FALSE, psvals.rle);
  else if (drawable_type == GIMP_GRAY_IMAGE)
    retval = save_index (ofp,image_ID, drawable_ID, TRUE, psvals.rle);
  else if (drawable_type == GIMP_RGB_IMAGE)
    retval = save_rgb (ofp,image_ID, drawable_ID, psvals.rle);
  else
    retval = FALSE;

  fclose (ofp);

  return retval;
}


static L_CARD32
read_card32 (FILE *ifp,
             gint *err)
{
  L_CARD32 c;

  if (read_msb_first)
    {
      c = (((L_CARD32)(getc (ifp))) << 24);
      c |= (((L_CARD32)(getc (ifp))) << 16);
      c |= (((L_CARD32)(getc (ifp))) << 8);
      c |= ((L_CARD32)(*err = getc (ifp)));
    }
  else
    {
      c = ((L_CARD32)(getc (ifp)));
      c |= (((L_CARD32)(getc (ifp))) << 8);
      c |= (((L_CARD32)(getc (ifp))) << 16);
      c |= (((L_CARD32)(*err = getc (ifp))) << 24);
    }

  *err = (*err < 0);

  return c;
}


static void
write_card32 (FILE     *ofp,
              L_CARD32  c)
{
  putc ((int)((c >> 24) & 0xff), ofp);
  putc ((int)((c >> 16) & 0xff), ofp);
  putc ((int)((c >> 8) & 0xff), ofp);
  putc ((int)((c) & 0xff), ofp);
}


/* Convert n bytes of 0/1 to a line of bits */
static void
byte2bit (guchar   *byteline,
          gint      width,
          guchar   *bitline,
          gboolean  invert)
{
  guchar bitval;
  guchar rest[8];

  while (width >= 8)
    {
      bitval = 0;
      if (*(byteline++)) bitval |= 0x80;
      if (*(byteline++)) bitval |= 0x40;
      if (*(byteline++)) bitval |= 0x20;
      if (*(byteline++)) bitval |= 0x10;
      if (*(byteline++)) bitval |= 0x08;
      if (*(byteline++)) bitval |= 0x04;
      if (*(byteline++)) bitval |= 0x02;
      if (*(byteline++)) bitval |= 0x01;
      *(bitline++) = invert ? ~bitval : bitval;
      width -= 8;
    }
  if (width > 0)
    {
      memset (rest, 0, 8);
      memcpy (rest, byteline, width);
      bitval = 0;
      byteline = rest;
      if (*(byteline++)) bitval |= 0x80;
      if (*(byteline++)) bitval |= 0x40;
      if (*(byteline++)) bitval |= 0x20;
      if (*(byteline++)) bitval |= 0x10;
      if (*(byteline++)) bitval |= 0x08;
      if (*(byteline++)) bitval |= 0x04;
      if (*(byteline++)) bitval |= 0x02;
      *bitline = invert ? ~bitval : bitval;
    }
}


/* Start reading Runlength Encoded Data */
static void
rle_startread (FILE *ifp)
{
  /* Clear RLE-buffer */
  rlebuf.val = rlebuf.n = 0;
}


/* Read uncompressed elements from RLE-stream */
static gint
rle_fread (gchar *ptr,
           gint   sz,
           gint   nelem,
           FILE  *ifp)
{
  int elem_read, cnt, val, err = 0;

  for (elem_read = 0; elem_read < nelem; elem_read++)
    {
      for (cnt = 0; cnt < sz; cnt++)
        {
          val = rle_getc (ifp);

          if (val < 0)
            {
              err = 1;
              break;
            }

          *(ptr++) = (char)val;
        }

      if (err)
        break;
    }

  return elem_read;
}


/* Get one byte of uncompressed data from RLE-stream */
static gint
rle_fgetc (FILE *ifp)
{
  int flag, runcnt, runval;

  if (rlebuf.n > 0)    /* Something in the buffer ? */
    {
      (rlebuf.n)--;
      return rlebuf.val;
    }

  /* Nothing in the buffer. We have to read something */
  if ((flag = getc (ifp)) < 0) return -1;
  if (flag != 0x0080) return flag;    /* Single byte run ? */

  if ((runcnt = getc (ifp)) < 0) return -1;
  if (runcnt == 0) return 0x0080;     /* Single 0x80 ? */

  /* The run */
  if ((runval = getc (ifp)) < 0) return -1;
  rlebuf.n = runcnt;
  rlebuf.val = runval;

  return runval;
}


/* Start writing Runlength Encoded Data */
static void
rle_startwrite (FILE *ofp)
{
  /* Clear RLE-buffer */
  rlebuf.val = rlebuf.n = 0;
}


/* Write uncompressed elements to RLE-stream */
static gint
rle_fwrite (gchar *ptr,
            gint   sz,
            gint   nelem,
            FILE  *ofp)
{
  int     elem_write, cnt, val, err = 0;
  guchar *pixels = (unsigned char *)ptr;

  for (elem_write = 0; elem_write < nelem; elem_write++)
    {
      for (cnt = 0; cnt < sz; cnt++)
        {
          val = rle_fputc (*(pixels++), ofp);
          if (val < 0)
            {
              err = 1;
              break;
            }
        }

      if (err)
        break;
    }

  return elem_write;
}


/* Write uncompressed character to RLE-stream */
static gint
rle_fputc (gint  val,
           FILE *ofp)
{
  int retval;

  if (rlebuf.n == 0)    /* Nothing in the buffer ? Save the value */
    {
      rlebuf.n   = 1;
      rlebuf.val = val;

      return val;
    }

  /* Something in the buffer */

  if (rlebuf.val == val)   /* Same value in the buffer ? */
    {
      (rlebuf.n)++;
      if (rlebuf.n == 257) /* Can not be encoded in a single run ? */
        {
          retval = rle_putrun (256, rlebuf.val, ofp);
          if (retval < 0)
            return retval;

          rlebuf.n -= 256;
        }

      return val;
    }

  /* Something different in the buffer ? Write out the run */

  retval = rle_putrun (rlebuf.n, rlebuf.val, ofp);
  if (retval < 0)
    return retval;

  /* Save the new value */
  rlebuf.n   = 1;
  rlebuf.val = val;

  return val;
}


/* Write out a run with 0 < n < 257 */
static gint
rle_putrun (gint  n,
            gint  val,
            FILE *ofp)
{
  int retval, flag = 0x80;

  /* Useful to write a 3 byte run ? */
  if ((n > 2) || ((n == 2) && (val == flag)))
    {
      putc (flag, ofp);
      putc (n-1, ofp);
      retval = putc (val, ofp);
    }
  else if (n == 2) /* Write two single runs (could not be value 0x80) */
    {
      putc (val, ofp);
      retval = putc (val, ofp);
    }
  else  /* Write a single run */
    {
      if (val == flag)
        retval = putc (flag, ofp), putc (0x00, ofp);
      else
        retval = putc (val, ofp);
    }

  return (retval < 0) ? retval : val;
}


/* End writing Runlength Encoded Data */
static void
rle_endwrite (FILE *ofp)
{
  if (rlebuf.n > 0)
    {
      rle_putrun (rlebuf.n, rlebuf.val, ofp);
      rlebuf.val = rlebuf.n = 0; /* Clear RLE-buffer */
    }
}


static void
read_sun_header (FILE            *ifp,
                 L_SUNFILEHEADER *sunhdr)
{
  int       j, err;
  L_CARD32 *cp;

  cp = (L_CARD32 *)sunhdr;

  /* Read in all 32-bit values of the header and check for byte order */
  for (j = 0; j < sizeof (L_SUNFILEHEADER) / sizeof(sunhdr->l_ras_magic); j++)
    {
      *(cp++) = read_card32 (ifp, &err);
      if (err)
        break;
    }

  if (err)
    sunhdr->l_ras_magic = 0;  /* Not a valid SUN-raster file */
}


/* Write out a SUN-fileheader */

static void
write_sun_header (FILE            *ofp,
                  L_SUNFILEHEADER *sunhdr)
{
  int       j, hdr_entries;
  L_CARD32 *cp;

  hdr_entries = sizeof (L_SUNFILEHEADER) / sizeof(sunhdr->l_ras_magic);

  cp = (L_CARD32 *)sunhdr;

  /* Write out all 32-bit values of the header and check for byte order */
  for (j = 0; j < hdr_entries; j++)
    {
      write_card32 (ofp, *(cp++));
    }
}


/* Read the sun colormap */

static void
read_sun_cols (FILE            *ifp,
               L_SUNFILEHEADER *sunhdr,
               guchar          *colormap)
{
  int ncols, err = 0;

  /* Read in SUN-raster Colormap */
  ncols = sunhdr->l_ras_maplength / 3;
  if (ncols <= 0)
    err = 1;
  else
    err = (fread (colormap, 3, ncols, ifp) != ncols);

  if (err)
    sunhdr->l_ras_magic = 0;  /* Not a valid SUN-raster file */
}


/* Write a sun colormap */

static void
write_sun_cols (FILE            *ofp,
                L_SUNFILEHEADER *sunhdr,
                guchar          *colormap)
{
  int ncols;

  ncols = sunhdr->l_ras_maplength / 3;
  fwrite (colormap, 3, ncols, ofp);
}


/* Set a GIMP colortable using the sun colormap */

static void
set_color_table (gint32           image_ID,
                 L_SUNFILEHEADER *sunhdr,
                 const guchar    *suncolmap)
{
  guchar ColorMap[256 * 3];
  gint   ncols, j;

  ncols = sunhdr->l_ras_maplength / 3;
  if (ncols <= 0)
    return;

  for (j = 0; j < MIN (ncols, 256); j++)
    {
      ColorMap[j * 3 + 0] = suncolmap[j];
      ColorMap[j * 3 + 1] = suncolmap[j + ncols];
      ColorMap[j * 3 + 2] = suncolmap[j + 2 * ncols];
    }

#ifdef DEBUG
  printf ("Set GIMP colortable:\n");
  for (j = 0; j < ncols; j++)
    printf ("%3d: 0x%02x 0x%02x 0x%02x\n", j,
            ColorMap[j*3], ColorMap[j*3+1], ColorMap[j*3+2]);
#endif

  gimp_image_set_colormap (image_ID, ColorMap, ncols);
}


/* Create an image. Sets layer_ID, drawable and rgn. Returns image_ID */
static gint32
create_new_image (const gchar        *filename,
                  guint               width,
                  guint               height,
                  GimpImageBaseType   type,
                  gint32             *layer_ID,
                  GeglBuffer        **buffer)
{
  gint32        image_ID;
  GimpImageType gdtype;

  switch (type)
    {
    case GIMP_RGB:
      gdtype = GIMP_RGB_IMAGE;
      break;
    case GIMP_GRAY:
      gdtype = GIMP_GRAY_IMAGE;
      break;
    case GIMP_INDEXED:
      gdtype = GIMP_INDEXED_IMAGE;
      break;
    default:
      g_warning ("Unsupported image type");
      return -1;
    }

  image_ID = gimp_image_new (width, height, type);
  gimp_image_set_filename (image_ID, filename);

  *layer_ID = gimp_layer_new (image_ID, _("Background"), width, height,
                              gdtype,
                              100,
                              gimp_image_get_default_new_layer_mode (image_ID));
  gimp_image_insert_layer (image_ID, *layer_ID, -1, 0);

  *buffer = gimp_drawable_get_buffer (*layer_ID);

  return image_ID;
}


/* Load SUN-raster-file with depth 1 */
static gint32
load_sun_d1 (const gchar     *filename,
             FILE            *ifp,
             L_SUNFILEHEADER *sunhdr,
             guchar          *suncolmap)
{
  int               pix8;
  int               width, height, linepad, scan_lines, tile_height;
  int               i, j;
  guchar           *dest, *data;
  gint32            layer_ID, image_ID;
  GeglBuffer       *buffer;
  guchar            bit2byte[256 * 8];
  L_SUNFILEHEADER   sun_bwhdr;
  guchar            sun_bwcolmap[6] = { 255,0,255,0,255,0 };
  int               err = 0;
  gboolean          rle = (sunhdr->l_ras_type == RAS_TYPE_RLE);

  width = sunhdr->l_ras_width;
  height = sunhdr->l_ras_height;

  image_ID = create_new_image (filename, width, height, GIMP_INDEXED,
                               &layer_ID, &buffer);

  tile_height = gimp_tile_height ();
  data = g_malloc (tile_height * width);

  if (suncolmap != NULL)   /* Set up the specified color map */
    {
      set_color_table (image_ID, sunhdr, suncolmap);
    }
  else   /* No colormap available. Set up a dummy b/w-colormap */
    {      /* Copy the original header and simulate b/w-colormap */
      memcpy ((char *)&sun_bwhdr,(char *)sunhdr,sizeof (L_SUNFILEHEADER));
      sun_bwhdr.l_ras_maptype = 2;
      sun_bwhdr.l_ras_maplength = 6;
      set_color_table (image_ID, &sun_bwhdr, sun_bwcolmap);
    }

  /* Get an array for mapping 8 bits in a byte to 8 bytes */
  dest = bit2byte;
  for (j = 0; j < 256; j++)
    for (i = 7; i >= 0; i--)
      *(dest++) = ((j & (1 << i)) != 0);

  linepad = (((sunhdr->l_ras_width+7)/8) % 2); /* Check for 16bit align */

  if (rle)
    rle_startread (ifp);

  dest = data;
  scan_lines = 0;

  for (i = 0; i < height; i++)
    {
      j = width;
      while (j >= 8)
        {
          pix8 = rle ? rle_getc (ifp) : getc (ifp);
          if (pix8 < 0) { err = 1; pix8 = 0; }

          memcpy (dest, bit2byte + pix8*8, 8);
          dest += 8;
          j -= 8;
        }

      if (j > 0)
        {
          pix8 = rle ? rle_getc (ifp) : getc (ifp);
          if (pix8 < 0) { err = 1; pix8 = 0; }

          memcpy (dest, bit2byte + pix8*8, j);
          dest += j;
        }

      if (linepad)
        err |= ((rle ? rle_getc (ifp) : getc (ifp)) < 0);

      scan_lines++;

      if ((i % 20) == 0)
        gimp_progress_update ((double)(i+1) / (double)height);

      if ((scan_lines == tile_height) || ((i+1) == height))
        {
          gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1,
                                                   width, scan_lines), 0,
                           NULL, data, GEGL_AUTO_ROWSTRIDE);
          scan_lines = 0;
          dest = data;
        }
    }

  g_free (data);

  if (err)
    g_message (_("EOF encountered on reading"));

  g_object_unref (buffer);

  return image_ID;
}


/* Load SUN-raster-file with depth 8 */

static gint32
load_sun_d8 (const gchar     *filename,
             FILE            *ifp,
             L_SUNFILEHEADER *sunhdr,
             guchar          *suncolmap)
{
  int         width, height, linepad, i, j;
  gboolean    grayscale;
  gint        ncols;
  int         scan_lines, tile_height;
  guchar     *dest, *data;
  gint32      layer_ID, image_ID;
  GeglBuffer *buffer;
  int         err = 0;
  gboolean    rle = (sunhdr->l_ras_type == RAS_TYPE_RLE);

  width  = sunhdr->l_ras_width;
  height = sunhdr->l_ras_height;

  /* This could also be a grayscale image. Check it */
  ncols = sunhdr->l_ras_maplength / 3;

  grayscale = TRUE;  /* Also grayscale if no colormap present */

  if ((ncols > 0) && (suncolmap != NULL))
    {
      for (j = 0; j < ncols; j++)
        {
          if ((suncolmap[j]             != j) ||
              (suncolmap[j + ncols]     != j) ||
              (suncolmap[j + 2 * ncols] != j))
            {
              grayscale = FALSE;
              break;
            }
        }
    }

  image_ID = create_new_image (filename, width, height,
                               grayscale ? GIMP_GRAY : GIMP_INDEXED,
                               &layer_ID, &buffer);

  tile_height = gimp_tile_height ();
  data = g_malloc (tile_height * width);

  if (!grayscale)
    set_color_table (image_ID, sunhdr, suncolmap);

  linepad = (sunhdr->l_ras_width % 2);

  if (rle)
    rle_startread (ifp);  /* Initialize RLE-buffer */

  dest = data;
  scan_lines = 0;

  for (i = 0; i < height; i++)
    {
      memset ((char *)dest, 0, width);
      err |= ((rle ? rle_fread ((char *)dest, 1, width, ifp)
               : fread ((char *)dest, 1, width, ifp)) != width);

      if (linepad)
        err |= ((rle ? rle_getc (ifp) : getc (ifp)) < 0);

      dest += width;
      scan_lines++;

      if ((i % 20) == 0)
        gimp_progress_update ((double)(i+1) / (double)height);

      if ((scan_lines == tile_height) || ((i+1) == height))
        {
          gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1,
                                                   width, scan_lines), 0,
                           NULL, data, GEGL_AUTO_ROWSTRIDE);
          scan_lines = 0;
          dest = data;
        }
    }

  g_free (data);

  if (err)
    g_message (_("EOF encountered on reading"));

  g_object_unref (buffer);

  return image_ID;
}


/* Load SUN-raster-file with depth 24 */
static gint32
load_sun_d24 (const gchar      *filename,
              FILE             *ifp,
              L_SUNFILEHEADER  *sunhdr,
              guchar           *suncolmap)
{
  guchar     *dest, blue;
  guchar     *data;
  int         width, height, linepad, tile_height, scan_lines;
  int         i, j;
  gint32      layer_ID, image_ID;
  GeglBuffer *buffer;
  int         err = 0;
  gboolean    rle = (sunhdr->l_ras_type == RAS_TYPE_RLE);

  width  = sunhdr->l_ras_width;
  height = sunhdr->l_ras_height;

  image_ID = create_new_image (filename, width, height, GIMP_RGB,
                               &layer_ID, &buffer);

  tile_height = gimp_tile_height ();
  data = g_malloc (tile_height * width * 3);

  linepad = ((sunhdr->l_ras_width*3) % 2);

  if (rle)
    rle_startread (ifp);  /* Initialize RLE-buffer */

  dest = data;
  scan_lines = 0;

  for (i = 0; i < height; i++)
    {
      memset ((char *)dest, 0, 3*width);
      err |= ((rle ? rle_fread ((char *)dest, 3, width, ifp)
               : fread ((char *)dest, 3, width, ifp)) != width);

      if (linepad)
        err |= ((rle ? rle_getc (ifp) : getc (ifp)) < 0);

      if (sunhdr->l_ras_type == 3) /* RGB-format ? That is what GIMP wants */
        {
          dest += width * 3;
        }
      else                         /* We have BGR format. Correct it */
        {
          for (j = 0; j < width; j++)
            {
              blue = *dest;
              *dest = *(dest+2);
              *(dest+2) = blue;
              dest += 3;
            }
        }

      scan_lines++;

      if ((i % 20) == 0)
        gimp_progress_update ((double)(i + 1) / (double)height);

      if ((scan_lines == tile_height) || ((i + 1) == height))
        {
          gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1,
                                                   width, scan_lines), 0,
                           NULL, data, GEGL_AUTO_ROWSTRIDE);
          scan_lines = 0;
          dest = data;
        }
    }

  g_free (data);

  if (err)
    g_message (_("EOF encountered on reading"));

  g_object_unref (buffer);

  return image_ID;
}


/* Load SUN-raster-file with depth 32 */

static gint32
load_sun_d32 (const gchar     *filename,
              FILE            *ifp,
              L_SUNFILEHEADER *sunhdr,
              guchar          *suncolmap)
{
  guchar     *dest, blue;
  guchar     *data;
  int         width, height, tile_height, scan_lines;
  int         i, j;
  gint32      layer_ID, image_ID;
  GeglBuffer *buffer;
  int         err = 0;
  int         cerr;
  gboolean    rle = (sunhdr->l_ras_type == RAS_TYPE_RLE);

  width  = sunhdr->l_ras_width;
  height = sunhdr->l_ras_height;

  /* initialize */

  cerr = 0;

  image_ID = create_new_image (filename, width, height, GIMP_RGB,
                               &layer_ID, &buffer);

  tile_height = gimp_tile_height ();
  data = g_malloc (tile_height * width * 3);

  if (rle)
    rle_startread (ifp);  /* Initialize RLE-buffer */

  dest = data;
  scan_lines = 0;

  for (i = 0; i < height; i++)
    {
      if (rle)
        {
          for (j = 0; j < width; j++)
            {
              rle_getc (ifp);   /* Skip unused byte */
              *(dest++) = rle_getc (ifp);
              *(dest++) = rle_getc (ifp);
              *(dest++) = (cerr = (rle_getc (ifp)));
            }
        }
      else
        {
          for (j = 0; j < width; j++)
            {
              getc (ifp);   /* Skip unused byte */
              *(dest++) = getc (ifp);
              *(dest++) = getc (ifp);
              *(dest++) = (cerr = (getc (ifp)));
            }
        }
      err |= (cerr < 0);

      if (sunhdr->l_ras_type != 3) /* BGR format ? Correct it */
        {
          for (j = 0; j < width; j++)
            {
              dest -= 3;
              blue = *dest;
              *dest = *(dest+2);
              *(dest+2) = blue;
            }
          dest += width*3;
        }

      scan_lines++;

      if ((i % 20) == 0)
        gimp_progress_update ((double)(i + 1) / (double)height);

      if ((scan_lines == tile_height) || ((i + 1) == height))
        {
          gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i - scan_lines + 1,
                                                   width, scan_lines), 0,
                           NULL, data, GEGL_AUTO_ROWSTRIDE);
          scan_lines = 0;
          dest = data;
        }
    }

  g_free (data);

  if (err)
    g_message (_("EOF encountered on reading"));

  g_object_unref (buffer);

  return image_ID;
}


static gint
save_index (FILE     *ofp,
            gint32    image_ID,
            gint32    drawable_ID,
            gboolean  grey,
            gboolean  rle)
{
  int             height, width, linepad, i, j;
  int             ncols, bw, is_bw, is_wb, bpl;
  int             tile_height;
  long            tmp = 0;
  guchar         *cmap, *bwline = NULL;
  guchar         *data, *src;
  L_SUNFILEHEADER sunhdr;
  guchar          sun_colormap[256*3];
  static guchar   sun_bwmap[6] = { 0,   255, 0,   255, 0,   255 };
  static guchar   sun_wbmap[6] = { 255, 0,   255, 0,   255, 0   };
  unsigned char  *suncolmap = sun_colormap;
  GeglBuffer     *buffer;
  const Babl     *format;
  WRITE_FUN      *write_fun;

  buffer = gimp_drawable_get_buffer (drawable_ID);

  width  = gegl_buffer_get_width (buffer);
  height = gegl_buffer_get_height (buffer);

  tile_height = gimp_tile_height ();

  if (grey)
    format = babl_format ("Y' u8");
  else
    format = gegl_buffer_get_format (buffer);

  /* allocate a buffer for retrieving information from the buffer  */
  src = data = g_malloc (tile_height * width *
                         babl_format_get_bytes_per_pixel (format));

  /* Fill SUN-color map */
  if (grey)
    {
      ncols = 256;

      for (j = 0; j < ncols; j++)
        {
          suncolmap[j]             = j;
          suncolmap[j + ncols]     = j;
          suncolmap[j + ncols * 2] = j;
        }
    }
  else
    {
      cmap = gimp_image_get_colormap (image_ID, &ncols);

      for (j = 0; j < ncols; j++)
        {
          suncolmap[j]             = *(cmap++);
          suncolmap[j + ncols]     = *(cmap++);
          suncolmap[j + ncols * 2] = *(cmap++);
        }
    }

  bw = (ncols == 2);   /* Maybe this is a two-color image */
  if (bw)
    {
      bwline = g_malloc ((width + 7) / 8);
      if (bwline == NULL)
        bw = 0;
    }

  is_bw = is_wb = 0;
  if (bw)    /* The Sun-OS imagetool generates index 0 for white and */
    {          /* index 1 for black. Do the same without colortable. */
      is_bw = (memcmp (suncolmap, sun_bwmap, 6) == 0);
      is_wb = (memcmp (suncolmap, sun_wbmap, 6) == 0);
    }

  /* Number of data bytes per line */
  bpl = bw ?  (width+7)/8 : width;
  linepad = bpl % 2;

  /* Fill in the SUN header */
  sunhdr.l_ras_magic  = RAS_MAGIC;
  sunhdr.l_ras_width  = width;
  sunhdr.l_ras_height = height;
  sunhdr.l_ras_depth  = bw ? 1 : 8;
  sunhdr.l_ras_length = (bpl + linepad) * height;
  sunhdr.l_ras_type   = rle ? RAS_TYPE_RLE : RAS_TYPE_STD;

  if (is_bw || is_wb)   /* No colortable for real b/w images */
    {
      sunhdr.l_ras_maptype = 0;   /* No colormap */
      sunhdr.l_ras_maplength = 0; /* Length of colormap */
    }
  else
    {
      sunhdr.l_ras_maptype = 1;   /* RGB colormap */
      sunhdr.l_ras_maplength = ncols*3; /* Length of colormap */
    }

  write_sun_header (ofp, &sunhdr);

  if (sunhdr.l_ras_maplength > 0)
    write_sun_cols (ofp, &sunhdr, suncolmap);

#define GET_INDEX_TILE(begin) \
  {int scan_lines; \
    scan_lines = (i+tile_height-1 < height) ? tile_height : (height-i); \
    gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scan_lines), 1.0, \
                     format, begin, \
                     GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); \
    src = begin; }

  if (rle)
    {
      write_fun = (WRITE_FUN *) &rle_fwrite;
      rle_startwrite (ofp);
    }
  else
    {
      write_fun = (WRITE_FUN *) &my_fwrite;
    }

  if (bw)  /* Two color image */
    {
      for (i = 0; i < height; i++)
        {
          if ((i % tile_height) == 0)
            GET_INDEX_TILE (data); /* Get more data */

          byte2bit (src, width, bwline, is_bw);
          (*write_fun) (bwline, bpl, 1, ofp);
          if (linepad)
            (*write_fun) ((char *)&tmp, linepad, 1, ofp);
          src += width;

          if ((i % 20) == 0)
            gimp_progress_update ((double) i / (double) height);
        }
    }
  else   /* Color or grey-image */
    {
      for (i = 0; i < height; i++)
        {
          if ((i % tile_height) == 0)
            GET_INDEX_TILE (data); /* Get more data */

          (*write_fun) ((char *)src, width, 1, ofp);
          if (linepad)
            (*write_fun) ((char *)&tmp, linepad, 1, ofp);
          src += width;

          if ((i % 20) == 0)
            gimp_progress_update ((double) i / (double) height);
        }
    }

#undef GET_INDEX_TILE

  if (rle)
    rle_endwrite (ofp);

  g_free (data);

  if (bwline)
    g_free (bwline);

  g_object_unref (buffer);

  if (ferror (ofp))
    {
      g_message (_("Write error occurred"));
      return FALSE;
    }

  return TRUE;
}


static gint
save_rgb (FILE     *ofp,
          gint32    image_ID,
          gint32    drawable_ID,
          gboolean  rle)
{
  int              height, width, tile_height, linepad;
  int              i, j, bpp;
  guchar          *data, *src;
  L_SUNFILEHEADER  sunhdr;
  GeglBuffer      *buffer;
  const Babl      *format;

  buffer = gimp_drawable_get_buffer (drawable_ID);

  width  = gegl_buffer_get_width  (buffer);
  height = gegl_buffer_get_height (buffer);

  tile_height = gimp_tile_height ();

  format = babl_format ("R'G'B' u8");

  /* allocate a buffer for retrieving information from the pixel region  */
  src = data = g_malloc (tile_height * width *
                         babl_format_get_bytes_per_pixel (format));

/* #define SUNRAS_32 */
#ifdef SUNRAS_32
  bpp = 4;
#else
  bpp = 3;
#endif
  linepad = (width * bpp) % 2;

  /* Fill in the SUN header */
  sunhdr.l_ras_magic     = RAS_MAGIC;
  sunhdr.l_ras_width     = width;
  sunhdr.l_ras_height    = height;
  sunhdr.l_ras_depth     = 8 * bpp;
  sunhdr.l_ras_length    = (width * bpp + linepad) * height;
  sunhdr.l_ras_type      = rle ? RAS_TYPE_RLE : RAS_TYPE_STD;
  sunhdr.l_ras_maptype   = 0;   /* No colormap */
  sunhdr.l_ras_maplength = 0; /* Length of colormap */

  write_sun_header (ofp, &sunhdr);

#define GET_RGB_TILE(begin) \
  {int scan_lines; \
    scan_lines = (i+tile_height-1 < height) ? tile_height : (height-i); \
    gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, scan_lines), 1.0, \
                     format, begin, \
                     GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); \
    src = begin; }

  if (! rle)
    {
      for (i = 0; i < height; i++)
        {
          if ((i % tile_height) == 0)
            GET_RGB_TILE (data); /* Get more data */

          for (j = 0; j < width; j++)
            {
              if (bpp == 4) putc (0, ofp);   /* Dummy */
              putc (*(src + 2), ofp);        /* Blue */
              putc (*(src + 1), ofp);        /* Green */
              putc (*src,       ofp);        /* Red */
              src += 3;
            }

          for (j = 0; j < linepad; j++)
            putc (0, ofp);

          if ((i % 20) == 0)
            gimp_progress_update ((double) i / (double) height);
        }
    }
  else  /* Write runlength encoded */
    {
      rle_startwrite (ofp);

      for (i = 0; i < height; i++)
        {
          if ((i % tile_height) == 0)
            GET_RGB_TILE (data); /* Get more data */

          for (j = 0; j < width; j++)
            {
              if (bpp == 4) rle_putc (0, ofp);   /* Dummy */
              rle_putc (*(src + 2), ofp);        /* Blue */
              rle_putc (*(src + 1), ofp);        /* Green */
              rle_putc (*src,       ofp);        /* Red */
              src += 3;
            }

          for (j = 0; j < linepad; j++)
            rle_putc (0, ofp);

          if ((i % 20) == 0)
            gimp_progress_update ((double) i / (double) height);
        }

      rle_endwrite (ofp);
    }

#undef GET_RGB_TILE

  g_free (data);

  g_object_unref (buffer);

  if (ferror (ofp))
    {
      g_message (_("Write error occurred"));
      return FALSE;
    }

  return TRUE;
}


/*  Save interface functions  */

static gboolean
save_dialog (void)
{
  GtkWidget *dialog;
  GtkWidget *frame;
  gboolean   run;

  dialog = gimp_export_dialog_new (_("SUNRAS"), PLUG_IN_BINARY, SAVE_PROC);

  /*  file save type  */
  frame = gimp_int_radio_group_new (TRUE, _("Data Formatting"),
                                    G_CALLBACK (gimp_radio_button_update),
                                    &psvals.rle, psvals.rle,

                                    _("RunLength Encoded"), TRUE,  NULL,
                                    _("Standard"),          FALSE, NULL,

                                    NULL);

  gtk_container_set_border_width (GTK_CONTAINER (frame), 12);
  gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
                      frame, TRUE, TRUE, 0);
  gtk_widget_show (frame);

  gtk_widget_show (dialog);

  run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);

  gtk_widget_destroy (dialog);

  return run;
}

static int
my_fwrite (void *ptr,
           int   size,
           int   nmemb,
           FILE *stream)
{
  return fwrite (ptr, size, nmemb, stream);
}
