< prev index next >
modules/javafx.graphics/src/main/java/com/sun/marlin/DTransformingPathConsumer2D.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
*** 27,39 ****
--- 27,43 ----
import com.sun.javafx.geom.Path2D;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.marlin.DHelpers.IndexStack;
import com.sun.marlin.DHelpers.PolyStack;
+ import java.util.Arrays;
public final class DTransformingPathConsumer2D {
+ // smaller uncertainty in double variant
+ static final double CLIP_RECT_PADDING = 0.25d;
+
private final DRendererContext rdrCtx;
// recycled ClosedPathDetector instance from detectClosedPath()
private final ClosedPathDetector cpDetector;
*** 54,63 ****
--- 58,68 ----
// 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");
DTransformingPathConsumer2D(final DRendererContext rdrCtx) {
// used by RendererContext
this.rdrCtx = rdrCtx;
this.cpDetector = new ClosedPathDetector(rdrCtx);
*** 82,91 ****
--- 87,100 ----
public DPathConsumer2D traceStroker(DPathConsumer2D out) {
return tracerStroker.init(out);
}
+ public DPathConsumer2D traceDasher(DPathConsumer2D out) {
+ return tracerDasher.init(out);
+ }
+
public DPathConsumer2D detectClosedPath(DPathConsumer2D out) {
return cpDetector.init(out);
}
public DPathConsumer2D pathClipper(DPathConsumer2D out,
*** 484,498 ****
// 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 double cx0, cy0;
PathClipFilter(final DRendererContext 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)
--- 493,515 ----
// 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 double cx0, cy0;
+ // The current point OUTSIDE
+ private double cox0, coy0;
+
+ private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
+ private final CurveClipSplitter curveSplitter;
+
PathClipFilter(final DRendererContext 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)
*** 513,522 ****
--- 530,544 ----
_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
}
*** 563,573 ****
_corners[6] = _clipRect[3];
_corners[7] = _clipRect[1];
}
stack.pullAll(corners, out);
}
! out.lineTo(cx0, cy0);
}
@Override
public void pathDone() {
finishPath();
--- 585,597 ----
_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();
*** 588,629 ****
@Override
public void moveTo(final double x0, final double y0) {
finishPath();
! final int outcode = DHelpers.outcode(x0, y0, clipRect);
! this.cOutCode = outcode;
this.outside = false;
out.moveTo(x0, y0);
}
@Override
public void lineTo(final double xe, final double ye) {
final int outcode0 = this.cOutCode;
final int outcode1 = DHelpers.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)
--- 612,683 ----
@Override
public void moveTo(final double x0, final double y0) {
finishPath();
! this.cOutCode = DHelpers.outcode(x0, y0, clipRect);
this.outside = false;
out.moveTo(x0, y0);
+ this.cx0 = x0;
+ this.cy0 = y0;
}
@Override
public void lineTo(final double xe, final double ye) {
final int outcode0 = this.cOutCode;
final int outcode1 = DHelpers.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)
*** 639,664 ****
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
}
}
--- 693,714 ----
*** 669,740 ****
public void curveTo(final double x1, final double y1,
final double x2, final double y2,
final double xe, final double ye)
{
final int outcode0 = this.cOutCode;
final int outcode3 = DHelpers.outcode(xe, ye, clipRect);
- this.cOutCode = outcode3;
-
- int sideCode = outcode0 & outcode3;
! if (sideCode == 0) {
! this.gOutCode = 0;
! } else {
! sideCode &= DHelpers.outcode(x1, y1, clipRect);
! sideCode &= DHelpers.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 double x1, final double y1,
final double xe, final double ye)
{
final int outcode0 = this.cOutCode;
final int outcode2 = DHelpers.outcode(xe, ye, clipRect);
- this.cOutCode = outcode2;
-
- int sideCode = outcode0 & outcode2;
! if (sideCode == 0) {
! this.gOutCode = 0;
! } else {
! sideCode &= DHelpers.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);
}
}
static final class PathTracer implements DPathConsumer2D {
private final String prefix;
--- 719,1099 ----
public void curveTo(final double x1, final double y1,
final double x2, final double y2,
final double xe, final double ye)
{
final int outcode0 = this.cOutCode;
+ final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
+ final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
final int outcode3 = DHelpers.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 double x1, final double y1,
final double xe, final double ye)
{
final int outcode0 = this.cOutCode;
+ final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
final int outcode2 = DHelpers.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;
+ }
+ }
+
+ static final class CurveClipSplitter {
+
+ static final double LEN_TH = MarlinProperties.getSubdividerMinLength();
+ static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0d);
+
+ private static final boolean TRACE = false;
+
+ private static final int MAX_N_CURVES = 3 * 4;
+
+ // clip rectangle (ymin, ymax, xmin, xmax):
+ final double[] clipRect;
+
+ // clip rectangle (ymin, ymax, xmin, xmax) including padding:
+ final double[] clipRectPad = new double[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 double[] middle = new double[MAX_N_CURVES * 8 + 2];
+ // t values at subdivision points
+ private final double[] subdivTs = new double[MAX_N_CURVES];
+
+ // dirty curve
+ private final DCurve curve;
+
+ CurveClipSplitter(final DRendererContext 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 double[] _clipRect = clipRect;
+ final double[] _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 double x0, final double y0,
+ final double x1, final double y1,
+ final int outCodeOR,
+ final DPathConsumer2D out)
+ {
+ if (TRACE) {
+ MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");
+ }
+
+ if (DO_CHECK_LENGTH && DHelpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) {
+ return false;
+ }
+
+ final double[] mid = middle;
+ mid[0] = x0; mid[1] = y0;
+ mid[2] = x1; mid[3] = y1;
+
+ return subdivideAtIntersections(4, outCodeOR, out);
+ }
+
+ boolean splitQuad(final double x0, final double y0,
+ final double x1, final double y1,
+ final double x2, final double y2,
+ final int outCodeOR,
+ final DPathConsumer2D out)
+ {
+ if (TRACE) {
+ MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")");
+ }
+
+ if (DO_CHECK_LENGTH && DHelpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) {
+ return false;
+ }
+
+ final double[] 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 double x0, final double y0,
+ final double x1, final double y1,
+ final double x2, final double y2,
+ final double x3, final double y3,
+ final int outCodeOR,
+ final DPathConsumer2D out)
+ {
+ if (TRACE) {
+ MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")");
+ }
+
+ if (DO_CHECK_LENGTH && DHelpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) {
+ return false;
+ }
+
+ final double[] 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 DPathConsumer2D out)
+ {
+ final double[] mid = middle;
+ final double[] subTs = subdivTs;
+
+ if (init_clipRectPad) {
+ init_clipRectPad = false;
+ initPaddedClip();
+ }
+
+ final int nSplits = DHelpers.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;
+ }
+ double prevT = 0.0d;
+
+ for (int i = 0, off = 0; i < nSplits; i++, off += type) {
+ final double t = subTs[i];
+
+ DHelpers.subdivideAt((t - prevT) / (1.0d - 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 double[] pts,
+ final int off, final DPathConsumer2D 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]);
+ }
+ }
+ }
+
+ public static final class CurveBasicMonotonizer {
+
+ private static final int MAX_N_CURVES = 11;
+
+ // squared half line width (for stroker)
+ private double 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 double[] middle = new double[MAX_N_CURVES * 6 + 2];
+ // t values at subdivision points
+ private final double[] subdivTs = new double[MAX_N_CURVES - 1];
+
+ // dirty curve
+ private final DCurve curve;
+
+ CurveBasicMonotonizer(final DRendererContext rdrCtx) {
+ this.curve = rdrCtx.curve;
+ }
+
+ public void init(final double lineWidth) {
+ this.lw2 = (lineWidth * lineWidth) / 4.0d;
+ }
+
+ CurveBasicMonotonizer curve(final double x0, final double y0,
+ final double x1, final double y1,
+ final double x2, final double y2,
+ final double x3, final double y3)
+ {
+ final double[] 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 double[] subTs = subdivTs;
+ final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 8, lw2);
+
+ double prevT = 0.0d;
+ for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
+ final double t = subTs[i];
+
+ DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),
+ mid, off, mid, off, off + 6);
+ prevT = t;
+ }
+
+ this.nbSplits = nSplits;
+ return this;
+ }
+
+ CurveBasicMonotonizer quad(final double x0, final double y0,
+ final double x1, final double y1,
+ final double x2, final double y2)
+ {
+ final double[] mid = middle;
+ mid[0] = x0; mid[1] = y0;
+ mid[2] = x1; mid[3] = y1;
+ mid[4] = x2; mid[5] = y2;
+
+ final double[] subTs = subdivTs;
+ final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 6, lw2);
+
+ double prevt = 0.0d;
+ for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
+ final double t = subTs[i];
+ DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
+ mid, off, mid, off, off + 4);
+ prevt = t;
+ }
+
+ this.nbSplits = nSplits;
+ return this;
}
}
static final class PathTracer implements DPathConsumer2D {
private final String prefix;
*** 787,795 ****
log("pathDone");
out.pathDone();
}
private void log(final String message) {
! System.out.println(prefix + message);
}
}
}
--- 1146,1154 ----
log("pathDone");
out.pathDone();
}
private void log(final String message) {
! MarlinUtils.logInfo(prefix + message);
}
}
}
< prev index next >