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
{
56 // ------------------------------------------------------------------------
57 // Variables --------------------------------------------------------------
58 // ------------------------------------------------------------------------
61 * If true, enable debug messages.
63 private static boolean DEBUG
= false;
66 * Number of pixels to increment when we need more horizontal room.
68 private static int WIDTH_INCREASE
= 400;
71 * Number of pixels to increment when we need more vertical room.
73 private static int HEIGHT_INCREASE
= 400;
76 * Current scanning state.
78 private ScanState scanState
= ScanState
.GROUND
;
81 * Parameters being collected.
83 private int [] params
= new int[5];
86 * Current parameter being collected.
88 private int paramsI
= 0;
91 * The sixel palette colors specified.
93 private HashMap
<Integer
, Color
> palette
;
96 * The buffer to parse.
98 private String buffer
;
101 * The image being drawn to.
103 private BufferedImage image
;
106 * The real width of image.
108 private int width
= 0;
111 * The real height of image.
113 private int height
= 0;
118 private int repeatCount
= -1;
121 * The current drawing x position.
126 * The maximum y drawn to. This will set the final image height.
131 * The current drawing color.
133 private Color color
= Color
.BLACK
;
135 // ------------------------------------------------------------------------
136 // Constructors -----------------------------------------------------------
137 // ------------------------------------------------------------------------
140 * Public constructor.
142 * @param buffer the sixel data to parse
144 public Sixel(final String buffer
) {
145 this.buffer
= buffer
;
146 palette
= new HashMap
<Integer
, Color
>();
147 image
= new BufferedImage(200, 100, BufferedImage
.TYPE_INT_ARGB
);
148 for (int i
= 0; i
< buffer
.length(); i
++) {
149 consume(buffer
.charAt(i
));
153 // ------------------------------------------------------------------------
154 // Sixel ------------------------------------------------------------------
155 // ------------------------------------------------------------------------
160 * @return the sixel data as an image.
162 public BufferedImage
getImage() {
163 if ((width
> 0) && (height
> 0)) {
164 return image
.getSubimage(0, 0, width
, y
+ 1);
170 * Resize image to a new size.
172 * @param newWidth new width of image
173 * @param newHeight new height of image
175 private void resizeImage(final int newWidth
, final int newHeight
) {
176 BufferedImage newImage
= new BufferedImage(newWidth
, newHeight
,
177 BufferedImage
.TYPE_INT_ARGB
);
180 System
.err
.println("resizeImage(); old " + image
.getWidth() + "x" +
181 image
.getHeight() + " new " + newWidth
+ "x" + newHeight
);
184 Graphics2D gr
= newImage
.createGraphics();
185 gr
.drawImage(image
, 0, 0, image
.getWidth(), image
.getHeight(), null);
191 * Clear the parameters and flags.
193 private void toGround() {
195 for (int i
= 0; i
< params
.length
; i
++) {
198 scanState
= ScanState
.GROUND
;
203 * Get a color parameter value, with a default.
205 * @param position parameter index. 0 is the first parameter.
206 * @param defaultValue value to use if colorParams[position] doesn't exist
207 * @return parameter value
209 private int getColorParam(final int position
, final int defaultValue
) {
210 if (position
> paramsI
) {
213 return params
[position
];
217 * Get a color parameter value, clamped to within min/max.
219 * @param position parameter index. 0 is the first parameter.
220 * @param defaultValue value to use if colorParams[position] doesn't exist
221 * @param minValue minimum value inclusive
222 * @param maxValue maximum value inclusive
223 * @return parameter value
225 private int getColorParam(final int position
, final int defaultValue
,
226 final int minValue
, final int maxValue
) {
228 assert (minValue
<= maxValue
);
229 int value
= getColorParam(position
, defaultValue
);
230 if (value
< minValue
) {
233 if (value
> maxValue
) {
240 * Add sixel data to the image.
242 * @param ch the character of sixel data
244 private void addSixel(final char ch
) {
245 int n
= ((int) ch
- 63);
247 if (DEBUG
&& (color
== null)) {
248 System
.err
.println("color is null?!");
249 System
.err
.println(buffer
);
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
);
262 if (x
+ rep
> image
.getWidth()) {
263 // Resize the image, give us another max(rep, WIDTH_INCREASE)
264 // pixels of horizontal length.
265 resizeImage(image
.getWidth() + Math
.max(rep
, WIDTH_INCREASE
),
269 // If nothing will be drawn, just advance x.
279 for (int i
= 0; i
< rep
; i
++) {
280 if ((n
& 0x01) != 0) {
282 image
.setRGB(x
, height
+ dy
, rgb
);
284 if ((n
& 0x02) != 0) {
286 image
.setRGB(x
, height
+ dy
, rgb
);
288 if ((n
& 0x04) != 0) {
290 image
.setRGB(x
, height
+ dy
, rgb
);
292 if ((n
& 0x08) != 0) {
294 image
.setRGB(x
, height
+ dy
, rgb
);
296 if ((n
& 0x10) != 0) {
298 image
.setRGB(x
, height
+ dy
, rgb
);
300 if ((n
& 0x20) != 0) {
302 image
.setRGB(x
, height
+ dy
, rgb
);
304 if (height
+ dy
> y
) {
315 * Process a color palette change.
317 private void setPalette() {
318 int idx
= getColorParam(0, 0);
321 Color newColor
= palette
.get(idx
);
322 if (newColor
!= null) {
326 System
.err
.println("COLOR " + idx
+ " NOT FOUND");
332 System
.err
.println("set color " + idx
+ " " + color
);
337 int type
= getColorParam(1, 0);
338 float red
= (float) (getColorParam(2, 0, 0, 100) / 100.0);
339 float green
= (float) (getColorParam(3, 0, 0, 100) / 100.0);
340 float blue
= (float) (getColorParam(4, 0, 0, 100) / 100.0);
343 Color newColor
= new Color(red
, green
, blue
);
344 palette
.put(idx
, newColor
);
346 System
.err
.println("Palette color " + idx
+ " --> " + newColor
);
350 System
.err
.println("UNKNOWN COLOR TYPE " + type
+ ": " + type
+
351 " " + idx
+ " R " + red
+ " G " + green
+ " B " + blue
);
357 * Run this input character through the sixel state machine.
359 * @param ch character from the remote side
361 private void consume(char ch
) {
364 // System.err.printf("Sixel.consume() %c STATE = %s\n", ch, scanState);
366 // Between decimal 63 (inclusive) and 127 (exclusive) --> pixels
367 if ((ch
>= 63) && (ch
< 127)) {
368 if (scanState
== ScanState
.COLOR
) {
377 // Next color is here, parse what we had before.
378 if (scanState
== ScanState
.COLOR
) {
382 scanState
= ScanState
.COLOR
;
388 if (scanState
== ScanState
.COLOR
) {
392 scanState
= ScanState
.REPEAT
;
398 if (scanState
== ScanState
.COLOR
) {
406 if (height
+ 6 > image
.getHeight()) {
407 // Resize the image, give us another HEIGHT_INCREASE
408 // pixels of vertical length.
409 resizeImage(image
.getWidth(),
410 image
.getHeight() + HEIGHT_INCREASE
);
416 if (scanState
== ScanState
.COLOR
) {
425 if (scanState
== ScanState
.COLOR
) {
429 scanState
= ScanState
.QUOTE
;
436 // Unknown character.
438 System
.err
.println("UNKNOWN CHAR: " + ch
);
443 // Ignore everything else in the quote header.
447 // 30-39, 3B --> param
448 if ((ch
>= '0') && (ch
<= '9')) {
449 params
[paramsI
] *= 10;
450 params
[paramsI
] += (ch
- '0');
453 if (paramsI
< params
.length
- 1) {
460 if ((ch
>= '0') && (ch
<= '9')) {
461 if (repeatCount
== -1) {
462 repeatCount
= (int) (ch
- '0');
465 repeatCount
+= (int) (ch
- '0');