diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/DoSetViewPort.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/DoSetViewPort.java
index 9d35941ac89e588f958a5256d17166792f538357..f61ea60f85913f553a4a03c668746d8849898c64 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/DoSetViewPort.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/DoSetViewPort.java
@@ -355,7 +355,7 @@ final class DoSetViewPort extends RBaseNode {
         }
     }
 
-    // Note: unlike the GnuR conterpart of this method, we expect the LayoutPos to have the NULL
+    // Note: unlike the GnuR counterpart of this method, we expect the LayoutPos to have the NULL
     // positions replaced with nrow/ncol already.
     private ViewPortLocation calcViewportLocationFromLayout(LayoutPos pos, RList parentVP, Size parentSize) {
         // unlike in GnuR, we maintain parent viewport widths/heights in inches like anything else
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 f77ddeb6c5b4a9d2a8882984195d530ff99e0654..354cc207472ef0334c9b04627f23b0179e87e366 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
@@ -22,8 +22,11 @@
  */
 package com.oracle.truffle.r.library.fastrGrid;
 
+import com.oracle.truffle.r.library.fastrGrid.grDevices.DevCurr;
 import com.oracle.truffle.r.library.fastrGrid.grDevices.DevHoldFlush;
+import com.oracle.truffle.r.library.fastrGrid.grDevices.DevOff;
 import com.oracle.truffle.r.library.fastrGrid.grDevices.InitWindowedDevice;
+import com.oracle.truffle.r.library.fastrGrid.grDevices.SavePlot;
 import com.oracle.truffle.r.library.fastrGrid.graphics.CPar;
 import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
 import com.oracle.truffle.r.nodes.builtin.RInternalCodeBuiltinNode;
@@ -48,6 +51,10 @@ public final class FastRGridExternalLookup {
         switch (name) {
             case "devholdflush":
                 return DevHoldFlush.create();
+            case "devcur":
+                return new DevCurr();
+            case "devoff":
+                return DevOff.create();
             case "PDF":
                 return new IgnoredGridExternal(RNull.instance);
             default:
@@ -59,6 +66,8 @@ public final class FastRGridExternalLookup {
         switch (name) {
             case "C_par":
                 return new CPar();
+            case "savePlot":
+                return SavePlot.create();
             case "X11":
                 return new InitWindowedDevice();
             default:
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridContext.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridContext.java
index ec68fcff20f2af94b77f30ea667e8bb8b0164465..356fc3173b6ffea43e0c830bcc22d75ad33c71ef 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridContext.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridContext.java
@@ -22,30 +22,91 @@
  */
 package com.oracle.truffle.r.library.fastrGrid;
 
-import com.oracle.truffle.r.library.fastrGrid.device.BufferedJFrameDevice;
+import java.util.ArrayList;
+
+import com.oracle.truffle.r.library.fastrGrid.GridState.GridDeviceState;
 import com.oracle.truffle.r.library.fastrGrid.device.GridDevice;
-import com.oracle.truffle.r.library.fastrGrid.device.JFrameDevice;
+import com.oracle.truffle.r.library.fastrGrid.device.GridDevice.DeviceCloseException;
+import com.oracle.truffle.r.library.fastrGrid.device.awt.BufferedJFrameDevice;
+import com.oracle.truffle.r.library.fastrGrid.device.awt.JFrameDevice;
+import com.oracle.truffle.r.library.fastrGrid.graphics.RGridGraphicsAdapter;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RError.Message;
 
 /**
- * Encapsulated the acces to the global grid state.
+ * Encapsulated the access to the global grid state.
  */
 public final class GridContext {
     private static final GridContext INSTANCE = new GridContext();
     private final GridState gridState = new GridState();
-    private GridDevice currentDevice;
+    /**
+     * This list should correspond to the names inside {@code .Devices} variable in R.
+     */
+    private final ArrayList<DeviceAndState> devices = new ArrayList<>(2);
+    private int currentDeviceIdx = 0;
+
+    private GridContext() {
+        devices.add(new DeviceAndState(null));
+    }
 
     public static GridContext getContext() {
         return INSTANCE;
     }
 
     public GridState getGridState() {
+        gridState.setDeviceState(devices.get(currentDeviceIdx).state);
         return gridState;
     }
 
+    public int getCurrentDeviceIndex() {
+        return currentDeviceIdx;
+    }
+
+    public int getDevicesSize() {
+        return devices.size();
+    }
+
     public GridDevice getCurrentDevice() {
-        if (currentDevice == null) {
-            currentDevice = new BufferedJFrameDevice(new JFrameDevice());
+        assert currentDeviceIdx >= 0 : "accessing devices before they were initialized";
+        return devices.get(currentDeviceIdx).device;
+    }
+
+    public void setCurrentDevice(String name, GridDevice currentDevice) {
+        RGridGraphicsAdapter.addDevice(name);
+        RGridGraphicsAdapter.setCurrentDevice(name);
+        currentDeviceIdx = this.devices.size();
+        this.devices.add(new DeviceAndState(currentDevice));
+        assert devices.size() == RGridGraphicsAdapter.getDevicesCount();
+    }
+
+    public void openDefaultDevice() {
+        String defaultDev = RGridGraphicsAdapter.getDefaultDevice();
+        if (defaultDev.equals("awt") || defaultDev.startsWith("X11")) {
+            BufferedJFrameDevice result = new BufferedJFrameDevice(JFrameDevice.create());
+            setCurrentDevice(defaultDev, result);
+        } else {
+            throw RError.error(RError.NO_CALLER, Message.GENERIC, "FastR does not support device '" + defaultDev + "'.");
+        }
+        assert devices.size() == RGridGraphicsAdapter.getDevicesCount();
+    }
+
+    public void closeDevice(int which) throws DeviceCloseException {
+        assert which >= 0 && which < devices.size();
+        devices.get(which).device.close();
+        RGridGraphicsAdapter.removeDevice(which);
+        devices.remove(which);
+        if (currentDeviceIdx >= which) {
+            currentDeviceIdx--;
+        }
+    }
+
+    private static final class DeviceAndState {
+        final GridDevice device;
+        final GridDeviceState state;
+
+        DeviceAndState(GridDevice device) {
+            this.device = device;
+            this.state = new GridDeviceState();
         }
-        return currentDevice;
     }
 }
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridLinesNode.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridLinesNode.java
index ba1b404e0ddb2ff9dee47cf69dffb55c3a5dc17d..8db8ff46c3412d83f496c751a747fc24888e9b62 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridLinesNode.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridLinesNode.java
@@ -76,7 +76,7 @@ public abstract class GridLinesNode extends Node {
             // following loop finds series of valid points (finite x and y values) and draws each
             // such series as a polyline
             for (int i = 0; i < unitIndexesLen; i++) {
-                int unitIndex = unitIndexes.getDataAt(i) - 1;   // coverting R's 1-based index
+                int unitIndex = unitIndexes.getDataAt(i) - 1;   // converting R's 1-based index
                 Point origLoc = Point.fromUnits(unitToInches, x, y, unitIndex, conversionCtx);
                 Point loc = TransformMatrix.transLocation(origLoc, vpTransform.transform);
                 xx[i] = loc.x;
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 6774a7b7220676b3ded896ea99b0ed6c1efa01a6..deab204f3b8ce9f20b3608108a34ce0c26fe3780 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
@@ -17,12 +17,8 @@ import com.oracle.truffle.r.runtime.data.RNull;
 import com.oracle.truffle.r.runtime.env.REnvironment;
 
 public final class GridState {
-    private RList gpar;
-    private RList viewPort;
     private REnvironment gridEnv;
-    private double scale = 1;
-    private boolean deviceInitialized;
-    private int devHoldCount;
+    private GridDeviceState devState;
 
     /**
      * Current grob being drawn (for determining the list of grobs to search when evaluating a
@@ -33,23 +29,26 @@ public final class GridState {
     GridState() {
     }
 
+    void setDeviceState(GridDeviceState state) {
+        devState = state;
+    }
+
     public int getDevHoldCount() {
-        return devHoldCount;
+        return devState.devHoldCount;
     }
 
     public int setDevHoldCount(int devHoldCount) {
-        this.devHoldCount = devHoldCount;
+        devState.devHoldCount = devHoldCount;
         return devHoldCount;
     }
 
-    public void init(REnvironment gridEnv, GridDevice currentDevice) {
+    public void init(REnvironment gridEnv) {
         this.gridEnv = gridEnv;
         this.currentGrob = RNull.instance;
-        initGPar(currentDevice);
     }
 
     void initGPar(GridDevice currentDevice) {
-        gpar = GPar.createNew(currentDevice);
+        devState.gpar = GPar.createNew(currentDevice);
     }
 
     /**
@@ -62,28 +61,33 @@ public final class GridState {
 
     public RList getGpar() {
         assert gridEnv != null : "GridState not initialized";
-        return gpar;
+        return devState.gpar;
     }
 
     public void setGpar(RList gpar) {
         assert gridEnv != null : "GridState not initialized";
-        this.gpar = gpar;
+        devState.gpar = gpar;
     }
 
+    /**
+     * Has the current device been initialized for use by grid? Note: the null device should never
+     * get initialized. The code initializing device should check if any device is open and if not,
+     * it should open the default device and initialize it.
+     */
     public boolean isDeviceInitialized() {
-        return deviceInitialized;
+        return devState.isDeviceInitialized;
     }
 
     public void setDeviceInitialized() {
-        this.deviceInitialized = true;
+        devState.isDeviceInitialized = true;
     }
 
     public RList getViewPort() {
-        return viewPort;
+        return devState.viewPort;
     }
 
     public void setViewPort(RList viewPort) {
-        this.viewPort = viewPort;
+        devState.viewPort = viewPort;
     }
 
     public REnvironment getGridEnv() {
@@ -99,7 +103,14 @@ public final class GridState {
     }
 
     public double getScale() {
-        return scale;
+        return devState.scale;
     }
 
+    static final class GridDeviceState {
+        private boolean isDeviceInitialized = false;
+        private RList gpar;
+        private RList viewPort;
+        private double scale = 1;
+        private int devHoldCount;
+    }
 }
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/IgnoredGridExternal.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/IgnoredGridExternal.java
index 37090901e18992f8bc4f215d28e2987a9445ee57..17e5e85daea190ec92d812ff8d980bd0716c4e2a 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/IgnoredGridExternal.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/IgnoredGridExternal.java
@@ -26,8 +26,8 @@ import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
 import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
 
 /**
- * A node for externals that we ignore, becuase we do not need to implement them or because they
- * support functionallity we do not implement yet, especially record/replay.
+ * A node for externals that we ignore, because we do not need to implement them or because they
+ * support features we do not implement yet, especially record/replay.
  */
 final class IgnoredGridExternal extends RExternalBuiltinNode {
     private final Object result;
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LGridDirty.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LGridDirty.java
index 2fe0e336c3df91d19d5b25083ebdb1bc19d10f20..a6314dff2c06b37db7b29a26f97f4fb674ea078c 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LGridDirty.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LGridDirty.java
@@ -13,15 +13,14 @@ package com.oracle.truffle.r.library.fastrGrid;
 
 import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.frame.VirtualFrame;
-import com.oracle.truffle.api.profiles.ConditionProfile;
 import com.oracle.truffle.r.library.fastrGrid.ViewPort.InitViewPortNode;
+import com.oracle.truffle.r.library.fastrGrid.device.GridDevice;
 import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
 import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
 import com.oracle.truffle.r.runtime.data.RNull;
 
 final class LGridDirty extends RExternalBuiltinNode {
     @Child private InitViewPortNode initViewPort = new InitViewPortNode();
-    private final ConditionProfile initViewPortProfile = ConditionProfile.createCountingProfile();
 
     static {
         Casts.noCasts(LGridDirty.class);
@@ -30,16 +29,32 @@ final class LGridDirty extends RExternalBuiltinNode {
     @Override
     public Object call(VirtualFrame frame, RArgsValuesAndNames args) {
         GridState gridState = GridContext.getContext().getGridState();
-        if (!gridState.isDeviceInitialized()) {
-            CompilerDirectives.transferToInterpreter();
-            GridContext.getContext().getCurrentDevice().openNewPage();
-            gridState.setDeviceInitialized();
+        if (gridState.isDeviceInitialized()) {
+            return RNull.instance;
         }
-        if (initViewPortProfile.profile(gridState.getViewPort() == null)) {
-            // this rarely happens, but we do not have a slow-path implementation (yet)
-            CompilerDirectives.transferToInterpreter();
+
+        // the rest only takes place if the device has been changed since the last time
+        CompilerDirectives.transferToInterpreter();
+
+        // if no device has been opened yet, open the default one and make it current
+        if (GridContext.getContext().getCurrentDevice() == null) {
+            GridContext.getContext().openDefaultDevice();
+            gridState = GridContext.getContext().getGridState();    // grid state is device
+                                                                    // dependent
+        }
+
+        // the current device has not been initialized yet...
+        GridDevice device = GridContext.getContext().getCurrentDevice();
+        device.openNewPage();
+        gridState.setViewPort(initViewPort.execute(frame));
+        gridState.setDeviceInitialized();
+        if (gridState.getGpar() == null) {
+            gridState.initGPar(device);
+        }
+        if (gridState.getViewPort() == null) {
             gridState.setViewPort(initViewPort.execute(frame));
         }
+
         return RNull.instance;
     }
 
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitGrid.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitGrid.java
index 885ace9762156ae4fba296ebb0e32a710ef8591d..74aa2a0b0d02c99dda2cb92fba9605167e7462f8 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitGrid.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitGrid.java
@@ -31,7 +31,7 @@ public abstract class LInitGrid extends RExternalBuiltinNode.Arg1 {
     @TruffleBoundary
     public Object doEnv(REnvironment gridEnv) {
         GridContext context = GridContext.getContext();
-        context.getGridState().init(gridEnv, context.getCurrentDevice());
+        context.getGridState().init(gridEnv);
         return RNull.instance;
     }
 }
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/Unit.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/Unit.java
index 04165ca1749f927b4853bbaa103330760df49886..f881cc708180ce2e8da82a15ad5704eb000c8788 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/Unit.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/Unit.java
@@ -56,7 +56,7 @@ import com.oracle.truffle.r.runtime.nodes.RBaseNode;
 
 /**
  * Note: internally in FastR Grid everything is in inches. However, some lists that are exposed to
- * the R code should contain values in centimeters, we convert such values immediatelly once they
+ * the R code should contain values in centimeters, we convert such values immediately once they
  * enter our system.
  */
 public final class Unit {
@@ -185,6 +185,9 @@ public final class Unit {
 
     private static double convertToInches(double value, int index, int unitId, RList data, UnitConversionContext ctx, AxisOrDimension axisOrDim) {
         double vpSize = ctx.getViewPortSize(axisOrDim);
+        String str;
+        String[] lines;
+        double result = 0;
         switch (unitId) {
             case INCHES:
                 return value;
@@ -207,10 +210,20 @@ public final class Unit {
                 return (value * ctx.gpar.getDrawingContext(index).getFontSize() * ctx.gpar.getDrawingContext(index).getLineHeight()) / INCH_TO_POINTS_FACTOR;
             case STRINGWIDTH:
             case MYSTRINGWIDTH:
-                return ctx.device.getStringWidth(ctx.gpar.getDrawingContext(index), RRuntime.asString(data.getDataAt(0)));
+                str = RRuntime.asString(data.getDataAt(0));
+                lines = str.split("\n");
+                for (int i = 0; i < lines.length; i++) {
+                    result = Math.max(result, ctx.device.getStringWidth(ctx.gpar.getDrawingContext(index), lines[i]));
+                }
+                return result;
             case STRINGHEIGHT:
             case MYSTRINGHEIGHT:
-                return ctx.device.getStringHeight(ctx.gpar.getDrawingContext(index), RRuntime.asString(data.getDataAt(0)));
+                str = RRuntime.asString(data.getDataAt(0));
+                lines = str.split("\n");
+                for (int i = 0; i < lines.length; i++) {
+                    result += ctx.device.getStringHeight(ctx.gpar.getDrawingContext(index), lines[i]);
+                }
+                return result;
             case NULL:
                 return evaluateNullUnit(value, vpSize, ctx.nullLayoutMode, ctx.nullArithmeticMode);
             default:
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPort.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPort.java
index 2a694a69f9e0a7c7dd0c25bfbfc691a0ed1dd409..80e93f6270d712ec482957010c2dde21dce72014 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPort.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPort.java
@@ -153,6 +153,7 @@ final class ViewPort {
         public RList execute(VirtualFrame frame) {
             RFunction gridTopLevel = (RFunction) readGridTopLevel.execute(frame);
             RList topVP = (RList) callNode.execute(frame, gridTopLevel, RArgsValuesAndNames.EMPTY);
+            topVP.makeSharedPermanent();
 
             GridDevice device = GridContext.getContext().getCurrentDevice();
             // TODO: properly set the scale according to the current device
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/BufferedJFrameDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/BufferedJFrameDevice.java
deleted file mode 100644
index 131165f954c651e39fd2558735541c9a627b1a3f..0000000000000000000000000000000000000000
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/BufferedJFrameDevice.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package com.oracle.truffle.r.library.fastrGrid.device;
-
-import java.awt.image.BufferStrategy;
-import java.util.ArrayList;
-
-/**
- * Decorator for {@link JFrameDevice} that implements {@link #hold()} and {@link #flush()}. Those
- * methods open/draw a 2D graphics buffer, while the buffer is open, any drawing is done in the
- * buffer not on the screen and we also record any drawing code to be able to replay it if the
- * buffer happens to loose contents, which is a possibility mentioned in the documentation. Note: we
- * rely on the fact that {@link DrawingContext} is immutable.
- */
-public final class BufferedJFrameDevice implements GridDevice {
-    private final JFrameDevice inner;
-    private BufferStrategy buffer;
-    private ArrayList<Runnable> drawActions;
-
-    public BufferedJFrameDevice(JFrameDevice inner) {
-        this.inner = inner;
-    }
-
-    @Override
-    public void openNewPage() {
-        inner.openNewPage();
-    }
-
-    @Override
-    public void hold() {
-        if (buffer != null) {
-            return; // already buffering
-        }
-        buffer = inner.getCurrentFrame().getBufferStrategy();
-        if (buffer == null) {
-            inner.getCurrentFrame().createBufferStrategy(2);
-            buffer = inner.getCurrentFrame().getBufferStrategy();
-        }
-        if (drawActions == null) {
-            drawActions = new ArrayList<>();
-        } else {
-            drawActions.clear();
-        }
-        inner.initGraphics(buffer.getDrawGraphics());
-    }
-
-    @Override
-    public void flush() {
-        if (buffer == null) {
-            return;
-        }
-
-        buffer.show();
-        // re-draw the buffer if the contents were lost
-        while (buffer.contentsLost()) {
-            inner.initGraphics(buffer.getDrawGraphics());
-            for (Runnable drawAction : drawActions) {
-                drawAction.run();
-            }
-            buffer.show();
-        }
-
-        inner.initGraphics(inner.getCurrentFrame().getGraphics());
-        buffer.dispose();
-        buffer = null;
-    }
-
-    @Override
-    public void drawRect(DrawingContext ctx, double leftX, double topY, double width, double height, double rotationAnticlockWise) {
-        inner.drawRect(ctx, leftX, topY, width, height, rotationAnticlockWise);
-        if (buffer != null) {
-            drawActions.add(() -> inner.drawRect(ctx, leftX, topY, width, height, rotationAnticlockWise));
-        }
-    }
-
-    @Override
-    public void drawPolyLines(DrawingContext ctx, double[] x, double[] y, int startIndex, int length) {
-        inner.drawPolyLines(ctx, x, y, startIndex, length);
-        if (buffer != null) {
-            drawActions.add(() -> inner.drawPolyLines(ctx, x, y, startIndex, length));
-        }
-    }
-
-    @Override
-    public void drawPolygon(DrawingContext ctx, double[] x, double[] y, int startIndex, int length) {
-        inner.drawPolygon(ctx, x, y, startIndex, length);
-        if (buffer != null) {
-            drawActions.add(() -> inner.drawPolygon(ctx, x, y, startIndex, length));
-        }
-    }
-
-    @Override
-    public void drawCircle(DrawingContext ctx, double centerX, double centerY, double radius) {
-        inner.drawCircle(ctx, centerX, centerY, radius);
-        if (buffer != null) {
-            drawActions.add(() -> inner.drawCircle(ctx, centerX, centerY, radius));
-        }
-    }
-
-    @Override
-    public void drawString(DrawingContext ctx, double leftX, double bottomY, double rotationAnticlockWise, String text) {
-        inner.drawString(ctx, leftX, bottomY, rotationAnticlockWise, text);
-        if (buffer != null) {
-            drawActions.add(() -> inner.drawString(ctx, leftX, bottomY, rotationAnticlockWise, text));
-        }
-    }
-
-    @Override
-    public double getWidth() {
-        return inner.getWidth();
-    }
-
-    @Override
-    public double getHeight() {
-        return inner.getHeight();
-    }
-
-    @Override
-    public double getStringWidth(DrawingContext ctx, String text) {
-        return inner.getStringWidth(ctx, text);
-    }
-
-    @Override
-    public double getStringHeight(DrawingContext ctx, String text) {
-        return inner.getStringHeight(ctx, text);
-    }
-}
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/GridDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/GridDevice.java
index afe759f3f09cdd99b23f3fda22107266ab63def3..90a2dcd6e941b70aa7f45f1dd1cefecc4c6eb6a3 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/GridDevice.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/GridDevice.java
@@ -46,15 +46,25 @@ public interface GridDevice {
     default void flush() {
     }
 
+    /**
+     * Gets called when the device is closed from R. This is the point where non-interactive devices
+     * should save their output into a file.
+     *
+     * @throws DeviceCloseException if the closing was not successful e.g. because the file could
+     *             not be written.
+     */
+    default void close() throws DeviceCloseException {
+    }
+
     /**
      * Draws a rectangle at given position, the center of the rotation should be the center of the
      * rectangle. The rotation is given in radians.
      */
-    void drawRect(DrawingContext ctx, double leftX, double topY, double width, double height, double rotationAnticlockWise);
+    void drawRect(DrawingContext ctx, double leftX, double bottomY, double width, double height, double rotationAnticlockWise);
 
     /**
      * Connects given points with a line, there has to be at least two points in order to actually
-     * draw somethig.
+     * draw something.
      */
     void drawPolyLines(DrawingContext ctx, double[] x, double[] y, int startIndex, int length);
 
@@ -96,11 +106,27 @@ public interface GridDevice {
     double getStringWidth(DrawingContext ctx, String text);
 
     /**
-     * Gets the height of a line of text in inches, the default implementation uses only the
-     * parameters from the drawing context, but we allow the device to override this calculation
-     * with something more precise.
+     * Gets the height of a line of text in inches. This should include ascent and descent, i.e.
+     * from the very bottom to the very top of the tallest letter(s). The text is guaranteed to not
+     * contain any new lines.
      */
-    default double getStringHeight(DrawingContext ctx, @SuppressWarnings("unused") String text) {
-        return (ctx.getLineHeight() * ctx.getFontSize()) / INCH_TO_POINTS_FACTOR;
+    double getStringHeight(DrawingContext ctx, String text);
+
+    final class DeviceCloseException extends Exception {
+        private static final long serialVersionUID = 1182697755931636214L;
+
+        public DeviceCloseException(Throwable cause) {
+            super(cause);
+        }
+
+        @Override
+        public String getMessage() {
+            return getCause().getMessage();
+        }
+
+        @Override
+        public synchronized Throwable fillInStackTrace() {
+            return this;
+        }
     }
 }
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/ImageSaver.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/ImageSaver.java
new file mode 100644
index 0000000000000000000000000000000000000000..7377c90be20238f0d8edb961100e82e531b2d64e
--- /dev/null
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/ImageSaver.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.library.fastrGrid.device;
+
+import java.io.IOException;
+
+/**
+ * Devices that support saving their current state into a file should implement this interface.
+ * Note: this only makes sense for interactive devices. Devices like SVG are already saving into a
+ * file.
+ */
+public interface ImageSaver {
+    void save(String path, String fileType) throws IOException;
+}
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/BufferedImageDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/BufferedImageDevice.java
new file mode 100644
index 0000000000000000000000000000000000000000..485ad251614d1fc06524ab20352361391b1060f9
--- /dev/null
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/BufferedImageDevice.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.library.fastrGrid.device.awt;
+
+import static java.awt.image.BufferedImage.TYPE_INT_RGB;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+
+public final class BufferedImageDevice extends Graphics2DDevice {
+    private final BufferedImage image;
+    private final String filename;
+    private final String fileType;
+
+    private BufferedImageDevice(String filename, String fileType, BufferedImage image, Graphics2D graphics, int width, int height) {
+        super(graphics, width, height, true);
+        this.filename = filename;
+        this.fileType = fileType;
+        this.image = image;
+        graphics.setBackground(new Color(255, 255, 255));
+        graphics.clearRect(0, 0, width, height);
+    }
+
+    public static BufferedImageDevice open(String filename, String fileType, int width, int height) throws NotSupportedImageFormatException {
+        if (!isSupportedFormat(fileType)) {
+            throw new NotSupportedImageFormatException();
+        }
+        BufferedImage image = new BufferedImage(width, height, TYPE_INT_RGB);
+        return new BufferedImageDevice(filename, fileType, image, (Graphics2D) image.getGraphics(), width, height);
+    }
+
+    @Override
+    public void close() throws DeviceCloseException {
+        try {
+            ImageIO.write(image, fileType, new File(filename));
+        } catch (IOException e) {
+            throw new DeviceCloseException(e);
+        }
+    }
+
+    private static boolean isSupportedFormat(String formatName) {
+        String[] formatNames = ImageIO.getWriterFormatNames();
+        for (String n : formatNames) {
+            if (n.equals(formatName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static class NotSupportedImageFormatException extends Exception {
+        private static final long serialVersionUID = 1182697755931636217L;
+
+        @Override
+        public synchronized Throwable fillInStackTrace() {
+            return this;
+        }
+    }
+}
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/BufferedJFrameDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/BufferedJFrameDevice.java
new file mode 100644
index 0000000000000000000000000000000000000000..b94f4e39aa0864e6819430128b69eb432dd2439d
--- /dev/null
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/BufferedJFrameDevice.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.library.fastrGrid.device.awt;
+
+import static java.awt.image.BufferedImage.TYPE_INT_RGB;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferStrategy;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import javax.imageio.ImageIO;
+
+import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext;
+import com.oracle.truffle.r.library.fastrGrid.device.GridDevice;
+import com.oracle.truffle.r.library.fastrGrid.device.ImageSaver;
+
+/**
+ * Decorator for {@link JFrameDevice} that implements {@link #hold()} and {@link #flush()}
+ * functionality and implements the {@link ImageSaver} device.
+ *
+ * Methods {@link #hold()} and {@link #flush()} open/draw a 2D graphics buffer, while the buffer is
+ * open, any drawing is done in the buffer not on the screen and the buffer is dumped to the screen
+ * once {@link #flush()} is called.
+ *
+ * We also record any drawing code to be able to replay it if the buffer happens to loose its
+ * contents, which is a possibility mentioned in the documentation. Moreover, this record of drawing
+ * can be used in {@link #save(String, String)} to replay the drawing in a {@code BufferedImage}.
+ * Note: here we rely on the fact that {@link DrawingContext} is immutable.
+ */
+public final class BufferedJFrameDevice implements GridDevice, ImageSaver {
+    private final JFrameDevice inner;
+    private final ArrayList<Runnable> drawActions = new ArrayList<>(200);
+    private BufferStrategy buffer;
+
+    public BufferedJFrameDevice(JFrameDevice inner) {
+        this.inner = inner;
+    }
+
+    @Override
+    public void openNewPage() {
+        inner.openNewPage();
+        drawActions.clear();
+        if (buffer != null) {
+            // if new page is opened while we are on hold, we should throw away current buffer. In
+            // other words that is like starting new hold without previous flush.
+            buffer.dispose();
+            buffer = null;
+            hold();
+        }
+    }
+
+    @Override
+    public void hold() {
+        if (buffer != null) {
+            return; // already buffering
+        }
+        buffer = inner.getCurrentFrame().getBufferStrategy();
+        if (buffer == null) {
+            inner.getCurrentFrame().createBufferStrategy(2);
+            buffer = inner.getCurrentFrame().getBufferStrategy();
+        }
+        drawActions.clear();
+        setGraphics(buffer.getDrawGraphics());
+    }
+
+    @Override
+    public void flush() {
+        if (buffer == null) {
+            return;
+        }
+
+        buffer.show();
+        // re-draw the buffer if the contents were lost
+        while (buffer.contentsLost()) {
+            setGraphics(buffer.getDrawGraphics());
+            for (Runnable drawAction : drawActions) {
+                drawAction.run();
+            }
+            buffer.show();
+        }
+
+        setGraphics(inner.getCurrentFrame().getGraphics());
+        buffer.dispose();
+        buffer = null;
+    }
+
+    @Override
+    public void close() throws DeviceCloseException {
+        inner.close();
+    }
+
+    @Override
+    public void drawRect(DrawingContext ctx, double leftX, double bottomY, double width, double height, double rotationAnticlockWise) {
+        inner.drawRect(ctx, leftX, bottomY, width, height, rotationAnticlockWise);
+        drawActions.add(() -> inner.drawRect(ctx, leftX, bottomY, width, height, rotationAnticlockWise));
+    }
+
+    @Override
+    public void drawPolyLines(DrawingContext ctx, double[] x, double[] y, int startIndex, int length) {
+        inner.drawPolyLines(ctx, x, y, startIndex, length);
+        double[] xCopy = new double[x.length];
+        double[] yCopy = new double[y.length];
+        System.arraycopy(x, 0, xCopy, 0, x.length);
+        System.arraycopy(y, 0, yCopy, 0, y.length);
+        drawActions.add(() -> inner.drawPolyLines(ctx, xCopy, yCopy, startIndex, length));
+    }
+
+    @Override
+    public void drawPolygon(DrawingContext ctx, double[] x, double[] y, int startIndex, int length) {
+        inner.drawPolygon(ctx, x, y, startIndex, length);
+        double[] xCopy = new double[x.length];
+        double[] yCopy = new double[y.length];
+        System.arraycopy(x, 0, xCopy, 0, x.length);
+        System.arraycopy(y, 0, yCopy, 0, y.length);
+        drawActions.add(() -> inner.drawPolygon(ctx, xCopy, yCopy, startIndex, length));
+    }
+
+    @Override
+    public void drawCircle(DrawingContext ctx, double centerX, double centerY, double radius) {
+        inner.drawCircle(ctx, centerX, centerY, radius);
+        drawActions.add(() -> inner.drawCircle(ctx, centerX, centerY, radius));
+    }
+
+    @Override
+    public void drawString(DrawingContext ctx, double leftX, double bottomY, double rotationAnticlockWise, String text) {
+        inner.drawString(ctx, leftX, bottomY, rotationAnticlockWise, text);
+        drawActions.add(() -> inner.drawString(ctx, leftX, bottomY, rotationAnticlockWise, text));
+    }
+
+    @Override
+    public double getWidth() {
+        return inner.getWidth();
+    }
+
+    @Override
+    public double getHeight() {
+        return inner.getHeight();
+    }
+
+    @Override
+    public double getStringWidth(DrawingContext ctx, String text) {
+        return inner.getStringWidth(ctx, text);
+    }
+
+    @Override
+    public double getStringHeight(DrawingContext ctx, String text) {
+        return inner.getStringHeight(ctx, text);
+    }
+
+    @Override
+    public void save(String path, String fileType) throws IOException {
+        int realWidth = (int) (getWidth() * JFrameDevice.AWT_POINTS_IN_INCH);
+        int readHeight = (int) (getHeight() * JFrameDevice.AWT_POINTS_IN_INCH);
+        BufferedImage image = new BufferedImage(realWidth, readHeight, TYPE_INT_RGB);
+        Graphics2D imageGraphics = (Graphics2D) image.getGraphics();
+        imageGraphics.setBackground(new Color(255, 255, 255));
+        imageGraphics.clearRect(0, 0, realWidth, readHeight);
+        setGraphics(imageGraphics);
+        for (Runnable drawAction : drawActions) {
+            drawAction.run();
+        }
+        ImageIO.write(image, fileType, new File(path));
+        setGraphics(inner.getCurrentFrame().getGraphics());
+    }
+
+    private void setGraphics(Graphics graphics) {
+        JFrameDevice.defaultInitGraphics((Graphics2D) graphics);
+        inner.getGraphics2D().dispose();
+        inner.setGraphics2D((Graphics2D) graphics);
+    }
+}
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/JFrameDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/Graphics2DDevice.java
similarity index 54%
rename from com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/JFrameDevice.java
rename to com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/Graphics2DDevice.java
index 6ee2268766a6fb73822b1734bfff37def493a466..055084a451c858ad163dd07e432c2121f14c67b4 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/JFrameDevice.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/Graphics2DDevice.java
@@ -20,82 +20,86 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
-package com.oracle.truffle.r.library.fastrGrid.device;
+package com.oracle.truffle.r.library.fastrGrid.device.awt;
 
 import static com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.INCH_TO_POINTS_FACTOR;
 import static java.awt.geom.Path2D.WIND_EVEN_ODD;
 
 import java.awt.BasicStroke;
-import java.awt.BorderLayout;
 import java.awt.Color;
-import java.awt.Dimension;
 import java.awt.Font;
-import java.awt.Graphics;
+import java.awt.FontMetrics;
 import java.awt.Graphics2D;
-import java.awt.HeadlessException;
 import java.awt.Paint;
-import java.awt.RenderingHints;
 import java.awt.Shape;
-import java.awt.Toolkit;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Ellipse2D;
 import java.awt.geom.Path2D;
 import java.awt.geom.Rectangle2D;
-import java.util.function.Supplier;
-
-import javax.swing.JFrame;
-import javax.swing.JPanel;
 
+import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext;
 import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.GridFontStyle;
 import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.GridLineEnd;
 import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.GridLineJoin;
+import com.oracle.truffle.r.library.fastrGrid.device.GridColor;
+import com.oracle.truffle.r.library.fastrGrid.device.GridDevice;
 import com.oracle.truffle.r.runtime.RInternalError;
 
-public final class JFrameDevice implements GridDevice {
+/**
+ * A device that draws to given {@code Graphics2D} object regardless of whether it was created for
+ * e.g. an image, or window. This device only used by other devices and not exposed at the R level.
+ * Note: it is responsibility of the use to handle resources management, i.e. calling
+ * {@code dispose} on the graphics object once it is not needed anymore.
+ */
+public class Graphics2DDevice implements GridDevice {
     // Grid's coordinate system has origin in left bottom corner and y axis grows from bottom to
-    // top. Moreover, the grid system uses inches as units. We use transformation to adjust the java
-    // coordinate system to the grid one. However, in the case of text rendering, we cannot simply
-    // turn upside down the y-axis, because the text would be upside down too, so for text rendering
-    // only, we reset the transformation completely and transform the coordinates ourselves
-    private static final double POINTS_IN_INCH = 72.;
+    // top. Moreover, the grid system uses inches as units. We do not use builtin transformations,
+    // because (a) text rendering gets affected badly (upside down text), (b) the user of this call
+    // may wish to apply his/her own transformations to the graphics object and we should not
+    // interfere with these. In cases we do use transformation, we make sure to set back the
+    // original one after we're done.
+    static final double AWT_POINTS_IN_INCH = 72.;
 
-    private static BasicStroke solidStroke;
     private static BasicStroke blankStroke;
 
-    private FastRFrame currentFrame;
+    private final int width;
+    private final int height;
     private Graphics2D graphics;
+    private final boolean graphicsIsExclusive;
+    private DrawingContext cachedContext;
+
+    /**
+     * @param graphics Object that should be used for the drawing.
+     * @param width Width of the drawing area in AWT units.
+     * @param height Height of the drawing area in AWT units.
+     * @param graphicsIsExclusive If the graphics object is exclusively used for drawing only by
+     *            this class, then it can optimize some things.
+     */
+    Graphics2DDevice(Graphics2D graphics, int width, int height, boolean graphicsIsExclusive) {
+        initStrokes();
+        setGraphics2D(graphics);
+        this.width = width;
+        this.height = height;
+        this.graphicsIsExclusive = graphicsIsExclusive;
+    }
 
     @Override
     public void openNewPage() {
-        initStrokes();
-        if (currentFrame == null) {
-            currentFrame = new FastRFrame();
-            currentFrame.setVisible(true);
-            initGraphics(currentFrame.getGraphics());
-        } else {
-            noTransform(() -> {
-                graphics.clearRect(0, 0, currentFrame.getWidth(), currentFrame.getHeight());
-                return null;
-            });
-        }
+        graphics.clearRect(0, 0, width, height);
     }
 
     @Override
-    public void drawRect(DrawingContext ctx, double leftX, double topY, double width, double height, double rotationAnticlockWise) {
+    public void drawRect(DrawingContext ctx, double leftXIn, double bottomYIn, double widthIn, double heightIn, double rotationAnticlockWise) {
+        int leftX = transX(leftXIn);
+        int topY = transY(bottomYIn + heightIn);
+        int width = transDim(widthIn);
+        int height = transDim(heightIn);
         setContext(ctx);
         if (rotationAnticlockWise == 0.) {
             drawShape(ctx, new Rectangle2D.Double(leftX, topY, width, height));
             return;
         }
-        AffineTransform oldTr = graphics.getTransform();
-        AffineTransform newTr = new AffineTransform(oldTr);
-        newTr.translate(leftX + width / 2, topY + height / 2);
-        newTr.rotate(rotationAnticlockWise);
-        graphics.setTransform(newTr);
-        drawShape(ctx, new Rectangle2D.Double(-(width / 2), -(height / 2), width, height));
-        graphics.setTransform(oldTr);
+        transformed(leftX + width / 2, topY + height / 2, rotationAnticlockWise, () -> drawShape(ctx, new Rectangle2D.Double(-(width / 2), -(height / 2), width, height)));
     }
 
     @Override
@@ -113,78 +117,93 @@ public final class JFrameDevice implements GridDevice {
     }
 
     @Override
-    public void drawCircle(DrawingContext ctx, double centerX, double centerY, double radius) {
+    public void drawCircle(DrawingContext ctx, double centerXIn, double centerYIn, double radiusIn) {
         setContext(ctx);
+        int centerX = transX(centerXIn);
+        int centerY = transY(centerYIn);
+        int radius = transDim(radiusIn);
         drawShape(ctx, new Ellipse2D.Double(centerX - radius, centerY - radius, radius * 2d, radius * 2d));
     }
 
     @Override
-    public void drawString(DrawingContext ctx, double leftX, double bottomY, double rotationAnticlockWise, String text) {
-        setContext(ctx);
-        noTransform(() -> {
-            AffineTransform tr = graphics.getTransform();
-            tr.translate((float) (leftX * POINTS_IN_INCH), (float) (currentFrame.getContentPane().getHeight() - bottomY * POINTS_IN_INCH));
-            tr.rotate(-rotationAnticlockWise);
-            graphics.setTransform(tr);
-            setFont(ctx);
-            graphics.drawString(text, 0, 0);
-            return null;
-        });
+    public void drawString(DrawingContext ctx, double leftXIn, double bottomYIn, double rotationAnticlockWise, String text) {
+        setContextAndFont(ctx);
+        int leftX = transX(leftXIn);
+        FontMetrics fontMetrics = graphics.getFontMetrics(graphics.getFont());
+        int bottomY = transY(bottomYIn) - fontMetrics.getDescent();
+        transformed(leftX, bottomY, rotationAnticlockWise, () -> graphics.drawString(text, 0, 0));
     }
 
     @Override
     public double getWidth() {
-        return currentFrame.getContentPane().getWidth() / POINTS_IN_INCH;
+        return width / AWT_POINTS_IN_INCH;
     }
 
     @Override
     public double getHeight() {
-        return currentFrame.getContentPane().getHeight() / POINTS_IN_INCH;
+        return height / AWT_POINTS_IN_INCH;
     }
 
     @Override
     public double getStringWidth(DrawingContext ctx, String text) {
-        setContext(ctx);
-        return noTransform(() -> {
-            setFont(ctx);
-            int swingUnits = graphics.getFontMetrics(graphics.getFont()).stringWidth(text);
-            return swingUnits / POINTS_IN_INCH;
-        });
+        setContextAndFont(ctx);
+        int swingUnits = graphics.getFontMetrics(graphics.getFont()).stringWidth(text);
+        return swingUnits / AWT_POINTS_IN_INCH;
     }
 
     @Override
     public double getStringHeight(DrawingContext ctx, String text) {
-        setContext(ctx);
-        return noTransform(() -> {
-            setFont(ctx);
-            int swingUnits = graphics.getFont().getSize();
-            return swingUnits / POINTS_IN_INCH;
-        });
+        setContextAndFont(ctx);
+        FontMetrics fontMetrics = graphics.getFontMetrics(graphics.getFont());
+        double swingUnits = fontMetrics.getAscent() + fontMetrics.getDescent();
+        return swingUnits / AWT_POINTS_IN_INCH;
     }
 
-    FastRFrame getCurrentFrame() {
-        return currentFrame;
+    void setGraphics2D(Graphics2D newGraphics) {
+        assert newGraphics != null;
+        graphics = newGraphics;
     }
 
-    void initGraphics(Graphics newGraphics) {
-        if (graphics != null) {
-            graphics.dispose();
+    public Graphics2D getGraphics2D() {
+        return graphics;
+    }
+
+    private int transY(double y) {
+        return height - (int) (y * AWT_POINTS_IN_INCH);
+    }
+
+    private static int transX(double x) {
+        return (int) (x * AWT_POINTS_IN_INCH);
+    }
+
+    private static int transDim(double widthOrHeight) {
+        return (int) (widthOrHeight * AWT_POINTS_IN_INCH);
+    }
+
+    private static void initStrokes() {
+        if (blankStroke != null) {
+            return;
         }
-        graphics = (Graphics2D) newGraphics;
-        graphics.translate(0, currentFrame.getHeight());
-        graphics.scale(POINTS_IN_INCH, -POINTS_IN_INCH);
-        graphics.setStroke(new BasicStroke((float) (1d / POINTS_IN_INCH)));
-        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-        graphics.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
+        blankStroke = new BasicStroke(0f);
+    }
+
+    private void transformed(int centreX, int centreY, double radiansAnticlockwise, Runnable action) {
+        AffineTransform oldTransform = graphics.getTransform();
+        AffineTransform newTr = new AffineTransform(oldTransform);
+        newTr.translate(centreX, centreY);
+        newTr.rotate(-radiansAnticlockwise);
+        graphics.setTransform(newTr);
+        action.run();
+        graphics.setTransform(oldTransform);
     }
 
     private Path2D.Double getPath2D(double[] x, double[] y, int startIndex, int length) {
         assert startIndex >= 0 && startIndex < x.length && startIndex < y.length : "startIndex out of bounds";
         assert length > 0 && (startIndex + length) <= Math.min(x.length, y.length) : "length out of bounds";
         Path2D.Double path = new Path2D.Double(WIND_EVEN_ODD, x.length);
-        path.moveTo(x[startIndex], y[startIndex]);
+        path.moveTo(transX(x[startIndex]), transY(y[startIndex]));
         for (int i = startIndex + 1; i < length; i++) {
-            path.lineTo(x[i], y[i]);
+            path.lineTo(transX(x[i]), transY(y[i]));
         }
         return path;
     }
@@ -198,16 +217,26 @@ public final class JFrameDevice implements GridDevice {
     }
 
     private void setContext(DrawingContext ctx) {
+        if (graphicsIsExclusive && cachedContext == ctx) {
+            return;
+        }
         graphics.setColor(fromGridColor(ctx.getColor()));
         graphics.setStroke(getStrokeFromCtx(ctx));
+        cachedContext = ctx;
     }
 
-    private void setFont(DrawingContext ctx) {
-        float fontSize = (float) ((ctx.getFontSize() / INCH_TO_POINTS_FACTOR) * POINTS_IN_INCH);
+    private void setContextAndFont(DrawingContext ctx) {
+        if (graphicsIsExclusive && cachedContext == ctx) {
+            return;
+        }
+        setContext(ctx);
+        float fontSize = (float) ((ctx.getFontSize() / INCH_TO_POINTS_FACTOR) * AWT_POINTS_IN_INCH);
         Font font = new Font(getFontName(ctx.getFontFamily()), getAwtFontStyle(ctx.getFontStyle()), 1).deriveFont(fontSize);
         graphics.setFont(font);
     }
 
+    // Transformation of DrawingContext data types to AWT constants
+
     private String getFontName(String gridFontFamily) {
         if (gridFontFamily == null) {
             return null;
@@ -240,19 +269,11 @@ public final class JFrameDevice implements GridDevice {
         }
     }
 
-    private <T> T noTransform(Supplier<T> action) {
-        AffineTransform transform = graphics.getTransform();
-        graphics.setTransform(new AffineTransform());
-        T result = action.get();
-        graphics.setTransform(transform);
-        return result;
-    }
-
     private static Color fromGridColor(GridColor color) {
         return new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
     }
 
-    private static BasicStroke getStrokeFromCtx(DrawingContext ctx) {
+    private BasicStroke getStrokeFromCtx(DrawingContext ctx) {
         byte[] type = ctx.getLineType();
         double width = ctx.getLineWidth();
         int lineJoin = fromGridLineJoin(ctx.getLineJoin());
@@ -261,16 +282,13 @@ public final class JFrameDevice implements GridDevice {
         if (type == DrawingContext.GRID_LINE_BLANK) {
             return blankStroke;
         } else if (type == DrawingContext.GRID_LINE_SOLID) {
-            if (width == 1. && solidStroke.getLineJoin() == lineJoin && solidStroke.getMiterLimit() == lineMitre && solidStroke.getEndCap() == endCap) {
-                return solidStroke;
-            }
-            return new BasicStroke((float) (width / POINTS_IN_INCH), endCap, lineJoin, lineMitre);
+            return new BasicStroke((float) (width), endCap, lineJoin, lineMitre);
         }
         float[] pattern = new float[type.length];
         for (int i = 0; i < pattern.length; i++) {
-            pattern[i] = (float) (type[i] / POINTS_IN_INCH);
+            pattern[i] = (float) (type[i]);
         }
-        return new BasicStroke((float) (width / POINTS_IN_INCH), endCap, lineJoin, lineMitre, pattern, 0f);
+        return new BasicStroke((float) (width), endCap, lineJoin, lineMitre, pattern, 0f);
     }
 
     private static int fromGridLineEnd(GridLineEnd lineEnd) {
@@ -298,49 +316,4 @@ public final class JFrameDevice implements GridDevice {
                 throw RInternalError.shouldNotReachHere("unexpected value of GridLineJoin enum");
         }
     }
-
-    private static void initStrokes() {
-        if (solidStroke != null) {
-            return;
-        }
-        solidStroke = new BasicStroke((float) (1f / POINTS_IN_INCH));
-        blankStroke = new BasicStroke(0f);
-    }
-
-    static class FastRFrame extends JFrame {
-        private static final long serialVersionUID = 1L;
-        private final Dimension framePreferredSize = new Dimension(720, 720);
-        private final JPanel fastRComponent = new JPanel();
-
-        FastRFrame() throws HeadlessException {
-            super("FastR");
-            addCloseListener();
-            createUI();
-            center();
-        }
-
-        private void createUI() {
-            setLayout(new BorderLayout());
-            setSize(framePreferredSize);
-            add(fastRComponent, BorderLayout.CENTER);
-            fastRComponent.setPreferredSize(getSize());
-        }
-
-        private void addCloseListener() {
-            addWindowFocusListener(new WindowAdapter() {
-                @Override
-                public void windowClosing(WindowEvent e) {
-                    dispose();
-                }
-            });
-        }
-
-        private void center() {
-            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
-            Dimension frameSize = getSize();
-            int x = (screenSize.width - frameSize.width) / 2;
-            int y = (screenSize.height - frameSize.height) / 2;
-            setLocation(x, y);
-        }
-    }
 }
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/JFrameDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/JFrameDevice.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e31f1f417c26d9f791b73dd9e540838c93bc0e5
--- /dev/null
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/JFrameDevice.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.library.fastrGrid.device.awt;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.HeadlessException;
+import java.awt.RenderingHints;
+import java.awt.Toolkit;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+public final class JFrameDevice extends Graphics2DDevice {
+    private final JFrame currentFrame;
+    private final boolean disposeResources;
+
+    /**
+     * @param frame The frame that should be used for drawing.
+     * @param graphics The graphics object that should be used for drawing. This constructor
+     *            overload allows to initialize the graphics object.
+     * @param disposeResources Should the frame and graphics objects be disposed at {@link #close()}
+     *            .
+     */
+    private JFrameDevice(JFrame frame, Graphics2D graphics, boolean disposeResources) {
+        super(graphics, frame.getContentPane().getWidth(), frame.getContentPane().getHeight(), true);
+        currentFrame = frame;
+        this.disposeResources = disposeResources;
+    }
+
+    /**
+     * Creates a standalone device that manages the window itself and closes it once
+     * {@link #close()} gets called.
+     */
+    public static JFrameDevice create() {
+        FastRFrame frame = new FastRFrame();
+        frame.setVisible(true);
+        Graphics2D graphics = (Graphics2D) frame.getGraphics();
+        defaultInitGraphics(graphics);
+        return new JFrameDevice(frame, graphics, true);
+    }
+
+    @Override
+    public void close() throws DeviceCloseException {
+        if (disposeResources) {
+            getGraphics2D().dispose();
+            currentFrame.dispose();
+        }
+    }
+
+    JFrame getCurrentFrame() {
+        return currentFrame;
+    }
+
+    static void defaultInitGraphics(Graphics2D graphics) {
+        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        graphics.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
+    }
+
+    static class FastRFrame extends JFrame {
+        private static final long serialVersionUID = 1L;
+        private final Dimension framePreferredSize = new Dimension(720, 720);
+        private final JPanel fastRComponent = new JPanel();
+
+        FastRFrame() throws HeadlessException {
+            super("FastR");
+            addCloseListener();
+            createUI();
+            center();
+        }
+
+        private void createUI() {
+            setLayout(new BorderLayout());
+            setSize(framePreferredSize);
+            add(fastRComponent, BorderLayout.CENTER);
+            fastRComponent.setPreferredSize(getSize());
+        }
+
+        private void addCloseListener() {
+            addWindowFocusListener(new WindowAdapter() {
+                @Override
+                public void windowClosing(WindowEvent e) {
+                    dispose();
+                }
+            });
+        }
+
+        private void center() {
+            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+            Dimension frameSize = getSize();
+            int x = (screenSize.width - frameSize.width) / 2;
+            int y = (screenSize.height - frameSize.height) / 2;
+            setLocation(x, y);
+        }
+    }
+}
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevCurr.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevCurr.java
new file mode 100644
index 0000000000000000000000000000000000000000..5c9046e35dc561a4f13b74eb43bc67676f16c48c
--- /dev/null
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevCurr.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.library.fastrGrid.grDevices;
+
+import com.oracle.truffle.r.library.fastrGrid.GridContext;
+import com.oracle.truffle.r.library.fastrGrid.graphics.RGridGraphicsAdapter;
+import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
+
+public final class DevCurr extends RExternalBuiltinNode.Arg0 {
+    static {
+        Casts.noCasts(DevCurr.class);
+    }
+
+    @Override
+    public RAbstractIntVector execute() {
+        int index = GridContext.getContext().getCurrentDeviceIndex();
+        RStringVector names = RDataFactory.createStringVectorFromScalar(RGridGraphicsAdapter.getDeviceName(index));
+        return RDataFactory.createIntVector(new int[]{index + 1}, RDataFactory.COMPLETE_VECTOR, names);
+    }
+}
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevOff.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevOff.java
new file mode 100644
index 0000000000000000000000000000000000000000..e0d57b606a07af5bb4a1a457f0c0a90d5d50dffe
--- /dev/null
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevOff.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.library.fastrGrid.grDevices;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.r.library.fastrGrid.GridContext;
+import com.oracle.truffle.r.library.fastrGrid.device.GridDevice.DeviceCloseException;
+import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RError.Message;
+import com.oracle.truffle.r.runtime.data.RNull;
+
+public abstract class DevOff extends RExternalBuiltinNode.Arg1 {
+    static {
+        Casts casts = new Casts(DevOff.class);
+        casts.arg(0).asIntegerVector().findFirst();
+    }
+
+    public static DevOff create() {
+        return DevOffNodeGen.create();
+    }
+
+    @Specialization
+    @TruffleBoundary
+    public Object devOff(int whichR) {
+        GridContext ctx = GridContext.getContext();
+        int which = whichR - 1; // convert to Java index
+        if (which < 0 || which >= ctx.getDevicesSize()) {
+            throw RError.error(RError.NO_CALLER, Message.GENERIC, "Wrong device number.");
+        }
+        try {
+            ctx.closeDevice(which);
+        } catch (DeviceCloseException e) {
+            throw error(Message.GENERIC, "Cannot close the device. Details: " + e.getMessage());
+        }
+        return RNull.instance;
+    }
+}
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/InitWindowedDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/InitWindowedDevice.java
index d6314346b22dd1fc81e28524a31f56f0b1a27bc6..f1fab85632025eb617b80a6347c691d6a4f8a679 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/InitWindowedDevice.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/InitWindowedDevice.java
@@ -22,18 +22,24 @@
  */
 package com.oracle.truffle.r.library.fastrGrid.grDevices;
 
+import java.io.IOException;
+
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.r.library.fastrGrid.GridContext;
-import com.oracle.truffle.r.library.fastrGrid.GridState;
-import com.oracle.truffle.r.library.fastrGrid.graphics.RGridGraphicsAdapter;
+import com.oracle.truffle.r.library.fastrGrid.device.awt.BufferedImageDevice;
+import com.oracle.truffle.r.library.fastrGrid.device.awt.BufferedImageDevice.NotSupportedImageFormatException;
+import com.oracle.truffle.r.library.fastrGrid.device.awt.BufferedJFrameDevice;
+import com.oracle.truffle.r.library.fastrGrid.device.awt.JFrameDevice;
 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.RArgsValuesAndNames;
 import com.oracle.truffle.r.runtime.data.RNull;
 
 /**
  * Node that handles the {@code C_X11} external calls. Those calls may be initiated from either the
- * {@code X11} function or FastR specific {@code awt} function. In either case the result is that
- * the AWT window is opened and ready for drawing.
+ * {@code X11}, {@code jpeg}, {@code bmp}, {@code png} functions and from FastR specific {@code awt}
+ * . The arguments determine which device should be opened.
  */
 public final class InitWindowedDevice extends RExternalBuiltinNode {
     static {
@@ -43,12 +49,28 @@ public final class InitWindowedDevice extends RExternalBuiltinNode {
     @Override
     @TruffleBoundary
     protected Object call(RArgsValuesAndNames args) {
-        GridState gridState = GridContext.getContext().getGridState();
-        if (!gridState.isDeviceInitialized()) {
-            GridContext.getContext().getCurrentDevice().openNewPage();
-            gridState.setDeviceInitialized();
+        // if the first argument is a String, then it may describes the image format and filename to
+        // use, the format is e.g. "jpeg::quality:filename"
+        if (args.getLength() >= 1) {
+            String name = RRuntime.asString(args.getArgument(0));
+            if (!RRuntime.isNA(name) && name.contains("::")) {
+                return openImageDevice(name);
+            }
+        }
+        // otherwise the
+        GridContext.getContext().setCurrentDevice(args.getLength() == 0 ? "awt" : "X11cairo", new BufferedJFrameDevice(JFrameDevice.create()));
+        return RNull.instance;
+    }
+
+    private Object openImageDevice(String name) {
+        String formatName = name.substring(0, name.indexOf("::"));
+        String filename = name.substring(name.lastIndexOf(':') + 1);
+        try {
+            BufferedImageDevice device = BufferedImageDevice.open(filename, formatName, 700, 700);
+            GridContext.getContext().setCurrentDevice(formatName, device);
+        } catch (NotSupportedImageFormatException e) {
+            throw error(Message.GENERIC, String.format("Format '%s' is not supported.", formatName));
         }
-        RGridGraphicsAdapter.setCurrentDevice(args.getLength() == 0 ? "awt" : "X11cairo");
         return RNull.instance;
     }
 }
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/R/fastrGridDevices.R b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/R/fastrGridDevices.R
index 16a15ce9b2ba2313874a6d1e22eca5c237f15dfc..390bdacd81a88ad29531012f05a1cdf3e662103f 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/R/fastrGridDevices.R
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/R/fastrGridDevices.R
@@ -27,4 +27,20 @@ eval(expression({
     awt <- function(...) {
         .External2(grDevices:::C_X11)
     }
+    # GnuR version only works with "X11cairo" device. Our version of savePlot
+    # works with "awt" device and "X11cairo", which is for us only alias for
+    # "awt". Moreover, we only support formats that awt supports.
+    savePlot <- function (filename = paste("Rplot", type, sep = "."), type = c("png", "jpeg", "bmp"), device = dev.cur()) {
+        type <- match.arg(type)
+        devlist <- dev.list()
+        devcur <- match(device, devlist, NA)
+        if (is.na(devcur)) {
+            stop("no such device")
+        }
+        devname <- names(devlist)[devcur]
+        if (devname != "X11cairo" && devname != "awt") {
+            stop("can only copy from 'X11(type=\"*cairo\")' or 'awt' devices")
+        }
+        invisible(.External2(C_savePlot, filename, type, device))
+    }
 }), asNamespace("grDevices"))
\ No newline at end of file
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/SavePlot.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/SavePlot.java
new file mode 100644
index 0000000000000000000000000000000000000000..c42a0e33b9b96c8116cc6439102d52553c929a47
--- /dev/null
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/SavePlot.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.library.fastrGrid.grDevices;
+
+import java.io.IOException;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.r.library.fastrGrid.GridContext;
+import com.oracle.truffle.r.library.fastrGrid.device.GridDevice;
+import com.oracle.truffle.r.library.fastrGrid.device.ImageSaver;
+import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
+import com.oracle.truffle.r.runtime.RError.Message;
+import com.oracle.truffle.r.runtime.data.RNull;
+
+public abstract class SavePlot extends RExternalBuiltinNode.Arg3 {
+    static {
+        Casts casts = new Casts(SavePlot.class);
+        casts.arg(0).asStringVector().findFirst();
+        casts.arg(1).asStringVector().findFirst();
+    }
+
+    public static SavePlot create() {
+        return SavePlotNodeGen.create();
+    }
+
+    @Specialization
+    @TruffleBoundary
+    Object savePlot(String filename, String type) {
+        GridDevice device = GridContext.getContext().getCurrentDevice();
+        if (!(device instanceof ImageSaver)) {
+            throw error(Message.GENERIC, "Current device does not support plot saving. " +
+                            "Note that FastR savePlot function ignores the device argument and always uses the current device.");
+        }
+        try {
+            ((ImageSaver) device).save(filename, type);
+        } catch (IOException e) {
+            throw error(Message.GENERIC, "I/O error occured when saving the image.");
+        }
+        return RNull.instance;
+    }
+}
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/graphics/RGridGraphicsAdapter.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/graphics/RGridGraphicsAdapter.java
index 2bb5adf63c7a98eab79bddc9739ca6327002107f..f99012e978ce97b5acaeadaa66f323021627937c 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/graphics/RGridGraphicsAdapter.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/graphics/RGridGraphicsAdapter.java
@@ -19,6 +19,7 @@ import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RError.Message;
 import com.oracle.truffle.r.runtime.ROptions;
 import com.oracle.truffle.r.runtime.ROptions.OptionsException;
+import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.context.RContext;
 import com.oracle.truffle.r.runtime.data.RDataFactory;
 import com.oracle.truffle.r.runtime.data.RPairList;
@@ -38,8 +39,14 @@ import com.oracle.truffle.r.runtime.env.REnvironment;
  * tries to open the default device it uses 'awt'. If the future this should be either 'awt' for
  * interactive sessions, or some image format device for batch sessions. We should also honor the
  * R_INTERACTIVE_DEVICE and R_DEFAULT_DEVICE environment variables.
+ *
+ * The responsibility of this class if to provide convenient access to those R-level variables. The
+ * actual devices instances are maintained in
+ * {@link com.oracle.truffle.r.library.fastrGrid.GridContext} since we only have grid devices and no
+ * generic graphics devices.
  */
 public final class RGridGraphicsAdapter {
+    private static final String DEFAULT_DEVICE_OPTION = "device";
     private static final String NULL_DEVICE = "null device";
     /**
      * The graphics devices system maintains two variables .Device and .Devices in the base
@@ -55,23 +62,72 @@ public final class RGridGraphicsAdapter {
     }
 
     public static void initialize() {
+        addDevice(NULL_DEVICE);
         setCurrentDevice(NULL_DEVICE);
         ROptions.ContextStateImpl options = RContext.getInstance().stateROptions;
         try {
-            options.setValue("device", "awt");
+            options.setValue(DEFAULT_DEVICE_OPTION, "awt");
         } catch (OptionsException e) {
             RError.warning(RError.NO_CALLER, Message.GENERIC, "FastR could not set the 'device' options to awt.");
         }
     }
 
+    public static void removeDevice(int index) {
+        assert index > 0 : "cannot remove null device";
+        REnvironment baseEnv = REnvironment.baseEnv();
+        RPairList devices = (RPairList) baseEnv.get(DOT_DEVICES);
+        assert index < devices.getLength() : "wrong index in removeDevice";
+        RPairList prev = devices;
+        for (int i = 0; i < index - 1; ++i) {
+            prev = (RPairList) prev.cdr();
+        }
+        RPairList toRemove = (RPairList) prev.cdr();
+        prev.setCdr(toRemove.cdr());
+        setCurrentDevice((String) prev.car());
+    }
+
     public static void setCurrentDevice(String name) {
         REnvironment baseEnv = REnvironment.baseEnv();
+        assert contains((RPairList) baseEnv.get(DOT_DEVICES), name) : "setCurrentDevice can be invoked only after the device is added with addDevice";
         baseEnv.safePut(DOT_DEVICE, name);
-        Object devices = baseEnv.get(DOT_DEVICES);
-        if (devices instanceof RPairList) {
-            ((RPairList) devices).appendToEnd(RDataFactory.createPairList(name));
+    }
+
+    public static void addDevice(String name) {
+        REnvironment baseEnv = REnvironment.baseEnv();
+        baseEnv.safePut(DOT_DEVICE, name);
+        Object dotDevices = baseEnv.get(DOT_DEVICES);
+        if (dotDevices instanceof RPairList) {
+            ((RPairList) dotDevices).appendToEnd(RDataFactory.createPairList(name));
         } else {
             baseEnv.safePut(DOT_DEVICES, RDataFactory.createPairList(name));
         }
     }
+
+    public static String getDefaultDevice() {
+        ROptions.ContextStateImpl options = RContext.getInstance().stateROptions;
+        String defaultDev = RRuntime.asString(options.getValue(DEFAULT_DEVICE_OPTION));
+        if (RRuntime.isNA(defaultDev)) {
+            throw RError.error(RError.NO_CALLER, Message.GENERIC, "FastR does only supports character value as the default 'device' option");
+        }
+        return defaultDev;
+    }
+
+    public static int getDevicesCount() {
+        Object dotDevices = REnvironment.baseEnv().get(DOT_DEVICES);
+        return dotDevices instanceof RPairList ? ((RPairList) dotDevices).getLength() : 0;
+    }
+
+    public static String getDeviceName(int index) {
+        RPairList dotDevices = (RPairList) REnvironment.baseEnv().get(DOT_DEVICES);
+        return RRuntime.asString(dotDevices.getDataAtAsObject(index));
+    }
+
+    private static boolean contains(RPairList devices, String name) {
+        for (RPairList dev : devices) {
+            if (dev.car().equals(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }