From 24f4f16bde4e7d892daf1093cad262227ba3943a Mon Sep 17 00:00:00 2001
From: stepan <stepan.sindelar@oracle.com>
Date: Tue, 23 May 2017 13:22:19 +0200
Subject: [PATCH] FastR Grid: color palette support.

---
 .../fastrGrid/FastRGridExternalLookup.java    |   6 +
 .../truffle/r/library/fastrGrid/GPar.java     |  50 +++++++-
 .../r/library/fastrGrid/GridColorUtils.java   |  21 +++
 .../r/library/fastrGrid/GridState.java        |  32 +++++
 .../r/library/fastrGrid/PaletteExternals.java | 121 ++++++++++++++++++
 .../r/library/fastrGrid/device/GridColor.java |  12 ++
 mx.fastr/copyrights/overrides                 |   1 +
 7 files changed, 241 insertions(+), 2 deletions(-)
 create mode 100644 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/PaletteExternals.java

diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/FastRGridExternalLookup.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/FastRGridExternalLookup.java
index d45d0684ae..016b8d9e98 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/FastRGridExternalLookup.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/FastRGridExternalLookup.java
@@ -25,6 +25,8 @@ package com.oracle.truffle.r.library.fastrGrid;
 import com.oracle.truffle.r.library.fastrGrid.DisplayList.LGetDisplayListElement;
 import com.oracle.truffle.r.library.fastrGrid.DisplayList.LInitDisplayList;
 import com.oracle.truffle.r.library.fastrGrid.DisplayList.LSetDisplayListOn;
+import com.oracle.truffle.r.library.fastrGrid.PaletteExternals.CPalette;
+import com.oracle.truffle.r.library.fastrGrid.PaletteExternals.CPalette2;
 import com.oracle.truffle.r.library.fastrGrid.grDevices.DevCairo;
 import com.oracle.truffle.r.library.fastrGrid.grDevices.DevCurr;
 import com.oracle.truffle.r.library.fastrGrid.grDevices.DevHoldFlush;
@@ -75,6 +77,10 @@ public final class FastRGridExternalLookup {
                 return SavePlot.create();
             case "X11":
                 return new InitWindowedDevice();
+            case "palette":
+                return CPalette.create();
+            case "palette2":
+                return CPalette2.create();
             default:
                 return null;
         }
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GPar.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GPar.java
index 2e46872077..48e5ca2d58 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GPar.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GPar.java
@@ -18,6 +18,7 @@ import static com.oracle.truffle.r.library.fastrGrid.GridUtils.getDataAtMod;
 import java.util.Arrays;
 import java.util.function.Function;
 
+import com.oracle.truffle.r.library.fastrGrid.GridState.GridPalette;
 import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext;
 import com.oracle.truffle.r.library.fastrGrid.device.DrawingContextDefaults;
 import com.oracle.truffle.r.library.fastrGrid.device.GridColor;
@@ -25,6 +26,7 @@ import com.oracle.truffle.r.library.fastrGrid.device.GridDevice;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RError.Message;
 import com.oracle.truffle.r.runtime.RRuntime;
+import com.oracle.truffle.r.runtime.Utils;
 import com.oracle.truffle.r.runtime.data.RDataFactory;
 import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RNull;
@@ -265,15 +267,21 @@ public final class GPar {
 
         private GridColor getGridColor(int listIndex) {
             Object value = data[listIndex];
+            GridColor color = getPaletteColor(value);
+            if (color != null) {
+                return color;
+            }
+
             String strValue = null;
             if (value instanceof String) {
                 strValue = (String) value;
             } else if (value instanceof RAbstractStringVector && ((RAbstractStringVector) value).getLength() > 0) {
-                strValue = ((RAbstractStringVector) value).getDataAt(listIndex % ((RAbstractStringVector) value).getLength());
+                strValue = ((RAbstractStringVector) value).getDataAt(index % ((RAbstractStringVector) value).getLength());
             } else {
                 return GridColor.TRANSPARENT;
             }
-            GridColor color = GridColorUtils.gridColorFromString(strValue);
+
+            color = GridColorUtils.gridColorFromString(strValue);
             double alpha = asDouble(data[GP_ALPHA], index);
             if (alpha != 1.) {
                 int newAlpha = Math.min(255, (int) (alpha * ((color.getAlpha() / 255.0) * 255)));
@@ -283,6 +291,44 @@ public final class GPar {
             }
         }
 
+        private GridColor getPaletteColor(Object colorIdIn) {
+            Object colorId = colorIdIn;
+            if (colorId instanceof RAbstractVector) {
+                RAbstractVector vec = (RAbstractVector) colorId;
+                colorId = vec.getDataAtAsObject(index % vec.getLength());
+            }
+            int paletteIdx = RRuntime.INT_NA;
+            if (colorId instanceof Integer) {
+                paletteIdx = (int) colorId;
+            } else if (colorId instanceof Double && !RRuntime.isNA((Double) colorId)) {
+                paletteIdx = (int) (double) colorId;
+            } else if (colorId instanceof String && !RRuntime.isNA((String) colorId)) {
+                paletteIdx = paletteIdxFromString((String) colorId);
+            } else if (colorId instanceof Byte && !RRuntime.isNA((byte) colorId)) {
+                paletteIdx = (int) (byte) colorId;
+            }
+            if (RRuntime.isNA(paletteIdx)) {
+                return null;
+            }
+            if (paletteIdx < 0) {
+                throw RError.error(RError.NO_CALLER, Message.GENERIC, Utils.stringFormat("numerical color values must be >= 0, found %d", paletteIdx));
+            }
+            if (paletteIdx == 0) {
+                return GridColor.TRANSPARENT;
+            }
+            GridPalette palette = GridContext.getContext().getGridState().getPalette();
+            GridColor result = palette.colors[(paletteIdx - 1) % palette.colors.length];
+            return result; // one based index
+        }
+
+        private int paletteIdxFromString(String colorId) {
+            try {
+                return Integer.parseInt(colorId, 10);
+            } catch (NumberFormatException ex) {
+                return RRuntime.INT_NA;
+            }
+        }
+
         private static final byte[] DASHED_LINE = new byte[]{4, 4};
         private static final byte[] DOTTED_LINE = new byte[]{1, 3};
         private static final byte[] DOTDASH_LINE = new byte[]{1, 3, 4, 3};
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java
index 9ab71e8923..1ec25bf728 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java
@@ -14,15 +14,36 @@ package com.oracle.truffle.r.library.fastrGrid;
 import java.util.HashMap;
 import java.util.Locale;
 
+import com.oracle.truffle.r.library.fastrGrid.GridState.GridPalette;
 import com.oracle.truffle.r.library.fastrGrid.device.GridColor;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RError.Message;
 
 public final class GridColorUtils {
+
+    private static GridPalette defaultPalette;
+
     private GridColorUtils() {
         // only static members
     }
 
+    public static GridPalette getDefaultPalette() {
+        if (defaultPalette == null) {
+            // Note: default palette copied from GNU R
+            defaultPalette = new GridPalette(new String[]{
+                            "black",
+                            "red",
+                            "green3",
+                            "blue",
+                            "cyan",
+                            "magenta",
+                            "yellow",
+                            "grey"
+            });
+        }
+        return defaultPalette;
+    }
+
     /**
      * Converts the representation of color used within R, e.g. as value for
      * {@code gpar(col='value')}, to our internal representation that grid device should understand.
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridState.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridState.java
index 63585c1837..20027f9800 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridState.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridState.java
@@ -13,6 +13,7 @@ package com.oracle.truffle.r.library.fastrGrid;
 
 import java.util.function.Supplier;
 
+import com.oracle.truffle.r.library.fastrGrid.device.GridColor;
 import com.oracle.truffle.r.library.fastrGrid.device.GridDevice;
 import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RNull;
@@ -21,6 +22,7 @@ import com.oracle.truffle.r.runtime.env.REnvironment;
 public final class GridState {
     private REnvironment gridEnv;
     private GridDeviceState devState;
+    private GridPalette palette;
 
     /**
      * Current grob being drawn (for determining the list of grobs to search when evaluating a
@@ -87,6 +89,14 @@ public final class GridState {
         devState.displayListIndex = newValue;
     }
 
+    public GridPalette getPalette() {
+        return palette == null ? GridColorUtils.getDefaultPalette() : palette;
+    }
+
+    public void setPalette(GridPalette palette) {
+        this.palette = palette;
+    }
+
     public void init(REnvironment gridEnv) {
         this.gridEnv = gridEnv;
         this.currentGrob = RNull.instance;
@@ -153,6 +163,28 @@ public final class GridState {
         return devState.scale;
     }
 
+    public static final class GridPalette {
+        public final GridColor[] colors;
+        public final String[] colorNames;
+
+        public GridPalette(String[] colorNames) {
+            this.colorNames = colorNames;
+            colors = new GridColor[colorNames.length];
+            for (int i = 0; i < colorNames.length; i++) {
+                colors[i] = GridColorUtils.gridColorFromString(colorNames[i]);
+            }
+        }
+
+        public GridPalette(int[] colors) {
+            this.colors = new GridColor[colors.length];
+            colorNames = new String[colors.length];
+            for (int i = 0; i < colors.length; i++) {
+                this.colors[i] = GridColor.fromRawValue(colors[i]);
+                colorNames[i] = GridColorUtils.gridColorToRString(this.colors[i]);
+            }
+        }
+    }
+
     static final class GridDeviceState {
         private boolean isDeviceInitialized = false;
         private RList gpar;
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/PaletteExternals.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/PaletteExternals.java
new file mode 100644
index 0000000000..67646f37e3
--- /dev/null
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/PaletteExternals.java
@@ -0,0 +1,121 @@
+/*
+ * This material is distributed under the GNU General Public License
+ * Version 2. You may review the terms of this license at
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * Copyright (c) 1997-2014, The R Core Team
+ * Copyright (c) 2003, The R Foundation
+ * Copyright (c) 2017, Oracle and/or its affiliates
+ *
+ * All rights reserved.
+ */
+package com.oracle.truffle.r.library.fastrGrid;
+
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.abstractVectorValue;
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.stringValue;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.r.library.fastrGrid.GridState.GridPalette;
+import com.oracle.truffle.r.library.fastrGrid.PaletteExternalsFactory.CPalette2NodeGen;
+import com.oracle.truffle.r.library.fastrGrid.PaletteExternalsFactory.CPaletteNodeGen;
+import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
+import com.oracle.truffle.r.runtime.RError.Message;
+import com.oracle.truffle.r.runtime.RRuntime;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RIntVector;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
+
+public final class PaletteExternals {
+    private PaletteExternals() {
+        // only static members
+    }
+
+    /**
+     * Implements external {@code C_palette} used in palette R function.
+     */
+    public abstract static class CPalette extends RExternalBuiltinNode.Arg1 {
+
+        static {
+            Casts casts = new Casts(CPalette.class);
+            casts.arg(0).mustBe(stringValue());
+        }
+
+        public static CPalette create() {
+            return CPaletteNodeGen.create();
+        }
+
+        @Specialization
+        @TruffleBoundary
+        public RStringVector updatePalette(RAbstractStringVector palette) {
+            GridState state = GridContext.getContext().getGridState();
+            GridPalette newPalette = null;
+            if (palette.getLength() == 1) {
+                if (palette.getDataAt(0).toLowerCase().equals("default")) {
+                    newPalette = GridColorUtils.getDefaultPalette();
+                } else {
+                    throw error(Message.GENERIC, "unknown palette (need >= 2 colors)");
+                }
+            } else if (palette.getLength() > 1) {
+                newPalette = new GridPalette(palette.materialize().getDataCopy());
+            }
+
+            String[] original = state.getPalette().colorNames;
+            if (newPalette != null) {
+                // the contract is that if the argument's length is zero, we only return the palette
+                // and
+                // do not set anything.
+                state.setPalette(newPalette);
+            }
+            // don't know the completeness, assume the worst rather than finding out
+            RStringVector result = RDataFactory.createStringVector(original, RDataFactory.INCOMPLETE_VECTOR);
+            result.makeSharedPermanent();
+            return result;
+        }
+    }
+
+    /**
+     * Implements external {@code C_palette2} used only internally in grDevices, the parameter are
+     * colors encoded in integer already.
+     */
+    public abstract static class CPalette2 extends RExternalBuiltinNode.Arg1 {
+        static {
+            Casts casts = new Casts(CPalette2.class);
+            casts.arg(0).mustBe(abstractVectorValue());
+        }
+
+        public static CPalette2 create() {
+            return CPalette2NodeGen.create();
+        }
+
+        @Specialization
+        @TruffleBoundary
+        public RIntVector updatePalette(RAbstractVector palette) {
+            GridState state = GridContext.getContext().getGridState();
+            int[] newPalette = null;
+            if (palette.getLength() > 0) {
+                RAbstractIntVector data = GridUtils.asIntVector(palette);
+                newPalette = data.materialize().getDataCopy();
+            }
+            RIntVector result = getResult(state);
+            if (newPalette != null) {
+                state.setPalette(new GridPalette(newPalette));
+            }
+            return result;
+        }
+
+        private static RIntVector getResult(GridState state) {
+            GridPalette palette = state.getPalette();
+            int[] result = new int[palette.colors.length];
+            boolean complete = true;
+            for (int i = 0; i < result.length; i++) {
+                result[i] = palette.colors[i].getRawValue();
+                complete &= !RRuntime.isNA(result[i]);
+            }
+            return RDataFactory.createIntVector(result, complete);
+        }
+    }
+}
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/GridColor.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/GridColor.java
index 28abf944f1..ec11023ea1 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/GridColor.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/GridColor.java
@@ -33,6 +33,10 @@ public final class GridColor {
 
     private final int value;
 
+    private GridColor(int value) {
+        this.value = value;
+    }
+
     public GridColor(int red, int green, int blue, int alpha) {
         value = ((alpha & 0xFF) << 24) |
                         ((red & 0xFF) << 16) |
@@ -40,6 +44,10 @@ public final class GridColor {
                         (blue & 0xFF);
     }
 
+    public static GridColor fromRawValue(int value) {
+        return new GridColor(value);
+    }
+
     public int getRed() {
         return (value >> 16) & 0xFF;
     }
@@ -56,6 +64,10 @@ public final class GridColor {
         return (value >> 24) & 0xff;
     }
 
+    public int getRawValue() {
+        return value;
+    }
+
     @Override
     public boolean equals(Object obj) {
         return obj instanceof GridColor && value == ((GridColor) obj).value;
diff --git a/mx.fastr/copyrights/overrides b/mx.fastr/copyrights/overrides
index 3479fff1b8..b2505b0cb6 100644
--- a/mx.fastr/copyrights/overrides
+++ b/mx.fastr/copyrights/overrides
@@ -770,6 +770,7 @@ com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridLine
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPortTransform.java,gnu_r_murrel_core.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LGridDirty.java,gnu_r_murrel_core.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java,gnu_r.copyright
+com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/PaletteExternals.java,gnu_r.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LCircle.java,gnu_r_murrel_core.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/EdgeDetection.java,gnu_r_murrel_core.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridTextNode.java,gnu_r_murrel_core.copyright
-- 
GitLab