[OpenJDK 2D-Dev] RFR JDK-8184429: Path clipper added in Marlin2D & MarlinFX 0.8.0
Laurent Bourgès
bourges.laurent at gmail.com
Fri Sep 1 21:09:18 UTC 2017
Jim,
Here is the current code for my experimental PathClipFilter only to show
you what I have done so far:
- DMarlingRenderingEngine.getAATileGenerator():
...
// fill shape:
final PathIterator pi =
norm.getNormalizingPathIterator(rdrCtx,
s.getPathIterator(_at));
final int windingRule = pi.getWindingRule();
// note: Winding rule may be EvenOdd ONLY for fill
operations !
r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
clip.getWidth(), clip.getHeight(),
windingRule);
if (windingRule == WIND_NON_ZERO) {
DPathConsumer2D pc2d = r;
if (rdrCtx.doClip) {
pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);
}
// TODO: subdivide quad/cubic curves into monotonic
curves ?
pathTo(rdrCtx, pi, pc2d);
}
- PathClipper:
static final class GeneralPathClipper implements DPathConsumer2D {
private DPathConsumer2D out;
// Bounds of the drawing region, at pixel precision.
private final double[] clipRect;
// the outcode of the starting point
private int sOutCode = 0;
// the current outcode of the current sub path
private int cOutCode = 0;
GeneralPathClipper(final DRendererContext rdrCtx) {
this.clipRect = rdrCtx.clipRect;
}
GeneralPathClipper init(final DPathConsumer2D out) {
this.out = out;
// Adjust the clipping rectangle with the renderer offsets
final double rdrOffX = DRenderer.RDR_OFFSET_X;
final double rdrOffY = DRenderer.RDR_OFFSET_Y;
final double[] _clipRect = this.clipRect;
_clipRect[0] += rdrOffY;
_clipRect[1] += rdrOffY;
_clipRect[2] += rdrOffX;
_clipRect[3] += rdrOffX;
return this; // fluent API
}
@Override
public void pathDone() {
out.pathDone();
}
@Override
public void closePath() {
out.closePath();
this.cOutCode = sOutCode;
}
private void finish() {
if (outside) {
this.outside = false;
out.lineTo(cx0, cy0);
}
}
@Override
public void moveTo(final double x0, final double y0) {
final int outcode = DHelpers.outcode(x0, y0, clipRect);
// basic rejection criteria
this.sOutCode = outcode;
this.cOutCode = outcode;
this.outside = false;
out.moveTo(x0, y0);
}
boolean outside = false;
double cx0, cy0;
@Override
public void lineTo(final double x1, final double y1) {
final int outcode0 = this.cOutCode;
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
this.cOutCode = outcode1;
final int sidecode = (outcode0 & outcode1);
// basic rejection criteria:
if (sidecode != 0) {
// top or bottom or same:
if ((outcode0 == outcode1)
|| ((sidecode & DHelpers.OUTCODE_MASK_T_B) != 0) ) {
// corner or cross-boundary
this.outside = true;
// TODO: add to outside stack (throw if never enter
again ?)
this.cx0 = x1;
this.cy0 = y1;
return;
} else {
// corner or cross-boundary on left or right side:
// TODO: add to outside stack (throw if never enter
again ?)
// line(P0-P1)
}
}
finish();
// clipping disabled:
out.lineTo(x1, y1);
}
@Override
public void curveTo(final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3)
{
final int outcode0 = this.cOutCode;
final int outcode3 = DHelpers.outcode(x3, y3, clipRect);
this.cOutCode = outcode3;
// TODO: optimize ?
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
// basic rejection criteria
if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
// Different quadrant ?
if ((outcode0 == outcode3) && (outcode0 == outcode1) &&
(outcode0 == outcode2)) {
// corner or cross-boundary
this.outside = true;
// TODO: add to outside stack (throw if never enter
again ?)
this.cx0 = x3;
this.cy0 = y3;
return;
} else {
// corner or cross-boundary
// TODO: add to outside stack (throw if never enter
again ?)
// line(P0-P1)
finish();
// clipping disabled:
out.lineTo(x3, y3);
return;
}
}
finish();
// clipping disabled:
out.curveTo(x1, y1, x2, y2, x3, y3);
}
@Override
public void quadTo(final double x1, final double y1,
final double x2, final double y2)
{
final int outcode0 = this.cOutCode;
final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
this.cOutCode = outcode2;
// TODO: optimize ?
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
// basic rejection criteria
if ((outcode0 & outcode1 & outcode2) != 0) {
// Different quadrant ?
if ((outcode0 == outcode2) && (outcode0 == outcode1)) {
// corner or cross-boundary
this.outside = true;
// TODO: add to outside stack (throw if never enter
again ?)
this.cx0 = x2;
this.cy0 = y2;
return;
} else {
// corner or cross-boundary
// TODO: add to outside stack (throw if never enter
again ?)
// line(P0-P1)
finish();
// clipping disabled:
out.lineTo(x2, y2);
return;
}
}
finish();
// clipping disabled:
out.quadTo(x1, y1, x2, y2);
}
@Override
public long getNativeConsumer() {
throw new InternalError("Not using a native peer");
}
}
Here is a screenshot illustrating the remaining paths in Renderer after
clipping a 4000x4000 spiral converted as stroked shape:
http://cr.openjdk.java.net/~lbourges/png/SpiralTest-dash-false.ser.png
You can see all rounds arround the clip that I expect soon to ignore too as
I plan to use a corner stack to remember turining points until the path
enters again or go back in reverse order...
clip off: ~ 145ms
clip on: ~ 106ms
TODO: handle corner points & turns arround / reverse the clip
Cheers,
Laurent
2017-09-01 22:09 GMT+02:00 Laurent Bourgès <bourges.laurent at gmail.com>:
> Dear Jim,
>
> I am not so good at explaining my early solution for NZ rule in english:
> - it mimics the Stroker approach
> - it skips all intermediate segments outside (except at corners ie
> different outcodes)
> - preserve only the last point of the entering segment
>
> So mostly invisible segments on the left and right sides are skipped from
> the original that makes the Renderer only processing visible segments like
> in the Stroker case where I opened the path.
>
> It improves the performance as less left / right segments are processed in
> addLine(). For now the Renderer accepts all segments and only rejects
> top/bottom parts and keeps all left/right edges in its processing: that
> costs a lot.
>
>
> I think we are discussing two different things. I thought you were
> considering a separate filter for clipping filled only paths. I agree that
> the Stroker webrev is still needed for stroked paths. We should also
> create one for dashed paths that will clip before dashing. There are
> multiple stages at which we can clip to reduce processing.
>
>
> Exactly I also like the pipeline approach that decouples stages into
> different parts.
>
>
> I take that case into account. How is a prefilter that excludes those
> segments any simpler than the Renderer rejecting them itself?
>
> Keep in mind that they are already rejected. Renderer.addLine() already
> does this. In the case of a polygon, there isn't much code you can reduce
> because it goes from the loop that feeds the path in the rendering engine
> directly to Renderer.move/lineTo() which goes directly to addLine() which
> rejects all of those segments.
>
>
> Not on the left / right side.
> Closed subpaths or extra segments can be ignored for NZ rule = only
> keeping the path closed properly matters.
>
> Will send you the current code to illustrate the basic filter asap.
>
>
> In the case of curves, the path is nearly as direct, but first you run a
> DDA on it to break it into lines. Simply modifying Renderer.quadTo and
> cubicTo to perform quick-rejection would be a very simple change (certainly
> simpler than adding a new class to do that) and would reject all of those
> curved segments much faster...
>
>
> Agreed, I have done it in the past and can provide the change.
>
> Laurent
>
>
--
--
Laurent Bourgès
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/2d-dev/attachments/20170901/b0ca882d/attachment-0001.html>
More information about the 2d-dev
mailing list