< prev index next >

modules/javafx.graphics/src/main/java/com/sun/marlin/DTransformingPathConsumer2D.java

Print this page

        

*** 1,7 **** /* ! * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this --- 1,7 ---- /* ! * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this
*** 27,39 **** --- 27,43 ---- import com.sun.javafx.geom.Path2D; import com.sun.javafx.geom.transform.BaseTransform; import com.sun.marlin.DHelpers.IndexStack; import com.sun.marlin.DHelpers.PolyStack; + import java.util.Arrays; public final class DTransformingPathConsumer2D { + // smaller uncertainty in double variant + static final double CLIP_RECT_PADDING = 0.25d; + private final DRendererContext rdrCtx; // recycled ClosedPathDetector instance from detectClosedPath() private final ClosedPathDetector cpDetector;
*** 54,63 **** --- 58,68 ---- // recycled PathTracer instances from tracer...() methods private final PathTracer tracerInput = new PathTracer("[Input]"); private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector"); private final PathTracer tracerFiller = new PathTracer("Filler"); private final PathTracer tracerStroker = new PathTracer("Stroker"); + private final PathTracer tracerDasher = new PathTracer("Dasher"); DTransformingPathConsumer2D(final DRendererContext rdrCtx) { // used by RendererContext this.rdrCtx = rdrCtx; this.cpDetector = new ClosedPathDetector(rdrCtx);
*** 82,91 **** --- 87,100 ---- public DPathConsumer2D traceStroker(DPathConsumer2D out) { return tracerStroker.init(out); } + public DPathConsumer2D traceDasher(DPathConsumer2D out) { + return tracerDasher.init(out); + } + public DPathConsumer2D detectClosedPath(DPathConsumer2D out) { return cpDetector.init(out); } public DPathConsumer2D pathClipper(DPathConsumer2D out,
*** 484,498 **** // the cumulated (and) outcode of the complete path private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R; private boolean outside = false; ! // The current point OUTSIDE 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) --- 493,515 ---- // the cumulated (and) outcode of the complete path private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R; private boolean outside = false; ! // The current point (TODO stupid repeated info) private double cx0, cy0; + // The current point OUTSIDE + private double cox0, coy0; + + private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER; + private final CurveClipSplitter curveSplitter; + PathClipFilter(final DRendererContext rdrCtx) { this.clipRect = rdrCtx.clipRect; + this.curveSplitter = rdrCtx.curveClipSplitter; + 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)
*** 513,522 **** --- 530,544 ---- _clipRect[0] -= margin - rdrOffY; _clipRect[1] += margin + rdrOffY; _clipRect[2] -= margin - rdrOffX; _clipRect[3] += margin + rdrOffX; + if (MarlinConst.DO_CLIP_SUBDIVIDER) { + // adjust padded clip rectangle: + curveSplitter.init(); + } + this.init_corners = true; this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R; return this; // fluent API }
*** 563,573 **** _corners[6] = _clipRect[3]; _corners[7] = _clipRect[1]; } stack.pullAll(corners, out); } ! out.lineTo(cx0, cy0); } @Override public void pathDone() { finishPath(); --- 585,597 ---- _corners[6] = _clipRect[3]; _corners[7] = _clipRect[1]; } stack.pullAll(corners, out); } ! out.lineTo(cox0, coy0); ! this.cx0 = cox0; ! this.cy0 = coy0; } @Override public void pathDone() { finishPath();
*** 588,629 **** @Override public void moveTo(final double x0, final double y0) { finishPath(); ! final int outcode = DHelpers.outcode(x0, y0, clipRect); ! 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) { ! this.gOutCode = 0; ! } else { ! this.gOutCode &= sideCode; ! // 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) { finish(); } // clipping disabled: out.lineTo(xe, ye); } private void clip(final int sideCode, final int outcode0, final int outcode1) --- 612,683 ---- @Override public void moveTo(final double x0, final double y0) { finishPath(); ! this.cOutCode = DHelpers.outcode(x0, y0, clipRect); this.outside = false; out.moveTo(x0, y0); + this.cx0 = x0; + this.cy0 = y0; } @Override public void lineTo(final double xe, final double ye) { final int outcode0 = this.cOutCode; final int outcode1 = DHelpers.outcode(xe, ye, clipRect); ! // Should clip ! final int orCode = (outcode0 | outcode1); ! if (orCode != 0) { ! final int sideCode = (outcode0 & outcode1); ! // basic rejection criteria: ! if (sideCode == 0) { ! // ovelap clip: ! if (subdivide) { ! // avoid reentrance ! subdivide = false; ! boolean ret; ! // subdivide curve => callback with subdivided parts: ! if (outside) { ! ret = curveSplitter.splitLine(cox0, coy0, xe, ye, ! orCode, this); ! } else { ! ret = curveSplitter.splitLine(cx0, cy0, xe, ye, ! orCode, this); ! } ! // reentrance is done: ! subdivide = true; ! if (ret) { ! return; ! } ! } ! // already subdivided so render it ! } else { ! this.cOutCode = outcode1; ! this.gOutCode &= sideCode; ! // keep last point coordinate before entering the clip again: ! this.outside = true; ! this.cox0 = xe; ! this.coy0 = ye; ! clip(sideCode, outcode0, outcode1); ! return; ! } } + + this.cOutCode = outcode1; + this.gOutCode = 0; + if (outside) { finish(); } // clipping disabled: out.lineTo(xe, ye); + this.cx0 = xe; + this.cy0 = ye; } private void clip(final int sideCode, final int outcode0, final int outcode1)
*** 639,664 **** final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2; // add corners to outside stack: switch (tbCode) { case MarlinConst.OUTCODE_TOP: - // System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT")); stack.push(off); // top return; case MarlinConst.OUTCODE_BOTTOM: - // System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT")); stack.push(off + 1); // bottom return; default: // both TOP / BOTTOM: if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) { - // System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT")); // top to bottom stack.push(off); // top stack.push(off + 1); // bottom } else { - // System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT")); // bottom to top stack.push(off + 1); // bottom stack.push(off); // top } } --- 693,714 ----
*** 669,740 **** 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) { ! this.gOutCode = 0; ! } else { ! sideCode &= DHelpers.outcode(x1, y1, clipRect); ! sideCode &= DHelpers.outcode(x2, y2, clipRect); ! this.gOutCode &= sideCode; // 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) { 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 (sideCode == 0) { ! this.gOutCode = 0; ! } else { ! sideCode &= DHelpers.outcode(x1, y1, clipRect); ! this.gOutCode &= sideCode; // 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) { finish(); } // clipping disabled: out.quadTo(x1, y1, xe, ye); } } static final class PathTracer implements DPathConsumer2D { private final String prefix; --- 719,1099 ---- 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 outcode1 = DHelpers.outcode(x1, y1, clipRect); + final int outcode2 = DHelpers.outcode(x2, y2, clipRect); final int outcode3 = DHelpers.outcode(xe, ye, clipRect); ! // Should clip ! final int orCode = (outcode0 | outcode1 | outcode2 | outcode3); ! if (orCode != 0) { ! final int sideCode = outcode0 & outcode1 & outcode2 & outcode3; // basic rejection criteria: ! if (sideCode == 0) { ! // ovelap clip: ! if (subdivide) { ! // avoid reentrance ! subdivide = false; ! // subdivide curve => callback with subdivided parts: ! boolean ret; ! if (outside) { ! ret = curveSplitter.splitCurve(cox0, coy0, x1, y1, ! x2, y2, xe, ye, ! orCode, this); ! } else { ! ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, ! x2, y2, xe, ye, ! orCode, this); ! } ! // reentrance is done: ! subdivide = true; ! if (ret) { ! return; ! } ! } ! // already subdivided so render it ! } else { ! this.cOutCode = outcode3; ! this.gOutCode &= sideCode; // keep last point coordinate before entering the clip again: this.outside = true; ! this.cox0 = xe; ! this.coy0 = ye; clip(sideCode, outcode0, outcode3); return; } } + + this.cOutCode = outcode3; + this.gOutCode = 0; + if (outside) { finish(); } // clipping disabled: out.curveTo(x1, y1, x2, y2, xe, ye); + this.cx0 = xe; + this.cy0 = ye; } @Override public void quadTo(final double x1, final double y1, final double xe, final double ye) { final int outcode0 = this.cOutCode; + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); final int outcode2 = DHelpers.outcode(xe, ye, clipRect); ! // Should clip ! final int orCode = (outcode0 | outcode1 | outcode2); ! if (orCode != 0) { ! final int sideCode = outcode0 & outcode1 & outcode2; // basic rejection criteria: ! if (sideCode == 0) { ! // ovelap clip: ! if (subdivide) { ! // avoid reentrance ! subdivide = false; ! // subdivide curve => callback with subdivided parts: ! boolean ret; ! if (outside) { ! ret = curveSplitter.splitQuad(cox0, coy0, x1, y1, ! xe, ye, orCode, this); ! } else { ! ret = curveSplitter.splitQuad(cx0, cy0, x1, y1, ! xe, ye, orCode, this); ! } ! // reentrance is done: ! subdivide = true; ! if (ret) { ! return; ! } ! } ! // already subdivided so render it ! } else { ! this.cOutCode = outcode2; ! this.gOutCode &= sideCode; // keep last point coordinate before entering the clip again: this.outside = true; ! this.cox0 = xe; ! this.coy0 = ye; clip(sideCode, outcode0, outcode2); return; } } + + this.cOutCode = outcode2; + this.gOutCode = 0; + if (outside) { finish(); } // clipping disabled: out.quadTo(x1, y1, xe, ye); + this.cx0 = xe; + this.cy0 = ye; + } + } + + static final class CurveClipSplitter { + + static final double LEN_TH = MarlinProperties.getSubdividerMinLength(); + static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0d); + + private static final boolean TRACE = false; + + private static final int MAX_N_CURVES = 3 * 4; + + // clip rectangle (ymin, ymax, xmin, xmax): + final double[] clipRect; + + // clip rectangle (ymin, ymax, xmin, xmax) including padding: + final double[] clipRectPad = new double[4]; + private boolean init_clipRectPad = false; + + // This is where the curve to be processed is put. We give it + // enough room to store all curves. + final double[] middle = new double[MAX_N_CURVES * 8 + 2]; + // t values at subdivision points + private final double[] subdivTs = new double[MAX_N_CURVES]; + + // dirty curve + private final DCurve curve; + + CurveClipSplitter(final DRendererContext rdrCtx) { + this.clipRect = rdrCtx.clipRect; + this.curve = rdrCtx.curve; + } + + void init() { + this.init_clipRectPad = true; + } + + private void initPaddedClip() { + // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY + // adjust padded clip rectangle (ymin, ymax, xmin, xmax): + // add a rounding error (curve subdivision ~ 0.1px): + final double[] _clipRect = clipRect; + final double[] _clipRectPad = clipRectPad; + + _clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING; + _clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING; + _clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING; + _clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING; + + if (TRACE) { + MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] " + + "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]"); + } + } + + boolean splitLine(final double x0, final double y0, + final double x1, final double y1, + final int outCodeOR, + final DPathConsumer2D out) + { + if (TRACE) { + MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")"); + } + + if (DO_CHECK_LENGTH && DHelpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) { + return false; + } + + final double[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + + return subdivideAtIntersections(4, outCodeOR, out); + } + + boolean splitQuad(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2, + final int outCodeOR, + final DPathConsumer2D out) + { + if (TRACE) { + MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")"); + } + + if (DO_CHECK_LENGTH && DHelpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) { + return false; + } + + final double[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + + return subdivideAtIntersections(6, outCodeOR, out); + } + + boolean splitCurve(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3, + final int outCodeOR, + final DPathConsumer2D out) + { + if (TRACE) { + MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")"); + } + + if (DO_CHECK_LENGTH && DHelpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) { + return false; + } + + final double[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + mid[6] = x3; mid[7] = y3; + + return subdivideAtIntersections(8, outCodeOR, out); + } + + private boolean subdivideAtIntersections(final int type, final int outCodeOR, + final DPathConsumer2D out) + { + final double[] mid = middle; + final double[] subTs = subdivTs; + + if (init_clipRectPad) { + init_clipRectPad = false; + initPaddedClip(); + } + + final int nSplits = DHelpers.findClipPoints(curve, mid, subTs, type, + outCodeOR, clipRectPad); + + if (TRACE) { + MarlinUtils.logInfo("nSplits: "+ nSplits); + MarlinUtils.logInfo("subTs: " + Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits))); + } + if (nSplits == 0) { + // only curve support shortcut + return false; + } + double prevT = 0.0d; + + for (int i = 0, off = 0; i < nSplits; i++, off += type) { + final double t = subTs[i]; + + DHelpers.subdivideAt((t - prevT) / (1.0d - prevT), + mid, off, mid, off, type); + prevT = t; + } + + for (int i = 0, off = 0; i <= nSplits; i++, off += type) { + if (TRACE) { + MarlinUtils.logInfo("Part Curve " + Arrays.toString(Arrays.copyOfRange(mid, off, off + type))); + } + emitCurrent(type, mid, off, out); + } + return true; + } + + static void emitCurrent(final int type, final double[] pts, + final int off, final DPathConsumer2D out) + { + // if instead of switch (perf + most probable cases first) + if (type == 8) { + out.curveTo(pts[off + 2], pts[off + 3], + pts[off + 4], pts[off + 5], + pts[off + 6], pts[off + 7]); + } else if (type == 4) { + out.lineTo(pts[off + 2], pts[off + 3]); + } else { + out.quadTo(pts[off + 2], pts[off + 3], + pts[off + 4], pts[off + 5]); + } + } + } + + public static final class CurveBasicMonotonizer { + + private static final int MAX_N_CURVES = 11; + + // squared half line width (for stroker) + private double lw2; + + // number of splitted curves + int nbSplits; + + // This is where the curve to be processed is put. We give it + // enough room to store all curves. + final double[] middle = new double[MAX_N_CURVES * 6 + 2]; + // t values at subdivision points + private final double[] subdivTs = new double[MAX_N_CURVES - 1]; + + // dirty curve + private final DCurve curve; + + CurveBasicMonotonizer(final DRendererContext rdrCtx) { + this.curve = rdrCtx.curve; + } + + public void init(final double lineWidth) { + this.lw2 = (lineWidth * lineWidth) / 4.0d; + } + + CurveBasicMonotonizer curve(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3) + { + final double[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + mid[6] = x3; mid[7] = y3; + + final double[] subTs = subdivTs; + final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 8, lw2); + + double prevT = 0.0d; + for (int i = 0, off = 0; i < nSplits; i++, off += 6) { + final double t = subTs[i]; + + DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT), + mid, off, mid, off, off + 6); + prevT = t; + } + + this.nbSplits = nSplits; + return this; + } + + CurveBasicMonotonizer quad(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2) + { + final double[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + + final double[] subTs = subdivTs; + final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 6, lw2); + + double prevt = 0.0d; + for (int i = 0, off = 0; i < nSplits; i++, off += 4) { + final double t = subTs[i]; + DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt), + mid, off, mid, off, off + 4); + prevt = t; + } + + this.nbSplits = nSplits; + return this; } } static final class PathTracer implements DPathConsumer2D { private final String prefix;
*** 787,795 **** log("pathDone"); out.pathDone(); } private void log(final String message) { ! System.out.println(prefix + message); } } } --- 1146,1154 ---- log("pathDone"); out.pathDone(); } private void log(final String message) { ! MarlinUtils.logInfo(prefix + message); } } }
< prev index next >