From a97c293e48473fa1bca38725b7c67471ae3a06a2 Mon Sep 17 00:00:00 2001
From: stepan <stepan.sindelar@oracle.com>
Date: Wed, 14 Jun 2017 15:30:10 +0200
Subject: [PATCH] FastR Grid: implement L_raster external

---
 .../fastrGrid/FastRGridExternalLookup.java    |   2 +
 .../truffle/r/library/fastrGrid/LRaster.java  | 127 ++++++++++++++++++
 .../library/fastrGrid/device/GridDevice.java  |  21 +++
 .../r/library/fastrGrid/device/SVGDevice.java |  64 +++++++++
 .../device/awt/BufferedJFrameDevice.java      |   6 +
 .../device/awt/Graphics2DDevice.java          |  16 +++
 mx.fastr/copyrights/overrides                 |   1 +
 7 files changed, 237 insertions(+)
 create mode 100644 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LRaster.java

diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/FastRGridExternalLookup.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/FastRGridExternalLookup.java
index 016b8d9e98..8b0a0588ca 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
@@ -132,6 +132,8 @@ public final class FastRGridExternalLookup {
                 return LCircle.create();
             case "L_points":
                 return LPoints.create();
+            case "L_raster":
+                return LRaster.create();
 
             // Bounds primitive:
             case "L_rectBounds":
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LRaster.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LRaster.java
new file mode 100644
index 0000000000..6741f4f107
--- /dev/null
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LRaster.java
@@ -0,0 +1,127 @@
+/*
+ * 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) 2001-3 Paul Murrell
+ * Copyright (c) 1998-2013, The R Core Team
+ * Copyright (c) 2017, Oracle and/or its affiliates
+ *
+ * All rights reserved.
+ */
+package com.oracle.truffle.r.library.fastrGrid;
+
+import static com.oracle.truffle.r.library.fastrGrid.GridUtils.getDataAtMod;
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.abstractVectorValue;
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.logicalValue;
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.numericValue;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.r.library.fastrGrid.Unit.UnitConversionContext;
+import com.oracle.truffle.r.library.fastrGrid.device.GridDevice;
+import com.oracle.truffle.r.library.fastrGrid.device.GridDevice.ImageInterpolation;
+import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
+import com.oracle.truffle.r.runtime.RError.Message;
+import com.oracle.truffle.r.runtime.RInternalError;
+import com.oracle.truffle.r.runtime.RRuntime;
+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.RAbstractDoubleVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractLogicalVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
+
+/**
+ * Draws a raster image at specified position. The image may be matrix of integers, in which case it
+ * is used directly, or matrix of any valid color representation, e.g. strings with color codes.
+ */
+public abstract class LRaster extends RExternalBuiltinNode.Arg8 {
+    private static final String NATIVE_RASTER_CLASS = "nativeRaster";
+
+    static {
+        Casts casts = new Casts(LRaster.class);
+        casts.arg(0).mustBe(abstractVectorValue());
+        casts.arg(1).mustBe(abstractVectorValue());
+        casts.arg(2).mustBe(abstractVectorValue());
+        casts.arg(3).mustBe(abstractVectorValue());
+        casts.arg(4).mustBe(abstractVectorValue());
+        casts.arg(5).mustBe(numericValue()).asDoubleVector();
+        casts.arg(6).mustBe(numericValue()).asDoubleVector();
+        casts.arg(7).mustBe(logicalValue()).asLogicalVector();
+    }
+
+    public static LRaster create() {
+        return LRasterNodeGen.create();
+    }
+
+    @Specialization
+    @TruffleBoundary
+    Object doRaster(RAbstractVector raster, RAbstractVector xVec, RAbstractVector yVec, RAbstractVector widthVec, RAbstractVector heightVec, RAbstractDoubleVector hjust, RAbstractDoubleVector vjust,
+                    RAbstractLogicalVector interpolate) {
+        GridContext ctx = GridContext.getContext();
+        GridDevice dev = ctx.getCurrentDevice();
+
+        RList currentVP = ctx.getGridState().getViewPort();
+        GPar gpar = GPar.create(ctx.getGridState().getGpar());
+        ViewPortTransform vpTransform = ViewPortTransform.get(currentVP, dev);
+        ViewPortContext vpContext = ViewPortContext.fromViewPort(currentVP);
+        UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, gpar);
+
+        if (vpTransform.rotationAngle != 0) {
+            throw RInternalError.unimplemented("L_raster with view-port rotation.");
+        }
+
+        int[] pixels;
+        if (raster instanceof RAbstractIntVector && isNativeRaster(raster)) {
+            pixels = ((RAbstractIntVector) raster).materialize().getDataWithoutCopying();
+        } else {
+            int rasterLen = raster.getLength();
+            pixels = new int[rasterLen];
+            for (int i = 0; i < rasterLen; i++) {
+                pixels[i] = GridColorUtils.getColor(raster, i).getRawValue();
+            }
+        }
+
+        Object dimsObj = raster.getAttr(RRuntime.DIM_ATTR_KEY);
+        if (!(dimsObj instanceof RAbstractIntVector)) {
+            throw RInternalError.shouldNotReachHere("Dims attribute should always be integer vector.");
+        }
+        RAbstractIntVector dims = (RAbstractIntVector) dimsObj;
+        if (dims.getLength() != 2) {
+            throw error(Message.GENERIC, "L_raster dims attribute is not of size 2");
+        }
+
+        int length = GridUtils.maxLength(xVec, yVec, widthVec, heightVec);
+        for (int i = 0; i < length; i++) {
+            Size size = Size.fromUnits(widthVec, heightVec, i, conversionCtx);
+            Point origLoc = Point.fromUnits(xVec, yVec, i, conversionCtx);
+            Point transLoc = TransformMatrix.transLocation(origLoc, vpTransform.transform);
+            Point loc = transLoc.justify(size, getDataAtMod(hjust, i), getDataAtMod(vjust, i));
+            ImageInterpolation interpolation = getInterpolation(interpolate, i);
+            dev.drawRaster(loc.x, loc.y, size.getWidth(), size.getHeight(), pixels, dims.getDataAt(1), interpolation);
+        }
+        return RNull.instance;
+    }
+
+    private static ImageInterpolation getInterpolation(RAbstractLogicalVector interpolation, int idx) {
+        if (RRuntime.fromLogical(interpolation.getDataAt(idx % interpolation.getLength()))) {
+            return ImageInterpolation.LINEAR_INTERPOLATION;
+        }
+        return ImageInterpolation.NEAREST_NEIGHBOR;
+    }
+
+    private static boolean isNativeRaster(RAbstractVector vec) {
+        RStringVector clazz = vec.getClassAttr();
+        if (clazz == null) {
+            return false;
+        }
+        for (int i = 0; i < clazz.getLength(); i++) {
+            if (clazz.getDataAt(i).equals(NATIVE_RASTER_CLASS)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
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 0515ded9b5..87ed10b88a 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
@@ -30,6 +30,20 @@ public interface GridDevice {
     int DEFAULT_WIDTH = 720;
     int DEFAULT_HEIGHT = 720;
 
+    /**
+     * Raster image resizing methods.
+     */
+    enum ImageInterpolation {
+        /**
+         * The device should use linear interpolation if available.
+         */
+        LINEAR_INTERPOLATION,
+        /**
+         * The device should use the nearest neighbor interpolation if available.
+         */
+        NEAREST_NEIGHBOR,
+    }
+
     void openNewPage();
 
     /**
@@ -80,6 +94,13 @@ public interface GridDevice {
 
     void drawCircle(DrawingContext ctx, double centerX, double centerY, double radius);
 
+    /**
+     * Draws a raster image at specified position. The pixels array shall be treated as by row
+     * matrix, the values are values compatible with the internal {@link GridColor} representation,
+     * e.g. what {@link GridColor#getRawValue()} would return.
+     */
+    void drawRaster(double leftX, double bottomY, double width, double height, int[] pixels, int pixelsColumnsCount, ImageInterpolation interpolation);
+
     /**
      * Prints a string with left bottom corner at given position rotated by given angle anti clock
      * wise, the centre of the rotation should be the bottom left corer.
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/SVGDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/SVGDevice.java
index baad5ebe41..aae079d9c3 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/SVGDevice.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/SVGDevice.java
@@ -30,6 +30,7 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.text.DecimalFormat;
+import java.util.Base64;
 import java.util.Collections;
 
 import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.GridFontStyle;
@@ -108,6 +109,13 @@ public class SVGDevice implements GridDevice {
         append("<circle vector-effect='non-scaling-stroke' cx='%.3f' cy='%.3f' r='%.3f'/>", centerX, transY(centerY), radius);
     }
 
+    @Override
+    public void drawRaster(double leftX, double bottomY, double width, double height, int[] pixels, int pixelsColumnsCount, ImageInterpolation interpolation) {
+        byte[] bitmap = Bitmap.create(pixels, pixelsColumnsCount);
+        String base64 = Base64.getEncoder().encodeToString(bitmap);
+        append("<image x='%.3f' y='%.3f' width='%.3f' height='%.3f' preserveAspectRatio='none' xlink:href='data:image/bmp;base64,%s'/>", leftX, transY(bottomY + height), width, height, base64);
+    }
+
     @Override
     public void drawString(DrawingContext ctx, double leftX, double bottomY, double rotationAnticlockWise, String text) {
         appendStyle(ctx);
@@ -270,4 +278,60 @@ public class SVGDevice implements GridDevice {
     private static double toDegrees(double rotationAnticlockWise) {
         return (180. / Math.PI) * -rotationAnticlockWise;
     }
+
+    private static final class Bitmap {
+        private static final int FILE_HEADER_SIZE = 14;
+        private static final int IMAGE_HEADER_SIZE = 40;
+        private static final int BITS_PER_PIXEL = 24;
+        private static final int COMPRESSION_TYPE = 0;
+
+        static byte[] create(int[] pixels, int width) {
+            int height = pixels.length / width;
+            int widthInBytes = width * 3;
+            int widthPadding = widthInBytes % 2;
+            widthInBytes += widthPadding;
+
+            int len = FILE_HEADER_SIZE + IMAGE_HEADER_SIZE + height * widthInBytes;
+            byte[] result = new byte[len];
+
+            // file header
+            result[0] = 0x42; // B
+            result[1] = 0x4d; // M
+            int offset = putInt(result, 2, len);
+            offset += 4;    // unused 4B must be zero
+            offset = putInt(result, offset, FILE_HEADER_SIZE + IMAGE_HEADER_SIZE);  // data offset
+
+            // image header
+            offset = putInt(result, offset, IMAGE_HEADER_SIZE);
+            offset = putInt(result, offset, width);
+            offset = putInt(result, offset, height);
+            result[offset++] = 1;   // fixed value
+            result[offset++] = 0;   // fixed value
+            result[offset++] = BITS_PER_PIXEL;
+            result[offset++] = 0;   // bits per pixel is 2B value
+            offset = putInt(result, offset, COMPRESSION_TYPE);
+            // followed by 5 unimportant values (each 4B) that we leave 0
+            offset += 4 * 5;
+
+            // image data
+            for (int row = height - 1; row >= 0; row--) {
+                for (int col = 0; col < width; col++) {
+                    GridColor color = GridColor.fromRawValue(pixels[row * width + col]);
+                    result[offset++] = (byte) (color.getBlue() & 0xff);
+                    result[offset++] = (byte) (color.getGreen() & 0xff);
+                    result[offset++] = (byte) (color.getRed() & 0xff);
+                }
+                offset += widthPadding;
+            }
+            return result;
+        }
+
+        private static int putInt(byte[] data, int offset, int value) {
+            data[offset] = (byte) (value & 0xff);
+            data[offset + 1] = (byte) (value >>> 8 & 0xff);
+            data[offset + 2] = (byte) (value >>> 16 & 0xff);
+            data[offset + 3] = (byte) (value >>> 24 & 0xff);
+            return offset + 4;
+        }
+    }
 }
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
index 607b0ad8b0..84375d755d 100644
--- 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
@@ -146,6 +146,12 @@ public final class BufferedJFrameDevice implements GridDevice, ImageSaver {
         drawActions.add(() -> inner.drawCircle(ctx, centerX, centerY, radius));
     }
 
+    @Override
+    public void drawRaster(double leftX, double bottomY, double width, double height, int[] pixels, int pixelsColumnsCount, ImageInterpolation interpolation) {
+        inner.drawRaster(leftX, bottomY, width, height, pixels, pixelsColumnsCount, interpolation);
+        drawActions.add(() -> inner.drawRaster(leftX, bottomY, width, height, pixels, pixelsColumnsCount, interpolation));
+    }
+
     @Override
     public void drawString(DrawingContext ctx, double leftX, double bottomY, double rotationAnticlockWise, String text) {
         inner.drawString(ctx, leftX, bottomY, rotationAnticlockWise, text);
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/Graphics2DDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/Graphics2DDevice.java
index a363fbd189..1e10c2cd88 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/Graphics2DDevice.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/Graphics2DDevice.java
@@ -31,6 +31,7 @@ import java.awt.Font;
 import java.awt.FontMetrics;
 import java.awt.Graphics2D;
 import java.awt.GraphicsEnvironment;
+import java.awt.Image;
 import java.awt.Paint;
 import java.awt.RenderingHints;
 import java.awt.Shape;
@@ -39,6 +40,7 @@ import java.awt.geom.AffineTransform;
 import java.awt.geom.Ellipse2D;
 import java.awt.geom.Path2D;
 import java.awt.geom.Rectangle2D;
+import java.awt.image.MemoryImageSource;
 
 import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext;
 import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.GridFontStyle;
@@ -133,6 +135,13 @@ public class Graphics2DDevice implements GridDevice {
         drawShape(ctx, new Ellipse2D.Double(centerX - radius, centerY - radius, radius * 2d, radius * 2d));
     }
 
+    @Override
+    public void drawRaster(double leftX, double bottomY, double width, double height, int[] pixels, int pixelsColumnsCount, ImageInterpolation interpolation) {
+        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, fromInterpolation(interpolation));
+        Image image = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(pixelsColumnsCount, pixels.length / pixelsColumnsCount, pixels, 0, pixelsColumnsCount));
+        graphics.drawImage(image, transX(leftX), transY(bottomY + height), transDim(width), transDim(height), null);
+    }
+
     @Override
     public void drawString(DrawingContext ctx, double leftXIn, double bottomYIn, double rotationAnticlockWise, String text) {
         setContextAndFont(ctx);
@@ -339,4 +348,11 @@ public class Graphics2DDevice implements GridDevice {
                 throw RInternalError.shouldNotReachHere("unexpected value of GridLineJoin enum");
         }
     }
+
+    private static Object fromInterpolation(ImageInterpolation interpolation) {
+        if (interpolation == ImageInterpolation.NEAREST_NEIGHBOR) {
+            return RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
+        }
+        return RenderingHints.VALUE_INTERPOLATION_BILINEAR;
+    }
 }
diff --git a/mx.fastr/copyrights/overrides b/mx.fastr/copyrights/overrides
index e3f4d743c2..2013131423 100644
--- a/mx.fastr/copyrights/overrides
+++ b/mx.fastr/copyrights/overrides
@@ -745,6 +745,7 @@ com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/p
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/deriv/Deriv.java,gnu_r_gentleman_ihaka2.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/deriv/DerivVisitor.java,gnu_r_gentleman_ihaka2.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LRect.java,gnu_r_murrel_core.copyright
+com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LRaster.java,gnu_r_murrel_core.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LPretty.java,gnu_r_murrel_core.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LRectBounds.java,gnu_r_murrel_core.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LCircleBounds.java,gnu_r_murrel_core.copyright
-- 
GitLab