Last active
May 8, 2016 07:35
-
-
Save saitoha/c1e1f1df48054fca4ae28a397dbdc6d8 to your computer and use it in GitHub Desktop.
gimp: SIXEL file coder plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
commit e7356ec11f4e7f8eb5d74ef491a9106ae8646034 | |
Author: Hayaki Saito <[email protected]> | |
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 <[email protected]> | |
+ */ | |
+ | |
+/* 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 = ⊤ | |
+ | |
+ 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