8d8429b8dd2c63cacacc689c50611c6f6db19d04
2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 package jexer
.tterminal
;
31 import java
.awt
.Color
;
32 import java
.awt
.Graphics2D
;
33 import java
.awt
.image
.BufferedImage
;
34 import java
.util
.ArrayList
;
35 import java
.util
.HashMap
;
38 * Sixel parses a buffer of sixel image data into a BufferedImage.
42 // ------------------------------------------------------------------------
43 // Constants --------------------------------------------------------------
44 // ------------------------------------------------------------------------
47 * Parser character scan states.
49 private enum ScanState
{
58 // ------------------------------------------------------------------------
59 // Variables --------------------------------------------------------------
60 // ------------------------------------------------------------------------
63 * If true, enable debug messages.
65 private static boolean DEBUG
= true;
68 * Number of pixels to increment when we need more horizontal room.
70 private static int WIDTH_INCREASE
= 400;
73 * Number of pixels to increment when we need more vertical room.
75 private static int HEIGHT_INCREASE
= 400;
78 * Current scanning state.
80 private ScanState scanState
= ScanState
.GROUND
;
83 * Parameter characters being collected.
85 private ArrayList
<Integer
> colorParams
;
88 * The sixel palette colors specified.
90 private HashMap
<Integer
, Color
> palette
;
93 * The buffer to parse.
95 private String buffer
;
98 * The image being drawn to.
100 private BufferedImage image
;
103 * The real width of image.
105 private int width
= 0;
108 * The real height of image.
110 private int height
= 0;
115 private int repeatCount
= -1;
118 * The current drawing x position.
123 * The current drawing color.
125 private Color color
= Color
.BLACK
;
127 // ------------------------------------------------------------------------
128 // Constructors -----------------------------------------------------------
129 // ------------------------------------------------------------------------
132 * Public constructor.
134 * @param buffer the sixel data to parse
136 public Sixel(final String buffer
) {
137 this.buffer
= buffer
;
138 colorParams
= new ArrayList
<Integer
>();
139 palette
= new HashMap
<Integer
, Color
>();
140 image
= new BufferedImage(200, 100, BufferedImage
.TYPE_INT_ARGB
);
141 for (int i
= 0; i
< buffer
.length(); i
++) {
142 consume(buffer
.charAt(i
));
146 // ------------------------------------------------------------------------
147 // Sixel ------------------------------------------------------------------
148 // ------------------------------------------------------------------------
153 * @return the sixel data as an image.
155 public BufferedImage
getImage() {
156 if ((width
> 0) && (height
> 0)) {
157 return image
.getSubimage(0, 0, width
, height
);
163 * Resize image to a new size.
165 * @param newWidth new width of image
166 * @param newHeight new height of image
168 private void resizeImage(final int newWidth
, final int newHeight
) {
169 BufferedImage newImage
= new BufferedImage(newWidth
, newHeight
,
170 BufferedImage
.TYPE_INT_ARGB
);
172 Graphics2D gr
= newImage
.createGraphics();
173 gr
.drawImage(image
, 0, 0, image
.getWidth(), image
.getHeight(), null);
179 * Clear the parameters and flags.
181 private void toGround() {
183 scanState
= ScanState
.GROUND
;
188 * Save a byte into the color parameters buffer.
190 * @param ch byte to save
192 private void param(final byte ch
) {
193 if (colorParams
.size() == 0) {
194 colorParams
.add(Integer
.valueOf(0));
196 Integer n
= colorParams
.get(colorParams
.size() - 1);
197 if ((ch
>= '0') && (ch
<= '9')) {
200 colorParams
.set(colorParams
.size() - 1, n
);
203 if ((ch
== ';') && (colorParams
.size() < 16)) {
204 colorParams
.add(Integer
.valueOf(0));
209 * Get a color parameter value, with a default.
211 * @param position parameter index. 0 is the first parameter.
212 * @param defaultValue value to use if colorParams[position] doesn't exist
213 * @return parameter value
215 private int getColorParam(final int position
, final int defaultValue
) {
216 if (colorParams
.size() < position
+ 1) {
219 return colorParams
.get(position
).intValue();
223 * Get a color parameter value, clamped to within min/max.
225 * @param position parameter index. 0 is the first parameter.
226 * @param defaultValue value to use if colorParams[position] doesn't exist
227 * @param minValue minimum value inclusive
228 * @param maxValue maximum value inclusive
229 * @return parameter value
231 private int getColorParam(final int position
, final int defaultValue
,
232 final int minValue
, final int maxValue
) {
234 assert (minValue
<= maxValue
);
235 int value
= getColorParam(position
, defaultValue
);
236 if (value
< minValue
) {
239 if (value
> maxValue
) {
246 * Add sixel data to the image.
248 * @param ch the character of sixel data
250 private void addSixel(final char ch
) {
251 int n
= ((int) ch
- 63);
252 int rgb
= color
.getRGB();
253 int rep
= (repeatCount
== -1 ?
1 : repeatCount
);
256 System
.err
.println("addSixel() rep " + rep
+ " char " +
257 Integer
.toHexString(n
) + " color " + color
);
260 if (x
+ rep
> image
.getWidth()) {
261 // Resize the image, give us another max(rep, WIDTH_INCREASE)
262 // pixels of horizontal length.
263 resizeImage(image
.getWidth() + Math
.max(rep
, WIDTH_INCREASE
),
267 // If nothing will be drawn, just advance x.
276 for (int i
= 0; i
< rep
; i
++) {
277 if ((n
& 0x01) == 0x01) {
278 image
.setRGB(x
, height
, rgb
);
280 if ((n
& 0x02) == 0x02) {
281 image
.setRGB(x
, height
+ 1, rgb
);
283 if ((n
& 0x04) == 0x04) {
284 image
.setRGB(x
, height
+ 2, rgb
);
286 if ((n
& 0x08) == 0x08) {
287 image
.setRGB(x
, height
+ 3, rgb
);
289 if ((n
& 0x10) == 0x10) {
290 image
.setRGB(x
, height
+ 4, rgb
);
292 if ((n
& 0x20) == 0x20) {
293 image
.setRGB(x
, height
+ 5, rgb
);
304 * Process a color palette change.
306 private void setPalette() {
307 int idx
= getColorParam(0, 0);
309 if (colorParams
.size() == 1) {
310 Color newColor
= palette
.get(idx
);
311 if (newColor
!= null) {
316 System
.err
.println("set color: " + color
);
321 int type
= getColorParam(1, 0);
322 float red
= (float) (getColorParam(2, 0, 0, 100) / 100.0);
323 float green
= (float) (getColorParam(3, 0, 0, 100) / 100.0);
324 float blue
= (float) (getColorParam(4, 0, 0, 100) / 100.0);
327 Color newColor
= new Color(red
, green
, blue
);
328 palette
.put(idx
, newColor
);
330 System
.err
.println("Palette color " + idx
+ " --> " + newColor
);
336 * Run this input character through the sixel state machine.
338 * @param ch character from the remote side
340 private void consume(char ch
) {
343 // System.err.printf("Sixel.consume() %c STATE = %s\n", ch, scanState);
350 scanState
= ScanState
.COLOR_ENTRY
;
353 scanState
= ScanState
.QUOTE
;
361 scanState
= ScanState
.SIXEL_REPEAT
;
364 if (height
+ 6 < image
.getHeight()) {
365 // Resize the image, give us another HEIGHT_INCREASE
366 // pixels of vertical length.
367 resizeImage(image
.getWidth(),
368 image
.getHeight() + HEIGHT_INCREASE
);
382 scanState
= ScanState
.COLOR_ENTRY
;
388 // Ignore everything else in the quote header.
392 // Between decimal 63 (inclusive) and 189 (exclusive) --> pixels
393 if ((ch
>= 63) && (ch
< 189)) {
398 // 30-39, 3B --> param, then switch to COLOR_PARAM
399 if ((ch
>= '0') && (ch
<= '9')) {
401 scanState
= ScanState
.COLOR_PARAM
;
405 scanState
= ScanState
.COLOR_PARAM
;
409 // Next color is here, parse what we had before.
419 scanState
= ScanState
.SIXEL_REPEAT
;
425 if (height
+ 6 < image
.getHeight()) {
426 // Resize the image, give us another HEIGHT_INCREASE
427 // pixels of vertical length.
428 resizeImage(image
.getWidth(),
429 image
.getHeight() + HEIGHT_INCREASE
);
445 // Between decimal 63 (inclusive) and 189 (exclusive) --> pixels
446 if ((ch
>= 63) && (ch
< 189)) {
451 // 30-39, 3B --> param, then switch to COLOR_PARAM
452 if ((ch
>= '0') && (ch
<= '9')) {
460 // Next color is here, parse what we had before.
463 scanState
= ScanState
.COLOR_ENTRY
;
471 scanState
= ScanState
.SIXEL_REPEAT
;
477 if (height
+ 6 < image
.getHeight()) {
478 // Resize the image, give us another HEIGHT_INCREASE
479 // pixels of vertical length.
480 resizeImage(image
.getWidth(),
481 image
.getHeight() + HEIGHT_INCREASE
);
497 // Between decimal 63 (inclusive) and 189 (exclusive) --> pixels
498 if ((ch
>= 63) && (ch
< 189)) {
503 if ((ch
>= '0') && (ch
<= '9')) {
504 if (repeatCount
== -1) {
505 repeatCount
= (int) (ch
- '0');
508 repeatCount
+= (int) (ch
- '0');
515 scanState
= ScanState
.COLOR_ENTRY
;