/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * $Id: tga.c,v 1.50 2004/11/02 12:00:24 mitch 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * Modified August-November 2000, Nick Lamb <njl195@zepler.org.uk>
 *   - Clean-up more code, avoid structure implementation dependency,
 *   - Load more types of images reliably, reject others firmly
 *   - This is not perfect, but I think it's much better. Test please!
 *
 * 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:
 *   - 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.
 */

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

#include "config.h"

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

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <gtk/gtk.h>

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

#include "libgimp/stdplugins-intl.h"


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

typedef struct _TgaSaveVals
{
  gint rle;
  gint origin;
} TgaSaveVals;

static TgaSaveVals tsvals =
{
  1,    /* rle = ON */
  1, 	/* origin = bottom left */
};


 /* TRUEVISION-XFILE magic signature string */
static guchar magic[18] =
{
  0x54, 0x52, 0x55, 0x45, 0x56, 0x49, 0x53, 0x49, 0x4f,
  0x4e, 0x2d, 0x58, 0x46, 0x49, 0x4c, 0x45, 0x2e, 0x0
};

typedef struct tga_info_struct
{
  guint8 idLength;
  guint8 colorMapType;

  guint8 imageType;
  /* Known image types. */
#define TGA_TYPE_MAPPED      1
#define TGA_TYPE_COLOR       2
#define TGA_TYPE_GRAY        3

  guint8 imageCompression;
  /* Only known compression is RLE */
#define TGA_COMP_NONE        0
#define TGA_COMP_RLE         1

  /* Color Map Specification. */
  /* We need to separately specify high and low bytes to avoid endianness
     and alignment problems. */

  guint16 colorMapIndex;
  guint16 colorMapLength;
  guint8 colorMapSize;

  /* Image Specification. */
  guint16 xOrigin;
  guint16 yOrigin;

  guint16 width;
  guint16 height;

  guint8 bpp;
  guint8 bytes;

  guint8 alphaBits;
  guint8 flipHoriz;
  guint8 flipVert;

  /* Extensions (version 2) */

/* Not all the structures described in the standard are transcribed here
   only those which seem applicable to Gimp */

  gchar authorName[41];
  gchar comment[324];
  guint month, day, year, hour, minute, second;
  gchar jobName[41];
  gchar softwareID[41];
  guint pixelWidth, pixelHeight;  /* write dpi? */
  gdouble gamma;
} tga_info;


/* 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);
static gint      save_image    (const gchar      *filename,
                                gint32            image_ID,
                                gint32            drawable_ID);

static gboolean  save_dialog   (void);

static gint32    ReadImage     (FILE             *fp,
                                tga_info         *info,
                                const gchar      *filename);


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


MAIN ()

static void
query (void)
{
  static GimpParamDef load_args[] =
  {
    { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" },
    { GIMP_PDB_STRING, "filename", "The name of the file to load" },
    { GIMP_PDB_STRING, "raw_filename", "The name entered" }
  };

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

  static GimpParamDef save_args[] =
  {
    { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" },
    { GIMP_PDB_IMAGE, "image", "Input image" },
    { GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" },
    { GIMP_PDB_STRING, "filename", "The name of the file to save the image in" },
    { GIMP_PDB_STRING, "raw_filename", "The name of the file to save the image in" },
    { GIMP_PDB_INT32, "rle", "Use RLE compression" },
    { GIMP_PDB_INT32, "origin", "Image origin (0 = top-left, 1 = bottom-left)"}

  } ;

  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",
                          N_("TarGA 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 ("file_tga_load", "image/x-tga");
  gimp_register_load_handler ("file_tga_load", "tga", "");

  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",
                          N_("TarGA image"),
			  "RGB*, GRAY*, INDEXED*",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (save_args), 0,
                          save_args, NULL);

  gimp_register_file_handler_mime ("file_tga_save", "image/x-tga");
  gimp_register_save_handler ("file_tga_save", "tga", "");
}

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;

#ifdef PROFILE
  struct tms tbuf1, tbuf2;
#endif

  run_mode = param[0].data.d_int32;

  INIT_I18N ();

  *nreturn_vals = 1;
  *return_vals  = values;
  values[0].type          = GIMP_PDB_STATUS;
  values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;

  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[1].type         = GIMP_PDB_IMAGE;
          values[1].data.d_image = image_ID;
        }
      else
        {
          status = GIMP_PDB_EXECUTION_ERROR;
        }
    }
  else if (strcmp (name, "file_tga_save") == 0)
    {
      gimp_ui_init ("tga", FALSE);

      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:
	  export = gimp_export_image (&image_ID, &drawable_ID, "TGA",
				      (GIMP_EXPORT_CAN_HANDLE_RGB |
				       GIMP_EXPORT_CAN_HANDLE_GRAY |
				       GIMP_EXPORT_CAN_HANDLE_INDEXED |
				       GIMP_EXPORT_CAN_HANDLE_ALPHA ));
	  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 ("file_tga_save", &tsvals);

	  /*  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 != 7)
	    {
	      status = GIMP_PDB_CALLING_ERROR;
	    }
	  else
	    {
	      tsvals.rle = (param[5].data.d_int32) ? TRUE : FALSE;
	    }
	  break;

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

	default:
	  break;
	}

#ifdef PROFILE
      times (&tbuf1);
#endif

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

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

  values[0].data.d_status = status;

#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
load_image (const gchar *filename)
{
  FILE     *fp;
  gchar    *name_buf;
  tga_info  info;
  guchar    header[18];
  guchar    footer[26];
  guchar    extension[495];
  long      offset;

  gint32 image_ID = -1;

  fp = fopen (filename, "rb");
  if (!fp)
    {
      g_message (_("Could not open '%s' for reading: %s"),
                 gimp_filename_to_utf8 (filename), g_strerror (errno));
      return -1;
    }

  name_buf = g_strdup_printf (_("Opening '%s'..."),
                              gimp_filename_to_utf8 (filename));
  gimp_progress_init (name_buf);
  g_free (name_buf);

  if (!fseek (fp, -26L, SEEK_END)) { /* Is file big enough for a footer? */
    if (fread (footer, sizeof (footer), 1, fp) != 1)
      {
        g_message (_("Cannot read footer from '%s'"),
                   gimp_filename_to_utf8 (filename));
        return -1;
      }
    else if (memcmp (footer + 8, magic, sizeof (magic)) == 0)
      {
        /* Check the signature. */

        offset= footer[0] + (footer[1] * 256) + (footer[2] * 65536)
                          + (footer[3] * 16777216);

        if (offset != 0)
          {
            if (fseek (fp, offset, SEEK_SET) ||
                fread (extension, sizeof (extension), 1, fp) != 1)
              {
                g_message (_("Cannot read extension from '%s'"),
                           gimp_filename_to_utf8 (filename));
                return -1;
              }
            /* Eventually actually handle version 2 TGA here */
          }
      }
  }

  if (fseek (fp, 0, SEEK_SET) ||
      fread (header, sizeof (header), 1, fp) != 1)
    {
      g_message ("Cannot read header from '%s'",
                 gimp_filename_to_utf8 (filename));
      return -1;
    }

  switch (header[2])
    {
    case 1:
      info.imageType = TGA_TYPE_MAPPED;
      info.imageCompression = TGA_COMP_NONE;
      break;
    case 2:
      info.imageType = TGA_TYPE_COLOR;
      info.imageCompression = TGA_COMP_NONE;
      break;
    case 3:
      info.imageType = TGA_TYPE_GRAY;
      info.imageCompression = TGA_COMP_NONE;
      break;

    case 9:
      info.imageType = TGA_TYPE_MAPPED;
      info.imageCompression = TGA_COMP_RLE;
      break;
    case 10:
      info.imageType = TGA_TYPE_COLOR;
      info.imageCompression = TGA_COMP_RLE;
      break;
    case 11:
      info.imageType = TGA_TYPE_GRAY;
      info.imageCompression = TGA_COMP_RLE;
      break;

    default:
      info.imageType = 0;
    }

  info.idLength     = header[0];
  info.colorMapType = header[1];

  info.colorMapIndex  = header[3] + header[4] * 256;
  info.colorMapLength = header[5] + header[6] * 256;
  info.colorMapSize   = header[7];

  info.xOrigin = header[8] + header[9] * 256;
  info.yOrigin = header[10] + header[11] * 256;
  info.width   = header[12] + header[13] * 256;
  info.height  = header[14] + header[15] * 256;

  info.bpp       = header[16];
  info.bytes     = (info.bpp + 7) / 8;
  info.alphaBits = header[17] & 0x0f; /* Just the low 4 bits */
  info.flipHoriz = (header[17] & 0x10) ? 1 : 0;
  info.flipVert  = (header[17] & 0x20) ? 0 : 1;

  switch (info.imageType)
    {
      case TGA_TYPE_MAPPED:
        if (info.bpp != 8)
          {
            g_message ("Unhandled sub-format in '%s'",
                       gimp_filename_to_utf8 (filename));
            return -1;
          }
        break;
      case TGA_TYPE_COLOR:
        if (info.bpp != 15 && info.bpp != 16 && info.bpp != 24
                     && info.bpp != 32)
          {
            g_message ("Unhandled sub-format in '%s'",
                       gimp_filename_to_utf8 (filename));
            return -1;
          }
        break;
      case TGA_TYPE_GRAY:
        if (info.bpp != 8 && (info.alphaBits != 8 || (info.bpp != 16 && info.bpp != 15)))
          {
            g_message ("Unhandled sub-format in '%s'",
                       gimp_filename_to_utf8 (filename));
            return -1;
          }
        break;

      default:
        g_message ("Unknown image type for '%s'",
                   gimp_filename_to_utf8 (filename));
        return -1;
    }

  /* Plausible but unhandled formats */
  if (info.bytes * 8 != info.bpp && !(info.bytes == 2 && info.bpp == 15))
    {
      g_message ("No support yet for TGA with these parameters");
      return -1;
    }

  /* Check that we have a color map only when we need it. */
  if (info.imageType == TGA_TYPE_MAPPED && info.colorMapType != 1)
    {
      g_message ("Indexed image has invalid color map type %d",
                 info.colorMapType);
      return -1;
    }
  else if (info.imageType != TGA_TYPE_MAPPED && info.colorMapType != 0)
    {
      g_message ("Non-indexed image has invalid color map type %d",
                 info.colorMapType);
      return -1;
    }

  /* Skip the image ID field. */
  if (info.idLength && fseek (fp, info.idLength, SEEK_CUR))
    {
      g_message ("File '%s' is truncated or corrupted",
                 gimp_filename_to_utf8 (filename));
      return -1;
    }

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

static void
rle_write (FILE   *fp,
	   guchar *buffer,
	   guint   width,
	   guint   bytes)
{
  gint    repeat = 0;
  gint    direct = 0;
  guchar *from   = buffer;
  gint    x;

  for (x = 1; x < width; ++x)
    {
      if (memcmp (buffer, buffer + bytes, bytes))
	{
	  /* next pixel is different */
	  if (repeat)
	    {
	      putc (128 + repeat, fp);
	      fwrite (from, bytes, 1, fp);
	      from = buffer+ bytes; /* point to first different pixel */
	      repeat = 0;
	      direct = 0;
	    }
	  else
	    {
	      direct += 1;
	    }
	}
      else
	{
	  /* next pixel is the same */
	  if (direct)
	    {
	      putc (direct - 1, fp);
	      fwrite (from, bytes, direct, fp);
	      from = buffer; /* point to first identical pixel */
	      direct = 0;
	      repeat = 1;
	    }
	  else
	    {
	      repeat += 1;
	    }
	}

      if (repeat == 128)
	{
	  putc (255, fp);
	  fwrite (from, bytes, 1, fp);
	  from = buffer+ bytes;
	  direct = 0;
	  repeat = 0;
	}
      else if (direct == 128)
	{
	  putc (127, fp);
	  fwrite (from, bytes, direct, fp);
	  from = buffer+ bytes;
	  direct = 0;
	  repeat = 0;
	}

      buffer += bytes;
    }

  if (repeat > 0)
    {
      putc (128 + repeat, fp);
      fwrite (from, bytes, 1, fp);
    }
  else
    {
      putc (direct, fp);
      fwrite (from, bytes, direct + 1, fp);
    }
}

static gint
rle_read (FILE     *fp,
	  guchar   *buffer,
	  tga_info *info)
{
  static gint   repeat = 0;
  static gint   direct = 0;
  static guchar sample[4];
  gint head;
  gint x, k;

  for (x = 0; x < info->width; x++)
    {
      if (repeat == 0 && direct == 0)
	{
	  head = getc (fp);

	  if (head == EOF)
	    {
	      return EOF;
	    }
	  else if (head >= 128)
	    {
	      repeat = head - 127;

	      if (fread (sample, info->bytes, 1, fp) < 1)
		return EOF;
	    }
	  else
	    {
	      direct = head + 1;
	    }
	}

      if (repeat > 0)
	{
	  for (k = 0; k < info->bytes; ++k)
	    {
	      buffer[k] = sample[k];
	    }

	  repeat--;
	}
      else /* direct > 0 */
	{
	  if (fread (buffer, info->bytes, 1, fp) < 1)
	    return EOF;

	  direct--;
	}

      buffer += info->bytes;
    }

  return 0;
}

static void
flip_line (guchar   *buffer,
	   tga_info *info)
{
  guchar  temp;
  guchar *alt;
  gint    x, s;

  alt = buffer + (info->bytes * (info->width - 1));

  for (x = 0; x * 2 <= info->width; x++)
    {
      for (s = 0; s < info->bytes; ++s)
	{
	  temp = buffer[s];
	  buffer[s] = alt[s];
	  alt[s] = temp;
	}

      buffer += info->bytes;
      alt -= info->bytes;
    }
}

/* Some people write 16-bit RGB TGA files. The spec would probably
   allow 27-bit RGB too, for what it's worth, but I won't fix that
   unless someone actually provides an existence proof */

static void
upsample (guchar *dest,
	  guchar *src,
	  guint   width,
	  guint   bytes,
	  guint8  alphaBits)
{
  gint x;

  for (x = 0; x < width; x++)
    {
      dest[0] =  ((src[1] << 1) & 0xf8);
      dest[0] += (dest[0] >> 5);

      dest[1] =  ((src[0] & 0xe0) >> 2) + ((src[1] & 0x03) << 6);
      dest[1] += (dest[1] >> 5);

      dest[2] =  ((src[0] << 3) & 0xf8);
      dest[2] += (dest[2] >> 5);

      switch (alphaBits)
        {
        case 1:
	  dest[3] = (src[1] & 0x80)? 0: 255;
	  dest += 4;
          break;
        default:
	  dest += 3;
        }

      src += bytes;
    }
}

static void
bgr2rgb (guchar *dest,
	 guchar *src,
	 guint   width,
	 guint   bytes,
	 guint   alpha)
{
  guint x;

  if (alpha)
    {
      for (x = 0; x < width; x++)
	{
	  *(dest++) = src[2];
	  *(dest++) = src[1];
	  *(dest++) = src[0];
	  *(dest++) = src[3];

	  src += bytes;
	}
    }
  else
    {
      for (x = 0; x < width; x++)
	{
	  *(dest++) = src[2];
	  *(dest++) = src[1];
	  *(dest++) = src[0];

	  src += bytes;
	}
    }
}

static void
read_line (FILE         *fp,
	   guchar       *row,
	   guchar       *buffer,
	   tga_info     *info,
	   GimpDrawable *drawable)
{
  if (info->imageCompression == TGA_COMP_RLE)
    {
      rle_read (fp, buffer, info);
    }
  else
    {
      fread (buffer, info->bytes, info->width, fp);
    }

  if (info->flipHoriz)
    {
      flip_line (buffer, info);
    }

  if (info->imageType == TGA_TYPE_COLOR)
    {
      if (info->bpp == 16 || info->bpp == 15)
	{
	  upsample (row, buffer, info->width, info->bytes, info->alphaBits);
	}
      else
	{
	  bgr2rgb (row, buffer,info->width,info->bytes,info->alphaBits);
	}
    }
  else
    {
      memcpy (row, buffer, info->width * drawable->bpp);
    }
}

static gint32
ReadImage (FILE        *fp,
	   tga_info    *info,
	   const gchar *filename)
{
  static gint32 image_ID;
  gint32        layer_ID;

  GimpPixelRgn       pixel_rgn;
  GimpDrawable      *drawable;
  guchar            *data, *buffer, *row;
  GimpImageType      dtype = 0;
  GimpImageBaseType  itype = 0;
  gint               i, y;

  gint max_tileheight, tileheight;

  guint  cmap_bytes;
  guchar tga_cmap[4 * 256], gimp_cmap[3 * 256];

  switch (info->imageType)
    {
    case TGA_TYPE_MAPPED:
      itype = GIMP_INDEXED;

      if (info->alphaBits)
	dtype = GIMP_INDEXEDA_IMAGE;
      else
	dtype = GIMP_INDEXED_IMAGE;
      break;

    case TGA_TYPE_GRAY:
      itype = GIMP_GRAY;

      if (info->alphaBits)
	dtype = GIMP_GRAYA_IMAGE;
      else
	dtype = GIMP_GRAY_IMAGE;
      break;

    case TGA_TYPE_COLOR:
      itype = GIMP_RGB;

      if (info->alphaBits)
	dtype = GIMP_RGBA_IMAGE;
      else
	dtype = GIMP_RGB_IMAGE;
      break;
    }

  /* Handle colormap */

  if (info->colorMapType == 1)
    {
      cmap_bytes= (info->colorMapSize + 7 ) / 8;
      if (cmap_bytes <= 4 &&
          fread (tga_cmap, info->colorMapLength * cmap_bytes, 1, fp) == 1)
        {
          if (info->colorMapSize == 32)
            bgr2rgb(gimp_cmap, tga_cmap, info->colorMapLength, cmap_bytes, 1);
          else if (info->colorMapSize == 24)
            bgr2rgb(gimp_cmap, tga_cmap, info->colorMapLength, cmap_bytes, 0);
          else if (info->colorMapSize == 16 || info->colorMapSize == 15)
            upsample(gimp_cmap, tga_cmap, info->colorMapLength, cmap_bytes, info->alphaBits);

        }
      else
        {
          g_message ("File '%s' is truncated or corrupted",
                     gimp_filename_to_utf8 (filename));
          return -1;
        }
    }

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

  if (info->colorMapType == 1)
    gimp_image_set_colormap(image_ID, gimp_cmap, info->colorMapLength);

  layer_ID = gimp_layer_new (image_ID,
			     _("Background"),
			     info->width, info->height,
			     dtype, 100,
			     GIMP_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,
                        info->width, info->height, TRUE, FALSE);

  /* Allocate the data. */
  max_tileheight = gimp_tile_height ();
  data = (guchar *) g_malloc (info->width * max_tileheight * drawable->bpp);
  buffer = (guchar *) g_malloc (info->width * info->bytes);

  if (info->flipVert)
    {
      for (i = 0; i < info->height; i += tileheight)
        {
          tileheight = i ? max_tileheight : (info->height % max_tileheight);
          if (tileheight == 0) tileheight= max_tileheight;

          for (y= 1; y <= tileheight; ++y)
            {
              row= data + (info->width * drawable->bpp * (tileheight - y));
              read_line(fp, row, buffer, info, drawable);
            }

            gimp_progress_update ((double) (i + tileheight) /
                                  (double) info->height);
            gimp_pixel_rgn_set_rect (&pixel_rgn, data, 0,
                                     info->height - i - tileheight,
                                     info->width, tileheight);
        }
    }
  else
    {
      for (i = 0; i < info->height; i += max_tileheight)
        {
          tileheight = MIN (max_tileheight, info->height - i);

          for (y= 0; y < tileheight; ++y)
            {
              row= data + (info->width * drawable->bpp * y);
              read_line(fp, row, buffer, info, drawable);
            }

            gimp_progress_update ((double) (i + tileheight) /
                                  (double) info->height);
            gimp_pixel_rgn_set_rect (&pixel_rgn, data, 0, i,
                                     info->width, tileheight);
        }
    }

  g_free (data);
  g_free (buffer);

  gimp_drawable_flush (drawable);
  gimp_drawable_detach (drawable);

  return image_ID;
}  /*read_image*/


static gint
save_image (const gchar *filename,
	    gint32       image_ID,
	    gint32       drawable_ID)
{
  GimpPixelRgn   pixel_rgn;
  GimpDrawable  *drawable;
  GimpImageType  dtype;
  gint           width;
  gint           height;

  FILE     *fp;
  gchar    *name_buf;
  gint      tileheight;
  gint      out_bpp = 0;
  gboolean  status  = TRUE;
  gint      i, row;

  guchar  header[18];
  guchar  footer[26];
  guchar *pixels;
  guchar *data;

  gint    num_colors;
  guchar *gimp_cmap = NULL;

  drawable = gimp_drawable_get (drawable_ID);
  dtype    = gimp_drawable_type (drawable_ID);

  width  = drawable->width;
  height = drawable->height;

  if ((fp = fopen (filename, "wb")) == NULL)
    {
      g_message (_("Could not open '%s' for writing: %s"),
                 gimp_filename_to_utf8 (filename), g_strerror (errno));
      return FALSE;
    }

  name_buf = g_strdup_printf (_("Saving '%s'..."),
                              gimp_filename_to_utf8 (filename));
  gimp_progress_init (name_buf);
  g_free (name_buf);

  header[0] = 0; /* No image identifier / description */

  if (dtype == GIMP_INDEXED_IMAGE || dtype == GIMP_INDEXEDA_IMAGE)
    {
      gimp_cmap = gimp_image_get_colormap (image_ID, &num_colors);

      header[1] = 1; /* cmap type */
      header[2] = (tsvals.rle) ? 9 : 1;
      header[3] = header[4]= 0; /* no offset */
      header[5] = num_colors % 256;
      header[6] = num_colors / 256;
      header[7] = 24; /* cmap size / bits */
    }
  else
    {
      header[1]= 0;

      if (dtype == GIMP_RGB_IMAGE || dtype == GIMP_RGBA_IMAGE)
	{
	  header[2]= (tsvals.rle) ? 10 : 2;
	}
      else
	{
	  header[2]= (tsvals.rle) ? 11 : 3;
	}

      header[3] = header[4] = header[5] = header[6] = header[7] = 0;
    }

  header[8]  = header[9]  = 0; /* xorigin */
  header[10] = header[11] = 0; /* yorigin */

  header[12] = width % 256;
  header[13] = width / 256;

  header[14] = height % 256;
  header[15] = height / 256;

  switch (dtype)
    {
    case GIMP_INDEXED_IMAGE:
    case GIMP_GRAY_IMAGE:
    case GIMP_INDEXEDA_IMAGE:
      out_bpp = 1;
      header[16] = 8; /* bpp */
      header[17] = (tsvals.origin) ? 0 :  0x20; /* alpha + orientation */
      break;

    case GIMP_GRAYA_IMAGE:
      out_bpp = 2;
      header[16] = 16; /* bpp */
      header[17] = (tsvals.origin) ? 8 : 0x28; /* alpha + orientation */
      break;

    case GIMP_RGB_IMAGE:
      out_bpp = 3;
      header[16] = 24; /* bpp */
      header[17] = (tsvals.origin) ? 0 : 0x20; /* alpha + orientation */
      break;

    case GIMP_RGBA_IMAGE:
      out_bpp = 4;
      header[16] = 32; /* bpp */
      header[17] = (tsvals.origin) ? 8 : 0x28; /* alpha + orientation */
      break;
    }

  /* write header to front of file */
  fwrite (header, sizeof (header), 1, fp);

  if (dtype == GIMP_INDEXED_IMAGE || dtype == GIMP_INDEXEDA_IMAGE)
    {
      /* write out palette */
      for (i= 0; i < num_colors; ++i)
	{
	  fputc (gimp_cmap[(i * 3) + 2], fp);
	  fputc (gimp_cmap[(i * 3) + 1], fp);
	  fputc (gimp_cmap[(i * 3) + 0], fp);
	}
    }

  /* Allocate a new set of pixels. */
  tileheight = gimp_tile_height ();

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

  pixels = g_new (guchar, width * drawable->bpp);
  data   = g_new (guchar, width * out_bpp);

  for (row = 0; row < height; ++row)
    {
      if(tsvals.origin)
        {
          gimp_pixel_rgn_get_rect (&pixel_rgn, pixels, 0, height-(row+1), width, 1);
        }
      else
        {
          gimp_pixel_rgn_get_rect (&pixel_rgn, pixels, 0, row, width, 1);
        }

      if (dtype == GIMP_RGB_IMAGE)
	{
	  bgr2rgb(data, pixels, width, drawable->bpp, 0);
	}
      else if (dtype == GIMP_RGBA_IMAGE)
	{
	  bgr2rgb(data, pixels, width, drawable->bpp, 1);
	}
      else if (dtype == GIMP_INDEXEDA_IMAGE)
	{
	  for (i = 0; i < width; ++i)
	    {
	      data[i]= pixels[i*2];
	    }
	}
      else
	{
	  memcpy (data, pixels, width * drawable->bpp);
	}

      if (tsvals.rle)
	{
	  rle_write (fp, data, width, out_bpp);
	}
      else
	{
	  fwrite (data, width * out_bpp, 1, fp);
	}

      gimp_progress_update ((gdouble) row / (gdouble) height);
    }

  gimp_drawable_detach (drawable);
  g_free (data);

  /* footer must be the last thing written to file */
  memset (footer, 0, 8); /* No extensions, no developer directory */
  memcpy (footer + 8, magic, sizeof (magic)); /* magic signature */
  fwrite (footer, sizeof (footer), 1, fp);

  fclose (fp);

  return status;
}

static gboolean
save_dialog (void)
{
  GtkWidget *dlg;
  GtkWidget *toggle;
  GtkWidget *origin;
  GtkWidget *vbox;
  gboolean   run;

  dlg = gimp_dialog_new (_("Save as TGA"), "tga",
                         NULL, 0,
			 gimp_standard_help_func, "file-tga-save",

                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                         GTK_STOCK_OK,     GTK_RESPONSE_OK,

                         NULL);

  vbox = gtk_vbox_new (FALSE, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), vbox, TRUE, TRUE, 0);
  gtk_widget_show (vbox);

  /*  rle  */
  toggle = gtk_check_button_new_with_mnemonic (_("_RLE compression"));
  gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), tsvals.rle);
  gtk_widget_show (toggle);

  g_signal_connect (toggle, "toggled",
                    G_CALLBACK (gimp_toggle_button_update),
                    &tsvals.rle);

  /*  origin  */
  origin = gtk_check_button_new_with_mnemonic (_("Or_igin at bottom left"));
  gtk_box_pack_start (GTK_BOX (vbox), origin, FALSE, FALSE, 0);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (origin), tsvals.origin);
  gtk_widget_show (origin);

  g_signal_connect (origin, "toggled",
                    G_CALLBACK (gimp_toggle_button_update),
                    &tsvals.origin);

  gtk_widget_show (dlg);

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

  gtk_widget_destroy (dlg);

  return run;
}
