< prev index next >
src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java
Print this page
*** 1,7 ****
/*
! * Copyright (c) 2007, 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. Oracle designates this
--- 1,7 ----
/*
! * 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,40 ****
--- 26,44 ----
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,64 ****
--- 59,69 ----
// 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,92 ****
--- 88,101 ----
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,512 ****
// 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
private float cx0, cy0;
PathClipFilter(final RendererContext rdrCtx) {
this.clipRect = rdrCtx.clipRect;
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)
--- 507,529 ----
// 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 (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,536 ****
--- 544,558 ----
_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,587 ****
_corners[6] = _clipRect[3];
_corners[7] = _clipRect[1];
}
stack.pullAll(corners, out);
}
! out.lineTo(cx0, cy0);
}
@Override
public void pathDone() {
finishPath();
--- 599,611 ----
_corners[6] = _clipRect[3];
_corners[7] = _clipRect[1];
}
stack.pullAll(corners, out);
}
! out.lineTo(cox0, coy0);
! this.cx0 = cox0;
! this.cy0 = coy0;
}
@Override
public void pathDone() {
finishPath();
*** 602,643 ****
@Override
public void moveTo(final float x0, final float y0) {
finishPath();
! final int outcode = Helpers.outcode(x0, y0, clipRect);
! this.cOutCode = outcode;
this.outside = false;
out.moveTo(x0, 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);
! // 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;
! clip(sideCode, outcode0, outcode1);
! return;
}
if (outside) {
finish();
}
// clipping disabled:
out.lineTo(xe, ye);
}
private void clip(final int sideCode,
final int outcode0,
final int outcode1)
--- 626,697 ----
@Override
public void moveTo(final float x0, final float y0) {
finishPath();
! 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);
! // Should clip
! final int orCode = (outcode0 | outcode1);
! if (orCode != 0) {
! final int sideCode = (outcode0 & outcode1);
! // 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;
! }
}
+
+ 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,678 ****
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
}
}
--- 707,728 ----
*** 683,762 ****
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 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;
// basic rejection criteria:
! if (sideCode != 0) {
// keep last point coordinate before entering the clip again:
this.outside = true;
! this.cx0 = xe;
! this.cy0 = ye;
clip(sideCode, outcode0, outcode3);
return;
}
}
if (outside) {
finish();
}
// clipping disabled:
out.curveTo(x1, y1, x2, y2, xe, ye);
}
@Override
public void quadTo(final float x1, final float y1,
final float xe, final float ye)
{
final int outcode0 = this.cOutCode;
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;
// basic rejection criteria:
! if (sideCode != 0) {
// keep last point coordinate before entering the clip again:
this.outside = true;
! this.cx0 = xe;
! this.cy0 = ye;
clip(sideCode, outcode0, outcode2);
return;
}
}
if (outside) {
finish();
}
// clipping disabled:
out.quadTo(x1, y1, xe, ye);
}
@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) {
--- 733,1121 ----
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);
! // 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) {
! // 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.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);
! // Should clip
! final int orCode = (outcode0 | outcode1 | outcode2);
! if (orCode != 0) {
! final int sideCode = outcode0 & outcode1 & outcode2;
// basic rejection criteria:
! 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.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,816 ****
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");
--- 1165,1175 ----
log("pathDone");
out.pathDone();
}
private void log(final String message) {
! MarlinUtils.logInfo(prefix + message);
}
@Override
public long getNativeConsumer() {
throw new InternalError("Not using a native peer");
< prev index next >