/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * $Id: tga.c,v 1.2 1997/09/27 10:30:44 nobody Exp $
 * TrueVision Targa loading and saving file filter for the Gimp.
 * Targa code Copyright (C) 1997 Raphael FRANCOIS and Gordon Matzigkeit
 *
 * The Targa reading and writing code was written from scratch by
 * Raphael FRANCOIS <fraph@ibm.net> and Gordon Matzigkeit
 * <gord@gnu.ai.mit.edu> based on the TrueVision TGA File Format
 * Specification, Version 2.0:
 *
 *   <URL:ftp://ftp.truevision.com/pub/TGA.File.Format.Spec/>
 *
 * It does not contain any code written for other TGA file loaders.
 * Not even the RLE handling. ;)
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Release 1.2, 1997-09-24, Gordon Matzigkeit <gord@gnu.ai.mit.edu>:
 *   - Bug fixes and source cleanups.
 *
 * Release 1.1, 1997-09-19, Gordon Matzigkeit <gord@gnu.ai.mit.edu>:
 *   - Preserve alpha channels.  For indexed images, this can only be
 *     done if there is at least one free colormap entry.
 *
 * Release 1.0, 1997-09-06, Gordon Matzigkeit <gord@gnu.ai.mit.edu>:
 *   - Handle loading all image types from the 2.0 specification.
 *   - Fix many alignment and endianness problems.
 *   - Use tiles for lower memory consumption and better speed.
 *   - Rewrite RLE code for clarity and speed.
 *   - Handle saving with RLE.
 *
 * Release 0.9, 1997-06-18, Raphael FRANCOIS <fraph@ibm.net>:
 *   - Can load 24 and 32-bit Truecolor images, with and without RLE.
 *   - Saving currently only works without RLE.
 *
 *
 * TODO:
 *   - Handle loading images that aren't 8 bits per channel.
 *   - Maybe handle special features in developer and extension sections
 *     (the `save' dialogue would give access to them).
 *   - The GIMP stores the indexed alpha channel as a separate byte,
 *     one for each pixel.  The TGA file format spec requires that the
 *     alpha channel be stored as part of the colormap, not with each
 *     individual pixel.  This means that we have no good way of
 *     saving and loading INDEXEDA images that use alpha channel values
 *     other than 0 and 255.  Find a workaround.
 */

#define SAVE_ID_STRING "CREATOR: The GIMP's TGA Filter Version 1.2"

/* Set these for debugging. */
/* #define PROFILE 1 */
/* #define VERBOSE 1 */

#ifdef PROFILE
# include <sys/times.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include "libgimp/gimp.h"

#define FALSE 0
#define TRUE 1

/* Round up a division to the nearest integer. */
#define ROUNDUP_DIVIDE(n,d) (((n) + (d - 1)) / (d))

typedef struct _TgaSaveVals
{
  gint rle;
} TgaSaveVals;

static TgaSaveVals tsvals =
{
  1,    /* rle */
};

typedef struct _TgaSaveInterface
{
  unsigned char run;
} TgaSaveInterface;

static TgaSaveInterface tsint =
{
  FALSE                /*  run  */
};

struct tga_header
{
  guint8 idLength;
  guint8 colorMapType;

  /* The image type. */
#define TGA_TYPE_MAPPED 1
#define TGA_TYPE_COLOR 2
#define TGA_TYPE_GRAY 3
#define TGA_TYPE_MAPPED_RLE 9
#define TGA_TYPE_COLOR_RLE 10
#define TGA_TYPE_GRAY_RLE 11
  guint8 imageType;

  /* Color Map Specification. */
  /* We need to separately specify high and low bytes to avoid endianness
     and alignment problems. */
  guint8 colorMapIndexLo, colorMapIndexHi;
  guint8 colorMapLengthLo, colorMapLengthHi;
  guint8 colorMapSize;

  /* Image Specification. */
  guint8 xOriginLo, xOriginHi;
  guint8 yOriginLo, yOriginHi;

  guint8 widthLo, widthHi;
  guint8 heightLo, heightHi;

  guint8 bpp;

  /* Image descriptor.
     3-0: attribute bpp
     4:   left-to-right ordering
     5:   top-to-bottom ordering
     7-6: zero
     */
#define TGA_DESC_ABITS 0x0f
#define TGA_DESC_HORIZONTAL 0x10
#define TGA_DESC_VERTICAL 0x20
  guint8 descriptor;
};

static struct
{
  guint32 extensionAreaOffset;
  guint32 developerDirectoryOffset;
#define TGA_SIGNATURE "TRUEVISION-XFILE"
  gchar signature[16];
  gchar dot;
  gchar null;
} tga_footer;


/* Declare some local functions.
 */
static void   query      (void);
static void   run        (char    *name,
                          int      nparams,
                          GParam  *param,
                          int     *nreturn_vals,
                          GParam **return_vals);
static gint32 load_image (char   *filename);
static gint   save_image (char   *filename,
			  gint32  image_ID,
			  gint32  drawable_ID);

static gint   save_dialog ();

static void   save_close_callback  (GtkWidget *widget,
				    gpointer   data);
static void   save_ok_callback     (GtkWidget *widget,
				    gpointer   data);
static void   save_toggle_update   (GtkWidget *widget,
				    gpointer   data);

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


MAIN ();

#ifdef VERBOSE
static int verbose = VERBOSE;
#endif

static void
query ()
{
  static GParamDef load_args[] =
  {
    { PARAM_INT32, "run_mode", "Interactive, non-interactive" },
    { PARAM_STRING, "filename", "The name of the file to load" },
    { PARAM_STRING, "raw_filename", "The name entered" },
  };
  static GParamDef load_return_vals[] =
  {
    { PARAM_IMAGE, "image", "Output image" },
  };
  static int nload_args = sizeof (load_args) / sizeof (load_args[0]);
  static int nload_return_vals = sizeof (load_return_vals) / sizeof (load_return_vals[0]);


  static GParamDef save_args[] =
  {
    { PARAM_INT32, "run_mode", "Interactive, non-interactive" },
    { PARAM_IMAGE, "image", "Input image" },
    { PARAM_DRAWABLE, "drawable", "Drawable to save" },
    { PARAM_STRING, "filename", "The name of the file to save the image in" },
    { PARAM_STRING, "raw_filename", "The name of the file to save the image in" },
    { PARAM_INT32, "rle", "Enable RLE compression" },
  } ;
  static int nsave_args = sizeof (save_args) / sizeof (save_args[0]);

  gimp_install_procedure ("file_tga_load",
                          "Loads files of Targa file format",
                          "FIXME: write help for tga_load",
                          "Raphael FRANCOIS, Gordon Matzigkeit",
                          "Raphael FRANCOIS, Gordon Matzigkeit",
                          "1997",
                          "<Load>/TGA",
			  NULL,
                          PROC_PLUG_IN,
                          nload_args, nload_return_vals,
                          load_args, load_return_vals);


  gimp_install_procedure ("file_tga_save",
                          "saves files in the Targa file format",
                          "FIXME: write help for tga_save",
			  "Raphael FRANCOIS, Gordon Matzigkeit",
                          "Raphael FRANCOIS, Gordon Matzigkeit",
                          "1997",
                          "<Save>/TGA",
			  "RGB*, GRAY*, INDEXED*",
                          PROC_PLUG_IN,
                          nsave_args, 0,
                          save_args, NULL);

  gimp_register_magic_load_handler ("file_tga_load", "tga", "",
				    "0&,byte,10,2&,byte,1,3&,byte,>0,3,byte,<9");
  gimp_register_save_handler ("file_tga_save", "tga", "");

}

static void
run (char    *name,
     int      nparams,
     GParam  *param,
     int     *nreturn_vals,
     GParam **return_vals)
{
  static GParam values[2];
  GStatusType status = STATUS_SUCCESS;
  GRunModeType run_mode;
  gint32 image_ID;

#ifdef PROFILE
  struct tms tbuf1, tbuf2;
#endif

  run_mode = param[0].data.d_int32;

  *nreturn_vals = 1;
  *return_vals = values;
  values[0].type = PARAM_STATUS;
  values[0].data.d_status = STATUS_CALLING_ERROR;

#ifdef VERBOSE
  if (verbose)
    printf ("TGA: RUN %s\n", name);
#endif

  if (strcmp (name, "file_tga_load") == 0)
    {
#ifdef PROFILE
      times (&tbuf1);
#endif
      image_ID = load_image (param[1].data.d_string);

      if (image_ID != -1)
        {
          *nreturn_vals = 2;
          values[0].data.d_status = STATUS_SUCCESS;
          values[1].type = PARAM_IMAGE;
          values[1].data.d_image = image_ID;
        }
      else
        {
          values[0].data.d_status = STATUS_EXECUTION_ERROR;
        }
    }
  else if (strcmp (name, "file_tga_save") == 0)
    {
      switch (run_mode)
	{
	case RUN_INTERACTIVE:
	  /*  Possibly retrieve data  */
	  gimp_get_data ("file_tga_save", &tsvals);

	  /*  First acquire information with a dialog  */
	  if (! save_dialog ())
	    return;

	  break;

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

	case RUN_WITH_LAST_VALS:
	  /*  Possibly retrieve data  */
	  gimp_get_data ("file_tga_save", &tsvals);
	  break;

	default:
	  break;
	}

#ifdef PROFILE
      times (&tbuf1);
#endif
      *nreturn_vals = 1;
      if (save_image (param[3].data.d_string, param[1].data.d_int32, param[2].data.d_int32))
	{
	  /*  Store psvals data  */
	  gimp_set_data ("file_tga_save", &tsvals, sizeof (tsvals));

	  values[0].data.d_status = STATUS_SUCCESS;
	}
      else
	values[0].data.d_status = STATUS_EXECUTION_ERROR;
    }

#ifdef PROFILE
  times (&tbuf2);
  printf ("TGA: %s profile: %ld user %ld system\n", name,
	  (long) tbuf2.tms_utime - tbuf1.tms_utime,
	  (long) tbuf2.tms_stime - tbuf2.tms_stime);
#endif
}

static gint32 ReadImage (FILE *fp, struct tga_header *hdr, char *filename);

static gint32
load_image (char *filename)
{
  FILE *fp;
  char * name_buf;
  struct tga_header hdr;

  gint32 image_ID = -1;

  fp = fopen (filename, "rb");
  if (!fp) {
      printf ("TGA: can't open \"%s\"\n", filename);
      return -1;
    }

  name_buf = g_malloc (strlen (filename) + 11);
  sprintf (name_buf, "Loading %s:", filename);
  gimp_progress_init (name_buf);
  g_free (name_buf);

  /* Check the footer. */
  if (fseek (fp, 0L - (sizeof (tga_footer)), SEEK_END)
      || fread (&tga_footer, sizeof (tga_footer), 1, fp) != 1)
    {
      printf ("TGA: Cannot read footer from \"%s\"\n", filename);
      return -1;
    }

  /* Check the signature. */
#ifdef VERBOSE
  if (memcmp (tga_footer.signature, TGA_SIGNATURE,
	      sizeof (tga_footer.signature)) == 0)
    {
      if (verbose)
	printf ("TGA: found New TGA\n");
    }
  else if (verbose)
    printf ("TGA: found Original TGA\n");
#endif

  if (fseek (fp, 0, SEEK_SET) ||
      fread (&hdr, sizeof (hdr), 1, fp) != 1) {
    printf("TGA: Cannot read header from \"%s\"\n", filename);
    return -1;
  }

  /* Skip the image ID field. */
  if (hdr.idLength && fseek (fp, hdr.idLength, SEEK_CUR))
    {
      printf ("TGA: Cannot skip ID field in \"%s\"\n", filename);
      return -1;
    }

  image_ID = ReadImage (fp, &hdr, filename);
  fclose (fp);
  return image_ID;
}


#ifdef VERBOSE
static int totbytes = 0;
#endif

static int
std_fread (guchar *buf, int datasize, int nelems, FILE *fp)
{
#ifdef VERBOSE
  if (verbose > 1)
    {
      totbytes += nelems * datasize;
      printf ("TGA: std_fread %d (total %d)\n",
	      nelems * datasize, totbytes);
    }
#endif

  return fread (buf, datasize, nelems, fp);
}

static int
std_fwrite (guchar *buf, int datasize, int nelems, FILE *fp)
{
#ifdef VERBOSE
  if (verbose > 1)
    {
      totbytes += nelems * datasize;
      printf ("TGA: std_fwrite %d (total %d)\n",
	      nelems * datasize, totbytes);
    }
#endif

  return fwrite (buf, datasize, nelems, fp);
}

#define RLE_PACKETSIZE 0x80

/* Decode a bufferful of file. */
static int
rle_fread (guchar *buf, int datasize, int nelems, FILE *fp)
{
  static guchar *statebuf = 0;
  static int statelen = 0;
  static int laststate = 0;

  int j, k;
  int buflen, count, bytes;
  guchar *p;
#ifdef VERBOSE
  int curbytes = totbytes;
#endif

  /* Scale the buffer length. */
  buflen = nelems * datasize;

  j = 0;
  while (j < buflen)
    {
      if (laststate < statelen)
	{
	  /* Copy bytes from our previously decoded buffer. */
	  bytes = MIN (buflen - j, statelen - laststate);
	  memcpy (buf + j, statebuf + laststate, bytes);
	  j += bytes;
	  laststate += bytes;

	  /* If we used up all of our state bytes, then reset them. */
	  if (laststate >= statelen)
	    {
	      laststate = 0;
	      statelen = 0;
	    }

	  /* If we filled the buffer, then exit the loop. */
	  if (j >= buflen)
	    break;
	}

      /* Decode the next packet. */
      count = fgetc (fp);
      if (count == EOF)
	{
#ifdef VERBOSE
	  if (verbose)
	    printf ("TGA: hit EOF while looking for count\n");
#endif
	  return j / datasize;
	}

      /* Scale the byte length to the size of the data. */
      bytes = ((count & ~RLE_PACKETSIZE) + 1) * datasize;

      if (j + bytes <= buflen)
	{
	  /* We can copy directly into the image buffer. */
	  p = buf + j;
	}
      else {
#if defined(PROFILE) || defined(VERBOSE)
	printf ("TGA: needed to use statebuf for %d bytes\n", buflen - j);
#endif
	/* Allocate the state buffer if we haven't already. */
	if (!statebuf)
	  statebuf = (guchar *) g_malloc (RLE_PACKETSIZE * datasize);
	p = statebuf;
      }

      if (count & RLE_PACKETSIZE)
	{
	  /* Fill the buffer with the next value. */
	  if (fread (p, datasize, 1, fp) != 1)
	    {
#ifdef VERBOSE
	      if (verbose)
		printf ("TGA: EOF while reading %d/%d element RLE packet\n",
			bytes, datasize);
#endif
	      return j / datasize;
	    }

	  /* Optimized case for single-byte encoded data. */
	  if (datasize == 1)
	    memset (p + 1, *p, bytes - 1);
	  else
	    for (k = datasize; k < bytes; k += datasize)
	      memcpy (p + k, p, datasize);
	}
      else
	{
	  /* Read in the buffer. */
	  if (fread (p, bytes, 1, fp) != 1)
	    {
#ifdef VERBOSE
	      if (verbose)
		printf ("TGA: EOF while reading %d/%d element raw packet\n",
			bytes, datasize);
#endif
	      return j / datasize;
	    }
	}

#ifdef VERBOSE
      if (verbose > 1)
	{
	  totbytes += bytes;
	  if (verbose > 2)
	    printf ("TGA: %s packet %d/%d\n",
		    (count & RLE_PACKETSIZE) ? "RLE" : "raw",
		    bytes, totbytes);
	}
#endif

      /* We may need to copy bytes from the state buffer. */
      if (p == statebuf)
	statelen = bytes;
      else
	j += bytes;
    }

#ifdef VERBOSE
  if (verbose > 1)
    {
      printf ("TGA: rle_fread %d/%d (total %d)\n",
	      nelems * datasize, totbytes - curbytes, totbytes);
    }
#endif
  return nelems;
}


/* This function is stateless, which means that we always finish packets
   on buffer boundaries.  As a beneficial side-effect, rle_fread
   never has to allocate a state buffer when it loads our files, provided
   it is called using the same buffer lengths!

   So, we get better compression than line-by-line encoders, and better
   loading performance than whole-stream images. */
/* RunLength Encode a bufferful of file. */
static int
rle_fwrite (guchar *buf, int datasize, int nelems, FILE *fp)
{
  /* Now runlength-encode the whole buffer. */
  int count, j, buflen;
  guchar *begin;

#ifdef VERBOSE
  int curbytes = totbytes;
#endif

  /* Scale the buffer length. */
  buflen = datasize * nelems;

  begin = buf;
  j = datasize;
  while (j < buflen)
    {
      /* BUF[J] is our lookahead element, BEGIN is the beginning of this
	 run, and COUNT is the number of elements in this run. */
      if (memcmp (buf + j, begin, datasize) == 0)
	{
	  /* We have a run of identical characters. */
	  count = 1;
	  do
	    {
	      j += datasize;
	      count ++;
	    }
	  while (j < buflen && count < RLE_PACKETSIZE &&
		 memcmp (buf + j, begin, datasize) == 0);

	  /* J now either points to the beginning of the next run,
	     or close to the end of the buffer. */

	  /* Write out the run. */
	  if (fputc ((count - 1) | RLE_PACKETSIZE, fp) == EOF ||
	      fwrite (begin, datasize, 1, fp) != 1)
	    return 0;

#ifdef VERBOSE
	  if (verbose > 1)
	    {
	      totbytes += count * datasize;
	      if (verbose > 2)
		printf ("TGA: RLE packet %d/%d\n", count * datasize, totbytes);
	    }
#endif
	}
      else
	{
	  /* We have a run of raw characters. */
	  count = 0;
	  do
	    {
	      j += datasize;
	      count ++;
	    }
	  while (j < buflen && count < RLE_PACKETSIZE &&
		 memcmp (buf + j - datasize, buf + j, datasize) != 0);

	  /* Back up to the previous character. */
	  j -= datasize;

	  /* J now either points to the beginning of the next run,
	     or at the end of the buffer. */

	  /* Write out the raw packet. */
	  if (fputc (count - 1, fp) == EOF ||
	      fwrite (begin, datasize, count, fp) != count)
	    return 0;

#ifdef VERBOSE
	  if (verbose > 1)
	    {
	      totbytes += count * datasize;
	      if (verbose > 2)
		printf ("TGA: raw packet %d/%d\n", count * datasize, totbytes);
	    }
#endif
	}

      /* Set the beginning of the next run and the next lookahead. */
      begin = buf + j;
      j += datasize;
    }

  /* If we didn't encode all the elements, write one last packet. */
  if (begin < buf + buflen)
    {
#ifdef VERBOSE
      if (verbose > 1)
	{
	  totbytes += datasize;
	  if (verbose > 2)
	    printf ("TGA: FINAL raw packet %d/%d\n", datasize, totbytes);
	}
#endif
      if (fputc (0, fp) == EOF ||
	  fwrite (begin, datasize, 1, fp) != 1)
	return 0;
    }

#ifdef VERBOSE
  if (verbose > 1)
    {
      printf ("TGA: rle_fwrite %d/%d (total %d)\n",
	      totbytes - curbytes, nelems * datasize, totbytes);
    }
#endif
  return nelems;
}




static gint32
ReadImage (FILE *fp, struct tga_header *hdr, char *filename)
{
  static gint32 image_ID;
  gint32 layer_ID;

  GPixelRgn pixel_rgn;
  GDrawable *drawable;
  guchar *data, *buffer;
  GDrawableType dtype;
  GImageType itype;
  guchar *alphas;

  int width, height, bpp, abpp, pbpp, nalphas;
  int i, j, k;
  int pelbytes, tileheight, wbytes, bsize, npels, pels;
  int rle, badread;

  int (*myfread)(guchar *, int, int, FILE *);

  /* Find out whether the image is horizontally or vertically reversed.
     The GIMP likes things left-to-right, top-to-bottom. */
  gchar horzrev = hdr->descriptor & TGA_DESC_HORIZONTAL;
  gchar vertrev = !(hdr->descriptor & TGA_DESC_VERTICAL);

  /* Reassemble the multi-byte values correctly, regardless of
     host endianness. */
  width = (hdr->widthHi << 8) | hdr->widthLo;
  height = (hdr->heightHi << 8) | hdr->heightLo;

  bpp = hdr->bpp;
  abpp = hdr->descriptor & TGA_DESC_ABITS;

  if (hdr->imageType == TGA_TYPE_COLOR ||
      hdr->imageType == TGA_TYPE_COLOR_RLE)
    pbpp = MIN (bpp / 3, 8) * 3;
  else if (abpp < bpp)
    pbpp = bpp - abpp;
  else
    pbpp = bpp;

  if (abpp + pbpp > bpp)
    {
      printf ("TGA: %d bit image, %d bit alpha is greater than %d total bits per pixel\n",
	      pbpp, abpp, bpp);

      /* Assume that alpha bits were set incorrectly. */
      abpp = bpp - pbpp;
      printf ("TGA: reducing to %d bit alpha\n", abpp);
    }
  else if (abpp + pbpp < bpp)
    {
      printf ("TGA: %d bit image, %d bit alpha is less than %d total bits per pixel\n",
	      pbpp, abpp, bpp);

      /* Again, assume that alpha bits were set incorrectly. */
      abpp = bpp - pbpp;
      printf ("TGA: increasing to %d bit alpha\n", abpp);
    }

  rle = 0;
  switch (hdr->imageType)
    {
    case TGA_TYPE_MAPPED_RLE:
      rle = 1;
    case TGA_TYPE_MAPPED:
      itype = INDEXED;

      /* Find the size of palette elements. */
      pbpp = MIN (hdr->colorMapSize / 3, 8) * 3;
      if (pbpp < hdr->colorMapSize)
	abpp = hdr->colorMapSize - pbpp;
      else
	abpp = 0;

#ifdef VERBOSE
      if (verbose)
	printf ("TGA: %d bit indexed image, %d bit alpha (%d bit indices)\n",
		pbpp, abpp, bpp);
#endif

      if (bpp != 8)
	{
	  /* We can only cope with 8-bit indices. */
	  printf ("TGA: index sizes other than 8 bits are unimplemented\n");
	  return -1;
	}

      if (abpp)
	dtype = INDEXEDA_IMAGE;
      else
	dtype = INDEXED_IMAGE;
      break;

    case TGA_TYPE_GRAY_RLE:
      rle = 1;
    case TGA_TYPE_GRAY:
      itype = GRAY;
#ifdef VERBOSE
      if (verbose)
	printf ("TGA: %d bit grayscale image, %d bit alpha\n",
		pbpp, abpp);
#endif

      if (abpp)
	dtype = GRAYA_IMAGE;
      else
	dtype = GRAY_IMAGE;
      break;

    case TGA_TYPE_COLOR_RLE:
      rle = 1;
    case TGA_TYPE_COLOR:
      itype = RGB;
#ifdef VERBOSE
      if (verbose)
	printf ("TGA: %d bit color image, %d bit alpha\n", pbpp, abpp);
#endif

      if (abpp)
	dtype = RGBA_IMAGE;
      else
	dtype = RGB_IMAGE;
      break;

    default:
      printf ("TGA: unrecognized image type %d\n", hdr->imageType);
      return -1;
    }

  if ((abpp && abpp != 8) ||
      ((itype == RGB || itype == INDEXED) && pbpp != 24) ||
      (itype == GRAY && pbpp != 8))
    {
      /* FIXME: We haven't implemented bit-packed fields yet. */
      printf ("TGA: channel sizes other than 8 bits are unimplemented\n");
      return -1;
    }

  /* Check that we have a color map only when we need it. */
  if (itype == INDEXED)
    {
      if (hdr->colorMapType != 1)
	{
	  printf ("TGA: indexed image has invalid color map type %d\n",
		  hdr->colorMapType);
	  return -1;
	}
    }
  else if (hdr->colorMapType != 0)
    {
      printf ("TGA: non-indexed image has invalid color map type %d\n",
	      hdr->colorMapType);
      return -1;
    }

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

  alphas = 0;
  nalphas = 0;
  if (hdr->colorMapType == 1)
    {
      /* We need to read in the colormap. */
      int index, length, colors;
      int tmp;

      guchar *cmap;

      index = (hdr->colorMapIndexHi << 8) | hdr->colorMapIndexLo;
      length = (hdr->colorMapLengthHi << 8) | hdr->colorMapLengthLo;

#ifdef VERBOSE
      if (verbose)
	printf ("TGA: reading color map (%d + %d) * (%d bits)\n",
		index, length, hdr->colorMapSize);
#endif
      if (length == 0)
	{
	  printf ("TGA: invalid color map length %d\n", length);
	  gimp_image_delete (image_ID);
	  return -1;
	}

      pelbytes = ROUNDUP_DIVIDE (hdr->colorMapSize, 8);
      colors = length + index;
      cmap = g_malloc (colors * pelbytes);

      /* Zero the entries up to the beginning of the map. */
      memset (cmap, 0, index * pelbytes);

      /* Read in the rest of the colormap. */
      if (fread (cmap + (index * pelbytes), pelbytes, length, fp) != length)
	{
	  printf ("TGA: error reading colormap (ftell == %ld)\n", ftell (fp));
	  gimp_image_delete (image_ID);
	  return -1;
	}

      /* If we have an alpha channel, then create a mapping to the alpha
	 values. */
      if (pelbytes > 3)
	alphas = (guchar *) g_malloc (colors);

      k = 0;
      for (j = 0; j < colors * pelbytes; j += pelbytes)
	{
	  /* Swap from BGR to RGB. */
	  tmp = cmap[j];
	  cmap[k ++] = cmap[j + 2];
	  cmap[k ++] = cmap[j + 1];
	  cmap[k ++] = tmp;

	  /* Take the alpha values out of the colormap. */
	  if (alphas)
	    alphas[nalphas ++] = cmap[j + 3];
	}

      /* If the last color was transparent, then omit it from the
	 GIMP mapping. */
      if (nalphas && alphas[nalphas - 1] == 0)
	colors --;

      /* Set the colormap. */
      gimp_image_set_cmap (image_ID, cmap, colors);
      g_free (cmap);

      /* Now pretend as if we only have 8 bpp. */
      abpp = 0;
      pbpp = 8;
    }

  /* Continue initializing. */
  layer_ID = gimp_layer_new (image_ID,
			     "Background",
			     width, height,
			     dtype,
			     100,
			     NORMAL_MODE);

  gimp_image_add_layer (image_ID, layer_ID, 0);

  drawable = gimp_drawable_get (layer_ID);

  /* Prepare the pixel region. */
  gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0, width, height, TRUE, FALSE);

  /* Calculate number of GIMP bytes per pixel. */
  pelbytes = drawable->bpp;

  /* Calculate TGA bytes per pixel. */
  bpp = ROUNDUP_DIVIDE (pbpp + abpp, 8);

  /* Allocate the data. */
  tileheight = gimp_tile_height ();
  data = (guchar *) g_malloc (width * tileheight * pelbytes);

  /* Maybe we need to reverse the data. */
  buffer = 0;
  if (horzrev || vertrev)
    buffer = (guchar *) g_malloc (width * tileheight * pelbytes);

  if (rle)
    myfread = rle_fread;
  else
    myfread = std_fread;

  wbytes = width * pelbytes;
  badread = 0;
  for (i = 0; i < height; i += tileheight)
    {
      tileheight = MIN (tileheight, height - i);

#ifdef VERBOSE
      if (verbose > 1)
	printf ("TGA: reading %dx(%d+%d)x%d pixel region\n", width,
		(vertrev ? (height - (i + tileheight)) : i),
		tileheight, pelbytes);
#endif

      npels = width * tileheight;
      bsize = wbytes * tileheight;

      /* Suck in the data one tileheight at a time. */
      if (badread)
	pels = 0;
      else
	pels = (*myfread) (data, bpp, npels, fp);

      if (pels != npels)
	{
	  if (!badread)
	    {
	      /* Probably premature end of file. */
	      printf ("TGA: error reading (ftell == %ld)\n", ftell (fp));
	      badread = 1;
	    }

#if 0
  verbose = 2;
  printf ("TGA: debug %d\n", getpid ());
  kill (getpid (), 19);
#endif

	  /* Fill the rest of this tile with zeros. */
	  memset (data + (pels * bpp), 0, ((npels - pels) * bpp));
	}

      /* If we have indexed alphas, then set them. */
      if (nalphas)
	{
	  /* Start at the end of the buffer, and work backwards. */
	  k = (npels - 1) * bpp;
	  for (j = bsize - pelbytes; j >= 0; j -= pelbytes)
	    {
	      /* Find the alpha for this index. */
	      data[j + 1] = alphas[data[k]];
	      data[j] = data[k --];
	    }
	}

      if (pelbytes >= 3)
	{
	  /* Rearrange the colors from BGR to RGB. */
	  int tmp;
	  for (j = 0; j < bsize; j += pelbytes)
	    {
	      tmp = data[j];
	      data[j] = data[j + 2];
	      data[j + 2] = tmp;
	    }
	}


      if (horzrev || vertrev)
	{
	  guchar *tmp;
	  if (vertrev)
	    {
	      /* We need to mirror only vertically. */
	      for (j = 0; j < bsize; j += wbytes)
		memcpy (buffer + j,
			data + bsize - (j + wbytes), wbytes);
	    }
	  else if (horzrev)
	    {
	      /* We need to mirror only horizontally. */
	      for (j = 0; j < bsize; j += wbytes)
		for (k = 0; k < wbytes; k += pelbytes)
		  memcpy (buffer + k + j,
			  data + (j + wbytes) - (k + pelbytes), pelbytes);
	    }
	  else
	    {
	      /* Completely reverse the pixels in the buffer. */
	      for (j = 0; j < bsize; j += pelbytes)
		memcpy (buffer + j,
			data + bsize - (j + pelbytes), pelbytes);
	    }

	  /* Swap the buffers because we modified them. */
	  tmp = buffer;
	  buffer = data;
	  data = tmp;
	}

      gimp_progress_update ((double) (i + tileheight) / (double) height);

      /* If vertically reversed, put the data at the end. */
      gimp_pixel_rgn_set_rect
	(&pixel_rgn, data,
	 0, (vertrev ? (height - (i + tileheight)) : i),
	 width, tileheight);
    }

  if (fgetc (fp) != EOF)
    printf ("TGA: too much input data, ignoring extra...\n");

  g_free (data);
  g_free (buffer);

  if (alphas)
    g_free (alphas);

  gimp_drawable_flush (drawable);
  gimp_drawable_detach (drawable);

  return image_ID;
}  /*read_image*/


gint
save_image (char   *filename,
	    gint32  image_ID,
	    gint32  drawable_ID)
{
  GPixelRgn pixel_rgn;
  GDrawable *drawable;
  GDrawableType dtype;
  int width, height;
  FILE *fp;
  guchar *name_buf;
  int i, j, k;
  int npels, tileheight, pelbytes, bsize;
  int transparent, status;

  struct tga_header hdr;

  int (*myfwrite)(guchar *, int, int, FILE *);
  guchar *data;

  drawable = gimp_drawable_get(drawable_ID);
  dtype = gimp_drawable_type(drawable_ID);
  width = drawable->width;
  height = drawable->height;

  name_buf = (guchar *) g_malloc(strlen(filename) + 11);
  sprintf(name_buf, "Saving %s:", filename);
  gimp_progress_init(name_buf);
  g_free(name_buf);

  memset (&hdr, 0, sizeof (hdr));
  /* We like our images top-to-bottom, thank you! */
  hdr.descriptor |= TGA_DESC_VERTICAL;

  /* Choose the imageType based on our drawable and compression option. */
  switch (dtype)
    {
    case INDEXEDA_IMAGE:
    case INDEXED_IMAGE:
      hdr.bpp = 8;
      hdr.imageType = TGA_TYPE_MAPPED;
      break;

    case GRAYA_IMAGE:
      hdr.bpp = 8;
      hdr.descriptor |= 8;
    case GRAY_IMAGE:
      hdr.bpp += 8;
      hdr.imageType = TGA_TYPE_GRAY;
      break;

    case RGBA_IMAGE:
      hdr.bpp = 8;
      hdr.descriptor |= 8;
    case RGB_IMAGE:
      hdr.bpp += 24;
      hdr.imageType = TGA_TYPE_COLOR;
      break;

    default:
      return FALSE;
    }

  if (tsvals.rle)
    {
      /* Here we take advantage of the fact that the RLE image type codes
	 are exactly 8 greater than the non-RLE. */
      hdr.imageType += 8;
      myfwrite = rle_fwrite;
    }
  else
    myfwrite = std_fwrite;

  hdr.widthLo = (width & 0xff);
  hdr.widthHi = (width >> 8);

  hdr.heightLo = (height & 0xff);
  hdr.heightHi = (height >> 8);

  if((fp = fopen(filename, "wb")) == NULL) {
    printf("TGA: can't create \"%s\"\n", filename);
    return FALSE;
  }

  /* Mark our save ID. */
  hdr.idLength = strlen (SAVE_ID_STRING);

  /* See if we have to write out the colour map. */
  transparent = 0;
  if (hdr.imageType == TGA_TYPE_MAPPED_RLE ||
      hdr.imageType == TGA_TYPE_MAPPED)
    {
      guchar *cmap;
      int colors;

      hdr.colorMapType = 1;
      cmap = gimp_image_get_cmap (image_ID, &colors);
      if (colors > 256)
	{
	  printf ("TGA: cannot handle colormap with more than 256 colors (got %d)\n", colors);
	  return FALSE;
	}

      /* If we already have more than 256 colors, then ignore the
	 alpha channel.  Otherwise, create an entry for any completely
	 transparent pixels. */
      if (dtype == INDEXEDA_IMAGE && colors < 256)
	{
	  transparent = colors;
	  hdr.colorMapSize = 32;
	  hdr.colorMapLengthLo = ((colors + 1) & 0xff);
	  hdr.colorMapLengthHi = ((colors + 1) >> 8);
	}
      else
	{
	  hdr.colorMapSize = 24;
	  hdr.colorMapLengthLo = (colors & 0xff);
	  hdr.colorMapLengthHi = (colors >> 8);
	}

      /* Write the header. */
      if (fwrite(&hdr, sizeof (hdr), 1, fp) != 1)
	return FALSE;
      if (fwrite (SAVE_ID_STRING, hdr.idLength, 1, fp) != 1)
	return FALSE;

      pelbytes = ROUNDUP_DIVIDE (hdr.colorMapSize, 8);
      if (transparent)
	{
	  guchar *newcmap;

	  /* Reallocate our colormap to have an alpha channel and
	     a fully transparent color. */
	  newcmap = (guchar *) g_malloc ((colors + 1) * pelbytes);

	  k = 0;
	  for (j = 0; j < colors * 3; j += 3)
	    {
	      /* Rearrange from RGB to BGR. */
	      newcmap[k ++] = cmap[j + 2];
	      newcmap[k ++] = cmap[j + 1];
	      newcmap[k ++] = cmap[j];

	      /* Set to maximum opacity. */
	      newcmap[k ++] = -1;
	    }

	  /* Add the transparent color. */
	  memset (newcmap + k, 0, pelbytes);

	  /* Write out the colormap. */
	  if (fwrite (newcmap, pelbytes, colors + 1, fp) != colors + 1)
	    return FALSE;
	  g_free (newcmap);
	}
      else
	{
	  /* Rearrange the colors from RGB to BGR. */
	  int tmp;
	  for (j = 0; j < colors * pelbytes; j += pelbytes)
	    {
	      tmp = cmap[j];
	      cmap[j] = cmap[j + 2];
	      cmap[j + 2] = tmp;
	    }

	  /* Write out the colormap. */
	  if (fwrite (cmap, pelbytes, colors, fp) != colors)
	    return FALSE;
	}

      g_free (cmap);
    }
  /* Just write the header. */
  else
    {
      if (fwrite(&hdr, sizeof (hdr), 1, fp) != 1)
	return FALSE;
      if (fwrite (SAVE_ID_STRING, hdr.idLength, 1, fp) != 1)
	return FALSE;
    }

  /* Allocate a new set of pixels. */
  tileheight = gimp_tile_height ();
  data = (guchar *) g_malloc(width * tileheight * drawable->bpp);

  gimp_pixel_rgn_init(&pixel_rgn, drawable, 0, 0, width, height, FALSE, FALSE);

  /* Write out the pixel data. */
  pelbytes = ROUNDUP_DIVIDE (hdr.bpp, 8);
  status = TRUE;
  for (i = 0; i < height; i += tileheight)
    {
      /* Get a horizontal slice of the image. */
      tileheight = MIN(tileheight, height - i);
      gimp_pixel_rgn_get_rect(&pixel_rgn, data, 0, i, width, tileheight);

#ifdef VERBOSE
      if (verbose > 1)
	printf ("TGA: writing %dx(%d+%d)x%d pixel region\n",
		width, i, tileheight, pelbytes);
#endif

      npels = width * tileheight;
      bsize = npels * pelbytes;

      /* If the GIMP's bpp does not match the TGA format, strip
	 the excess bytes. */
      if (drawable->bpp > pelbytes)
	{
	  int nbytes, difference, fullbsize;

	  j = k = 0;
	  fullbsize = npels * drawable->bpp;

	  difference = drawable->bpp - pelbytes;
	  while (j < fullbsize)
	    {
	      nbytes = 0;
	      for (nbytes = 0; nbytes < pelbytes; nbytes ++)
		/* Be careful to handle overlapping pixels. */
		data[k ++] = data[j ++];

	      /* If this is an indexed image, and data[j] (alpha
		 channel) is zero, then we should write our transparent
		 pixel's index. */
	      if (dtype == INDEXEDA_IMAGE && transparent && data[j] == 0)
		data[k - 1] = transparent;

	      /* Increment J to the next pixel. */
	      j += difference;
	    }
	}

      if (pelbytes >= 3)
	{
	  /* Rearrange the colors from RGB to BGR. */
	  int tmp;
	  for (j = 0; j < bsize; j += pelbytes)
	    {
	      tmp = data[j];
	      data[j] = data[j + 2];
	      data[j + 2] = tmp;
	    }
	}

      if ((*myfwrite) (data, pelbytes, npels, fp) != npels)
	{
	  /* We have no choice but to break and truncate the file
	     if we are writing with RLE. */
	  status = FALSE;
	  break;
	}

      gimp_progress_update ((double) (i + tileheight) / (double) height);
    }

  gimp_drawable_detach(drawable);
  g_free (data);

  fclose(fp);
  return status;
}


static gint
save_dialog ()
{
  GtkWidget *dlg;
  GtkWidget *button;
  GtkWidget *toggle;
  GtkWidget *frame;
  GtkWidget *vbox;
  gchar **argv;
  gint argc;

  argc = 1;
  argv = g_new (gchar *, 1);
  argv[0] = g_strdup ("save");

  gtk_init (&argc, &argv);
  gtk_rc_parse (gimp_gtkrc ());

  dlg = gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (dlg), "Save as Tga");
  gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      (GtkSignalFunc) save_close_callback,
		      NULL);

  /*  Action area  */
  button = gtk_button_new_with_label ("OK");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) save_ok_callback,
                      dlg);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_grab_default (button);
  gtk_widget_show (button);

  button = gtk_button_new_with_label ("Cancel");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
			     (GtkSignalFunc) gtk_widget_destroy,
			     GTK_OBJECT (dlg));
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

  /* regular tga parameter settings */
  frame = gtk_frame_new ("Targa Options");
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_container_border_width (GTK_CONTAINER (frame), 10);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), frame, TRUE, TRUE, 0);
  vbox = gtk_vbox_new (FALSE, 5);
  gtk_container_border_width (GTK_CONTAINER (vbox), 5);
  gtk_container_add (GTK_CONTAINER (frame), vbox);

  /*  rle  */
  toggle = gtk_check_button_new_with_label ("RLE compression");
  gtk_box_pack_start (GTK_BOX (vbox), toggle, TRUE, TRUE, 0);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      (GtkSignalFunc) save_toggle_update,
		      &tsvals.rle);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), tsvals.rle);
  gtk_widget_show (toggle);

  gtk_widget_show (vbox);
  gtk_widget_show (frame);

  gtk_widget_show (dlg);

  gtk_main ();
  gdk_flush ();

  return tsint.run;
}

/*  Save interface functions  */

static void
save_close_callback (GtkWidget *widget,
		     gpointer   data)
{
  gtk_main_quit ();
}

static void
save_ok_callback (GtkWidget *widget,
		  gpointer   data)
{
  tsint.run = TRUE;
  gtk_widget_destroy (GTK_WIDGET (data));
}

static void
save_toggle_update (GtkWidget *widget,
		    gpointer   data)
{
  int *toggle_val;

  toggle_val = (int *) data;

  if (GTK_TOGGLE_BUTTON (widget)->active)
    *toggle_val = TRUE;
  else
    *toggle_val = FALSE;
}

/* The End */

/*
Local Variables:
compile-command:"gcc -Wall -g -O -o tga tga.c -lgimp -lgtk -lgdk -lglib -lm"
End:
*/
