< prev index next >
src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java
Print this page
@@ -1,7 +1,7 @@
/*
- * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 2018, 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
@@ -26,15 +26,19 @@
package sun.java2d.marlin;
import sun.awt.geom.PathConsumer2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
+import java.util.Arrays;
import sun.java2d.marlin.Helpers.IndexStack;
import sun.java2d.marlin.Helpers.PolyStack;
final class TransformingPathConsumer2D {
+ // higher uncertainty in float variant for huge shapes > 10^7
+ static final float CLIP_RECT_PADDING = 1.0f;
+
private final RendererContext rdrCtx;
// recycled ClosedPathDetector instance from detectClosedPath()
private final ClosedPathDetector cpDetector;
@@ -55,10 +59,11 @@
// recycled PathTracer instances from tracer...() methods
private final PathTracer tracerInput = new PathTracer("[Input]");
private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
private final PathTracer tracerFiller = new PathTracer("Filler");
private final PathTracer tracerStroker = new PathTracer("Stroker");
+ private final PathTracer tracerDasher = new PathTracer("Dasher");
TransformingPathConsumer2D(final RendererContext rdrCtx) {
// used by RendererContext
this.rdrCtx = rdrCtx;
this.cpDetector = new ClosedPathDetector(rdrCtx);
@@ -83,10 +88,14 @@
PathConsumer2D traceStroker(PathConsumer2D out) {
return tracerStroker.init(out);
}
+ PathConsumer2D traceDasher(PathConsumer2D out) {
+ return tracerDasher.init(out);
+ }
+
PathConsumer2D detectClosedPath(PathConsumer2D out) {
return cpDetector.init(out);
}
PathConsumer2D pathClipper(PathConsumer2D out) {
@@ -498,15 +507,23 @@
// the cumulated (and) outcode of the complete path
private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
private boolean outside = false;
- // The current point OUTSIDE
+ // The current point (TODO stupid repeated info)
private float cx0, cy0;
+ // The current point OUTSIDE
+ private float cox0, coy0;
+
+ private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
+ private final CurveClipSplitter curveSplitter;
+
PathClipFilter(final RendererContext rdrCtx) {
this.clipRect = rdrCtx.clipRect;
+ this.curveSplitter = rdrCtx.curveClipSplitter;
+
this.stack = (rdrCtx.stats != null) ?
new IndexStack(rdrCtx,
rdrCtx.stats.stat_pcf_idxstack_indices,
rdrCtx.stats.hist_pcf_idxstack_indices,
rdrCtx.stats.stat_array_pcf_idxstack_indices)
@@ -527,10 +544,15 @@
_clipRect[0] -= margin - rdrOffY;
_clipRect[1] += margin + rdrOffY;
_clipRect[2] -= margin - rdrOffX;
_clipRect[3] += margin + rdrOffX;
+ if (MarlinConst.DO_CLIP_SUBDIVIDER) {
+ // adjust padded clip rectangle:
+ curveSplitter.init();
+ }
+
this.init_corners = true;
this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
return this; // fluent API
}
@@ -577,11 +599,13 @@
_corners[6] = _clipRect[3];
_corners[7] = _clipRect[1];
}
stack.pullAll(corners, out);
}
- out.lineTo(cx0, cy0);
+ out.lineTo(cox0, coy0);
+ this.cx0 = cox0;
+ this.cy0 = coy0;
}
@Override
public void pathDone() {
finishPath();
@@ -602,42 +626,72 @@
@Override
public void moveTo(final float x0, final float y0) {
finishPath();
- final int outcode = Helpers.outcode(x0, y0, clipRect);
- this.cOutCode = outcode;
+ this.cOutCode = Helpers.outcode(x0, y0, clipRect);
this.outside = false;
out.moveTo(x0, y0);
+ this.cx0 = x0;
+ this.cy0 = y0;
}
@Override
public void lineTo(final float xe, final float ye) {
final int outcode0 = this.cOutCode;
final int outcode1 = Helpers.outcode(xe, ye, clipRect);
- this.cOutCode = outcode1;
- final int sideCode = (outcode0 & outcode1);
+ // Should clip
+ final int orCode = (outcode0 | outcode1);
+ if (orCode != 0) {
+ final int sideCode = (outcode0 & outcode1);
- // basic rejection criteria:
- if (sideCode == 0) {
- this.gOutCode = 0;
- } else {
- this.gOutCode &= sideCode;
- // keep last point coordinate before entering the clip again:
- this.outside = true;
- this.cx0 = xe;
- this.cy0 = ye;
+ // basic rejection criteria:
+ if (sideCode == 0) {
+ // ovelap clip:
+ if (subdivide) {
+ // avoid reentrance
+ subdivide = false;
+ boolean ret;
+ // subdivide curve => callback with subdivided parts:
+ if (outside) {
+ ret = curveSplitter.splitLine(cox0, coy0, xe, ye,
+ orCode, this);
+ } else {
+ ret = curveSplitter.splitLine(cx0, cy0, xe, ye,
+ orCode, this);
+ }
+ // reentrance is done:
+ subdivide = true;
+ if (ret) {
+ return;
+ }
+ }
+ // already subdivided so render it
+ } else {
+ this.cOutCode = outcode1;
+ this.gOutCode &= sideCode;
+ // keep last point coordinate before entering the clip again:
+ this.outside = true;
+ this.cox0 = xe;
+ this.coy0 = ye;
- clip(sideCode, outcode0, outcode1);
- return;
+ clip(sideCode, outcode0, outcode1);
+ return;
+ }
}
+
+ this.cOutCode = outcode1;
+ this.gOutCode = 0;
+
if (outside) {
finish();
}
// clipping disabled:
out.lineTo(xe, ye);
+ this.cx0 = xe;
+ this.cy0 = ye;
}
private void clip(final int sideCode,
final int outcode0,
final int outcode1)
@@ -653,26 +707,22 @@
final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
// add corners to outside stack:
switch (tbCode) {
case MarlinConst.OUTCODE_TOP:
-// System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
stack.push(off); // top
return;
case MarlinConst.OUTCODE_BOTTOM:
-// System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
stack.push(off + 1); // bottom
return;
default:
// both TOP / BOTTOM:
if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
-// System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
// top to bottom
stack.push(off); // top
stack.push(off + 1); // bottom
} else {
-// System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
// bottom to top
stack.push(off + 1); // bottom
stack.push(off); // top
}
}
@@ -683,80 +733,389 @@
public void curveTo(final float x1, final float y1,
final float x2, final float y2,
final float xe, final float ye)
{
final int outcode0 = this.cOutCode;
+ final int outcode1 = Helpers.outcode(x1, y1, clipRect);
+ final int outcode2 = Helpers.outcode(x2, y2, clipRect);
final int outcode3 = Helpers.outcode(xe, ye, clipRect);
- this.cOutCode = outcode3;
-
- int sideCode = outcode0 & outcode3;
- if (sideCode == 0) {
- this.gOutCode = 0;
- } else {
- sideCode &= Helpers.outcode(x1, y1, clipRect);
- sideCode &= Helpers.outcode(x2, y2, clipRect);
- this.gOutCode &= sideCode;
+ // Should clip
+ final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
+ if (orCode != 0) {
+ final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
// basic rejection criteria:
- if (sideCode != 0) {
+ if (sideCode == 0) {
+ // ovelap clip:
+ if (subdivide) {
+ // avoid reentrance
+ subdivide = false;
+ // subdivide curve => callback with subdivided parts:
+ boolean ret;
+ if (outside) {
+ ret = curveSplitter.splitCurve(cox0, coy0, x1, y1,
+ x2, y2, xe, ye,
+ orCode, this);
+ } else {
+ ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
+ x2, y2, xe, ye,
+ orCode, this);
+ }
+ // reentrance is done:
+ subdivide = true;
+ if (ret) {
+ return;
+ }
+ }
+ // already subdivided so render it
+ } else {
+ this.cOutCode = outcode3;
+ this.gOutCode &= sideCode;
// keep last point coordinate before entering the clip again:
this.outside = true;
- this.cx0 = xe;
- this.cy0 = ye;
+ this.cox0 = xe;
+ this.coy0 = ye;
clip(sideCode, outcode0, outcode3);
return;
}
}
+
+ this.cOutCode = outcode3;
+ this.gOutCode = 0;
+
if (outside) {
finish();
}
// clipping disabled:
out.curveTo(x1, y1, x2, y2, xe, ye);
+ this.cx0 = xe;
+ this.cy0 = ye;
}
@Override
public void quadTo(final float x1, final float y1,
final float xe, final float ye)
{
final int outcode0 = this.cOutCode;
+ final int outcode1 = Helpers.outcode(x1, y1, clipRect);
final int outcode2 = Helpers.outcode(xe, ye, clipRect);
- this.cOutCode = outcode2;
-
- int sideCode = outcode0 & outcode2;
- if (sideCode == 0) {
- this.gOutCode = 0;
- } else {
- sideCode &= Helpers.outcode(x1, y1, clipRect);
- this.gOutCode &= sideCode;
+ // Should clip
+ final int orCode = (outcode0 | outcode1 | outcode2);
+ if (orCode != 0) {
+ final int sideCode = outcode0 & outcode1 & outcode2;
// basic rejection criteria:
- if (sideCode != 0) {
+ if (sideCode == 0) {
+ // ovelap clip:
+ if (subdivide) {
+ // avoid reentrance
+ subdivide = false;
+ // subdivide curve => callback with subdivided parts:
+ boolean ret;
+ if (outside) {
+ ret = curveSplitter.splitQuad(cox0, coy0, x1, y1,
+ xe, ye, orCode, this);
+ } else {
+ ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
+ xe, ye, orCode, this);
+ }
+ // reentrance is done:
+ subdivide = true;
+ if (ret) {
+ return;
+ }
+ }
+ // already subdivided so render it
+ } else {
+ this.cOutCode = outcode2;
+ this.gOutCode &= sideCode;
// keep last point coordinate before entering the clip again:
this.outside = true;
- this.cx0 = xe;
- this.cy0 = ye;
+ this.cox0 = xe;
+ this.coy0 = ye;
clip(sideCode, outcode0, outcode2);
return;
}
}
+
+ this.cOutCode = outcode2;
+ this.gOutCode = 0;
+
if (outside) {
finish();
}
// clipping disabled:
out.quadTo(x1, y1, xe, ye);
+ this.cx0 = xe;
+ this.cy0 = ye;
}
@Override
public long getNativeConsumer() {
throw new InternalError("Not using a native peer");
}
}
+ static final class CurveClipSplitter {
+
+ static final float LEN_TH = MarlinProperties.getSubdividerMinLength();
+ static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0f);
+
+ private static final boolean TRACE = false;
+
+ private static final int MAX_N_CURVES = 3 * 4;
+
+ // clip rectangle (ymin, ymax, xmin, xmax):
+ final float[] clipRect;
+
+ // clip rectangle (ymin, ymax, xmin, xmax) including padding:
+ final float[] clipRectPad = new float[4];
+ private boolean init_clipRectPad = false;
+
+ // This is where the curve to be processed is put. We give it
+ // enough room to store all curves.
+ final float[] middle = new float[MAX_N_CURVES * 8 + 2];
+ // t values at subdivision points
+ private final float[] subdivTs = new float[MAX_N_CURVES];
+
+ // dirty curve
+ private final Curve curve;
+
+ CurveClipSplitter(final RendererContext rdrCtx) {
+ this.clipRect = rdrCtx.clipRect;
+ this.curve = rdrCtx.curve;
+ }
+
+ void init() {
+ this.init_clipRectPad = true;
+ }
+
+ private void initPaddedClip() {
+ // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
+ // adjust padded clip rectangle (ymin, ymax, xmin, xmax):
+ // add a rounding error (curve subdivision ~ 0.1px):
+ final float[] _clipRect = clipRect;
+ final float[] _clipRectPad = clipRectPad;
+
+ _clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING;
+ _clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING;
+ _clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING;
+ _clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING;
+
+ if (TRACE) {
+ MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] "
+ + "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]");
+ }
+ }
+
+ boolean splitLine(final float x0, final float y0,
+ final float x1, final float y1,
+ final int outCodeOR,
+ final PathConsumer2D out)
+ {
+ if (TRACE) {
+ MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");
+ }
+
+ if (DO_CHECK_LENGTH && Helpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) {
+ return false;
+ }
+
+ final float[] mid = middle;
+ mid[0] = x0; mid[1] = y0;
+ mid[2] = x1; mid[3] = y1;
+
+ return subdivideAtIntersections(4, outCodeOR, out);
+ }
+
+ boolean splitQuad(final float x0, final float y0,
+ final float x1, final float y1,
+ final float x2, final float y2,
+ final int outCodeOR,
+ final PathConsumer2D out)
+ {
+ if (TRACE) {
+ MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")");
+ }
+
+ if (DO_CHECK_LENGTH && Helpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) {
+ return false;
+ }
+
+ final float[] mid = middle;
+ mid[0] = x0; mid[1] = y0;
+ mid[2] = x1; mid[3] = y1;
+ mid[4] = x2; mid[5] = y2;
+
+ return subdivideAtIntersections(6, outCodeOR, out);
+ }
+
+ boolean splitCurve(final float x0, final float y0,
+ final float x1, final float y1,
+ final float x2, final float y2,
+ final float x3, final float y3,
+ final int outCodeOR,
+ final PathConsumer2D out)
+ {
+ if (TRACE) {
+ MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")");
+ }
+
+ if (DO_CHECK_LENGTH && Helpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) {
+ return false;
+ }
+
+ final float[] mid = middle;
+ mid[0] = x0; mid[1] = y0;
+ mid[2] = x1; mid[3] = y1;
+ mid[4] = x2; mid[5] = y2;
+ mid[6] = x3; mid[7] = y3;
+
+ return subdivideAtIntersections(8, outCodeOR, out);
+ }
+
+ private boolean subdivideAtIntersections(final int type, final int outCodeOR,
+ final PathConsumer2D out)
+ {
+ final float[] mid = middle;
+ final float[] subTs = subdivTs;
+
+ if (init_clipRectPad) {
+ init_clipRectPad = false;
+ initPaddedClip();
+ }
+
+ final int nSplits = Helpers.findClipPoints(curve, mid, subTs, type,
+ outCodeOR, clipRectPad);
+
+ if (TRACE) {
+ MarlinUtils.logInfo("nSplits: "+ nSplits);
+ MarlinUtils.logInfo("subTs: "+Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits)));
+ }
+ if (nSplits == 0) {
+ // only curve support shortcut
+ return false;
+ }
+ float prevT = 0.0f;
+
+ for (int i = 0, off = 0; i < nSplits; i++, off += type) {
+ final float t = subTs[i];
+
+ Helpers.subdivideAt((t - prevT) / (1.0f - prevT),
+ mid, off, mid, off, type);
+ prevT = t;
+ }
+
+ for (int i = 0, off = 0; i <= nSplits; i++, off += type) {
+ if (TRACE) {
+ MarlinUtils.logInfo("Part Curve "+Arrays.toString(Arrays.copyOfRange(mid, off, off + type)));
+ }
+ emitCurrent(type, mid, off, out);
+ }
+ return true;
+ }
+
+ static void emitCurrent(final int type, final float[] pts,
+ final int off, final PathConsumer2D out)
+ {
+ // if instead of switch (perf + most probable cases first)
+ if (type == 8) {
+ out.curveTo(pts[off + 2], pts[off + 3],
+ pts[off + 4], pts[off + 5],
+ pts[off + 6], pts[off + 7]);
+ } else if (type == 4) {
+ out.lineTo(pts[off + 2], pts[off + 3]);
+ } else {
+ out.quadTo(pts[off + 2], pts[off + 3],
+ pts[off + 4], pts[off + 5]);
+ }
+ }
+ }
+
+ static final class CurveBasicMonotonizer {
+
+ private static final int MAX_N_CURVES = 11;
+
+ // squared half line width (for stroker)
+ private float lw2;
+
+ // number of splitted curves
+ int nbSplits;
+
+ // This is where the curve to be processed is put. We give it
+ // enough room to store all curves.
+ final float[] middle = new float[MAX_N_CURVES * 6 + 2];
+ // t values at subdivision points
+ private final float[] subdivTs = new float[MAX_N_CURVES - 1];
+
+ // dirty curve
+ private final Curve curve;
+
+ CurveBasicMonotonizer(final RendererContext rdrCtx) {
+ this.curve = rdrCtx.curve;
+ }
+
+ void init(final float lineWidth) {
+ this.lw2 = (lineWidth * lineWidth) / 4.0f;
+ }
+
+ CurveBasicMonotonizer curve(final float x0, final float y0,
+ final float x1, final float y1,
+ final float x2, final float y2,
+ final float x3, final float y3)
+ {
+ final float[] mid = middle;
+ mid[0] = x0; mid[1] = y0;
+ mid[2] = x1; mid[3] = y1;
+ mid[4] = x2; mid[5] = y2;
+ mid[6] = x3; mid[7] = y3;
+
+ final float[] subTs = subdivTs;
+ final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 8, lw2);
+
+ float prevT = 0.0f;
+ for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
+ final float t = subTs[i];
+
+ Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT),
+ mid, off, mid, off, off + 6);
+ prevT = t;
+ }
+
+ this.nbSplits = nSplits;
+ return this;
+ }
+
+ CurveBasicMonotonizer quad(final float x0, final float y0,
+ final float x1, final float y1,
+ final float x2, final float y2)
+ {
+ final float[] mid = middle;
+ mid[0] = x0; mid[1] = y0;
+ mid[2] = x1; mid[3] = y1;
+ mid[4] = x2; mid[5] = y2;
+
+ final float[] subTs = subdivTs;
+ final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 6, lw2);
+
+ float prevt = 0.0f;
+ for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
+ final float t = subTs[i];
+ Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt),
+ mid, off, mid, off, off + 4);
+ prevt = t;
+ }
+
+ this.nbSplits = nSplits;
+ return this;
+ }
+ }
+
static final class PathTracer implements PathConsumer2D {
private final String prefix;
private PathConsumer2D out;
PathTracer(String name) {
@@ -806,11 +1165,11 @@
log("pathDone");
out.pathDone();
}
private void log(final String message) {
- System.out.println(prefix + message);
+ MarlinUtils.logInfo(prefix + message);
}
@Override
public long getNativeConsumer() {
throw new InternalError("Not using a native peer");
< prev index next >