summaryrefslogtreecommitdiffstats
path: root/plug-ins/common/file-pcx.c
diff options
context:
space:
mode:
Diffstat (limited to 'plug-ins/common/file-pcx.c')
-rw-r--r--plug-ins/common/file-pcx.c1070
1 files changed, 1070 insertions, 0 deletions
diff --git a/plug-ins/common/file-pcx.c b/plug-ins/common/file-pcx.c
new file mode 100644
index 0000000..9d5d9d9
--- /dev/null
+++ b/plug-ins/common/file-pcx.c
@@ -0,0 +1,1070 @@
+/*
+ * pcx.c GIMP plug-in for loading & exporting PCX files
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* This code is based in parts on code by Francisco Bustamante, but the
+ largest portion of the code has been rewritten and is now maintained
+ occasionally by Nick Lamb njl195@zepler.org.uk */
+
+#include "config.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+
+#define LOAD_PROC "file-pcx-load"
+#define SAVE_PROC "file-pcx-save"
+#define PLUG_IN_BINARY "file-pcx"
+#define PLUG_IN_ROLE "gimp-file-pcx"
+
+
+/* Declare 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 void load_1 (FILE *fp,
+ gint width,
+ gint height,
+ guchar *buf,
+ guint16 bytes);
+static void load_4 (FILE *fp,
+ gint width,
+ gint height,
+ guchar *buf,
+ guint16 bytes);
+static void load_sub_8 (FILE *fp,
+ gint width,
+ gint height,
+ gint bpp,
+ gint plane,
+ guchar *buf,
+ guint16 bytes);
+static void load_8 (FILE *fp,
+ gint width,
+ gint height,
+ guchar *buf,
+ guint16 bytes);
+static void load_24 (FILE *fp,
+ gint width,
+ gint height,
+ guchar *buf,
+ guint16 bytes);
+static void readline (FILE *fp,
+ guchar *buf,
+ gint bytes);
+
+static gint save_image (const gchar *filename,
+ gint32 image,
+ gint32 layer,
+ GError **error);
+static void save_less_than_8 (FILE *fp,
+ gint width,
+ gint height,
+ const gint bpp,
+ const guchar *buf,
+ gboolean padding);
+static void save_8 (FILE *fp,
+ gint width,
+ gint height,
+ const guchar *buf,
+ gboolean padding);
+static void save_24 (FILE *fp,
+ gint width,
+ gint height,
+ const guchar *buf,
+ gboolean padding);
+static void writeline (FILE *fp,
+ const guchar *buf,
+ gint bytes);
+
+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 entered" }
+ };
+
+ gimp_install_procedure (LOAD_PROC,
+ "Loads files in Zsoft PCX file format",
+ "FIXME: write help for pcx_load",
+ "Francisco Bustamante & Nick Lamb",
+ "Nick Lamb <njl195@zepler.org.uk>",
+ "January 1997",
+ N_("ZSoft PCX 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-pcx");
+ gimp_register_magic_load_handler (LOAD_PROC,
+ "pcx,pcc",
+ "",
+ "0&,byte,10,2&,byte,1,3&,byte,>0,3,byte,<9");
+
+ gimp_install_procedure (SAVE_PROC,
+ "Exports files in ZSoft PCX file format",
+ "FIXME: write help for pcx_save",
+ "Francisco Bustamante & Nick Lamb",
+ "Nick Lamb <njl195@zepler.org.uk>",
+ "January 1997",
+ N_("ZSoft PCX image"),
+ "INDEXED, RGB, GRAY",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (save_args), 0,
+ save_args, NULL);
+
+ gimp_register_file_handler_mime (SAVE_PROC, "image/x-pcx");
+ gimp_register_save_handler (SAVE_PROC, "pcx,pcc", "");
+}
+
+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;
+
+ 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)
+ {
+ image_ID = load_image (param[1].data.d_string, &error);
+
+ if (image_ID != -1)
+ {
+ *nreturn_vals = 2;
+ values[1].type = GIMP_PDB_IMAGE;
+ values[1].data.d_image = image_ID;
+ }
+ else
+ {
+ status = GIMP_PDB_EXECUTION_ERROR;
+ }
+ }
+ else if (strcmp (name, SAVE_PROC) == 0)
+ {
+ image_ID = param[1].data.d_int32;
+ drawable_ID = param[2].data.d_int32;
+
+ /* eventually export the image */
+ switch (run_mode)
+ {
+ case GIMP_RUN_INTERACTIVE:
+ case GIMP_RUN_WITH_LAST_VALS:
+ gimp_ui_init (PLUG_IN_BINARY, FALSE);
+
+ export = gimp_export_image (&image_ID, &drawable_ID, "PCX",
+ GIMP_EXPORT_CAN_HANDLE_RGB |
+ GIMP_EXPORT_CAN_HANDLE_GRAY |
+ GIMP_EXPORT_CAN_HANDLE_INDEXED);
+
+ if (export == GIMP_EXPORT_CANCEL)
+ {
+ values[0].data.d_status = GIMP_PDB_CANCEL;
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ switch (run_mode)
+ {
+ case GIMP_RUN_INTERACTIVE:
+ break;
+
+ case GIMP_RUN_NONINTERACTIVE:
+ if (nparams != 5)
+ status = GIMP_PDB_CALLING_ERROR;
+ break;
+
+ case GIMP_RUN_WITH_LAST_VALS:
+ break;
+
+ default:
+ break;
+ }
+
+ if (status == GIMP_PDB_SUCCESS)
+ {
+ if (! save_image (param[3].data.d_string, image_ID, drawable_ID,
+ &error))
+ {
+ status = GIMP_PDB_EXECUTION_ERROR;
+ }
+ }
+
+ if (export == GIMP_EXPORT_EXPORT)
+ gimp_image_delete (image_ID);
+ }
+ else
+ {
+ status = GIMP_PDB_CALLING_ERROR;
+ }
+
+ if (status != GIMP_PDB_SUCCESS && error)
+ {
+ *nreturn_vals = 2;
+ values[1].type = GIMP_PDB_STRING;
+ values[1].data.d_string = error->message;
+ }
+
+ values[0].data.d_status = status;
+}
+
+static struct
+{
+ guint8 manufacturer;
+ guint8 version;
+ guint8 compression;
+ guint8 bpp;
+ guint16 x1, y1;
+ guint16 x2, y2;
+ guint16 hdpi;
+ guint16 vdpi;
+ guint8 colormap[48];
+ guint8 reserved;
+ guint8 planes;
+ guint16 bytesperline;
+ guint16 color;
+ guint8 filler[58];
+} pcx_header;
+
+static struct {
+ size_t size;
+ gpointer address;
+} const pcx_header_buf_xlate[] = {
+ { 1, &pcx_header.manufacturer },
+ { 1, &pcx_header.version },
+ { 1, &pcx_header.compression },
+ { 1, &pcx_header.bpp },
+ { 2, &pcx_header.x1 },
+ { 2, &pcx_header.y1 },
+ { 2, &pcx_header.x2 },
+ { 2, &pcx_header.y2 },
+ { 2, &pcx_header.hdpi },
+ { 2, &pcx_header.vdpi },
+ { 48, &pcx_header.colormap },
+ { 1, &pcx_header.reserved },
+ { 1, &pcx_header.planes },
+ { 2, &pcx_header.bytesperline },
+ { 2, &pcx_header.color },
+ { 58, &pcx_header.filler },
+ { 0, NULL }
+};
+
+static void
+pcx_header_from_buffer (guint8 *buf)
+{
+ gint i;
+ gint buf_offset = 0;
+
+ for (i = 0; pcx_header_buf_xlate[i].size != 0; i++)
+ {
+ memmove (pcx_header_buf_xlate[i].address, buf + buf_offset,
+ pcx_header_buf_xlate[i].size);
+ buf_offset += pcx_header_buf_xlate[i].size;
+ }
+}
+
+static void
+pcx_header_to_buffer (guint8 *buf)
+{
+ gint i;
+ gint buf_offset = 0;
+
+ for (i = 0; pcx_header_buf_xlate[i].size != 0; i++)
+ {
+ memmove (buf + buf_offset, pcx_header_buf_xlate[i].address,
+ pcx_header_buf_xlate[i].size);
+ buf_offset += pcx_header_buf_xlate[i].size;
+ }
+}
+
+static gint32
+load_image (const gchar *filename,
+ GError **error)
+{
+ FILE *fd;
+ GeglBuffer *buffer;
+ guint16 offset_x, offset_y, bytesperline;
+ gint32 width, height;
+ guint16 resolution_x, resolution_y;
+ gint32 image, layer;
+ guchar *dest, cmap[768];
+ guint8 header_buf[128];
+
+ gimp_progress_init_printf (_("Opening '%s'"),
+ gimp_filename_to_utf8 (filename));
+
+ fd = g_fopen (filename, "rb");
+
+ if (! fd)
+ {
+ 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;
+ }
+
+ if (fread (header_buf, 128, 1, fd) == 0)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Could not read header from '%s'"),
+ gimp_filename_to_utf8 (filename));
+ fclose (fd);
+ return -1;
+ }
+
+ pcx_header_from_buffer (header_buf);
+
+ if (pcx_header.manufacturer != 10)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("'%s' is not a PCX file"),
+ gimp_filename_to_utf8 (filename));
+ fclose (fd);
+ return -1;
+ }
+
+ offset_x = GUINT16_FROM_LE (pcx_header.x1);
+ offset_y = GUINT16_FROM_LE (pcx_header.y1);
+ width = GUINT16_FROM_LE (pcx_header.x2) - offset_x + 1;
+ height = GUINT16_FROM_LE (pcx_header.y2) - offset_y + 1;
+ bytesperline = GUINT16_FROM_LE (pcx_header.bytesperline);
+ resolution_x = GUINT16_FROM_LE (pcx_header.hdpi);
+ resolution_y = GUINT16_FROM_LE (pcx_header.vdpi);
+
+ if ((width <= 0) || (width > GIMP_MAX_IMAGE_SIZE))
+ {
+ g_message (_("Unsupported or invalid image width: %d"), width);
+ fclose (fd);
+ return -1;
+ }
+ if ((height <= 0) || (height > GIMP_MAX_IMAGE_SIZE))
+ {
+ g_message (_("Unsupported or invalid image height: %d"), height);
+ fclose (fd);
+ return -1;
+ }
+ if (bytesperline < ((width * pcx_header.bpp + 7) / 8))
+ {
+ g_message (_("Invalid number of bytes per line in PCX header"));
+ fclose (fd);
+ return -1;
+ }
+ if ((resolution_x < 1) || (resolution_x > GIMP_MAX_RESOLUTION) ||
+ (resolution_y < 1) || (resolution_y > GIMP_MAX_RESOLUTION))
+ {
+ g_message (_("Resolution out of bounds in XCX header, using 72x72"));
+ resolution_x = 72;
+ resolution_y = 72;
+ }
+
+ /* Shield against potential buffer overflows in load_*() functions. */
+ if (G_MAXSIZE / width / height < 3)
+ {
+ g_message (_("Image dimensions too large: width %d x height %d"), width, height);
+ fclose (fd);
+ return -1;
+ }
+
+ if (pcx_header.planes == 3 && pcx_header.bpp == 8)
+ {
+ image= gimp_image_new (width, height, GIMP_RGB);
+ layer= gimp_layer_new (image, _("Background"), width, height,
+ GIMP_RGB_IMAGE,
+ 100,
+ gimp_image_get_default_new_layer_mode (image));
+ }
+ else
+ {
+ image= gimp_image_new (width, height, GIMP_INDEXED);
+ layer= gimp_layer_new (image, _("Background"), width, height,
+ GIMP_INDEXED_IMAGE,
+ 100,
+ gimp_image_get_default_new_layer_mode (image));
+ }
+
+ gimp_image_set_filename (image, filename);
+ gimp_image_set_resolution (image, resolution_x, resolution_y);
+
+ gimp_image_insert_layer (image, layer, -1, 0);
+ gimp_layer_set_offsets (layer, offset_x, offset_y);
+
+ buffer = gimp_drawable_get_buffer (layer);
+
+ if (pcx_header.planes == 1 && pcx_header.bpp == 1)
+ {
+ const guint8 *colormap = pcx_header.colormap;
+ dest = g_new (guchar, ((gsize) width) * height);
+ load_1 (fd, width, height, dest, bytesperline);
+ /* Monochrome does not mean necessarily B&W. Therefore we still
+ * want to check the header palette, even for just 2 colors.
+ * Hopefully the header palette will always be filled with
+ * meaningful colors and the creator software did not just assume
+ * B&W by being monochrome.
+ * Until now test samples showed that even when B&W the header
+ * palette was correctly filled with these 2 colors and we didn't
+ * find counter-examples.
+ * See bug 159947, comment 21 and 23.
+ */
+ /* ... Actually, there *are* files out there with a zeroed 1-bit palette,
+ * which are supposed to be displayed as B&W (see issue #2997.) These
+ * files *might* be in the wrong (who knows...) but the fact is that
+ * other software, including older versions of GIMP, do display them
+ * "correctly", so let's follow suit: if the two palette colors are
+ * equal, use a B&W palette instead.
+ */
+ if (! memcmp (colormap, colormap + 3, 3))
+ {
+ static const guint8 bw_colormap[6] = { 0, 0, 0,
+ 255, 255, 255};
+ colormap = bw_colormap;
+ }
+ gimp_image_set_colormap (image, colormap, 2);
+ }
+ else if (pcx_header.bpp == 1 && pcx_header.planes == 2)
+ {
+ dest = g_new (guchar, ((gsize) width) * height);
+ load_sub_8 (fd, width, height, 1, 2, dest, bytesperline);
+ gimp_image_set_colormap (image, pcx_header.colormap, 4);
+ }
+ else if (pcx_header.bpp == 2 && pcx_header.planes == 1)
+ {
+ dest = g_new (guchar, ((gsize) width) * height);
+ load_sub_8 (fd, width, height, 2, 1, dest, bytesperline);
+ gimp_image_set_colormap (image, pcx_header.colormap, 4);
+ }
+ else if (pcx_header.bpp == 1 && pcx_header.planes == 3)
+ {
+ dest = g_new (guchar, ((gsize) width) * height);
+ load_sub_8 (fd, width, height, 1, 3, dest, bytesperline);
+ gimp_image_set_colormap (image, pcx_header.colormap, 8);
+ }
+ else if (pcx_header.bpp == 1 && pcx_header.planes == 4)
+ {
+ dest = g_new (guchar, ((gsize) width) * height);
+ load_4 (fd, width, height, dest, bytesperline);
+ gimp_image_set_colormap (image, pcx_header.colormap, 16);
+ }
+ else if (pcx_header.bpp == 4 && pcx_header.planes == 1)
+ {
+ dest = g_new (guchar, ((gsize) width) * height);
+ load_sub_8 (fd, width, height, 4, 1, dest, bytesperline);
+ gimp_image_set_colormap (image, pcx_header.colormap, 16);
+ }
+ else if (pcx_header.bpp == 8 && pcx_header.planes == 1)
+ {
+ dest = g_new (guchar, ((gsize) width) * height);
+ load_8 (fd, width, height, dest, bytesperline);
+ fseek (fd, -768L, SEEK_END);
+ fread (cmap, 768, 1, fd);
+ gimp_image_set_colormap (image, cmap, 256);
+ }
+ else if (pcx_header.bpp == 8 && pcx_header.planes == 3)
+ {
+ dest = g_new (guchar, ((gsize) width) * height * 3);
+ load_24 (fd, width, height, dest, bytesperline);
+ }
+ else
+ {
+ g_message (_("Unusual PCX flavour, giving up"));
+ fclose (fd);
+ return -1;
+ }
+
+ gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0,
+ NULL, dest, GEGL_AUTO_ROWSTRIDE);
+
+ fclose (fd);
+ g_free (dest);
+ g_object_unref (buffer);
+
+ gimp_progress_update (1.0);
+
+ return image;
+}
+
+static void
+load_8 (FILE *fp,
+ gint width,
+ gint height,
+ guchar *buf,
+ guint16 bytes)
+{
+ gint row;
+ guchar *line = g_new (guchar, bytes);
+
+ for (row = 0; row < height; buf += width, ++row)
+ {
+ readline (fp, line, bytes);
+ memcpy (buf, line, width);
+ gimp_progress_update ((double) row / (double) height);
+ }
+
+ g_free (line);
+}
+
+static void
+load_24 (FILE *fp,
+ gint width,
+ gint height,
+ guchar *buf,
+ guint16 bytes)
+{
+ gint x, y, c;
+ guchar *line = g_new (guchar, bytes);
+
+ for (y = 0; y < height; buf += width * 3, ++y)
+ {
+ for (c = 0; c < 3; ++c)
+ {
+ readline (fp, line, bytes);
+ for (x = 0; x < width; ++x)
+ {
+ buf[x * 3 + c] = line[x];
+ }
+ }
+ gimp_progress_update ((double) y / (double) height);
+ }
+
+ g_free (line);
+}
+
+static void
+load_1 (FILE *fp,
+ gint width,
+ gint height,
+ guchar *buf,
+ guint16 bytes)
+{
+ gint x, y;
+ guchar *line = g_new (guchar, bytes);
+
+ for (y = 0; y < height; buf += width, ++y)
+ {
+ readline (fp, line, bytes);
+ for (x = 0; x < width; ++x)
+ {
+ if (line[x / 8] & (128 >> (x % 8)))
+ buf[x] = 1;
+ else
+ buf[x] = 0;
+ }
+ gimp_progress_update ((double) y / (double) height);
+ }
+
+ g_free (line);
+}
+
+static void
+load_4 (FILE *fp,
+ gint width,
+ gint height,
+ guchar *buf,
+ guint16 bytes)
+{
+ gint x, y, c;
+ guchar *line = g_new (guchar, bytes);
+
+ for (y = 0; y < height; buf += width, ++y)
+ {
+ for (x = 0; x < width; ++x)
+ buf[x] = 0;
+ for (c = 0; c < 4; ++c)
+ {
+ readline(fp, line, bytes);
+ for (x = 0; x < width; ++x)
+ {
+ if (line[x / 8] & (128 >> (x % 8)))
+ buf[x] += (1 << c);
+ }
+ }
+ gimp_progress_update ((double) y / (double) height);
+ }
+
+ g_free (line);
+}
+
+static void
+load_sub_8 (FILE *fp,
+ gint width,
+ gint height,
+ gint bpp,
+ gint plane,
+ guchar *buf,
+ guint16 bytes)
+{
+ gint x, y, c, b;
+ guchar *line = g_new (guchar, bytes);
+ gint real_bpp = bpp - 1;
+ gint current_bit = 0;
+
+ for (y = 0; y < height; buf += width, ++y)
+ {
+ for (x = 0; x < width; ++x)
+ buf[x] = 0;
+ for (c = 0; c < plane; ++c)
+ {
+ readline (fp, line, bytes);
+ for (x = 0; x < width; ++x)
+ {
+ for (b = 0; b < bpp; b++)
+ {
+ current_bit = bpp * x + b;
+ if (line[current_bit / 8] & (128 >> (current_bit % 8)))
+ buf[x] += (1 << (real_bpp - b + c));
+ }
+ }
+ }
+ gimp_progress_update ((double) y / (double) height);
+ }
+
+ g_free (line);
+}
+
+static void
+readline (FILE *fp,
+ guchar *buf,
+ gint bytes)
+{
+ static guchar count = 0, value = 0;
+
+ if (pcx_header.compression)
+ {
+ while (bytes--)
+ {
+ if (count == 0)
+ {
+ value = fgetc (fp);
+ if (value < 0xc0)
+ {
+ count = 1;
+ }
+ else
+ {
+ count = value - 0xc0;
+ value = fgetc (fp);
+ }
+ }
+ count--;
+ *(buf++) = value;
+ }
+ }
+ else
+ {
+ fread (buf, bytes, 1, fp);
+ }
+}
+
+static gint
+save_image (const gchar *filename,
+ gint32 image,
+ gint32 layer,
+ GError **error)
+{
+ FILE *fp;
+ GeglBuffer *buffer;
+ const Babl *format;
+ GimpImageType drawable_type;
+ guchar *cmap= NULL;
+ guchar *pixels;
+ gint offset_x, offset_y;
+ guint width, height;
+ gdouble resolution_x, resolution_y;
+ gint colors, i;
+ guint8 header_buf[128];
+ gboolean padding = FALSE;
+
+ drawable_type = gimp_drawable_type (layer);
+ gimp_drawable_offsets (layer, &offset_x, &offset_y);
+
+ buffer = gimp_drawable_get_buffer (layer);
+
+ width = gegl_buffer_get_width (buffer);
+ height = gegl_buffer_get_height (buffer);
+
+ gimp_progress_init_printf (_("Exporting '%s'"),
+ gimp_filename_to_utf8 (filename));
+
+ pcx_header.manufacturer = 0x0a;
+ pcx_header.version = 5;
+ pcx_header.compression = 1;
+
+ switch (drawable_type)
+ {
+ case GIMP_INDEXED_IMAGE:
+ cmap = gimp_image_get_colormap (image, &colors);
+ if (colors > 16)
+ {
+ pcx_header.bpp = 8;
+ pcx_header.planes = 1;
+ pcx_header.bytesperline = width;
+ }
+ else if (colors > 2)
+ {
+ pcx_header.bpp = 4;
+ pcx_header.planes = 1;
+ pcx_header.bytesperline = (width + 1) / 2;
+ }
+ else
+ {
+ pcx_header.bpp = 1;
+ pcx_header.planes = 1;
+ pcx_header.bytesperline = (width + 7) / 8;
+ }
+ pcx_header.color = GUINT16_TO_LE (1);
+ format = NULL;
+
+ /* Some references explain that 2bpp/1plane and 4bpp/1plane files
+ * would use the palette at EOF (not the one from the header) if
+ * we are in version 5 of PCX. Other sources affirm that even in
+ * version 5, EOF palette must be used only when there are more
+ * than 16 colors. We go with this second assumption.
+ * See bug 159947, comment 21 and 23.
+ */
+ if (colors <= 16)
+ {
+ for (i = 0; i < (colors * 3); i++)
+ {
+ pcx_header.colormap[i] = cmap[i];
+ }
+ }
+
+ break;
+
+ case GIMP_RGB_IMAGE:
+ pcx_header.bpp = 8;
+ pcx_header.planes = 3;
+ pcx_header.color = GUINT16_TO_LE (1);
+ pcx_header.bytesperline = width;
+ format = babl_format ("R'G'B' u8");
+ break;
+
+ case GIMP_GRAY_IMAGE:
+ pcx_header.bpp = 8;
+ pcx_header.planes = 1;
+ pcx_header.color = GUINT16_TO_LE (2);
+ pcx_header.bytesperline = width;
+ format = babl_format ("Y' u8");
+ break;
+
+ default:
+ g_message (_("Cannot export images with alpha channel."));
+ return FALSE;
+ }
+
+ /* Bytes per Line must be an even number, according to spec */
+ if (pcx_header.bytesperline % 2 != 0)
+ {
+ pcx_header.bytesperline++;
+ padding = TRUE;
+ }
+ pcx_header.bytesperline = GUINT16_TO_LE (pcx_header.bytesperline);
+
+ pixels = (guchar *) g_malloc (width * height * pcx_header.planes);
+
+ gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, width, height), 1.0,
+ format, pixels,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ if ((offset_x < 0) || (offset_x > (1<<16)))
+ {
+ g_message (_("Invalid X offset: %d"), offset_x);
+ return FALSE;
+ }
+
+ if ((offset_y < 0) || (offset_y > (1<<16)))
+ {
+ g_message (_("Invalid Y offset: %d"), offset_y);
+ return FALSE;
+ }
+
+ if (offset_x + width - 1 > (1<<16))
+ {
+ g_message (_("Right border out of bounds (must be < %d): %d"), (1<<16),
+ offset_x + width - 1);
+ return FALSE;
+ }
+
+ if (offset_y + height - 1 > (1<<16))
+ {
+ g_message (_("Bottom border out of bounds (must be < %d): %d"), (1<<16),
+ offset_y + height - 1);
+ return FALSE;
+ }
+
+ 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;
+ }
+
+ pcx_header.x1 = GUINT16_TO_LE ((guint16)offset_x);
+ pcx_header.y1 = GUINT16_TO_LE ((guint16)offset_y);
+ pcx_header.x2 = GUINT16_TO_LE ((guint16)(offset_x + width - 1));
+ pcx_header.y2 = GUINT16_TO_LE ((guint16)(offset_y + height - 1));
+
+ gimp_image_get_resolution (image, &resolution_x, &resolution_y);
+
+ pcx_header.hdpi = GUINT16_TO_LE (RINT (MAX (resolution_x, 1.0)));
+ pcx_header.vdpi = GUINT16_TO_LE (RINT (MAX (resolution_y, 1.0)));
+ pcx_header.reserved = 0;
+
+ pcx_header_to_buffer (header_buf);
+
+ fwrite (header_buf, 128, 1, fp);
+
+ switch (drawable_type)
+ {
+ case GIMP_INDEXED_IMAGE:
+ if (colors > 16)
+ {
+ save_8 (fp, width, height, pixels, padding);
+ fputc (0x0c, fp);
+ fwrite (cmap, colors, 3, fp);
+ for (i = colors; i < 256; i++)
+ {
+ fputc (0, fp);
+ fputc (0, fp);
+ fputc (0, fp);
+ }
+ }
+ else /* Covers 1 and 4 bpp */
+ {
+ save_less_than_8 (fp, width, height, pcx_header.bpp, pixels, padding);
+ }
+ break;
+
+ case GIMP_RGB_IMAGE:
+ save_24 (fp, width, height, pixels, padding);
+ break;
+
+ case GIMP_GRAY_IMAGE:
+ save_8 (fp, width, height, pixels, padding);
+ fputc (0x0c, fp);
+ for (i = 0; i < 256; i++)
+ {
+ fputc ((guchar) i, fp);
+ fputc ((guchar) i, fp);
+ fputc ((guchar) i, fp);
+ }
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ g_object_unref (buffer);
+ g_free (pixels);
+
+ if (fclose (fp) != 0)
+ {
+ g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+ _("Writing to file '%s' failed: %s"),
+ gimp_filename_to_utf8 (filename), g_strerror (errno));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+save_less_than_8 (FILE *fp,
+ gint width,
+ gint height,
+ const gint bpp,
+ const guchar *buf,
+ gboolean padding)
+{
+ const gint bit_limit = (8 - bpp);
+ const gint buf_size = width * height;
+ const gint line_end = width - 1;
+ gint j = bit_limit;
+ gint count = 0;
+ guchar byte_to_write = 0x00;
+ guchar *line;
+ gint x;
+
+ line = (guchar *) g_malloc (((width + 7) / 8) * bpp);
+
+ for (x = 0; x < buf_size; x++)
+ {
+ byte_to_write |= (buf[x] << j);
+ j -= bpp;
+
+ if (j < 0 || (x % width == line_end))
+ {
+ line[count] = byte_to_write;
+ count++;
+ byte_to_write = 0x00;
+ j = bit_limit;
+
+ if ((x % width == line_end))
+ {
+ writeline (fp, line, count);
+ count = 0;
+ if (padding)
+ fputc ('\0', fp);
+ gimp_progress_update ((double) x / (double) buf_size);
+ }
+ }
+ }
+ g_free (line);
+}
+
+static void
+save_8 (FILE *fp,
+ gint width,
+ gint height,
+ const guchar *buf,
+ gboolean padding)
+{
+ int row;
+
+ for (row = 0; row < height; ++row)
+ {
+ writeline (fp, buf, width);
+ buf += width;
+ if (padding)
+ fputc ('\0', fp);
+ gimp_progress_update ((double) row / (double) height);
+ }
+}
+
+static void
+save_24 (FILE *fp,
+ gint width,
+ gint height,
+ const guchar *buf,
+ gboolean padding)
+{
+ int x, y, c;
+ guchar *line;
+
+ line = (guchar *) g_malloc (width);
+
+ for (y = 0; y < height; ++y)
+ {
+ for (c = 0; c < 3; ++c)
+ {
+ for (x = 0; x < width; ++x)
+ {
+ line[x] = buf[(3*x) + c];
+ }
+ writeline (fp, line, width);
+ if (padding)
+ fputc ('\0', fp);
+ }
+ buf += width * 3;
+ gimp_progress_update ((double) y / (double) height);
+ }
+ g_free (line);
+}
+
+static void
+writeline (FILE *fp,
+ const guchar *buf,
+ gint bytes)
+{
+ const guchar *finish = buf + bytes;
+ guchar value;
+ guchar count;
+
+ while (buf < finish)
+ {
+ value = *(buf++);
+ count = 1;
+
+ while (buf < finish && count < 63 && *buf == value)
+ {
+ count++; buf++;
+ }
+
+ if (value < 0xc0 && count == 1)
+ {
+ fputc (value, fp);
+ }
+ else
+ {
+ fputc (0xc0 + count, fp);
+ fputc (value, fp);
+ }
+ }
+}