Hi all, As Jim is no more available to review & answer my questions on java2d / computer graphics, I need another reviewer on this webrev implementing path clipping in Marlin (huge potential gains).
Do you know someone else who can help me in the 2d / prism fields to improve the Marlin renderer even more ? Thanks, Laurent Le 5 sept. 2017 22:41, "Laurent Bourgès" <bourges.laur...@gmail.com> a écrit : > Hi Jim, > > I made good progress on my PathClipFilter that works now perfectly with > many test maps for the NZ rule (EO has artefacts if I enable the filter in > that case). > > Here is the updated code below to illustrate the approach: > - use a new IndexStack in (D)Helpers to store only corner indexes (0/1 for > Top/Bottom Left, 2/3 for Top/Bottom Right) > - when the segment is out, I now check (L/R case) if the segment ends have > different outcodes to insert needed corners that can be removed later if a > segment does the same in the reverse order (same repeated corner is > cancelled out): see IndexStack.push(int) > > - PathClipFilter: > > static final class PathClipFilter implements DPathConsumer2D { > > private DPathConsumer2D out; > > // Bounds of the drawing region, at pixel precision. > private final double[] clipRect; > > private final double[] corners = new double[8]; > private boolean init_corners = false; > > private final IndexStack stack; > > // the outcode of the starting point > private int sOutCode = 0; > > // the current outcode of the current sub path > private int cOutCode = 0; > > private boolean outside = false; > 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) > : new IndexStack(rdrCtx); > } > > PathClipFilter 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; > > // add a small rounding error: > final double margin = 1e-3d; > > final double[] _clipRect = this.clipRect; > _clipRect[0] -= margin - rdrOffY; > _clipRect[1] += margin + rdrOffY; > _clipRect[2] -= margin - rdrOffX; > _clipRect[3] += margin + rdrOffX; > > init_corners = true; > > return this; // fluent API > } > > /** > * Disposes this instance: > * clean up before reusing this instance > */ > void dispose() { > stack.dispose(); > } > > @Override > public void pathDone() { > out.pathDone(); > > // TODO: fix possible leak if exception happened > // Dispose this instance: > dispose(); > } > > @Override > public void closePath() { > if (outside) { > this.outside = false; > > if (sOutCode == 0) { > finish(); > } else { > stack.reset(); > } > } > out.closePath(); > this.cOutCode = sOutCode; > } > > private void finish() { > if (!stack.isEmpty()) { > if (init_corners) { > init_corners = false; > // Top Left (0): > corners[0] = clipRect[2]; > corners[1] = clipRect[0]; > // Bottom Left (1): > corners[2] = clipRect[2]; > corners[3] = clipRect[1]; > // Top right (2): > corners[4] = clipRect[3]; > corners[5] = clipRect[0]; > // Bottom Right (3): > corners[6] = clipRect[3]; > corners[7] = clipRect[1]; > } > stack.pullAll(corners, out); > } > out.lineTo(cx0, cy0); > } > > @Override > public void moveTo(final double x0, final double y0) { > final int outcode = DHelpers.outcode(x0, y0, clipRect); > this.sOutCode = outcode; > 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) { > // 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) { > this.outside = false; > finish(); > } > // clipping disabled: > out.lineTo(xe, ye); > } > > private void clip(final int sideCode, > final int outcode0, > final int outcode1) > { > // corner or cross-boundary on left or right side: > if ((outcode0 != outcode1) > && ((sideCode & DHelpers.OUTCODE_MASK_T_B) != 0)) > { > // combine outcodes: > final int mergeCode = (outcode0 | outcode1); > final int tbCode = mergeCode & DHelpers.OUTCODE_MASK_T_B; > final int lrCode = mergeCode & DHelpers.OUTCODE_MASK_L_R; > // add corners to outside stack: > final int off = (lrCode == DHelpers.OUTCODE_LEFT) ? 0 : 2; > > switch (tbCode) { > case DHelpers.OUTCODE_TOP: > stack.push(off); // top > return; > case DHelpers.OUTCODE_BOTTOM: > stack.push(off + 1); // bottom > return; > default: > // both TOP / BOTTOM: > if ((outcode0 & DHelpers.OUTCODE_TOP) != 0) { > // top to bottom > stack.push(off); // top > stack.push(off + 1); // bottom > } else { > // bottom to top > stack.push(off + 1); // bottom > stack.push(off); // top > } > } > } > } > > @Override > 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) { > sideCode &= DHelpers.outcode(x1, y1, clipRect); > sideCode &= DHelpers.outcode(x2, y2, clipRect); > > // 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) { > this.outside = false; > 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 (outcode2 != 0) { > sideCode &= DHelpers.outcode(x1, y1, clipRect); > > // 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) { > this.outside = false; > finish(); > } > // clipping disabled: > out.quadTo(x1, y1, xe, ye); > } > > @Override > public long getNativeConsumer() { > throw new InternalError("Not using a native peer"); > } > } > > - DHelpers.IndexStack: > // a stack of integer indices > static final class IndexStack { > > // integer capacity = edges count / 4 ~ 1024 > private static final int INITIAL_COUNT = INITIAL_EDGES_COUNT >> 2; > > private int end; > private int[] indices; > > // indices ref (dirty) > private final IntArrayCache.Reference indices_ref; > > // used marks (stats only) > private int indicesUseMark; > > private final StatLong stat_idxstack_indices; > private final Histogram hist_idxstack_indices; > private final StatLong stat_array_idxstack_indices; > > IndexStack(final DRendererContext rdrCtx) { > this(rdrCtx, null, null, null); > } > > IndexStack(final DRendererContext rdrCtx, > final StatLong stat_idxstack_indices, > final Histogram hist_idxstack_indices, > final StatLong stat_array_idxstack_indices) > { > indices_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_COUNT); // 4K > indices = indices_ref.initial; > end = 0; > > if (DO_STATS) { > indicesUseMark = 0; > } > this.stat_idxstack_indices = stat_idxstack_indices; > this.hist_idxstack_indices = hist_idxstack_indices; > this.stat_array_idxstack_indices = > stat_array_idxstack_indices; > } > > /** > * Disposes this PolyStack: > * clean up before reusing this instance > */ > void dispose() { > end = 0; > > if (DO_STATS) { > stat_idxstack_indices.add(indicesUseMark); > hist_idxstack_indices.add(indicesUseMark); > > // reset marks > indicesUseMark = 0; > } > > // Return arrays: > // values is kept dirty > indices = indices_ref.putArray(indices); > } > > boolean isEmpty() { > return (end == 0); > } > > void reset() { > end = 0; > } > > void push(final int v) { > // remove redundant values (reverse order): > int[] _values = indices; > final int nc = end; > if (nc != 0) { > if (_values[nc - 1] == v) { > // remove both duplicated values: > end--; > return; > } > } > if (_values.length <= nc) { > if (DO_STATS) { > stat_array_idxstack_indices.add(nc + 1); > } > indices = _values = indices_ref.widenArray(_values, nc, > nc + 1); > } > _values[end++] = v; > > if (DO_STATS) { > // update used marks: > if (end > indicesUseMark) { > indicesUseMark = end; > } > } > } > > void pullAll(final double[] points, final DPathConsumer2D io) { > final int nc = end; > if (nc == 0) { > return; > } > final int[] _values = indices; > > for (int i = 0, j; i < nc; i++) { > j = _values[i] << 1; > io.lineTo(points[j], points[j + 1]); > } > end = 0; > } > } > > > 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 >> > > Now all useless rounds are totally discarded from the path sent to the > Renderer (removing lots of edges on the left/right sides) > > >> clip off: ~ 145ms >> clip on: ~ 106ms >> > > clip on: ~ 68ms for this huge filled spiral ~ 50% faster > > > Could you answer my previous email on EO questions ? > How to deal with self intersections or is it possible to skip left > segments in the EO case or not ? > (I am a bit lost) > > I need a simple path to test clipping with the EO rule (redudant > segments); any idea ? > > Cheers, > Laurent >