diff --git a/pom.xml b/pom.xml
index fb617fb..616c8ee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
org.marlin
marlin
jar
- 0.7.5-Unsafe-OpenJDK
+ 0.8.0-Unsafe-OpenJDK
Marlin software rasterizer
https://github.com/bourgesl/marlin-renderer
@@ -87,7 +87,7 @@
sun-java2d
com/sun/imageio/**
- java/awt/geom/Path2D*
+ java/awt/**
sun/java2d/pipe/**
diff --git a/src/main/java/java/awt/TexturePaint.java b/src/main/java/java/awt/TexturePaint.java
new file mode 100644
index 0000000..7ea2e8a
--- /dev/null
+++ b/src/main/java/java/awt/TexturePaint.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
+ * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+
+package java.awt;
+
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+
+/**
+ * The TexturePaint
class provides a way to fill a
+ * {@link Shape} with a texture that is specified as
+ * a {@link BufferedImage}. The size of the BufferedImage
+ * object should be small because the BufferedImage
data
+ * is copied by the TexturePaint
object.
+ * At construction time, the texture is anchored to the upper
+ * left corner of a {@link Rectangle2D} that is
+ * specified in user space. Texture is computed for
+ * locations in the device space by conceptually replicating the
+ * specified Rectangle2D
infinitely in all directions
+ * in user space and mapping the BufferedImage
to each
+ * replicated Rectangle2D
.
+ * @see Paint
+ * @see Graphics2D#setPaint
+ * @version 1.48, 06/05/07
+ */
+
+public class TexturePaint implements Paint {
+
+ BufferedImage bufImg;
+ double tx;
+ double ty;
+ double sx;
+ double sy;
+
+ /**
+ * Constructs a TexturePaint
object.
+ * @param txtr the BufferedImage
object with the texture
+ * used for painting
+ * @param anchor the Rectangle2D
in user space used to
+ * anchor and replicate the texture
+ */
+ public TexturePaint(BufferedImage txtr,
+ Rectangle2D anchor) {
+ this.bufImg = txtr;
+ this.tx = anchor.getX();
+ this.ty = anchor.getY();
+ this.sx = anchor.getWidth() / bufImg.getWidth();
+ this.sy = anchor.getHeight() / bufImg.getHeight();
+ }
+
+ /**
+ * Returns the BufferedImage
texture used to
+ * fill the shapes.
+ * @return a BufferedImage
.
+ */
+ public BufferedImage getImage() {
+ return bufImg;
+ }
+
+ /**
+ * Returns a copy of the anchor rectangle which positions and
+ * sizes the textured image.
+ * @return the Rectangle2D
used to anchor and
+ * size this TexturePaint
.
+ */
+ public Rectangle2D getAnchorRect() {
+ return new Rectangle2D.Double(tx, ty,
+ sx * bufImg.getWidth(),
+ sy * bufImg.getHeight());
+ }
+
+ /**
+ * Creates and returns a {@link PaintContext} used to
+ * generate a tiled image pattern.
+ * See the {@link Paint#createContext specification} of the
+ * method in the {@link Paint} interface for information
+ * on null parameter handling.
+ *
+ * @param cm the preferred {@link ColorModel} which represents the most convenient
+ * format for the caller to receive the pixel data, or {@code null}
+ * if there is no preference.
+ * @param deviceBounds the device space bounding box
+ * of the graphics primitive being rendered.
+ * @param userBounds the user space bounding box
+ * of the graphics primitive being rendered.
+ * @param xform the {@link AffineTransform} from user
+ * space into device space.
+ * @param hints the set of hints that the context object can use to
+ * choose between rendering alternatives.
+ * @return the {@code PaintContext} for
+ * generating color patterns.
+ * @see Paint
+ * @see PaintContext
+ * @see ColorModel
+ * @see Rectangle
+ * @see Rectangle2D
+ * @see AffineTransform
+ * @see RenderingHints
+ */
+ public PaintContext createContext(ColorModel cm,
+ Rectangle deviceBounds,
+ Rectangle2D userBounds,
+ AffineTransform xform,
+ RenderingHints hints) {
+// TODO: avoid allocating new transform for Identity !
+ if (xform == null) {
+ xform = new AffineTransform();
+ } else {
+ xform = (AffineTransform) xform.clone();
+ }
+ xform.translate(tx, ty);
+ xform.scale(sx, sy);
+
+ return TexturePaintContext.getContext(bufImg, xform, hints,
+ deviceBounds);
+ }
+
+ /**
+ * Returns the transparency mode for this TexturePaint
.
+ * @return the transparency mode for this TexturePaint
+ * as an integer value.
+ * @see Transparency
+ */
+ public int getTransparency() {
+ return (bufImg.getColorModel()).getTransparency();
+ }
+
+}
diff --git a/src/main/java/java/awt/TexturePaintContext.java b/src/main/java/java/awt/TexturePaintContext.java
new file mode 100644
index 0000000..e54e67a
--- /dev/null
+++ b/src/main/java/java/awt/TexturePaintContext.java
@@ -0,0 +1,836 @@
+/*
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+
+package java.awt;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.awt.image.ColorModel;
+import java.awt.image.DirectColorModel;
+import java.awt.image.IndexColorModel;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.lang.ref.WeakReference;
+import sun.awt.image.SunWritableRaster;
+import sun.awt.image.IntegerInterleavedRaster;
+import sun.awt.image.ByteInterleavedRaster;
+
+abstract class TexturePaintContext implements PaintContext {
+ public static ColorModel xrgbmodel =
+ new DirectColorModel(24, 0xff0000, 0xff00, 0xff);
+ public static ColorModel argbmodel = ColorModel.getRGBdefault();
+
+ ColorModel colorModel;
+ int bWidth;
+ int bHeight;
+ int maxWidth;
+
+ WritableRaster outRas;
+
+ double xOrg;
+ double yOrg;
+ double incXAcross;
+ double incYAcross;
+ double incXDown;
+ double incYDown;
+
+ int colincx;
+ int colincy;
+ int colincxerr;
+ int colincyerr;
+ int rowincx;
+ int rowincy;
+ int rowincxerr;
+ int rowincyerr;
+
+ public static PaintContext getContext(BufferedImage bufImg,
+ AffineTransform xform,
+ RenderingHints hints,
+ Rectangle devBounds) {
+ WritableRaster raster = bufImg.getRaster();
+ ColorModel cm = bufImg.getColorModel();
+ int maxw = devBounds.width;
+ Object val = hints.get(RenderingHints.KEY_INTERPOLATION);
+ boolean filter =
+ (val == null
+ ? (hints.get(RenderingHints.KEY_RENDERING) == RenderingHints.VALUE_RENDER_QUALITY)
+ : (val != RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR));
+ if (raster instanceof IntegerInterleavedRaster &&
+ (!filter || isFilterableDCM(cm)))
+ {
+ IntegerInterleavedRaster iir = (IntegerInterleavedRaster) raster;
+ if (iir.getNumDataElements() == 1 && iir.getPixelStride() == 1) {
+// TODO: cache ?
+ return new Int(iir, cm, xform, maxw, filter);
+ }
+ } else if (raster instanceof ByteInterleavedRaster) {
+ ByteInterleavedRaster bir = (ByteInterleavedRaster) raster;
+ if (bir.getNumDataElements() == 1 && bir.getPixelStride() == 1) {
+ if (filter) {
+ if (isFilterableICM(cm)) {
+// TODO: cache ?
+ return new ByteFilter(bir, cm, xform, maxw);
+ }
+ } else {
+// TODO: cache ?
+ return new Byte(bir, cm, xform, maxw);
+ }
+ }
+ }
+// TODO: cache ?
+ return new Any(raster, cm, xform, maxw, filter);
+ }
+
+ public static boolean isFilterableICM(ColorModel cm) {
+ if (cm instanceof IndexColorModel) {
+ IndexColorModel icm = (IndexColorModel) cm;
+ if (icm.getMapSize() <= 256) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isFilterableDCM(ColorModel cm) {
+ if (cm instanceof DirectColorModel) {
+ DirectColorModel dcm = (DirectColorModel) cm;
+ return (isMaskOK(dcm.getAlphaMask(), true) &&
+ isMaskOK(dcm.getRedMask(), false) &&
+ isMaskOK(dcm.getGreenMask(), false) &&
+ isMaskOK(dcm.getBlueMask(), false));
+ }
+ return false;
+ }
+
+ public static boolean isMaskOK(int mask, boolean canbezero) {
+ if (canbezero && mask == 0) {
+ return true;
+ }
+ return (mask == 0xff ||
+ mask == 0xff00 ||
+ mask == 0xff0000 ||
+ mask == 0xff000000);
+ }
+
+ public static ColorModel getInternedColorModel(ColorModel cm) {
+ if (xrgbmodel == cm || xrgbmodel.equals(cm)) {
+ return xrgbmodel;
+ }
+ if (argbmodel == cm || argbmodel.equals(cm)) {
+ return argbmodel;
+ }
+ return cm;
+ }
+
+ TexturePaintContext(ColorModel cm, AffineTransform xform,
+ int bWidth, int bHeight, int maxw) {
+ this.colorModel = getInternedColorModel(cm);
+ this.bWidth = bWidth;
+ this.bHeight = bHeight;
+ this.maxWidth = maxw;
+
+// TODO: check for identity if no transform (avoid allocating new at)
+ try {
+ xform = xform.createInverse();
+ } catch (NoninvertibleTransformException e) {
+ xform.setToScale(0, 0);
+ }
+ this.incXAcross = mod(xform.getScaleX(), bWidth);
+ this.incYAcross = mod(xform.getShearY(), bHeight);
+ this.incXDown = mod(xform.getShearX(), bWidth);
+ this.incYDown = mod(xform.getScaleY(), bHeight);
+ this.xOrg = xform.getTranslateX();
+ this.yOrg = xform.getTranslateY();
+ this.colincx = (int) incXAcross;
+ this.colincy = (int) incYAcross;
+ this.colincxerr = fractAsInt(incXAcross);
+ this.colincyerr = fractAsInt(incYAcross);
+ this.rowincx = (int) incXDown;
+ this.rowincy = (int) incYDown;
+ this.rowincxerr = fractAsInt(incXDown);
+ this.rowincyerr = fractAsInt(incYDown);
+
+ }
+
+ static int fractAsInt(double d) {
+ return (int) ((d % 1.0) * Integer.MAX_VALUE);
+ }
+
+ static double mod(double num, double den) {
+ num = num % den;
+ if (num < 0) {
+ num += den;
+ if (num >= den) {
+ // For very small negative numerators, the answer might
+ // be such a tiny bit less than den that the difference
+ // is smaller than the mantissa of a double allows and
+ // the result would then be rounded to den. If that is
+ // the case then we map that number to 0 as the nearest
+ // modulus representation.
+ num = 0;
+ }
+ }
+ return num;
+ }
+
+ /**
+ * Release the resources allocated for the operation.
+ */
+ public void dispose() {
+ dropRaster(colorModel, outRas);
+ }
+
+ /**
+ * Return the ColorModel of the output.
+ */
+ public ColorModel getColorModel() {
+ return colorModel;
+ }
+
+ /**
+ * Return a Raster containing the colors generated for the graphics
+ * operation.
+ * @param x,y,w,h The area in device space for which colors are
+ * generated.
+ */
+ public Raster getRaster(int x, int y, int w, int h) {
+
+// Called by AlphaPaintPipe (to check)
+
+ if (outRas == null ||
+ outRas.getWidth() < w ||
+ outRas.getHeight() < h)
+ {
+ // If h==1, we will probably get lots of "scanline" rects
+ outRas = makeRaster((h == 1 ? Math.max(w, maxWidth) : w), h);
+ }
+ double X = mod(xOrg + x * incXAcross + y * incXDown, bWidth);
+ double Y = mod(yOrg + x * incYAcross + y * incYDown, bHeight);
+
+ setRaster((int) X, (int) Y, fractAsInt(X), fractAsInt(Y),
+ w, h, bWidth, bHeight,
+ colincx, colincxerr,
+ colincy, colincyerr,
+ rowincx, rowincxerr,
+ rowincy, rowincyerr);
+
+ SunWritableRaster.markDirty(outRas);
+
+ return outRas;
+ }
+
+ private static WeakReference xrgbRasRef;
+ private static WeakReference argbRasRef;
+
+ synchronized static WritableRaster makeRaster(ColorModel cm,
+ Raster srcRas,
+ int w, int h)
+ {
+ if (xrgbmodel == cm) {
+ if (xrgbRasRef != null) {
+ WritableRaster wr = (WritableRaster) xrgbRasRef.get();
+ if (wr != null && wr.getWidth() >= w && wr.getHeight() >= h) {
+ xrgbRasRef = null;
+ return wr;
+ }
+ }
+ // If we are going to cache this Raster, make it non-tiny
+ if (w <= 32 && h <= 32) {
+ w = h = 32;
+ }
+ } else if (argbmodel == cm) {
+ if (argbRasRef != null) {
+ WritableRaster wr = (WritableRaster) argbRasRef.get();
+ if (wr != null && wr.getWidth() >= w && wr.getHeight() >= h) {
+ argbRasRef = null;
+ return wr;
+ }
+ }
+ // If we are going to cache this Raster, make it non-tiny
+ if (w <= 32 && h <= 32) {
+ w = h = 32;
+ }
+ }
+// System.out.println("makeRaster: createCompatibleWritableRaster["+w+"x"+h+"]");
+ if (srcRas != null) {
+ return srcRas.createCompatibleWritableRaster(w, h);
+ } else {
+ return cm.createCompatibleWritableRaster(w, h);
+ }
+ }
+
+ synchronized static void dropRaster(ColorModel cm, Raster outRas) {
+ if (outRas == null) {
+ return;
+ }
+ if (xrgbmodel == cm) {
+ xrgbRasRef = new WeakReference<>(outRas);
+ } else if (argbmodel == cm) {
+ argbRasRef = new WeakReference<>(outRas);
+ }
+ }
+
+ private static WeakReference byteRasRef;
+
+ synchronized static WritableRaster makeByteRaster(Raster srcRas,
+ int w, int h)
+ {
+ if (byteRasRef != null) {
+ WritableRaster wr = (WritableRaster) byteRasRef.get();
+ if (wr != null && wr.getWidth() >= w && wr.getHeight() >= h) {
+ byteRasRef = null;
+ return wr;
+ }
+ }
+ // If we are going to cache this Raster, make it non-tiny
+ if (w <= 32 && h <= 32) {
+ w = h = 32;
+ }
+ return srcRas.createCompatibleWritableRaster(w, h);
+ }
+
+ synchronized static void dropByteRaster(Raster outRas) {
+ if (outRas == null) {
+ return;
+ }
+ byteRasRef = new WeakReference<>(outRas);
+ }
+
+ public abstract WritableRaster makeRaster(int w, int h);
+ public abstract void setRaster(int x, int y, int xerr, int yerr,
+ int w, int h, int bWidth, int bHeight,
+ int colincx, int colincxerr,
+ int colincy, int colincyerr,
+ int rowincx, int rowincxerr,
+ int rowincy, int rowincyerr);
+
+ /*
+ * Blends the four ARGB values in the rgbs array using the factors
+ * described by xmul and ymul in the following ratio:
+ *
+ * rgbs[0] * (1-xmul) * (1-ymul) +
+ * rgbs[1] * ( xmul) * (1-ymul) +
+ * rgbs[2] * (1-xmul) * ( ymul) +
+ * rgbs[3] * ( xmul) * ( ymul)
+ *
+ * xmul and ymul are integer values in the half-open range [0, 2^31)
+ * where 0 == 0.0 and 2^31 == 1.0.
+ *
+ * Note that since the range is half-open, the values are always
+ * logically less than 1.0. This makes sense because while choosing
+ * pixels to blend, when the error values reach 1.0 we move to the
+ * next pixel and reset them to 0.0.
+ */
+ public static int blend(int rgbs[], int xmul, int ymul) {
+ // xmul/ymul are 31 bits wide, (0 => 2^31-1)
+ // shift them to 12 bits wide, (0 => 2^12-1)
+ xmul = (xmul >>> 19);
+ ymul = (ymul >>> 19);
+ int accumA, accumR, accumG, accumB;
+ accumA = accumR = accumG = accumB = 0;
+ for (int i = 0; i < 4; i++) {
+ int rgb = rgbs[i];
+ // The complement of the [xy]mul values (1-[xy]mul) can result
+ // in new values in the range (1 => 2^12). Thus for any given
+ // loop iteration, the values could be anywhere in (0 => 2^12).
+ xmul = (1<<12) - xmul;
+ if ((i & 1) == 0) {
+ ymul = (1<<12) - ymul;
+ }
+ // xmul and ymul are each 12 bits (0 => 2^12)
+ // factor is thus 24 bits (0 => 2^24)
+ int factor = xmul * ymul;
+ if (factor != 0) {
+ // accum variables will accumulate 32 bits
+ // bytes extracted from rgb fit in 8 bits (0 => 255)
+ // byte * factor thus fits in 32 bits (0 => 255 * 2^24)
+ accumA += (((rgb >>> 24) ) * factor);
+ accumR += (((rgb >>> 16) & 0xff) * factor);
+ accumG += (((rgb >>> 8) & 0xff) * factor);
+ accumB += (((rgb ) & 0xff) * factor);
+ }
+ }
+ return ((((accumA + (1<<23)) >>> 24) << 24) |
+ (((accumR + (1<<23)) >>> 24) << 16) |
+ (((accumG + (1<<23)) >>> 24) << 8) |
+ (((accumB + (1<<23)) >>> 24) ));
+ }
+
+ static class Int extends TexturePaintContext {
+ IntegerInterleavedRaster srcRas;
+ int inData[];
+ int inOff;
+ int inSpan;
+ int outData[];
+ int outOff;
+ int outSpan;
+ boolean filter;
+
+ public Int(IntegerInterleavedRaster srcRas, ColorModel cm,
+ AffineTransform xform, int maxw, boolean filter)
+ {
+ super(cm, xform, srcRas.getWidth(), srcRas.getHeight(), maxw);
+ this.srcRas = srcRas;
+ this.inData = srcRas.getDataStorage();
+ this.inSpan = srcRas.getScanlineStride();
+ this.inOff = srcRas.getDataOffset(0);
+ this.filter = filter;
+ }
+
+ public WritableRaster makeRaster(int w, int h) {
+ WritableRaster ras = makeRaster(colorModel, srcRas, w, h);
+ IntegerInterleavedRaster iiRas = (IntegerInterleavedRaster) ras;
+ outData = iiRas.getDataStorage();
+ outSpan = iiRas.getScanlineStride();
+ outOff = iiRas.getDataOffset(0);
+ return ras;
+ }
+
+ public void setRaster(int x, int y, int xerr, int yerr,
+ int w, int h, int bWidth, int bHeight,
+ int colincx, int colincxerr,
+ int colincy, int colincyerr,
+ int rowincx, int rowincxerr,
+ int rowincy, int rowincyerr) {
+ int[] inData = this.inData;
+ int[] outData = this.outData;
+ int out = outOff;
+ int inSpan = this.inSpan;
+ int inOff = this.inOff;
+ int outSpan = this.outSpan;
+ boolean filter = this.filter;
+ boolean normalx = (colincx == 1 && colincxerr == 0 &&
+ colincy == 0 && colincyerr == 0) && !filter;
+ int rowx = x;
+ int rowy = y;
+ int rowxerr = xerr;
+ int rowyerr = yerr;
+ if (normalx) {
+ outSpan -= w;
+ }
+ int rgbs[] = filter ? new int[4] : null;
+ for (int j = 0; j < h; j++) {
+ if (normalx) {
+ int in = inOff + rowy * inSpan + bWidth;
+ x = bWidth - rowx;
+ out += w;
+ if (bWidth >= 32) {
+ int i = w;
+ while (i > 0) {
+ int copyw = (i < x) ? i : x;
+ System.arraycopy(inData, in - x,
+ outData, out - i,
+ copyw);
+ i -= copyw;
+ if ((x -= copyw) == 0) {
+ x = bWidth;
+ }
+ }
+ } else {
+ for (int i = w; i > 0; i--) {
+ outData[out - i] = inData[in - x];
+ if (--x == 0) {
+ x = bWidth;
+ }
+ }
+ }
+ } else {
+ x = rowx;
+ y = rowy;
+ xerr = rowxerr;
+ yerr = rowyerr;
+ for (int i = 0; i < w; i++) {
+ if (filter) {
+ int nextx, nexty;
+ if ((nextx = x + 1) >= bWidth) {
+ nextx = 0;
+ }
+ if ((nexty = y + 1) >= bHeight) {
+ nexty = 0;
+ }
+ rgbs[0] = inData[inOff + y * inSpan + x];
+ rgbs[1] = inData[inOff + y * inSpan + nextx];
+ rgbs[2] = inData[inOff + nexty * inSpan + x];
+ rgbs[3] = inData[inOff + nexty * inSpan + nextx];
+ outData[out + i] =
+ TexturePaintContext.blend(rgbs, xerr, yerr);
+ } else {
+ outData[out + i] = inData[inOff + y * inSpan + x];
+ }
+ if ((xerr += colincxerr) < 0) {
+ xerr &= Integer.MAX_VALUE;
+ x++;
+ }
+ if ((x += colincx) >= bWidth) {
+ x -= bWidth;
+ }
+ if ((yerr += colincyerr) < 0) {
+ yerr &= Integer.MAX_VALUE;
+ y++;
+ }
+ if ((y += colincy) >= bHeight) {
+ y -= bHeight;
+ }
+ }
+ }
+ if ((rowxerr += rowincxerr) < 0) {
+ rowxerr &= Integer.MAX_VALUE;
+ rowx++;
+ }
+ if ((rowx += rowincx) >= bWidth) {
+ rowx -= bWidth;
+ }
+ if ((rowyerr += rowincyerr) < 0) {
+ rowyerr &= Integer.MAX_VALUE;
+ rowy++;
+ }
+ if ((rowy += rowincy) >= bHeight) {
+ rowy -= bHeight;
+ }
+ out += outSpan;
+ }
+ }
+ }
+
+ static class Byte extends TexturePaintContext {
+ ByteInterleavedRaster srcRas;
+ byte inData[];
+ int inOff;
+ int inSpan;
+ byte outData[];
+ int outOff;
+ int outSpan;
+
+ public Byte(ByteInterleavedRaster srcRas, ColorModel cm,
+ AffineTransform xform, int maxw)
+ {
+ super(cm, xform, srcRas.getWidth(), srcRas.getHeight(), maxw);
+ this.srcRas = srcRas;
+ this.inData = srcRas.getDataStorage();
+ this.inSpan = srcRas.getScanlineStride();
+ this.inOff = srcRas.getDataOffset(0);
+ }
+
+ public WritableRaster makeRaster(int w, int h) {
+ WritableRaster ras = makeByteRaster(srcRas, w, h);
+ ByteInterleavedRaster biRas = (ByteInterleavedRaster) ras;
+ outData = biRas.getDataStorage();
+ outSpan = biRas.getScanlineStride();
+ outOff = biRas.getDataOffset(0);
+ return ras;
+ }
+
+ public void dispose() {
+ dropByteRaster(outRas);
+ }
+
+ public void setRaster(int x, int y, int xerr, int yerr,
+ int w, int h, int bWidth, int bHeight,
+ int colincx, int colincxerr,
+ int colincy, int colincyerr,
+ int rowincx, int rowincxerr,
+ int rowincy, int rowincyerr) {
+ byte[] inData = this.inData;
+ byte[] outData = this.outData;
+ int out = outOff;
+ int inSpan = this.inSpan;
+ int inOff = this.inOff;
+ int outSpan = this.outSpan;
+ boolean normalx = (colincx == 1 && colincxerr == 0 &&
+ colincy == 0 && colincyerr == 0);
+ int rowx = x;
+ int rowy = y;
+ int rowxerr = xerr;
+ int rowyerr = yerr;
+ if (normalx) {
+ outSpan -= w;
+ }
+ for (int j = 0; j < h; j++) {
+ if (normalx) {
+ int in = inOff + rowy * inSpan + bWidth;
+ x = bWidth - rowx;
+ out += w;
+ if (bWidth >= 32) {
+ int i = w;
+ while (i > 0) {
+ int copyw = (i < x) ? i : x;
+ System.arraycopy(inData, in - x,
+ outData, out - i,
+ copyw);
+ i -= copyw;
+ if ((x -= copyw) == 0) {
+ x = bWidth;
+ }
+ }
+ } else {
+ for (int i = w; i > 0; i--) {
+ outData[out - i] = inData[in - x];
+ if (--x == 0) {
+ x = bWidth;
+ }
+ }
+ }
+ } else {
+ x = rowx;
+ y = rowy;
+ xerr = rowxerr;
+ yerr = rowyerr;
+ for (int i = 0; i < w; i++) {
+ outData[out + i] = inData[inOff + y * inSpan + x];
+ if ((xerr += colincxerr) < 0) {
+ xerr &= Integer.MAX_VALUE;
+ x++;
+ }
+ if ((x += colincx) >= bWidth) {
+ x -= bWidth;
+ }
+ if ((yerr += colincyerr) < 0) {
+ yerr &= Integer.MAX_VALUE;
+ y++;
+ }
+ if ((y += colincy) >= bHeight) {
+ y -= bHeight;
+ }
+ }
+ }
+ if ((rowxerr += rowincxerr) < 0) {
+ rowxerr &= Integer.MAX_VALUE;
+ rowx++;
+ }
+ if ((rowx += rowincx) >= bWidth) {
+ rowx -= bWidth;
+ }
+ if ((rowyerr += rowincyerr) < 0) {
+ rowyerr &= Integer.MAX_VALUE;
+ rowy++;
+ }
+ if ((rowy += rowincy) >= bHeight) {
+ rowy -= bHeight;
+ }
+ out += outSpan;
+ }
+ }
+ }
+
+ static class ByteFilter extends TexturePaintContext {
+ ByteInterleavedRaster srcRas;
+ int inPalette[];
+ byte inData[];
+ int inOff;
+ int inSpan;
+ int outData[];
+ int outOff;
+ int outSpan;
+
+ public ByteFilter(ByteInterleavedRaster srcRas, ColorModel cm,
+ AffineTransform xform, int maxw)
+ {
+ super((cm.getTransparency() == Transparency.OPAQUE
+ ? xrgbmodel : argbmodel),
+ xform, srcRas.getWidth(), srcRas.getHeight(), maxw);
+ this.inPalette = new int[256];
+ ((IndexColorModel) cm).getRGBs(this.inPalette);
+ this.srcRas = srcRas;
+ this.inData = srcRas.getDataStorage();
+ this.inSpan = srcRas.getScanlineStride();
+ this.inOff = srcRas.getDataOffset(0);
+ }
+
+ public WritableRaster makeRaster(int w, int h) {
+ // Note that we do not pass srcRas to makeRaster since it
+ // is a Byte Raster and this colorModel needs an Int Raster
+ WritableRaster ras = makeRaster(colorModel, null, w, h);
+ IntegerInterleavedRaster iiRas = (IntegerInterleavedRaster) ras;
+ outData = iiRas.getDataStorage();
+ outSpan = iiRas.getScanlineStride();
+ outOff = iiRas.getDataOffset(0);
+ return ras;
+ }
+
+ public void setRaster(int x, int y, int xerr, int yerr,
+ int w, int h, int bWidth, int bHeight,
+ int colincx, int colincxerr,
+ int colincy, int colincyerr,
+ int rowincx, int rowincxerr,
+ int rowincy, int rowincyerr) {
+ byte[] inData = this.inData;
+ int[] outData = this.outData;
+ int out = outOff;
+ int inSpan = this.inSpan;
+ int inOff = this.inOff;
+ int outSpan = this.outSpan;
+ int rowx = x;
+ int rowy = y;
+ int rowxerr = xerr;
+ int rowyerr = yerr;
+ int rgbs[] = new int[4];
+ for (int j = 0; j < h; j++) {
+ x = rowx;
+ y = rowy;
+ xerr = rowxerr;
+ yerr = rowyerr;
+ for (int i = 0; i < w; i++) {
+ int nextx, nexty;
+ if ((nextx = x + 1) >= bWidth) {
+ nextx = 0;
+ }
+ if ((nexty = y + 1) >= bHeight) {
+ nexty = 0;
+ }
+ rgbs[0] = inPalette[0xff & inData[inOff + x +
+ inSpan * y]];
+ rgbs[1] = inPalette[0xff & inData[inOff + nextx +
+ inSpan * y]];
+ rgbs[2] = inPalette[0xff & inData[inOff + x +
+ inSpan * nexty]];
+ rgbs[3] = inPalette[0xff & inData[inOff + nextx +
+ inSpan * nexty]];
+ outData[out + i] =
+ TexturePaintContext.blend(rgbs, xerr, yerr);
+ if ((xerr += colincxerr) < 0) {
+ xerr &= Integer.MAX_VALUE;
+ x++;
+ }
+ if ((x += colincx) >= bWidth) {
+ x -= bWidth;
+ }
+ if ((yerr += colincyerr) < 0) {
+ yerr &= Integer.MAX_VALUE;
+ y++;
+ }
+ if ((y += colincy) >= bHeight) {
+ y -= bHeight;
+ }
+ }
+ if ((rowxerr += rowincxerr) < 0) {
+ rowxerr &= Integer.MAX_VALUE;
+ rowx++;
+ }
+ if ((rowx += rowincx) >= bWidth) {
+ rowx -= bWidth;
+ }
+ if ((rowyerr += rowincyerr) < 0) {
+ rowyerr &= Integer.MAX_VALUE;
+ rowy++;
+ }
+ if ((rowy += rowincy) >= bHeight) {
+ rowy -= bHeight;
+ }
+ out += outSpan;
+ }
+ }
+ }
+
+ static class Any extends TexturePaintContext {
+ WritableRaster srcRas;
+ boolean filter;
+
+ public Any(WritableRaster srcRas, ColorModel cm,
+ AffineTransform xform, int maxw, boolean filter)
+ {
+ super(cm, xform, srcRas.getWidth(), srcRas.getHeight(), maxw);
+ this.srcRas = srcRas;
+ this.filter = filter;
+ }
+
+ public WritableRaster makeRaster(int w, int h) {
+ return makeRaster(colorModel, srcRas, w, h);
+ }
+
+ public void setRaster(int x, int y, int xerr, int yerr,
+ int w, int h, int bWidth, int bHeight,
+ int colincx, int colincxerr,
+ int colincy, int colincyerr,
+ int rowincx, int rowincxerr,
+ int rowincy, int rowincyerr) {
+ Object data = null;
+ int rowx = x;
+ int rowy = y;
+ int rowxerr = xerr;
+ int rowyerr = yerr;
+ WritableRaster srcRas = this.srcRas;
+ WritableRaster outRas = this.outRas;
+ int rgbs[] = filter ? new int[4] : null;
+ for (int j = 0; j < h; j++) {
+ x = rowx;
+ y = rowy;
+ xerr = rowxerr;
+ yerr = rowyerr;
+ for (int i = 0; i < w; i++) {
+ data = srcRas.getDataElements(x, y, data);
+ if (filter) {
+ int nextx, nexty;
+ if ((nextx = x + 1) >= bWidth) {
+ nextx = 0;
+ }
+ if ((nexty = y + 1) >= bHeight) {
+ nexty = 0;
+ }
+ rgbs[0] = colorModel.getRGB(data);
+ data = srcRas.getDataElements(nextx, y, data);
+ rgbs[1] = colorModel.getRGB(data);
+ data = srcRas.getDataElements(x, nexty, data);
+ rgbs[2] = colorModel.getRGB(data);
+ data = srcRas.getDataElements(nextx, nexty, data);
+ rgbs[3] = colorModel.getRGB(data);
+ int rgb =
+ TexturePaintContext.blend(rgbs, xerr, yerr);
+ data = colorModel.getDataElements(rgb, data);
+ }
+ outRas.setDataElements(i, j, data);
+ if ((xerr += colincxerr) < 0) {
+ xerr &= Integer.MAX_VALUE;
+ x++;
+ }
+ if ((x += colincx) >= bWidth) {
+ x -= bWidth;
+ }
+ if ((yerr += colincyerr) < 0) {
+ yerr &= Integer.MAX_VALUE;
+ y++;
+ }
+ if ((y += colincy) >= bHeight) {
+ y -= bHeight;
+ }
+ }
+ if ((rowxerr += rowincxerr) < 0) {
+ rowxerr &= Integer.MAX_VALUE;
+ rowx++;
+ }
+ if ((rowx += rowincx) >= bWidth) {
+ rowx -= bWidth;
+ }
+ if ((rowyerr += rowincyerr) < 0) {
+ rowyerr &= Integer.MAX_VALUE;
+ rowy++;
+ }
+ if ((rowy += rowincy) >= bHeight) {
+ rowy -= bHeight;
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/sun/java2d/marlin/Curve.java b/src/main/java/sun/java2d/marlin/Curve.java
index 675a1d8..7880703 100644
--- a/src/main/java/sun/java2d/marlin/Curve.java
+++ b/src/main/java/sun/java2d/marlin/Curve.java
@@ -56,10 +56,10 @@ void set(float x1, float y1,
float x3, float y3,
float x4, float y4)
{
- ax = 3.0f * (x2 - x3) + x4 - x1;
- ay = 3.0f * (y2 - y3) + y4 - y1;
- bx = 3.0f * (x1 - 2.0f * x2 + x3);
- by = 3.0f * (y1 - 2.0f * y2 + y3);
+ ax = 3.0f * (x2 - x3) + (x4 - x1);
+ ay = 3.0f * (y2 - y3) + (y4 - y1);
+ bx = 3.0f * ((x1 - x2) + (x3 - x2));
+ by = 3.0f * ((y1 - y2) + (y3 - y2));
cx = 3.0f * (x2 - x1);
cy = 3.0f * (y2 - y1);
dx = x1;
diff --git a/src/main/java/sun/java2d/marlin/DDasher.java b/src/main/java/sun/java2d/marlin/DDasher.java
index 3999b9e..069b6cf 100644
--- a/src/main/java/sun/java2d/marlin/DDasher.java
+++ b/src/main/java/sun/java2d/marlin/DDasher.java
@@ -202,7 +202,7 @@ void dispose() {
@Override
public void moveTo(double x0, double y0) {
- if (firstSegidx > 0) {
+ if (firstSegidx != 0) {
out.moveTo(sx, sy);
emitFirstSegments();
}
@@ -251,104 +251,133 @@ private void emitFirstSegments() {
private int firstSegidx;
// precondition: pts must be in relative coordinates (relative to x0,y0)
- private void goTo(double[] pts, int off, final int type) {
- double x = pts[off + type - 4];
- double y = pts[off + type - 3];
- if (dashOn) {
+ private void goTo(double[] pts, int off, final int type, final boolean on) {
+ final int index = off + type;
+ final double x = pts[index - 4];
+ final double y = pts[index - 3];
+
+ if (on) {
if (starting) {
- int len = type - 1; // - 2 + 1
- int segIdx = firstSegidx;
- double[] buf = firstSegmentsBuffer;
- if (segIdx + len > buf.length) {
- if (DO_STATS) {
- rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer
- .add(segIdx + len);
- }
- firstSegmentsBuffer = buf
- = firstSegmentsBuffer_ref.widenArray(buf, segIdx,
- segIdx + len);
- }
- buf[segIdx++] = type;
- len--;
- // small arraycopy (2, 4 or 6) but with offset:
- System.arraycopy(pts, off, buf, segIdx, len);
- segIdx += len;
- firstSegidx = segIdx;
+ goTo_starting(pts, off, type);
} else {
if (needsMoveTo) {
- out.moveTo(x0, y0);
needsMoveTo = false;
+ out.moveTo(x0, y0);
}
emitSeg(pts, off, type);
}
} else {
- starting = false;
+ if (starting) {
+ // low probability test (hotspot)
+ starting = false;
+ }
needsMoveTo = true;
}
this.x0 = x;
this.y0 = y;
}
+ private void goTo_starting(final double[] pts, final int off, final int type) {
+ int len = type - 1; // - 2 + 1
+ int segIdx = firstSegidx;
+ double[] buf = firstSegmentsBuffer;
+
+ if (segIdx + len > buf.length) {
+ if (DO_STATS) {
+ rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer
+ .add(segIdx + len);
+ }
+ firstSegmentsBuffer = buf
+ = firstSegmentsBuffer_ref.widenArray(buf, segIdx,
+ segIdx + len);
+ }
+ buf[segIdx++] = type;
+ len--;
+ // small arraycopy (2, 4 or 6) but with offset:
+ System.arraycopy(pts, off, buf, segIdx, len);
+ firstSegidx = segIdx + len;
+ }
+
@Override
public void lineTo(double x1, double y1) {
- double dx = x1 - x0;
- double dy = y1 - y0;
+ final double dx = x1 - x0;
+ final double dy = y1 - y0;
double len = dx*dx + dy*dy;
if (len == 0.0d) {
return;
}
- len = Math.sqrt(len);
- // The scaling factors needed to get the dx and dy of the
+ // The scaling factors [0..1] needed to get the dx and dy of the
// transformed dash segments.
+ len = Math.sqrt(len);
final double cx = dx / len;
final double cy = dy / len;
final double[] _curCurvepts = curCurvepts;
final double[] _dash = dash;
+ final int _dashLen = this.dashLen;
+ int _idx = idx;
+ boolean _dashOn = dashOn;
+ double _phase = phase;
+
double leftInThisDashSegment;
- double dashdx, dashdy, p;
+ double d, dashdx, dashdy, p;
while (true) {
- leftInThisDashSegment = _dash[idx] - phase;
+ d = _dash[_idx];
+ leftInThisDashSegment = d - _phase;
if (len <= leftInThisDashSegment) {
_curCurvepts[0] = x1;
_curCurvepts[1] = y1;
- goTo(_curCurvepts, 0, 4);
+
+ goTo(_curCurvepts, 0, 4, _dashOn);
// Advance phase within current dash segment
- phase += len;
+ _phase += len;
+
// TODO: compare double values using epsilon:
if (len == leftInThisDashSegment) {
- phase = 0.0d;
- idx = (idx + 1) % dashLen;
- dashOn = !dashOn;
+ _phase = 0.0d;
+ _idx = (_idx + 1) % _dashLen;
+ _dashOn = !_dashOn;
}
+
+ // Save local state:
+ idx = _idx;
+ dashOn = _dashOn;
+ phase = _phase;
return;
}
- dashdx = _dash[idx] * cx;
- dashdy = _dash[idx] * cy;
+ // TODO: out of loop if nb(seg) >> dashLen
+ // may avoid multiply by precomputation
+ dashdx = d * cx;
+ dashdy = d * cy;
- if (phase == 0.0d) {
+ if (_phase == 0.0d) {
+ // most probable case (99%)
_curCurvepts[0] = x0 + dashdx;
_curCurvepts[1] = y0 + dashdy;
} else {
- p = leftInThisDashSegment / _dash[idx];
+ // may avoid divide by precomputation
+ // TODO: out of loop if nb(seg) >> dashLen
+ p = leftInThisDashSegment / d;
+
_curCurvepts[0] = x0 + p * dashdx;
_curCurvepts[1] = y0 + p * dashdy;
}
- goTo(_curCurvepts, 0, 4);
+ goTo(_curCurvepts, 0, 4, _dashOn);
len -= leftInThisDashSegment;
+
// Advance to next dash segment
- idx = (idx + 1) % dashLen;
- dashOn = !dashOn;
- phase = 0.0d;
+ _idx = (_idx + 1) % _dashLen;
+ _dashOn = !_dashOn;
+ _phase = 0.0d;
}
}
@@ -361,39 +390,55 @@ private void somethingTo(int type) {
if (pointCurve(curCurvepts, type)) {
return;
}
- li.initializeIterationOnCurve(curCurvepts, type);
-
+ final LengthIterator _li = li;
+ final double[] _curCurvepts = curCurvepts;
+ final double[] _dash = dash;
+ final int _dashLen = this.dashLen;
+
+ _li.initializeIterationOnCurve(_curCurvepts, type);
+
+ int _idx = idx;
+ boolean _dashOn = dashOn;
+ double _phase = phase;
+
// initially the current curve is at curCurvepts[0...type]
int curCurveoff = 0;
double lastSplitT = 0.0d;
double t;
- double leftInThisDashSegment = dash[idx] - phase;
+ double leftInThisDashSegment = _dash[_idx] - _phase;
- while ((t = li.next(leftInThisDashSegment)) < 1.0d) {
+ while ((t = _li.next(leftInThisDashSegment)) < 1.0d) {
if (t != 0.0d) {
DHelpers.subdivideAt((t - lastSplitT) / (1.0d - lastSplitT),
- curCurvepts, curCurveoff,
- curCurvepts, 0,
- curCurvepts, type, type);
+ _curCurvepts, curCurveoff,
+ _curCurvepts, 0,
+ _curCurvepts, type, type);
lastSplitT = t;
- goTo(curCurvepts, 2, type);
+ goTo(_curCurvepts, 2, type, _dashOn);
curCurveoff = type;
}
// Advance to next dash segment
- idx = (idx + 1) % dashLen;
- dashOn = !dashOn;
- phase = 0.0d;
- leftInThisDashSegment = dash[idx];
+ _idx = (_idx + 1) % _dashLen;
+ _dashOn = !_dashOn;
+ _phase = 0.0d;
+ leftInThisDashSegment = _dash[_idx];
}
- goTo(curCurvepts, curCurveoff+2, type);
- phase += li.lastSegLen();
- if (phase >= dash[idx]) {
- phase = 0.0d;
- idx = (idx + 1) % dashLen;
- dashOn = !dashOn;
+
+ goTo(_curCurvepts, curCurveoff+2, type, _dashOn);
+
+ _phase += _li.lastSegLen();
+ if (_phase >= _dash[_idx]) {
+ _phase = 0.0d;
+ _idx = (_idx + 1) % _dashLen;
+ _dashOn = !_dashOn;
}
+ // Save local state:
+ idx = _idx;
+ dashOn = _dashOn;
+ phase = _phase;
+
// reset LengthIterator:
- li.reset();
+ _li.reset();
}
private static boolean pointCurve(double[] curve, int type) {
@@ -669,11 +714,12 @@ private void goLeft() {
// this is a bit of a hack. It returns -1 if we're not on a leaf, and
// the length of the leaf if we are on a leaf.
private double onLeaf() {
- double[] curve = recCurveStack[recLevel];
+ final double[] curve = recCurveStack[recLevel];
+ final int _curveType = curveType;
double polyLen = 0.0d;
double x0 = curve[0], y0 = curve[1];
- for (int i = 2; i < curveType; i += 2) {
+ for (int i = 2; i < _curveType; i += 2) {
final double x1 = curve[i], y1 = curve[i+1];
final double len = DHelpers.linelen(x0, y0, x1, y1);
polyLen += len;
@@ -683,8 +729,8 @@ private double onLeaf() {
}
final double lineLen = DHelpers.linelen(curve[0], curve[1],
- curve[curveType-2],
- curve[curveType-1]);
+ curve[_curveType-2],
+ curve[_curveType-1]);
if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) {
return (polyLen + lineLen) / 2.0d;
}
@@ -717,7 +763,7 @@ public void quadTo(double x1, double y1, double x2, double y2) {
@Override
public void closePath() {
lineTo(sx, sy);
- if (firstSegidx > 0) {
+ if (firstSegidx != 0) {
if (!dashOn || needsMoveTo) {
out.moveTo(sx, sy);
}
@@ -728,7 +774,7 @@ public void closePath() {
@Override
public void pathDone() {
- if (firstSegidx > 0) {
+ if (firstSegidx != 0) {
out.moveTo(sx, sy);
emitFirstSegments();
}
diff --git a/src/main/java/sun/java2d/marlin/Dasher.java b/src/main/java/sun/java2d/marlin/Dasher.java
index d8f9c9a..4fc9141 100644
--- a/src/main/java/sun/java2d/marlin/Dasher.java
+++ b/src/main/java/sun/java2d/marlin/Dasher.java
@@ -56,6 +56,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
private float startPhase;
private boolean startDashOn;
private int startIdx;
+ private float dashTotal;
private boolean starting;
private boolean needsMoveTo;
@@ -138,7 +139,7 @@ Dasher init(final PathConsumer2D out, float[] dash, int dashLen,
dashOn = !dashOn;
}
}
- } else if (phase > 0) {
+ } else if (phase > 0.0f) {
if (cycles >= MAX_CYCLES) {
phase = 0.0f;
} else {
@@ -158,14 +159,18 @@ Dasher init(final PathConsumer2D out, float[] dash, int dashLen,
this.dash = dash;
this.dashLen = dashLen;
- this.startPhase = this.phase = phase;
+ this.dashTotal = sum;
+ this.phase = phase;
+ this.startPhase = phase;
this.startDashOn = dashOn;
this.startIdx = sidx;
this.starting = true;
- needsMoveTo = false;
- firstSegidx = 0;
+ this.needsMoveTo = false;
+ this.firstSegidx = 0;
this.recycleDashes = recycleDashes;
+
+// System.out.println("dashTotal: "+dashTotal);
return this; // fluent API
}
@@ -203,7 +208,7 @@ float[] copyDashArray(final float[] dashes) {
@Override
public void moveTo(float x0, float y0) {
- if (firstSegidx > 0) {
+ if (firstSegidx != 0) {
out.moveTo(sx, sy);
emitFirstSegments();
}
@@ -211,8 +216,10 @@ public void moveTo(float x0, float y0) {
this.idx = startIdx;
this.dashOn = this.startDashOn;
this.phase = this.startPhase;
- this.sx = this.x0 = x0;
- this.sy = this.y0 = y0;
+ this.sx = x0;
+ this.sy = y0;
+ this.x0 = x0;
+ this.y0 = y0;
this.starting = true;
}
@@ -237,7 +244,7 @@ private void emitSeg(float[] buf, int off, int type) {
private void emitFirstSegments() {
final float[] fSegBuf = firstSegmentsBuffer;
- for (int i = 0; i < firstSegidx; ) {
+ for (int i = 0, len = firstSegidx; i < len; ) {
int type = (int)fSegBuf[i];
emitSeg(fSegBuf, i + 1, type);
i += (type - 1);
@@ -252,109 +259,206 @@ private void emitFirstSegments() {
private int firstSegidx;
// precondition: pts must be in relative coordinates (relative to x0,y0)
- private void goTo(float[] pts, int off, final int type) {
- float x = pts[off + type - 4];
- float y = pts[off + type - 3];
- if (dashOn) {
+ private void goTo(final float[] pts, final int off, final int type, final boolean on) {
+ final int index = off + type;
+ final float x = pts[index - 4];
+ final float y = pts[index - 3];
+
+ if (on) {
if (starting) {
- int len = type - 1; // - 2 + 1
- int segIdx = firstSegidx;
- float[] buf = firstSegmentsBuffer;
- if (segIdx + len > buf.length) {
- if (DO_STATS) {
- rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer
- .add(segIdx + len);
- }
- firstSegmentsBuffer = buf
- = firstSegmentsBuffer_ref.widenArray(buf, segIdx,
- segIdx + len);
- }
- buf[segIdx++] = type;
- len--;
- // small arraycopy (2, 4 or 6) but with offset:
- System.arraycopy(pts, off, buf, segIdx, len);
- segIdx += len;
- firstSegidx = segIdx;
+ goTo_starting(pts, off, type);
} else {
if (needsMoveTo) {
- out.moveTo(x0, y0);
needsMoveTo = false;
+ out.moveTo(x0, y0);
}
emitSeg(pts, off, type);
}
} else {
- starting = false;
+ if (starting) {
+ // low probability test (hotspot)
+ starting = false;
+ }
needsMoveTo = true;
}
this.x0 = x;
this.y0 = y;
}
+ private void goTo_starting(final float[] pts, final int off, final int type) {
+ int len = type - 1; // - 2 + 1
+ int segIdx = firstSegidx;
+ float[] buf = firstSegmentsBuffer;
+
+ if (segIdx + len > buf.length) {
+ if (DO_STATS) {
+ rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer
+ .add(segIdx + len);
+ }
+ firstSegmentsBuffer = buf
+ = firstSegmentsBuffer_ref.widenArray(buf, segIdx,
+ segIdx + len);
+ }
+ buf[segIdx++] = type;
+ len--;
+ // small arraycopy (2, 4 or 6) but with offset:
+ System.arraycopy(pts, off, buf, segIdx, len);
+ firstSegidx = segIdx + len;
+ }
+
+ private final static boolean USE_NAIVE_SUM = false;
+
@Override
public void lineTo(float x1, float y1) {
- float dx = x1 - x0;
- float dy = y1 - y0;
+ final float dx = x1 - x0;
+ final float dy = y1 - y0;
float len = dx*dx + dy*dy;
if (len == 0.0f) {
return;
}
- len = (float) Math.sqrt(len);
- // The scaling factors needed to get the dx and dy of the
+ // The scaling factors [0..1] needed to get the dx and dy of the
// transformed dash segments.
+ len = (float) Math.sqrt(len);
final float cx = dx / len;
final float cy = dy / len;
-
+
+ final float dcx = Math.ulp(cx);
+ final float dcy = Math.ulp(cy);
+/*
+ System.out.println("cx: "+cx + " cy: " + cy);
+ System.out.println("len vs dash: "+len + " vs " + dashTotal + " ratio = "+(len / dashTotal));
+*/
final float[] _curCurvepts = curCurvepts;
final float[] _dash = dash;
+ final int _dashLen = this.dashLen;
+
+ int _idx = idx;
+ boolean _dashOn = dashOn;
+ float _phase = phase;
float leftInThisDashSegment;
- float dashdx, dashdy, p;
+ float el = 0.0f, ex = 0.0f, ey = 0.0f;
+
+ float d, x, y, dashdx, dashdy, p;
+ float z, t;
while (true) {
- leftInThisDashSegment = _dash[idx] - phase;
+ d = _dash[_idx];
+ /* 0 < leftInThisDashSegment < dashTotal ie SMALL */
+ leftInThisDashSegment = d - _phase;
if (len <= leftInThisDashSegment) {
_curCurvepts[0] = x1;
_curCurvepts[1] = y1;
- goTo(_curCurvepts, 0, 4);
+
+ goTo(_curCurvepts, 0, 4, _dashOn);
// Advance phase within current dash segment
- phase += len;
+ _phase += len;
+
// TODO: compare float values using epsilon:
if (len == leftInThisDashSegment) {
- phase = 0.0f;
- idx = (idx + 1) % dashLen;
- dashOn = !dashOn;
+ _phase = 0.0f;
+ _idx = (_idx + 1) % _dashLen;
+ _dashOn = !_dashOn;
}
+
+ // Save local state:
+ idx = _idx;
+ dashOn = _dashOn;
+ phase = _phase;
return;
}
- dashdx = _dash[idx] * cx;
- dashdy = _dash[idx] * cy;
-
- if (phase == 0.0f) {
- _curCurvepts[0] = x0 + dashdx;
- _curCurvepts[1] = y0 + dashdy;
+ // TODO: out of loop if nb(seg) >> dashLen
+ // may avoid multiply by precomputation
+ dashdx = d * cx;
+ dashdy = d * cy;
+
+ // Numeric precision problem: d << (x0,y0)
+ x = this.x0;
+ y = this.y0;
+
+ if (_phase == 0.0f) {
+ // most probable case (99%)
+if (USE_NAIVE_SUM) {
+ // naive sum:
+ _curCurvepts[0] = x + dashdx;
+ _curCurvepts[1] = y + dashdy;
+} else {
+ // kahan sum:
+ z = dashdx - ex;
+ t = x + z;
+ ex = (t - x) - z - dcx;
+ _curCurvepts[0] = t;
+// System.out.println("z: "+z + " t: "+t + " e: "+ex);
+
+ // kahan sum:
+ z = dashdy - ey;
+ t = y + z;
+ ey = (t - y) - z - dcy;
+ _curCurvepts[1] = t;
+// System.out.println("z: "+z + " t: "+t + " e: "+ey);
+}
} else {
- p = leftInThisDashSegment / _dash[idx];
- _curCurvepts[0] = x0 + p * dashdx;
- _curCurvepts[1] = y0 + p * dashdy;
+// System.out.println("phase != 0 at: "+len);
+
+ // may avoid divide by precomputation
+ // TODO: out of loop if nb(seg) >> dashLen
+ p = leftInThisDashSegment / d;
+
+if (USE_NAIVE_SUM) {
+ // naive sum:
+ _curCurvepts[0] = x + p * dashdx;
+ _curCurvepts[1] = y + p * dashdy;
+} else {
+ // kahan sum:
+ z = (p * dashdx) - ex;
+ t = x + z;
+ ex = (t - x) - z - dcx;
+ _curCurvepts[0] = t;
+// System.out.println("z: "+z + " t: "+t + " e: "+ex);
+
+ // kahan sum:
+ z = (p * dashdy) - ey;
+ t = y + z;
+ ey = (t - y) - z - dcy;
+ _curCurvepts[1] = t;
+// System.out.println("z: "+z + " t: "+t + " e: "+ey);
+}
}
- goTo(_curCurvepts, 0, 4);
+ goTo(_curCurvepts, 0, 4, _dashOn);
+if (USE_NAIVE_SUM) {
+ // naive sum:
len -= leftInThisDashSegment;
+} else {
+ // Very high dynamic: (len / dashTotal) > 4million
+ // TODO: find the proper threshold with accuracy ie dynamic range is larger than float precision !
+ // see ulps ! ie smallest dash length vs line length ~ 5e6 ?
+
+ // kahan sum:
+ z = leftInThisDashSegment - el;
+ t = len - z;
+ el = (len - t) - z;
+ len = t;
+// System.out.println("z: "+z + " t: "+t + " e: "+el);
+}
+
// Advance to next dash segment
- idx = (idx + 1) % dashLen;
- dashOn = !dashOn;
- phase = 0.0f;
+ _idx = (_idx + 1) % _dashLen;
+ _dashOn = !_dashOn;
+ _phase = 0.0f;
}
- }
-
+ }
+
// shared instance in Dasher
private final LengthIterator li = new LengthIterator();
+
+ private final float[] initCurvepts = new float[8];
// preconditions: curCurvepts must be an array of length at least 2 * type,
// that contains the curve we want to dash in the first type elements
@@ -362,39 +466,104 @@ private void somethingTo(int type) {
if (pointCurve(curCurvepts, type)) {
return;
}
- li.initializeIterationOnCurve(curCurvepts, type);
+ final LengthIterator _li = li;
+ final float[] _curCurvepts = curCurvepts;
+ final float[] _dash = dash;
+ final int _dashLen = this.dashLen;
+
+ if (true) {
+ // backup (lolo):
+ // optimize arraycopy (8 values faster than 6 = type):
+ System.arraycopy(_curCurvepts, 0, initCurvepts, 0, 8);
+ }
+ _li.initializeIterationOnCurve(_curCurvepts, type);
+
+ int _idx = idx;
+ boolean _dashOn = dashOn;
+ float _phase = phase;
+
// initially the current curve is at curCurvepts[0...type]
- int curCurveoff = 0;
+ int curCurveoff = 0; // type
float lastSplitT = 0.0f;
float t;
- float leftInThisDashSegment = dash[idx] - phase;
-
- while ((t = li.next(leftInThisDashSegment)) < 1.0f) {
+ float leftInThisDashSegment = _dash[_idx] - _phase;
+
+// int n = 0;
+ int c = 0;
+
+ while ((t = _li.next(leftInThisDashSegment)) < 1.0f) {
+/*
+ if (t != 0.0f && _dashOn) {
+ // special case next 2 lines for prevt=0 and t=1
+// left,right = subdivide curve at prevt
+ Helpers.subdivideAt(lastSplitT,
+ initCurvepts, 0,
+ _curCurvepts, 0,
+ _curCurvepts, type, type);
+// left,right = subdivide right at (t-prevt)/(1-prevt)
+ Helpers.subdivideAt((t - lastSplitT) / (1.0f - lastSplitT),
+ _curCurvepts, type,
+ _curCurvepts, 0,
+ _curCurvepts, type, type);
+// emit left;
+ goTo(_curCurvepts, 2, type, _dashOn);
+ // curve is not modified
+ // right is not saved
+ }
+// prevt = t;
+ lastSplitT = t;
+ // Advance to next dash segment
+ _idx = (_idx + 1) % _dashLen;
+ _dashOn = !_dashOn;
+ _phase = 0.0f;
+ leftInThisDashSegment = _dash[_idx];
+*/
if (t != 0.0f) {
Helpers.subdivideAt((t - lastSplitT) / (1.0f - lastSplitT),
- curCurvepts, curCurveoff,
- curCurvepts, 0,
- curCurvepts, type, type);
+ _curCurvepts, curCurveoff,
+ _curCurvepts, 0,
+ _curCurvepts, type, type);
lastSplitT = t;
- goTo(curCurvepts, 2, type);
+ goTo(_curCurvepts, 2, type, _dashOn);
curCurveoff = type;
}
// Advance to next dash segment
- idx = (idx + 1) % dashLen;
- dashOn = !dashOn;
- phase = 0.0f;
- leftInThisDashSegment = dash[idx];
+ _idx = (_idx + 1) % _dashLen;
+ _dashOn = !_dashOn;
+ _phase = 0.0f;
+ leftInThisDashSegment = _dash[_idx];
+
+ if (true) {
+ if ((++c) == 10000) {
+ // Split from initial curve:
+ Helpers.subdivideAt(lastSplitT,
+ initCurvepts, 0,
+ _curCurvepts, 0,
+ _curCurvepts, type, type);
+ c = 0;
+ }
+ }
+// n++;
}
- goTo(curCurvepts, curCurveoff+2, type);
- phase += li.lastSegLen();
- if (phase >= dash[idx]) {
- phase = 0.0f;
- idx = (idx + 1) % dashLen;
- dashOn = !dashOn;
+
+// System.out.println("somethingTo("+type+") : "+n+" iterations.");
+
+ goTo(_curCurvepts, curCurveoff + 2, type, _dashOn);
+
+ _phase += _li.lastSegLen();
+ if (_phase >= _dash[_idx]) {
+ _phase = 0.0f;
+ _idx = (_idx + 1) % _dashLen;
+ _dashOn = !_dashOn;
}
+ // Save local state:
+ idx = _idx;
+ dashOn = _dashOn;
+ phase = _phase;
+
// reset LengthIterator:
- li.reset();
+ _li.reset();
}
private static boolean pointCurve(float[] curve, int type) {
@@ -670,11 +839,12 @@ private void goLeft() {
// this is a bit of a hack. It returns -1 if we're not on a leaf, and
// the length of the leaf if we are on a leaf.
private float onLeaf() {
- float[] curve = recCurveStack[recLevel];
+ final float[] curve = recCurveStack[recLevel];
+ final int _curveType = curveType;
float polyLen = 0.0f;
float x0 = curve[0], y0 = curve[1];
- for (int i = 2; i < curveType; i += 2) {
+ for (int i = 2; i < _curveType; i += 2) {
final float x1 = curve[i], y1 = curve[i+1];
final float len = Helpers.linelen(x0, y0, x1, y1);
polyLen += len;
@@ -684,8 +854,8 @@ private float onLeaf() {
}
final float lineLen = Helpers.linelen(curve[0], curve[1],
- curve[curveType-2],
- curve[curveType-1]);
+ curve[_curveType-2],
+ curve[_curveType-1]);
if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) {
return (polyLen + lineLen) / 2.0f;
}
@@ -718,7 +888,7 @@ public void quadTo(float x1, float y1, float x2, float y2) {
@Override
public void closePath() {
lineTo(sx, sy);
- if (firstSegidx > 0) {
+ if (firstSegidx != 0) {
if (!dashOn || needsMoveTo) {
out.moveTo(sx, sy);
}
@@ -729,7 +899,7 @@ public void closePath() {
@Override
public void pathDone() {
- if (firstSegidx > 0) {
+ if (firstSegidx != 0) {
out.moveTo(sx, sy);
emitFirstSegments();
}
diff --git a/src/main/java/sun/java2d/marlin/Helpers.java b/src/main/java/sun/java2d/marlin/Helpers.java
index 85b9f36..8ab4864 100644
--- a/src/main/java/sun/java2d/marlin/Helpers.java
+++ b/src/main/java/sun/java2d/marlin/Helpers.java
@@ -30,6 +30,9 @@
import static java.lang.Math.sqrt;
import static java.lang.Math.cbrt;
import static java.lang.Math.acos;
+import java.util.Arrays;
+import sun.awt.geom.PathConsumer2D;
+import static sun.java2d.marlin.MarlinConst.INITIAL_EDGES_COUNT;
final class Helpers implements MarlinConst {
@@ -176,15 +179,6 @@ static int filterOutNotInAB(float[] nums, final int off, final int len,
return ret;
}
- static float polyLineLength(float[] poly, final int off, final int nCoords) {
- assert nCoords % 2 == 0 && poly.length >= off + nCoords : "";
- float acc = 0.0f;
- for (int i = off + 2; i < off + nCoords; i += 2) {
- acc += linelen(poly[i], poly[i+1], poly[i-2], poly[i-1]);
- }
- return acc;
- }
-
static float linelen(float x1, float y1, float x2, float y2) {
final float dx = x2 - x1;
final float dy = y2 - y1;
@@ -438,4 +432,271 @@ static void subdivideAt(float t, float[] src, int srcoff,
return;
}
}
+
+ // From sun.java2d.loops.GeneralRenderer:
+
+ static final int OUTCODE_TOP = 1;
+ static final int OUTCODE_BOTTOM = 2;
+ static final int OUTCODE_LEFT = 4;
+ static final int OUTCODE_RIGHT = 8;
+
+ static int outcode(final float x, final float y,
+ final float[] clipRect)
+ {
+ int code;
+ if (y < clipRect[0]) {
+ code = OUTCODE_TOP;
+ } else if (y >= clipRect[1]) {
+ code = OUTCODE_BOTTOM;
+ } else {
+ code = 0;
+ }
+ if (x < clipRect[2]) {
+ code |= OUTCODE_LEFT;
+ } else if (x >= clipRect[3]) {
+ code |= OUTCODE_RIGHT;
+ }
+ return code;
+ }
+
+ // a stack of polynomial curves where each curve shares endpoints with
+ // adjacent ones.
+ static final class PolyStack {
+ private static final byte TYPE_LINETO = (byte) 0;
+ private static final byte TYPE_QUADTO = (byte) 1;
+ private static final byte TYPE_CUBICTO = (byte) 2;
+
+ // curves capacity = edges count (8192) = edges x 2 (coords)
+ private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1;
+
+ // types capacity = edges count (4096)
+ private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT;
+
+ float[] curves;
+ int end;
+ byte[] curveTypes;
+ int numCurves;
+
+ // per-thread renderer context
+ final RendererContext rdrCtx;
+
+ // curves ref (dirty)
+ final FloatArrayCache.Reference curves_ref;
+ // curveTypes ref (dirty)
+ final ByteArrayCache.Reference curveTypes_ref;
+
+ // used marks (stats only)
+ int curveTypesUseMark;
+ int curvesUseMark;
+
+ /**
+ * Constructor
+ * @param rdrCtx per-thread renderer context
+ */
+ PolyStack(final RendererContext rdrCtx) {
+ this.rdrCtx = rdrCtx;
+
+ curves_ref = rdrCtx.newDirtyFloatArrayRef(INITIAL_CURVES_COUNT); // 32K
+ curves = curves_ref.initial;
+
+ curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K
+ curveTypes = curveTypes_ref.initial;
+ numCurves = 0;
+ end = 0;
+
+ if (DO_STATS) {
+ curveTypesUseMark = 0;
+ curvesUseMark = 0;
+ }
+ }
+
+ /**
+ * Disposes this PolyStack:
+ * clean up before reusing this instance
+ */
+ void dispose() {
+ end = 0;
+ numCurves = 0;
+
+ if (DO_STATS) {
+ rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark);
+ rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark);
+ rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark);
+
+ // reset marks
+ curveTypesUseMark = 0;
+ curvesUseMark = 0;
+ }
+
+ // Return arrays:
+ // curves and curveTypes are kept dirty
+ curves = curves_ref.putArray(curves);
+ curveTypes = curveTypes_ref.putArray(curveTypes);
+ }
+
+ boolean isEmpty() {
+ return (numCurves == 0);
+ }
+
+ private void ensureSpace(final int n) {
+ // use substraction to avoid integer overflow:
+ if (curves.length - end < n) {
+ if (DO_STATS) {
+ rdrCtx.stats.stat_array_stroker_polystack_curves
+ .add(end + n);
+ }
+ curves = curves_ref.widenArray(curves, end, end + n);
+ }
+ if (curveTypes.length <= numCurves) {
+ if (DO_STATS) {
+ rdrCtx.stats.stat_array_stroker_polystack_curveTypes
+ .add(numCurves + 1);
+ }
+ curveTypes = curveTypes_ref.widenArray(curveTypes,
+ numCurves,
+ numCurves + 1);
+ }
+ }
+
+ void pushCubic(float x0, float y0,
+ float x1, float y1,
+ float x2, float y2)
+ {
+ ensureSpace(6);
+ curveTypes[numCurves++] = TYPE_CUBICTO;
+ // we reverse the coordinate order to make popping easier
+ final float[] _curves = curves;
+ int e = end;
+ _curves[e++] = x2; _curves[e++] = y2;
+ _curves[e++] = x1; _curves[e++] = y1;
+ _curves[e++] = x0; _curves[e++] = y0;
+ end = e;
+ }
+
+ void pushQuad(float x0, float y0,
+ float x1, float y1)
+ {
+ ensureSpace(4);
+ curveTypes[numCurves++] = TYPE_QUADTO;
+ final float[] _curves = curves;
+ int e = end;
+ _curves[e++] = x1; _curves[e++] = y1;
+ _curves[e++] = x0; _curves[e++] = y0;
+ end = e;
+ }
+
+ void pushLine(float x, float y) {
+ ensureSpace(2);
+ curveTypes[numCurves++] = TYPE_LINETO;
+ curves[end++] = x; curves[end++] = y;
+ }
+
+ void pullAll(PathConsumer2D io) {
+ if (DO_STATS) {
+ // update used marks:
+ if (numCurves > curveTypesUseMark) {
+ curveTypesUseMark = numCurves;
+ }
+ if (end > curvesUseMark) {
+ curvesUseMark = end;
+ }
+ }
+ final byte[] _curveTypes = curveTypes;
+ final float[] _curves = curves;
+ final int nc = numCurves;
+ int e = 0;
+
+ for (int i = 0; i < nc; i++) {
+ switch(_curveTypes[i]) {
+ case TYPE_LINETO:
+ io.lineTo(_curves[e], _curves[e+1]);
+ e += 2;
+ continue;
+ case TYPE_QUADTO:
+ io.quadTo(_curves[e+0], _curves[e+1],
+ _curves[e+2], _curves[e+3]);
+ e += 4;
+ continue;
+ case TYPE_CUBICTO:
+ io.curveTo(_curves[e+0], _curves[e+1],
+ _curves[e+2], _curves[e+3],
+ _curves[e+4], _curves[e+5]);
+ e += 6;
+ continue;
+ default:
+ }
+ }
+ numCurves = 0;
+ end = 0;
+ }
+
+ void popAll(PathConsumer2D io) {
+ if (DO_STATS) {
+ // update used marks:
+ if (numCurves > curveTypesUseMark) {
+ curveTypesUseMark = numCurves;
+ }
+ if (end > curvesUseMark) {
+ curvesUseMark = end;
+ }
+ }
+ final byte[] _curveTypes = curveTypes;
+ final float[] _curves = curves;
+ int nc = numCurves;
+ int e = end;
+
+ while (nc != 0) {
+ switch(_curveTypes[--nc]) {
+ case TYPE_LINETO:
+ e -= 2;
+ io.lineTo(_curves[e], _curves[e+1]);
+ continue;
+ case TYPE_QUADTO:
+ e -= 4;
+ io.quadTo(_curves[e+0], _curves[e+1],
+ _curves[e+2], _curves[e+3]);
+ continue;
+ case TYPE_CUBICTO:
+ e -= 6;
+ io.curveTo(_curves[e+0], _curves[e+1],
+ _curves[e+2], _curves[e+3],
+ _curves[e+4], _curves[e+5]);
+ continue;
+ default:
+ }
+ }
+ numCurves = 0;
+ end = 0;
+ }
+
+ @Override
+ public String toString() {
+ String ret = "";
+ int nc = numCurves;
+ int last = end;
+ int len;
+ while (nc != 0) {
+ switch(curveTypes[--nc]) {
+ case TYPE_LINETO:
+ len = 2;
+ ret += "line: ";
+ break;
+ case TYPE_QUADTO:
+ len = 4;
+ ret += "quad: ";
+ break;
+ case TYPE_CUBICTO:
+ len = 6;
+ ret += "cubic: ";
+ break;
+ default:
+ len = 0;
+ }
+ last -= len;
+ ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len))
+ + "\n";
+ }
+ return ret;
+ }
+ }
}
diff --git a/src/main/java/sun/java2d/marlin/MarlinProperties.java b/src/main/java/sun/java2d/marlin/MarlinProperties.java
index 0bb13ff..9ba8f0a 100644
--- a/src/main/java/sun/java2d/marlin/MarlinProperties.java
+++ b/src/main/java/sun/java2d/marlin/MarlinProperties.java
@@ -145,6 +145,11 @@ public static boolean isUseSimplifier() {
return getBoolean("sun.java2d.renderer.useSimplifier", "false");
}
+ public static boolean isDoClip() {
+ return getBoolean("sun.java2d.renderer.clip", "true");
+ }
+
+ // TODO: remove
public static boolean isDoClipCurves() {
return getBoolean("sun.java2d.renderer.clip.curves", "false");
}
diff --git a/src/main/java/sun/java2d/marlin/MarlinRenderingEngine.java b/src/main/java/sun/java2d/marlin/MarlinRenderingEngine.java
index 217a787..95fa9c2 100644
--- a/src/main/java/sun/java2d/marlin/MarlinRenderingEngine.java
+++ b/src/main/java/sun/java2d/marlin/MarlinRenderingEngine.java
@@ -84,6 +84,9 @@ abstract PathIterator getNormalizingPathIterator(RendererContext rdrCtx,
static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
static final float LOWER_BND = -UPPER_BND;
+
+ static final boolean DO_CLIP = MarlinProperties.isDoClip();
+ static final boolean DO_TRACE = false;
/**
* Public constructor
@@ -324,6 +327,7 @@ final void strokeTo(final RendererContext rdrCtx,
int dashLen = -1;
boolean recycleDashes = false;
+ float scale = 1.0f;
if (at != null && !at.isIdentity()) {
final double a = at.getScaleX();
@@ -356,7 +360,7 @@ final void strokeTo(final RendererContext rdrCtx,
// a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
// leave a bit of room for error.
if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) {
- final float scale = (float) Math.sqrt(a*a + c*c);
+ scale = (float) Math.sqrt(a*a + c*c);
if (dashes != null) {
recycleDashes = true;
@@ -394,16 +398,24 @@ final void strokeTo(final RendererContext rdrCtx,
at = null;
}
+ final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
+
+ if (DO_TRACE) {
+ // trace Stroker:
+ pc2d = transformerPC2D.traceStroker(pc2d);
+ }
+
if (USE_SIMPLIFIER) {
// Use simplifier after stroker before Renderer
// to remove collinear segments (notably due to cap square)
pc2d = rdrCtx.simplifier.init(pc2d);
}
-
- final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
+
+ // deltaTransformConsumer may adjust the clip rectangle:
pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat);
-
- pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit);
+
+ // stroker will adjust the clip rectangle (width / miter limit):
+ pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale);
if (dashes != null) {
if (!recycleDashes) {
@@ -411,9 +423,22 @@ final void strokeTo(final RendererContext rdrCtx,
}
pc2d = rdrCtx.dasher.init(pc2d, dashes, dashLen, dashphase,
recycleDashes);
+ } else if ((rdrCtx.doClip == 1) && (caps != Stroker.CAP_BUTT)) {
+ if (DO_TRACE) {
+ pc2d = transformerPC2D.traceClosedPathDetector(pc2d);
+ }
+
+ // If no dash and clip is enabled:
+ // detect closedPaths (polygons) for caps
+ pc2d = transformerPC2D.detectClosedPath(pc2d);
}
pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat);
+ if (DO_TRACE) {
+ // trace Input:
+ pc2d = transformerPC2D.traceInput(pc2d);
+ }
+
final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,
src.getPathIterator(at));
@@ -802,6 +827,18 @@ public AATileGenerator getAATileGenerator(Shape s,
r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
clip.getWidth(), clip.getHeight(),
PathIterator.WIND_NON_ZERO);
+
+ if (DO_CLIP) {
+ // Define the initial clip bounds:
+ final float[] clipRect = rdrCtx.clipRect;
+ clipRect[0] = clip.getLoY();
+ clipRect[1] = clip.getLoY() + clip.getHeight();
+ clipRect[2] = clip.getLoX();
+ clipRect[3] = clip.getLoX() + clip.getWidth();
+
+ // Enable clipping:
+ rdrCtx.doClip = 1;
+ }
strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r);
}
@@ -860,8 +897,8 @@ public final AATileGenerator getAATileGenerator(double x, double y,
final RendererContext rdrCtx = getRendererContext();
try {
r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
- clip.getWidth(), clip.getHeight(),
- Renderer.WIND_EVEN_ODD);
+ clip.getWidth(), clip.getHeight(),
+ Renderer.WIND_EVEN_ODD);
r.moveTo((float) x, (float) y);
r.lineTo((float) (x+dx1), (float) (y+dy1));
@@ -1045,6 +1082,8 @@ private static void logSettings(final String reClass) {
// optimisation parameters
logInfo("sun.java2d.renderer.useSimplifier = "
+ MarlinConst.USE_SIMPLIFIER);
+ logInfo("sun.java2d.renderer.clip = "
+ + MarlinProperties.isDoClip());
logInfo("sun.java2d.renderer.clip.curves = "
+ MarlinProperties.isDoClipCurves());
diff --git a/src/main/java/sun/java2d/marlin/Renderer.java b/src/main/java/sun/java2d/marlin/Renderer.java
index 1ca76b2..fdd2ced 100644
--- a/src/main/java/sun/java2d/marlin/Renderer.java
+++ b/src/main/java/sun/java2d/marlin/Renderer.java
@@ -49,6 +49,10 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
static final float SUBPIXEL_SCALE_Y = (float) SUBPIXEL_POSITIONS_Y;
static final int SUBPIXEL_MASK_X = SUBPIXEL_POSITIONS_X - 1;
static final int SUBPIXEL_MASK_Y = SUBPIXEL_POSITIONS_Y - 1;
+
+ static final float RDR_OFFSET_X = 0.501f / SUBPIXEL_POSITIONS_X;
+ static final float RDR_OFFSET_Y = 0.501f / SUBPIXEL_POSITIONS_Y;
+
// number of subpixels corresponding to a tile line
private static final int SUBPIXEL_TILE
@@ -231,6 +235,8 @@ private void quadBreakIntoLinesAndAdd(float x0, float y0,
}
}
+ private final static boolean USE_NAIVE_SUM = false;
+
// x0, y0 and x3,y3 are the endpoints of the curve. We could compute these
// using c.xat(0),c.yat(0) and c.xat(1),c.yat(1), but this might introduce
// numerical errors, and our callers already have the exact values.
@@ -262,6 +268,9 @@ private void curveBreakIntoLinesAndAdd(float x0, float y0,
final float _DEC_BND = CUB_DEC_BND;
final float _INC_BND = CUB_INC_BND;
+ float z, t;
+ float ex = 0.0f, ey = 0.0f;
+
while (count > 0) {
// divide step by half:
while (Math.abs(ddx) + Math.abs(ddy) >= _DEC_BND) {
@@ -296,12 +305,31 @@ private void curveBreakIntoLinesAndAdd(float x0, float y0,
}
}
if (--count > 0) {
+if (USE_NAIVE_SUM) {
x1 += dx;
dx += ddx;
ddx += dddx;
y1 += dy;
dy += ddy;
ddy += dddy;
+} else {
+ // kahan sum:
+ z = dx - ex;
+ t = x1 + z;
+ ex = (t - x1) - z;
+ x1 = t;
+ // error are small enough:
+ dx += ddx;
+ ddx += dddx;
+ // kahan sum:
+ z = dy - ey;
+ t = y1 + z;
+ ey = (t - y1) - z;
+ y1 = t;
+ // error are small enough:
+ dy += ddy;
+ ddy += dddy;
+}
} else {
x1 = x3;
y1 = y3;
@@ -313,6 +341,10 @@ private void curveBreakIntoLinesAndAdd(float x0, float y0,
x0 = x1;
y0 = y1;
}
+/*
+ System.out.println("Iterations : "+n);
+ System.out.println("Errors x/y : "+ex+" , "+ey);
+*/
if (DO_STATS) {
rdrCtx.stats.stat_rdr_curveBreak.add(nL);
}
@@ -862,9 +894,12 @@ private void min_max4(float v1, float v2, float v3, float v4,
@Override
public void closePath() {
- addLine(x0, y0, sx0, sy0);
- x0 = sx0;
- y0 = sy0;
+ if (x0 != sx0 || y0 != sy0) {
+// System.out.println("Renderer.closePath: from ("+x0+","+y0+") to ("+sx0+","+sy0+")");
+ addLine(x0, y0, sx0, sy0);
+ x0 = sx0;
+ y0 = sy0;
+ }
}
@Override
diff --git a/src/main/java/sun/java2d/marlin/RendererContext.java b/src/main/java/sun/java2d/marlin/RendererContext.java
index 3b2546a..b4fd68e 100644
--- a/src/main/java/sun/java2d/marlin/RendererContext.java
+++ b/src/main/java/sun/java2d/marlin/RendererContext.java
@@ -25,6 +25,7 @@
package sun.java2d.marlin;
+import java.awt.Rectangle;
import java.awt.geom.Path2D;
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicInteger;
@@ -75,6 +76,12 @@ static RendererContext createContext() {
final MarlinCache cache;
// flag indicating the shape is stroked (1) or filled (0)
int stroking = 0;
+ // flag indicating to clip the shape
+ int doClip = 0;
+ // clip rectangle (ymin, ymax, xmin, xmax):
+ final float[] clipRect = new float[4];
+ // flag indicating if the path is closed or not (in advance) to handle properly caps
+ int closedPath = 0;
// Array caches:
/* clean int[] cache (zero-filled) = 5 refs */
@@ -116,7 +123,7 @@ static RendererContext createContext() {
nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(float6);
// MarlinRenderingEngine.TransformingPathConsumer2D
- transformerPC2D = new TransformingPathConsumer2D();
+ transformerPC2D = new TransformingPathConsumer2D(this);
// Renderer:
cache = new MarlinCache(this);
@@ -138,7 +145,10 @@ void dispose() {
}
stats.totalOffHeap = 0L;
}
- stroking = 0;
+ stroking = 0;
+ doClip = 0;
+ closedPath = 0;
+
// if context is maked as DIRTY:
if (dirty) {
// may happen if an exception if thrown in the pipeline processing:
diff --git a/src/main/java/sun/java2d/marlin/Stroker.java b/src/main/java/sun/java2d/marlin/Stroker.java
index c98b3f0..6dc4da1 100644
--- a/src/main/java/sun/java2d/marlin/Stroker.java
+++ b/src/main/java/sun/java2d/marlin/Stroker.java
@@ -28,6 +28,7 @@
import java.util.Arrays;
import sun.awt.geom.PathConsumer2D;
+import sun.java2d.marlin.Helpers.PolyStack;
// TODO: some of the arithmetic here is too verbose and prone to hard to
// debug typos. We should consider making a small Point/Vector class that
@@ -120,6 +121,20 @@ final class Stroker implements PathConsumer2D, MarlinConst {
// dirty curve
final Curve curve;
+ // Bounds of the drawing region, at pixel precision.
+ private float[] clipRect;
+
+ // the outcode of the current point
+ private int cOutCode = 0;
+
+ // the outcode of the starting point
+ private int sOutCode = 0;
+
+ // flag indicating if the path is opened (clipped)
+ private boolean opened = false;
+ // flag indicating if the starting point's cap is done
+ private boolean capStart = false;
+
/**
* Constructs a Stroker
.
* @param rdrCtx per-thread renderer context
@@ -143,13 +158,15 @@ final class Stroker implements PathConsumer2D, MarlinConst {
* JOIN_MITER
, JOIN_ROUND
or
* JOIN_BEVEL
.
* @param miterLimit the desired miter limit
+ * @param scale scaling factor applied to clip boundaries
* @return this instance
*/
- Stroker init(PathConsumer2D pc2d,
- float lineWidth,
- int capStyle,
- int joinStyle,
- float miterLimit)
+ Stroker init(final PathConsumer2D pc2d,
+ final float lineWidth,
+ final int capStyle,
+ final int joinStyle,
+ final float miterLimit,
+ final float scale)
{
this.out = pc2d;
@@ -158,13 +175,50 @@ Stroker init(PathConsumer2D pc2d,
this.capStyle = capStyle;
this.joinStyle = joinStyle;
- float limit = miterLimit * lineWidth2;
+ final float limit = miterLimit * lineWidth2;
this.miterLimitSq = limit * limit;
this.prev = CLOSE;
rdrCtx.stroking = 1;
+ if (rdrCtx.doClip == 1) {
+ // Adjust the clipping rectangle with the stroker margin (miter limit, width)
+// System.out.println("Stroker: adjust clip");
+
+ // round joins / caps:
+ final float widthLimit =
+ ((joinStyle == JOIN_ROUND) || (capStyle == CAP_ROUND)) ? C * lineWidth // why 0.55 ?
+ : lineWidth2;
+
+ float boundsMargin;
+ if (joinStyle == JOIN_MITER) {
+ boundsMargin = Math.max(widthLimit, limit);
+ } else {
+ boundsMargin = widthLimit;
+ }
+ float rdrOffX = 0.0f, rdrOffY = 0.0f;
+
+ if (scale != 1.0f) {
+ boundsMargin *= scale;
+ rdrOffX = scale * Renderer.RDR_OFFSET_X;
+ rdrOffY = scale * Renderer.RDR_OFFSET_Y;
+ }
+
+ // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
+ // adjust clip rectangle (ymin, ymax, xmin, xmax):
+ final float[] _clipRect = rdrCtx.clipRect;
+ // TODO: offset X/Y must be scaled also !
+ _clipRect[0] -= boundsMargin - rdrOffY;
+ _clipRect[1] += boundsMargin + rdrOffY;
+ _clipRect[2] -= boundsMargin - rdrOffX;
+ _clipRect[3] += boundsMargin + rdrOffX;
+// System.out.println("clip: "+Arrays.toString(_clipRect));
+
+ this.clipRect = _clipRect;
+ } else {
+ this.clipRect = null;
+ }
return this; // fluent API
}
@@ -174,6 +228,9 @@ Stroker init(PathConsumer2D pc2d,
*/
void dispose() {
reverse.dispose();
+
+ capStart = false;
+ opened = false;
if (DO_CLEAN_DIRTY) {
// Force zero-fill dirty arrays:
@@ -446,18 +503,62 @@ private void drawMiter(final float pdx, final float pdy,
@Override
public void moveTo(float x0, float y0) {
- if (prev == DRAWING_OP_TO) {
- finish();
+ moveTo(x0, y0, cOutCode);
+ // update starting point:
+ this.sx0 = x0;
+ this.sy0 = y0;
+ this.sdx = 1.0f;
+ this.sdy = 0.0f;
+ this.opened = false;
+ this.capStart = false;
+
+ if (clipRect != null) {
+ final int outcode = Helpers.outcode(x0, y0, clipRect);
+ this.cOutCode = outcode;
+ this.sOutCode = outcode;
+ }
+ }
+
+ private void moveTo(float x0, float y0,
+ final int outcode)
+ {
+ if (prev == MOVE_TO) {
+ this.cx0 = x0;
+ this.cy0 = y0;
+ } else {
+ if (prev == DRAWING_OP_TO) {
+ finish(outcode);
+ }
+ this.prev = MOVE_TO;
+ this.cx0 = x0;
+ this.cy0 = y0;
+ this.cdx = 1.0f;
+ this.cdy = 0.0f;
}
- this.sx0 = this.cx0 = x0;
- this.sy0 = this.cy0 = y0;
- this.cdx = this.sdx = 1.0f;
- this.cdy = this.sdy = 0.0f;
- this.prev = MOVE_TO;
}
@Override
public void lineTo(float x1, float y1) {
+ lineTo(x1, y1, false);
+ }
+
+ private void lineTo(float x1, float y1, boolean force) {
+ final int outcode0 = this.cOutCode;
+ if (!force && clipRect != null) {
+ final int outcode1 = Helpers.outcode(x1, y1, clipRect);
+ this.cOutCode = outcode1;
+
+// System.out.println("outcodes: "+outcode0 + " " + outcode1);
+
+ // basic rejection criteria
+ if ((outcode0 & outcode1) != 0) {
+// System.out.println("lineTo: Ignore: "+x1 + " "+y1);
+ moveTo(x1, y1, outcode0);
+ opened = true;
+ return;
+ }
+ }
+
float dx = x1 - cx0;
float dy = y1 - cy0;
if (dx == 0.0f && dy == 0.0f) {
@@ -467,7 +568,7 @@ public void lineTo(float x1, float y1) {
final float mx = offset0[0];
final float my = offset0[1];
- drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my);
+ drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
emitLineTo(cx0 + mx, cy0 + my);
emitLineTo( x1 + mx, y1 + my);
@@ -475,43 +576,67 @@ public void lineTo(float x1, float y1) {
emitLineToRev(cx0 - mx, cy0 - my);
emitLineToRev( x1 - mx, y1 - my);
- this.cmx = mx;
- this.cmy = my;
- this.cdx = dx;
- this.cdy = dy;
+ this.prev = DRAWING_OP_TO;
this.cx0 = x1;
this.cy0 = y1;
- this.prev = DRAWING_OP_TO;
+ this.cdx = dx;
+ this.cdy = dy;
+ this.cmx = mx;
+ this.cmy = my;
}
@Override
public void closePath() {
- if (prev != DRAWING_OP_TO) {
+ // distinguish empty path at all vs opened path ?
+ if (prev != DRAWING_OP_TO && !opened) {
if (prev == CLOSE) {
return;
}
emitMoveTo(cx0, cy0 - lineWidth2);
- this.cmx = this.smx = 0.0f;
- this.cmy = this.smy = -lineWidth2;
- this.cdx = this.sdx = 1.0f;
- this.cdy = this.sdy = 0.0f;
- finish();
+
+ this.sdx = 1.0f;
+ this.sdy = 0.0f;
+ this.cdx = 1.0f;
+ this.cdy = 0.0f;
+
+ this.smx = 0.0f;
+ this.smy = -lineWidth2;
+ this.cmx = 0.0f;
+ this.cmy = -lineWidth2;
+
+ finish(cOutCode);
return;
}
+
+// System.out.println("closePath: "+sx0 + " " + sy0 + " from "+cx0 + " "+cy0);
if (cx0 != sx0 || cy0 != sy0) {
- lineTo(sx0, sy0);
+ lineTo(sx0, sy0, true);
}
- drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
+ drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, cOutCode);
emitLineTo(sx0 + smx, sy0 + smy);
- emitMoveTo(sx0 - smx, sy0 - smy);
+ if (opened) {
+ emitLineTo(sx0 - smx, sy0 - smy);
+ } else {
+ emitMoveTo(sx0 - smx, sy0 - smy);
+ }
+
+// System.out.println("emitReverse");
+
+ // Ignore caps like finish(false)
emitReverse();
this.prev = CLOSE;
- emitClose();
+
+ if (opened) {
+ // do not emit close
+ opened = false;
+ } else {
+ emitClose();
+ }
}
private void emitReverse() {
@@ -521,7 +646,7 @@ private void emitReverse() {
@Override
public void pathDone() {
if (prev == DRAWING_OP_TO) {
- finish();
+ finish(cOutCode);
}
out.pathDone();
@@ -534,26 +659,45 @@ public void pathDone() {
dispose();
}
- private void finish() {
- if (capStyle == CAP_ROUND) {
- drawRoundCap(cx0, cy0, cmx, cmy);
- } else if (capStyle == CAP_SQUARE) {
- emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
- emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
- }
-
- emitReverse();
-
- if (capStyle == CAP_ROUND) {
- drawRoundCap(sx0, sy0, -smx, -smy);
- } else if (capStyle == CAP_SQUARE) {
- emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
- emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
+ private void finish(final int outcode) {
+// System.out.println("Stroker: closedPath : "+rdrCtx.closedPath);
+
+ // Problem: impossible to guess if the path will be closed in advance
+ // i.e. if caps must be drawn or not ?
+ // Solution: use the ClosedPathDetector before Stroker to determine
+ // if the path is a closed path or not
+ if (rdrCtx.closedPath == 0) {
+ if (outcode == 0) {
+ // current point = end's cap:
+ if (capStyle == CAP_ROUND) {
+ drawRoundCap(cx0, cy0, cmx, cmy);
+ } else if (capStyle == CAP_SQUARE) {
+ emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
+ emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
+ }
+ }
+ emitReverse();
+
+ if (!capStart) {
+ capStart = true;
+
+ if (sOutCode == 0) {
+ // starting point = initial cap:
+ if (capStyle == CAP_ROUND) {
+ drawRoundCap(sx0, sy0, -smx, -smy);
+ } else if (capStyle == CAP_SQUARE) {
+ emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
+ emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
+ }
+ }
+ }
+ } else {
+ emitReverse();
}
-
+// System.out.println("finish: "+sx0 + " " + sy0 + " from "+cx0 + " "+cy0);
emitClose();
}
-
+
private void emitMoveTo(final float x0, final float y0) {
out.moveTo(x0, y0);
}
@@ -622,23 +766,28 @@ private void drawJoin(float pdx, float pdy,
float x0, float y0,
float dx, float dy,
float omx, float omy,
- float mx, float my)
+ float mx, float my,
+ final int outcode)
{
if (prev != DRAWING_OP_TO) {
emitMoveTo(x0 + mx, y0 + my);
- this.sdx = dx;
- this.sdy = dy;
- this.smx = mx;
- this.smy = my;
+ if (!opened) {
+ this.sdx = dx;
+ this.sdy = dy;
+ this.smx = mx;
+ this.smy = my;
+ }
} else {
- boolean cw = isCW(pdx, pdy, dx, dy);
- if (joinStyle == JOIN_MITER) {
- drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
- } else if (joinStyle == JOIN_ROUND) {
- drawRoundJoin(x0, y0,
- omx, omy,
- mx, my, cw,
- ROUND_JOIN_THRESHOLD);
+ final boolean cw = isCW(pdx, pdy, dx, dy);
+ if (outcode == 0) {
+ if (joinStyle == JOIN_MITER) {
+ drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
+ } else if (joinStyle == JOIN_ROUND) {
+ drawRoundJoin(x0, y0,
+ omx, omy,
+ mx, my, cw,
+ ROUND_JOIN_THRESHOLD);
+ }
}
emitLineTo(x0, y0, !cw);
}
@@ -943,10 +1092,30 @@ private static int findSubdivPoints(final Curve c, float[] pts, float[] ts,
return ret;
}
- @Override public void curveTo(float x1, float y1,
- float x2, float y2,
- float x3, float y3)
+ @Override
+ public void curveTo(float x1, float y1,
+ float x2, float y2,
+ float x3, float y3)
{
+ final int outcode0 = this.cOutCode;
+ if (clipRect != null) {
+ final int outcode3 = Helpers.outcode(x3, y3, clipRect);
+ this.cOutCode = outcode3;
+
+ if (outcode3 != 0) {
+ final int outcode1 = Helpers.outcode(x1, y1, clipRect);
+ final int outcode2 = Helpers.outcode(x2, y2, clipRect);
+
+ // basic rejection criteria
+ if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
+ // System.out.println("curveTo: Ignore: "+x3 + " "+y3);
+ moveTo(x3, y3, outcode0);
+ opened = true;
+ return;
+ }
+ }
+ }
+
final float[] mid = middle;
mid[0] = cx0; mid[1] = cy0;
@@ -955,7 +1124,7 @@ private static int findSubdivPoints(final Curve c, float[] pts, float[] ts,
mid[6] = x3; mid[7] = y3;
// need these so we can update the state at the end of this method
- final float xf = mid[6], yf = mid[7];
+ final float xf = x3, yf = y3;
float dxs = mid[2] - mid[0];
float dys = mid[3] - mid[1];
float dxf = mid[6] - mid[4];
@@ -981,6 +1150,10 @@ private static int findSubdivPoints(final Curve c, float[] pts, float[] ts,
}
if (dxs == 0.0f && dys == 0.0f) {
// this happens if the "curve" is just a point
+ // fix outcode0 for lineTo() call:
+ if (clipRect != null) {
+ this.cOutCode = outcode0;
+ }
lineTo(mid[0], mid[1]);
return;
}
@@ -999,7 +1172,7 @@ private static int findSubdivPoints(final Curve c, float[] pts, float[] ts,
}
computeOffset(dxs, dys, lineWidth2, offset0);
- drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
+ drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
@@ -1034,16 +1207,37 @@ private static int findSubdivPoints(final Curve c, float[] pts, float[] ts,
emitLineToRev(r[kind - 2], r[kind - 1]);
}
- this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
- this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
- this.cdx = dxf;
- this.cdy = dyf;
+ this.prev = DRAWING_OP_TO;
this.cx0 = xf;
this.cy0 = yf;
- this.prev = DRAWING_OP_TO;
+ this.cdx = dxf;
+ this.cdy = dyf;
+ this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
+ this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
}
- @Override public void quadTo(float x1, float y1, float x2, float y2) {
+ @Override
+ public void quadTo(float x1, float y1,
+ float x2, float y2)
+ {
+ final int outcode0 = this.cOutCode;
+ if (clipRect != null) {
+ final int outcode2 = Helpers.outcode(x2, y2, clipRect);
+ this.cOutCode = outcode2;
+
+ if (outcode2 != 0) {
+ final int outcode1 = Helpers.outcode(x1, y1, clipRect);
+
+ // basic rejection criteria
+ if ((outcode0 & outcode1 & outcode2) != 0) {
+ // System.out.println("quadTo: Ignore: "+x2 + " "+y2);
+ moveTo(x2, y2, outcode0);
+ opened = true;
+ return;
+ }
+ }
+ }
+
final float[] mid = middle;
mid[0] = cx0; mid[1] = cy0;
@@ -1051,7 +1245,7 @@ private static int findSubdivPoints(final Curve c, float[] pts, float[] ts,
mid[4] = x2; mid[5] = y2;
// need these so we can update the state at the end of this method
- final float xf = mid[4], yf = mid[5];
+ final float xf = x2, yf = y2;
float dxs = mid[2] - mid[0];
float dys = mid[3] - mid[1];
float dxf = mid[4] - mid[2];
@@ -1062,6 +1256,10 @@ private static int findSubdivPoints(final Curve c, float[] pts, float[] ts,
}
if (dxs == 0.0f && dys == 0.0f) {
// this happens if the "curve" is just a point
+ // fix outcode0 for lineTo() call:
+ if (clipRect != null) {
+ this.cOutCode = outcode0;
+ }
lineTo(mid[0], mid[1]);
return;
}
@@ -1079,7 +1277,7 @@ private static int findSubdivPoints(final Curve c, float[] pts, float[] ts,
}
computeOffset(dxs, dys, lineWidth2, offset0);
- drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
+ drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
@@ -1114,214 +1312,16 @@ private static int findSubdivPoints(final Curve c, float[] pts, float[] ts,
emitLineToRev(r[kind - 2], r[kind - 1]);
}
- this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
- this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
- this.cdx = dxf;
- this.cdy = dyf;
+ this.prev = DRAWING_OP_TO;
this.cx0 = xf;
this.cy0 = yf;
- this.prev = DRAWING_OP_TO;
+ this.cdx = dxf;
+ this.cdy = dyf;
+ this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
+ this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
}
@Override public long getNativeConsumer() {
throw new InternalError("Stroker doesn't use a native consumer");
}
-
- // a stack of polynomial curves where each curve shares endpoints with
- // adjacent ones.
- static final class PolyStack {
- private static final byte TYPE_LINETO = (byte) 0;
- private static final byte TYPE_QUADTO = (byte) 1;
- private static final byte TYPE_CUBICTO = (byte) 2;
-
- // curves capacity = edges count (8192) = edges x 2 (coords)
- private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1;
-
- // types capacity = edges count (4096)
- private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT;
-
- float[] curves;
- int end;
- byte[] curveTypes;
- int numCurves;
-
- // per-thread renderer context
- final RendererContext rdrCtx;
-
- // curves ref (dirty)
- final FloatArrayCache.Reference curves_ref;
- // curveTypes ref (dirty)
- final ByteArrayCache.Reference curveTypes_ref;
-
- // used marks (stats only)
- int curveTypesUseMark;
- int curvesUseMark;
-
- /**
- * Constructor
- * @param rdrCtx per-thread renderer context
- */
- PolyStack(final RendererContext rdrCtx) {
- this.rdrCtx = rdrCtx;
-
- curves_ref = rdrCtx.newDirtyFloatArrayRef(INITIAL_CURVES_COUNT); // 32K
- curves = curves_ref.initial;
-
- curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K
- curveTypes = curveTypes_ref.initial;
- numCurves = 0;
- end = 0;
-
- if (DO_STATS) {
- curveTypesUseMark = 0;
- curvesUseMark = 0;
- }
- }
-
- /**
- * Disposes this PolyStack:
- * clean up before reusing this instance
- */
- void dispose() {
- end = 0;
- numCurves = 0;
-
- if (DO_STATS) {
- rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark);
- rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark);
- rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark);
-
- // reset marks
- curveTypesUseMark = 0;
- curvesUseMark = 0;
- }
-
- // Return arrays:
- // curves and curveTypes are kept dirty
- curves = curves_ref.putArray(curves);
- curveTypes = curveTypes_ref.putArray(curveTypes);
- }
-
- private void ensureSpace(final int n) {
- // use substraction to avoid integer overflow:
- if (curves.length - end < n) {
- if (DO_STATS) {
- rdrCtx.stats.stat_array_stroker_polystack_curves
- .add(end + n);
- }
- curves = curves_ref.widenArray(curves, end, end + n);
- }
- if (curveTypes.length <= numCurves) {
- if (DO_STATS) {
- rdrCtx.stats.stat_array_stroker_polystack_curveTypes
- .add(numCurves + 1);
- }
- curveTypes = curveTypes_ref.widenArray(curveTypes,
- numCurves,
- numCurves + 1);
- }
- }
-
- void pushCubic(float x0, float y0,
- float x1, float y1,
- float x2, float y2)
- {
- ensureSpace(6);
- curveTypes[numCurves++] = TYPE_CUBICTO;
- // we reverse the coordinate order to make popping easier
- final float[] _curves = curves;
- int e = end;
- _curves[e++] = x2; _curves[e++] = y2;
- _curves[e++] = x1; _curves[e++] = y1;
- _curves[e++] = x0; _curves[e++] = y0;
- end = e;
- }
-
- void pushQuad(float x0, float y0,
- float x1, float y1)
- {
- ensureSpace(4);
- curveTypes[numCurves++] = TYPE_QUADTO;
- final float[] _curves = curves;
- int e = end;
- _curves[e++] = x1; _curves[e++] = y1;
- _curves[e++] = x0; _curves[e++] = y0;
- end = e;
- }
-
- void pushLine(float x, float y) {
- ensureSpace(2);
- curveTypes[numCurves++] = TYPE_LINETO;
- curves[end++] = x; curves[end++] = y;
- }
-
- void popAll(PathConsumer2D io) {
- if (DO_STATS) {
- // update used marks:
- if (numCurves > curveTypesUseMark) {
- curveTypesUseMark = numCurves;
- }
- if (end > curvesUseMark) {
- curvesUseMark = end;
- }
- }
- final byte[] _curveTypes = curveTypes;
- final float[] _curves = curves;
- int nc = numCurves;
- int e = end;
-
- while (nc != 0) {
- switch(_curveTypes[--nc]) {
- case TYPE_LINETO:
- e -= 2;
- io.lineTo(_curves[e], _curves[e+1]);
- continue;
- case TYPE_QUADTO:
- e -= 4;
- io.quadTo(_curves[e+0], _curves[e+1],
- _curves[e+2], _curves[e+3]);
- continue;
- case TYPE_CUBICTO:
- e -= 6;
- io.curveTo(_curves[e+0], _curves[e+1],
- _curves[e+2], _curves[e+3],
- _curves[e+4], _curves[e+5]);
- continue;
- default:
- }
- }
- numCurves = 0;
- end = 0;
- }
-
- @Override
- public String toString() {
- String ret = "";
- int nc = numCurves;
- int last = end;
- int len;
- while (nc != 0) {
- switch(curveTypes[--nc]) {
- case TYPE_LINETO:
- len = 2;
- ret += "line: ";
- break;
- case TYPE_QUADTO:
- len = 4;
- ret += "quad: ";
- break;
- case TYPE_CUBICTO:
- len = 6;
- ret += "cubic: ";
- break;
- default:
- len = 0;
- }
- last -= len;
- ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len))
- + "\n";
- }
- return ret;
- }
- }
}
diff --git a/src/main/java/sun/java2d/marlin/TransformingPathConsumer2D.java b/src/main/java/sun/java2d/marlin/TransformingPathConsumer2D.java
index 65a502b..8b2c814 100644
--- a/src/main/java/sun/java2d/marlin/TransformingPathConsumer2D.java
+++ b/src/main/java/sun/java2d/marlin/TransformingPathConsumer2D.java
@@ -28,24 +28,58 @@
import sun.awt.geom.PathConsumer2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
+import sun.java2d.marlin.Helpers.PolyStack;
final class TransformingPathConsumer2D {
- TransformingPathConsumer2D() {
- // used by RendererContext
- }
+ private final RendererContext rdrCtx;
+
+ // recycled ClosedPathDetector instance from detectClosedPath()
+ private final ClosedPathDetector cpDetector;
// recycled PathConsumer2D instance from wrapPath2d()
- private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();
+ private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();
+
+ // recycled PathConsumer2D instances from deltaTransformConsumer()
+ private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();
+ private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
+
+ // recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
+ private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
+ private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
+
+ // recycled PathTracer instances from tracer...() methods
+ private final PathTracer tracerInput = new PathTracer("[Input]");
+ private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
+ private final PathTracer tracerStroker = new PathTracer("Stroker");
+
+ TransformingPathConsumer2D(final RendererContext rdrCtx) {
+ // used by RendererContext
+ this.rdrCtx = rdrCtx;
+ this.cpDetector = new ClosedPathDetector(rdrCtx);
+ }
PathConsumer2D wrapPath2d(Path2D.Float p2d)
{
return wp_Path2DWrapper.init(p2d);
}
- // recycled PathConsumer2D instances from deltaTransformConsumer()
- private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();
- private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
+ PathConsumer2D traceInput(PathConsumer2D out) {
+ return tracerInput.init(out);
+ }
+
+ PathConsumer2D traceClosedPathDetector(PathConsumer2D out) {
+ return tracerCPDetector.init(out);
+ }
+
+ PathConsumer2D traceStroker(PathConsumer2D out) {
+ return tracerStroker.init(out);
+ }
+
+ PathConsumer2D detectClosedPath(PathConsumer2D out)
+ {
+ return cpDetector.init(out);
+ }
PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
AffineTransform at)
@@ -62,17 +96,82 @@ PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
if (mxx == 1.0f && myy == 1.0f) {
return out;
} else {
+ // Scale only
+ if (rdrCtx.doClip == 1) {
+ // adjust clip rectangle (ymin, ymax, xmin, xmax):
+ final float[] _clipRect = rdrCtx.clipRect;
+ _clipRect[0] += Renderer.RDR_OFFSET_Y;
+ _clipRect[1] += Renderer.RDR_OFFSET_Y;
+ _clipRect[2] += Renderer.RDR_OFFSET_X;
+ _clipRect[3] += Renderer.RDR_OFFSET_X;
+
+// System.out.println("deltaTransformConsumer: scale clip ie transformed points are scaled back to normal device");
+ // Adjust the clipping rectangle (inverseDeltaTransform):
+ _clipRect[0] /= myy;
+ _clipRect[1] /= myy;
+ _clipRect[2] /= mxx;
+ _clipRect[3] /= mxx;
+ }
return dt_DeltaScaleFilter.init(out, mxx, myy);
}
} else {
+ if (rdrCtx.doClip == 1) {
+ // adjust clip rectangle (ymin, ymax, xmin, xmax):
+ final float[] _clipRect = rdrCtx.clipRect;
+ _clipRect[0] += Renderer.RDR_OFFSET_Y;
+ _clipRect[1] += Renderer.RDR_OFFSET_Y;
+ _clipRect[2] += Renderer.RDR_OFFSET_X;
+ _clipRect[3] += Renderer.RDR_OFFSET_X;
+
+// System.out.println("deltaTransformConsumer: scale clip ie transformed points are scaled back to normal device");
+ // Adjust the clipping rectangle (inverseDeltaTransform):
+ final float det = mxx * myy - mxy * myx;
+ final float imxx = myy / det;
+ final float imxy = -mxy / det;
+ final float imyx = -myx / det;
+ final float imyy = mxx / det;
+
+ float xmin, xmax, ymin, ymax;
+ float x, y;
+ // xmin, ymin:
+ x = _clipRect[2] * imxx + _clipRect[0] * imxy;
+ y = _clipRect[2] * imyx + _clipRect[0] * imyy;
+
+ xmin = xmax = x;
+ ymin = ymax = y;
+
+ // xmax, ymin:
+ x = _clipRect[3] * imxx + _clipRect[0] * imxy;
+ y = _clipRect[3] * imyx + _clipRect[0] * imyy;
+
+ if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
+ if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
+
+ // xmin, ymax:
+ x = _clipRect[2] * imxx + _clipRect[1] * imxy;
+ y = _clipRect[2] * imyx + _clipRect[1] * imyy;
+
+ if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
+ if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
+
+ // xmax, ymax:
+ x = _clipRect[3] * imxx + _clipRect[1] * imxy;
+ y = _clipRect[3] * imyx + _clipRect[1] * imyy;
+
+ if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
+ if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
+
+ _clipRect[0] = ymin;
+ _clipRect[1] = ymax;
+ _clipRect[2] = xmin;
+ _clipRect[3] = xmax;
+
+// System.out.println("clip: "+java.util.Arrays.toString(_clipRect));
+ }
return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
}
}
- // recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
- private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
- private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
-
PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out,
AffineTransform at)
{
@@ -91,7 +190,7 @@ PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out,
return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy);
}
} else {
- float det = mxx * myy - mxy * myx;
+ final float det = mxx * myy - mxy * myx;
return iv_DeltaTransformFilter.init(out,
myy / det,
-mxy / det,
@@ -100,7 +199,6 @@ PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out,
}
}
-
static final class DeltaScaleFilter implements PathConsumer2D {
private PathConsumer2D out;
private float sx, sy;
@@ -275,4 +373,148 @@ public long getNativeConsumer() {
throw new InternalError("Not using a native peer");
}
}
+
+ static final class ClosedPathDetector implements PathConsumer2D {
+
+ private final RendererContext rdrCtx;
+ private final PolyStack stack;
+
+ private PathConsumer2D out;
+
+ ClosedPathDetector(final RendererContext rdrCtx) {
+ this.rdrCtx = rdrCtx;
+ this.stack = new PolyStack(rdrCtx);
+ }
+
+ ClosedPathDetector init(PathConsumer2D out) {
+ this.out = out;
+ return this; // fluent API
+ }
+
+ /**
+ * Disposes this instance:
+ * clean up before reusing this instance
+ */
+ void dispose() {
+ stack.dispose();
+ }
+
+ @Override
+ public void pathDone() {
+ // previous path is not closed:
+ finish(0);
+ out.pathDone();
+
+ // TODO: fix possible leak if exception happened
+ // Dispose this instance:
+ dispose();
+ }
+
+ @Override
+ public void closePath() {
+ // path is closed
+ finish(1);
+ out.closePath();
+ }
+
+ @Override
+ public void moveTo(float x0, float y0) {
+ // previous path is not closed:
+ finish(0);
+ out.moveTo(x0, y0);
+ }
+
+ private void finish(int closed) {
+ rdrCtx.closedPath = closed;
+
+ if (!stack.isEmpty()) {
+ stack.pullAll(out);
+ }
+ }
+
+ @Override
+ public void lineTo(float x1, float y1) {
+ stack.pushLine(x1, y1);
+ }
+
+ @Override
+ public void curveTo(float x3, float y3,
+ float x2, float y2,
+ float x1, float y1)
+ {
+ stack.pushCubic(x1, y1, x2, y2, x3, y3);
+ }
+
+ @Override
+ public void quadTo(float x2, float y2, float x1, float y1) {
+ stack.pushQuad(x1, y1, x2, y2);
+ }
+
+ @Override
+ public long getNativeConsumer() {
+ throw new InternalError("Not using a native peer");
+ }
+ }
+
+ static final class PathTracer implements PathConsumer2D {
+ private final String prefix;
+ private PathConsumer2D out;
+
+ PathTracer(String name) {
+ this.prefix = name + ": ";
+ }
+
+ PathTracer init(PathConsumer2D out) {
+ this.out = out;
+ return this; // fluent API
+ }
+
+ @Override
+ public void moveTo(float x0, float y0) {
+ log("moveTo (" + x0 + ", " + y0 + ')');
+ out.moveTo(x0, y0);
+ }
+
+ @Override
+ public void lineTo(float x1, float y1) {
+ log("lineTo (" + x1 + ", " + y1 + ')');
+ out.lineTo(x1, y1);
+ }
+
+ @Override
+ public void curveTo(float x1, float y1,
+ float x2, float y2,
+ float x3, float y3)
+ {
+ log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ')');
+ out.curveTo(x1, y1, x2, y2, x3, y3);
+ }
+
+ @Override
+ public void quadTo(float x1, float y1, float x2, float y2) {
+ log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')');
+ out.quadTo(x1, y1, x2, y2);
+ }
+
+ @Override
+ public void closePath() {
+ log("closePath");
+ out.closePath();
+ }
+
+ @Override
+ public void pathDone() {
+ log("pathDone");
+ out.pathDone();
+ }
+
+ private void log(final String message) {
+ System.out.println(prefix + message);
+ }
+
+ @Override
+ public long getNativeConsumer() {
+ throw new InternalError("Not using a native peer");
+ }
+ }
}
diff --git a/src/main/java/sun/java2d/pipe/AlphaPaintPipe.java b/src/main/java/sun/java2d/pipe/AlphaPaintPipe.java
new file mode 100644
index 0000000..1b26c7c
--- /dev/null
+++ b/src/main/java/sun/java2d/pipe/AlphaPaintPipe.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 1997, 2002, 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 sun.java2d.pipe;
+
+import java.lang.ref.WeakReference;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.PaintContext;
+import java.awt.Transparency;
+import java.awt.image.ColorModel;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.awt.image.BufferedImage;
+import sun.awt.image.BufImgSurfaceData;
+import sun.java2d.SunGraphics2D;
+import sun.java2d.SurfaceData;
+import sun.java2d.loops.Blit;
+import sun.java2d.loops.MaskBlit;
+import sun.java2d.loops.CompositeType;
+import sun.java2d.loops.GraphicsPrimitiveMgr;
+
+/**
+ * This class implements a CompositePipe that renders path alpha tiles
+ * into a destination according to the Composite attribute of a
+ * SunGraphics2D.
+ */
+public class AlphaPaintPipe implements CompositePipe {
+ static WeakReference cachedLastRaster;
+ static WeakReference cachedLastColorModel;
+ static WeakReference cachedLastData;
+
+ static class TileContext {
+ SunGraphics2D sunG2D;
+ PaintContext paintCtxt;
+ ColorModel paintModel;
+ WeakReference lastRaster;
+ WeakReference lastData;
+ MaskBlit lastMask;
+ Blit lastBlit;
+ SurfaceData dstData;
+
+ public TileContext(SunGraphics2D sg, PaintContext pc) {
+ sunG2D = sg;
+ paintCtxt = pc;
+ paintModel = pc.getColorModel();
+ dstData = sg.getSurfaceData();
+ synchronized (AlphaPaintPipe.class) {
+ if (cachedLastColorModel != null &&
+ cachedLastColorModel.get() == paintModel)
+ {
+ this.lastRaster = cachedLastRaster;
+ this.lastData = cachedLastData;
+ }
+ }
+ }
+ }
+
+ public Object startSequence(SunGraphics2D sg, Shape s, Rectangle devR,
+ int[] abox) {
+ PaintContext paintContext =
+ sg.paint.createContext(sg.getDeviceColorModel(),
+ devR,
+ s.getBounds2D(),
+ sg.cloneTransform(),
+ sg.getRenderingHints());
+ return new TileContext(sg, paintContext);
+ }
+
+ public boolean needTile(Object context, int x, int y, int w, int h) {
+ return true;
+ }
+
+ private static final int TILE_SIZE = 32;
+
+ public void renderPathTile(Object ctx,
+ byte[] atile, int offset, int tilesize,
+ int x, int y, int w, int h) {
+ TileContext context = (TileContext) ctx;
+ PaintContext paintCtxt = context.paintCtxt;
+ SunGraphics2D sg = context.sunG2D;
+ SurfaceData dstData = context.dstData;
+ SurfaceData srcData = null;
+ Raster lastRas = null;
+ if (context.lastData != null && context.lastRaster != null) {
+ srcData = context.lastData.get();
+ lastRas = context.lastRaster.get();
+ if (srcData == null || lastRas == null) {
+ srcData = null;
+ lastRas = null;
+ }
+ }
+ ColorModel paintModel = context.paintModel;
+
+ for (int rely = 0; rely < h; rely += TILE_SIZE) {
+ int ty = y + rely;
+ int th = Math.min(h-rely, TILE_SIZE);
+ for (int relx = 0; relx < w; relx += TILE_SIZE) {
+ int tx = x + relx;
+ int tw = Math.min(w-relx, TILE_SIZE);
+
+ Raster srcRaster = paintCtxt.getRaster(tx, ty, tw, th);
+ if ((srcRaster.getMinX() != 0) || (srcRaster.getMinY() != 0)) {
+ srcRaster = srcRaster.createTranslatedChild(0, 0);
+ }
+ if (lastRas != srcRaster) {
+ lastRas = srcRaster;
+ context.lastRaster = new WeakReference<>(lastRas);
+ // REMIND: This will fail for a non-Writable raster!
+ BufferedImage bImg =
+ new BufferedImage(paintModel,
+ (WritableRaster) srcRaster,
+ paintModel.isAlphaPremultiplied(),
+ null);
+ srcData = BufImgSurfaceData.createData(bImg);
+ context.lastData = new WeakReference<>(srcData);
+ context.lastMask = null;
+ context.lastBlit = null;
+ }
+
+ if (atile == null) {
+ if (context.lastBlit == null) {
+ CompositeType comptype = sg.imageComp;
+ if (CompositeType.SrcOverNoEa.equals(comptype) &&
+ paintModel.getTransparency() == Transparency.OPAQUE)
+ {
+ comptype = CompositeType.SrcNoEa;
+ }
+ context.lastBlit =
+ Blit.getFromCache(srcData.getSurfaceType(),
+ comptype,
+ dstData.getSurfaceType());
+ }
+ context.lastBlit.Blit(srcData, dstData,
+ sg.composite, null,
+ 0, 0, tx, ty, tw, th);
+ } else {
+ if (context.lastMask == null) {
+ CompositeType comptype = sg.imageComp;
+ if (CompositeType.SrcOverNoEa.equals(comptype) &&
+ paintModel.getTransparency() == Transparency.OPAQUE)
+ {
+ comptype = CompositeType.SrcNoEa;
+ }
+ context.lastMask =
+ MaskBlit.getFromCache(srcData.getSurfaceType(),
+ comptype,
+ dstData.getSurfaceType());
+ }
+
+ int toff = offset + rely * tilesize + relx;
+ context.lastMask.MaskBlit(srcData, dstData,
+ sg.composite, null,
+ 0, 0, tx, ty, tw, th,
+ atile, toff, tilesize);
+ }
+ }
+ }
+ }
+
+ public void skipTile(Object context, int x, int y) {
+ return;
+ }
+
+ public void endSequence(Object ctx) {
+ TileContext context = (TileContext) ctx;
+ if (context.paintCtxt != null) {
+ context.paintCtxt.dispose();
+ }
+ synchronized (AlphaPaintPipe.class) {
+ if (context.lastData != null) {
+ cachedLastRaster = context.lastRaster;
+ if (cachedLastColorModel == null ||
+ cachedLastColorModel.get() != context.paintModel)
+ {
+ // Avoid creating new WeakReference if possible
+ cachedLastColorModel =
+ new WeakReference<>(context.paintModel);
+ }
+ cachedLastData = context.lastData;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/test/ShapeOutlineTest.java b/src/main/java/test/ShapeOutlineTest.java
new file mode 100644
index 0000000..cbe4893
--- /dev/null
+++ b/src/main/java/test/ShapeOutlineTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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 test;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Path2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+
+/**
+ * Simple Line rendering test using GeneralPath to enable Pisces / marlin /
+ * ductus renderers
+ */
+public class ShapeOutlineTest {
+
+ private final static int N = 10;
+
+ private final static boolean DO_CIRCLE = false;
+
+ private final static boolean DO_DRAW = true;
+
+ private final static float CIRCLE_RADIUS = 1843200.0f * 10.0f;
+
+ private final static float RECT_SIZE = 900f * 1024 * 5 * 30;
+
+ private final static double sqrt2 = Math.sqrt(2);
+
+ public static void main(String[] args) {
+
+ final float lineStroke = 2f;
+ final int size = 1000;
+
+ // First display which renderer is tested:
+ // JDK9 only:
+ System.setProperty("sun.java2d.renderer.verbose", "true");
+ System.out.println("Testing renderer: ");
+ // Other JDK:
+ String renderer = "undefined";
+ try {
+ renderer = sun.java2d.pipe.RenderingEngine.getInstance().getClass().getName();
+ System.out.println(renderer);
+ }
+ catch (Throwable th) {
+ // may fail with JDK9 jigsaw (jake)
+ if (false) {
+ System.err.println("Unable to get RenderingEngine.getInstance()");
+ th.printStackTrace();
+ }
+ }
+
+ System.out.println("ShapeOutlineTest: size = " + size);
+
+ final BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
+
+ final Graphics2D g2d = (Graphics2D) image.getGraphics();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
+
+ g2d.setClip(0, 0, size, size);
+ g2d.setStroke(
+ new BasicStroke(lineStroke, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 20f,
+ new float[]{10f, 5f}, 0f
+ )
+ );
+
+ g2d.setBackground(Color.WHITE);
+ g2d.clearRect(0, 0, size, size);
+
+ // RECT:
+ // old: 4009.9072039999996 ms.
+ // fix: 4094.81685
+ // with inlined variables: old = 3770 ms
+ // Ductus 1.8: 35934.7 ms
+ // DMarlinRenderingEngine: 4131.276773 ms.
+
+ // CIRCLE:
+ // old: 2696.341058 ms.
+ // fix: 2442.098762 ms.
+
+ // CPU fixed without clipping: 4357.567511 ms.
+ // Stroker clipping: 700 ms.
+
+ for (int i = 0; i < N; i++) {
+ final long start = System.nanoTime();
+
+ paint(g2d, size);
+
+ final long time = System.nanoTime() - start;
+
+ System.out.println("paint: duration= " + (1e-6 * time) + " ms.");
+ }
+
+ try {
+ final File file = new File("ShapeOutlineTest-"
+ + (DO_CIRCLE ? "circle" : "rect") + ".png");
+
+ System.out.println("Writing file: " + file.getAbsolutePath());
+ ImageIO.write(image, "PNG", file);
+ }
+ catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ finally {
+ g2d.dispose();
+ }
+ }
+
+ private static void paint(final Graphics2D g2d, final float size) {
+ final Shape path = createPath(size);
+
+ g2d.setColor(Color.BLUE);
+ g2d.fill(path);
+
+ if (DO_DRAW) {
+ g2d.setColor(Color.RED);
+ g2d.draw(path);
+ }
+ }
+
+ private static Shape createPath(final float size) {
+ if (DO_CIRCLE) {
+ final float c = (float)(0.5f * size - CIRCLE_RADIUS / sqrt2);
+
+ return new Ellipse2D.Float(
+ c - CIRCLE_RADIUS,
+ c - CIRCLE_RADIUS,
+ 2.0f * CIRCLE_RADIUS,
+ 2.0f * CIRCLE_RADIUS
+ );
+
+ } else {
+ final double half = 0.5f * size;
+
+ System.out.println("RECT_SIZE: " + RECT_SIZE);
+
+ final Path2D p = new Path2D.Float();
+ p.moveTo(half, half);
+ p.lineTo(-RECT_SIZE, -RECT_SIZE);
+ p.lineTo(0.0, -RECT_SIZE * 2.0);
+ p.lineTo(RECT_SIZE, -RECT_SIZE);
+ p.lineTo(half, half);
+ p.closePath();
+ return p;
+ }
+ }
+}
diff --git a/src/main/java/test/StrokeClipTest.java b/src/main/java/test/StrokeClipTest.java
new file mode 100644
index 0000000..ac63b8c
--- /dev/null
+++ b/src/main/java/test/StrokeClipTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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 test;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Path2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+
+/**
+ * Simple Line rendering test using GeneralPath to enable Pisces / marlin /
+ * ductus renderers
+ */
+public class StrokeClipTest {
+
+ private final static int N = 1;
+
+ private final static boolean DO_CIRCLE = false;
+
+ private final static boolean DO_DRAW = true;
+
+ private final static boolean DO_CLOSE_PATH = false;
+
+ private final static float CIRCLE_RADIUS = 100f;
+
+ private final static float RECT_SIZE = 100f;
+
+ private final static double sqrt2 = Math.sqrt(2);
+
+ public static void main(String[] args) {
+
+ final float lineStroke = 4f;
+ final int size = 400;
+
+ // First display which renderer is tested:
+ // JDK9 only:
+ System.setProperty("sun.java2d.renderer.verbose", "true");
+ System.out.println("Testing renderer: ");
+ // Other JDK:
+ String renderer = "undefined";
+ try {
+ renderer = sun.java2d.pipe.RenderingEngine.getInstance().getClass().getName();
+ System.out.println(renderer);
+ }
+ catch (Throwable th) {
+ // may fail with JDK9 jigsaw (jake)
+ if (false) {
+ System.err.println("Unable to get RenderingEngine.getInstance()");
+ th.printStackTrace();
+ }
+ }
+
+ System.out.println("StrokeClipTest: size = " + size);
+
+ final BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
+
+ final Graphics2D g2d = (Graphics2D) image.getGraphics();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
+
+ g2d.setClip(0, 0, size, size);
+ g2d.setStroke(
+ new BasicStroke(lineStroke, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 20f)
+ );
+
+ g2d.setBackground(Color.WHITE);
+ g2d.clearRect(0, 0, size, size);
+
+ for (int i = 0; i < N; i++) {
+ final long start = System.nanoTime();
+
+ paint(g2d, size);
+
+ final long time = System.nanoTime() - start;
+
+ System.out.println("paint: duration= " + (1e-6 * time) + " ms.");
+ }
+
+ try {
+ final File file = new File("StrokeClipTest-"
+ + (DO_CIRCLE ? "circle" : "rect") + ".png");
+
+ System.out.println("Writing file: " + file.getAbsolutePath());
+ ImageIO.write(image, "PNG", file);
+ }
+ catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ finally {
+ g2d.dispose();
+ }
+ }
+
+ private static void paint(final Graphics2D g2d, final float size) {
+ final AffineTransform tx = g2d.getTransform();
+ final Shape path = createPath(size);
+
+ for (int i = 0; i < 600; i += 1500) {
+ g2d.setTransform(AffineTransform.getTranslateInstance(i, 0));
+
+ g2d.setColor(Color.BLUE);
+ g2d.fill(path);
+
+ if (DO_DRAW) {
+ g2d.setColor(Color.RED);
+ g2d.draw(path);
+ }
+ }
+ g2d.setTransform(tx);
+ }
+
+ private static Shape createPath(final float size) {
+ if (DO_CIRCLE) {
+ final float c = (float) (0.5f * size - CIRCLE_RADIUS / sqrt2);
+
+ return new Ellipse2D.Float(
+ -CIRCLE_RADIUS,
+ 100,
+ 2.0f * CIRCLE_RADIUS,
+ 2.0f * CIRCLE_RADIUS
+ );
+
+ } else {
+ final Path2D p = new Path2D.Float();
+ p.moveTo(100, 100);
+ p.lineTo(100.0, 50);
+ p.lineTo(-100, 10);
+ p.lineTo(-100, 100);
+ p.lineTo(300.0, 200);
+ p.lineTo(100, 300);
+ p.lineTo(120, 80);
+
+ if (DO_CLOSE_PATH) {
+ p.closePath();
+ }
+ return p;
+ }
+ }
+}