summaryrefslogtreecommitdiffstats
path: root/plug-ins/common/file-tga.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:13:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:13:10 +0000
commit3c57dd931145d43f2b0aef96c4d178135956bf91 (patch)
tree3de698981e9f0cc2c4f9569b19a5f3595e741f6b /plug-ins/common/file-tga.c
parentInitial commit. (diff)
downloadgimp-3c57dd931145d43f2b0aef96c4d178135956bf91.tar.xz
gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.zip
Adding upstream version 2.10.36.upstream/2.10.36
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plug-ins/common/file-tga.c')
-rw-r--r--plug-ins/common/file-tga.c1486
1 files changed, 1486 insertions, 0 deletions
diff --git a/plug-ins/common/file-tga.c b/plug-ins/common/file-tga.c
new file mode 100644
index 0000000..e833c60
--- /dev/null
+++ b/plug-ins/common/file-tga.c
@@ -0,0 +1,1486 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * TrueVision Targa loading and exporting file filter for 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 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/>.
+ */
+
+/*
+ * Modified 2007-07-20, Raphaƫl Quinet <raphael@gimp.org>:
+ * - Workaround for loading indexed images with full alpha channel.
+ * - Bug fix: save_image() was saving uninitialized tile data for
+ * indexed images with alpha.
+ *
+ * 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:
+ * - Handle TGA images with version 2 extensions (image comment,
+ * resolution, date, ...).
+ * - 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. Workaround implemented for loading by
+ * promoting the image to RGBA, but saving indexed TGA images with
+ * full alpha information in the coloramp is not supported yet (only
+ * one fully transparent color is allowed in INDEXEDA mode).
+ */
+
+/* Set these for debugging. */
+/* #define PROFILE 1 */
+
+#include "config.h"
+
+#ifdef PROFILE
+# include <sys/times.h>
+#endif
+
+#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-tga-load"
+#define SAVE_PROC "file-tga-save"
+#define PLUG_IN_BINARY "file-tga"
+#define PLUG_IN_ROLE "gimp-file-tga"
+
+typedef enum
+{
+ ORIGIN_TOP_LEFT = 0,
+ ORIGIN_BOTTOM_LEFT = 1
+} TgaOrigin;
+
+typedef struct _TgaSaveVals
+{
+ gboolean rle;
+ TgaOrigin origin;
+} TgaSaveVals;
+
+static TgaSaveVals tsvals =
+{
+ TRUE, /* rle */
+ ORIGIN_BOTTOM_LEFT /* origin */
+};
+
+
+ /* 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,
+ GError **error);
+static gint save_image (const gchar *filename,
+ gint32 image_ID,
+ gint32 drawable_ID,
+ GError **error);
+
+static gboolean save_dialog (void);
+
+static gint32 ReadImage (FILE *fp,
+ tga_info *info,
+ const gchar *filename);
+
+
+const GimpPlugInInfo PLUG_IN_INFO =
+{
+ NULL, /* init_proc */
+ NULL, /* quit_proc */
+ query, /* query_proc */
+ run, /* run_proc */
+};
+
+
+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 entered" }
+ };
+
+ 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", "Use RLE compression" },
+ { GIMP_PDB_INT32, "origin", "Image origin (0 = top-left, 1 = bottom-left)"}
+ } ;
+
+ gimp_install_procedure (LOAD_PROC,
+ "Loads files of Targa file format",
+ "FIXME: write help for tga_load",
+ "Raphael FRANCOIS, Gordon Matzigkeit",
+ "Raphael FRANCOIS, Gordon Matzigkeit",
+ "1997,2000,2007",
+ 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 (LOAD_PROC, "image/x-tga");
+ gimp_register_magic_load_handler (LOAD_PROC,
+ "tga,vda,icb,vst",
+ "",
+ "-18&,string,TRUEVISION-XFILE.,-1,byte,0");
+
+ gimp_install_procedure (SAVE_PROC,
+ "exports files in the Targa file format",
+ "FIXME: write help for tga_save",
+ "Raphael FRANCOIS, Gordon Matzigkeit",
+ "Raphael FRANCOIS, Gordon Matzigkeit",
+ "1997,2000",
+ N_("TarGA image"),
+ "RGB*, GRAY*, INDEXED*",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (save_args), 0,
+ save_args, NULL);
+
+ gimp_register_file_handler_mime (SAVE_PROC, "image/x-tga");
+ gimp_register_save_handler (SAVE_PROC, "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;
+ GError *error = NULL;
+
+#ifdef PROFILE
+ struct tms tbuf1, tbuf2;
+#endif
+
+ INIT_I18N ();
+ gegl_init (NULL, NULL);
+
+ run_mode = param[0].data.d_int32;
+
+ *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)
+ {
+#ifdef PROFILE
+ times (&tbuf1);
+#endif
+
+ 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)
+ {
+ gimp_ui_init (PLUG_IN_BINARY, 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 (SAVE_PROC, &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 (SAVE_PROC, &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,
+ &error))
+ {
+ /* Store psvals data */
+ gimp_set_data (SAVE_PROC, &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;
+ }
+
+ 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;
+
+#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,
+ GError **error)
+{
+ FILE *fp;
+ tga_info info;
+ guchar header[18];
+ guchar footer[26];
+ guchar extension[495];
+ long offset;
+ gint32 image_ID = -1;
+
+ gimp_progress_init_printf (_("Opening '%s'"),
+ gimp_filename_to_utf8 (filename));
+
+ fp = g_fopen (filename, "rb");
+
+ if (! fp)
+ {
+ 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;
+ }
+
+ /* Is file big enough for a footer? */
+ if (!fseek (fp, -26L, SEEK_END))
+ {
+ if (fread (footer, sizeof (footer), 1, fp) != 1)
+ {
+ g_message (_("Cannot read footer from '%s'"),
+ gimp_filename_to_utf8 (filename));
+ fclose (fp);
+ return -1;
+ }
+ else if (memcmp (footer + 8, magic, sizeof (magic)) == 0)
+ {
+ /* Check the signature. */
+
+ offset = (footer[0] +
+ footer[1] * 256L +
+ footer[2] * 65536L +
+ footer[3] * 16777216L);
+
+ 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));
+ fclose (fp);
+ 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));
+ fclose (fp);
+ 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;
+
+ /* hack to handle some existing files with incorrect headers, see bug #306675 */
+ if (info.alphaBits == info.bpp)
+ info.alphaBits = 0;
+
+ /* hack to handle yet another flavor of incorrect headers, see bug #540969 */
+ if (info.alphaBits == 0)
+ {
+ if (info.imageType == TGA_TYPE_MAPPED && info.colorMapSize == 32)
+ info.alphaBits = 8;
+
+ if (info.imageType == TGA_TYPE_COLOR && info.bpp == 32)
+ info.alphaBits = 8;
+
+ if (info.imageType == TGA_TYPE_GRAY && info.bpp == 16)
+ info.alphaBits = 8;
+ }
+ else if (info.alphaBits == 4 && info.imageType == TGA_TYPE_COLOR && info.bpp == 32)
+ {
+ /* Incorrect TGA saved by Krita, see issue #9067*/
+ info.alphaBits = 8;
+ }
+
+ switch (info.imageType)
+ {
+ case TGA_TYPE_MAPPED:
+ if (info.bpp != 8)
+ {
+ g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u)",
+ gimp_filename_to_utf8 (filename),
+ info.imageType, info.bpp);
+ fclose (fp);
+ return -1;
+ }
+ break;
+ case TGA_TYPE_COLOR:
+ if ((info.bpp != 15 && info.bpp != 16 &&
+ info.bpp != 24 && info.bpp != 32) ||
+ ((info.bpp == 15 || info.bpp == 24) &&
+ info.alphaBits != 0) ||
+ (info.bpp == 16 && info.alphaBits != 1 &&
+ info.alphaBits != 0) ||
+ (info.bpp == 32 && info.alphaBits != 8))
+ {
+ g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u, alpha = %u)",
+ gimp_filename_to_utf8 (filename),
+ info.imageType, info.bpp, info.alphaBits);
+ fclose (fp);
+ 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' (type = %u, bpp = %u)",
+ gimp_filename_to_utf8 (filename),
+ info.imageType, info.bpp);
+ fclose (fp);
+ return -1;
+ }
+ break;
+
+ default:
+ g_message ("Unknown image type %u for '%s'",
+ info.imageType, gimp_filename_to_utf8 (filename));
+ fclose (fp);
+ return -1;
+ }
+
+ /* Plausible but unhandled formats */
+ if (info.bytes * 8 != info.bpp && info.bpp != 15)
+ {
+ g_message ("Unhandled sub-format in '%s' (type = %u, bpp = %u)",
+ gimp_filename_to_utf8 (filename),
+ info.imageType, info.bpp);
+ fclose (fp);
+ 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 %u",
+ info.colorMapType);
+ fclose (fp);
+ return -1;
+ }
+ else if (info.imageType != TGA_TYPE_MAPPED && info.colorMapType != 0)
+ {
+ g_message ("Non-indexed image has invalid color map type %u",
+ info.colorMapType);
+ fclose (fp);
+ 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));
+ fclose (fp);
+ return -1;
+ }
+
+ image_ID = ReadImage (fp, &info, filename);
+
+ fclose (fp);
+
+ return image_ID;
+}
+
+static void
+rle_write (FILE *fp,
+ guchar *buf,
+ guint width,
+ guint bytes)
+{
+ gint repeat = 0;
+ gint direct = 0;
+ guchar *from = buf;
+ guint x;
+
+ for (x = 1; x < width; ++x)
+ {
+ if (memcmp (buf, buf + bytes, bytes))
+ {
+ /* next pixel is different */
+ if (repeat)
+ {
+ putc (128 + repeat, fp);
+ fwrite (from, bytes, 1, fp);
+ from = buf + 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 = buf; /* point to first identical pixel */
+ direct = 0;
+ repeat = 1;
+ }
+ else
+ {
+ repeat += 1;
+ }
+ }
+
+ if (repeat == 128)
+ {
+ putc (255, fp);
+ fwrite (from, bytes, 1, fp);
+ from = buf + bytes;
+ direct = 0;
+ repeat = 0;
+ }
+ else if (direct == 128)
+ {
+ putc (127, fp);
+ fwrite (from, bytes, direct, fp);
+ from = buf+ bytes;
+ direct = 0;
+ repeat = 0;
+ }
+
+ buf += 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 *buf,
+ 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)
+ {
+ buf[k] = sample[k];
+ }
+
+ repeat--;
+ }
+ else /* direct > 0 */
+ {
+ if (fread (buf, info->bytes, 1, fp) < 1)
+ return EOF;
+
+ direct--;
+ }
+
+ buf += info->bytes;
+ }
+
+ return 0;
+}
+
+static void
+flip_line (guchar *buf,
+ tga_info *info)
+{
+ guchar temp;
+ guchar *alt;
+ gint x, s;
+
+ alt = buf + (info->bytes * (info->width - 1));
+
+ for (x = 0; x * 2 < info->width; x++)
+ {
+ for (s = 0; s < info->bytes; ++s)
+ {
+ temp = buf[s];
+ buf[s] = alt[s];
+ alt[s] = temp;
+ }
+
+ buf += 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,
+ const guchar *src,
+ guint width,
+ guint bytes,
+ guint alpha)
+{
+ guint 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);
+
+ if (alpha)
+ {
+ dest[3] = (src[1] & 0x80) ? 255 : 0;
+ dest += 4;
+ }
+ else
+ {
+ dest += 3;
+ }
+
+ src += bytes;
+ }
+}
+
+static void
+bgr2rgb (guchar *dest,
+ const 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
+apply_colormap (guchar *dest,
+ const guchar *src,
+ guint width,
+ const guchar *cmap,
+ gboolean alpha,
+ guint16 index)
+{
+ guint x;
+
+ if (alpha)
+ {
+ for (x = 0; x < width; x++)
+ {
+ *(dest++) = cmap[(*src - index) * 4];
+ *(dest++) = cmap[(*src - index) * 4 + 1];
+ *(dest++) = cmap[(*src - index) * 4 + 2];
+ *(dest++) = cmap[(*src - index) * 4 + 3];
+
+ src++;
+ }
+ }
+ else
+ {
+ for (x = 0; x < width; x++)
+ {
+ *(dest++) = cmap[(*src - index) * 3];
+ *(dest++) = cmap[(*src - index) * 3 + 1];
+ *(dest++) = cmap[(*src - index) * 3 + 2];
+
+ src++;
+ }
+ }
+}
+
+static void
+apply_index (guchar *dest,
+ const guchar *src,
+ guint width,
+ guint16 index)
+{
+ guint x;
+
+ for (x = 0; x < width; x++)
+ {
+ *(dest++) = *(src++) - index;
+ }
+}
+
+static void
+read_line (FILE *fp,
+ guchar *row,
+ guchar *buf,
+ tga_info *info,
+ gint bpp,
+ const guchar *convert_cmap)
+{
+ if (info->imageCompression == TGA_COMP_RLE)
+ {
+ rle_read (fp, buf, info);
+ }
+ else
+ {
+ fread (buf, info->bytes, info->width, fp);
+ }
+
+ if (info->flipHoriz)
+ {
+ flip_line (buf, info);
+ }
+
+ if (info->imageType == TGA_TYPE_COLOR)
+ {
+ if (info->bpp == 16 || info->bpp == 15)
+ {
+ upsample (row, buf, info->width, info->bytes, info->alphaBits);
+ }
+ else
+ {
+ bgr2rgb (row, buf, info->width, info->bytes, info->alphaBits);
+ }
+ }
+ else if (convert_cmap)
+ {
+ gboolean has_alpha = (info->alphaBits > 0);
+
+ apply_colormap (row, buf, info->width, convert_cmap, has_alpha,
+ info->colorMapIndex);
+ }
+ else if (info->imageType == TGA_TYPE_MAPPED)
+ {
+ g_assert (bpp == 1);
+
+ apply_index (row, buf, info->width, info->colorMapIndex);
+ }
+ else
+ {
+ memcpy (row, buf, info->width * bpp);
+ }
+}
+
+static gint32
+ReadImage (FILE *fp,
+ tga_info *info,
+ const gchar *filename)
+{
+ static gint32 image_ID;
+ gint32 layer_ID;
+ GeglBuffer *buffer;
+ guchar *data, *buf, *row;
+ GimpImageType dtype = 0;
+ GimpImageBaseType itype = 0;
+ gint bpp;
+ gint i, y;
+ gint max_tileheight, tileheight;
+ guint cmap_bytes = 0;
+ guchar *tga_cmap = NULL;
+ guchar *gimp_cmap = NULL;
+ guchar *convert_cmap = NULL;
+
+ switch (info->imageType)
+ {
+ case TGA_TYPE_MAPPED:
+ cmap_bytes = (info->colorMapSize + 7 ) / 8;
+ tga_cmap = g_new (guchar, info->colorMapLength * cmap_bytes);
+
+ if (info->colorMapSize > 24)
+ {
+ /* indexed + full alpha => promoted to RGBA */
+ itype = GIMP_RGB;
+ dtype = GIMP_RGBA_IMAGE;
+ convert_cmap = g_new (guchar, info->colorMapLength * 4);
+ }
+ else if (info->colorMapIndex + info->colorMapLength > 256)
+ {
+ /* more than 256 colormap entries => promoted to RGB */
+ itype = GIMP_RGB;
+ dtype = GIMP_RGB_IMAGE;
+ convert_cmap = g_new (guchar, info->colorMapLength * 3);
+ }
+ else if (info->alphaBits > 0)
+ {
+ /* if alpha exists here, promote to RGB */
+ itype = GIMP_RGB;
+ dtype = GIMP_RGBA_IMAGE;
+ convert_cmap = g_new (guchar, info->colorMapLength * 4);
+ }
+ else
+ {
+ itype = GIMP_INDEXED;
+ dtype = GIMP_INDEXED_IMAGE;
+ gimp_cmap = g_new (guchar, info->colorMapLength * 3);
+ }
+ 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->imageType == TGA_TYPE_MAPPED)
+ {
+ if (cmap_bytes <= 4 &&
+ fread (tga_cmap, info->colorMapLength * cmap_bytes, 1, fp) == 1)
+ {
+ if (convert_cmap)
+ {
+ if (info->colorMapSize == 32)
+ bgr2rgb (convert_cmap, tga_cmap,
+ info->colorMapLength, cmap_bytes, 1);
+ else if (info->colorMapSize == 24)
+ bgr2rgb (convert_cmap, tga_cmap,
+ info->colorMapLength, cmap_bytes, 0);
+ else if (info->colorMapSize == 16 || info->colorMapSize == 15)
+ upsample (convert_cmap, tga_cmap,
+ info->colorMapLength, cmap_bytes, info->alphaBits);
+ else
+ {
+ g_message ("Unsupported colormap depth: %u",
+ info->colorMapSize);
+ return -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 ("Unsupported colormap depth: %u",
+ info->colorMapSize);
+ return -1;
+ }
+ }
+ }
+ 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 (gimp_cmap)
+ 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_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);
+
+ bpp = gimp_drawable_bpp (layer_ID);
+
+ /* Allocate the data. */
+ max_tileheight = gimp_tile_height ();
+ data = g_new (guchar, info->width * max_tileheight * bpp);
+ buf = g_new (guchar, 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 * bpp * (tileheight - y));
+ read_line (fp, row, buf, info, bpp, convert_cmap);
+ }
+
+ gegl_buffer_set (buffer,
+ GEGL_RECTANGLE (0, info->height - i - tileheight,
+ info->width, tileheight), 0,
+ NULL, data, GEGL_AUTO_ROWSTRIDE);
+
+ gimp_progress_update ((gdouble) (i + tileheight) /
+ (gdouble) info->height);
+ }
+ }
+ 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 * bpp * y);
+ read_line (fp, row, buf, info, bpp, convert_cmap);
+ }
+
+ gegl_buffer_set (buffer,
+ GEGL_RECTANGLE (0, i, info->width, tileheight), 0,
+ NULL, data, GEGL_AUTO_ROWSTRIDE);
+
+ gimp_progress_update ((gdouble) (i + tileheight) /
+ (gdouble) info->height);
+ }
+ }
+
+ g_free (data);
+ g_free (buf);
+
+ g_free (convert_cmap);
+ g_free (gimp_cmap);
+ g_free (tga_cmap);
+
+ g_object_unref (buffer);
+
+ gimp_progress_update (1.0);
+
+ return image_ID;
+}
+
+
+static gboolean
+save_image (const gchar *filename,
+ gint32 image_ID,
+ gint32 drawable_ID,
+ GError **error)
+{
+ GeglBuffer *buffer;
+ const Babl *format = NULL;
+ GimpImageType dtype;
+ gint width;
+ gint height;
+ FILE *fp;
+ 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;
+
+ buffer = gimp_drawable_get_buffer (drawable_ID);
+
+ dtype = gimp_drawable_type (drawable_ID);
+
+ width = gegl_buffer_get_width (buffer);
+ height = gegl_buffer_get_height (buffer);
+
+ gimp_progress_init_printf (_("Exporting '%s'"),
+ gimp_filename_to_utf8 (filename));
+
+ if ((fp = g_fopen (filename, "wb")) == NULL)
+ {
+ 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;
+ }
+
+ header[0] = 0; /* No image identifier / description */
+
+ if (dtype == GIMP_INDEXED_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 if (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 + 1) % 256;
+ header[6] = (num_colors + 1) / 256;
+ header[7] = 32; /* 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] = tsvals.origin ? 0 : (height % 256); /* yorigin */
+ header[11] = tsvals.origin ? 0 : (height / 256); /* 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_INDEXEDA_IMAGE:
+ format = NULL;
+ out_bpp = 1;
+ header[16] = 8; /* bpp */
+ header[17] = tsvals.origin ? 0 : 0x20; /* alpha + orientation */
+ break;
+
+ case GIMP_GRAY_IMAGE:
+ format = babl_format ("Y' u8");
+ out_bpp = 1;
+ header[16] = 8; /* bpp */
+ header[17] = tsvals.origin ? 0 : 0x20; /* alpha + orientation */
+ break;
+
+ case GIMP_GRAYA_IMAGE:
+ format = babl_format ("Y'A u8");
+ out_bpp = 2;
+ header[16] = 16; /* bpp */
+ header[17] = tsvals.origin ? 8 : 0x28; /* alpha + orientation */
+ break;
+
+ case GIMP_RGB_IMAGE:
+ format = babl_format ("R'G'B' u8");
+ out_bpp = 3;
+ header[16] = 24; /* bpp */
+ header[17] = tsvals.origin ? 0 : 0x20; /* alpha + orientation */
+ break;
+
+ case GIMP_RGBA_IMAGE:
+ format = babl_format ("R'G'B'A u8");
+ 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)
+ {
+ /* 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);
+ }
+ }
+ else if (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);
+ fputc (255, fp);
+ }
+
+ fputc (0, fp);
+ fputc (0, fp);
+ fputc (0, fp);
+ fputc (0, fp);
+ }
+
+ if (dtype == GIMP_INDEXEDA_IMAGE)
+ pixels = g_new (guchar, width * 2);
+ else
+ pixels = g_new (guchar, width * out_bpp);
+ data = g_new (guchar, width * out_bpp);
+
+ for (row = 0; row < height; ++row)
+ {
+ if (tsvals.origin)
+ {
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (0, height - (row + 1), width, 1), 1.0,
+ format, pixels,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ }
+ else
+ {
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (0, row, width, 1), 1.0,
+ format, pixels,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ }
+
+ if (dtype == GIMP_RGB_IMAGE)
+ {
+ bgr2rgb (data, pixels, width, out_bpp, 0);
+ }
+ else if (dtype == GIMP_RGBA_IMAGE)
+ {
+ bgr2rgb (data, pixels, width, out_bpp, 1);
+ }
+ else if (dtype == GIMP_INDEXEDA_IMAGE)
+ {
+ for (i = 0; i < width; ++i)
+ {
+ if (pixels[i * 2 + 1] > 127)
+ data[i] = pixels[i * 2];
+ else
+ data[i] = num_colors;
+ }
+ }
+ else
+ {
+ memcpy (data, pixels, width * out_bpp);
+ }
+
+ if (tsvals.rle)
+ {
+ rle_write (fp, data, width, out_bpp);
+ }
+ else
+ {
+ fwrite (data, width * out_bpp, 1, fp);
+ }
+
+ if (row % 16 == 0)
+ gimp_progress_update ((gdouble) row / (gdouble) height);
+ }
+
+ g_object_unref (buffer);
+
+ g_free (data);
+ g_free (pixels);
+
+ /* 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);
+
+ gimp_progress_update (1.0);
+
+ return status;
+}
+
+static gboolean
+save_dialog (void)
+{
+ GtkWidget *dialog;
+ GtkWidget *label;
+ GtkWidget *toggle;
+ GtkWidget *combo;
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ gboolean run;
+
+ dialog = gimp_export_dialog_new (_("TGA"), PLUG_IN_BINARY, SAVE_PROC);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
+ gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
+ 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 */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new_with_mnemonic (_("Or_igin:"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ combo = gimp_int_combo_box_new (_("Bottom left"), ORIGIN_BOTTOM_LEFT,
+ _("Top left"), ORIGIN_TOP_LEFT,
+ NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0);
+ gtk_widget_show (combo);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
+
+ gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo),
+ tsvals.origin,
+ G_CALLBACK (gimp_int_combo_box_get_active),
+ &tsvals.origin);
+
+ gtk_widget_show (dialog);
+
+ run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
+
+ gtk_widget_destroy (dialog);
+
+ return run;
+}