[OpenJDK 2D-Dev] RFR JDK-8184429: Path clipper added in Marlin2D & MarlinFX 0.8.0

Laurent Bourgès bourges.laurent at gmail.com
Wed Sep 6 08:25:50 UTC 2017


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.laurent at 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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/2d-dev/attachments/20170906/c90e4cda/attachment-0001.html>


More information about the 2d-dev mailing list