[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 2d-dev
mailing list