Hi Kevin, Ok I propose to withdraw or postpone this review after JavaOne where we will be able to discuss in a face to face meeting about Marlin & MarlinFX changes for JDK10.
I hope the 2d / jfx groups have other Graphics Guru to help, as good as Jim Graham. Cheers, Laurent Le 6 sept. 2017 16:23, "Kevin Rushforth" <kevin.rushfo...@oracle.com> a écrit : > Hi Laurent, > > Some combination of Phil, Sergey, and I will take a look at this when we > can. Perhaps there might be others on these two lists who could lend a > helping hand? > > -- Kevin > > > Laurent Bourgès wrote: > >> 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 >>> >>> >>> >>