[vte/wip/sixels: 13/111] sixels: Initial rebase of saitoha's patch.




commit 67d654f2c02e09aa3cc94ea70c9de68ad0988bdb
Author: Hans Petter Jansson <hpj cl no>
Date:   Sat Aug 8 20:42:48 2020 +0200

    sixels: Initial rebase of saitoha's patch.

 src/debug.h           |   1 +
 src/meson.build       |   4 +
 src/ring.cc           | 161 +++++++++++
 src/ring.hh           |  27 +-
 src/sixel.cc          | 770 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/sixel.h           |  71 +++++
 src/vte.cc            | 182 ++++++++++++
 src/vte/vteterminal.h |  18 ++
 src/vtedefines.hh     |   1 +
 src/vtegtk.cc         |  85 ++++++
 src/vtegtk.hh         |   2 +
 src/vteimage.cc       | 275 ++++++++++++++++++
 src/vteimage.h        |  61 ++++
 src/vteinternal.hh    |  17 ++
 src/vteseq.cc         | 218 +++++++++++++-
 15 files changed, 1886 insertions(+), 7 deletions(-)
---
diff --git a/src/debug.h b/src/debug.h
index 90be5c5f..a9970f5f 100644
--- a/src/debug.h
+++ b/src/debug.h
@@ -68,6 +68,7 @@ typedef enum {
         VTE_DEBUG_BIDI          = 1 << 28,
         VTE_DEBUG_CONVERSION    = 1 << 29,
         VTE_DEBUG_EXCEPTIONS    = 1 << 30,
+        VTE_DEBUG_IMAGE         = 1 << 31,
 } VteDebugFlags;
 
 void _vte_debug_init(void);
diff --git a/src/meson.build b/src/meson.build
index 087831fa..dcb49f6a 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -126,6 +126,8 @@ libvte_common_sources = debug_sources + glib_glue_sources + libc_glue_sources +
   'ring.hh',
   'ringview.cc',
   'ringview.hh',
+  'sixel.cc',
+  'sixel.h',
   'spawn.cc',
   'spawn.hh',
   'utf8.cc',
@@ -135,6 +137,8 @@ libvte_common_sources = debug_sources + glib_glue_sources + libc_glue_sources +
   'vtegtk.cc',
   'vtegtk.hh',
   'vteinternal.hh',
+  'vteimage.cc',
+  'vteimage.h',
   'vtepcre2.h',
   'vteregex.cc',
   'vteregexinternal.hh',
diff --git a/src/ring.cc b/src/ring.cc
index 2d76d136..c1ac3844 100644
--- a/src/ring.cc
+++ b/src/ring.cc
@@ -25,6 +25,7 @@
 #include "vterowdata.hh"
 
 #include <string.h>
+#include <new>
 
 /*
  * Copy the common attributes from VteCellAttr to VteStreamCellAttr or vice versa.
@@ -74,6 +75,7 @@ Ring::Ring(row_t max_rows,
                m_attr_stream = _vte_file_stream_new ();
                m_text_stream = _vte_file_stream_new ();
                m_row_stream = _vte_file_stream_new ();
+               m_image_stream = _vte_file_stream_new ();
        } else {
                m_attr_stream = m_text_stream = m_row_stream = nullptr;
        }
@@ -86,20 +88,33 @@ Ring::Ring(row_t max_rows,
         auto empty_str = g_string_new_len("", 0);
         g_ptr_array_add(m_hyperlinks, empty_str);
 
+        m_image_map = new (std::nothrow) std::map<gint, vte::image::image_object *>();
+        m_image_onscreen_resource_counter = 0;
+        m_image_offscreen_resource_counter = 0;
+
        validate();
 }
 
 Ring::~Ring()
 {
+       auto image_map = m_image_map;
+
        for (size_t i = 0; i <= m_mask; i++)
                _vte_row_data_fini (&m_array[i]);
 
        g_free (m_array);
 
+       /* Clear SIXEL images */
+       for (auto it = image_map->begin (); it != image_map->end (); ++it)
+               delete it->second;
+       image_map->clear();
+       delete m_image_map;
+
        if (m_has_streams) {
                g_object_unref (m_attr_stream);
                g_object_unref (m_text_stream);
                g_object_unref (m_row_stream);
+               g_object_unref (m_image_stream);
        }
 
        g_string_free (m_utf8_buffer, TRUE);
@@ -585,12 +600,21 @@ Ring::reset_streams(row_t position)
 Ring::row_t
 Ring::reset()
 {
+       auto image_map = m_image_map;
+
         _vte_debug_print (VTE_DEBUG_RING, "Reseting the ring at %lu.\n", m_end);
 
         reset_streams(m_end);
         m_start = m_writable = m_end;
         m_cached_row_num = (row_t)-1;
 
+       /* Clear SIXEL images */
+       for (auto it = image_map->begin (); it != image_map->end (); ++it)
+               delete it->second;
+       image_map->clear();
+       if (m_has_streams)
+               _vte_stream_reset (m_image_stream, _vte_stream_head (m_image_stream));
+
         return m_end;
 }
 
@@ -1483,3 +1507,140 @@ Ring::write_contents(GOutputStream* stream,
 
        return true;
 }
+
+/**
+ * Ring::append_image:
+ * @surface: a Cairo surface object
+ * @pixelwidth: image width in pixels
+ * @pixelheight: image height in pixels
+ * @left: left position of image in cell unit
+ * @top: left position of image in cell unit
+ * @width: width of image in cell unit
+ * @height: width of image in cell unit
+ *
+ * Append an image into the internal image list.
+ */
+void
+Ring::append_image (cairo_surface_t *surface, gint pixelwidth, gint pixelheight, glong left, glong top, 
glong width, glong height)
+{
+       using namespace vte::image;
+       image_object *image;
+       gulong char_width, char_height;
+
+       image = new (std::nothrow) image_object (surface, pixelwidth, pixelheight, left, top, width, height, 
m_image_stream);
+       g_assert_true (image != NULL);
+
+       char_width = pixelwidth / width;
+       char_height = pixelwidth / height;
+
+       /* composition */
+       for (auto it = m_image_map->lower_bound (top); it != m_image_map->end (); ++it) {
+               image_object *current = it->second;
+
+               /* Combine two images if one's area includes another's area */
+               if (image->includes (current)) {
+                       /*
+                        * Replace current image with new image
+                        *
+                        *  +--------------+
+                        *  |     new      |
+                        *  | ...........  |
+                        *  | : current :  |
+                        *  | :.........:  |
+                        *  +--------------+
+                        */
+                       m_image_map->erase (image->get_bottom ());
+                       if (current->is_freezed())
+                               m_image_offscreen_resource_counter -= current->resource_size ();
+                       else
+                               m_image_onscreen_resource_counter -= current->resource_size ();
+                       delete current;
+               } else if (current->includes (image)) {
+                       /*
+                        * Copy new image to current image's sub-area.
+                        *
+                        *  +--------------+
+                        *  | +-----+      |
+                        *  | | new |      |
+                        *  | +-----+      |
+                        *  |    current   |
+                        *  +--------------+
+                        */
+                       if (current->is_freezed()) {
+                               m_image_offscreen_resource_counter -= current->resource_size ();
+                               current->thaw ();
+                       } else {
+                               m_image_onscreen_resource_counter -= current->resource_size ();
+                       }
+                       current->combine (image, char_width, char_height);
+                       m_image_onscreen_resource_counter += current->resource_size ();
+                       delete image;
+                       goto end;
+               }
+
+               if ((current->get_bottom () - image->get_bottom ()) * (current->get_top () - image->get_top 
()) <= 0) {
+                        /*
+                        * Unite two images if one's [top, bottom] includes another's [top, bottom].
+                        * This operation ensures bottom-position-based order is same to top-position-based 
order.
+                        *
+                        *              +------+
+                        *  +---------+ |      |
+                        *  | current | | new  |
+                        *  |         | |      |
+                        *  +---------+ |      |
+                        *              +------+
+                        *  or
+                        *
+                        *  +---------+
+                        *  | current | +------+
+                        *  |         | | new  |
+                        *  |         | +------+
+                        *  +---------+
+                        *          |
+                        *          v
+                        *  +------------------+
+                        *  | new (united)     |
+                        *  |                  |
+                        *  +------------------+
+                        */
+                       image->unite (image, char_width, char_height);
+                       m_image_map->erase (current->get_bottom ());
+                       if (current->is_freezed())
+                               m_image_offscreen_resource_counter -= current->resource_size ();
+                       else
+                               m_image_onscreen_resource_counter -= current->resource_size ();
+                       delete current;
+                       goto end;
+               }
+       }
+
+       /*
+        * Now register new image to the m_image_map container.
+        * the key is bottom positon.
+        *  +----------+
+        *  |   new    |
+        *  |          |
+        *  +----------+ <- bottom position (key)
+        */
+       m_image_map->insert (std::make_pair (image->get_bottom (), image));
+       m_image_onscreen_resource_counter += image->resource_size ();
+end:
+       /* noop */
+       ;
+}
+
+void
+Ring::shrink_image_stream ()
+{
+       using namespace vte::image;
+       image_object *first_image;
+
+       if (m_image_map->empty())
+               return;
+
+       first_image = m_image_map->begin()->second;
+
+       if (first_image->is_freezed ())
+               if (first_image->get_stream_position () > _vte_stream_tail (m_image_stream))
+                       _vte_stream_advance_tail (m_image_stream, first_image->get_stream_position ());
+}
diff --git a/src/ring.hh b/src/ring.hh
index 87b73645..0330858e 100644
--- a/src/ring.hh
+++ b/src/ring.hh
@@ -27,8 +27,10 @@
 
 #include "vterowdata.hh"
 #include "vtestream.h"
+#include "vteimage.h"
 
 #include <type_traits>
+#include <map>
 
 typedef struct _VteVisualPosition {
        long row, col;
@@ -89,6 +91,8 @@ public:
         VteRowData* insert(row_t position, guint8 bidi_flags);
         VteRowData* append(guint8 bidi_flags);
         void remove(row_t position);
+        void append_image (cairo_surface_t *surface, gint pixelwidth, gint pixelheight, glong left, glong 
top, glong width, glong height);
+        void shrink_image_stream ();
         void drop_scrollback(row_t position);
         void set_visible_rows(row_t rows);
         void rewrap(column_t columns,
@@ -98,6 +102,22 @@ public:
                             GCancellable* cancellable,
                             GError** error);
 
+        /* FIXME-hpj: These should be private, but are being accessed from the Terminal class for now:
+         *
+         * >>> */
+
+       bool m_has_streams;
+
+       row_t m_max;
+       row_t m_start{0};
+        row_t m_end{0};
+
+        std::map<gint, vte::image::image_object *> *m_image_map;
+        gulong m_image_onscreen_resource_counter;
+        gulong m_image_offscreen_resource_counter;
+
+        /* <<< */
+
 private:
 
         #ifdef VTE_DEBUG
@@ -183,10 +203,6 @@ private:
                       char const** hyperlink);
         void reset_streams(row_t position);
 
-       row_t m_max;
-       row_t m_start{0};
-        row_t m_end{0};
-
        /* Writable */
        row_t m_writable{0};
         row_t m_mask{31};
@@ -206,8 +222,7 @@ private:
          *    if nonempty, it actually contains the ID and URI separated with a semicolon. Not NUL 
terminated.
          *  - 2 bytes repeating attr.hyperlink_length so that we can walk backwards.
          */
-       bool m_has_streams;
-       VteStream *m_attr_stream, *m_text_stream, *m_row_stream;
+       VteStream *m_attr_stream, *m_text_stream, *m_row_stream, *m_image_stream;
        size_t m_last_attr_text_start_offset{0};
        VteCellAttr m_last_attr;
        GString *m_utf8_buffer;
diff --git a/src/sixel.cc b/src/sixel.cc
new file mode 100644
index 00000000..0e06ca66
--- /dev/null
+++ b/src/sixel.cc
@@ -0,0 +1,770 @@
+/*
+ * Copyright (C) Hayaki Saito
+ * originally written by kmiya@cluti (https://github.com/saitoha/sixel/blob/master/fromsixel.c)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>   /* isdigit */
+#include <string.h>  /* memcpy */
+#include <glib.h>
+
+#include "sixel.h"
+
+#define SIXEL_RGB(r, g, b) ((r) + ((g) << 8) +  ((b) << 16))
+#define PALVAL(n,a,m) (((n) * (a) + ((m) / 2)) / (m))
+#define SIXEL_XRGB(r,g,b) SIXEL_RGB(PALVAL(r, 255, 100), PALVAL(g, 255, 100), PALVAL(b, 255, 100))
+
+static int const sixel_default_color_table[] = {
+       SIXEL_XRGB(0,  0,  0),   /*  0 Black    */
+       SIXEL_XRGB(20, 20, 80),  /*  1 Blue     */
+       SIXEL_XRGB(80, 13, 13),  /*  2 Red      */
+       SIXEL_XRGB(20, 80, 20),  /*  3 Green    */
+       SIXEL_XRGB(80, 20, 80),  /*  4 Magenta  */
+       SIXEL_XRGB(20, 80, 80),  /*  5 Cyan     */
+       SIXEL_XRGB(80, 80, 20),  /*  6 Yellow   */
+       SIXEL_XRGB(53, 53, 53),  /*  7 Gray 50% */
+       SIXEL_XRGB(26, 26, 26),  /*  8 Gray 25% */
+       SIXEL_XRGB(33, 33, 60),  /*  9 Blue*    */
+       SIXEL_XRGB(60, 26, 26),  /* 10 Red*     */
+       SIXEL_XRGB(33, 60, 33),  /* 11 Green*   */
+       SIXEL_XRGB(60, 33, 60),  /* 12 Magenta* */
+       SIXEL_XRGB(33, 60, 60),  /* 13 Cyan*    */
+       SIXEL_XRGB(60, 60, 33),  /* 14 Yellow*  */
+       SIXEL_XRGB(80, 80, 80),  /* 15 Gray 75% */
+};
+
+/*
+ *  HLS-formatted color handling.
+ *  (0 degree = blue, double-hexcone model)
+ *  http://odl.sysworks.biz/disk$vaxdocdec021/progtool/d3qsaaa1.p64.bkb
+ */
+static int
+hls_to_rgb(int hue, int lum, int sat)
+{
+    double min, max;
+    int r, g, b;
+
+    if (sat == 0) {
+        r = g = b = lum;
+    }
+
+    /* https://wikimedia.org/api/rest_v1/media/math/render/svg/17e876f7e3260ea7fed73f69e19c71eb715dd09d */
+    max = lum + sat * (100 - (lum > 50 ? 1 : -1) * ((lum << 1) - 100)) / 200.0;
+
+    /* https://wikimedia.org/api/rest_v1/media/math/render/svg/f6721b57985ad83db3d5b800dc38c9980eedde1d */
+    min = lum - sat * (100 - (lum > 50 ? 1 : -1) * ((lum << 1) - 100)) / 200.0;
+
+    /* HLS hue color ring is roteted -120 degree from HSL's one. */
+    hue = (hue + 240) % 360;
+
+    /* https://wikimedia.org/api/rest_v1/media/math/render/svg/937e8abdab308a22ff99de24d645ec9e70f1e384 */
+    switch (hue / 60) {
+    case 0:  /* 0 <= hue < 60 */
+        r = max;
+        g = (min + (max - min) * (hue / 60.0));
+        b = min;
+        break;
+    case 1:  /* 60 <= hue < 120 */
+        r = min + (max - min) * ((120 - hue) / 60.0);
+        g = max;
+        b = min;
+        break;
+    case 2:  /* 120 <= hue < 180 */
+        r = min;
+        g = max;
+        b = (min + (max - min) * ((hue - 120) / 60.0));
+        break;
+    case 3:  /* 180 <= hue < 240 */
+        r = min;
+        g = (min + (max - min) * ((240 - hue) / 60.0));
+        b = max;
+        break;
+    case 4:  /* 240 <= hue < 300 */
+        r = (min + (max - min) * ((hue - 240) / 60.0));
+        g = min;
+        b = max;
+        break;
+    case 5:  /* 300 <= hue < 360 */
+    default:
+        r = max;
+        g = min;
+        b = (min + (max - min) * ((360 - hue) / 60.0));
+        break;
+    }
+
+    return SIXEL_XRGB(r, g, b);
+}
+
+static int
+set_default_color(sixel_image_t *image)
+{
+       int i;
+       int n;
+       int r;
+       int g;
+       int b;
+
+       /* palette initialization */
+       for (n = 1; n < 17; n++) {
+               image->palette[n] = sixel_default_color_table[n - 1];
+       }
+
+       /* colors 17-232 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++] = SIXEL_RGB(r * 51, g * 51, b * 51);
+                       }
+               }
+       }
+
+       /* colors 233-256 are a grayscale ramp, intentionally leaving out */
+       for (i = 0; i < 24; i++) {
+               image->palette[n++] = SIXEL_RGB(i * 11, i * 11, i * 11);
+       }
+
+       for (; n < DECSIXEL_PALETTE_MAX; n++) {
+               image->palette[n] = SIXEL_RGB(255, 255, 255);
+       }
+
+       return (0);
+}
+
+static int
+sixel_image_init(
+    sixel_image_t    *image,
+    int              width,
+    int              height,
+    int              fgcolor,
+    int              bgcolor,
+    int              use_private_register)
+{
+       int status = (-1);
+       size_t size;
+
+       size = (size_t)(width * height) * sizeof(sixel_color_no_t);
+       image->width = width;
+       image->height = height;
+       image->data = (sixel_color_no_t *)g_malloc(size);
+       image->ncolors = 2;
+       image->use_private_register = use_private_register;
+
+       if (image->data == NULL) {
+               status = (-1);
+               goto end;
+       }
+       memset(image->data, 0, size);
+
+       image->palette[0] = bgcolor;
+
+       if (image->use_private_register)
+               image->palette[1] = fgcolor;
+
+       image->palette_modified = 0;
+
+       status = (0);
+
+end:
+       return status;
+}
+
+
+static int
+image_buffer_resize(
+    sixel_image_t   *image,
+    int              width,
+    int              height)
+{
+       int status = (-1);
+       size_t size;
+       sixel_color_no_t *alt_buffer;
+       int n;
+       int min_height;
+
+       size = (size_t)(width * height) * sizeof(sixel_color_no_t);
+       alt_buffer = (sixel_color_no_t *)g_malloc(size);
+       if (alt_buffer == NULL) {
+               /* free source image */
+               g_free(image->data);
+               image->data = NULL;
+               status = (-1);
+               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 image */
+                       memcpy(alt_buffer + width * n,
+                              image->data + image->width * n,
+                              (size_t)image->width * sizeof(sixel_color_no_t));
+                       /* fill extended area with background color */
+                       memset(alt_buffer + width * n + image->width,
+                              0,
+                              (size_t)(width - image->width) * sizeof(sixel_color_no_t));
+               }
+       } else {
+               for (n = 0; n < min_height; ++n) {
+                       /* copy from source image */
+                       memcpy(alt_buffer + width * n,
+                              image->data + image->width * n,
+                              (size_t)width * sizeof(sixel_color_no_t));
+               }
+       }
+
+       if (height > image->height) {  /* if height is extended */
+               /* fill extended area with background color */
+               memset(alt_buffer + width * image->height,
+                      0,
+                      (size_t)(width * (height - image->height)) * sizeof(sixel_color_no_t));
+       }
+
+       /* free source image */
+       g_free(image->data);
+
+       image->data = alt_buffer;
+       image->width = width;
+       image->height = height;
+
+       status = (0);
+
+end:
+       return status;
+}
+
+static void
+sixel_image_deinit(sixel_image_t *image)
+{
+       g_free(image->data);
+       image->data = NULL;
+}
+
+int
+sixel_parser_init(sixel_state_t *st,
+                  int fgcolor, int bgcolor,
+                  int use_private_register)
+{
+       int status = (-1);
+
+       st->state = PS_DCS;
+       st->pos_x = 0;
+       st->pos_y = 0;
+       st->max_x = 0;
+       st->max_y = 0;
+       st->attributed_pan = 2;
+       st->attributed_pad = 1;
+       st->attributed_ph = 0;
+       st->attributed_pv = 0;
+       st->repeat_count = 1;
+       st->color_index = 16;
+       st->nparams = 0;
+       st->param = 0;
+
+       /* buffer initialization */
+       status = sixel_image_init(&st->image, 1, 1, fgcolor, bgcolor, use_private_register);
+
+       return status;
+}
+
+int
+sixel_parser_set_default_color(sixel_state_t *st)
+{
+       return set_default_color(&st->image);
+}
+
+int
+sixel_parser_finalize(sixel_state_t *st, unsigned char *pixels)
+{
+       int status = (-1);
+       int sx;
+       int sy;
+       sixel_image_t *image = &st->image;
+       int x, y;
+       sixel_color_no_t *src;
+       unsigned char *dst;
+       int color;
+
+       if (++st->max_x < st->attributed_ph)
+               st->max_x = st->attributed_ph;
+
+       if (++st->max_y < st->attributed_pv)
+               st->max_y = st->attributed_pv;
+
+       sx = st->max_x;
+       sy = st->max_y;
+
+       if (image->width > sx || image->height > sy) {
+               status = image_buffer_resize(image, sx, sy);
+               if (status < 0)
+                       goto end;
+       }
+
+       if (image->use_private_register && image->ncolors > 2 && !image->palette_modified) {
+               status = set_default_color(image);
+               if (status < 0)
+                       goto end;
+       }
+
+       src = st->image.data;
+       dst = pixels;
+       for (y = 0; y < st->image.height; ++y) {
+               for (x = 0; x < st->image.width; ++x) {
+                       color = st->image.palette[*src++];
+                       *dst++ = color >> 16 & 0xff;   /* b */
+                       *dst++ = color >> 8 & 0xff;    /* g */
+                       *dst++ = color >> 0 & 0xff;    /* r */
+                       *dst++ = 0xff;                 /* a */
+               }
+       }
+
+       status = (0);
+
+end:
+       return status;
+}
+
+/* convert sixel data into indexed pixel bytes and palette data */
+int
+sixel_parser_parse(sixel_state_t *st, unsigned char *p, size_t len)
+{
+       int status = (-1);
+       int n;
+       int i;
+       int x;
+       int y;
+       int bits;
+       int sixel_vertical_mask;
+       int sx;
+       int sy;
+       int c;
+       int pos;
+       unsigned char *p0 = p;
+       sixel_image_t *image = &st->image;
+
+       if (! image->data)
+               goto end;
+
+       while (p < p0 + len) {
+               switch (st->state) {
+               case PS_ESC:
+                       switch (*p) {
+                       case '\\':
+                       case 0x9c:
+                               p++;
+                               break;
+                       case 'P':
+                               st->param = -1;
+                               st->state = PS_DCS;
+                               p++;
+                               break;
+                       default:
+                               p++;
+                               break;
+                       }
+                       goto end;
+               case PS_DCS:
+                       switch (*p) {
+                       case 0x1b:
+                               st->state = PS_ESC;
+                               p++;
+                               break;
+                       case '0':
+                       case '1':
+                       case '2':
+                       case '3':
+                       case '4':
+                       case '5':
+                       case '6':
+                       case '7':
+                       case '8':
+                       case '9':
+                               if (st->param < 0)
+                                       st->param = 0;
+                               st->param = st->param * 10 + *p - '0';
+                               p++;
+                               break;
+                       case ';':
+                               if (st->param < 0) {
+                                       st->param = 0;
+                               }
+                               if (st->nparams < DECSIXEL_PARAMS_MAX) {
+                                       st->params[st->nparams++] = st->param;
+                               }
+                               st->param = 0;
+                               p++;
+                               break;
+                       case 'q':
+                               if (st->param >= 0 && st->nparams < DECSIXEL_PARAMS_MAX) {
+                                       st->params[st->nparams++] = st->param;
+                               }
+                               if (st->nparams > 0) {
+                                       /* Pn1 */
+                                       switch (st->params[0]) {
+                                       case 0:
+                                       case 1:
+                                               st->attributed_pad = 2;
+                                               break;
+                                       case 2:
+                                               st->attributed_pad = 5;
+                                               break;
+                                       case 3:
+                                       case 4:
+                                               st->attributed_pad = 4;
+                                               break;
+                                       case 5:
+                                       case 6:
+                                               st->attributed_pad = 3;
+                                               break;
+                                       case 7:
+                                       case 8:
+                                               st->attributed_pad = 2;
+                                               break;
+                                       case 9:
+                                               st->attributed_pad = 1;
+                                               break;
+                                       default:
+                                               st->attributed_pad = 2;
+                                               break;
+                                       }
+                               }
+
+                               if (st->nparams > 2) {
+                                       /* Pn3 */
+                                       if (st->params[2] == 0)
+                                               st->params[2] = 10;
+                                       st->attributed_pan = st->attributed_pan * st->params[2] / 10;
+                                       st->attributed_pad = st->attributed_pad * st->params[2] / 10;
+                                       if (st->attributed_pan <= 0)
+                                               st->attributed_pan = 1;
+                                       if (st->attributed_pad <= 0)
+                                               st->attributed_pad = 1;
+                               }
+                               st->nparams = 0;
+                               st->state = PS_DECSIXEL;
+                               p++;
+                               break;
+                       default:
+                               p++;
+                               break;
+                       }
+                       break;
+
+               case PS_DECSIXEL:
+                       switch (*p) {
+                       case '\x1b':
+                               st->state = PS_ESC;
+                               p++;
+                               break;
+                       case '"':
+                               st->param = 0;
+                               st->nparams = 0;
+                               st->state = PS_DECGRA;
+                               p++;
+                               break;
+                       case '!':
+                               st->param = 0;
+                               st->nparams = 0;
+                               st->state = PS_DECGRI;
+                               p++;
+                               break;
+                       case '#':
+                               st->param = 0;
+                               st->nparams = 0;
+                               st->state = PS_DECGCI;
+                               p++;
+                               break;
+                       case '$':
+                               /* DECGCR Graphics Carriage Return */
+                               st->pos_x = 0;
+                               p++;
+                               break;
+                       case '-':
+                               /* DECGNL Graphics Next Line */
+                               st->pos_x = 0;
+                               if (st->pos_y < DECSIXEL_HEIGHT_MAX - 5 - 6)
+                                       st->pos_y += 6;
+                               else
+                                       st->pos_y = DECSIXEL_HEIGHT_MAX + 1;
+                               p++;
+                               break;
+                       default:
+                               if (*p >= '?' && *p <= '~') {  /* sixel characters */
+                                       if ((image->width < (st->pos_x + st->repeat_count) || image->height < 
(st->pos_y + 6))
+                                               && image->width < DECSIXEL_WIDTH_MAX && image->height < 
DECSIXEL_HEIGHT_MAX) {
+                                               sx = image->width * 2;
+                                               sy = image->height * 2;
+                                               while (sx < (st->pos_x + st->repeat_count) || sy < (st->pos_y 
+ 6)) {
+                                                       sx *= 2;
+                                                       sy *= 2;
+                                               }
+
+                                               if (sx > DECSIXEL_WIDTH_MAX)
+                                                       sx = DECSIXEL_WIDTH_MAX;
+                                               if (sy > DECSIXEL_HEIGHT_MAX)
+                                                       sy = DECSIXEL_HEIGHT_MAX;
+
+                                               status = image_buffer_resize(image, sx, sy);
+                                               if (status < 0)
+                                                       goto end;
+                                       }
+
+                                       if (st->color_index > image->ncolors)
+                                               image->ncolors = st->color_index;
+
+                                       if (st->pos_x + st->repeat_count > image->width)
+                                               st->repeat_count = image->width - st->pos_x;
+
+                                       if (st->repeat_count > 0 && st->pos_y - 5 < image->height) {
+                                               bits = *p - '?';
+                                               if (bits != 0) {
+                                                       sixel_vertical_mask = 0x01;
+                                                       if (st->repeat_count <= 1) {
+                                                               for (i = 0; i < 6; i++) {
+                                                                       if ((bits & sixel_vertical_mask) != 
0) {
+                                                                               pos = image->width * 
(st->pos_y + i) + st->pos_x;
+                                                                               image->data[pos] = 
st->color_index;
+                                                                               if (st->max_x < st->pos_x)
+                                                                                       st->max_x = st->pos_x;
+                                                                               if (st->max_y < (st->pos_y + 
i))
+                                                                                       st->max_y = st->pos_y 
+ i;
+                                                                       }
+                                                                       sixel_vertical_mask <<= 1;
+                                                               }
+                                                       } else {
+                                                               /* st->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 = st->pos_y + i; y < 
st->pos_y + i + n; ++y) {
+                                                                                       for (x = st->pos_x; x 
< st->pos_x + st->repeat_count; ++x)
+                                                                                               
image->data[image->width * y + x] = st->color_index;
+                                                                               }
+                                                                               if (st->max_x < (st->pos_x + 
st->repeat_count - 1))
+                                                                                       st->max_x = st->pos_x 
+ st->repeat_count - 1;
+                                                                               if (st->max_y < (st->pos_y + 
i + n - 1))
+                                                                                       st->max_y = st->pos_y 
+ i + n - 1;
+                                                                               i += (n - 1);
+                                                                               sixel_vertical_mask <<= (n - 
1);
+                                                                       }
+                                                                       sixel_vertical_mask <<= 1;
+                                                               }
+                                                       }
+                                               }
+                                       }
+                                       if (st->repeat_count > 0)
+                                               st->pos_x += st->repeat_count;
+                                       st->repeat_count = 1;
+                               }
+                               p++;
+                               break;
+                       }
+                       break;
+
+               case PS_DECGRA:
+                       /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */
+                       switch (*p) {
+                       case '\x1b':
+                               st->state = PS_ESC;
+                               p++;
+                               break;
+                       case '0':
+                       case '1':
+                       case '2':
+                       case '3':
+                       case '4':
+                       case '5':
+                       case '6':
+                       case '7':
+                       case '8':
+                       case '9':
+                               st->param = st->param * 10 + *p - '0';
+                               if (st->param > DECSIXEL_PARAMVALUE_MAX)
+                                       st->param = DECSIXEL_PARAMVALUE_MAX;
+                               p++;
+                               break;
+                       case ';':
+                               if (st->nparams < DECSIXEL_PARAMS_MAX)
+                                       st->params[st->nparams++] = st->param;
+                               st->param = 0;
+                               p++;
+                               break;
+                       default:
+                               if (st->nparams < DECSIXEL_PARAMS_MAX)
+                                       st->params[st->nparams++] = st->param;
+                               if (st->nparams > 0)
+                                       st->attributed_pad = st->params[0];
+                               if (st->nparams > 1)
+                                       st->attributed_pan = st->params[1];
+                               if (st->nparams > 2 && st->params[2] > 0)
+                                       st->attributed_ph = st->params[2];
+                               if (st->nparams > 3 && st->params[3] > 0)
+                                       st->attributed_pv = st->params[3];
+
+                               if (st->attributed_pan <= 0)
+                                       st->attributed_pan = 1;
+                               if (st->attributed_pad <= 0)
+                                       st->attributed_pad = 1;
+
+                               if (image->width < st->attributed_ph ||
+                                       image->height < st->attributed_pv) {
+                                       sx = st->attributed_ph;
+                                       if (image->width > st->attributed_ph)
+                                               sx = image->width;
+
+                                       sy = st->attributed_pv;
+                                       if (image->height > st->attributed_pv)
+                                               sy = image->height;
+
+                                       if (sx > DECSIXEL_WIDTH_MAX)
+                                               sx = DECSIXEL_WIDTH_MAX;
+                                       if (sy > DECSIXEL_HEIGHT_MAX)
+                                               sy = DECSIXEL_HEIGHT_MAX;
+
+                                       status = image_buffer_resize(image, sx, sy);
+                                       if (status < 0)
+                                               goto end;
+                               }
+                               st->state = PS_DECSIXEL;
+                               st->param = 0;
+                               st->nparams = 0;
+                       }
+                       break;
+
+               case PS_DECGRI:
+                       /* DECGRI Graphics Repeat Introducer ! Pn Ch */
+                       switch (*p) {
+                       case '\x1b':
+                               st->state = PS_ESC;
+                               p++;
+                               break;
+                       case '0':
+                       case '1':
+                       case '2':
+                       case '3':
+                       case '4':
+                       case '5':
+                       case '6':
+                       case '7':
+                       case '8':
+                       case '9':
+                               st->param = st->param * 10 + *p - '0';
+                               if (st->param > DECSIXEL_PARAMVALUE_MAX)
+                                       st->param = DECSIXEL_PARAMVALUE_MAX;
+                               p++;
+                               break;
+                       default:
+                               st->repeat_count = st->param;
+                               if (st->repeat_count == 0)
+                                       st->repeat_count = 1;
+                               st->state = PS_DECSIXEL;
+                               st->param = 0;
+                               st->nparams = 0;
+                               break;
+                       }
+                       break;
+
+               case PS_DECGCI:
+                       /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */
+                       switch (*p) {
+                       case '\x1b':
+                               st->state = PS_ESC;
+                               p++;
+                               break;
+                       case '0':
+                       case '1':
+                       case '2':
+                       case '3':
+                       case '4':
+                       case '5':
+                       case '6':
+                       case '7':
+                       case '8':
+                       case '9':
+                               st->param = st->param * 10 + *p - '0';
+                               if (st->param > DECSIXEL_PARAMVALUE_MAX)
+                                       st->param = DECSIXEL_PARAMVALUE_MAX;
+                               p++;
+                               break;
+                       case ';':
+                               if (st->nparams < DECSIXEL_PARAMS_MAX)
+                                       st->params[st->nparams++] = st->param;
+                               st->param = 0;
+                               p++;
+                               break;
+                       default:
+                               st->state = PS_DECSIXEL;
+                               if (st->nparams < DECSIXEL_PARAMS_MAX)
+                                       st->params[st->nparams++] = st->param;
+                               st->param = 0;
+
+                               if (st->nparams > 0) {
+                                       st->color_index = 1 + st->params[0];  /* offset 1(background color) 
added */
+                                       if (st->color_index < 0)
+                                               st->color_index = 0;
+                                       else if (st->color_index >= DECSIXEL_PALETTE_MAX)
+                                               st->color_index = DECSIXEL_PALETTE_MAX - 1;
+                               }
+
+                               if (st->nparams > 4) {
+                                       st->image.palette_modified = 1;
+                                       if (st->params[1] == 1) {
+                                               /* HLS */
+                                               if (st->params[2] > 360)
+                                                       st->params[2] = 360;
+                                               if (st->params[3] > 100)
+                                                       st->params[3] = 100;
+                                               if (st->params[4] > 100)
+                                                       st->params[4] = 100;
+                                               image->palette[st->color_index]
+                                                   = hls_to_rgb(st->params[2], st->params[3], st->params[4]);
+                                       } else if (st->params[1] == 2) {
+                                               /* RGB */
+                                               if (st->params[2] > 100)
+                                                       st->params[2] = 100;
+                                               if (st->params[3] > 100)
+                                                       st->params[3] = 100;
+                                               if (st->params[4] > 100)
+                                                       st->params[4] = 100;
+                                               image->palette[st->color_index]
+                                                   = SIXEL_XRGB(st->params[2], st->params[3], st->params[4]);
+                                       }
+                               }
+                               break;
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       status = (0);
+
+end:
+       return status;
+}
+
+void
+sixel_parser_deinit(sixel_state_t *st)
+{
+       sixel_image_deinit(&st->image);
+}
diff --git a/src/sixel.h b/src/sixel.h
new file mode 100644
index 00000000..8e06ea56
--- /dev/null
+++ b/src/sixel.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) Hayaki Saito
+ * originally written by kmiya@cluti (https://github.com/saitoha/sixel/blob/master/fromsixel.c)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#pragma once
+
+#define DECSIXEL_PARAMS_MAX 16
+#define DECSIXEL_PALETTE_MAX 1024
+#define DECSIXEL_PARAMVALUE_MAX 65535
+#define DECSIXEL_WIDTH_MAX 4096
+#define DECSIXEL_HEIGHT_MAX 4096
+
+typedef unsigned short sixel_color_no_t;
+typedef struct sixel_image_buffer {
+       sixel_color_no_t *data;
+       int width;
+       int height;
+       int palette[DECSIXEL_PALETTE_MAX];
+       sixel_color_no_t ncolors;
+       int palette_modified;
+       int use_private_register;
+} sixel_image_t;
+
+typedef enum parse_state {
+       PS_ESC        = 1,  /* ESC */
+       PS_DCS        = 2,  /* DCS */
+       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;
+       int pos_x;
+       int pos_y;
+       int max_x;
+       int max_y;
+       int attributed_pan;
+       int attributed_pad;
+       int attributed_ph;
+       int attributed_pv;
+       int repeat_count;
+       int color_index;
+       int bgindex;
+       int param;
+       int nparams;
+       int params[DECSIXEL_PARAMS_MAX];
+       sixel_image_t image;
+} sixel_state_t;
+
+int sixel_parser_init(sixel_state_t *st, int fgcolor, int bgcolor, int use_private_register);
+int sixel_parser_parse(sixel_state_t *st, unsigned char *p, size_t len);
+int sixel_parser_set_default_color(sixel_state_t *st);
+int sixel_parser_finalize(sixel_state_t *st, unsigned char *pixels);
+void sixel_parser_deinit(sixel_state_t *st);
diff --git a/src/vte.cc b/src/vte.cc
index 829e6630..df24f078 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -3887,6 +3887,9 @@ Terminal::process_incoming_pcterm()
         /* After processing some data, do a hyperlink GC. The multiplier is totally arbitrary, feel free to 
fine tune. */
         _vte_ring_hyperlink_maybe_gc(m_screen->row_data, bytes_processed * 8);
 
+       if (m_sixel_enabled)
+               maybe_remove_images ();
+
        _vte_debug_print (VTE_DEBUG_WORK, ")");
        _vte_debug_print (VTE_DEBUG_IO,
                           "%" G_GSIZE_FORMAT " bytes in %" G_GSIZE_FORMAT " chunks left to process.\n",
@@ -4229,6 +4232,120 @@ Terminal::feed_child_binary(std::string_view const& data)
         connect_pty_write();
 }
 
+void
+Terminal::maybe_remove_images ()
+{
+       VteRing *ring = m_screen->row_data;
+       auto image_map = ring->m_image_map;
+       vte::image::image_object *image;
+
+       auto it = image_map->begin();
+
+       /* step 1. collect images out of scroll-back area */
+       while (it != image_map->end()) {
+               /* image_map is sorted from oldest to new */
+               image = it->second;
+
+               /* break if the image is still in scrollback area */
+               if (image->get_bottom () >= (glong) ring->m_start)
+                       break;
+
+               /* otherwise, delete it */
+               if (image->is_freezed ())
+                       ring->m_image_offscreen_resource_counter -= image->resource_size ();
+               else
+                       ring->m_image_onscreen_resource_counter -= image->resource_size ();
+               image_map->erase (image->get_bottom ());
+               delete image;
+               _vte_debug_print (VTE_DEBUG_IMAGE,
+                                 "deleted, offscreen: %zu\n",
+                                 ring->m_image_offscreen_resource_counter);
+       }
+
+       /* step 2. If the resource amount of freezed images (serialized into VteBoa)
+        * exceeds the upper limit, remove images from oldest.
+        */
+       if (ring->m_image_offscreen_resource_counter > m_freezed_image_limit) {
+               _vte_debug_print (VTE_DEBUG_IMAGE,
+                                 "checked, offscreen: %zu, max: %zu\n",
+                                 ring->m_image_offscreen_resource_counter,
+                                 m_freezed_image_limit);
+               while (it != image_map->end()) {
+                       image = it->second;
+                       ++it;
+
+                       /* remove */
+                       image_map->erase (image->get_bottom ());
+                       if (image->is_freezed ())
+                               ring->m_image_offscreen_resource_counter -= image->resource_size ();
+                       else
+                               ring->m_image_onscreen_resource_counter -= image->resource_size ();
+                       _vte_debug_print (VTE_DEBUG_IMAGE,
+                                         "deleted, offscreen: %zu\n",
+                                         ring->m_image_offscreen_resource_counter);
+                       delete image;
+
+                       /* break if the resource amount becomes less than limit */
+                       if (ring->m_image_offscreen_resource_counter <= m_freezed_image_limit)
+                               break;
+               }
+       }
+
+       /* step 3. shrink image stream with calling _vte_stream_advance_tail() */
+       if (ring->m_has_streams)
+               ring->shrink_image_stream ();
+}
+
+void
+Terminal::freeze_hidden_images_before_view_area (double start_pos, double end_pos)
+{
+       VteRing *ring = m_screen->row_data;
+       auto image_map = ring->m_image_map;
+       /* for images before view area */
+       vte::grid::row_t top_of_view = (vte::grid::row_t)start_pos;
+       typedef std::remove_pointer<decltype(ring->m_image_map)>::type map_t;
+
+       /* iterate from new to old */
+       for (auto it = map_t::reverse_iterator (image_map->lower_bound (top_of_view)); it != image_map->rend 
(); ++it) {
+               vte::image::image_object *image = it->second;
+               if (image->get_bottom () + 1 < end_pos)
+                       break;
+               if (! image->is_freezed ()) {
+                       ring->m_image_onscreen_resource_counter -= image->resource_size ();
+                       image->freeze ();
+                       ring->m_image_offscreen_resource_counter += image->resource_size ();
+                       _vte_debug_print (VTE_DEBUG_IMAGE,
+                                         "freezed, onscreen: %zu, offscreen: %zu\n",
+                                         ring->m_image_onscreen_resource_counter,
+                                         ring->m_image_offscreen_resource_counter);
+               }
+       }
+}
+
+void
+Terminal::freeze_hidden_images_after_view_area (double start_pos, double end_pos)
+{
+       VteRing *ring = m_screen->row_data;
+       auto image_map = ring->m_image_map;
+       vte::grid::row_t bottom_of_view = (vte::grid::row_t)(start_pos + m_row_count);
+
+       /* for images after view area */
+       for (auto it = image_map->lower_bound (bottom_of_view); it != image_map->end (); ++it) {
+               vte::image::image_object *image = it->second;
+               if (image->get_top () < end_pos + m_row_count)
+                       break;
+               if (image->get_top () > bottom_of_view && ! image->is_freezed ()) {
+                       ring->m_image_onscreen_resource_counter -= image->resource_size ();
+                       image->freeze ();
+                       ring->m_image_offscreen_resource_counter += image->resource_size ();
+                       _vte_debug_print (VTE_DEBUG_IMAGE,
+                                         "freezed, onscreen: %zu, offscreen: %zu\n",
+                                         ring->m_image_onscreen_resource_counter,
+                                         ring->m_image_offscreen_resource_counter);
+               }
+       }
+}
+
 void
 Terminal::send(vte::parser::u8SequenceBuilder const& builder,
                          bool c1,
@@ -7389,6 +7506,14 @@ Terminal::set_cell_height_scale(double scale)
         return true;
 }
 
+bool
+Terminal::set_freezed_image_limit(gulong limit)
+{
+        m_freezed_image_limit = limit;
+
+        return true;
+}
+
 /* Read and refresh our perception of the size of the PTY. */
 void
 Terminal::refresh_size()
@@ -7552,6 +7677,14 @@ Terminal::screen_set_size(VteScreen *screen_,
                screen_->scroll_delta = new_scroll_delta;
 }
 
+bool
+Terminal::set_sixel_enabled(bool enabled)
+{
+        m_sixel_enabled = enabled;
+
+        return true;
+}
+
 void
 Terminal::set_size(long columns,
                              long rows)
@@ -7637,6 +7770,13 @@ Terminal::vadjustment_value_changed()
        if (!_vte_double_equal(dy, 0)) {
                _vte_debug_print(VTE_DEBUG_ADJ,
                            "Scrolling by %f\n", dy);
+
+                if (dy > 0.0) {
+                       freeze_hidden_images_before_view_area (adj, adj - dy);
+                } else {
+                       freeze_hidden_images_after_view_area (adj, adj - dy);
+                }
+
                 invalidate_all();
                 match_contents_clear();
                emit_text_scrolled(dy);
@@ -7716,6 +7856,10 @@ Terminal::Terminal(vte::platform::Widget* w,
         m_overline_position = 1;
         m_regex_underline_position = 1;
 
+        /* Image */
+        m_freezed_image_limit = VTE_DEFAULT_FREEZED_IMAGE_LIMIT;
+        m_sixel_enabled = TRUE;
+
         reset_default_attributes(true);
 
        /* Set up the desired palette. */
@@ -7744,6 +7888,9 @@ Terminal::Terminal(vte::platform::Widget* w,
         save_cursor(&m_normal_screen);
         save_cursor(&m_alternate_screen);
 
+       /* Initialize SIXEL color register */
+       sixel_parser_set_default_color(&m_sixel_state);
+
        /* Matching data. */
         m_match_span.clear(); // FIXMEchpe unnecessary
        match_hilite_clear(); // FIXMEchpe unnecessary
@@ -9196,6 +9343,7 @@ Terminal::widget_draw(cairo_t *cr)
         int allocated_width, allocated_height;
         int extra_area_for_cursor;
         bool text_blink_enabled_now;
+        VteRing *ring = m_screen->row_data;
         gint64 now = 0;
 
         if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect))
@@ -9223,6 +9371,32 @@ Terminal::widget_draw(cairo_t *cr)
                                  get_color(VTE_DEFAULT_BG), m_background_alpha);
         }
 
+       /* Draw SIXEL images */
+       if (m_sixel_enabled) {
+               vte::grid::row_t top_row = first_displayed_row();
+               vte::grid::row_t bottom_row = last_displayed_row();
+               auto image_map = ring->m_image_map;
+               auto it = image_map->lower_bound (top_row);
+               for (; it != image_map->end (); ++it) {
+                       vte::image::image_object *image = it->second;
+                       if (image->get_top () > bottom_row)
+                               break;
+                       if (image->is_freezed ()) {
+                               ring->m_image_offscreen_resource_counter -= image->resource_size ();
+                               image->thaw ();
+                               ring->m_image_onscreen_resource_counter += image->resource_size ();
+                               _vte_debug_print (VTE_DEBUG_IMAGE,
+                                                 "thawn, onscreen: %zu, offscreen: %zu\n",
+                                                 ring->m_image_onscreen_resource_counter,
+                                                 ring->m_image_offscreen_resource_counter);
+                       }
+                       /* Display images */
+                       int x = m_padding.left + image->get_left () * m_cell_width;
+                       int y = m_padding.top + (image->get_top () - m_screen->scroll_delta) * m_cell_height;
+                       image->paint (cr, x, y);
+               }
+       }
+
         /* Clip vertically, for the sake of smooth scrolling. We want the top and bottom paddings to be 
unused.
          * Don't clip horizontally so that antialiasing can legally overflow to the right padding. */
         cairo_save(cr);
@@ -9906,6 +10080,14 @@ Terminal::reset(bool clear_tabstops,
        m_mouse_smooth_scroll_delta = 0.;
        /* Clear modifiers. */
        m_modifiers = 0;
+       /* Reset SIXEL display mode */
+       m_sixel_display_mode = FALSE;
+       /* Reset SIXEL-scrolls-right mode */
+       m_sixel_scrolls_right = FALSE;
+       /* Reset privae color register mode */
+       m_sixel_use_private_register = FALSE;
+       /* Reset SIXEL color register */
+       sixel_parser_set_default_color(&m_sixel_state);
         /* Reset the saved cursor. */
         save_cursor(&m_normal_screen);
         save_cursor(&m_alternate_screen);
diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h
index bdede897..25cd0f0a 100644
--- a/src/vte/vteterminal.h
+++ b/src/vte/vteterminal.h
@@ -503,6 +503,24 @@ gboolean vte_terminal_write_contents_sync (VteTerminal *terminal,
                                            GCancellable *cancellable,
                                            GError **error) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1) 
_VTE_GNUC_NONNULL(2);
 
+/* Images */
+
+/* Set or get maximum storage size for offscreen freezed images */
+_VTE_PUBLIC
+void vte_terminal_set_freezed_image_limit(VteTerminal *terminal,
+                                          gulong limit) _VTE_GNUC_NONNULL(1);
+
+_VTE_PUBLIC
+gulong vte_terminal_get_freezed_image_limit(VteTerminal *terminal) _VTE_GNUC_NONNULL(1);
+
+/* Set or get whether the SIXEL graphics feature is enabled */
+_VTE_PUBLIC
+void vte_terminal_set_sixel_enabled (VteTerminal *terminal,
+                                     gboolean enabled) _VTE_GNUC_NONNULL(1);
+
+_VTE_PUBLIC
+gboolean vte_terminal_get_sixel_enabled (VteTerminal *terminal) _VTE_GNUC_NONNULL(1);
+
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(VteTerminal, g_object_unref)
 
 G_END_DECLS
diff --git a/src/vtedefines.hh b/src/vtedefines.hh
index 8aa4d9dc..8f130eac 100644
--- a/src/vtedefines.hh
+++ b/src/vtedefines.hh
@@ -88,6 +88,7 @@
 #define VTE_MAX_PROCESS_TIME           100
 #define VTE_CELL_BBOX_SLACK            1
 #define VTE_DEFAULT_UTF8_AMBIGUOUS_WIDTH 1
+#define VTE_DEFAULT_FREEZED_IMAGE_LIMIT (16 * 1024 * 1024)  /* 16 MB */
 
 #define VTE_UTF8_BPC                    (4) /* Maximum number of bytes used per UTF-8 character */
 
diff --git a/src/vtegtk.cc b/src/vtegtk.cc
index 443f9479..1ce4000b 100644
--- a/src/vtegtk.cc
+++ b/src/vtegtk.cc
@@ -1824,6 +1824,17 @@ vte_terminal_class_init(VteTerminalClass *klass)
                                      NULL,
                                      (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | 
G_PARAM_EXPLICIT_NOTIFY));
 
+        /**
+         * VteTerminal:freezed-image-limit:
+         *
+         * This property indicates allowed max storage size for offscreen freezed images
+         */
+        pspecs[PROP_FREEZED_IMAGE_LIMIT] =
+                g_param_spec_ulong ("freezed-image-limit", NULL, NULL,
+                                    0, G_MAXULONG, VTE_DEFAULT_FREEZED_IMAGE_LIMIT,
+                                    (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | 
G_PARAM_EXPLICIT_NOTIFY));
+
+
         /**
          * VteTerminal:input-enabled:
          *
@@ -1910,6 +1921,17 @@ vte_terminal_class_init(VteTerminalClass *klass)
                                       TRUE,
                                       (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | 
G_PARAM_EXPLICIT_NOTIFY));
 
+        /**
+         * VteTerminal:sixel-enabled:
+         *
+         * Controls whether the SIXEL graphics feature is enabled.
+         */
+        pspecs[PROP_SIXEL_ENABLED] =
+                g_param_spec_boolean ("sixel-enabled", NULL, NULL,
+                                      TRUE,
+                                      (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | 
G_PARAM_EXPLICIT_NOTIFY));
+
+
         /**
          * VteTerminal:text-blink-mode:
          *
@@ -5663,3 +5685,66 @@ catch (...)
 
 } // namespace glib
 } // namespace vte
+
+/**
+ * vte_terminal_set_freezed_image_limit:
+ * @terminal: a #VteTerminal
+ * @limit: 0 to G_MAXINT
+ *
+ * Set allowed max storage size for offscreen freezed images
+ */
+void
+vte_terminal_set_freezed_image_limit(VteTerminal *terminal, gulong limit)
+{
+        g_return_if_fail(VTE_IS_TERMINAL(terminal));
+
+        if (IMPL(terminal)->set_freezed_image_limit(limit))
+                g_object_notify_by_pspec(G_OBJECT(terminal), pspecs[PROP_FREEZED_IMAGE_LIMIT]);
+}
+
+/**
+ * vte_terminal_get_freezed_image_limit:
+ * @terminal: a #VteTerminal
+ *
+ * Get allowed max storage size for offscreen freezed images
+ */
+gulong
+vte_terminal_get_freezed_image_limit(VteTerminal *terminal)
+{
+        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), 0);
+
+        return IMPL(terminal)->m_freezed_image_limit;
+}
+
+/**
+ * vte_terminal_set_sixel_enabled:
+ * @terminal: a #VteTerminal
+ * @enabled: %TRUE enables SIXEL graphics feature.
+ *
+ * Set whether to enable SIXEL graphics feature
+ */
+void
+vte_terminal_set_sixel_enabled(VteTerminal *terminal, gboolean enabled)
+{
+        g_return_if_fail(VTE_IS_TERMINAL(terminal));
+
+        if (IMPL(terminal)->set_sixel_enabled(enabled))
+                g_object_notify_by_pspec(G_OBJECT(terminal), pspecs[PROP_SIXEL_ENABLED]);
+}
+
+/**
+ * vte_terminal_set_sixel_enabled:
+ * @terminal: a #VteTerminal
+ *
+ * Get whether the SIXEL graphics feature is enabled.
+ *
+ * Returns: %TRUE if SIXEL graphics feature is enabled.
+ */
+
+gboolean
+vte_terminal_get_sixel_enabled(VteTerminal *terminal)
+{
+        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
+
+        return IMPL(terminal)->m_sixel_enabled;
+}
diff --git a/src/vtegtk.hh b/src/vtegtk.hh
index ae9fb08c..2ebe22bc 100644
--- a/src/vtegtk.hh
+++ b/src/vtegtk.hh
@@ -81,6 +81,7 @@ enum {
         PROP_ENCODING,
         PROP_FONT_DESC,
         PROP_FONT_SCALE,
+        PROP_FREEZED_IMAGE_LIMIT,
         PROP_HYPERLINK_HOVER_URI,
         PROP_ICON_TITLE,
         PROP_INPUT_ENABLED,
@@ -93,6 +94,7 @@ enum {
         PROP_TEXT_BLINK_MODE,
         PROP_WINDOW_TITLE,
         PROP_WORD_CHAR_EXCEPTIONS,
+        PROP_SIXEL_ENABLED,
         LAST_PROP,
 
         /* override properties */
diff --git a/src/vteimage.cc b/src/vteimage.cc
new file mode 100644
index 00000000..628d01d4
--- /dev/null
+++ b/src/vteimage.cc
@@ -0,0 +1,275 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <stdio.h>
+#include "vteimage.h"
+#include "vteinternal.hh"
+
+namespace vte {
+
+namespace image {
+
+/* image_object implementation */
+image_object::image_object (cairo_surface_t *surface, gint pixelwidth, gint pixelheight, gint col, gint row, 
gint w, gint h, _VteStream *stream)
+{
+       m_pixelwidth = pixelwidth;
+       m_pixelheight = pixelheight;
+       m_left = col;
+       m_top = row;
+       m_width = w;
+       m_height = h;
+       m_surface = surface;
+       m_stream = stream;
+
+       g_object_ref (m_stream);
+}
+
+image_object::~image_object ()
+{
+       if (m_surface)
+               cairo_surface_destroy (m_surface);
+       g_object_unref (m_stream);
+}
+
+glong
+image_object::get_left () const
+{
+       return (glong)m_left;
+}
+
+glong
+image_object::get_top () const
+{
+       return (glong)m_top;
+}
+
+glong
+image_object::get_bottom () const
+{
+       return (glong)(m_top + m_height - 1);
+}
+
+gulong
+image_object::get_stream_position () const
+{
+       return m_position;
+}
+
+/* Indicate whether the image is serialized to the stream */
+bool
+image_object::is_freezed () const
+{
+       return (m_surface == NULL);
+}
+
+/* Test whether this image includes given image */
+bool
+image_object::includes (const image_object *other) const
+{
+       g_assert_true (other != NULL);
+
+       return other->m_left >= m_left &&
+              other->m_top >= m_top &&
+              other->m_left + other->m_width <= m_left + m_width &&
+              other->m_top + other->m_height <= m_top + m_height;
+}
+
+size_t
+image_object::resource_size () const
+{
+       size_t result_size;
+
+       if (is_freezed ()) {
+               /* If freezed, return the size sent to VteBoa.
+                 * In reality, it may be more compressed on the real storage.
+                 */
+               result_size = m_nwrite;
+       } else {
+               /* If not freezed, return the pixel buffer size
+                 * width x height x 4
+                 */
+               result_size = m_pixelwidth * m_pixelheight * 4;
+       }
+
+       return result_size;
+}
+
+/* Deserialize the cairo image from the temporary file */
+bool
+image_object::thaw ()
+{
+       if (m_surface)
+               return true;
+       if (m_position < _vte_stream_tail (m_stream))
+               return false;
+
+       m_nread = 0;
+       m_surface = cairo_image_surface_create_from_png_stream ((cairo_read_func_t)read_callback, this);
+       if (! m_surface)
+               return false;
+
+       return true;
+}
+
+/* Serialize the image for saving RAM */
+void
+image_object::freeze ()
+{
+       cairo_status_t status;
+       double x_scale, y_scale;
+
+       if (! m_surface)
+               return;
+
+       m_position = _vte_stream_head (m_stream);
+       m_nwrite = 0;
+
+       cairo_surface_get_device_scale (m_surface, &x_scale, &y_scale);
+       if (x_scale != 1.0 || y_scale != 1.0) {
+               /* If device scale exceeds 1.0, large size of PNG image created with 
cairo_surface_write_to_png_stream()
+                 * So we need to convert m_surface into an image surface with resizing it.
+                 */
+               cairo_surface_t *image_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 
m_pixelwidth, m_pixelheight);
+               cairo_t *cr = cairo_create (image_surface);
+               paint (cr, 0, 0);
+               cairo_destroy (cr);
+               cairo_surface_destroy (m_surface);
+               m_surface = image_surface;
+       }
+       status = cairo_surface_write_to_png_stream (m_surface, (cairo_write_func_t)write_callback, this);
+       if (status == CAIRO_STATUS_SUCCESS) {
+               cairo_surface_destroy (m_surface);
+               m_surface = NULL;
+       }
+}
+
+/* Merge another image into this image */
+bool
+image_object::combine (image_object *other, gulong char_width, gulong char_height)
+{
+       cairo_t *cr;
+
+       g_assert_true (other != NULL);
+
+       gulong offsetx = (other->m_left - m_left) * char_width;
+       gulong offsety = (other->m_top - m_top) * char_height;
+
+       if (is_freezed ())
+               if (! thaw ())
+                       return false;
+
+       if (other->is_freezed ())
+               if (! other->thaw ())
+                       return false;
+
+       cr = cairo_create (m_surface);
+       cairo_rectangle (cr, offsetx, offsety, m_pixelwidth, m_pixelheight);
+       cairo_clip (cr);
+       cairo_set_source_surface (cr, other->m_surface, offsetx, offsety);
+       cairo_paint (cr);
+       cairo_destroy (cr);
+
+       return true;
+}
+
+bool
+image_object::unite (image_object *other, gulong char_width, gulong char_height)
+{
+       if (is_freezed ())
+               if (! thaw ())
+                       return false;
+
+       gint new_left = std::min (m_left, other->m_left);
+       gint new_top = std::min (m_top, other->m_top);
+       gint new_width = std::max (m_left + m_width, other->m_left + other->m_width) - new_left;
+       gint new_height = std::max (m_top + m_height, other->m_top + other->m_width) - new_top;
+       gint pixelwidth = new_width * char_width;
+       gint pixelheight = new_height * char_height;
+       gint offsetx = (m_left - new_left) * char_width;
+       gint offsety = (m_top - new_top) * char_height;
+
+       cairo_surface_t * new_surface = cairo_surface_create_similar (other->m_surface, 
CAIRO_CONTENT_COLOR_ALPHA, m_pixelwidth, m_pixelheight);
+       cairo_t *cr = cairo_create (new_surface);
+       cairo_rectangle (cr, offsetx, offsety, m_pixelwidth, m_pixelheight);
+       cairo_clip (cr);
+       cairo_set_source_surface (cr, m_surface, offsetx, offsety);
+       cairo_paint (cr);
+       cairo_destroy (cr);
+
+       cairo_surface_destroy (m_surface);
+
+       m_left = new_left;
+       m_top = new_top;
+       m_width = new_width;
+       m_height = new_height;
+       m_pixelwidth = pixelwidth;
+       m_pixelheight = pixelheight;
+       m_surface = new_surface;
+
+       return combine (other, char_width, char_height);
+}
+
+/* Paint the image into given cairo rendering context */
+bool
+image_object::paint (cairo_t *cr, gint offsetx, gint offsety)
+{
+       if (is_freezed ())
+               if (! thaw ())
+                       return false;
+
+       cairo_save (cr);
+       cairo_rectangle (cr, offsetx, offsety, m_pixelwidth, m_pixelheight);
+       cairo_clip (cr);
+       cairo_set_source_surface (cr, m_surface, offsetx, offsety);
+       cairo_paint (cr);
+       cairo_restore (cr);
+
+       return true;
+}
+
+/* callback routines for stream I/O */
+
+cairo_status_t
+image_object::read_callback (void *closure, char *data, unsigned int length)
+{
+       image_object *image = (image_object *)closure;
+
+       g_assert_true (image != NULL);
+
+       _vte_stream_read (image->m_stream, image->m_position + image->m_nread, data, length);
+       image->m_nread += length;
+
+       return CAIRO_STATUS_SUCCESS;
+}
+
+cairo_status_t
+image_object::write_callback (void *closure, const char *data, unsigned int length)
+{
+       image_object *image = (image_object *)closure;
+
+       g_assert_true (image != NULL);
+
+       _vte_stream_append (image->m_stream, data, length);
+       image->m_nwrite += length;
+
+       return CAIRO_STATUS_SUCCESS;
+}
+
+} // namespace image
+
+} // namespace vte
diff --git a/src/vteimage.h b/src/vteimage.h
new file mode 100644
index 00000000..76d4af20
--- /dev/null
+++ b/src/vteimage.h
@@ -0,0 +1,61 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#pragma once
+
+#include <pango/pangocairo.h>
+#include "vtestream.h"
+
+namespace vte {
+
+namespace image {
+
+struct image_object {
+private:
+       gint m_left;                /* left position in cell unit at the vte virtual screen */
+       gint m_width;               /* width in cell unit */
+       gint m_top;                 /* top position in cell unit at the vte virtual screen */
+       gint m_height;              /* height in cell unit */
+       VteStream *m_stream;        /* NULL if it's serialized */
+       gint m_pixelwidth;          /* image width in pixels */
+       gint m_pixelheight;         /* image hieght in pixels */
+       gulong m_position;          /* indicates the position at the stream if it's serialized */
+       size_t m_nread;             /* private use: for read callback */
+       size_t m_nwrite;            /* private use: for write callback */
+       cairo_surface_t *m_surface; /* internal cairo image */
+public:
+       explicit image_object (cairo_surface_t *surface, gint pixelwidth, gint pixelheight, gint col, gint 
row, gint w, gint h, _VteStream *stream);
+       ~image_object ();
+       glong get_left () const;
+       glong get_top () const;
+       glong get_bottom () const;
+       gulong get_stream_position () const;
+       bool is_freezed () const;
+       bool includes (const image_object *rhs) const;
+       size_t resource_size () const;
+       void freeze ();
+       bool thaw ();
+       bool combine (image_object *rhs, gulong char_width, gulong char_height);
+       bool unite (image_object *rhs, gulong char_width, gulong char_height);
+       bool paint (cairo_t *cr, gint offsetx, gint offsety);
+public:
+       static cairo_status_t read_callback (void *closure, char *data, unsigned int length);
+       static cairo_status_t write_callback (void *closure, const char *data, unsigned int length);
+};
+
+} // namespace image
+
+} // namespace vte
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index bc75888e..6fc4b923 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -48,6 +48,7 @@
 
 #include "vtepcre2.h"
 #include "vteregexinternal.hh"
+#include "sixel.h"
 
 #include "chunk.hh"
 #include "pty.hh"
@@ -752,6 +753,14 @@ public:
                                                             this),
                                                   "mouse-autoscroll-timer"};
 
+        /* SIXEL feature */
+        gboolean m_sixel_display_mode;
+        gboolean m_sixel_scrolls_right;
+        gboolean m_sixel_use_private_register;
+        sixel_state_t m_sixel_state;
+        gulong m_freezed_image_limit;
+        gboolean m_sixel_enabled;
+
        /* State variables for handling match checks. */
         int m_match_regex_next_tag{0};
         auto regex_match_next_tag() noexcept { return m_match_regex_next_tag++; }
@@ -1503,12 +1512,14 @@ public:
                           GError** error);
         bool set_font_desc(PangoFontDescription const* desc);
         bool set_font_scale(double scale);
+        bool set_freezed_image_limit(gulong limit);
         bool set_input_enabled(bool enabled);
         bool set_mouse_autohide(bool autohide);
         bool set_rewrap_on_resize(bool rewrap);
         bool set_scrollback_lines(long lines);
         bool set_scroll_on_keystroke(bool scroll);
         bool set_scroll_on_output(bool scroll);
+        bool set_sixel_enabled(bool enabled);
         bool set_word_char_exceptions(std::optional<std::string_view> stropt);
         void set_clear_background(bool setting);
 
@@ -1585,6 +1596,7 @@ public:
                                    vte::grid::row_t end_row,
                                    vte::grid::column_t end_col);
 
+        void seq_load_sixel(char const* p);
         void subscribe_accessible_events();
         void select_text(vte::grid::column_t start_col,
                          vte::grid::row_t start_row,
@@ -1668,6 +1680,11 @@ public:
 #include "parser-cmd.hh"
 #undef _VTE_CMD
 #undef _VTE_NOP
+
+private:
+        void freeze_hidden_images_before_view_area(double start_pos, double end_pos);
+        void freeze_hidden_images_after_view_area(double start_pos, double end_pos);
+        void maybe_remove_images();
 };
 
 } // namespace terminal
diff --git a/src/vteseq.cc b/src/vteseq.cc
index ba9a2df6..4b1d46a1 100644
--- a/src/vteseq.cc
+++ b/src/vteseq.cc
@@ -35,6 +35,7 @@
 #include "vtegtk.hh"
 #include "caps.hh"
 #include "debug.h"
+#include "sixel.h"
 
 #define BEL_C0 "\007"
 #define ST_C0 _VTE_CAP_ST
@@ -589,6 +590,20 @@ Terminal::set_mode_private(int mode,
                 maybe_apply_bidi_attributes(VTE_BIDI_FLAG_AUTO);
                 break;
 
+#if 0
+        /* FIXME-hpj */
+
+        case vte::terminal::modes::Private::eSIXEL_DISPLAY_MODE:
+                /* 80: SIXEL display mode(DECSDM). */
+                break;
+        case vte::terminal::modes::Private::eSIXEL_USE_PRIVATE_REGISTER:
+                /* 1070: private color register mode. */
+                break;
+        case vtw::terminal::modes::Private::eSIXEL_SCROLLS_RIGHT:
+                /* 8452: SIXEL-scrolls-right mode. */
+                break;
+#endif
+
         default:
                 break;
         }
@@ -2376,7 +2391,7 @@ Terminal::DA1(vte::parser::Sequence const& seq)
         if (seq.collect1(0, 0) != 0)
                 return;
 
-        reply(seq, VTE_REPLY_DECDA1R, {65, 1, 9});
+        reply(seq, VTE_REPLY_DECDA1R, {65, 1, 9, 4});
 }
 
 void
@@ -3060,6 +3075,153 @@ Terminal::DECIC(vte::parser::Sequence const& seq)
          */
 }
 
+#if 0
+static void
+vte_sequence_handler_device_control_string (VteTerminalPrivate *that, GValueArray *params)
+{
+       GValue *value;
+       char *dcs = NULL;
+       char *p;
+       glong cmd = 0;
+       gint param;
+       size_t nparams = 0;
+       gint dcsparams[DECSIXEL_PARAMS_MAX];
+
+       value = g_value_array_get_nth(params, 0);
+       if (!value)
+               return;
+       if (G_VALUE_HOLDS_STRING(value)) {
+               /* Copy the string into the buffer. */
+               dcs = g_value_dup_string(value);
+       }
+       else if (G_VALUE_HOLDS_POINTER(value)) {
+               dcs = that->ucs4_to_utf8((const guchar *)g_value_get_pointer (value));
+       }
+       if (! dcs)
+               return;
+
+       for (p = dcs; p; ++p) {
+               switch (*p) {
+               case ' ' ... '/':
+                       cmd = cmd << 8 | *p;
+                       if (cmd > (1 << 24))
+                               goto end;
+                       break;
+               case '0' ... '9':
+                       if (param < 0)
+                               param = 0;
+                       param = param * 10 + *p - '0';
+                       break;
+               case ';':
+                       if (param < 0)
+                               param = 0;
+                       if (nparams < sizeof(dcsparams) / sizeof(dcsparams[0]))
+                               dcsparams[nparams++] = param;
+                       param = 0;
+                       break;
+               case '@' ... '~':
+                       cmd = cmd << 8 | *p;
+                       goto dispatch;
+               default:
+                   goto end;
+               }
+       }
+
+dispatch:
+       switch (cmd) {
+       case 'q':
+               if (that->m_sixel_enabled)
+                       that->seq_load_sixel(dcs);
+               break;
+       default:
+               break;
+       }
+
+end:
+       g_free(dcs);
+}
+#endif
+
+void
+Terminal::seq_load_sixel(char const* dcs)
+{
+       unsigned char *pixels = NULL;
+       auto fg = get_color(VTE_DEFAULT_FG);
+       auto bg = get_color(VTE_DEFAULT_BG);
+       int nfg = fg->red >> 8 | fg->green >> 8 << 8 | fg->blue >> 8 << 16;
+       int nbg = bg->red >> 8 | bg->green >> 8 << 8 | bg->blue >> 8 << 16;
+       glong left, top, width, height;
+       glong pixelwidth, pixelheight;
+       glong i;
+       cairo_surface_t *image_surface, *surface;
+       cairo_t *cr;
+
+       /* Parse images */
+       if (sixel_parser_init(&m_sixel_state, nfg, nbg, m_sixel_use_private_register) < 0) {
+               sixel_parser_deinit(&m_sixel_state);
+               return;
+       }
+       if (sixel_parser_parse(&m_sixel_state, (unsigned char *)dcs, strlen(dcs)) < 0) {
+               sixel_parser_deinit(&m_sixel_state);
+               return;
+       }
+       pixels = (unsigned char *)g_malloc(m_sixel_state.image.width * m_sixel_state.image.height * 4);
+       if (! pixels) {
+               sixel_parser_deinit(&m_sixel_state);
+               return;
+       }
+       if (sixel_parser_finalize(&m_sixel_state, pixels) < 0) {
+               sixel_parser_deinit(&m_sixel_state);
+               return;
+       }
+       sixel_parser_deinit(&m_sixel_state);
+
+       if (m_sixel_display_mode)
+               home_cursor();
+
+       /* Append new image to VteRing */
+       left = m_screen->cursor.col;
+       top = m_screen->cursor.row;
+       width = (m_sixel_state.image.width + m_cell_width - 1) / m_cell_width;
+       height = (m_sixel_state.image.height + m_cell_height - 1) / m_cell_height;
+       pixelwidth = m_sixel_state.image.width;
+       pixelheight = m_sixel_state.image.height;
+
+       /* create image surface (in-memory, device-independant) */
+       image_surface = cairo_image_surface_create_for_data (pixels, CAIRO_FORMAT_ARGB32, pixelwidth, 
pixelheight, pixelwidth * 4);
+       g_assert (image_surface);
+
+       /* create device-dependant surface compatible with m_widget */
+       surface = gdk_window_create_similar_surface (gtk_widget_get_window (m_widget), 
CAIRO_CONTENT_COLOR_ALPHA, pixelwidth, pixelheight);
+       g_assert (surface);
+
+       /* copy image surface to a device-compatible surface */
+       cr = cairo_create (surface);
+       cairo_set_source_surface (cr, image_surface, 0, 0);
+       cairo_paint (cr);
+       cairo_destroy (cr);
+       cairo_surface_destroy (image_surface);
+       free (pixels);
+
+       /* create image object */
+       m_screen->row_data->append_image (surface, pixelwidth, pixelheight, left, top, width, height);
+
+       /* Erase characters on the image */
+       for (i = 0; i < height; ++i) {
+               erase_characters(width);
+               if (i == height - 1) {
+                       if (m_sixel_scrolls_right)
+                               move_cursor_forward(width);
+                       else
+                               cursor_down(true);
+               } else {
+                       cursor_down(true);
+               }
+       }
+       if (m_sixel_display_mode)
+               home_cursor();
+}
+
 void
 Terminal::DECINVM(vte::parser::Sequence const& seq)
 {
@@ -8766,5 +8928,59 @@ Terminal::XTERM_WM(vte::parser::Sequence const& seq)
         }
 }
 
+#if 0
+/* FIXME-hpj */
+
+/* graphics attributes */
+static void
+vte_sequence_handler_graphics_attributes(VteTerminalPrivate *that, GValueArray *params)
+{
+       if (params == NULL || params->n_values != 3) {
+               return;
+       }
+       GValue* value = g_value_array_get_nth(params, 0);
+       if (!G_VALUE_HOLDS_LONG(value)) {
+               return;
+       }
+       auto param = g_value_get_long(value);
+
+       char buf[128];
+       long arg1, arg2;
+       arg1 = arg2 = -1;
+       if (params->n_values > 1) {
+               value = g_value_array_get_nth(params, 1);
+               if (G_VALUE_HOLDS_LONG(value)) {
+                       arg1 = g_value_get_long(value);
+               }
+       }
+       if (params->n_values > 2) {
+               value = g_value_array_get_nth(params, 2);
+               if (G_VALUE_HOLDS_LONG(value)) {
+                       arg2 = g_value_get_long(value);
+               }
+       }
+
+       switch (arg1) {
+       case 1:
+               switch (arg2) {
+               case 1:
+                       that->feed_child(_VTE_CAP_CSI "?1;0;256S", -1);
+                       break;
+               case 2:
+                       that->feed_child(_VTE_CAP_CSI "?1;0;256S", -1);
+                       break;
+               case 3:
+                       that->feed_child(_VTE_CAP_CSI "?1;3;0S", -1);
+                       break;
+               default:
+                       break;
+               }
+       default:
+                g_snprintf(buf, sizeof(buf), _VTE_CAP_CSI "?%ld;1;0S", arg1);
+               break;
+       }
+}
+#endif
+
 } // namespace terminal
 } // namespace vte


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]