1 ;;; sixel.el --- minor mode for processing sixel graphics
3 ;; Copyright (C) 2019 Tim Vaughan
5 ;; Author: Tim Vaughan <plugd@thelambdalab.xyz>
6 ;; Created: 19 May 2019
9 ;; Homepage: gopher://thelambdalab.xyz/1/projects/sixel
10 ;; Package-Requires: ((emacs "26"))
14 ;; This file is not part of GNU Emacs.
16 ;; This program is free software: you can redistribute it and/or modify
17 ;; it under the terms of the GNU General Public License as published by
18 ;; the Free Software Foundation, either version 3 of the License, or
19 ;; (at your option) any later version.
21 ;; This program is distributed in the hope that it will be useful,
22 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
23 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 ;; GNU General Public License for more details.
26 ;; You should have received a copy of the GNU General Public License
27 ;; along with this file. If not, see <http://www.gnu.org/licenses/>.
33 "#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0"
38 (defun sixel-get-params (string)
39 "Retrieve the sixel parameters."
40 (car (split-string (substring string 2) "q")))
42 (defun sixel-get-data (string)
43 "Retrieve data string."
44 (substring string (1+ (string-match "q" string))))
46 (defun sixel-tag-bits (sixel n tag)
47 "Set bits of SIXEL corresponding to N with to the value TAG."
53 (defun sixel-tag-sixel-in-row (row index char tag)
54 "Tag the bits of the sixel at INDEX in the list identified by
55 the variable ROW-VARIABLE corresponding to input character CHAR
57 (while (not (< index (length row)))
58 (push (make-vector 6 nil) row))
59 (let ((sixel (elt row (- (length row) 1 index))))
60 (sixel-tag-bits sixel (- char 63) tag))
63 (defun sixel-process-data (string)
64 "Convert STRING into a list of lists representing individual sixels.
65 Returns a sixel image object."
68 (goto-char (point-min))
71 current-colour colour-map
76 ((looking-at "#\\([0-9]+\\);\\([0-9]+\\);\\([0-9]+\\);\\([0-9]+\\);\\([0-9]+\\)")
77 (let ((tag (format "%02x" (string-to-number (match-string 1))))
78 (mode (match-string 2))
79 (r (string-to-number (match-string 3)))
80 (g (string-to-number (match-string 4)))
81 (b (string-to-number (match-string 5))))
82 (push (list tag r g b) colour-map)))
83 ;; Set current colour:
84 ((looking-at "#\\([0-9]+\\)")
85 (let ((tag (format "%02x" (string-to-number (match-string 1)))))
86 (setq current-colour tag)))
92 (push (reverse this-row) rows)
95 ;; RLE sixel char sequence:
96 ((looking-at "!\\([0-9]+\\)\\([?-~]\\)")
97 (let ((repeat-count (string-to-number (match-string 1)))
98 (char (elt (match-string 2) 0)))
99 (dotimes (i repeat-count)
101 (sixel-tag-sixel-in-row this-row idx-out char current-colour))
102 (setq idx-out (1+ idx-out)))))
104 ((looking-at "\\([?-~]\\)") ; Sixel char
105 (let ((char (elt (match-string 1) 0)))
107 (sixel-tag-sixel-in-row this-row idx-out char current-colour))
108 (setq idx-out (1+ idx-out))))
109 ;; Termination sequence:
110 ((looking-at "
\e\\\\")
113 ((looking-at "[[:ascii:]]")))
114 (goto-char (match-end 0)))
115 (push (reverse this-row) rows)
119 (defun sixel-pad-rows (sixel-image)
120 "Pad out contents of rows in SIXEL-IMAGE so that all rows are the same length."
121 (let ((width (car (sixel-image-dims sixel-image)))
122 (rows (cdr sixel-image)))
123 (dotimes (row-idx (length rows))
124 (let* ((row-cdr (nthcdr row-idx rows))
125 (row-width (length (car row-cdr))))
126 (if (< row-width width)
127 (setcar row-cdr (append (car row-cdr)
128 (make-list (- width row-width)
129 [nil nil nil nil nil nil])))))))
132 (defun sixel-image-colour-map (sixel-image)
133 "Extract colour map from SIXEL-IMAGE."
136 (defun sixel-image-sixels (sixel-image)
137 "Extract sixels from SIXEL-IMAGE."
140 (defun sixel-image-dims (sixel-image)
141 "Compute image width from SIXEL-IMAGE. Return pair (width . height)."
142 (let ((sixels (sixel-image-sixels sixel-image)))
144 (apply #'max (mapcar (lambda (row) (length row)) sixels))
145 (* 6 (length sixels)))))
147 (defun sixel-image-to-xpm-values (sixel-image)
148 "Produce parameter values component of XPM representation of SIXEL-IMAGE."
149 (let* ((dims (sixel-image-dims sixel-image))
150 (colour-map (sixel-image-colour-map sixel-image))
151 (n-colours (1+ (length colour-map))))
153 (number-to-string (car dims)) " "
154 (number-to-string (cdr dims)) " "
155 (number-to-string n-colours) " 2\"")))
157 (defun sixel-image-to-xpm-colours (sixel-image)
158 "Produce colour definitions component of XPM representation of SIXEL-IMAGE."
159 (let ((colour-map (sixel-image-colour-map sixel-image)))
162 (mapcar (lambda (colour)
167 (format "%02x%02x%02x"
168 (/ (* 255 (elt colour 1)) 100)
169 (/ (* 255 (elt colour 2)) 100)
170 (/ (* 255 (elt colour 3)) 100))
175 "\"-- c #000000\"")))
177 (defun sixel-image-to-xpm-pixels (sixel-image)
178 "Produce pixels component of XPM representation of SIXEL-IMAGE."
182 (mapcar (lambda (sixel-row)
186 (mapcar (lambda (sixel)
187 (let ((pixel (elt sixel i)))
192 (number-sequence 0 5))
194 (sixel-image-sixels sixel-image))
198 (defun sixel-to-xpm (string)
199 "Return an XPM image representation of the SIXEL graphic encoded in STRING."
200 (let* ((param-string (sixel-get-params string))
201 (data-string (sixel-get-data string))
202 (sixel-image (sixel-pad-rows (sixel-process-data data-string))))
203 (if (string-prefix-p "
\eP" string)
206 "static char * pixmap[] = {"
207 (sixel-image-to-xpm-values sixel-image) ",\n"
208 (sixel-image-to-xpm-colours sixel-image) ",\n"
209 (sixel-image-to-xpm-pixels sixel-image) "};")
210 (error "Incorrecly formatted sixel string"))))
212 (defun sixel-render-images-in-buffer ()
213 "Find and render any sixel images in the current buffer."
216 (goto-char (point-min))
217 (while (re-search-forward "
\eP[[:ascii:]]*
\e\\\\" nil t)
218 (let ((sixel-string (match-string 0))
219 (inhibit-read-only t))
220 (delete-region (match-beginning 0)
223 (create-image (sixel-to-xpm sixel-string) 'xpm t))
227 "Render sixel images."
230 (define-minor-mode sixel-mode
231 "A minor mode which renders sixel graphics." nil "sixel" nil
232 (add-hook 'after-change-functions
233 (lambda (start end size)
234 (sixel-render-images-in-buffer)
235 (message "Render complete."))
238 ;;; sixel.el ends here