[OpenJDK 2D-Dev] RFR JDK-8184429: Path clipper added in Marlin2D & MarlinFX 0.8.0
Laurent Bourgès
bourges.laurent at gmail.com
Thu Sep 7 06:52:18 UTC 2017
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.rushforth at 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.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/20170907/b3c42107/attachment-0001.html>
More information about the 2d-dev
mailing list