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 * Maximum width in pixels.
78 private static int MAX_WIDTH
= 1000;
81 * Maximum height in pixels.
83 private static int MAX_HEIGHT
= 1000;
86 * Current scanning state.
88 private ScanState scanState
= ScanState
.GROUND
;
91 * Parameters being collected.
93 private int [] params
= new int[5];
96 * Current parameter being collected.
98 private int paramsI
= 0;
101 * The sixel palette colors specified.
103 private HashMap
<Integer
, Color
> palette
;
106 * The buffer to parse.
108 private String buffer
;
111 * The image being drawn to.
113 private BufferedImage image
;
116 * The real width of image.
118 private int width
= 0;
121 * The real height of image.
123 private int height
= 0;
126 * The width of image provided in the raster attribute.
128 private int rasterWidth
= 0;
131 * The height of image provided in the raster attribute.
133 private int rasterHeight
= 0;
138 private int repeatCount
= -1;
141 * The current drawing x position.
146 * The maximum y drawn to. This will set the final image height.
151 * The current drawing color.
153 private Color color
= Color
.BLACK
;
156 * If set, abort processing this image.
158 private boolean abort
= false;
160 // ------------------------------------------------------------------------
161 // Constructors -----------------------------------------------------------
162 // ------------------------------------------------------------------------
165 * Public constructor.
167 * @param buffer the sixel data to parse
168 * @param palette palette to use, or null for a private palette
170 public Sixel(final String buffer
, final HashMap
<Integer
, Color
> palette
) {
171 this.buffer
= buffer
;
172 if (palette
== null) {
173 this.palette
= new HashMap
<Integer
, Color
>();
175 this.palette
= palette
;
179 // ------------------------------------------------------------------------
180 // Sixel ------------------------------------------------------------------
181 // ------------------------------------------------------------------------
186 * @return the sixel data as an image.
188 public BufferedImage
getImage() {
189 if (buffer
!= null) {
190 for (int i
= 0; (i
< buffer
.length()) && (abort
== false); i
++) {
191 consume(buffer
.charAt(i
));
199 if ((width
> 0) && (height
> 0) && (image
!= null)) {
201 System.err.println(String.format("%d %d %d %d", width, y + 1,
202 rasterWidth, rasterHeight));
205 if ((rasterWidth
> width
) || (rasterHeight
> y
+ 1)) {
206 resizeImage(Math
.max(width
, rasterWidth
),
207 Math
.max(y
+ 1, rasterHeight
));
209 return image
.getSubimage(0, 0, width
, y
+ 1);
215 * Resize image to a new size.
217 * @param newWidth new width of image
218 * @param newHeight new height of image
220 private void resizeImage(final int newWidth
, final int newHeight
) {
221 BufferedImage newImage
= new BufferedImage(newWidth
, newHeight
,
222 BufferedImage
.TYPE_INT_ARGB
);
230 System
.err
.println("resizeImage(); old " + image
.getWidth() + "x" +
231 image
.getHeight() + " new " + newWidth
+ "x" + newHeight
);
234 Graphics2D gr
= newImage
.createGraphics();
235 gr
.drawImage(image
, 0, 0, image
.getWidth(), image
.getHeight(), null);
241 * Clear the parameters and flags.
243 private void toGround() {
245 for (int i
= 0; i
< params
.length
; i
++) {
248 scanState
= ScanState
.GROUND
;
253 * Get a color parameter value, with a default.
255 * @param position parameter index. 0 is the first parameter.
256 * @param defaultValue value to use if colorParams[position] doesn't exist
257 * @return parameter value
259 private int getParam(final int position
, final int defaultValue
) {
260 if (position
> paramsI
) {
263 return params
[position
];
267 * Get a color parameter value, clamped to within min/max.
269 * @param position parameter index. 0 is the first parameter.
270 * @param defaultValue value to use if colorParams[position] doesn't exist
271 * @param minValue minimum value inclusive
272 * @param maxValue maximum value inclusive
273 * @return parameter value
275 private int getParam(final int position
, final int defaultValue
,
276 final int minValue
, final int maxValue
) {
278 assert (minValue
<= maxValue
);
279 int value
= getParam(position
, defaultValue
);
280 if (value
< minValue
) {
283 if (value
> maxValue
) {
290 * Add sixel data to the image.
292 * @param ch the character of sixel data
294 private void addSixel(final char ch
) {
295 int n
= ((int) ch
- 63);
297 if (DEBUG
&& (color
== null)) {
298 System
.err
.println("color is null?!");
299 System
.err
.println(buffer
);
302 int rgb
= color
.getRGB();
303 int rep
= (repeatCount
== -1 ?
1 : repeatCount
);
306 System
.err
.println("addSixel() rep " + rep
+ " char " +
307 Integer
.toHexString(n
) + " color " + color
);
313 // The raster attributes was not provided.
314 resizeImage(WIDTH_INCREASE
, HEIGHT_INCREASE
);
317 if (x
+ rep
> image
.getWidth()) {
318 // Resize the image, give us another max(rep, WIDTH_INCREASE)
319 // pixels of horizontal length.
320 resizeImage(image
.getWidth() + Math
.max(rep
, WIDTH_INCREASE
),
324 // If nothing will be drawn, just advance x.
330 if (width
> MAX_WIDTH
) {
337 for (int i
= 0; i
< rep
; i
++) {
338 if ((n
& 0x01) != 0) {
340 image
.setRGB(x
, height
+ dy
, rgb
);
342 if ((n
& 0x02) != 0) {
344 image
.setRGB(x
, height
+ dy
, rgb
);
346 if ((n
& 0x04) != 0) {
348 image
.setRGB(x
, height
+ dy
, rgb
);
350 if ((n
& 0x08) != 0) {
352 image
.setRGB(x
, height
+ dy
, rgb
);
354 if ((n
& 0x10) != 0) {
356 image
.setRGB(x
, height
+ dy
, rgb
);
358 if ((n
& 0x20) != 0) {
360 image
.setRGB(x
, height
+ dy
, rgb
);
362 if (height
+ dy
> y
) {
370 if (width
> MAX_WIDTH
) {
373 if (y
+ 1 > MAX_HEIGHT
) {
379 * Process a color palette change.
381 private void setPalette() {
382 int idx
= getParam(0, 0);
385 Color newColor
= palette
.get(idx
);
386 if (newColor
!= null) {
390 System
.err
.println("COLOR " + idx
+ " NOT FOUND");
396 System
.err
.println("set color " + idx
+ " " + color
);
401 int type
= getParam(1, 0);
402 float red
= (float) (getParam(2, 0, 0, 100) / 100.0);
403 float green
= (float) (getParam(3, 0, 0, 100) / 100.0);
404 float blue
= (float) (getParam(4, 0, 0, 100) / 100.0);
407 Color newColor
= new Color(red
, green
, blue
);
408 palette
.put(idx
, newColor
);
410 System
.err
.println("Palette color " + idx
+ " --> " + newColor
);
414 System
.err
.println("UNKNOWN COLOR TYPE " + type
+ ": " + type
+
415 " " + idx
+ " R " + red
+ " G " + green
+ " B " + blue
);
421 * Parse the raster attributes.
423 private void parseRaster() {
424 int pan
= getParam(0, 0); // Aspect ratio numerator
425 int pad
= getParam(1, 0); // Aspect ratio denominator
426 int pah
= getParam(2, 0); // Horizontal width
427 int pav
= getParam(3, 0); // Vertical height
429 if ((pan
== pad
) && (pah
> 0) && (pav
> 0)) {
432 if ((rasterWidth
<= MAX_WIDTH
) && (rasterHeight
<= MAX_HEIGHT
)) {
433 resizeImage(rasterWidth
, rasterHeight
);
443 * Run this input character through the sixel state machine.
445 * @param ch character from the remote side
447 private void consume(char ch
) {
450 // System.err.printf("Sixel.consume() %c STATE = %s\n", ch, scanState);
452 // Between decimal 63 (inclusive) and 127 (exclusive) --> pixels
453 if ((ch
>= 63) && (ch
< 127)) {
454 if (scanState
== ScanState
.COLOR
) {
457 if (scanState
== ScanState
.RASTER
) {
467 // Next color is here, parse what we had before.
468 if (scanState
== ScanState
.COLOR
) {
472 if (scanState
== ScanState
.RASTER
) {
476 scanState
= ScanState
.COLOR
;
482 if (scanState
== ScanState
.COLOR
) {
486 if (scanState
== ScanState
.RASTER
) {
490 scanState
= ScanState
.REPEAT
;
496 if (scanState
== ScanState
.COLOR
) {
500 if (scanState
== ScanState
.RASTER
) {
508 if (height
+ 6 > image
.getHeight()) {
509 // Resize the image, give us another HEIGHT_INCREASE
510 // pixels of vertical length.
511 resizeImage(image
.getWidth(),
512 image
.getHeight() + HEIGHT_INCREASE
);
518 if (scanState
== ScanState
.COLOR
) {
522 if (scanState
== ScanState
.RASTER
) {
531 if (scanState
== ScanState
.COLOR
) {
535 scanState
= ScanState
.RASTER
;
542 // Unknown character.
544 System
.err
.println("UNKNOWN CHAR: " + ch
);
549 // 30-39, 3B --> param
550 if ((ch
>= '0') && (ch
<= '9')) {
551 params
[paramsI
] *= 10;
552 params
[paramsI
] += (ch
- '0');
555 if (paramsI
< params
.length
- 1) {
562 // 30-39, 3B --> param
563 if ((ch
>= '0') && (ch
<= '9')) {
564 params
[paramsI
] *= 10;
565 params
[paramsI
] += (ch
- '0');
568 if (paramsI
< params
.length
- 1) {
575 if ((ch
>= '0') && (ch
<= '9')) {
576 if (repeatCount
== -1) {
577 repeatCount
= (int) (ch
- '0');
580 repeatCount
+= (int) (ch
- '0');