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.laur...@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