expose sixel palette size
[fanfix.git] / src / jexer / backend / ECMA48Terminal.java
index ca1d499594734c75275a53f90a7f5fb8aa9f9a56..cb9724c4d5b1bfff85d4e7600b04e6d3b31a01d0 100644 (file)
@@ -81,14 +81,6 @@ public class ECMA48Terminal extends LogicalScreen
         MOUSE_SGR,
     }
 
-    /**
-     * Number of colors in the sixel palette.  Xterm 335 defines the max as
-     * 1024.
-     */
-    private static final int MAX_COLOR_REGISTERS = 1024;
-    // Black-and-white is possible too.
-    // private static final int MAX_COLOR_REGISTERS = 2;
-
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -200,6 +192,13 @@ public class ECMA48Terminal extends LogicalScreen
      */
     private SixelCache sixelCache = null;
 
+    /**
+     * Number of colors in the sixel palette.  Xterm 335 defines the max as
+     * 1024.  Valid values are: 2 (black and white), 256, 512, 1024, and
+     * 2048.
+     */
+    private int sixelPaletteSize = 1024;
+
     /**
      * If true, then we changed System.in and need to change it back.
      */
@@ -234,7 +233,7 @@ public class ECMA48Terminal extends LogicalScreen
 
     /**
      * SixelPalette is used to manage the conversion of images between 24-bit
-     * RGB color and a palette of MAX_COLOR_REGISTERS colors.
+     * RGB color and a palette of sixelPaletteSize colors.
      */
     private class SixelPalette {
 
@@ -247,7 +246,7 @@ public class ECMA48Terminal extends LogicalScreen
          * Map of color palette index for sixel output, from the order it was
          * generated by makePalette() to rgbColors.
          */
-        private int [] rgbSortedIndex = new int[MAX_COLOR_REGISTERS];
+        private int [] rgbSortedIndex = new int[sixelPaletteSize];
 
         /**
          * The color palette, organized by hue, saturation, and luminance.
@@ -345,7 +344,7 @@ public class ECMA48Terminal extends LogicalScreen
             int green = (color >>>  8) & 0xFF;
             int blue  =  color         & 0xFF;
 
-            if (MAX_COLOR_REGISTERS == 2) {
+            if (sixelPaletteSize == 2) {
                 if (((red * red) + (green * green) + (blue * blue)) < 35568) {
                     // Black
                     return 0;
@@ -427,7 +426,7 @@ public class ECMA48Terminal extends LogicalScreen
                     ((255 - blue) * (255 - blue))) < diff) {
 
                 // White is a closer match.
-                idx = MAX_COLOR_REGISTERS - 1;
+                idx = sixelPaletteSize - 1;
             }
             assert (idx != -1);
             return idx;
@@ -450,7 +449,7 @@ public class ECMA48Terminal extends LogicalScreen
         }
 
         /**
-         * Dither an image to a MAX_COLOR_REGISTERS palette.  The dithered
+         * Dither an image to a sixelPaletteSize palette.  The dithered
          * image cells will contain indexes into the palette.
          *
          * @param image the image to dither
@@ -473,7 +472,7 @@ public class ECMA48Terminal extends LogicalScreen
                         imageY) & 0xFFFFFF;
                     int colorIdx = matchColor(oldPixel);
                     assert (colorIdx >= 0);
-                    assert (colorIdx < MAX_COLOR_REGISTERS);
+                    assert (colorIdx < sixelPaletteSize);
                     int newPixel = rgbColors.get(colorIdx);
                     ditheredImage.setRGB(imageX, imageY, colorIdx);
 
@@ -671,11 +670,11 @@ public class ECMA48Terminal extends LogicalScreen
         private void makePalette() {
             // Generate the sixel palette.  Because we have no idea at this
             // layer which image(s) will be shown, we have to use a common
-            // palette with MAX_COLOR_REGISTERS colors for everything, and
+            // palette with sixelPaletteSize colors for everything, and
             // map the BufferedImage colors to their nearest neighbor in RGB
             // space.
 
-            if (MAX_COLOR_REGISTERS == 2) {
+            if (sixelPaletteSize == 2) {
                 rgbColors.add(0);
                 rgbColors.add(0xFFFFFF);
                 rgbSortedIndex[0] = 0;
@@ -696,13 +695,13 @@ public class ECMA48Terminal extends LogicalScreen
             satBits = 2;
             lumBits = 1;
 
-            assert (MAX_COLOR_REGISTERS >= 256);
-            assert ((MAX_COLOR_REGISTERS == 256)
-                || (MAX_COLOR_REGISTERS == 512)
-                || (MAX_COLOR_REGISTERS == 1024)
-                || (MAX_COLOR_REGISTERS == 2048));
+            assert (sixelPaletteSize >= 256);
+            assert ((sixelPaletteSize == 256)
+                || (sixelPaletteSize == 512)
+                || (sixelPaletteSize == 1024)
+                || (sixelPaletteSize == 2048));
 
-            switch (MAX_COLOR_REGISTERS) {
+            switch (sixelPaletteSize) {
             case 512:
                 hueBits = 5;
                 satBits = 2;
@@ -788,7 +787,7 @@ public class ECMA48Terminal extends LogicalScreen
             }
             // System.err.printf("\n</body></html>\n");
 
-            assert (rgbColors.size() == MAX_COLOR_REGISTERS);
+            assert (rgbColors.size() == sixelPaletteSize);
 
             /*
              * We need to sort rgbColors, so that toSixel() can know where
@@ -800,19 +799,19 @@ public class ECMA48Terminal extends LogicalScreen
             Collections.sort(rgbColors);
             HashMap<Integer, Integer> rgbColorIndices = null;
             rgbColorIndices = new HashMap<Integer, Integer>();
-            for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+            for (int i = 0; i < sixelPaletteSize; i++) {
                 rgbColorIndices.put(rgbColors.get(i), i);
             }
-            for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+            for (int i = 0; i < sixelPaletteSize; i++) {
                 int rawColor = rawRgbList.get(i);
                 rgbSortedIndex[i] = rgbColorIndices.get(rawColor);
             }
             if (DEBUG) {
-                for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+                for (int i = 0; i < sixelPaletteSize; i++) {
                     assert (rawRgbList != null);
                     int idx = rgbSortedIndex[i];
                     int rgbColor = rgbColors.get(idx);
-                    if ((idx != 0) && (idx != MAX_COLOR_REGISTERS - 1)) {
+                    if ((idx != 0) && (idx != sixelPaletteSize - 1)) {
                         /*
                         System.err.printf("%d %06x --> %d %06x\n",
                             i, rawRgbList.get(i), idx, rgbColors.get(idx));
@@ -825,7 +824,7 @@ public class ECMA48Terminal extends LogicalScreen
             // Set the dimmest color as true black, and the brightest as true
             // white.
             rgbColors.set(0, 0);
-            rgbColors.set(MAX_COLOR_REGISTERS - 1, 0xFFFFFF);
+            rgbColors.set(sixelPaletteSize - 1, 0xFFFFFF);
 
             /*
             System.err.printf("<html><body>\n");
@@ -850,7 +849,7 @@ public class ECMA48Terminal extends LogicalScreen
         public String emitPalette(final StringBuilder sb,
             final boolean [] used) {
 
-            for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+            for (int i = 0; i < sixelPaletteSize; i++) {
                 if (((used != null) && (used[i] == true)) || (used == null)) {
                     int rgbColor = rgbColors.get(i);
                     sb.append(String.format("#%d;2;%d;%d;%d", i,
@@ -1380,6 +1379,27 @@ public class ECMA48Terminal extends LogicalScreen
         } else {
             sixel = false;
         }
+
+        // Palette size
+        int paletteSize = 1024;
+        try {
+            paletteSize = Integer.parseInt(System.getProperty(
+                "jexer.ECMA48.sixelPaletteSize", "1024"));
+            switch (paletteSize) {
+            case 2:
+            case 256:
+            case 512:
+            case 1024:
+            case 2048:
+                sixelPaletteSize = paletteSize;
+                break;
+            default:
+                // Ignore value
+                break;
+            }
+        } catch (NumberFormatException e) {
+            // SQUASH
+        }
     }
 
     // ------------------------------------------------------------------------
@@ -2748,6 +2768,46 @@ public class ECMA48Terminal extends LogicalScreen
     // Sixel output support ---------------------------------------------------
     // ------------------------------------------------------------------------
 
+    /**
+     * Get the number of colors in the sixel palette.
+     *
+     * @return the palette size
+     */
+    public int getSixelPaletteSize() {
+        return sixelPaletteSize;
+    }
+
+    /**
+     * Set the number of colors in the sixel palette.
+     *
+     * @param paletteSize the new palette size
+     */
+    public void setSixelPaletteSize(final int paletteSize) {
+        if (paletteSize == sixelPaletteSize) {
+            return;
+        }
+
+        switch (paletteSize) {
+        case 2:
+        case 256:
+        case 512:
+        case 1024:
+        case 2048:
+            break;
+        default:
+            throw new IllegalArgumentException("Unsupported sixel palette " +
+                " size: " + paletteSize);
+        }
+
+        // Don't step on the screen refresh thread.
+        synchronized (this) {
+            sixelPaletteSize = paletteSize;
+            palette = null;
+            sixelCache = null;
+            clearPhysical();
+        }
+    }
+
     /**
      * Start a sixel string for display one row's worth of bitmap data.
      *
@@ -2944,7 +3004,7 @@ public class ECMA48Terminal extends LogicalScreen
 
         // Emit the palette, but only for the colors actually used by these
         // cells.
-        boolean [] usedColors = new boolean[MAX_COLOR_REGISTERS];
+        boolean [] usedColors = new boolean[sixelPaletteSize];
         for (int imageX = 0; imageX < image.getWidth(); imageX++) {
             for (int imageY = 0; imageY < image.getHeight(); imageY++) {
                 usedColors[image.getRGB(imageX, imageY)] = true;
@@ -2964,13 +3024,13 @@ public class ECMA48Terminal extends LogicalScreen
 
                     int colorIdx = image.getRGB(imageX, imageY + currentRow);
                     assert (colorIdx >= 0);
-                    assert (colorIdx < MAX_COLOR_REGISTERS);
+                    assert (colorIdx < sixelPaletteSize);
 
                     sixels[imageX][imageY] = colorIdx;
                 }
             }
 
-            for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+            for (int i = 0; i < sixelPaletteSize; i++) {
                 boolean isUsed = false;
                 for (int imageX = 0; imageX < image.getWidth(); imageX++) {
                     for (int j = 0; j < 6; j++) {
@@ -3047,7 +3107,7 @@ public class ECMA48Terminal extends LogicalScreen
                     sb.append((char) oldData);
                 }
 
-            } // for (int i = 0; i < MAX_COLOR_REGISTERS; i++)
+            } // for (int i = 0; i < sixelPaletteSize; i++)
 
             // Advance to the next scan line.
             sb.append("-");