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

Kevin Rushforth kevin.rushforth at oracle.com
Wed Sep 6 14:22:55 UTC 2017


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


More information about the openjfx-dev mailing list