Skip to content

Instantly share code, notes, and snippets.

@saitoha
Last active May 8, 2016 07:35
Show Gist options
  • Save saitoha/c1e1f1df48054fca4ae28a397dbdc6d8 to your computer and use it in GitHub Desktop.
Save saitoha/c1e1f1df48054fca4ae28a397dbdc6d8 to your computer and use it in GitHub Desktop.
gimp: SIXEL file coder plugin
commit e7356ec11f4e7f8eb5d74ef491a9106ae8646034
Author: Hayaki Saito <saitoha@me.com>
Date: Fri May 6 02:46:15 2016 +0900
Add SIXEL file plugin
diff --git a/plug-ins/common/.gitignore b/plug-ins/common/.gitignore
index 61c986d..339e12c 100644
--- a/plug-ins/common/.gitignore
+++ b/plug-ins/common/.gitignore
@@ -104,6 +104,8 @@
/file-psp.exe
/file-raw-data
/file-raw-data.exe
+/file-sixel
+/file-sixel.exe
/file-sunras
/file-sunras.exe
/file-svg
diff --git a/plug-ins/common/Makefile.am b/plug-ins/common/Makefile.am
index 4316218..438e0fa 100644
--- a/plug-ins/common/Makefile.am
+++ b/plug-ins/common/Makefile.am
@@ -95,6 +95,7 @@ libexec_PROGRAMS = \
$(FILE_PS) \
file-psp \
file-raw-data \
+ file-sixel \
file-sunras \
$(FILE_SVG) \
file-tga \
@@ -1066,6 +1067,23 @@ file_raw_data_LDADD = \
$(INTLLIBS) \
$(file_raw_data_RC)
+file_sixel_SOURCES = \
+ file-sixel.c
+
+file_sixel_LDADD = \
+ $(libgimpui) \
+ $(libgimpwidgets) \
+ $(libgimpmodule) \
+ $(libgimp) \
+ $(libgimpmath) \
+ $(libgimpconfig) \
+ $(libgimpcolor) \
+ $(libgimpbase) \
+ $(GTK_LIBS) \
+ $(GEGL_LIBS) \
+ $(RT_LIBS) \
+ $(INTLLIBS)
+
file_sunras_SOURCES = \
file-sunras.c
diff --git a/plug-ins/common/file-sixel.c b/plug-ins/common/file-sixel.c
new file mode 100644
index 0000000..380255a
--- /dev/null
+++ b/plug-ins/common/file-sixel.c
@@ -0,0 +1,2031 @@
+/**
+ * file-sixel.c version 1.0
+ * A plugin for load/save SIXEL images.
+ * Hayaki Saito <saitoha@me.com>
+ */
+
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/* SIXEL plugin version 1.0.0 */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <gdk/gdk.h> /* For GDK_WINDOWING_WIN32 */
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+#define LOAD_PROC "file-sixel-load"
+#define SAVE_PROC "file-sixel-save"
+#define PLUG_IN_BINARY "file-sixel"
+#define PLUG_IN_ROLE "gimp-file-sixel"
+#define SCALE_WIDTH 125
+
+/* Structs for the save dialog */
+typedef struct
+{
+ gint threshold;
+} sixelSaveVals;
+
+/* 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 gboolean load_image (const gchar *filename,
+ gint32 *pimage_ID,
+ GError **error);
+static gboolean save_image (const gchar *filename,
+ gint32 image_ID,
+ gint32 drawable_ID,
+ GError **error);
+static gboolean save_dialog (void);
+
+
+const GimpPlugInInfo PLUG_IN_INFO =
+{
+ NULL, /* init_proc */
+ NULL, /* quit_proc */
+ query, /* query_proc */
+ run, /* run_proc */
+};
+
+static sixelSaveVals sixelvals =
+{
+ 127 /* alpha threshold */
+};
+
+
+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, "threshold", "Alpha threshold (0-255)" }
+ };
+
+ gimp_install_procedure (LOAD_PROC,
+ "Load files in SIXEL format.",
+ "Load files in SIXEL format. "
+ "SIXEL is one of image formats for printer and "
+ "terminal imaging introduced by Digital Equipment Corp. (DEC)."
+ "Nowdays it's available for some terminal emulators such as XTerm.",
+ "Hayaki Saito",
+ "Hayaki Saito",
+ "2016",
+ N_("SIXEL image"),
+ NULL,
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (load_args),
+ G_N_ELEMENTS (load_return_vals),
+ load_args, load_return_vals);
+
+ gimp_register_magic_load_handler (LOAD_PROC,
+ "six,sixel",
+ "",
+ "0, string,\\033P");
+
+ gimp_install_procedure (SAVE_PROC,
+ "Export files in SIXEL format.",
+ "Export files in SIXEL format. "
+ "SIXEL is one of image formats for printer and "
+ "terminal imaging introduced by Digital Equipment Corp. (DEC)."
+ "Nowdays it's available for some terminal emulators such as XTerm.",
+ "Hayaki Saito",
+ "Hayaki Saito",
+ "1997",
+ N_("SIXEL image"),
+ "INDEXED*",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (save_args), 0,
+ save_args, NULL);
+
+ gimp_register_save_handler (SAVE_PROC, "six,sixel", "");
+}
+
+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)
+ {
+ if (! load_image (param[1].data.d_string, &image_ID, &error))
+ {
+ status = GIMP_PDB_EXECUTION_ERROR;
+ }
+ else
+ {
+ *nreturn_vals = 2;
+ values[1].type = GIMP_PDB_IMAGE;
+ values[1].data.d_image = image_ID;
+ }
+ }
+ 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, "SIXEL",
+ GIMP_EXPORT_CAN_HANDLE_INDEXED);
+
+ if (export == GIMP_EXPORT_CANCEL)
+ {
+ values[0].data.d_status = GIMP_PDB_CANCEL;
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ switch (run_mode)
+ {
+ case GIMP_RUN_INTERACTIVE:
+ /* Possibly retrieve data */
+ gimp_get_data ("file_sixel_save", &sixelvals);
+
+ /* First acquire information with a dialog */
+ if (gimp_drawable_has_alpha (drawable_ID))
+ if (! save_dialog ())
+ status = GIMP_PDB_CANCEL;
+ break;
+
+ case GIMP_RUN_NONINTERACTIVE:
+ /* Make sure all the arguments are there! */
+ if (nparams != 6)
+ {
+ status = GIMP_PDB_CALLING_ERROR;
+ }
+ else
+ {
+ sixelvals.threshold = param[5].data.d_int32;
+
+ if (sixelvals.threshold < 0 ||
+ sixelvals.threshold > 255)
+ status = GIMP_PDB_CALLING_ERROR;
+ }
+ break;
+
+ case GIMP_RUN_WITH_LAST_VALS:
+ /* Possibly retrieve data */
+ gimp_get_data ("file_sixel_save", &sixelvals);
+ break;
+
+ default:
+ break;
+ }
+
+ if (status == GIMP_PDB_SUCCESS)
+ {
+ if (save_image (param[3].data.d_string,
+ image_ID, drawable_ID, &error))
+ {
+ gimp_set_data ("file_sixel_save", &sixelvals, sizeof (sixelSaveVals));
+ }
+ 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;
+}
+
+/* convert sixel data into indexed pixel bytes and palette data */
+
+#define RGB(r, g, b) (((r) << 16) + ((g) << 8) + (b))
+
+#define PALVAL(n,a,m) (((n) * (a) + ((m) / 2)) / (m))
+
+#define XRGB(r, g, b) RGB (PALVAL (r, 255, 100), PALVAL (g, 255, 100), PALVAL (b, 255, 100))
+
+#define DECSIXEL_PARAMS_MAX 16
+#define SIXEL_PALETTE_MAX 256
+
+/* palette type */
+#define SIXEL_PALETTETYPE_AUTO 0 /* choose palette type automatically */
+#define SIXEL_PALETTETYPE_HLS 1 /* HLS colorspace */
+#define SIXEL_PALETTETYPE_RGB 2 /* RGB colorspace */
+
+/* policies of SIXEL encoding */
+#define SIXEL_ENCODEPOLICY_AUTO 0 /* choose encoding policy automatically */
+#define SIXEL_ENCODEPOLICY_FAST 1 /* encode as fast as possible */
+#define SIXEL_ENCODEPOLICY_SIZE 2 /* encode to as small sixel sequence as possible */
+
+static gint const color_table[] =
+{
+ XRGB (0, 0, 0), /* 0 Black */
+ XRGB (20, 20, 80), /* 1 Blue */
+ XRGB (80, 13, 13), /* 2 Red */
+ XRGB (20, 80, 20), /* 3 Green */
+ XRGB (80, 20, 80), /* 4 Magenta */
+ XRGB (20, 80, 80), /* 5 Cyan */
+ XRGB (80, 80, 20), /* 6 Yellow */
+ XRGB (53, 53, 53), /* 7 Gray 50% */
+ XRGB (26, 26, 26), /* 8 Gray 25% */
+ XRGB (33, 33, 60), /* 9 Blue* */
+ XRGB (60, 26, 26), /* 10 Red* */
+ XRGB (33, 60, 33), /* 11 Green* */
+ XRGB (60, 33, 60), /* 12 Magenta* */
+ XRGB (33, 60, 60), /* 13 Cyan* */
+ XRGB (60, 60, 33), /* 14 Yellow* */
+ XRGB (80, 80, 80), /* 15 Gray 75% */
+};
+
+typedef struct image_buffer
+{
+ guchar *data;
+ gint32 width;
+ gint32 height;
+ gint32 palette[SIXEL_PALETTE_MAX];
+ gint32 ncolors;
+} image_buffer_t;
+
+typedef enum parse_state
+{
+ PS_GROUND = 0,
+ PS_ESC = 1, /* ESC */
+ PS_DCS = 2, /* DCS Device Control String Introducer \033P P...P I...I F */
+ PS_DECSIXEL = 3, /* DECSIXEL body part ", $, -, ? ... ~ */
+ PS_DECGRA = 4, /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */
+ PS_DECGRI = 5, /* DECGRI Graphics Repeat Introducer ! Pn Ch */
+ PS_DECGCI = 6, /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */
+} parse_state_t;
+
+typedef struct parser_context
+{
+ parse_state_t state;
+ gint32 pos_x;
+ gint32 pos_y;
+ gint32 max_x;
+ gint32 max_y;
+ gint32 attributed_pan;
+ gint32 attributed_pad;
+ gint32 attributed_ph;
+ gint32 attributed_pv;
+ gint32 repeat_count;
+ gint32 color_index;
+ gint32 bgindex;
+ gint32 param;
+ gint32 nparams;
+ gint32 params[DECSIXEL_PARAMS_MAX];
+} parser_context_t;
+
+
+/*
+ * Primary color hues:
+ * blue: 0 degrees
+ * red: 120 degrees
+ * green: 240 degrees
+ */
+static gint32
+hls2rgb (gint32 hue, gint32 lum, gint32 sat)
+{
+ gdouble hs = (hue + 240) % 360;
+ gdouble hv = hs / 360.0;
+ gdouble lv = lum / 100.0;
+ gdouble sv = sat / 100.0;
+ gdouble c, x, m, c2;
+ gdouble r1, g1, b1;
+ gint r, g, b;
+ gint hpi;
+
+ if (sat == 0)
+ {
+ r = g = b = lum * 255 / 100;
+ return RGB (r, g, b);
+ }
+
+ if ((c2 = ((2.0 * lv) - 1.0)) < 0.0)
+ c2 = -c2;
+ c = (1.0 - c2) * sv;
+ hpi = (gint) (hv * 6.0);
+ x = (hpi & 1) ? c : 0.0;
+ m = lv - 0.5 * c;
+
+ switch (hpi)
+ {
+ case 0:
+ r1 = c;
+ g1 = x;
+ b1 = 0.0;
+ break;
+ case 1:
+ r1 = x;
+ g1 = c;
+ b1 = 0.0;
+ break;
+ case 2:
+ r1 = 0.0;
+ g1 = c;
+ b1 = x;
+ break;
+ case 3:
+ r1 = 0.0;
+ g1 = x;
+ b1 = c;
+ break;
+ case 4:
+ r1 = x;
+ g1 = 0.0;
+ b1 = c;
+ break;
+ case 5:
+ r1 = c;
+ g1 = 0.0;
+ b1 = x;
+ break;
+ default:
+ return RGB (255, 255, 255);
+ }
+
+ r = (gint) ((r1 + m) * 100.0 + 0.5);
+ g = (gint) ((g1 + m) * 100.0 + 0.5);
+ b = (gint) ((b1 + m) * 100.0 + 0.5);
+
+ if (r < 0)
+ r = 0;
+ else if (r > 100)
+ r = 100;
+ if (g < 0)
+ g = 0;
+ else if (g > 100)
+ g = 100;
+ if (b < 0)
+ b = 0;
+ else if (b > 100)
+ b = 100;
+ return RGB (r * 255 / 100, g * 255 / 100, b * 255 / 100);
+}
+
+
+static gboolean
+image_buffer_init (image_buffer_t *image, gint32 width, gint32 height, gint32 bgindex)
+{
+ gboolean status = FALSE;
+ gssize size;
+ gint i;
+ gint n;
+ gint r;
+ gint g;
+ gint b;
+
+ size = width * height;
+ image->width = width;
+ image->height = height;
+ image->ncolors = 2;
+ image->data = (unsigned char *) g_new0 (guchar, size);
+ if (! image->data)
+ goto end;
+
+ /* palette initialization */
+ for (n = 0; n < 16; n++)
+ image->palette[n] = color_table[n];
+
+ /* colors 16-231 are a 6x6x6 color cube */
+ for (r = 0; r < 6; r++)
+ for (g = 0; g < 6; g++)
+ for (b = 0; b < 6; b++)
+ image->palette[n++] = RGB (r * 51, g * 51, b * 51);
+
+ /* colors 232-255 are a grayscale ramp, intentionally leaving out */
+ for (i = 0; i < 24; i++)
+ image->palette[n++] = RGB (i * 11, i * 11, i * 11);
+
+ for (; n < SIXEL_PALETTE_MAX; n++)
+ image->palette[n] = RGB (255, 255, 255);
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+
+static void
+image_buffer_deinit (image_buffer_t *image)
+{
+ g_free (image->data);
+ image->data = NULL;
+}
+
+
+static gboolean
+image_buffer_resize (image_buffer_t *image, gint32 width, gint32 height, gint32 bgindex)
+{
+ gboolean status = FALSE;
+ gssize size;
+ guchar *alt_buffer;
+ gint32 n;
+ gint32 min_height;
+
+ size = width * height;
+ alt_buffer = g_new (guchar, size);
+ if (alt_buffer == NULL)
+ goto end;
+
+ min_height = height > image->height ? image->height: height;
+ if (width > image->width) /* if width is extended */
+ {
+ for (n = 0; n < min_height; ++n)
+ {
+ /* copy from source buffer */
+ memcpy (alt_buffer + width * n,
+ image->data + image->width * n,
+ (size_t)image->width);
+ /* fill extended area with background color */
+ memset (alt_buffer + width * n + image->width,
+ bgindex,
+ (size_t)(width - image->width));
+ }
+ }
+ else
+ {
+ for (n = 0; n < min_height; ++n)
+ {
+ /* copy from source buffer */
+ memcpy (alt_buffer + width * n,
+ image->data + image->width * n,
+ (size_t)width);
+ }
+ }
+
+ if (height > image->height) /* if height is extended */
+ {
+ /* fill extended area with background color */
+ memset (alt_buffer + width * image->height,
+ bgindex,
+ (size_t)(width * (height - image->height)));
+ }
+
+ /* free source buffer */
+ g_free (image->data);
+
+ image->data = alt_buffer;
+ image->width = width;
+ image->height = height;
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+
+static gboolean
+parser_context_init (parser_context_t *context)
+{
+ gboolean status = FALSE;
+
+ context->state = PS_GROUND;
+ context->pos_x = 0;
+ context->pos_y = 0;
+ context->max_x = 0;
+ context->max_y = 0;
+ context->attributed_pan = 2;
+ context->attributed_pad = 1;
+ context->attributed_ph = 0;
+ context->attributed_pv = 0;
+ context->repeat_count = 1;
+ context->color_index = 15;
+ context->bgindex = -1;
+ context->nparams = 0;
+ context->param = 0;
+
+ status = TRUE;
+
+ return status;
+}
+
+
+/* convert sixel data into indexed pixel bytes and palette data */
+static gboolean
+sixel_decode (guchar *p, gint len, image_buffer_t *image, parser_context_t *context)
+{
+ gboolean status = FALSE;
+ gint n;
+ gint i;
+ gint y;
+ gint bits;
+ gint sixel_vertical_mask;
+ gint sx;
+ gint sy;
+ gint c;
+ gint pos;
+ guchar *p0 = p;
+
+ while (p < p0 + len)
+ {
+ switch (context->state)
+ {
+ case PS_GROUND:
+ switch (*p)
+ {
+ case 0x1b:
+ context->state = PS_ESC;
+ p++;
+ break;
+ case 0x90:
+ context->state = PS_DCS;
+ p++;
+ break;
+ case 0x9c:
+ status = TRUE;
+ p++;
+ goto end;
+ default:
+ p++;
+ break;
+ }
+ break;
+
+ case PS_ESC:
+ switch (*p)
+ {
+ case '\\':
+ case 0x9c:
+ status = TRUE;
+ p++;
+ goto end;
+ case 'P':
+ context->param = -1;
+ context->state = PS_DCS;
+ p++;
+ break;
+ default:
+ p++;
+ break;
+ }
+ break;
+
+ case PS_DCS:
+ switch (*p)
+ {
+ case 0x1b:
+ context->state = PS_ESC;
+ p++;
+ break;
+ case '0' ... '9':
+ if (context->param < 0)
+ context->param = 0;
+ context->param = context->param * 10 + *p - '0';
+ p++;
+ break;
+ case ';':
+ if (context->param < 0)
+ context->param = 0;
+ if (context->nparams < DECSIXEL_PARAMS_MAX)
+ context->params[context->nparams++] = context->param;
+ context->param = 0;
+ p++;
+ break;
+ case '\x40' ... '\x70':
+ p++;
+ goto end;
+ case 'q':
+ if (context->param >= 0 && context->nparams < DECSIXEL_PARAMS_MAX)
+ context->params[context->nparams++] = context->param;
+ if (context->nparams > 0)
+ {
+ /* Pn1 */
+ switch (context->params[0])
+ {
+ case 0:
+ case 1:
+ context->attributed_pad = 2;
+ break;
+ case 2:
+ context->attributed_pad = 5;
+ break;
+ case 3:
+ context->attributed_pad = 4;
+ break;
+ case 4:
+ context->attributed_pad = 4;
+ break;
+ case 5:
+ context->attributed_pad = 3;
+ break;
+ case 6:
+ context->attributed_pad = 3;
+ break;
+ case 7:
+ context->attributed_pad = 2;
+ break;
+ case 8:
+ context->attributed_pad = 2;
+ break;
+ case 9:
+ context->attributed_pad = 1;
+ break;
+ default:
+ context->attributed_pad = 2;
+ break;
+ }
+ }
+
+ if (n > 2)
+ {
+ /* Pn3 */
+ if (context->params[2] == 0)
+ {
+ context->params[2] = 10;
+ }
+ context->attributed_pan = context->attributed_pan * context->params[2] / 10;
+ context->attributed_pad = context->attributed_pad * context->params[2] / 10;
+ if (context->attributed_pan <= 0)
+ {
+ context->attributed_pan = 1;
+ }
+ if (context->attributed_pad <= 0)
+ {
+ context->attributed_pad = 1;
+ }
+ }
+ context->nparams = 0;
+ context->state = PS_DECSIXEL;
+ p++;
+ break;
+ default:
+ p++;
+ goto end;
+ }
+ break;
+
+ case PS_DECSIXEL:
+ switch (*p)
+ {
+ case '\x1b':
+ context->state = PS_ESC;
+ p++;
+ break;
+ case '"':
+ context->param = 0;
+ context->nparams = 0;
+ context->state = PS_DECGRA;
+ p++;
+ break;
+ case '!':
+ context->param = 0;
+ context->nparams = 0;
+ context->state = PS_DECGRI;
+ p++;
+ break;
+ case '#':
+ context->param = 0;
+ context->nparams = 0;
+ context->state = PS_DECGCI;
+ p++;
+ break;
+ case '$':
+ /* DECGCR Graphics Carriage Return */
+ context->pos_x = 0;
+ p++;
+ break;
+ case '-':
+ /* DECGNL Graphics Next Line */
+ context->pos_x = 0;
+ context->pos_y += 6;
+ p++;
+ break;
+ case '?' ... '~':
+ if (image->width < (context->pos_x + context->repeat_count) ||
+ image->height < (context->pos_y + 6))
+ {
+ sx = image->width * 2;
+ sy = image->height * 2;
+ while (sx < (context->pos_x + context->repeat_count) ||
+ sy < (context->pos_y + 6))
+ {
+ sx *= 2;
+ sy *= 2;
+ }
+ if (! image_buffer_resize (image, sx, sy, context->bgindex))
+ goto end;
+ }
+
+ if (context->color_index > image->ncolors)
+ image->ncolors = context->color_index;
+
+ if ((bits = *p - '?') == 0)
+ {
+ context->pos_x += context->repeat_count;
+ }
+ else
+ {
+ sixel_vertical_mask = 0x01;
+ if (context->repeat_count <= 1)
+ {
+ for (i = 0; i < 6; i++)
+ {
+ if ((bits & sixel_vertical_mask) != 0)
+ {
+ pos = image->width * (context->pos_y + i) + context->pos_x;
+ image->data[pos] = context->color_index;
+ if (context->max_x < context->pos_x)
+ context->max_x = context->pos_x;
+ if (context->max_y < (context->pos_y + i))
+ context->max_y = context->pos_y + i;
+ }
+ sixel_vertical_mask <<= 1;
+ }
+ context->pos_x += 1;
+ }
+ else
+ {
+ /* context->repeat_count > 1 */
+ for (i = 0; i < 6; i++)
+ {
+ if ((bits & sixel_vertical_mask) != 0)
+ {
+ c = sixel_vertical_mask << 1;
+ for (n = 1; (i + n) < 6; n++)
+ {
+ if ((bits & c) == 0)
+ break;
+ c <<= 1;
+ }
+ for (y = context->pos_y + i; y < context->pos_y + i + n; ++y)
+ memset (image->data + image->width * y + context->pos_x,
+ context->color_index,
+ (size_t)context->repeat_count);
+ if (context->max_x < (context->pos_x + context->repeat_count - 1))
+ context->max_x = context->pos_x + context->repeat_count - 1;
+ if (context->max_y < (context->pos_y + i + n - 1))
+ context->max_y = context->pos_y + i + n - 1;
+ i += (n - 1);
+ sixel_vertical_mask <<= (n - 1);
+ }
+ sixel_vertical_mask <<= 1;
+ }
+ context->pos_x += context->repeat_count;
+ }
+ }
+ context->repeat_count = 1;
+ p++;
+ break;
+ default:
+ p++;
+ break;
+ }
+ break;
+
+ case PS_DECGRA:
+ /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */
+ switch (*p)
+ {
+ case '\x1b':
+ context->state = PS_ESC;
+ p++;
+ break;
+ case '0' ... '9':
+ context->param = context->param * 10 + *p - '0';
+ p++;
+ break;
+ case ';':
+ if (context->nparams < DECSIXEL_PARAMS_MAX)
+ context->params[context->nparams++] = context->param;
+ context->param = 0;
+ p++;
+ break;
+ default:
+ if (context->nparams < DECSIXEL_PARAMS_MAX)
+ context->params[context->nparams++] = context->param;
+ if (context->nparams > 0)
+ context->attributed_pad = context->params[0];
+ if (context->nparams > 1)
+ context->attributed_pan = context->params[1];
+ if (context->nparams > 2 && context->params[2] > 0)
+ context->attributed_ph = context->params[2];
+ if (context->nparams > 3 && context->params[3] > 0)
+ context->attributed_pv = context->params[3];
+
+ if (context->attributed_pan <= 0)
+ context->attributed_pan = 1;
+ if (context->attributed_pad <= 0)
+ context->attributed_pad = 1;
+
+ if (image->width < context->attributed_ph ||
+ image->height < context->attributed_pv)
+ {
+ sx = context->attributed_ph;
+ if (image->width > context->attributed_ph)
+ sx = image->width;
+
+ sy = context->attributed_pv;
+ if (image->height > context->attributed_pv)
+ sy = image->height;
+
+ if (! image_buffer_resize (image, sx, sy, context->bgindex))
+ goto end;
+ }
+ context->state = PS_DECSIXEL;
+ context->param = 0;
+ context->nparams = 0;
+ }
+ break;
+
+ case PS_DECGRI:
+ /* DECGRI Graphics Repeat Introducer ! Pn Ch */
+ switch (*p)
+ {
+ case '\x1b':
+ context->state = PS_ESC;
+ p++;
+ break;
+ case '0' ... '9':
+ context->param = context->param * 10 + *p - '0';
+ p++;
+ break;
+ case ';':
+ if (context->nparams < DECSIXEL_PARAMS_MAX)
+ context->params[context->nparams++] = context->param;
+ context->param = 0;
+ p++;
+ break;
+ default:
+ if (context->nparams < DECSIXEL_PARAMS_MAX)
+ context->params[context->nparams++] = context->param;
+ context->repeat_count = context->param;
+ if (context->repeat_count == 0)
+ context->repeat_count = 1;
+ context->state = PS_DECSIXEL;
+ context->param = 0;
+ context->nparams = 0;
+ break;
+ }
+ break;
+
+ case PS_DECGCI:
+ /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */
+ switch (*p)
+ {
+ case '\x1b':
+ context->state = PS_ESC;
+ p++;
+ break;
+ case '0' ... '9':
+ context->param = context->param * 10 + *p - '0';
+ p++;
+ break;
+ case ';':
+ if (context->nparams < DECSIXEL_PARAMS_MAX)
+ context->params[context->nparams++] = context->param;
+ context->param = 0;
+ p++;
+ break;
+ default:
+ context->state = PS_DECSIXEL;
+ if (context->nparams < DECSIXEL_PARAMS_MAX)
+ context->params[context->nparams++] = context->param;
+ context->param = 0;
+
+ if (context->nparams > 0)
+ {
+ context->color_index = context->params[0];
+ if (context->color_index < 0)
+ context->color_index = 0;
+ else if (context->color_index >= SIXEL_PALETTE_MAX)
+ context->color_index = SIXEL_PALETTE_MAX - 1;
+ }
+
+ if (context->nparams > 4)
+ {
+ if (context->params[1] == 1)
+ {
+ /* HLS */
+ if (context->params[2] > 360)
+ context->params[2] = 360;
+ if (context->params[3] > 100)
+ context->params[3] = 100;
+ if (context->params[4] > 100)
+ context->params[4] = 100;
+ image->palette[context->color_index]
+ = hls2rgb (context->params[2], context->params[3], context->params[4]);
+ }
+ else if (context->params[1] == 2)
+ {
+ /* RGB */
+ if (context->params[2] > 100)
+ context->params[2] = 100;
+ if (context->params[3] > 100)
+ context->params[3] = 100;
+ if (context->params[4] > 100)
+ context->params[4] = 100;
+ image->palette[context->color_index]
+ = XRGB (context->params[2], context->params[3], context->params[4]);
+ }
+ }
+ break;
+ }
+ break;
+ }
+ }
+
+ if (++context->max_x < context->attributed_ph)
+ context->max_x = context->attributed_ph;
+
+ if (++context->max_y < context->attributed_pv)
+ context->max_y = context->attributed_pv;
+
+ if (image->width > context->max_x || image->height > context->max_y)
+ {
+ if (! image_buffer_resize (image, context->max_x, context->max_y, context->bgindex))
+ goto end;
+ }
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+
+static gboolean
+load_image (const gchar *filename, gint32 *pimage_ID, GError **error)
+{
+ gboolean status = FALSE;
+ gint32 image_ID;
+ gint32 layer_ID;
+ GeglBuffer *buffer;
+ gint tile_height;
+ gint scanlines;
+ guchar bucket[2048];
+ guchar *buf;
+ guchar *dst;
+ guchar *src;
+ gint i;
+ gint j;
+ GFileInputStream *fileinput;
+ GInputStream *input;
+ gssize bytes_read;
+ image_buffer_t image;
+ parser_context_t context;
+ GFile *file;
+
+
+ gimp_progress_init_printf (_("Opening '%s'"),
+ gimp_filename_to_utf8 (filename));
+
+ file = g_file_new_for_path (filename);
+
+ fileinput = g_file_read (file, NULL, error);
+ if (! fileinput)
+ goto end;
+
+ input = G_INPUT_STREAM (fileinput);
+ if (! input)
+ goto end;
+
+ /* parser context initialization */
+ if (! parser_context_init (&context))
+ goto end;
+
+ /* buffer initialization */
+ if (! image_buffer_init (&image, 2048, 2048, context.bgindex))
+ goto end;
+
+ for (;;)
+ {
+ bytes_read = g_input_stream_read (input, bucket, sizeof(bucket),
+ NULL, error);
+ if (bytes_read == 0)
+ break;
+
+ if (bytes_read < 0)
+ {
+ g_object_unref (input);
+ g_free (image.data);
+ goto end;
+ }
+
+ if (! sixel_decode ((guchar *)bucket, bytes_read, &image, &context))
+ {
+ g_object_unref (input);
+ g_free (image.data);
+ goto end;
+ }
+ }
+
+ g_object_unref (input);
+
+ /* create the new image */
+ image_ID = gimp_image_new (image.width,
+ image.height,
+ GIMP_RGB);
+
+ /* name it */
+ gimp_image_set_filename (image_ID, filename);
+
+ layer_ID = gimp_layer_new (image_ID,
+ _("Color"),
+ image.width,
+ image.height,
+ GIMP_RGBA_IMAGE,
+ 100,
+ GIMP_NORMAL_MODE);
+
+ gimp_image_insert_layer (image_ID, layer_ID, -1, 0);
+
+ buffer = gimp_drawable_get_buffer (layer_ID);
+
+ tile_height = gimp_tile_height ();
+
+ buf = g_new (guchar, tile_height * image.width * 4);
+
+ src = image.data;
+
+ for (i = 0; i < image.height; i += tile_height)
+ {
+ dst = buf;
+ scanlines = MIN (tile_height, image.height - i);
+ j = scanlines * image.width;
+ while (j--)
+ {
+ *(dst++) = image.palette[*src] >> 16 & 0xff;
+ *(dst++) = image.palette[*src] >> 8 & 0xff;
+ *(dst++) = image.palette[*src] & 0xff;
+ *(dst++) = 0xff;
+ src++;
+
+ if ((j % 100) == 0)
+ gimp_progress_update ((double) i / (double) image.height);
+ }
+
+ gegl_buffer_set (buffer,
+ GEGL_RECTANGLE (0, i, image.width, scanlines), 0,
+ NULL, buf, GEGL_AUTO_ROWSTRIDE);
+ }
+
+ /* clean up and exit */
+
+ image_buffer_deinit (&image);
+ g_object_unref (buffer);
+ g_free (buf);
+
+ gimp_progress_update (1.0);
+
+ *pimage_ID = image_ID;
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+static gboolean
+sixel_putc (GOutputStream *output,
+ guchar b,
+ GError **error)
+{
+ return g_data_output_stream_put_byte (G_DATA_OUTPUT_STREAM (output),
+ b, NULL, error);
+}
+
+static gboolean
+sixel_puts (GOutputStream *output,
+ const gchar *s,
+ GError **error)
+{
+ return g_data_output_stream_put_string (G_DATA_OUTPUT_STREAM (output),
+ s, NULL, error);
+}
+
+
+#define SIXEL_OUTPUT_PACKET_SIZE 2048
+#define SIXEL_PALETTE_MIN 2
+#define SIXEL_PALETTE_MAX 256
+
+typedef struct sixel_node {
+ struct sixel_node *next;
+ gint32 pal;
+ gint32 sx;
+ gint32 mx;
+ unsigned char *map;
+} sixel_node_t;
+
+typedef struct sixel_output
+{
+
+ /* compatiblity flags */
+
+ /* 1: the argument of repeat introducer(DECGRI) is not limitted
+ 0: the argument of repeat introducer(DECGRI) is limitted 255 */
+ guchar has_gri_arg_limit;
+
+ /* PALETTETYPE_AUTO: select palette type automatically
+ * PALETTETYPE_HLS : HLS color space
+ * PALETTETYPE_RGB : RGB color space */
+ guchar palette_type;
+
+ GOutputStream *ostream;
+
+ gint save_pixel;
+ gint32 save_count;
+ gint32 active_palette;
+
+ sixel_node_t *node_top;
+ sixel_node_t *node_free;
+
+ gint32 encode_policy;
+
+ guchar buffer[SIXEL_OUTPUT_PACKET_SIZE];
+} sixel_output_t;
+
+
+static gboolean
+sixel_output_init (sixel_output_t *output, const gchar *filename, GError **error)
+{
+ gboolean status = FALSE;
+ GOutputStream *ostream;
+
+ /* open the destination file for writing */
+ ostream = G_OUTPUT_STREAM (g_file_replace (g_file_new_for_path (filename),
+ NULL, FALSE, G_FILE_CREATE_NONE,
+ NULL, error));
+ if (! ostream)
+ goto end;
+
+ GDataOutputStream *data_output;
+
+ data_output = g_data_output_stream_new (ostream);
+ g_object_unref (ostream);
+
+ g_data_output_stream_set_byte_order (data_output,
+ G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN);
+
+ ostream = G_OUTPUT_STREAM (data_output);
+
+ output->ostream = ostream;
+ output->has_gri_arg_limit = 1;
+ output->palette_type = SIXEL_PALETTETYPE_AUTO;
+ output->save_pixel = 0;
+ output->save_count = 0;
+ output->active_palette = 0;
+ output->node_top = NULL;
+ output->node_free = NULL;
+ output->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+static void
+sixel_output_deinit (sixel_output_t *output, GError **error)
+{
+ g_object_unref (output->ostream);
+}
+
+static gboolean
+sixel_putnum (GOutputStream *ostream, int value, GError **error)
+{
+ ldiv_t r;
+ gboolean status = FALSE;
+
+ r = ldiv (value, 10);
+ if (r.quot > 0)
+ {
+ if (! sixel_putnum (ostream, r.quot, error))
+ goto end;
+ }
+ if (! sixel_putc (ostream, '0' + r.rem, error))
+ goto end;
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+
+static gboolean
+sixel_put_flash (sixel_output_t *const output, GError **error)
+{
+ gboolean status = FALSE;
+ gint n;
+
+ if (output->has_gri_arg_limit) /* VT240 Max 255 ? */
+ {
+ while (output->save_count > 255)
+ {
+ /* argument of DECGRI('!') is limitted to 255 in real VT */
+ if (! sixel_puts (output->ostream, "!255", error))
+ goto end;
+
+ if (! sixel_putc (output->ostream, output->save_pixel, error))
+ goto end;
+
+ output->save_count -= 255;
+ }
+ }
+
+ if (output->save_count > 3)
+ {
+ /* DECGRI Graphics Repeat Introducer ! Pn Ch */
+ if (! sixel_putc (output->ostream, (guchar)'!', error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream, output->save_count, error))
+ goto end;
+
+ if (! sixel_putc (output->ostream, (guchar)output->save_pixel, error))
+ goto end;
+ }
+ else
+ {
+ for (n = 0; n < output->save_count; n++)
+ {
+ if (! sixel_putc (output->ostream, (guchar)output->save_pixel, error))
+ goto end;
+ }
+ }
+
+ output->save_pixel = 0;
+ output->save_count = 0;
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+
+static gboolean
+sixel_put_pixel (sixel_output_t *const output, gint pix, GError **error)
+{
+ gboolean status = FALSE;
+
+ if (pix < 0 || pix > '?')
+ {
+ pix = 0;
+ }
+
+ pix += '?';
+
+ if (pix == output->save_pixel)
+ {
+ output->save_count++;
+ }
+ else
+ {
+ if (! sixel_put_flash (output, error))
+ {
+ goto end;
+ }
+ output->save_pixel = pix;
+ output->save_count = 1;
+ }
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+
+static gboolean
+sixel_node_new (sixel_node_t **np)
+{
+ *np = g_new (sixel_node_t, 1);
+ if (np == NULL)
+ return FALSE;
+ return TRUE;
+}
+
+
+static void
+sixel_node_del (sixel_output_t *output, sixel_node_t *np)
+{
+ sixel_node_t *tp;
+
+ if ((tp = output->node_top) == np)
+ {
+ output->node_top = np->next;
+ }
+ else
+ {
+ while (tp->next != NULL)
+ {
+ if (tp->next == np)
+ {
+ tp->next = np->next;
+ break;
+ }
+ tp = tp->next;
+ }
+ }
+
+ np->next = output->node_free;
+ output->node_free = np;
+}
+
+
+static gboolean
+sixel_put_node (
+ sixel_output_t *output,
+ gint *x,
+ sixel_node_t *np,
+ gint ncolors,
+ gint32 keycolor,
+ GError **error)
+{
+ gboolean status = FALSE;
+
+ if (ncolors != 2 || keycolor == (-1))
+ {
+ /* designate palette index */
+ if (output->active_palette != np->pal)
+ {
+ if (! sixel_putc (output->ostream, (guchar)'#', error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream, np->pal, error))
+ goto end;
+
+ output->active_palette = np->pal;
+ }
+ }
+
+ for (; *x < np->sx; ++*x)
+ {
+ if (*x != keycolor)
+ {
+ if (! sixel_put_pixel (output, 0, error))
+ {
+ goto end;
+ }
+ }
+ }
+
+ for (; *x < np->mx; ++*x)
+ {
+ if (*x != keycolor)
+ {
+ if (! sixel_put_pixel (output, np->map[*x], error))
+ {
+ goto end;
+ }
+ }
+ }
+
+ if (! sixel_put_flash (output, error))
+ goto end;
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+
+static gboolean
+sixel_encode_header (
+ gint width,
+ gint height,
+ sixel_output_t *output,
+ GError **error)
+{
+ gboolean status = FALSE;
+ int p[3] = {0, 0, 0};
+ int pcount = 3;
+
+ if (! sixel_puts (output->ostream, "\033Pq", error))
+ return FALSE;
+
+ if (pcount > 0)
+ {
+ if (! sixel_putnum (output->ostream, p[0], error))
+ goto end;
+
+ if (pcount > 1)
+ {
+ if (! sixel_putc (output->ostream, ';', error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream, p[1], error))
+ goto end;
+
+ if (pcount > 2)
+ {
+ if (! sixel_putc (output->ostream, ';', error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream, p[2], error))
+ goto end;
+ }
+ }
+ }
+
+ if (! sixel_putc (output->ostream, 'q', error))
+ goto end;
+
+ if (! sixel_puts (output->ostream, "\"1;1;", error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream, width, error))
+ goto end;
+
+ if (! sixel_putc (output->ostream, ';', error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream, height, error))
+ goto end;
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+
+static gboolean
+output_rgb_palette_definition (
+ sixel_output_t *output,
+ guchar *palette,
+ gint n,
+ gint keycolor,
+ GError **error)
+{
+ gboolean status = FALSE;
+
+ if (n != keycolor)
+ {
+ /* DECGCI Graphics Color Introducer # Pc ; Pu; Px; Py; Pz */
+ if (! sixel_putc (output->ostream, (guchar)'#', error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream, n, error))
+ goto end;
+
+ if (! sixel_puts (output->ostream, ";2;", error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream,
+ (palette[n * 3 + 0] * 100 + 127) / 255,
+ error))
+ goto end;
+
+ if (! sixel_putc (output->ostream, ';', error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream,
+ (palette[n * 3 + 1] * 100 + 127) / 255,
+ error))
+ goto end;
+
+ if (! sixel_putc (output->ostream, ';', error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream,
+ (palette[n * 3 + 2] * 100 + 127) / 255,
+ error))
+ goto end;
+ }
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+
+static gboolean
+output_hls_palette_definition (
+ sixel_output_t *output,
+ guchar *palette,
+ gint n,
+ gint keycolor,
+ GError **error)
+{
+ gboolean status = FALSE;
+ int h;
+ int l;
+ int s;
+ int r;
+ int g;
+ int b;
+ int max;
+ int min;
+
+ if (n != keycolor)
+ {
+ r = palette[n * 3 + 0];
+ g = palette[n * 3 + 1];
+ b = palette[n * 3 + 2];
+ max = r > g ? (r > b ? r: b): (g > b ? g: b);
+ min = r < g ? (r < b ? r: b): (g < b ? g: b);
+ l = ((max + min) * 100 + 255) / 510;
+ if (max == min)
+ {
+ h = s = 0;
+ }
+ else
+ {
+ if (l < 50)
+ {
+ s = ((max - min) * 100 + 127) / (max + min);
+ }
+ else
+ {
+ s = ((max - min) * 100 + 127) / ((255 - max) + (255 - min));
+ }
+ if (r == max)
+ {
+ h = 120 + (g - b) * 60 / (max - min);
+ }
+ else if (g == max)
+ {
+ h = 240 + (b - r) * 60 / (max - min);
+ }
+ else if (r < g) /* if (b == max) */
+ {
+ h = 360 + (r - g) * 60 / (max - min);
+ }
+ else
+ {
+ h = 0 + (r - g) * 60 / (max - min);
+ }
+ }
+ /* DECGCI Graphics Color Introducer # Pc ; Pu; Px; Py; Pz */
+ if (! sixel_putc (output->ostream, (guchar)'#', error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream, n, error))
+ goto end;
+
+ if (! sixel_puts (output->ostream, ";1;", error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream, h, error))
+ goto end;
+
+ if (! sixel_putc (output->ostream, (guchar)';', error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream, l, error))
+ goto end;
+
+ if (! sixel_putc (output->ostream, (guchar)';', error))
+ goto end;
+
+ if (! sixel_putnum (output->ostream, s, error))
+ goto end;
+ }
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+
+static gboolean
+sixel_encode_body (
+ guchar *pixels,
+ gint32 width,
+ gint32 height,
+ guchar *palette,
+ gint ncolors,
+ sixel_output_t *output,
+ GError **error)
+{
+ gboolean status = FALSE;
+ gint x;
+ gint y;
+ gint i;
+ gint n;
+ gint c;
+ gint sx;
+ gint mx;
+ gint len;
+ gint pix;
+ guchar *map = NULL;
+ sixel_node_t *np, *tp, top;
+ gint fillable = 0;
+ gint keycolor = -1;
+
+ if (ncolors < 1)
+ goto end;
+
+ len = ncolors * width;
+
+ map = g_new (guchar, len);
+ if (map == NULL)
+ goto end;
+ memset (map, 0xff, len);
+
+ if (ncolors != 2 || keycolor == (-1))
+ {
+ if (output->palette_type == SIXEL_PALETTETYPE_HLS)
+ {
+ for (n = 0; n < ncolors; n++)
+ {
+ if (! output_hls_palette_definition (output, palette, n, keycolor, error))
+ goto end;
+ }
+ }
+ else
+ {
+ for (n = 0; n < ncolors; n++)
+ {
+ if (! output_rgb_palette_definition (output, palette, n, keycolor, error))
+ goto end;
+ }
+ }
+ }
+
+ for (y = i = 0; y < height; y++)
+ {
+ if (output->encode_policy != SIXEL_ENCODEPOLICY_SIZE)
+ {
+ fillable = 0;
+ }
+ else
+ {
+ /* normal sixel */
+ fillable = 1;
+ }
+
+ for (x = 0; x < width; x++)
+ {
+ pix = pixels[y * width + x]; /* color index */
+ if (pix >= 0 && pix < ncolors && pix != keycolor)
+ {
+ map[pix * width + x] |= (1 << i);
+ }
+ else
+ {
+ fillable = 0;
+ }
+ }
+
+ if (++i < 6 && (y + 1) < height)
+ {
+ continue;
+ }
+
+ for (c = 0; c < ncolors; c++)
+ {
+ for (sx = 0; sx < width; sx++)
+ {
+ if (map[c * width + sx] == 0)
+ {
+ continue;
+ }
+
+ for (mx = sx + 1; mx < width; mx++)
+ {
+ if (map[c * width + mx] != 0)
+ {
+ continue;
+ }
+
+ for (n = 1; (mx + n) < width; n++)
+ {
+ if (map[c * width + mx + n] != 0)
+ {
+ break;
+ }
+ }
+
+ if (n >= 10 || (mx + n) >= width)
+ {
+ break;
+ }
+ mx = mx + n - 1;
+ }
+
+ if ((np = output->node_free) != NULL)
+ {
+ output->node_free = np->next;
+ }
+ else
+ {
+ if (! sixel_node_new (&np))
+ {
+ goto end;
+ }
+ }
+
+ np->pal = c;
+ np->sx = sx;
+ np->mx = mx;
+ np->map = map + c * width;
+
+ top.next = output->node_top;
+ tp = &top;
+
+ while (tp->next != NULL)
+ {
+ if (np->sx < tp->next->sx)
+ {
+ break;
+ }
+ else if (np->sx == tp->next->sx && np->mx > tp->next->mx)
+ {
+ break;
+ }
+ tp = tp->next;
+ }
+
+ np->next = tp->next;
+ tp->next = np;
+ output->node_top = top.next;
+
+ sx = mx - 1;
+ }
+
+ }
+
+ if (y != 5)
+ {
+ /* DECGNL Graphics Next Line */
+ if (! sixel_putc (output->ostream, (guchar)'-', error))
+ goto end;
+ }
+
+ for (x = 0; (np = output->node_top) != NULL;)
+ {
+ sixel_node_t *next;
+ if (x > np->sx)
+ {
+ /* DECGCR Graphics Carriage Return */
+ if (! sixel_putc (output->ostream, (guchar)'$', error))
+ goto end;
+ x = 0;
+ }
+
+ if (fillable)
+ {
+ memset (np->map + np->sx, (1 << i) - 1, (size_t)(np->mx - np->sx));
+ }
+
+ if (! sixel_put_node (output, &x, np, ncolors, keycolor, error))
+ {
+ goto end;
+ }
+ next = np->next;
+ sixel_node_del (output, np);
+ np = next;
+
+ while (np != NULL)
+ {
+ if (np->sx < x)
+ {
+ np = np->next;
+ continue;
+ }
+
+ if (fillable)
+ {
+ memset (np->map + np->sx, (1 << i) - 1, (size_t)(np->mx - np->sx));
+ }
+ if (! sixel_put_node (output, &x, np, ncolors, keycolor, error))
+ {
+ goto end;
+ }
+ next = np->next;
+ sixel_node_del (output, np);
+ np = next;
+ }
+
+ fillable = 0;
+ }
+
+ i = 0;
+ memset (map, 0, (size_t)len);
+ }
+
+ status = TRUE;
+
+end:
+ /* free nodes */
+ while ((np = output->node_free) != NULL)
+ {
+ output->node_free = np->next;
+ g_free (np);
+ }
+ output->node_top = NULL;
+
+ g_free (map);
+
+ return status;
+}
+
+
+static gboolean
+sixel_encode_footer (sixel_output_t *output, GError **error)
+{
+ gboolean status = FALSE;
+
+ if (! sixel_puts (output->ostream, "\033\\", error))
+ goto end;
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+
+static gboolean
+save_image (const gchar *filename,
+ gint32 image_ID,
+ gint32 drawable_ID,
+ GError **error)
+{
+ GeglBuffer *buffer;
+ const Babl *format;
+ gint width;
+ gint height;
+ gint ncolors = 0;
+ gboolean indexed;
+ guchar *buf;
+ gboolean status = FALSE;
+ sixel_output_t output;
+
+ buffer = gimp_drawable_get_buffer (drawable_ID);
+
+ width = gegl_buffer_get_width (buffer);
+ height = gegl_buffer_get_height (buffer);
+
+ indexed = gimp_drawable_is_indexed (drawable_ID);
+
+ switch (gimp_drawable_type (drawable_ID))
+ {
+ case GIMP_INDEXED_IMAGE:
+ format = gegl_buffer_get_format (buffer);
+ break;
+
+ default:
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Unsupported drawable type"));
+ g_object_unref (buffer);
+ return FALSE;
+ }
+
+ gimp_progress_init_printf (_("Exporting '%s'"),
+ gimp_filename_to_utf8 (filename));
+
+ /* allocate a pixel region to work with */
+ buf = g_new (guchar, width * height *
+ babl_format_get_bytes_per_pixel (format));
+
+ guchar *cmap = gimp_image_get_colormap (image_ID, &ncolors);
+
+ gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, width, height), 1.0,
+ format, buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ if (! sixel_output_init (&output, filename, error))
+ goto end;
+
+ if (! sixel_encode_header (width, height, &output, error))
+ goto end;
+
+ if (! sixel_encode_body (buf, width, height, cmap, ncolors, &output, error))
+ goto end;
+
+ if (! sixel_encode_footer (&output, error))
+ goto end;
+
+ sixel_output_deinit (&output, error);
+
+ g_free (buf);
+ g_free (cmap);
+ g_object_unref (buffer);
+
+ gimp_progress_update (1.0);
+
+ status = TRUE;
+
+end:
+ return status;
+}
+
+
+static gboolean
+save_dialog (void)
+{
+ GtkWidget *dialog;
+ GtkWidget *table;
+ GtkObject *scale_data;
+ gboolean run;
+
+ dialog = gimp_export_dialog_new (_("SIXEL"), PLUG_IN_BINARY, SAVE_PROC);
+
+ table = gtk_table_new (1, 3, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_container_set_border_width (GTK_CONTAINER (table), 12);
+ gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
+ table, TRUE, TRUE, 0);
+ gtk_widget_show (table);
+
+ scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
+ _("_Alpha threshold:"), SCALE_WIDTH, 0,
+ sixelvals.threshold, 0, 255, 1, 8, 0,
+ TRUE, 0, 0,
+ NULL, NULL);
+
+ g_signal_connect (scale_data, "value-changed",
+ G_CALLBACK (gimp_int_adjustment_update),
+ &sixelvals.threshold);
+
+ gtk_widget_show (dialog);
+
+ run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
+
+ gtk_widget_destroy (dialog);
+
+ return run;
+}
diff --git a/plug-ins/common/gimprc.common b/plug-ins/common/gimprc.common
index 0ff328d..fb3d1e5 100644
--- a/plug-ins/common/gimprc.common
+++ b/plug-ins/common/gimprc.common
@@ -49,6 +49,7 @@ file_pnm_RC = file-pnm.rc.o
file_ps_RC = file-ps.rc.o
file_psp_RC = file-psp.rc.o
file_raw_data_RC = file-raw-data.rc.o
+file_sixel_RC = file-sixel.rc.o
file_sunras_RC = file-sunras.rc.o
file_svg_RC = file-svg.rc.o
file_tga_RC = file-tga.rc.o
diff --git a/plug-ins/common/plugin-defs.pl b/plug-ins/common/plugin-defs.pl
index 568c5fe..de9632f 100644
--- a/plug-ins/common/plugin-defs.pl
+++ b/plug-ins/common/plugin-defs.pl
@@ -50,6 +50,7 @@
'file-ps' => { ui => 1, gegl => 1, optional => 1, libs => 'GS_LIBS' },
'file-psp' => { ui => 1, gegl => 1, libs => 'Z_LIBS' },
'file-raw-data' => { ui => 1, gegl => 1 },
+ 'file-sixel' => { ui => 1, gegl => 1 },
'file-sunras' => { ui => 1, gegl => 1 },
'file-svg' => { ui => 1, optional => 1, libs => 'SVG_LIBS', cflags => 'SVG_CFLAGS' },
'file-tga' => { ui => 1, gegl => 1 },
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment