Merge commit 'e6bb1700749980e69b5e913acbfd276f129c24dc'
[nikiroo-utils.git] / src / jexer / tterminal / Sixel.java
index 8d8429b8dd2c63cacacc689c50611c6f6db19d04..b91e77a98beebc8cdfc726426654fd07c1b43967 100644 (file)
@@ -31,7 +31,6 @@ package jexer.tterminal;
 import java.awt.Color;
 import java.awt.Graphics2D;
 import java.awt.image.BufferedImage;
-import java.util.ArrayList;
 import java.util.HashMap;
 
 /**
@@ -48,11 +47,9 @@ public class Sixel {
      */
     private enum ScanState {
         GROUND,
-        QUOTE,
-        COLOR_ENTRY,
-        COLOR_PARAM,
-        COLOR_PIXELS,
-        SIXEL_REPEAT,
+        RASTER,
+        COLOR,
+        REPEAT,
     }
 
     // ------------------------------------------------------------------------
@@ -62,7 +59,7 @@ public class Sixel {
     /**
      * If true, enable debug messages.
      */
-    private static boolean DEBUG = true;
+    private static boolean DEBUG = false;
 
     /**
      * Number of pixels to increment when we need more horizontal room.
@@ -74,15 +71,30 @@ public class Sixel {
      */
     private static int HEIGHT_INCREASE = 400;
 
+    /**
+     * Maximum width in pixels.
+     */
+    private static int MAX_WIDTH = 1000;
+
+    /**
+     * Maximum height in pixels.
+     */
+    private static int MAX_HEIGHT = 1000;
+
     /**
      * Current scanning state.
      */
     private ScanState scanState = ScanState.GROUND;
 
     /**
-     * Parameter characters being collected.
+     * Parameters being collected.
+     */
+    private int [] params = new int[5];
+
+    /**
+     * Current parameter being collected.
      */
-    private ArrayList<Integer> colorParams;
+    private int paramsI = 0;
 
     /**
      * The sixel palette colors specified.
@@ -109,6 +121,16 @@ public class Sixel {
      */
     private int height = 0;
 
+    /**
+     * The width of image provided in the raster attribute.
+     */
+    private int rasterWidth = 0;
+
+    /**
+     * The height of image provided in the raster attribute.
+     */
+    private int rasterHeight = 0;
+
     /**
      * The repeat count.
      */
@@ -119,11 +141,21 @@ public class Sixel {
      */
     private int x = 0;
 
+    /**
+     * The maximum y drawn to.  This will set the final image height.
+     */
+    private int y = 0;
+
     /**
      * The current drawing color.
      */
     private Color color = Color.BLACK;
 
+    /**
+     * If set, abort processing this image.
+     */
+    private boolean abort = false;
+
     // ------------------------------------------------------------------------
     // Constructors -----------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -132,14 +164,14 @@ public class Sixel {
      * Public constructor.
      *
      * @param buffer the sixel data to parse
+     * @param palette palette to use, or null for a private palette
      */
-    public Sixel(final String buffer) {
+    public Sixel(final String buffer, final HashMap<Integer, Color> palette) {
         this.buffer = buffer;
-        colorParams = new ArrayList<Integer>();
-        palette = new HashMap<Integer, Color>();
-        image = new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB);
-        for (int i = 0; i < buffer.length(); i++) {
-            consume(buffer.charAt(i));
+        if (palette == null) {
+            this.palette = new HashMap<Integer, Color>();
+        } else {
+            this.palette = palette;
         }
     }
 
@@ -153,8 +185,27 @@ public class Sixel {
      * @return the sixel data as an image.
      */
     public BufferedImage getImage() {
-        if ((width > 0) && (height > 0)) {
-            return image.getSubimage(0, 0, width, height);
+        if (buffer != null) {
+            for (int i = 0; (i < buffer.length()) && (abort == false); i++) {
+                consume(buffer.charAt(i));
+            }
+            buffer = null;
+        }
+        if (abort == true) {
+            return null;
+        }
+
+        if ((width > 0) && (height > 0) && (image != null)) {
+            /*
+            System.err.println(String.format("%d %d %d %d", width, y + 1,
+                    rasterWidth, rasterHeight));
+            */
+
+            if ((rasterWidth > width) || (rasterHeight > y + 1)) {
+                resizeImage(Math.max(width, rasterWidth),
+                    Math.max(y + 1, rasterHeight));
+            }
+            return image.getSubimage(0, 0, width, y + 1);
         }
         return null;
     }
@@ -169,6 +220,16 @@ public class Sixel {
         BufferedImage newImage = new BufferedImage(newWidth, newHeight,
             BufferedImage.TYPE_INT_ARGB);
 
+        if (image == null) {
+            image = newImage;
+            return;
+        }
+
+        if (DEBUG) {
+            System.err.println("resizeImage(); old " + image.getWidth() + "x" +
+                image.getHeight() + " new " + newWidth + "x" + newHeight);
+        }
+
         Graphics2D gr = newImage.createGraphics();
         gr.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
         gr.dispose();
@@ -179,32 +240,14 @@ public class Sixel {
      * Clear the parameters and flags.
      */
     private void toGround() {
-        colorParams.clear();
+        paramsI = 0;
+        for (int i = 0; i < params.length; i++) {
+            params[i] = 0;
+        }
         scanState = ScanState.GROUND;
         repeatCount = -1;
     }
 
-    /**
-     * Save a byte into the color parameters buffer.
-     *
-     * @param ch byte to save
-     */
-    private void param(final byte ch) {
-        if (colorParams.size() == 0) {
-            colorParams.add(Integer.valueOf(0));
-        }
-        Integer n = colorParams.get(colorParams.size() - 1);
-        if ((ch >= '0') && (ch <= '9')) {
-            n *= 10;
-            n += (ch - '0');
-            colorParams.set(colorParams.size() - 1, n);
-        }
-
-        if ((ch == ';') && (colorParams.size() < 16)) {
-            colorParams.add(Integer.valueOf(0));
-        }
-    }
-
     /**
      * Get a color parameter value, with a default.
      *
@@ -212,11 +255,11 @@ public class Sixel {
      * @param defaultValue value to use if colorParams[position] doesn't exist
      * @return parameter value
      */
-    private int getColorParam(final int position, final int defaultValue) {
-        if (colorParams.size() < position + 1) {
+    private int getParam(final int position, final int defaultValue) {
+        if (position > paramsI) {
             return defaultValue;
         }
-        return colorParams.get(position).intValue();
+        return params[position];
     }
 
     /**
@@ -228,11 +271,11 @@ public class Sixel {
      * @param maxValue maximum value inclusive
      * @return parameter value
      */
-    private int getColorParam(final int position, final int defaultValue,
+    private int getParam(final int position, final int defaultValue,
         final int minValue, final int maxValue) {
 
         assert (minValue <= maxValue);
-        int value = getColorParam(position, defaultValue);
+        int value = getParam(position, defaultValue);
         if (value < minValue) {
             value = minValue;
         }
@@ -249,6 +292,12 @@ public class Sixel {
      */
     private void addSixel(final char ch) {
         int n = ((int) ch - 63);
+
+        if (DEBUG && (color == null)) {
+            System.err.println("color is null?!");
+            System.err.println(buffer);
+        }
+
         int rgb = color.getRGB();
         int rep = (repeatCount == -1 ? 1 : repeatCount);
 
@@ -257,6 +306,13 @@ public class Sixel {
                 Integer.toHexString(n) + " color " + color);
         }
 
+        assert (n >= 0);
+
+        if (image == null) {
+            // The raster attributes was not provided.
+            resizeImage(WIDTH_INCREASE, HEIGHT_INCREASE);
+        }
+
         if (x + rep > image.getWidth()) {
             // Resize the image, give us another max(rep, WIDTH_INCREASE)
             // pixels of horizontal length.
@@ -270,33 +326,51 @@ public class Sixel {
             if (x > width) {
                 width = x;
             }
+            if (width > MAX_WIDTH) {
+                abort = true;
+            }
             return;
         }
 
+        int dy = 0;
         for (int i = 0; i < rep; i++) {
-            if ((n & 0x01) == 0x01) {
-                image.setRGB(x, height, rgb);
+            if ((n & 0x01) != 0) {
+                dy = 0;
+                image.setRGB(x, height + dy, rgb);
             }
-            if ((n & 0x02) == 0x02) {
-                image.setRGB(x, height + 1, rgb);
+            if ((n & 0x02) != 0) {
+                dy = 1;
+                image.setRGB(x, height + dy, rgb);
             }
-            if ((n & 0x04) == 0x04) {
-                image.setRGB(x, height + 2, rgb);
+            if ((n & 0x04) != 0) {
+                dy = 2;
+                image.setRGB(x, height + dy, rgb);
             }
-            if ((n & 0x08) == 0x08) {
-                image.setRGB(x, height + 3, rgb);
+            if ((n & 0x08) != 0) {
+                dy = 3;
+                image.setRGB(x, height + dy, rgb);
             }
-            if ((n & 0x10) == 0x10) {
-                image.setRGB(x, height + 4, rgb);
+            if ((n & 0x10) != 0) {
+                dy = 4;
+                image.setRGB(x, height + dy, rgb);
             }
-            if ((n & 0x20) == 0x20) {
-                image.setRGB(x, height + 5, rgb);
+            if ((n & 0x20) != 0) {
+                dy = 5;
+                image.setRGB(x, height + dy, rgb);
             }
-            x++;
-            if (x > width) {
-                width++;
-                assert (x == width);
+            if (height + dy > y) {
+                y = height + dy;
             }
+            x++;
+        }
+        if (x > width) {
+            width = x;
+        }
+        if (width > MAX_WIDTH) {
+            abort = true;
+        }
+        if (y + 1 > MAX_HEIGHT) {
+            abort = true;
         }
     }
 
@@ -304,24 +378,29 @@ public class Sixel {
      * Process a color palette change.
      */
     private void setPalette() {
-        int idx = getColorParam(0, 0);
+        int idx = getParam(0, 0);
 
-        if (colorParams.size() == 1) {
+        if (paramsI == 0) {
             Color newColor = palette.get(idx);
             if (newColor != null) {
                 color = newColor;
+            } else {
+                if (DEBUG) {
+                    System.err.println("COLOR " + idx + " NOT FOUND");
+                }
+                color = Color.BLACK;
             }
 
             if (DEBUG) {
-                System.err.println("set color: " + color);
+                System.err.println("set color " + idx + " " + color);
             }
             return;
         }
 
-        int type = getColorParam(1, 0);
-        float red   = (float) (getColorParam(2, 0, 0, 100) / 100.0);
-        float green = (float) (getColorParam(3, 0, 0, 100) / 100.0);
-        float blue  = (float) (getColorParam(4, 0, 0, 100) / 100.0);
+        int type = getParam(1, 0);
+        float red   = (float) (getParam(2, 0, 0, 100) / 100.0);
+        float green = (float) (getParam(3, 0, 0, 100) / 100.0);
+        float blue  = (float) (getParam(4, 0, 0, 100) / 100.0);
 
         if (type == 2) {
             Color newColor = new Color(red, green, blue);
@@ -329,6 +408,33 @@ public class Sixel {
             if (DEBUG) {
                 System.err.println("Palette color " + idx + " --> " + newColor);
             }
+        } else {
+            if (DEBUG) {
+                System.err.println("UNKNOWN COLOR TYPE " + type + ": " + type +
+                    " " + idx + " R " + red + " G " + green + " B " + blue);
+            }
+        }
+    }
+
+    /**
+     * Parse the raster attributes.
+     */
+    private void parseRaster() {
+        int pan = getParam(0, 0);  // Aspect ratio numerator
+        int pad = getParam(1, 0);  // Aspect ratio denominator
+        int pah = getParam(2, 0);  // Horizontal width
+        int pav = getParam(3, 0);  // Vertical height
+
+        if ((pan == pad) && (pah > 0) && (pav > 0)) {
+            rasterWidth = pah;
+            rasterHeight = pav;
+            if ((rasterWidth <= MAX_WIDTH) && (rasterHeight <= MAX_HEIGHT)) {
+                resizeImage(rasterWidth, rasterHeight);
+            } else {
+                abort = true;
+            }
+        } else {
+            abort = true;
         }
     }
 
@@ -342,180 +448,139 @@ public class Sixel {
         // DEBUG
         // System.err.printf("Sixel.consume() %c STATE = %s\n", ch, scanState);
 
-        switch (scanState) {
-
-        case GROUND:
-            switch (ch) {
-            case '#':
-                scanState = ScanState.COLOR_ENTRY;
-                return;
-            case '\"':
-                scanState = ScanState.QUOTE;
-                return;
-            default:
-                break;
-            }
-
-            if (ch == '!') {
-                // Repeat count
-                scanState = ScanState.SIXEL_REPEAT;
-            }
-            if (ch == '-') {
-                if (height + 6 < image.getHeight()) {
-                    // Resize the image, give us another HEIGHT_INCREASE
-                    // pixels of vertical length.
-                    resizeImage(image.getWidth(),
-                        image.getHeight() + HEIGHT_INCREASE);
-                }
-                height += 6;
-                x = 0;
+        // Between decimal 63 (inclusive) and 127 (exclusive) --> pixels
+        if ((ch >= 63) && (ch < 127)) {
+            if (scanState == ScanState.COLOR) {
+                setPalette();
             }
-
-            if (ch == '$') {
-                x = 0;
+            if (scanState == ScanState.RASTER) {
+                parseRaster();
+                toGround();
             }
+            addSixel(ch);
+            toGround();
             return;
+        }
 
-        case QUOTE:
-            switch (ch) {
-            case '#':
-                scanState = ScanState.COLOR_ENTRY;
-                return;
-            default:
-                break;
+        if (ch == '#') {
+            // Next color is here, parse what we had before.
+            if (scanState == ScanState.COLOR) {
+                setPalette();
+                toGround();
             }
-
-            // Ignore everything else in the quote header.
-            return;
-
-        case COLOR_ENTRY:
-            // Between decimal 63 (inclusive) and 189 (exclusive) --> pixels
-            if ((ch >= 63) && (ch < 189)) {
-                addSixel(ch);
-                return;
+            if (scanState == ScanState.RASTER) {
+                parseRaster();
+                toGround();
             }
+            scanState = ScanState.COLOR;
+            return;
+        }
 
-            // 30-39, 3B           --> param, then switch to COLOR_PARAM
-            if ((ch >= '0') && (ch <= '9')) {
-                param((byte) ch);
-                scanState = ScanState.COLOR_PARAM;
+        if (ch == '!') {
+            // Repeat count
+            if (scanState == ScanState.COLOR) {
+                setPalette();
+                toGround();
             }
-            if (ch == ';') {
-                param((byte) ch);
-                scanState = ScanState.COLOR_PARAM;
+            if (scanState == ScanState.RASTER) {
+                parseRaster();
+                toGround();
             }
+            scanState = ScanState.REPEAT;
+            repeatCount = 0;
+            return;
+        }
 
-            if (ch == '#') {
-                // Next color is here, parse what we had before.
+        if (ch == '-') {
+            if (scanState == ScanState.COLOR) {
                 setPalette();
                 toGround();
             }
-
-            if (ch == '!') {
-                setPalette();
+            if (scanState == ScanState.RASTER) {
+                parseRaster();
                 toGround();
+            }
+
+            height += 6;
+            x = 0;
 
-                // Repeat count
-                scanState = ScanState.SIXEL_REPEAT;
+            if (height + 6 > image.getHeight()) {
+                // Resize the image, give us another HEIGHT_INCREASE
+                // pixels of vertical length.
+                resizeImage(image.getWidth(),
+                    image.getHeight() + HEIGHT_INCREASE);
             }
-            if (ch == '-') {
+            return;
+        }
+
+        if (ch == '$') {
+            if (scanState == ScanState.COLOR) {
                 setPalette();
                 toGround();
-
-                if (height + 6 < image.getHeight()) {
-                    // Resize the image, give us another HEIGHT_INCREASE
-                    // pixels of vertical length.
-                    resizeImage(image.getWidth(),
-                        image.getHeight() + HEIGHT_INCREASE);
-                }
-                height += 6;
-                x = 0;
             }
+            if (scanState == ScanState.RASTER) {
+                parseRaster();
+                toGround();
+            }
+            x = 0;
+            return;
+        }
 
-            if (ch == '$') {
+        if (ch == '"') {
+            if (scanState == ScanState.COLOR) {
                 setPalette();
                 toGround();
-
-                x = 0;
             }
+            scanState = ScanState.RASTER;
             return;
+        }
 
-        case COLOR_PARAM:
+        switch (scanState) {
 
-            // Between decimal 63 (inclusive) and 189 (exclusive) --> pixels
-            if ((ch >= 63) && (ch < 189)) {
-                addSixel(ch);
-                return;
+        case GROUND:
+            // Unknown character.
+            if (DEBUG) {
+                System.err.println("UNKNOWN CHAR: " + ch);
             }
+            return;
 
-            // 30-39, 3B           --> param, then switch to COLOR_PARAM
+        case RASTER:
+            // 30-39, 3B --> param
             if ((ch >= '0') && (ch <= '9')) {
-                param((byte) ch);
+                params[paramsI] *= 10;
+                params[paramsI] += (ch - '0');
             }
             if (ch == ';') {
-                param((byte) ch);
-            }
-
-            if (ch == '#') {
-                // Next color is here, parse what we had before.
-                setPalette();
-                toGround();
-                scanState = ScanState.COLOR_ENTRY;
+                if (paramsI < params.length - 1) {
+                    paramsI++;
+                }
             }
+            return;
 
-            if (ch == '!') {
-                setPalette();
-                toGround();
-
-                // Repeat count
-                scanState = ScanState.SIXEL_REPEAT;
+        case COLOR:
+            // 30-39, 3B --> param
+            if ((ch >= '0') && (ch <= '9')) {
+                params[paramsI] *= 10;
+                params[paramsI] += (ch - '0');
             }
-            if (ch == '-') {
-                setPalette();
-                toGround();
-
-                if (height + 6 < image.getHeight()) {
-                    // Resize the image, give us another HEIGHT_INCREASE
-                    // pixels of vertical length.
-                    resizeImage(image.getWidth(),
-                        image.getHeight() + HEIGHT_INCREASE);
+            if (ch == ';') {
+                if (paramsI < params.length - 1) {
+                    paramsI++;
                 }
-                height += 6;
-                x = 0;
-            }
-
-            if (ch == '$') {
-                setPalette();
-                toGround();
-
-                x = 0;
             }
             return;
 
-        case SIXEL_REPEAT:
-
-            // Between decimal 63 (inclusive) and 189 (exclusive) --> pixels
-            if ((ch >= 63) && (ch < 189)) {
-                addSixel(ch);
-                toGround();
-            }
-
+        case REPEAT:
             if ((ch >= '0') && (ch <= '9')) {
                 if (repeatCount == -1) {
-                    repeatCount = (int) (ch - '0');
+                    repeatCount = (ch - '0');
                 } else {
                     repeatCount *= 10;
-                    repeatCount += (int) (ch - '0');
+                    repeatCount += (ch - '0');
                 }
             }
-
-            if (ch == '#') {
-                // Next color.
-                toGround();
-                scanState = ScanState.COLOR_ENTRY;
-            }
-
             return;
+
         }
 
     }