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 e3555f6a22a5e967eef83646ee3d1300c28d055b..2999a620a3a8f28d5f7cc8d7baa51bccf19a656c 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
@@ -27,6 +27,8 @@ 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.color.Col2RGB;
+import com.oracle.truffle.r.library.fastrGrid.color.RGB;
 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;
@@ -184,6 +186,12 @@ public final class FastRGridExternalLookup {
             case "newpagerecording":
                 return new IgnoredGridExternal(RNull.instance);
 
+            // Color conversions
+            case "col2rgb":
+                return Col2RGB.create();
+            case "rgb":
+                return RGB.create();
+
             default:
                 return null;
         }
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 c751e57f43cddaab2e1f1f8cf82918e246fffff6..217e5a92c015d088d6a94eff1c2b277a406dec3d 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
@@ -13,10 +13,12 @@ package com.oracle.truffle.r.library.fastrGrid;
 
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.Map;
 
 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.RInternalError;
 import com.oracle.truffle.r.runtime.RError.Message;
 import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.Utils;
@@ -86,15 +88,11 @@ public final class GridColorUtils {
             return GridColor.TRANSPARENT;
         }
 
-        Object result = findByName(value);
+        GridColor result = findByName(value);
         if (result == null) {
             throw RError.error(RError.NO_CALLER, Message.GENERIC, "Invalid color '" + value + "'.");
         }
-        if (result instanceof String) {
-            return parseHex((String) result);
-        }
-        assert result instanceof GridColor : "synonyms map should only contain Strings and GridColors";
-        return (GridColor) result;
+        return result;
     }
 
     public static String gridColorToRString(GridColor color) {
@@ -104,7 +102,7 @@ public final class GridColorUtils {
         return String.format("#%02x%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
     }
 
-    private static GridColor parseHex(String value) {
+    public static GridColor parseHex(String value) {
         // hex format, e.g. #ffffff
         int red = Integer.parseInt(value.substring(1, 3), 16);
         int green = Integer.parseInt(value.substring(3, 5), 16);
@@ -116,8 +114,8 @@ public final class GridColorUtils {
         return new GridColor(red, green, blue, alpha);
     }
 
-    private static Object findByName(String synonym) {
-        return NamesHolder.NAMES.get(normalizeColorName(synonym));
+    private static GridColor findByName(String synonym) {
+        return (GridColor) NamesHolder.NAMES.get(normalizeColorName(synonym));
     }
 
     // GnuR compares the user given color name to the dictionary ignoring spaces and case. We remove
@@ -162,11 +160,7 @@ public final class GridColorUtils {
     }
 
     private static int paletteIdxFromString(String colorId) {
-        try {
-            return Integer.parseInt(colorId, 10);
-        } catch (NumberFormatException ex) {
-            return RRuntime.INT_NA;
-        }
+        return RRuntime.parseIntWithNA(colorId);
     }
 
     private static final class NamesHolder {
@@ -831,6 +825,15 @@ public final class GridColorUtils {
             NAMES.put("yellow3", "#CDCD00");
             NAMES.put("yellow4", "#8B8B00");
             NAMES.put("yellowgreen", "#9ACD32");
+            for (Map.Entry<String, Object> entry : NAMES.entrySet()) {
+                if (entry.getValue() instanceof GridColor) {
+                    // nothing to do
+                } else if (entry.getValue() instanceof String) {
+                    entry.setValue(parseHex((String) entry.getValue()));
+                } else {
+                    throw RInternalError.shouldNotReachHere();
+                }
+            }
         }
     }
 }
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/Col2RGB.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/Col2RGB.java
new file mode 100644
index 0000000000000000000000000000000000000000..3afaee6ca7f692e6b86148c1165ee05d18b886c1
--- /dev/null
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/Col2RGB.java
@@ -0,0 +1,126 @@
+/*
+ * 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.color;
+
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.asIntegerVector;
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.asStringVector;
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.doubleValue;
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.integerValue;
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.logicalValue;
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.toBoolean;
+
+import java.util.Arrays;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.r.library.fastrGrid.GridColorUtils;
+import com.oracle.truffle.r.library.fastrGrid.GridContext;
+import com.oracle.truffle.r.library.fastrGrid.GridState.GridPalette;
+import com.oracle.truffle.r.library.fastrGrid.device.GridColor;
+import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.GetNamesAttributeNode;
+import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
+import com.oracle.truffle.r.nodes.function.opt.ShareObjectNode;
+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.RList;
+import com.oracle.truffle.r.runtime.data.RNull;
+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 abstract class Col2RGB extends RExternalBuiltinNode.Arg2 {
+
+    private static final RStringVector NAMES = ShareObjectNode.sharePermanent(RDataFactory.createStringVector(new String[]{"red", "green", "blue"}, true));
+    private static final RStringVector NAMES_ALPHA = ShareObjectNode.sharePermanent(RDataFactory.createStringVector(new String[]{"red", "green", "blue", "alpha"}, true));
+    private static final GridColor TRANSPARENT_WHITE = new GridColor(255, 255, 255, 0);
+
+    static {
+        Casts casts = new Casts(Col2RGB.class);
+        casts.arg(0).mapIf(integerValue().or(doubleValue()), asIntegerVector(), asStringVector());
+        casts.arg(1).mustBe(logicalValue()).asLogicalVector().findFirst().map(toBoolean());
+    }
+
+    public static Col2RGB create() {
+        return Col2RGBNodeGen.create();
+    }
+
+    @Specialization
+    @TruffleBoundary
+    Object execute(RAbstractVector col, boolean alpha,
+                    @Cached("create()") GetNamesAttributeNode getNames) {
+        int length = col.getLength();
+        int columns = alpha ? 4 : 3;
+        int[] result = new int[length * columns];
+
+        GridPalette palette = GridContext.getContext().getGridState().getPalette();
+        int pos = 0;
+        if (col instanceof RAbstractIntVector) {
+            RAbstractIntVector vector = (RAbstractIntVector) col;
+
+            for (int i = 0; i < length; i++) {
+                int value = vector.getDataAt(i);
+                GridColor color;
+                if (RRuntime.isNA(value) || value == 0) {
+                    color = TRANSPARENT_WHITE;
+                } else if (value < 0) {
+                    throw error(Message.GENERIC, "numerical color values must be >= 0, found " + value);
+                } else {
+                    color = palette.colors[(value - 1) % palette.colors.length];
+                }
+                pos = addColor(alpha, result, pos, color);
+            }
+        } else if (col instanceof RAbstractStringVector) {
+            RAbstractStringVector vector = (RAbstractStringVector) col;
+
+            for (int i = 0; i < length; i++) {
+                String value = vector.getDataAt(i);
+                GridColor color;
+                if (value.length() > 0 && value.charAt(0) >= '0' && value.charAt(0) <= '9') {
+                    int index = RRuntime.parseIntWithNA(value);
+                    if (RRuntime.isNA(index)) {
+                        throw error(Message.GENERIC, "invalid color specification \"" + value + "\"");
+                    } else if (index == 0) {
+                        color = TRANSPARENT_WHITE;
+                    } else {
+                        color = palette.colors[(index - 1) % palette.colors.length];
+                    }
+                } else if ("transparent".equals(value)) {
+                    color = TRANSPARENT_WHITE;
+                } else {
+                    color = GridColorUtils.gridColorFromString(value);
+                }
+                pos = addColor(alpha, result, pos, color);
+            }
+        } else {
+            warning(Message.GENERIC, "supplied color is neither numeric nor character");
+            Arrays.fill(result, RRuntime.INT_NA);
+        }
+
+        RStringVector names = getNames.getNames(col);
+        RList dimNames = RDataFactory.createList(new Object[]{alpha ? NAMES_ALPHA : NAMES, names == null ? RNull.instance : names});
+        return RDataFactory.createIntVector(result, false, new int[]{columns, length}, null, dimNames);
+    }
+
+    private static int addColor(boolean alpha, int[] result, int start, GridColor color) {
+        int pos = start;
+        result[pos++] = color.getRed();
+        result[pos++] = color.getGreen();
+        result[pos++] = color.getBlue();
+        if (alpha) {
+            result[pos++] = color.getAlpha();
+        }
+        return pos;
+    }
+}
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/RGB.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/RGB.java
new file mode 100644
index 0000000000000000000000000000000000000000..e599a553cd1281d538dc5415285827afc3e25835
--- /dev/null
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/RGB.java
@@ -0,0 +1,273 @@
+/*
+ * 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.color;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.dsl.TypeSystemReference;
+import com.oracle.truffle.r.library.fastrGrid.color.RGBNodeGen.DoubleRGBNodeGen;
+import com.oracle.truffle.r.library.fastrGrid.color.RGBNodeGen.IntegerRGBNodeGen;
+import com.oracle.truffle.r.library.fastrGrid.device.GridColor;
+import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
+import com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef;
+import com.oracle.truffle.r.nodes.unary.CastDoubleNode;
+import com.oracle.truffle.r.nodes.unary.CastIntegerNode;
+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.RNull;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+import com.oracle.truffle.r.runtime.data.RTypes;
+import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
+import com.oracle.truffle.r.runtime.nodes.RBaseNode;
+
+public abstract class RGB extends RExternalBuiltinNode.Arg6 {
+
+    static {
+        Casts casts = new Casts(RGB.class);
+        casts.arg(4).asDoubleVector().findFirst();
+        casts.arg(5).mapNull(Predef.emptyStringVector()).asStringVector();
+    }
+
+    public static RGB create() {
+        return RGBNodeGen.create();
+    }
+
+    protected static boolean isIntRange(double mcv) {
+        return mcv == 255;
+    }
+
+    @Specialization(guards = "isIntRange(mcv)")
+    @TruffleBoundary
+    Object doInteger(Object r, Object g, Object b, Object a, @SuppressWarnings("unused") double mcv, RAbstractStringVector names,
+                    @Cached("create()") CastIntegerNode castR,
+                    @Cached("create()") CastIntegerNode castG,
+                    @Cached("create()") CastIntegerNode castB,
+                    @Cached("create()") CastIntegerNode castA,
+                    @Cached("createInteger()") IntegerRGB inner) {
+        return inner.execute(castR.doCast(r), castG.doCast(g), castB.doCast(b), a == RNull.instance ? RNull.instance : castA.doCast(a), names);
+    }
+
+    @Specialization(guards = "!isIntRange(mcv)")
+    @TruffleBoundary
+    Object doDouble(Object r, Object g, Object b, Object a, double mcv, RAbstractStringVector names,
+                    @Cached("create()") CastDoubleNode castR,
+                    @Cached("create()") CastDoubleNode castG,
+                    @Cached("create()") CastDoubleNode castB,
+                    @Cached("create()") CastDoubleNode castA,
+                    @Cached("createDouble()") DoubleRGB inner) {
+        if (!Double.isFinite(mcv)) {
+            throw error(Message.GENERIC, "invalid value of 'maxColorValue'");
+        }
+        return inner.execute(castR.doCast(r), castG.doCast(g), castB.doCast(b), a == RNull.instance ? RNull.instance : castA.doCast(a), mcv, names);
+    }
+
+    protected static IntegerRGB createInteger() {
+        return IntegerRGBNodeGen.create();
+    }
+
+    protected static DoubleRGB createDouble() {
+        return DoubleRGBNodeGen.create();
+    }
+
+    @TypeSystemReference(RTypes.class)
+    abstract static class RGBBase extends RBaseNode {
+
+        protected static int addColor(boolean alpha, int[] result, int start, GridColor color) {
+            int pos = start;
+            result[pos++] = color.getRed();
+            result[pos++] = color.getGreen();
+            result[pos++] = color.getBlue();
+            if (alpha) {
+                result[pos++] = color.getAlpha();
+            }
+            return pos;
+        }
+
+        private static final char[] HexDigits = "0123456789ABCDEF".toCharArray();
+
+        protected static String rgb2rgb(int r, int g, int b) {
+            char[] colBuf = new char[7];
+            colBuf[0] = '#';
+            colBuf[1] = HexDigits[(r >> 4) & 15];
+            colBuf[2] = HexDigits[r & 15];
+            colBuf[3] = HexDigits[(g >> 4) & 15];
+            colBuf[4] = HexDigits[g & 15];
+            colBuf[5] = HexDigits[(b >> 4) & 15];
+            colBuf[6] = HexDigits[b & 15];
+            return new String(colBuf);
+        }
+
+        protected static String rgba2rgb(int r, int g, int b, int a) {
+            char[] colBuf = new char[9];
+            colBuf[0] = '#';
+            colBuf[1] = HexDigits[(r >> 4) & 15];
+            colBuf[2] = HexDigits[r & 15];
+            colBuf[3] = HexDigits[(g >> 4) & 15];
+            colBuf[4] = HexDigits[g & 15];
+            colBuf[5] = HexDigits[(b >> 4) & 15];
+            colBuf[6] = HexDigits[b & 15];
+            colBuf[7] = HexDigits[(a >> 4) & 15];
+            colBuf[8] = HexDigits[a & 15];
+            return new String(colBuf);
+        }
+
+        protected int scaleColor(double x) {
+            if (RRuntime.isNA(x)) {
+                error(Message.GENERIC, "color intensity NA, not in [0,1]");
+            }
+            if (!Double.isFinite(x) || x < 0.0 || x > 1.0) {
+                error(Message.GENERIC, "color intensity " + x + ", not in [0,1]");
+            }
+            return (int) (255 * x + 0.5);
+        }
+
+        protected int checkColor(int x) {
+            if (RRuntime.isNA(x)) {
+                error(Message.GENERIC, "color intensity NA, not in 0:255");
+            }
+            if (x < 0 || x > 255) {
+                error(Message.GENERIC, "color intensity " + x + ", not in 0:255");
+            }
+            return x;
+        }
+
+        protected int scaleAlpha(double x) {
+            if (RRuntime.isNA(x)) {
+                error(Message.GENERIC, "alpha level NA, not in [0,1]");
+            }
+            if (!Double.isFinite(x) || x < 0.0 || x > 1.0) {
+                error(Message.GENERIC, "alpha level " + x + ", not in [0,1]");
+            }
+            return (int) (255 * x + 0.5);
+        }
+
+        protected int checkAlpha(int x) {
+            if (RRuntime.isNA(x)) {
+                error(Message.GENERIC, "alpha level NA, not in 0:255");
+            }
+            if (x < 0 || x > 255) {
+                error(Message.GENERIC, "alpha level " + x + ", not in 0:255");
+            }
+            return x;
+        }
+    }
+
+    abstract static class IntegerRGB extends RGBBase {
+
+        public abstract RStringVector execute(Object r, Object g, Object b, Object a, RAbstractStringVector names);
+
+        @Specialization
+        @TruffleBoundary
+        protected RStringVector doAlpha(RAbstractIntVector r, RAbstractIntVector g, RAbstractIntVector b, RAbstractIntVector a, RAbstractStringVector names) {
+            int lengthR = r.getLength();
+            int lengthG = g.getLength();
+            int lengthB = b.getLength();
+            int lengthA = a.getLength();
+            if (lengthR == 0 || lengthG == 0 || lengthB == 0 || lengthA == 0) {
+                return RDataFactory.createEmptyStringVector();
+            }
+            int length = Math.max(Math.max(lengthR, lengthG), Math.max(lengthB, lengthA));
+            if (names.getLength() != 0 && names.getLength() != length) {
+                throw error(Message.GENERIC, "invalid 'names' vector");
+            }
+            String[] result = new String[length];
+            for (int i = 0; i < length; i++) {
+                int rValue = r.getDataAt(i % lengthR);
+                int gValue = g.getDataAt(i % lengthG);
+                int bValue = b.getDataAt(i % lengthB);
+                int aValue = a.getDataAt(i % lengthA);
+                result[i] = rgba2rgb(checkColor(rValue), checkColor(gValue), checkColor(bValue), checkAlpha(aValue));
+            }
+            return RDataFactory.createStringVector(result, true, names.getLength() == 0 ? null : names.materialize());
+        }
+
+        @Specialization
+        @TruffleBoundary
+        protected RStringVector doNonAlpha(RAbstractIntVector r, RAbstractIntVector g, RAbstractIntVector b, @SuppressWarnings("unused") RNull a, RAbstractStringVector names) {
+            int lengthR = r.getLength();
+            int lengthG = g.getLength();
+            int lengthB = b.getLength();
+            if (lengthR == 0 || lengthG == 0 || lengthB == 0) {
+                return RDataFactory.createEmptyStringVector();
+            }
+            int length = Math.max(Math.max(lengthR, lengthG), lengthB);
+            if (names.getLength() != 0 && names.getLength() != length) {
+                throw error(Message.GENERIC, "invalid 'names' vector");
+            }
+            String[] result = new String[length];
+            for (int i = 0; i < length; i++) {
+                int rValue = r.getDataAt(i % lengthR);
+                int gValue = g.getDataAt(i % lengthG);
+                int bValue = b.getDataAt(i % lengthB);
+                result[i] = rgb2rgb(checkColor(rValue), checkColor(gValue), checkColor(bValue));
+            }
+            return RDataFactory.createStringVector(result, true, names.getLength() == 0 ? null : names.materialize());
+        }
+    }
+
+    abstract static class DoubleRGB extends RGBBase {
+
+        public abstract RStringVector execute(Object r, Object g, Object b, Object a, double mcv, RAbstractStringVector names);
+
+        @Specialization
+        @TruffleBoundary
+        protected RStringVector doAlpha(RAbstractDoubleVector r, RAbstractDoubleVector g, RAbstractDoubleVector b, RAbstractDoubleVector a, double mcv, RAbstractStringVector names) {
+            int lengthR = r.getLength();
+            int lengthG = g.getLength();
+            int lengthB = b.getLength();
+            int lengthA = a.getLength();
+            if (lengthR == 0 || lengthG == 0 || lengthB == 0 || lengthA == 0) {
+                return RDataFactory.createEmptyStringVector();
+            }
+            int length = Math.max(Math.max(lengthR, lengthG), Math.max(lengthB, lengthA));
+            if (names.getLength() != 0 && names.getLength() != length) {
+                throw error(Message.GENERIC, "invalid 'names' vector");
+            }
+            String[] result = new String[length];
+            for (int i = 0; i < length; i++) {
+                double rValue = r.getDataAt(i % lengthR);
+                double gValue = g.getDataAt(i % lengthG);
+                double bValue = b.getDataAt(i % lengthB);
+                double aValue = a.getDataAt(i % lengthA);
+                result[i] = rgba2rgb(scaleColor(rValue / mcv), scaleColor(gValue / mcv), scaleColor(bValue / mcv), scaleAlpha(aValue / mcv));
+            }
+            return RDataFactory.createStringVector(result, true, names.getLength() == 0 ? null : names.materialize());
+        }
+
+        @Specialization
+        @TruffleBoundary
+        protected RStringVector doNonAlpha(RAbstractDoubleVector r, RAbstractDoubleVector g, RAbstractDoubleVector b, @SuppressWarnings("unused") RNull a, double mcv, RAbstractStringVector names) {
+            int lengthR = r.getLength();
+            int lengthG = g.getLength();
+            int lengthB = b.getLength();
+            if (lengthR == 0 || lengthG == 0 || lengthB == 0) {
+                return RDataFactory.createEmptyStringVector();
+            }
+            int length = Math.max(Math.max(lengthR, lengthG), lengthB);
+            if (names.getLength() != 0 && names.getLength() != length) {
+                throw error(Message.GENERIC, "invalid 'names' vector");
+            }
+            String[] result = new String[length];
+            for (int i = 0; i < length; i++) {
+                double rValue = r.getDataAt(i % lengthR);
+                double gValue = g.getDataAt(i % lengthG);
+                double bValue = b.getDataAt(i % lengthB);
+                result[i] = rgb2rgb(scaleColor(rValue / mcv), scaleColor(gValue / mcv), scaleColor(bValue / mcv));
+            }
+            return RDataFactory.createStringVector(result, true, names.getLength() == 0 ? null : names.materialize());
+        }
+    }
+}
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/TypeConvert.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/TypeConvert.java
index 00ef74c006c7d5e32b83af503cbd31168310c637..d4e59d4ed4f581143040f495f6a481d4e67bc1cd 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/TypeConvert.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/TypeConvert.java
@@ -35,8 +35,10 @@ import static com.oracle.truffle.r.runtime.RError.Message.INVALID_ARG;
 import static com.oracle.truffle.r.runtime.RRuntime.LOGICAL_FALSE;
 
 import java.util.Arrays;
-import java.util.TreeSet;
+import java.util.Map;
+import java.util.TreeMap;
 
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.r.nodes.attributes.SetFixedAttributeNode;
 import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
@@ -93,9 +95,16 @@ public abstract class TypeConvert extends RExternalBuiltinNode.Arg5 {
         data[firstPos] = firstVal;
         for (int i = firstPos + 1; i < data.length; i++) {
             String s = x.getDataAt(i);
-            boolean isNA = isNA(s, naStrings);
-            data[i] = isNA ? RRuntime.INT_NA : RRuntime.string2intNoCheck(s, true);
-            complete = complete && !isNA;
+            if (isNA(s, naStrings)) {
+                data[i] = RRuntime.INT_NA;
+                complete = false;
+            } else {
+                int result = RRuntime.parseInt(s);
+                if (result == RRuntime.INT_NA) {
+                    throw new NumberFormatException();
+                }
+                data[i] = result;
+            }
         }
         return RDataFactory.createIntVector(data, complete);
     }
@@ -133,13 +142,14 @@ public abstract class TypeConvert extends RExternalBuiltinNode.Arg5 {
     }
 
     @Specialization
+    @TruffleBoundary
     protected Object typeConvert(RAbstractStringVector x, RAbstractStringVector naStrings, boolean asIs, @SuppressWarnings("unused") Object dec, @SuppressWarnings("unused") Object numeral) {
         if (x.getLength() == 0) {
             return RDataFactory.createEmptyLogicalVector();
         }
 
         int i = 0;
-        while (i < x.getLength() && isNA(x.getDataAt(i), naStrings)) {
+        while (i < x.getLength() && (x.getDataAt(i).isEmpty() || isNA(x.getDataAt(i), naStrings))) {
             i++;
         }
 
@@ -153,7 +163,7 @@ public abstract class TypeConvert extends RExternalBuiltinNode.Arg5 {
         String s = x.getDataAt(i);
         if (RRuntime.hasHexPrefix(s)) {
             // this is a mess
-            // double takes precedense even if s is a hexadecimal integer
+            // double takes precedence even if s is a hexadecimal integer
             try {
                 double doubleVal = RRuntime.string2doubleNoCheck(s, true);
                 return readDoubleVector(x, i, doubleVal, naStrings);
@@ -191,35 +201,33 @@ public abstract class TypeConvert extends RExternalBuiltinNode.Arg5 {
         if (asIs) {
             return x;
         } else {
-            // create a factor
-            TreeSet<String> levels = new TreeSet<>();
+            // collect levels for a factor result
+            TreeMap<String, Integer> levels = new TreeMap<>();
             for (int j = 0; j < x.getLength(); j++) {
                 s = x.getDataAt(j);
                 if (!isNA(s, naStrings)) {
-                    levels.add(s);
+                    levels.put(s, 0);
                 }
             }
-            String[] levelsArray = new String[levels.size()];
-            levels.toArray(levelsArray);
+            // assign levels IDs
+            int pos = 1;
+            for (Map.Entry<String, Integer> entry : levels.entrySet()) {
+                entry.setValue(pos++);
+            }
 
             int[] data = new int[x.getLength()];
             boolean complete = true;
             for (int j = 0; j < data.length; j++) {
                 s = x.getDataAt(j);
                 if (!isNA(s, naStrings)) {
-                    for (int k = 0; k < levelsArray.length; k++) {
-                        if (levelsArray[k].equals(s)) {
-                            data[j] = k + 1;
-                            break;
-                        }
-                    }
+                    data[j] = levels.get(s);
                 } else {
                     data[j] = RRuntime.INT_NA;
                     complete = false;
                 }
             }
             RIntVector res = RDataFactory.createIntVector(data, complete);
-            setLevelsAttrNode.execute(res, RDataFactory.createStringVector(levelsArray, RDataFactory.COMPLETE_VECTOR));
+            setLevelsAttrNode.execute(res, RDataFactory.createStringVector(levels.keySet().toArray(new String[0]), RDataFactory.COMPLETE_VECTOR));
             return RVector.setVectorClassAttr(res, RDataFactory.createStringVector("factor"));
         }
     }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Scan.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Scan.java
index 56407899292ee54f97a8d16cc5cd73c52b1a6a3d..5a93494aad88e38ed8ba5009cdf78c0002047a28 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Scan.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Scan.java
@@ -28,8 +28,7 @@ import static com.oracle.truffle.r.runtime.builtins.RBehavior.IO;
 import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.INTERNAL;
 
 import java.io.IOException;
-import java.util.LinkedList;
-import java.util.regex.Pattern;
+import java.util.ArrayList;
 
 import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
@@ -80,10 +79,9 @@ public abstract class Scan extends RBuiltinNode.Arg19 {
     private static class LocalData {
         RAbstractStringVector naStrings = null;
         boolean quiet = false;
-        String sepchar = null;
-        String sepregex = null;
+        char sepchar = 0; // 0 means any whitespace
         char decchar = '.';
-        String quoteset = null;
+        char[] quoteset = new char[0];
         int comchar = NO_COMCHAR;
         // connection-related (currently not supported)
         // int ttyflag = 0;
@@ -152,14 +150,13 @@ public abstract class Scan extends RBuiltinNode.Arg19 {
         LocalData data = new LocalData();
 
         // TODO: some sort of character translation happens here?
-        data.sepchar = sep.isEmpty() ? null : sep.substring(0, 1);
-        data.sepregex = sep.isEmpty() ? null : Pattern.quote(sep.substring(0, 1));
+        data.sepchar = sep.isEmpty() ? 0 : sep.charAt(0);
 
         // TODO: some sort of character translation happens here?
         data.decchar = dec.charAt(0);
 
         // TODO: some sort of character translation happens here?
-        data.quoteset = quotes;
+        data.quoteset = quotes.toCharArray();
 
         data.naStrings = naStringsVec;
 
@@ -202,89 +199,75 @@ public abstract class Scan extends RBuiltinNode.Arg19 {
         }
     }
 
-    private static int getFirstQuoteInd(String str, char quotechar, Character sepChar) {
-        int quoteInd = str.indexOf(quotechar);
-        if (quoteInd >= 0) {
-            if (quoteInd == 0 || (str.charAt(quoteInd - 1) == ' ' || str.charAt(quoteInd - 1) == '\t') || sepChar != null && str.charAt(quoteInd - 1) == sepChar) {
-                // it's a quote character if it starts the string or is preceded by a blank space
-                return quoteInd;
-            }
+    private static int skipWhitespace(String s, int start) {
+        int pos = start;
+        while (pos < s.length() && (s.charAt(pos) == ' ' || s.charAt(pos) == '\t')) {
+            pos++;
         }
-        return -1;
+        return pos;
     }
 
-    private static String[] getQuotedItems(LocalData data, String s) {
-        LinkedList<String> items = new LinkedList<>();
-
-        String str = s;
-        StringBuilder sb = null;
-
-        while (true) {
-            int sepInd;
-            if (data.sepchar == null) {
-                int blInd = str.indexOf(' ');
-                int tabInd = str.indexOf('\t');
-                if (blInd == -1) {
-                    sepInd = tabInd;
-                } else if (tabInd == -1) {
-                    sepInd = blInd;
-                } else {
-                    sepInd = Math.min(blInd, tabInd);
-                }
-            } else {
-                assert data.sepchar.length() == 1;
-                sepInd = str.indexOf(data.sepchar.charAt(0));
+    private static boolean isInSet(char ch, char[] quoteset) {
+        for (int i = 0; i < quoteset.length; i++) {
+            if (ch == quoteset[i]) {
+                return true;
             }
+        }
+        return false;
+    }
 
-            Character sepChar = data.sepchar != null ? data.sepchar.charAt(0) : null;
-            int quoteInd = getFirstQuoteInd(str, data.quoteset.charAt(0), sepChar);
-            char quoteChar = data.quoteset.charAt(0);
-            for (int i = 1; i < data.quoteset.length(); i++) {
-                int ind = getFirstQuoteInd(str, data.quoteset.charAt(i), sepChar);
-                if (ind >= 0 && (quoteInd == -1 || (quoteInd >= 0 && ind < quoteInd))) {
-                    // update quoteInd if either the new index is smaller or the previous one (for
-                    // another separator) was not found
-                    quoteInd = ind;
-                    quoteChar = data.quoteset.charAt(i);
+    private static String[] getQuotedItems(LocalData data, String s) {
+        ArrayList<String> items = new ArrayList<>();
+
+        char sepchar = data.sepchar;
+        char[] quoteset = data.quoteset;
+        int length = s.length();
+        int pos = 0;
+        if (sepchar == 0) {
+            pos = skipWhitespace(s, pos);
+        }
+        if (pos == length) {
+            return new String[0];
+        }
+        StringBuilder str = new StringBuilder();
+        do {
+            char ch = s.charAt(pos);
+            if (sepchar == 0 && (ch == ' ' || ch == '\t')) {
+                pos = skipWhitespace(s, pos);
+                if (pos == length) {
+                    break;
                 }
-            }
-
-            if (sb == null) {
-                // first iteration
-                if (quoteInd == -1) {
-                    // no quotes at all
-                    return data.sepregex == null ? s.split("\\s+") : s.split(data.sepregex);
-                } else {
-                    sb = new StringBuilder();
+                items.add(str.toString());
+                str.setLength(0);
+            } else if (sepchar != 0 && ch == sepchar) {
+                pos++;
+                items.add(str.toString());
+                str.setLength(0);
+            } else if (str.length() == 0 && isInSet(ch, quoteset)) {
+                char quoteStart = ch;
+                pos++;
+                while (true) {
+                    if (pos == length) {
+                        throw RError.error(RError.SHOW_CALLER, Message.INCOMPLETE_FINAL_LINE, s);
+                    }
+                    ch = s.charAt(pos++);
+                    if (ch == quoteStart) {
+                        if (pos < length && s.charAt(pos) == quoteStart) {
+                            str.append(quoteStart);
+                            pos++;
+                        } else {
+                            break;
+                        }
+                    } else {
+                        str.append(ch);
+                    }
                 }
-            }
-
-            if (sepInd == -1 && quoteInd == -1) {
-                // no more separators and no more quotes - add the last item and return
-                sb.append(str);
-                items.add(sb.toString());
-                break;
-            }
-
-            if (quoteInd >= 0 && (sepInd == -1 || (sepInd >= 0 && quoteInd < sepInd))) {
-                // quote character was found before the separator character - everything from the
-                // beginning of str up to the end of quote becomes part of this item
-                sb.append(str.substring(0, quoteInd));
-                int nextQuoteInd = str.indexOf(quoteChar, quoteInd + 1);
-                sb.append(str.substring(quoteInd + 1, nextQuoteInd));
-                str = str.substring(nextQuoteInd + 1, str.length());
             } else {
-                assert sepInd >= 0;
-                // everything from the beginning of str becomes part of this time and item
-                // processing is completed (also eat up separators)
-                String[] tuple = data.sepregex == null ? str.split("\\s+", 2) : str.split(data.sepregex, 2);
-                assert tuple.length == 2;
-                sb.append(tuple[0]);
-                str = tuple[1];
-                items.add(sb.toString());
-                sb = new StringBuilder();
+                str.append(ch);
+                pos++;
             }
-        }
+        } while (pos < s.length());
+        items.add(str.toString());
 
         return items.toArray(new String[items.size()]);
     }
@@ -295,15 +278,11 @@ public abstract class Scan extends RBuiltinNode.Arg19 {
             if (str == null || str.length == 0) {
                 return null;
             } else {
-                String s = str[0].trim();
-                if (blSkip && s.length() == 0) {
+                String[] items = getQuotedItems(data, str[0]);
+                if (blSkip && items.length == 0) {
                     continue;
                 } else {
-                    if (data.quoteset.length() == 0) {
-                        return data.sepregex == null ? s.split("\\s+") : s.split(data.sepregex);
-                    } else {
-                        return getQuotedItems(data, s);
-                    }
+                    return items.length == 0 ? new String[]{""} : items;
                 }
             }
         }
@@ -317,8 +296,7 @@ public abstract class Scan extends RBuiltinNode.Arg19 {
     }
 
     private RVector<?> scanFrame(RList what, int maxRecords, int maxLines, boolean flush, boolean fill, @SuppressWarnings("unused") boolean stripWhite, boolean blSkip, boolean multiLine,
-                    LocalData data)
-                    throws IOException {
+                    LocalData data) throws IOException {
 
         int nc = what.getLength();
         if (nc == 0) {
@@ -505,45 +483,49 @@ public abstract class Scan extends RBuiltinNode.Arg19 {
     }
 
     private static Object extractItem(RAbstractVector what, String buffer, LocalData data) {
-        switch (what.getRType()) {
-            case Logical:
-                if (isNaString(buffer, 0, data)) {
-                    return RRuntime.LOGICAL_NA;
-                } else {
-                    return RRuntime.string2logicalNoCheck(buffer);
-                }
-            case Integer:
-                if (isNaString(buffer, 0, data)) {
-                    return RRuntime.INT_NA;
-                } else {
-                    return RRuntime.string2intNoCheck(buffer);
-                }
-            case Double:
-                if (isNaString(buffer, 0, data)) {
-                    return RRuntime.DOUBLE_NA;
-                } else {
-                    return RRuntime.string2doubleNoCheck(buffer);
-                }
-            case Complex:
-                if (isNaString(buffer, 0, data)) {
-                    return RComplex.createNA();
-                } else {
-                    return RRuntime.string2complexNoCheck(buffer);
-                }
-            case Character:
-                if (isNaString(buffer, 1, data)) {
-                    return RRuntime.STRING_NA;
-                } else {
-                    return buffer;
-                }
-            case Raw:
-                if (isNaString(buffer, 0, data)) {
-                    return RDataFactory.createRaw((byte) 0);
-                } else {
-                    return RRuntime.string2raw(buffer);
-                }
-            default:
-                throw RInternalError.shouldNotReachHere();
+        try {
+            switch (what.getRType()) {
+                case Logical:
+                    if (isNaString(buffer, 0, data)) {
+                        return RRuntime.LOGICAL_NA;
+                    } else {
+                        return RRuntime.string2logicalNoCheck(buffer);
+                    }
+                case Integer:
+                    if (isNaString(buffer, 0, data)) {
+                        return RRuntime.INT_NA;
+                    } else {
+                        return RRuntime.parseInt(buffer);
+                    }
+                case Double:
+                    if (isNaString(buffer, 0, data)) {
+                        return RRuntime.DOUBLE_NA;
+                    } else {
+                        return RRuntime.string2doubleNoCheck(buffer);
+                    }
+                case Complex:
+                    if (isNaString(buffer, 0, data)) {
+                        return RComplex.createNA();
+                    } else {
+                        return RRuntime.string2complexNoCheck(buffer);
+                    }
+                case Character:
+                    if (isNaString(buffer, 1, data)) {
+                        return RRuntime.STRING_NA;
+                    } else {
+                        return buffer;
+                    }
+                case Raw:
+                    if (isNaString(buffer, 0, data)) {
+                        return RDataFactory.createRaw((byte) 0);
+                    } else {
+                        return RRuntime.string2raw(buffer);
+                    }
+                default:
+                    throw RInternalError.shouldNotReachHere();
+            }
+        } catch (NumberFormatException e) {
+            throw RError.error(RError.SHOW_CALLER, Message.SCAN_UNEXPECTED, what.getRType().getName(), buffer);
         }
     }
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RRuntime.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RRuntime.java
index e5642fb9ccf8ae75fdd005cf74d015497ece1aba..7951ef74bb05ff6fc2da8b3c2b418d24b0c4b8fa 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RRuntime.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RRuntime.java
@@ -350,6 +350,84 @@ public class RRuntime {
 
     // conversions from string
 
+    @TruffleBoundary
+    public static int parseInt(String s) {
+        int length = s.length();
+        long value = 0;
+        if (s.charAt(0) == '-') {
+            if (length == 1) {
+                throw new NumberFormatException();
+            }
+            int pos = 1;
+            while (pos < length) {
+                char ch = s.charAt(pos++);
+                if (ch < '0' || ch > '9') {
+                    throw new NumberFormatException();
+                }
+                value = value * 10 + (ch - '0');
+                if (value > (Integer.MAX_VALUE + 1L)) {
+                    return INT_NA;
+                }
+            }
+            return (int) -value;
+        } else {
+            if (length == 0) {
+                throw new NumberFormatException();
+            }
+            int pos = 0;
+            while (pos < length) {
+                char ch = s.charAt(pos++);
+                if (ch < '0' || ch > '9') {
+                    throw new NumberFormatException();
+                }
+                value = value * 10 + (ch - '0');
+                if (value > Integer.MAX_VALUE) {
+                    return INT_NA;
+                }
+            }
+            return (int) value;
+        }
+    }
+
+    @TruffleBoundary
+    public static int parseIntWithNA(String s) {
+        int length = s.length();
+        long value = 0;
+        if (s.charAt(0) == '-') {
+            if (length == 1) {
+                return INT_NA;
+            }
+            int pos = 1;
+            while (pos < length) {
+                char ch = s.charAt(pos++);
+                if (ch < '0' || ch > '9') {
+                    return INT_NA;
+                }
+                value = value * 10 + (ch - '0');
+                if (value > (Integer.MAX_VALUE + 1L)) {
+                    return INT_NA;
+                }
+            }
+            return (int) -value;
+        } else {
+            if (length == 0) {
+                return INT_NA;
+            }
+            int pos = 0;
+            while (pos < length) {
+                char ch = s.charAt(pos++);
+                if (ch < '0' || ch > '9') {
+                    return INT_NA;
+                }
+                value = value * 10 + (ch - '0');
+                if (value > Integer.MAX_VALUE) {
+                    return INT_NA;
+                }
+            }
+            return (int) value;
+        }
+    }
+
     @TruffleBoundary
     public static int string2intNoCheck(String s, boolean exceptionOnFail) {
         // FIXME use R rules
@@ -367,6 +445,7 @@ public class RRuntime {
             throw new NumberFormatException();
         }
         return result;
+
     }
 
     @TruffleBoundary
diff --git a/mx.fastr/copyrights/overrides b/mx.fastr/copyrights/overrides
index 2fdbdd1da887e394c33a808053a6051f577a34f6..48739f73c646d07beefe62abd5378f7cbe87b88d 100644
--- a/mx.fastr/copyrights/overrides
+++ b/mx.fastr/copyrights/overrides
@@ -34,6 +34,8 @@ com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPort
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPortContext.java,gnu_r_murrel_core.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPortLocation.java,gnu_r_murrel_core.copyright
 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/color/Col2RGB.java,gnu_r.copyright
+com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/RGB.java,gnu_r.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/graphics/RGraphics.java,gnu_r_graphics.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/methods/MethodsListDispatch.java,gnu_r.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/methods/Slot.java,gnu_r.copyright