Skip to content
Snippets Groups Projects
Commit a97c293e authored by stepan's avatar stepan
Browse files

FastR Grid: implement L_raster external

parent 17c1ec6b
No related branches found
No related tags found
No related merge requests found
......@@ -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":
......
/*
* 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;
}
}
......@@ -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.
......
......@@ -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;
}
}
}
......@@ -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);
......
......@@ -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;
}
}
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment