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