--- old/src/java.desktop/share/classes/sun/java2d/marlin/Curve.java 2017-11-14 22:42:37.486329958 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Curve.java 2017-11-14 22:42:37.354327085 +0100 @@ -56,12 +56,16 @@ float x3, float y3, float x4, float y4) { - ax = 3.0f * (x2 - x3) + x4 - x1; - ay = 3.0f * (y2 - y3) + y4 - y1; - bx = 3.0f * (x1 - 2.0f * x2 + x3); - by = 3.0f * (y1 - 2.0f * y2 + y3); - cx = 3.0f * (x2 - x1); - cy = 3.0f * (y2 - y1); + final float dx32 = 3.0f * (x3 - x2); + final float dy32 = 3.0f * (y3 - y2); + final float dx21 = 3.0f * (x2 - x1); + final float dy21 = 3.0f * (y2 - y1); + ax = (x4 - x1) - dx32; + ay = (y4 - y1) - dy32; + bx = (dx32 - dx21); + by = (dy32 - dy21); + cx = dx21; + cy = dy21; dx = x1; dy = y1; dax = 3.0f * ax; day = 3.0f * ay; @@ -72,11 +76,13 @@ float x2, float y2, float x3, float y3) { + final float dx21 = (x2 - x1); + final float dy21 = (y2 - y1); ax = 0.0f; ay = 0.0f; - bx = x1 - 2.0f * x2 + x3; - by = y1 - 2.0f * y2 + y3; - cx = 2.0f * (x2 - x1); - cy = 2.0f * (y2 - y1); + bx = (x3 - x2) - dx21; + by = (y3 - y2) - dy21; + cx = 2.0f * dx21; + cy = 2.0f * dy21; dx = x1; dy = y1; dax = 0.0f; day = 0.0f; --- old/src/java.desktop/share/classes/sun/java2d/marlin/DCurve.java 2017-11-14 22:42:37.794336665 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DCurve.java 2017-11-14 22:42:37.662333790 +0100 @@ -56,12 +56,16 @@ double x3, double y3, double x4, double y4) { - ax = 3.0d * (x2 - x3) + x4 - x1; - ay = 3.0d * (y2 - y3) + y4 - y1; - bx = 3.0d * (x1 - 2.0d * x2 + x3); - by = 3.0d * (y1 - 2.0d * y2 + y3); - cx = 3.0d * (x2 - x1); - cy = 3.0d * (y2 - y1); + final double dx32 = 3.0d * (x3 - x2); + final double dy32 = 3.0d * (y3 - y2); + final double dx21 = 3.0d * (x2 - x1); + final double dy21 = 3.0d * (y2 - y1); + ax = (x4 - x1) - dx32; + ay = (y4 - y1) - dy32; + bx = (dx32 - dx21); + by = (dy32 - dy21); + cx = dx21; + cy = dy21; dx = x1; dy = y1; dax = 3.0d * ax; day = 3.0d * ay; @@ -72,11 +76,13 @@ double x2, double y2, double x3, double y3) { + final double dx21 = (x2 - x1); + final double dy21 = (y2 - y1); ax = 0.0d; ay = 0.0d; - bx = x1 - 2.0d * x2 + x3; - by = y1 - 2.0d * y2 + y3; - cx = 2.0d * (x2 - x1); - cy = 2.0d * (y2 - y1); + bx = (x3 - x2) - dx21; + by = (y3 - y2) - dy21; + cx = 2.0d * dx21; + cy = 2.0d * dy21; dx = x1; dy = y1; dax = 0.0d; day = 0.0d; --- old/src/java.desktop/share/classes/sun/java2d/marlin/DDasher.java 2017-11-14 22:42:38.106343457 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DDasher.java 2017-11-14 22:42:37.974340584 +0100 @@ -137,7 +137,7 @@ dashOn = !dashOn; } } - } else if (phase > 0) { + } else if (phase > 0.0d) { if (cycles >= MAX_CYCLES) { phase = 0.0d; } else { @@ -157,12 +157,13 @@ this.dash = dash; this.dashLen = dashLen; - this.startPhase = this.phase = phase; + this.phase = phase; + this.startPhase = phase; this.startDashOn = dashOn; this.startIdx = sidx; this.starting = true; - needsMoveTo = false; - firstSegidx = 0; + this.needsMoveTo = false; + this.firstSegidx = 0; this.recycleDashes = recycleDashes; @@ -201,8 +202,8 @@ } @Override - public void moveTo(double x0, double y0) { - if (firstSegidx > 0) { + public void moveTo(final double x0, final double y0) { + if (firstSegidx != 0) { out.moveTo(sx, sy); emitFirstSegments(); } @@ -210,8 +211,10 @@ this.idx = startIdx; this.dashOn = this.startDashOn; this.phase = this.startPhase; - this.sx = this.x0 = x0; - this.sy = this.y0 = y0; + this.sx = x0; + this.sy = y0; + this.x0 = x0; + this.y0 = y0; this.starting = true; } @@ -236,7 +239,7 @@ private void emitFirstSegments() { final double[] fSegBuf = firstSegmentsBuffer; - for (int i = 0; i < firstSegidx; ) { + for (int i = 0, len = firstSegidx; i < len; ) { int type = (int)fSegBuf[i]; emitSeg(fSegBuf, i + 1, type); i += (type - 1); @@ -251,48 +254,59 @@ private int firstSegidx; // precondition: pts must be in relative coordinates (relative to x0,y0) - private void goTo(double[] pts, int off, final int type) { - double x = pts[off + type - 4]; - double y = pts[off + type - 3]; - if (dashOn) { + private void goTo(final double[] pts, final int off, final int type, + final boolean on) + { + final int index = off + type; + final double x = pts[index - 4]; + final double y = pts[index - 3]; + + if (on) { if (starting) { - int len = type - 1; // - 2 + 1 - int segIdx = firstSegidx; - double[] buf = firstSegmentsBuffer; - if (segIdx + len > buf.length) { - if (DO_STATS) { - rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer - .add(segIdx + len); - } - firstSegmentsBuffer = buf - = firstSegmentsBuffer_ref.widenArray(buf, segIdx, - segIdx + len); - } - buf[segIdx++] = type; - len--; - // small arraycopy (2, 4 or 6) but with offset: - System.arraycopy(pts, off, buf, segIdx, len); - segIdx += len; - firstSegidx = segIdx; + goTo_starting(pts, off, type); } else { if (needsMoveTo) { - out.moveTo(x0, y0); needsMoveTo = false; + out.moveTo(x0, y0); } emitSeg(pts, off, type); } } else { - starting = false; + if (starting) { + // low probability test (hotspot) + starting = false; + } needsMoveTo = true; } this.x0 = x; this.y0 = y; } + private void goTo_starting(final double[] pts, final int off, final int type) { + int len = type - 1; // - 2 + 1 + int segIdx = firstSegidx; + double[] buf = firstSegmentsBuffer; + + if (segIdx + len > buf.length) { + if (DO_STATS) { + rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer + .add(segIdx + len); + } + firstSegmentsBuffer = buf + = firstSegmentsBuffer_ref.widenArray(buf, segIdx, + segIdx + len); + } + buf[segIdx++] = type; + len--; + // small arraycopy (2, 4 or 6) but with offset: + System.arraycopy(pts, off, buf, segIdx, len); + firstSegidx = segIdx + len; + } + @Override - public void lineTo(double x1, double y1) { - double dx = x1 - x0; - double dy = y1 - y0; + public void lineTo(final double x1, final double y1) { + final double dx = x1 - x0; + final double dy = y1 - y0; double len = dx*dx + dy*dy; if (len == 0.0d) { @@ -307,48 +321,61 @@ final double[] _curCurvepts = curCurvepts; final double[] _dash = dash; + final int _dashLen = this.dashLen; + + int _idx = idx; + boolean _dashOn = dashOn; + double _phase = phase; double leftInThisDashSegment; - double dashdx, dashdy, p; + double d, dashdx, dashdy, p; while (true) { - leftInThisDashSegment = _dash[idx] - phase; + d = _dash[_idx]; + leftInThisDashSegment = d - _phase; if (len <= leftInThisDashSegment) { _curCurvepts[0] = x1; _curCurvepts[1] = y1; - goTo(_curCurvepts, 0, 4); + + goTo(_curCurvepts, 0, 4, _dashOn); // Advance phase within current dash segment - phase += len; + _phase += len; + // TODO: compare double values using epsilon: if (len == leftInThisDashSegment) { - phase = 0.0d; - idx = (idx + 1) % dashLen; - dashOn = !dashOn; + _phase = 0.0d; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; } + + // Save local state: + idx = _idx; + dashOn = _dashOn; + phase = _phase; return; } - dashdx = _dash[idx] * cx; - dashdy = _dash[idx] * cy; + dashdx = d * cx; + dashdy = d * cy; - if (phase == 0.0d) { + if (_phase == 0.0d) { _curCurvepts[0] = x0 + dashdx; _curCurvepts[1] = y0 + dashdy; } else { - p = leftInThisDashSegment / _dash[idx]; + p = leftInThisDashSegment / d; _curCurvepts[0] = x0 + p * dashdx; _curCurvepts[1] = y0 + p * dashdy; } - goTo(_curCurvepts, 0, 4); + goTo(_curCurvepts, 0, 4, _dashOn); len -= leftInThisDashSegment; // Advance to next dash segment - idx = (idx + 1) % dashLen; - dashOn = !dashOn; - phase = 0.0d; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + _phase = 0.0d; } } @@ -361,39 +388,55 @@ if (pointCurve(curCurvepts, type)) { return; } - li.initializeIterationOnCurve(curCurvepts, type); + final LengthIterator _li = li; + final double[] _curCurvepts = curCurvepts; + final double[] _dash = dash; + final int _dashLen = this.dashLen; + + _li.initializeIterationOnCurve(_curCurvepts, type); + + int _idx = idx; + boolean _dashOn = dashOn; + double _phase = phase; // initially the current curve is at curCurvepts[0...type] int curCurveoff = 0; double lastSplitT = 0.0d; double t; - double leftInThisDashSegment = dash[idx] - phase; + double leftInThisDashSegment = _dash[_idx] - _phase; - while ((t = li.next(leftInThisDashSegment)) < 1.0d) { + while ((t = _li.next(leftInThisDashSegment)) < 1.0d) { if (t != 0.0d) { DHelpers.subdivideAt((t - lastSplitT) / (1.0d - lastSplitT), - curCurvepts, curCurveoff, - curCurvepts, 0, - curCurvepts, type, type); + _curCurvepts, curCurveoff, + _curCurvepts, 0, + _curCurvepts, type, type); lastSplitT = t; - goTo(curCurvepts, 2, type); + goTo(_curCurvepts, 2, type, _dashOn); curCurveoff = type; } // Advance to next dash segment - idx = (idx + 1) % dashLen; - dashOn = !dashOn; - phase = 0.0d; - leftInThisDashSegment = dash[idx]; - } - goTo(curCurvepts, curCurveoff+2, type); - phase += li.lastSegLen(); - if (phase >= dash[idx]) { - phase = 0.0d; - idx = (idx + 1) % dashLen; - dashOn = !dashOn; - } + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + _phase = 0.0d; + leftInThisDashSegment = _dash[_idx]; + } + + goTo(_curCurvepts, curCurveoff + 2, type, _dashOn); + + _phase += _li.lastSegLen(); + if (_phase >= _dash[_idx]) { + _phase = 0.0d; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + } + // Save local state: + idx = _idx; + dashOn = _dashOn; + phase = _phase; + // reset LengthIterator: - li.reset(); + _li.reset(); } private static boolean pointCurve(double[] curve, int type) { @@ -419,7 +462,7 @@ // tree; however, the trees we are interested in have the property that // every non leaf node has exactly 2 children static final class LengthIterator { - private enum Side {LEFT, RIGHT}; + private enum Side {LEFT, RIGHT} // Holds the curves at various levels of the recursion. The root // (i.e. the original curve) is at recCurveStack[0] (but then it // gets subdivided, the left half is put at 1, so most of the time @@ -669,11 +712,12 @@ // this is a bit of a hack. It returns -1 if we're not on a leaf, and // the length of the leaf if we are on a leaf. private double onLeaf() { - double[] curve = recCurveStack[recLevel]; + final double[] curve = recCurveStack[recLevel]; + final int _curveType = curveType; double polyLen = 0.0d; double x0 = curve[0], y0 = curve[1]; - for (int i = 2; i < curveType; i += 2) { + for (int i = 2; i < _curveType; i += 2) { final double x1 = curve[i], y1 = curve[i+1]; final double len = DHelpers.linelen(x0, y0, x1, y1); polyLen += len; @@ -683,8 +727,8 @@ } final double lineLen = DHelpers.linelen(curve[0], curve[1], - curve[curveType-2], - curve[curveType-1]); + curve[_curveType-2], + curve[_curveType-1]); if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) { return (polyLen + lineLen) / 2.0d; } @@ -693,9 +737,9 @@ } @Override - public void curveTo(double x1, double y1, - double x2, double y2, - double x3, double y3) + public void curveTo(final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3) { final double[] _curCurvepts = curCurvepts; _curCurvepts[0] = x0; _curCurvepts[1] = y0; @@ -706,7 +750,9 @@ } @Override - public void quadTo(double x1, double y1, double x2, double y2) { + public void quadTo(final double x1, final double y1, + final double x2, final double y2) + { final double[] _curCurvepts = curCurvepts; _curCurvepts[0] = x0; _curCurvepts[1] = y0; _curCurvepts[2] = x1; _curCurvepts[3] = y1; @@ -717,7 +763,7 @@ @Override public void closePath() { lineTo(sx, sy); - if (firstSegidx > 0) { + if (firstSegidx != 0) { if (!dashOn || needsMoveTo) { out.moveTo(sx, sy); } @@ -728,7 +774,7 @@ @Override public void pathDone() { - if (firstSegidx > 0) { + if (firstSegidx != 0) { out.moveTo(sx, sy); emitFirstSegments(); } --- old/src/java.desktop/share/classes/sun/java2d/marlin/DHelpers.java 2017-11-14 22:42:38.426350423 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DHelpers.java 2017-11-14 22:42:38.294347550 +0100 @@ -26,10 +26,10 @@ package sun.java2d.marlin; import static java.lang.Math.PI; -import static java.lang.Math.cos; -import static java.lang.Math.sqrt; -import static java.lang.Math.cbrt; -import static java.lang.Math.acos; +import java.util.Arrays; +import static sun.java2d.marlin.MarlinConst.INITIAL_EDGES_COUNT; +import sun.java2d.marlin.stats.Histogram; +import sun.java2d.marlin.stats.StatLong; final class DHelpers implements MarlinConst { @@ -115,17 +115,17 @@ int num; if (D < 0.0d) { // see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method - final double phi = (1.0d/3.0d) * acos(-q / sqrt(-cb_p)); - final double t = 2.0d * sqrt(-p); + final double phi = (1.0d/3.0d) * Math.acos(-q / Math.sqrt(-cb_p)); + final double t = 2.0d * Math.sqrt(-p); - pts[ off+0 ] = ( t * cos(phi)); - pts[ off+1 ] = (-t * cos(phi + (PI / 3.0d))); - pts[ off+2 ] = (-t * cos(phi - (PI / 3.0d))); + pts[ off+0 ] = ( t * Math.cos(phi)); + pts[ off+1 ] = (-t * Math.cos(phi + (PI / 3.0d))); + pts[ off+2 ] = (-t * Math.cos(phi - (PI / 3.0d))); num = 3; } else { - final double sqrt_D = sqrt(D); - final double u = cbrt(sqrt_D - q); - final double v = - cbrt(sqrt_D + q); + final double sqrt_D = Math.sqrt(D); + final double u = Math.cbrt(sqrt_D - q); + final double v = - Math.cbrt(sqrt_D + q); pts[ off ] = (u + v); num = 1; @@ -171,15 +171,6 @@ return ret; } - static double polyLineLength(double[] poly, final int off, final int nCoords) { - assert nCoords % 2 == 0 && poly.length >= off + nCoords : ""; - double acc = 0.0d; - for (int i = off + 2; i < off + nCoords; i += 2) { - acc += linelen(poly[i], poly[i+1], poly[i-2], poly[i-1]); - } - return acc; - } - static double linelen(double x1, double y1, double x2, double y2) { final double dx = x2 - x1; final double dy = y2 - y1; @@ -433,4 +424,388 @@ return; } } + + // From sun.java2d.loops.GeneralRenderer: + + static int outcode(final double x, final double y, + final double[] clipRect) + { + int code; + if (y < clipRect[0]) { + code = OUTCODE_TOP; + } else if (y >= clipRect[1]) { + code = OUTCODE_BOTTOM; + } else { + code = 0; + } + if (x < clipRect[2]) { + code |= OUTCODE_LEFT; + } else if (x >= clipRect[3]) { + code |= OUTCODE_RIGHT; + } + return code; + } + + // a stack of polynomial curves where each curve shares endpoints with + // adjacent ones. + static final class PolyStack { + private static final byte TYPE_LINETO = (byte) 0; + private static final byte TYPE_QUADTO = (byte) 1; + private static final byte TYPE_CUBICTO = (byte) 2; + + // curves capacity = edges count (8192) = edges x 2 (coords) + private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1; + + // types capacity = edges count (4096) + private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT; + + double[] curves; + int end; + byte[] curveTypes; + int numCurves; + + // curves ref (dirty) + final DoubleArrayCache.Reference curves_ref; + // curveTypes ref (dirty) + final ByteArrayCache.Reference curveTypes_ref; + + // used marks (stats only) + int curveTypesUseMark; + int curvesUseMark; + + private final StatLong stat_polystack_types; + private final StatLong stat_polystack_curves; + private final Histogram hist_polystack_curves; + private final StatLong stat_array_polystack_curves; + private final StatLong stat_array_polystack_curveTypes; + + PolyStack(final DRendererContext rdrCtx) { + this(rdrCtx, null, null, null, null, null); + } + + PolyStack(final DRendererContext rdrCtx, + final StatLong stat_polystack_types, + final StatLong stat_polystack_curves, + final Histogram hist_polystack_curves, + final StatLong stat_array_polystack_curves, + final StatLong stat_array_polystack_curveTypes) + { + curves_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_CURVES_COUNT); // 32K + curves = curves_ref.initial; + + curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K + curveTypes = curveTypes_ref.initial; + numCurves = 0; + end = 0; + + if (DO_STATS) { + curveTypesUseMark = 0; + curvesUseMark = 0; + } + this.stat_polystack_types = stat_polystack_types; + this.stat_polystack_curves = stat_polystack_curves; + this.hist_polystack_curves = hist_polystack_curves; + this.stat_array_polystack_curves = stat_array_polystack_curves; + this.stat_array_polystack_curveTypes = stat_array_polystack_curveTypes; + } + + /** + * Disposes this PolyStack: + * clean up before reusing this instance + */ + void dispose() { + end = 0; + numCurves = 0; + + if (DO_STATS) { + stat_polystack_types.add(curveTypesUseMark); + stat_polystack_curves.add(curvesUseMark); + hist_polystack_curves.add(curvesUseMark); + + // reset marks + curveTypesUseMark = 0; + curvesUseMark = 0; + } + + // Return arrays: + // curves and curveTypes are kept dirty + curves = curves_ref.putArray(curves); + curveTypes = curveTypes_ref.putArray(curveTypes); + } + + private void ensureSpace(final int n) { + // use substraction to avoid integer overflow: + if (curves.length - end < n) { + if (DO_STATS) { + stat_array_polystack_curves.add(end + n); + } + curves = curves_ref.widenArray(curves, end, end + n); + } + if (curveTypes.length <= numCurves) { + if (DO_STATS) { + stat_array_polystack_curveTypes.add(numCurves + 1); + } + curveTypes = curveTypes_ref.widenArray(curveTypes, + numCurves, + numCurves + 1); + } + } + + void pushCubic(double x0, double y0, + double x1, double y1, + double x2, double y2) + { + ensureSpace(6); + curveTypes[numCurves++] = TYPE_CUBICTO; + // we reverse the coordinate order to make popping easier + final double[] _curves = curves; + int e = end; + _curves[e++] = x2; _curves[e++] = y2; + _curves[e++] = x1; _curves[e++] = y1; + _curves[e++] = x0; _curves[e++] = y0; + end = e; + } + + void pushQuad(double x0, double y0, + double x1, double y1) + { + ensureSpace(4); + curveTypes[numCurves++] = TYPE_QUADTO; + final double[] _curves = curves; + int e = end; + _curves[e++] = x1; _curves[e++] = y1; + _curves[e++] = x0; _curves[e++] = y0; + end = e; + } + + void pushLine(double x, double y) { + ensureSpace(2); + curveTypes[numCurves++] = TYPE_LINETO; + curves[end++] = x; curves[end++] = y; + } + + void pullAll(final DPathConsumer2D io) { + final int nc = numCurves; + if (nc == 0) { + return; + } + if (DO_STATS) { + // update used marks: + if (numCurves > curveTypesUseMark) { + curveTypesUseMark = numCurves; + } + if (end > curvesUseMark) { + curvesUseMark = end; + } + } + final byte[] _curveTypes = curveTypes; + final double[] _curves = curves; + int e = 0; + + for (int i = 0; i < nc; i++) { + switch(_curveTypes[i]) { + case TYPE_LINETO: + io.lineTo(_curves[e], _curves[e+1]); + e += 2; + continue; + case TYPE_QUADTO: + io.quadTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3]); + e += 4; + continue; + case TYPE_CUBICTO: + io.curveTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3], + _curves[e+4], _curves[e+5]); + e += 6; + continue; + default: + } + } + numCurves = 0; + end = 0; + } + + void popAll(final DPathConsumer2D io) { + int nc = numCurves; + if (nc == 0) { + return; + } + if (DO_STATS) { + // update used marks: + if (numCurves > curveTypesUseMark) { + curveTypesUseMark = numCurves; + } + if (end > curvesUseMark) { + curvesUseMark = end; + } + } + final byte[] _curveTypes = curveTypes; + final double[] _curves = curves; + int e = end; + + while (nc != 0) { + switch(_curveTypes[--nc]) { + case TYPE_LINETO: + e -= 2; + io.lineTo(_curves[e], _curves[e+1]); + continue; + case TYPE_QUADTO: + e -= 4; + io.quadTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3]); + continue; + case TYPE_CUBICTO: + e -= 6; + io.curveTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3], + _curves[e+4], _curves[e+5]); + continue; + default: + } + } + numCurves = 0; + end = 0; + } + + @Override + public String toString() { + String ret = ""; + int nc = numCurves; + int last = end; + int len; + while (nc != 0) { + switch(curveTypes[--nc]) { + case TYPE_LINETO: + len = 2; + ret += "line: "; + break; + case TYPE_QUADTO: + len = 4; + ret += "quad: "; + break; + case TYPE_CUBICTO: + len = 6; + ret += "cubic: "; + break; + default: + len = 0; + } + last -= len; + ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len)) + + "\n"; + } + return ret; + } + } + + // 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; + } + } } --- old/src/java.desktop/share/classes/sun/java2d/marlin/DMarlinRenderingEngine.java 2017-11-14 22:42:38.750357476 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DMarlinRenderingEngine.java 2017-11-14 22:42:38.618354603 +0100 @@ -84,6 +84,13 @@ static final double UPPER_BND = Float.MAX_VALUE / 2.0d; static final double LOWER_BND = -UPPER_BND; + static final boolean DO_CLIP = MarlinProperties.isDoClip(); + static final boolean DO_CLIP_FILL = true; + + static final boolean DO_TRACE_PATH = false; + + static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag(); + /** * Public constructor */ @@ -133,7 +140,7 @@ miterlimit, dashes, dashphase, - rdrCtx.transformerPC2D.wrapPath2d(p2d) + rdrCtx.transformerPC2D.wrapPath2D(p2d) ); // Use Path2D copy constructor (trim) @@ -195,14 +202,14 @@ } } - final void strokeTo(final DRendererContext rdrCtx, - Shape src, - AffineTransform at, - BasicStroke bs, - boolean thin, - NormMode normalize, - boolean antialias, - DPathConsumer2D pc2d) + void strokeTo(final DRendererContext rdrCtx, + Shape src, + AffineTransform at, + BasicStroke bs, + boolean thin, + NormMode normalize, + boolean antialias, + DPathConsumer2D pc2d) { double lw; if (thin) { @@ -227,7 +234,7 @@ pc2d); } - private final double userSpaceLineWidth(AffineTransform at, double lw) { + private double userSpaceLineWidth(AffineTransform at, double lw) { double widthScale; @@ -295,17 +302,17 @@ return (lw / widthScale); } - final void strokeTo(final DRendererContext rdrCtx, - Shape src, - AffineTransform at, - double width, - NormMode norm, - int caps, - int join, - float miterlimit, - float[] dashes, - float dashphase, - DPathConsumer2D pc2d) + void strokeTo(final DRendererContext rdrCtx, + Shape src, + AffineTransform at, + double width, + NormMode norm, + int caps, + int join, + float miterlimit, + float[] dashes, + float dashphase, + DPathConsumer2D pc2d) { // We use strokerat so that in Stroker and Dasher we can work only // with the pre-transformation coordinates. This will repeat a lot of @@ -324,6 +331,7 @@ int dashLen = -1; boolean recycleDashes = false; + double scale = 1.0d; double[] dashesD = null; // Ensure converting dashes to double precision: @@ -364,7 +372,7 @@ // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we // leave a bit of room for error. if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { - final double scale = Math.sqrt(a*a + c*c); + scale = Math.sqrt(a*a + c*c); if (dashesD != null) { for (int i = 0; i < dashLen; i++) { @@ -399,23 +407,44 @@ at = null; } + final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; + + if (DO_TRACE_PATH) { + // trace Stroker: + pc2d = transformerPC2D.traceStroker(pc2d); + } + if (USE_SIMPLIFIER) { // Use simplifier after stroker before Renderer // to remove collinear segments (notably due to cap square) pc2d = rdrCtx.simplifier.init(pc2d); } - final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; + // deltaTransformConsumer may adjust the clip rectangle: pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat); - pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit); + // stroker will adjust the clip rectangle (width / miter limit): + pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale); if (dashesD != null) { pc2d = rdrCtx.dasher.init(pc2d, dashesD, dashLen, dashphase, recycleDashes); + } else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) { + if (DO_TRACE_PATH) { + pc2d = transformerPC2D.traceClosedPathDetector(pc2d); + } + + // If no dash and clip is enabled: + // detect closedPaths (polygons) for caps + pc2d = transformerPC2D.detectClosedPath(pc2d); } pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat); + if (DO_TRACE_PATH) { + // trace Input: + pc2d = transformerPC2D.traceInput(pc2d); + } + final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx, src.getPathIterator(at)); @@ -781,6 +810,19 @@ final DRendererContext rdrCtx = getRendererContext(); try { + if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) { + // Define the initial clip bounds: + final double[] clipRect = rdrCtx.clipRect; + + clipRect[0] = clip.getLoY(); + clipRect[1] = clip.getLoY() + clip.getHeight(); + clipRect[2] = clip.getLoX(); + clipRect[3] = clip.getLoX() + clip.getWidth(); + + // Enable clipping: + rdrCtx.doClip = true; + } + // Test if at is identity: final AffineTransform _at = (at != null && !at.isIdentity()) ? at : null; @@ -792,18 +834,36 @@ final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx, s.getPathIterator(_at)); + final int windingRule = pi.getWindingRule(); + // note: Winding rule may be EvenOdd ONLY for fill operations ! r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), clip.getWidth(), clip.getHeight(), - pi.getWindingRule()); + windingRule); + + DPathConsumer2D pc2d = r; + + if (DO_CLIP_FILL && rdrCtx.doClip) { + if (DO_TRACE_PATH) { + // trace Filler: + pc2d = rdrCtx.transformerPC2D.traceFiller(pc2d); + } + pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d); + } + + if (DO_TRACE_PATH) { + // trace Input: + pc2d = rdrCtx.transformerPC2D.traceInput(pc2d); + } // TODO: subdivide quad/cubic curves into monotonic curves ? - pathTo(rdrCtx, pi, r); + pathTo(rdrCtx, pi, pc2d); + } else { // draw shape with given stroke: r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), clip.getWidth(), clip.getHeight(), - PathIterator.WIND_NON_ZERO); + WIND_NON_ZERO); strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r); } @@ -826,12 +886,12 @@ } @Override - public final AATileGenerator getAATileGenerator(double x, double y, - double dx1, double dy1, - double dx2, double dy2, - double lw1, double lw2, - Region clip, - int[] bbox) + public AATileGenerator getAATileGenerator(double x, double y, + double dx1, double dy1, + double dx2, double dy2, + double lw1, double lw2, + Region clip, + int[] bbox) { // REMIND: Deal with large coordinates! double ldx1, ldy1, ldx2, ldy2; @@ -862,8 +922,8 @@ final DRendererContext rdrCtx = getRendererContext(); try { r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), - clip.getWidth(), clip.getHeight(), - DRenderer.WIND_EVEN_ODD); + clip.getWidth(), clip.getHeight(), + WIND_EVEN_ODD); r.moveTo( x, y); r.lineTo( (x+dx1), (y+dy1)); @@ -915,14 +975,14 @@ } static { - if (PathIterator.WIND_NON_ZERO != DRenderer.WIND_NON_ZERO || - PathIterator.WIND_EVEN_ODD != DRenderer.WIND_EVEN_ODD || - BasicStroke.JOIN_MITER != DStroker.JOIN_MITER || - BasicStroke.JOIN_ROUND != DStroker.JOIN_ROUND || - BasicStroke.JOIN_BEVEL != DStroker.JOIN_BEVEL || - BasicStroke.CAP_BUTT != DStroker.CAP_BUTT || - BasicStroke.CAP_ROUND != DStroker.CAP_ROUND || - BasicStroke.CAP_SQUARE != DStroker.CAP_SQUARE) + if (PathIterator.WIND_NON_ZERO != WIND_NON_ZERO || + PathIterator.WIND_EVEN_ODD != WIND_EVEN_ODD || + BasicStroke.JOIN_MITER != JOIN_MITER || + BasicStroke.JOIN_ROUND != JOIN_ROUND || + BasicStroke.JOIN_BEVEL != JOIN_BEVEL || + BasicStroke.CAP_BUTT != CAP_BUTT || + BasicStroke.CAP_ROUND != CAP_ROUND || + BasicStroke.CAP_SQUARE != CAP_SQUARE) { throw new InternalError("mismatched renderer constants"); } @@ -1044,6 +1104,8 @@ // optimisation parameters logInfo("sun.java2d.renderer.useSimplifier = " + MarlinConst.USE_SIMPLIFIER); + logInfo("sun.java2d.renderer.clip = " + + MarlinProperties.isDoClip()); // debugging parameters logInfo("sun.java2d.renderer.doStats = " --- old/src/java.desktop/share/classes/sun/java2d/marlin/DRenderer.java 2017-11-14 22:42:39.070364441 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DRenderer.java 2017-11-14 22:42:38.942361656 +0100 @@ -46,6 +46,9 @@ static final int SUBPIXEL_MASK_X = SUBPIXEL_POSITIONS_X - 1; static final int SUBPIXEL_MASK_Y = SUBPIXEL_POSITIONS_Y - 1; + static final double RDR_OFFSET_X = 0.5d / SUBPIXEL_SCALE_X; + static final double RDR_OFFSET_Y = 0.5d / SUBPIXEL_SCALE_Y; + // number of subpixels corresponding to a tile line private static final int SUBPIXEL_TILE = TILE_H << SUBPIXEL_LG_POSITIONS_Y; @@ -57,9 +60,6 @@ // crossing capacity = edges count / 4 ~ 1024 static final int INITIAL_CROSSING_COUNT = INITIAL_EDGES_COUNT >> 2; - public static final int WIND_EVEN_ODD = 0; - public static final int WIND_NON_ZERO = 1; - // common to all types of input path segments. // OFFSET as bytes // only integer values: @@ -668,7 +668,7 @@ } @Override - public void moveTo(double pix_x0, double pix_y0) { + public void moveTo(final double pix_x0, final double pix_y0) { closePath(); final double sx = tosubpixx(pix_x0); final double sy = tosubpixy(pix_y0); @@ -679,7 +679,7 @@ } @Override - public void lineTo(double pix_x1, double pix_y1) { + public void lineTo(final double pix_x1, final double pix_y1) { final double x1 = tosubpixx(pix_x1); final double y1 = tosubpixy(pix_y1); addLine(x0, y0, x1, y1); @@ -688,24 +688,26 @@ } @Override - public void curveTo(double x1, double y1, - double x2, double y2, - double x3, double y3) + public void curveTo(final double pix_x1, final double pix_y1, + final double pix_x2, final double pix_y2, + final double pix_x3, final double pix_y3) { - final double xe = tosubpixx(x3); - final double ye = tosubpixy(y3); - curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), - tosubpixx(x2), tosubpixy(y2), xe, ye); + final double xe = tosubpixx(pix_x3); + final double ye = tosubpixy(pix_y3); + curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), + tosubpixx(pix_x2), tosubpixy(pix_y2), xe, ye); curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; } @Override - public void quadTo(double x1, double y1, double x2, double y2) { - final double xe = tosubpixx(x2); - final double ye = tosubpixy(y2); - curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), xe, ye); + public void quadTo(final double pix_x1, final double pix_y1, + final double pix_x2, final double pix_y2) + { + final double xe = tosubpixx(pix_x2); + final double ye = tosubpixy(pix_y2); + curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), xe, ye); quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; @@ -713,9 +715,11 @@ @Override public void closePath() { - addLine(x0, y0, sx0, sy0); - x0 = sx0; - y0 = sy0; + if (x0 != sx0 || y0 != sy0) { + addLine(x0, y0, sx0, sy0); + x0 = sx0; + y0 = sy0; + } } @Override --- old/src/java.desktop/share/classes/sun/java2d/marlin/DRendererContext.java 2017-11-14 22:42:39.386371318 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DRendererContext.java 2017-11-14 22:42:39.258368533 +0100 @@ -75,16 +75,22 @@ final MarlinCache cache; // flag indicating the shape is stroked (1) or filled (0) int stroking = 0; + // flag indicating to clip the shape + boolean doClip = false; + // flag indicating if the path is closed or not (in advance) to handle properly caps + boolean closedPath = false; + // clip rectangle (ymin, ymax, xmin, xmax): + final double[] clipRect = new double[4]; // Array caches: /* clean int[] cache (zero-filled) = 5 refs */ private final IntArrayCache cleanIntCache = new IntArrayCache(true, 5); - /* dirty int[] cache = 4 refs */ - private final IntArrayCache dirtyIntCache = new IntArrayCache(false, 4); - /* dirty double[] cache = 3 refs */ - private final DoubleArrayCache dirtyDoubleCache = new DoubleArrayCache(false, 3); - /* dirty byte[] cache = 1 ref */ - private final ByteArrayCache dirtyByteCache = new ByteArrayCache(false, 1); + /* dirty int[] cache = 5 refs */ + private final IntArrayCache dirtyIntCache = new IntArrayCache(false, 5); + /* dirty double[] cache = 4 refs (2 polystack) */ + private final DoubleArrayCache dirtyDoubleCache = new DoubleArrayCache(false, 4); + /* dirty byte[] cache = 2 ref (2 polystack) */ + private final ByteArrayCache dirtyByteCache = new ByteArrayCache(false, 2); // RendererContext statistics final RendererStats stats; @@ -119,7 +125,7 @@ nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(double6); // MarlinRenderingEngine.TransformingPathConsumer2D - transformerPC2D = new DTransformingPathConsumer2D(); + transformerPC2D = new DTransformingPathConsumer2D(this); // Renderer: cache = new MarlinCache(this); @@ -141,7 +147,10 @@ } stats.totalOffHeap = 0L; } - stroking = 0; + stroking = 0; + doClip = false; + closedPath = false; + // if context is maked as DIRTY: if (dirty) { // may happen if an exception if thrown in the pipeline processing: @@ -167,7 +176,7 @@ // create a new Path2D ? if (p2d == null) { - p2d = new Path2D.Double(Path2D.WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K + p2d = new Path2D.Double(WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K // update weak reference: refPath2D = new WeakReference(p2d); --- old/src/java.desktop/share/classes/sun/java2d/marlin/DStroker.java 2017-11-14 22:42:39.702378195 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DStroker.java 2017-11-14 22:42:39.566375236 +0100 @@ -26,6 +26,7 @@ package sun.java2d.marlin; import java.util.Arrays; +import sun.java2d.marlin.DHelpers.PolyStack; // TODO: some of the arithmetic here is too verbose and prone to hard to // debug typos. We should consider making a small Point/Vector class that @@ -36,42 +37,16 @@ private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad private static final int CLOSE = 2; - /** - * Constant value for join style. - */ - public static final int JOIN_MITER = 0; - - /** - * Constant value for join style. - */ - public static final int JOIN_ROUND = 1; - - /** - * Constant value for join style. - */ - public static final int JOIN_BEVEL = 2; - - /** - * Constant value for end cap style. - */ - public static final int CAP_BUTT = 0; - - /** - * Constant value for end cap style. - */ - public static final int CAP_ROUND = 1; - - /** - * Constant value for end cap style. - */ - public static final int CAP_SQUARE = 2; - // pisces used to use fixed point arithmetic with 16 decimal digits. I // didn't want to change the values of the constant below when I converted // it to floating point, so that's why the divisions by 2^16 are there. private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d; - private static final double C = 0.5522847498307933d; + // kappa = (4/3) * (SQRT(2) - 1) + private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d); + + // SQRT(2) + private static final double SQRT_2 = Math.sqrt(2.0d); private static final int MAX_N_CURVES = 11; @@ -118,6 +93,20 @@ // dirty curve final DCurve curve; + // Bounds of the drawing region, at pixel precision. + private double[] clipRect; + + // the outcode of the current point + private int cOutCode = 0; + + // the outcode of the starting point + private int sOutCode = 0; + + // flag indicating if the path is opened (clipped) + private boolean opened = false; + // flag indicating if the starting point's cap is done + private boolean capStart = false; + /** * Constructs a DStroker. * @param rdrCtx per-thread renderer context @@ -125,7 +114,15 @@ DStroker(final DRendererContext rdrCtx) { this.rdrCtx = rdrCtx; - this.reverse = new PolyStack(rdrCtx); + this.reverse = (rdrCtx.stats != null) ? + new PolyStack(rdrCtx, + rdrCtx.stats.stat_str_polystack_types, + rdrCtx.stats.stat_str_polystack_curves, + rdrCtx.stats.hist_str_polystack_curves, + rdrCtx.stats.stat_array_str_polystack_curves, + rdrCtx.stats.stat_array_str_polystack_types) + : new PolyStack(rdrCtx); + this.curve = rdrCtx.curve; } @@ -141,13 +138,15 @@ * JOIN_MITER, JOIN_ROUND or * JOIN_BEVEL. * @param miterLimit the desired miter limit + * @param scale scaling factor applied to clip boundaries * @return this instance */ - DStroker init(DPathConsumer2D pc2d, - double lineWidth, - int capStyle, - int joinStyle, - double miterLimit) + DStroker init(final DPathConsumer2D pc2d, + final double lineWidth, + final int capStyle, + final int joinStyle, + final double miterLimit, + final double scale) { this.out = pc2d; @@ -156,13 +155,45 @@ this.capStyle = capStyle; this.joinStyle = joinStyle; - double limit = miterLimit * lineWidth2; + final double limit = miterLimit * lineWidth2; this.miterLimitSq = limit * limit; this.prev = CLOSE; rdrCtx.stroking = 1; + if (rdrCtx.doClip) { + // Adjust the clipping rectangle with the stroker margin (miter limit, width) + double rdrOffX = 0.0d, rdrOffY = 0.0d; + double margin = lineWidth2; + + if (capStyle == CAP_SQUARE) { + margin *= SQRT_2; + } + if ((joinStyle == JOIN_MITER) && (margin < limit)) { + margin = limit; + } + if (scale != 1.0d) { + margin *= scale; + rdrOffX = scale * DRenderer.RDR_OFFSET_X; + rdrOffY = scale * DRenderer.RDR_OFFSET_Y; + } + // add a small rounding error: + margin += 1e-3d; + + // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY + // adjust clip rectangle (ymin, ymax, xmin, xmax): + final double[] _clipRect = rdrCtx.clipRect; + _clipRect[0] -= margin - rdrOffY; + _clipRect[1] += margin + rdrOffY; + _clipRect[2] -= margin - rdrOffX; + _clipRect[3] += margin + rdrOffX; + this.clipRect = _clipRect; + } else { + this.clipRect = null; + this.cOutCode = 0; + this.sOutCode = 0; + } return this; // fluent API } @@ -173,6 +204,9 @@ void dispose() { reverse.dispose(); + opened = false; + capStart = false; + if (DO_CLEAN_DIRTY) { // Force zero-fill dirty arrays: Arrays.fill(offset0, 0.0d); @@ -443,19 +477,62 @@ } @Override - public void moveTo(double x0, double y0) { - if (prev == DRAWING_OP_TO) { - finish(); + public void moveTo(final double x0, final double y0) { + moveTo(x0, y0, cOutCode); + // update starting point: + this.sx0 = x0; + this.sy0 = y0; + this.sdx = 1.0d; + this.sdy = 0.0d; + this.opened = false; + this.capStart = false; + + if (clipRect != null) { + final int outcode = DHelpers.outcode(x0, y0, clipRect); + this.cOutCode = outcode; + this.sOutCode = outcode; + } + } + + private void moveTo(final double x0, final double y0, + final int outcode) + { + if (prev == MOVE_TO) { + this.cx0 = x0; + this.cy0 = y0; + } else { + if (prev == DRAWING_OP_TO) { + finish(outcode); + } + this.prev = MOVE_TO; + this.cx0 = x0; + this.cy0 = y0; + this.cdx = 1.0d; + this.cdy = 0.0d; } - this.sx0 = this.cx0 = x0; - this.sy0 = this.cy0 = y0; - this.cdx = this.sdx = 1.0d; - this.cdy = this.sdy = 0.0d; - this.prev = MOVE_TO; } @Override - public void lineTo(double x1, double y1) { + public void lineTo(final double x1, final double y1) { + lineTo(x1, y1, false); + } + + private void lineTo(final double x1, final double y1, + final boolean force) + { + final int outcode0 = this.cOutCode; + if (!force && clipRect != null) { + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); + this.cOutCode = outcode1; + + // basic rejection criteria + if ((outcode0 & outcode1) != 0) { + moveTo(x1, y1, outcode0); + opened = true; + return; + } + } + double dx = x1 - cx0; double dy = y1 - cy0; if (dx == 0.0d && dy == 0.0d) { @@ -465,7 +542,7 @@ final double mx = offset0[0]; final double my = offset0[1]; - drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my); + drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0); emitLineTo(cx0 + mx, cy0 + my); emitLineTo( x1 + mx, y1 + my); @@ -473,43 +550,65 @@ emitLineToRev(cx0 - mx, cy0 - my); emitLineToRev( x1 - mx, y1 - my); - this.cmx = mx; - this.cmy = my; - this.cdx = dx; - this.cdy = dy; + this.prev = DRAWING_OP_TO; this.cx0 = x1; this.cy0 = y1; - this.prev = DRAWING_OP_TO; + this.cdx = dx; + this.cdy = dy; + this.cmx = mx; + this.cmy = my; } @Override public void closePath() { - if (prev != DRAWING_OP_TO) { + // distinguish empty path at all vs opened path ? + if (prev != DRAWING_OP_TO && !opened) { if (prev == CLOSE) { return; } emitMoveTo(cx0, cy0 - lineWidth2); - this.cmx = this.smx = 0.0d; - this.cmy = this.smy = -lineWidth2; - this.cdx = this.sdx = 1.0d; - this.cdy = this.sdy = 0.0d; - finish(); + + this.sdx = 1.0d; + this.sdy = 0.0d; + this.cdx = 1.0d; + this.cdy = 0.0d; + + this.smx = 0.0d; + this.smy = -lineWidth2; + this.cmx = 0.0d; + this.cmy = -lineWidth2; + + finish(cOutCode); return; } - if (cx0 != sx0 || cy0 != sy0) { - lineTo(sx0, sy0); - } + // basic acceptance criteria + if ((sOutCode & cOutCode) == 0) { + if (cx0 != sx0 || cy0 != sy0) { + lineTo(sx0, sy0, true); + } - drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy); + drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode); - emitLineTo(sx0 + smx, sy0 + smy); + emitLineTo(sx0 + smx, sy0 + smy); - emitMoveTo(sx0 - smx, sy0 - smy); + if (opened) { + emitLineTo(sx0 - smx, sy0 - smy); + } else { + emitMoveTo(sx0 - smx, sy0 - smy); + } + } + // Ignore caps like finish(false) emitReverse(); this.prev = CLOSE; - emitClose(); + + if (opened) { + // do not emit close + opened = false; + } else { + emitClose(); + } } private void emitReverse() { @@ -519,7 +618,7 @@ @Override public void pathDone() { if (prev == DRAWING_OP_TO) { - finish(); + finish(cOutCode); } out.pathDone(); @@ -532,23 +631,39 @@ dispose(); } - private void finish() { - if (capStyle == CAP_ROUND) { - drawRoundCap(cx0, cy0, cmx, cmy); - } else if (capStyle == CAP_SQUARE) { - emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); - emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); - } + private void finish(final int outcode) { + // Problem: impossible to guess if the path will be closed in advance + // i.e. if caps must be drawn or not ? + // Solution: use the ClosedPathDetector before Stroker to determine + // if the path is a closed path or not + if (!rdrCtx.closedPath) { + if (outcode == 0) { + // current point = end's cap: + if (capStyle == CAP_ROUND) { + drawRoundCap(cx0, cy0, cmx, cmy); + } else if (capStyle == CAP_SQUARE) { + emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); + emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); + } + } + emitReverse(); - emitReverse(); + if (!capStart) { + capStart = true; - if (capStyle == CAP_ROUND) { - drawRoundCap(sx0, sy0, -smx, -smy); - } else if (capStyle == CAP_SQUARE) { - emitLineTo(sx0 + smy - smx, sy0 - smx - smy); - emitLineTo(sx0 + smy + smx, sy0 - smx + smy); + if (sOutCode == 0) { + // starting point = initial cap: + if (capStyle == CAP_ROUND) { + drawRoundCap(sx0, sy0, -smx, -smy); + } else if (capStyle == CAP_SQUARE) { + emitLineTo(sx0 + smy - smx, sy0 - smx - smy); + emitLineTo(sx0 + smy + smx, sy0 - smx + smy); + } + } + } + } else { + emitReverse(); } - emitClose(); } @@ -620,23 +735,28 @@ double x0, double y0, double dx, double dy, double omx, double omy, - double mx, double my) + double mx, double my, + final int outcode) { if (prev != DRAWING_OP_TO) { emitMoveTo(x0 + mx, y0 + my); - this.sdx = dx; - this.sdy = dy; - this.smx = mx; - this.smy = my; + if (!opened) { + this.sdx = dx; + this.sdy = dy; + this.smx = mx; + this.smy = my; + } } else { - boolean cw = isCW(pdx, pdy, dx, dy); - if (joinStyle == JOIN_MITER) { - drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); - } else if (joinStyle == JOIN_ROUND) { - drawRoundJoin(x0, y0, - omx, omy, - mx, my, cw, - ROUND_JOIN_THRESHOLD); + final boolean cw = isCW(pdx, pdy, dx, dy); + if (outcode == 0) { + if (joinStyle == JOIN_MITER) { + drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); + } else if (joinStyle == JOIN_ROUND) { + drawRoundJoin(x0, y0, + omx, omy, + mx, my, cw, + ROUND_JOIN_THRESHOLD); + } } emitLineTo(x0, y0, !cw); } @@ -941,10 +1061,29 @@ return ret; } - @Override public void curveTo(double x1, double y1, - double x2, double y2, - double x3, double y3) - { + @Override + public void curveTo(final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3) + { + final int outcode0 = this.cOutCode; + if (clipRect != null) { + final int outcode3 = DHelpers.outcode(x3, y3, clipRect); + this.cOutCode = outcode3; + + if ((outcode0 & outcode3) != 0) { + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); + final int outcode2 = DHelpers.outcode(x2, y2, clipRect); + + // basic rejection criteria + if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) { + moveTo(x3, y3, outcode0); + opened = true; + return; + } + } + } + final double[] mid = middle; mid[0] = cx0; mid[1] = cy0; @@ -953,7 +1092,7 @@ mid[6] = x3; mid[7] = y3; // need these so we can update the state at the end of this method - final double xf = mid[6], yf = mid[7]; + final double xf = x3, yf = y3; double dxs = mid[2] - mid[0]; double dys = mid[3] - mid[1]; double dxf = mid[6] - mid[4]; @@ -979,6 +1118,10 @@ } if (dxs == 0.0d && dys == 0.0d) { // this happens if the "curve" is just a point + // fix outcode0 for lineTo() call: + if (clipRect != null) { + this.cOutCode = outcode0; + } lineTo(mid[0], mid[1]); return; } @@ -997,7 +1140,7 @@ } computeOffset(dxs, dys, lineWidth2, offset0); - drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]); + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2); @@ -1032,16 +1175,36 @@ emitLineToRev(r[kind - 2], r[kind - 1]); } - this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; - this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d; - this.cdx = dxf; - this.cdy = dyf; + this.prev = DRAWING_OP_TO; this.cx0 = xf; this.cy0 = yf; - this.prev = DRAWING_OP_TO; + this.cdx = dxf; + this.cdy = dyf; + this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; + this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d; } - @Override public void quadTo(double x1, double y1, double x2, double y2) { + @Override + public void quadTo(final double x1, final double y1, + final double x2, final double y2) + { + final int outcode0 = this.cOutCode; + if (clipRect != null) { + final int outcode2 = DHelpers.outcode(x2, y2, clipRect); + this.cOutCode = outcode2; + + if ((outcode0 & outcode2) != 0) { + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); + + // basic rejection criteria + if ((outcode0 & outcode1 & outcode2) != 0) { + moveTo(x2, y2, outcode0); + opened = true; + return; + } + } + } + final double[] mid = middle; mid[0] = cx0; mid[1] = cy0; @@ -1049,7 +1212,7 @@ mid[4] = x2; mid[5] = y2; // need these so we can update the state at the end of this method - final double xf = mid[4], yf = mid[5]; + final double xf = x2, yf = y2; double dxs = mid[2] - mid[0]; double dys = mid[3] - mid[1]; double dxf = mid[4] - mid[2]; @@ -1060,6 +1223,10 @@ } if (dxs == 0.0d && dys == 0.0d) { // this happens if the "curve" is just a point + // fix outcode0 for lineTo() call: + if (clipRect != null) { + this.cOutCode = outcode0; + } lineTo(mid[0], mid[1]); return; } @@ -1077,7 +1244,7 @@ } computeOffset(dxs, dys, lineWidth2, offset0); - drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]); + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2); @@ -1112,214 +1279,16 @@ emitLineToRev(r[kind - 2], r[kind - 1]); } - this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; - this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d; - this.cdx = dxf; - this.cdy = dyf; + this.prev = DRAWING_OP_TO; this.cx0 = xf; this.cy0 = yf; - this.prev = DRAWING_OP_TO; + this.cdx = dxf; + this.cdy = dyf; + this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; + this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d; } @Override public long getNativeConsumer() { throw new InternalError("Stroker doesn't use a native consumer"); } - - // a stack of polynomial curves where each curve shares endpoints with - // adjacent ones. - static final class PolyStack { - private static final byte TYPE_LINETO = (byte) 0; - private static final byte TYPE_QUADTO = (byte) 1; - private static final byte TYPE_CUBICTO = (byte) 2; - - // curves capacity = edges count (8192) = edges x 2 (coords) - private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1; - - // types capacity = edges count (4096) - private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT; - - double[] curves; - int end; - byte[] curveTypes; - int numCurves; - - // per-thread renderer context - final DRendererContext rdrCtx; - - // curves ref (dirty) - final DoubleArrayCache.Reference curves_ref; - // curveTypes ref (dirty) - final ByteArrayCache.Reference curveTypes_ref; - - // used marks (stats only) - int curveTypesUseMark; - int curvesUseMark; - - /** - * Constructor - * @param rdrCtx per-thread renderer context - */ - PolyStack(final DRendererContext rdrCtx) { - this.rdrCtx = rdrCtx; - - curves_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_CURVES_COUNT); // 32K - curves = curves_ref.initial; - - curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K - curveTypes = curveTypes_ref.initial; - numCurves = 0; - end = 0; - - if (DO_STATS) { - curveTypesUseMark = 0; - curvesUseMark = 0; - } - } - - /** - * Disposes this PolyStack: - * clean up before reusing this instance - */ - void dispose() { - end = 0; - numCurves = 0; - - if (DO_STATS) { - rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark); - rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark); - rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark); - - // reset marks - curveTypesUseMark = 0; - curvesUseMark = 0; - } - - // Return arrays: - // curves and curveTypes are kept dirty - curves = curves_ref.putArray(curves); - curveTypes = curveTypes_ref.putArray(curveTypes); - } - - private void ensureSpace(final int n) { - // use substraction to avoid integer overflow: - if (curves.length - end < n) { - if (DO_STATS) { - rdrCtx.stats.stat_array_stroker_polystack_curves - .add(end + n); - } - curves = curves_ref.widenArray(curves, end, end + n); - } - if (curveTypes.length <= numCurves) { - if (DO_STATS) { - rdrCtx.stats.stat_array_stroker_polystack_curveTypes - .add(numCurves + 1); - } - curveTypes = curveTypes_ref.widenArray(curveTypes, - numCurves, - numCurves + 1); - } - } - - void pushCubic(double x0, double y0, - double x1, double y1, - double x2, double y2) - { - ensureSpace(6); - curveTypes[numCurves++] = TYPE_CUBICTO; - // we reverse the coordinate order to make popping easier - final double[] _curves = curves; - int e = end; - _curves[e++] = x2; _curves[e++] = y2; - _curves[e++] = x1; _curves[e++] = y1; - _curves[e++] = x0; _curves[e++] = y0; - end = e; - } - - void pushQuad(double x0, double y0, - double x1, double y1) - { - ensureSpace(4); - curveTypes[numCurves++] = TYPE_QUADTO; - final double[] _curves = curves; - int e = end; - _curves[e++] = x1; _curves[e++] = y1; - _curves[e++] = x0; _curves[e++] = y0; - end = e; - } - - void pushLine(double x, double y) { - ensureSpace(2); - curveTypes[numCurves++] = TYPE_LINETO; - curves[end++] = x; curves[end++] = y; - } - - void popAll(DPathConsumer2D io) { - if (DO_STATS) { - // update used marks: - if (numCurves > curveTypesUseMark) { - curveTypesUseMark = numCurves; - } - if (end > curvesUseMark) { - curvesUseMark = end; - } - } - final byte[] _curveTypes = curveTypes; - final double[] _curves = curves; - int nc = numCurves; - int e = end; - - while (nc != 0) { - switch(_curveTypes[--nc]) { - case TYPE_LINETO: - e -= 2; - io.lineTo(_curves[e], _curves[e+1]); - continue; - case TYPE_QUADTO: - e -= 4; - io.quadTo(_curves[e+0], _curves[e+1], - _curves[e+2], _curves[e+3]); - continue; - case TYPE_CUBICTO: - e -= 6; - io.curveTo(_curves[e+0], _curves[e+1], - _curves[e+2], _curves[e+3], - _curves[e+4], _curves[e+5]); - continue; - default: - } - } - numCurves = 0; - end = 0; - } - - @Override - public String toString() { - String ret = ""; - int nc = numCurves; - int last = end; - int len; - while (nc != 0) { - switch(curveTypes[--nc]) { - case TYPE_LINETO: - len = 2; - ret += "line: "; - break; - case TYPE_QUADTO: - len = 4; - ret += "quad: "; - break; - case TYPE_CUBICTO: - len = 6; - ret += "cubic: "; - break; - default: - len = 0; - } - last -= len; - ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len)) - + "\n"; - } - return ret; - } - } } --- old/src/java.desktop/share/classes/sun/java2d/marlin/DTransformingPathConsumer2D.java 2017-11-14 22:42:40.026385246 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DTransformingPathConsumer2D.java 2017-11-14 22:42:39.894382373 +0100 @@ -27,50 +27,169 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; +import sun.java2d.marlin.DHelpers.IndexStack; +import sun.java2d.marlin.DHelpers.PolyStack; final class DTransformingPathConsumer2D { - DTransformingPathConsumer2D() { - // used by DRendererContext - } + private final DRendererContext rdrCtx; - // recycled DPathConsumer2D instance from wrapPath2d() - private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper(); + // recycled ClosedPathDetector instance from detectClosedPath() + private final ClosedPathDetector cpDetector; - DPathConsumer2D wrapPath2d(Path2D.Double p2d) - { - return wp_Path2DWrapper.init(p2d); - } + // recycled PathClipFilter instance from pathClipper() + private final PathClipFilter pathClipper; + + // recycled DPathConsumer2D instance from wrapPath2D() + private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper(); // recycled DPathConsumer2D instances from deltaTransformConsumer() private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter(); private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter(); + // recycled DPathConsumer2D instances from inverseDeltaTransformConsumer() + private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter(); + private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter(); + + // 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"); + + DTransformingPathConsumer2D(final DRendererContext rdrCtx) { + // used by RendererContext + this.rdrCtx = rdrCtx; + this.cpDetector = new ClosedPathDetector(rdrCtx); + this.pathClipper = new PathClipFilter(rdrCtx); + } + + DPathConsumer2D wrapPath2D(Path2D.Double p2d) { + return wp_Path2DWrapper.init(p2d); + } + + DPathConsumer2D traceInput(DPathConsumer2D out) { + return tracerInput.init(out); + } + + DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) { + return tracerCPDetector.init(out); + } + + DPathConsumer2D traceFiller(DPathConsumer2D out) { + return tracerFiller.init(out); + } + + DPathConsumer2D traceStroker(DPathConsumer2D out) { + return tracerStroker.init(out); + } + + DPathConsumer2D detectClosedPath(DPathConsumer2D out) { + return cpDetector.init(out); + } + + DPathConsumer2D pathClipper(DPathConsumer2D out) { + return pathClipper.init(out); + } + DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out, AffineTransform at) { if (at == null) { return out; } - double mxx = at.getScaleX(); - double mxy = at.getShearX(); - double myx = at.getShearY(); - double myy = at.getScaleY(); + final double mxx = at.getScaleX(); + final double mxy = at.getShearX(); + final double myx = at.getShearY(); + final double myy = at.getScaleY(); if (mxy == 0.0d && myx == 0.0d) { if (mxx == 1.0d && myy == 1.0d) { return out; } else { + // Scale only + if (rdrCtx.doClip) { + // adjust clip rectangle (ymin, ymax, xmin, xmax): + adjustClipScale(rdrCtx.clipRect, mxx, myy); + } return dt_DeltaScaleFilter.init(out, mxx, myy); } } else { + if (rdrCtx.doClip) { + // adjust clip rectangle (ymin, ymax, xmin, xmax): + adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy); + } return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy); } } - // recycled DPathConsumer2D instances from inverseDeltaTransformConsumer() - private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter(); - private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter(); + private static void adjustClipOffset(final double[] clipRect) { + clipRect[0] += Renderer.RDR_OFFSET_Y; + clipRect[1] += Renderer.RDR_OFFSET_Y; + clipRect[2] += Renderer.RDR_OFFSET_X; + clipRect[3] += Renderer.RDR_OFFSET_X; + } + + private static void adjustClipScale(final double[] clipRect, + final double mxx, final double myy) + { + adjustClipOffset(clipRect); + + // Adjust the clipping rectangle (iv_DeltaScaleFilter): + clipRect[0] /= myy; + clipRect[1] /= myy; + clipRect[2] /= mxx; + clipRect[3] /= mxx; + } + + private static void adjustClipInverseDelta(final double[] clipRect, + final double mxx, final double mxy, + final double myx, final double myy) + { + adjustClipOffset(clipRect); + + // Adjust the clipping rectangle (iv_DeltaTransformFilter): + final double det = mxx * myy - mxy * myx; + final double imxx = myy / det; + final double imxy = -mxy / det; + final double imyx = -myx / det; + final double imyy = mxx / det; + + double xmin, xmax, ymin, ymax; + double x, y; + // xmin, ymin: + x = clipRect[2] * imxx + clipRect[0] * imxy; + y = clipRect[2] * imyx + clipRect[0] * imyy; + + xmin = xmax = x; + ymin = ymax = y; + + // xmax, ymin: + x = clipRect[3] * imxx + clipRect[0] * imxy; + y = clipRect[3] * imyx + clipRect[0] * imyy; + + if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } + if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } + + // xmin, ymax: + x = clipRect[2] * imxx + clipRect[1] * imxy; + y = clipRect[2] * imyx + clipRect[1] * imyy; + + if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } + if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } + + // xmax, ymax: + x = clipRect[3] * imxx + clipRect[1] * imxy; + y = clipRect[3] * imyx + clipRect[1] * imyy; + + if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } + if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } + + clipRect[0] = ymin; + clipRect[1] = ymax; + clipRect[2] = xmin; + clipRect[3] = xmax; + } DPathConsumer2D inverseDeltaTransformConsumer(DPathConsumer2D out, AffineTransform at) @@ -90,7 +209,7 @@ return iv_DeltaScaleFilter.init(out, 1.0d/mxx, 1.0d/myy); } } else { - double det = mxx * myy - mxy * myx; + final double det = mxx * myy - mxy * myx; return iv_DeltaTransformFilter.init(out, myy / det, -mxy / det, @@ -99,7 +218,6 @@ } } - static final class DeltaScaleFilter implements DPathConsumer2D { private DPathConsumer2D out; private double sx, sy; @@ -270,6 +388,429 @@ } @Override + public long getNativeConsumer() { + throw new InternalError("Not using a native peer"); + } + } + + static final class ClosedPathDetector implements DPathConsumer2D { + + private final DRendererContext rdrCtx; + private final PolyStack stack; + + private DPathConsumer2D out; + + ClosedPathDetector(final DRendererContext rdrCtx) { + this.rdrCtx = rdrCtx; + this.stack = (rdrCtx.stats != null) ? + new PolyStack(rdrCtx, + rdrCtx.stats.stat_cpd_polystack_types, + rdrCtx.stats.stat_cpd_polystack_curves, + rdrCtx.stats.hist_cpd_polystack_curves, + rdrCtx.stats.stat_array_cpd_polystack_curves, + rdrCtx.stats.stat_array_cpd_polystack_types) + : new PolyStack(rdrCtx); + } + + ClosedPathDetector init(DPathConsumer2D out) { + this.out = out; + return this; // fluent API + } + + /** + * Disposes this instance: + * clean up before reusing this instance + */ + void dispose() { + stack.dispose(); + } + + @Override + public void pathDone() { + // previous path is not closed: + finish(false); + out.pathDone(); + + // TODO: fix possible leak if exception happened + // Dispose this instance: + dispose(); + } + + @Override + public void closePath() { + // path is closed + finish(true); + out.closePath(); + } + + @Override + public void moveTo(double x0, double y0) { + // previous path is not closed: + finish(false); + out.moveTo(x0, y0); + } + + private void finish(final boolean closed) { + rdrCtx.closedPath = closed; + stack.pullAll(out); + } + + @Override + public void lineTo(double x1, double y1) { + stack.pushLine(x1, y1); + } + + @Override + public void curveTo(double x3, double y3, + double x2, double y2, + double x1, double y1) + { + stack.pushCubic(x1, y1, x2, y2, x3, y3); + } + + @Override + public void quadTo(double x2, double y2, double x1, double y1) { + stack.pushQuad(x1, y1, x2, y2); + } + + @Override + public long getNativeConsumer() { + throw new InternalError("Not using a native peer"); + } + } + + 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 current outcode of the current sub path + private int cOutCode = 0; + + // 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) + : 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; + + this.init_corners = true; + this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R; + + return this; // fluent API + } + + /** + * Disposes this instance: + * clean up before reusing this instance + */ + void dispose() { + stack.dispose(); + } + + private void finishPath() { + if (outside) { + // criteria: inside or totally outside ? + if (gOutCode == 0) { + finish(); + } else { + this.outside = false; + stack.reset(); + } + } + } + + private void finish() { + this.outside = false; + + if (!stack.isEmpty()) { + if (init_corners) { + init_corners = false; + + final double[] _corners = corners; + final double[] _clipRect = clipRect; + // 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 pathDone() { + finishPath(); + + out.pathDone(); + + // TODO: fix possible leak if exception happened + // Dispose this instance: + dispose(); + } + + @Override + public void closePath() { + finishPath(); + + out.closePath(); + } + + @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) + { + // corner or cross-boundary on left or right side: + if ((outcode0 != outcode1) + && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0)) + { + // combine outcodes: + final int mergeCode = (outcode0 | outcode1); + final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B; + final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R; + 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 + } + } + } + } + + @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) { + 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); + } + + @Override + public long getNativeConsumer() { + throw new InternalError("Not using a native peer"); + } + } + + static final class PathTracer implements DPathConsumer2D { + private final String prefix; + private DPathConsumer2D out; + + PathTracer(String name) { + this.prefix = name + ": "; + } + + PathTracer init(DPathConsumer2D out) { + this.out = out; + return this; // fluent API + } + + @Override + public void moveTo(double x0, double y0) { + log("moveTo (" + x0 + ", " + y0 + ')'); + out.moveTo(x0, y0); + } + + @Override + public void lineTo(double x1, double y1) { + log("lineTo (" + x1 + ", " + y1 + ')'); + out.lineTo(x1, y1); + } + + @Override + public void curveTo(double x1, double y1, + double x2, double y2, + double x3, double y3) + { + log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ')'); + out.curveTo(x1, y1, x2, y2, x3, y3); + } + + @Override + public void quadTo(double x1, double y1, double x2, double y2) { + log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')'); + out.quadTo(x1, y1, x2, y2); + } + + @Override + public void closePath() { + log("closePath"); + out.closePath(); + } + + @Override + public void pathDone() { + log("pathDone"); + out.pathDone(); + } + + private void log(final String message) { + System.out.println(prefix + message); + } + + @Override public long getNativeConsumer() { throw new InternalError("Not using a native peer"); } --- old/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java 2017-11-14 22:42:40.346392208 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java 2017-11-14 22:42:40.214389336 +0100 @@ -138,7 +138,7 @@ dashOn = !dashOn; } } - } else if (phase > 0) { + } else if (phase > 0.0f) { if (cycles >= MAX_CYCLES) { phase = 0.0f; } else { @@ -158,12 +158,13 @@ this.dash = dash; this.dashLen = dashLen; - this.startPhase = this.phase = phase; + this.phase = phase; + this.startPhase = phase; this.startDashOn = dashOn; this.startIdx = sidx; this.starting = true; - needsMoveTo = false; - firstSegidx = 0; + this.needsMoveTo = false; + this.firstSegidx = 0; this.recycleDashes = recycleDashes; @@ -202,8 +203,8 @@ } @Override - public void moveTo(float x0, float y0) { - if (firstSegidx > 0) { + public void moveTo(final float x0, final float y0) { + if (firstSegidx != 0) { out.moveTo(sx, sy); emitFirstSegments(); } @@ -211,8 +212,10 @@ this.idx = startIdx; this.dashOn = this.startDashOn; this.phase = this.startPhase; - this.sx = this.x0 = x0; - this.sy = this.y0 = y0; + this.sx = x0; + this.sy = y0; + this.x0 = x0; + this.y0 = y0; this.starting = true; } @@ -237,7 +240,7 @@ private void emitFirstSegments() { final float[] fSegBuf = firstSegmentsBuffer; - for (int i = 0; i < firstSegidx; ) { + for (int i = 0, len = firstSegidx; i < len; ) { int type = (int)fSegBuf[i]; emitSeg(fSegBuf, i + 1, type); i += (type - 1); @@ -252,48 +255,59 @@ private int firstSegidx; // precondition: pts must be in relative coordinates (relative to x0,y0) - private void goTo(float[] pts, int off, final int type) { - float x = pts[off + type - 4]; - float y = pts[off + type - 3]; - if (dashOn) { + private void goTo(final float[] pts, final int off, final int type, + final boolean on) + { + final int index = off + type; + final float x = pts[index - 4]; + final float y = pts[index - 3]; + + if (on) { if (starting) { - int len = type - 1; // - 2 + 1 - int segIdx = firstSegidx; - float[] buf = firstSegmentsBuffer; - if (segIdx + len > buf.length) { - if (DO_STATS) { - rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer - .add(segIdx + len); - } - firstSegmentsBuffer = buf - = firstSegmentsBuffer_ref.widenArray(buf, segIdx, - segIdx + len); - } - buf[segIdx++] = type; - len--; - // small arraycopy (2, 4 or 6) but with offset: - System.arraycopy(pts, off, buf, segIdx, len); - segIdx += len; - firstSegidx = segIdx; + goTo_starting(pts, off, type); } else { if (needsMoveTo) { - out.moveTo(x0, y0); needsMoveTo = false; + out.moveTo(x0, y0); } emitSeg(pts, off, type); } } else { - starting = false; + if (starting) { + // low probability test (hotspot) + starting = false; + } needsMoveTo = true; } this.x0 = x; this.y0 = y; } + private void goTo_starting(final float[] pts, final int off, final int type) { + int len = type - 1; // - 2 + 1 + int segIdx = firstSegidx; + float[] buf = firstSegmentsBuffer; + + if (segIdx + len > buf.length) { + if (DO_STATS) { + rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer + .add(segIdx + len); + } + firstSegmentsBuffer = buf + = firstSegmentsBuffer_ref.widenArray(buf, segIdx, + segIdx + len); + } + buf[segIdx++] = type; + len--; + // small arraycopy (2, 4 or 6) but with offset: + System.arraycopy(pts, off, buf, segIdx, len); + firstSegidx = segIdx + len; + } + @Override - public void lineTo(float x1, float y1) { - float dx = x1 - x0; - float dy = y1 - y0; + public void lineTo(final float x1, final float y1) { + final float dx = x1 - x0; + final float dy = y1 - y0; float len = dx*dx + dy*dy; if (len == 0.0f) { @@ -308,48 +322,61 @@ final float[] _curCurvepts = curCurvepts; final float[] _dash = dash; + final int _dashLen = this.dashLen; + + int _idx = idx; + boolean _dashOn = dashOn; + float _phase = phase; float leftInThisDashSegment; - float dashdx, dashdy, p; + float d, dashdx, dashdy, p; while (true) { - leftInThisDashSegment = _dash[idx] - phase; + d = _dash[_idx]; + leftInThisDashSegment = d - _phase; if (len <= leftInThisDashSegment) { _curCurvepts[0] = x1; _curCurvepts[1] = y1; - goTo(_curCurvepts, 0, 4); + + goTo(_curCurvepts, 0, 4, _dashOn); // Advance phase within current dash segment - phase += len; + _phase += len; + // TODO: compare float values using epsilon: if (len == leftInThisDashSegment) { - phase = 0.0f; - idx = (idx + 1) % dashLen; - dashOn = !dashOn; + _phase = 0.0f; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; } + + // Save local state: + idx = _idx; + dashOn = _dashOn; + phase = _phase; return; } - dashdx = _dash[idx] * cx; - dashdy = _dash[idx] * cy; + dashdx = d * cx; + dashdy = d * cy; - if (phase == 0.0f) { + if (_phase == 0.0f) { _curCurvepts[0] = x0 + dashdx; _curCurvepts[1] = y0 + dashdy; } else { - p = leftInThisDashSegment / _dash[idx]; + p = leftInThisDashSegment / d; _curCurvepts[0] = x0 + p * dashdx; _curCurvepts[1] = y0 + p * dashdy; } - goTo(_curCurvepts, 0, 4); + goTo(_curCurvepts, 0, 4, _dashOn); len -= leftInThisDashSegment; // Advance to next dash segment - idx = (idx + 1) % dashLen; - dashOn = !dashOn; - phase = 0.0f; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + _phase = 0.0f; } } @@ -362,39 +389,55 @@ if (pointCurve(curCurvepts, type)) { return; } - li.initializeIterationOnCurve(curCurvepts, type); + final LengthIterator _li = li; + final float[] _curCurvepts = curCurvepts; + final float[] _dash = dash; + final int _dashLen = this.dashLen; + + _li.initializeIterationOnCurve(_curCurvepts, type); + + int _idx = idx; + boolean _dashOn = dashOn; + float _phase = phase; // initially the current curve is at curCurvepts[0...type] int curCurveoff = 0; float lastSplitT = 0.0f; float t; - float leftInThisDashSegment = dash[idx] - phase; + float leftInThisDashSegment = _dash[_idx] - _phase; - while ((t = li.next(leftInThisDashSegment)) < 1.0f) { + while ((t = _li.next(leftInThisDashSegment)) < 1.0f) { if (t != 0.0f) { Helpers.subdivideAt((t - lastSplitT) / (1.0f - lastSplitT), - curCurvepts, curCurveoff, - curCurvepts, 0, - curCurvepts, type, type); + _curCurvepts, curCurveoff, + _curCurvepts, 0, + _curCurvepts, type, type); lastSplitT = t; - goTo(curCurvepts, 2, type); + goTo(_curCurvepts, 2, type, _dashOn); curCurveoff = type; } // Advance to next dash segment - idx = (idx + 1) % dashLen; - dashOn = !dashOn; - phase = 0.0f; - leftInThisDashSegment = dash[idx]; - } - goTo(curCurvepts, curCurveoff+2, type); - phase += li.lastSegLen(); - if (phase >= dash[idx]) { - phase = 0.0f; - idx = (idx + 1) % dashLen; - dashOn = !dashOn; - } + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + _phase = 0.0f; + leftInThisDashSegment = _dash[_idx]; + } + + goTo(_curCurvepts, curCurveoff + 2, type, _dashOn); + + _phase += _li.lastSegLen(); + if (_phase >= _dash[_idx]) { + _phase = 0.0f; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + } + // Save local state: + idx = _idx; + dashOn = _dashOn; + phase = _phase; + // reset LengthIterator: - li.reset(); + _li.reset(); } private static boolean pointCurve(float[] curve, int type) { @@ -420,7 +463,7 @@ // tree; however, the trees we are interested in have the property that // every non leaf node has exactly 2 children static final class LengthIterator { - private enum Side {LEFT, RIGHT}; + private enum Side {LEFT, RIGHT} // Holds the curves at various levels of the recursion. The root // (i.e. the original curve) is at recCurveStack[0] (but then it // gets subdivided, the left half is put at 1, so most of the time @@ -670,11 +713,12 @@ // this is a bit of a hack. It returns -1 if we're not on a leaf, and // the length of the leaf if we are on a leaf. private float onLeaf() { - float[] curve = recCurveStack[recLevel]; + final float[] curve = recCurveStack[recLevel]; + final int _curveType = curveType; float polyLen = 0.0f; float x0 = curve[0], y0 = curve[1]; - for (int i = 2; i < curveType; i += 2) { + for (int i = 2; i < _curveType; i += 2) { final float x1 = curve[i], y1 = curve[i+1]; final float len = Helpers.linelen(x0, y0, x1, y1); polyLen += len; @@ -684,8 +728,8 @@ } final float lineLen = Helpers.linelen(curve[0], curve[1], - curve[curveType-2], - curve[curveType-1]); + curve[_curveType-2], + curve[_curveType-1]); if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) { return (polyLen + lineLen) / 2.0f; } @@ -694,9 +738,9 @@ } @Override - public void curveTo(float x1, float y1, - float x2, float y2, - float x3, float y3) + public void curveTo(final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3) { final float[] _curCurvepts = curCurvepts; _curCurvepts[0] = x0; _curCurvepts[1] = y0; @@ -707,7 +751,9 @@ } @Override - public void quadTo(float x1, float y1, float x2, float y2) { + public void quadTo(final float x1, final float y1, + final float x2, final float y2) + { final float[] _curCurvepts = curCurvepts; _curCurvepts[0] = x0; _curCurvepts[1] = y0; _curCurvepts[2] = x1; _curCurvepts[3] = y1; @@ -718,7 +764,7 @@ @Override public void closePath() { lineTo(sx, sy); - if (firstSegidx > 0) { + if (firstSegidx != 0) { if (!dashOn || needsMoveTo) { out.moveTo(sx, sy); } @@ -729,7 +775,7 @@ @Override public void pathDone() { - if (firstSegidx > 0) { + if (firstSegidx != 0) { out.moveTo(sx, sy); emitFirstSegments(); } --- old/src/java.desktop/share/classes/sun/java2d/marlin/Helpers.java 2017-11-14 22:42:40.662399083 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Helpers.java 2017-11-14 22:42:40.534396298 +0100 @@ -26,10 +26,11 @@ package sun.java2d.marlin; import static java.lang.Math.PI; -import static java.lang.Math.cos; -import static java.lang.Math.sqrt; -import static java.lang.Math.cbrt; -import static java.lang.Math.acos; +import java.util.Arrays; +import sun.awt.geom.PathConsumer2D; +import static sun.java2d.marlin.MarlinConst.INITIAL_EDGES_COUNT; +import sun.java2d.marlin.stats.Histogram; +import sun.java2d.marlin.stats.StatLong; final class Helpers implements MarlinConst { @@ -120,17 +121,17 @@ int num; if (D < 0.0d) { // see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method - final double phi = (1.0d/3.0d) * acos(-q / sqrt(-cb_p)); - final double t = 2.0d * sqrt(-p); + final double phi = (1.0d/3.0d) * Math.acos(-q / Math.sqrt(-cb_p)); + final double t = 2.0d * Math.sqrt(-p); - pts[ off+0 ] = (float) ( t * cos(phi)); - pts[ off+1 ] = (float) (-t * cos(phi + (PI / 3.0d))); - pts[ off+2 ] = (float) (-t * cos(phi - (PI / 3.0d))); + pts[ off+0 ] = (float) ( t * Math.cos(phi)); + pts[ off+1 ] = (float) (-t * Math.cos(phi + (PI / 3.0d))); + pts[ off+2 ] = (float) (-t * Math.cos(phi - (PI / 3.0d))); num = 3; } else { - final double sqrt_D = sqrt(D); - final double u = cbrt(sqrt_D - q); - final double v = - cbrt(sqrt_D + q); + final double sqrt_D = Math.sqrt(D); + final double u = Math.cbrt(sqrt_D - q); + final double v = - Math.cbrt(sqrt_D + q); pts[ off ] = (float) (u + v); num = 1; @@ -176,15 +177,6 @@ return ret; } - static float polyLineLength(float[] poly, final int off, final int nCoords) { - assert nCoords % 2 == 0 && poly.length >= off + nCoords : ""; - float acc = 0.0f; - for (int i = off + 2; i < off + nCoords; i += 2) { - acc += linelen(poly[i], poly[i+1], poly[i-2], poly[i-1]); - } - return acc; - } - static float linelen(float x1, float y1, float x2, float y2) { final float dx = x2 - x1; final float dy = y2 - y1; @@ -438,4 +430,388 @@ return; } } + + // From sun.java2d.loops.GeneralRenderer: + + static int outcode(final float x, final float y, + final float[] clipRect) + { + int code; + if (y < clipRect[0]) { + code = OUTCODE_TOP; + } else if (y >= clipRect[1]) { + code = OUTCODE_BOTTOM; + } else { + code = 0; + } + if (x < clipRect[2]) { + code |= OUTCODE_LEFT; + } else if (x >= clipRect[3]) { + code |= OUTCODE_RIGHT; + } + return code; + } + + // a stack of polynomial curves where each curve shares endpoints with + // adjacent ones. + static final class PolyStack { + private static final byte TYPE_LINETO = (byte) 0; + private static final byte TYPE_QUADTO = (byte) 1; + private static final byte TYPE_CUBICTO = (byte) 2; + + // curves capacity = edges count (8192) = edges x 2 (coords) + private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1; + + // types capacity = edges count (4096) + private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT; + + float[] curves; + int end; + byte[] curveTypes; + int numCurves; + + // curves ref (dirty) + final FloatArrayCache.Reference curves_ref; + // curveTypes ref (dirty) + final ByteArrayCache.Reference curveTypes_ref; + + // used marks (stats only) + int curveTypesUseMark; + int curvesUseMark; + + private final StatLong stat_polystack_types; + private final StatLong stat_polystack_curves; + private final Histogram hist_polystack_curves; + private final StatLong stat_array_polystack_curves; + private final StatLong stat_array_polystack_curveTypes; + + PolyStack(final RendererContext rdrCtx) { + this(rdrCtx, null, null, null, null, null); + } + + PolyStack(final RendererContext rdrCtx, + final StatLong stat_polystack_types, + final StatLong stat_polystack_curves, + final Histogram hist_polystack_curves, + final StatLong stat_array_polystack_curves, + final StatLong stat_array_polystack_curveTypes) + { + curves_ref = rdrCtx.newDirtyFloatArrayRef(INITIAL_CURVES_COUNT); // 32K + curves = curves_ref.initial; + + curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K + curveTypes = curveTypes_ref.initial; + numCurves = 0; + end = 0; + + if (DO_STATS) { + curveTypesUseMark = 0; + curvesUseMark = 0; + } + this.stat_polystack_types = stat_polystack_types; + this.stat_polystack_curves = stat_polystack_curves; + this.hist_polystack_curves = hist_polystack_curves; + this.stat_array_polystack_curves = stat_array_polystack_curves; + this.stat_array_polystack_curveTypes = stat_array_polystack_curveTypes; + } + + /** + * Disposes this PolyStack: + * clean up before reusing this instance + */ + void dispose() { + end = 0; + numCurves = 0; + + if (DO_STATS) { + stat_polystack_types.add(curveTypesUseMark); + stat_polystack_curves.add(curvesUseMark); + hist_polystack_curves.add(curvesUseMark); + + // reset marks + curveTypesUseMark = 0; + curvesUseMark = 0; + } + + // Return arrays: + // curves and curveTypes are kept dirty + curves = curves_ref.putArray(curves); + curveTypes = curveTypes_ref.putArray(curveTypes); + } + + private void ensureSpace(final int n) { + // use substraction to avoid integer overflow: + if (curves.length - end < n) { + if (DO_STATS) { + stat_array_polystack_curves.add(end + n); + } + curves = curves_ref.widenArray(curves, end, end + n); + } + if (curveTypes.length <= numCurves) { + if (DO_STATS) { + stat_array_polystack_curveTypes.add(numCurves + 1); + } + curveTypes = curveTypes_ref.widenArray(curveTypes, + numCurves, + numCurves + 1); + } + } + + void pushCubic(float x0, float y0, + float x1, float y1, + float x2, float y2) + { + ensureSpace(6); + curveTypes[numCurves++] = TYPE_CUBICTO; + // we reverse the coordinate order to make popping easier + final float[] _curves = curves; + int e = end; + _curves[e++] = x2; _curves[e++] = y2; + _curves[e++] = x1; _curves[e++] = y1; + _curves[e++] = x0; _curves[e++] = y0; + end = e; + } + + void pushQuad(float x0, float y0, + float x1, float y1) + { + ensureSpace(4); + curveTypes[numCurves++] = TYPE_QUADTO; + final float[] _curves = curves; + int e = end; + _curves[e++] = x1; _curves[e++] = y1; + _curves[e++] = x0; _curves[e++] = y0; + end = e; + } + + void pushLine(float x, float y) { + ensureSpace(2); + curveTypes[numCurves++] = TYPE_LINETO; + curves[end++] = x; curves[end++] = y; + } + + void pullAll(final PathConsumer2D io) { + final int nc = numCurves; + if (nc == 0) { + return; + } + if (DO_STATS) { + // update used marks: + if (numCurves > curveTypesUseMark) { + curveTypesUseMark = numCurves; + } + if (end > curvesUseMark) { + curvesUseMark = end; + } + } + final byte[] _curveTypes = curveTypes; + final float[] _curves = curves; + int e = 0; + + for (int i = 0; i < nc; i++) { + switch(_curveTypes[i]) { + case TYPE_LINETO: + io.lineTo(_curves[e], _curves[e+1]); + e += 2; + continue; + case TYPE_QUADTO: + io.quadTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3]); + e += 4; + continue; + case TYPE_CUBICTO: + io.curveTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3], + _curves[e+4], _curves[e+5]); + e += 6; + continue; + default: + } + } + numCurves = 0; + end = 0; + } + + void popAll(final PathConsumer2D io) { + int nc = numCurves; + if (nc == 0) { + return; + } + if (DO_STATS) { + // update used marks: + if (numCurves > curveTypesUseMark) { + curveTypesUseMark = numCurves; + } + if (end > curvesUseMark) { + curvesUseMark = end; + } + } + final byte[] _curveTypes = curveTypes; + final float[] _curves = curves; + int e = end; + + while (nc != 0) { + switch(_curveTypes[--nc]) { + case TYPE_LINETO: + e -= 2; + io.lineTo(_curves[e], _curves[e+1]); + continue; + case TYPE_QUADTO: + e -= 4; + io.quadTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3]); + continue; + case TYPE_CUBICTO: + e -= 6; + io.curveTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3], + _curves[e+4], _curves[e+5]); + continue; + default: + } + } + numCurves = 0; + end = 0; + } + + @Override + public String toString() { + String ret = ""; + int nc = numCurves; + int last = end; + int len; + while (nc != 0) { + switch(curveTypes[--nc]) { + case TYPE_LINETO: + len = 2; + ret += "line: "; + break; + case TYPE_QUADTO: + len = 4; + ret += "quad: "; + break; + case TYPE_CUBICTO: + len = 6; + ret += "cubic: "; + break; + default: + len = 0; + } + last -= len; + ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len)) + + "\n"; + } + return ret; + } + } + + // 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 RendererContext rdrCtx) { + this(rdrCtx, null, null, null); + } + + IndexStack(final RendererContext 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 float[] points, final PathConsumer2D 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; + } + } } --- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinCache.java 2017-11-14 22:42:40.966405697 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinCache.java 2017-11-14 22:42:40.838402912 +0100 @@ -139,11 +139,7 @@ // ie number of primitives: // fast check min and max width (maxx < 23bits): - if (width <= RLE_MIN_WIDTH || width >= RLE_MAX_WIDTH) { - useRLE = false; - } else { - useRLE = true; - } + useRLE = (width > RLE_MIN_WIDTH && width < RLE_MAX_WIDTH); } // the ceiling of (maxy - miny + 1) / TILE_SIZE; --- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinConst.java 2017-11-14 22:42:41.278412483 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinConst.java 2017-11-14 22:42:41.150409699 +0100 @@ -128,4 +128,47 @@ public static final int BLOCK_SIZE_LG = MarlinProperties.getBlockSize_Log2(); public static final int BLOCK_SIZE = 1 << BLOCK_SIZE_LG; + + // Constants + public static final int WIND_EVEN_ODD = 0; + public static final int WIND_NON_ZERO = 1; + + /** + * Constant value for join style. + */ + public static final int JOIN_MITER = 0; + + /** + * Constant value for join style. + */ + public static final int JOIN_ROUND = 1; + + /** + * Constant value for join style. + */ + public static final int JOIN_BEVEL = 2; + + /** + * Constant value for end cap style. + */ + public static final int CAP_BUTT = 0; + + /** + * Constant value for end cap style. + */ + public static final int CAP_ROUND = 1; + + /** + * Constant value for end cap style. + */ + public static final int CAP_SQUARE = 2; + + // Out codes + static final int OUTCODE_TOP = 1; + static final int OUTCODE_BOTTOM = 2; + static final int OUTCODE_LEFT = 4; + static final int OUTCODE_RIGHT = 8; + static final int OUTCODE_MASK_T_B = OUTCODE_TOP | OUTCODE_BOTTOM; + static final int OUTCODE_MASK_L_R = OUTCODE_LEFT | OUTCODE_RIGHT; + static final int OUTCODE_MASK_T_B_L_R = OUTCODE_MASK_T_B | OUTCODE_MASK_L_R; } --- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinProperties.java 2017-11-14 22:42:41.582419095 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinProperties.java 2017-11-14 22:42:41.450416224 +0100 @@ -145,6 +145,18 @@ return getBoolean("sun.java2d.renderer.useSimplifier", "false"); } + public static boolean isDoClip() { + return getBoolean("sun.java2d.renderer.clip", "true"); + } + + public static boolean isDoClipRuntimeFlag() { + return getBoolean("sun.java2d.renderer.clip.runtime.enable", "false"); + } + + public static boolean isDoClipAtRuntime() { + return getBoolean("sun.java2d.renderer.clip.runtime", "true"); + } + // debugging parameters public static boolean isDoStats() { --- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java 2017-11-14 22:42:41.890425794 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java 2017-11-14 22:42:41.762423011 +0100 @@ -85,6 +85,13 @@ static final float UPPER_BND = Float.MAX_VALUE / 2.0f; static final float LOWER_BND = -UPPER_BND; + static final boolean DO_CLIP = MarlinProperties.isDoClip(); + static final boolean DO_CLIP_FILL = true; + + static final boolean DO_TRACE_PATH = false; + + static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag(); + /** * Public constructor */ @@ -134,7 +141,7 @@ miterlimit, dashes, dashphase, - rdrCtx.transformerPC2D.wrapPath2d(p2d) + rdrCtx.transformerPC2D.wrapPath2D(p2d) ); // Use Path2D copy constructor (trim) @@ -195,14 +202,14 @@ } } - final void strokeTo(final RendererContext rdrCtx, - Shape src, - AffineTransform at, - BasicStroke bs, - boolean thin, - NormMode normalize, - boolean antialias, - PathConsumer2D pc2d) + void strokeTo(final RendererContext rdrCtx, + Shape src, + AffineTransform at, + BasicStroke bs, + boolean thin, + NormMode normalize, + boolean antialias, + PathConsumer2D pc2d) { float lw; if (thin) { @@ -227,7 +234,7 @@ pc2d); } - private final float userSpaceLineWidth(AffineTransform at, float lw) { + private float userSpaceLineWidth(AffineTransform at, float lw) { float widthScale; @@ -295,17 +302,17 @@ return (lw / widthScale); } - final void strokeTo(final RendererContext rdrCtx, - Shape src, - AffineTransform at, - float width, - NormMode norm, - int caps, - int join, - float miterlimit, - float[] dashes, - float dashphase, - PathConsumer2D pc2d) + void strokeTo(final RendererContext rdrCtx, + Shape src, + AffineTransform at, + float width, + NormMode norm, + int caps, + int join, + float miterlimit, + float[] dashes, + float dashphase, + PathConsumer2D pc2d) { // We use strokerat so that in Stroker and Dasher we can work only // with the pre-transformation coordinates. This will repeat a lot of @@ -324,6 +331,7 @@ int dashLen = -1; boolean recycleDashes = false; + float scale = 1.0f; if (at != null && !at.isIdentity()) { final double a = at.getScaleX(); @@ -356,7 +364,7 @@ // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we // leave a bit of room for error. if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { - final float scale = (float) Math.sqrt(a*a + c*c); + scale = (float) Math.sqrt(a*a + c*c); if (dashes != null) { recycleDashes = true; @@ -394,16 +402,24 @@ at = null; } + final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; + + if (DO_TRACE_PATH) { + // trace Stroker: + pc2d = transformerPC2D.traceStroker(pc2d); + } + if (USE_SIMPLIFIER) { // Use simplifier after stroker before Renderer // to remove collinear segments (notably due to cap square) pc2d = rdrCtx.simplifier.init(pc2d); } - final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; + // deltaTransformConsumer may adjust the clip rectangle: pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat); - pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit); + // stroker will adjust the clip rectangle (width / miter limit): + pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale); if (dashes != null) { if (!recycleDashes) { @@ -411,9 +427,22 @@ } pc2d = rdrCtx.dasher.init(pc2d, dashes, dashLen, dashphase, recycleDashes); + } else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) { + if (DO_TRACE_PATH) { + pc2d = transformerPC2D.traceClosedPathDetector(pc2d); + } + + // If no dash and clip is enabled: + // detect closedPaths (polygons) for caps + pc2d = transformerPC2D.detectClosedPath(pc2d); } pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat); + if (DO_TRACE_PATH) { + // trace Input: + pc2d = transformerPC2D.traceInput(pc2d); + } + final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx, src.getPathIterator(at)); @@ -779,6 +808,19 @@ final RendererContext rdrCtx = getRendererContext(); try { + if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) { + // Define the initial clip bounds: + final float[] clipRect = rdrCtx.clipRect; + + clipRect[0] = clip.getLoY(); + clipRect[1] = clip.getLoY() + clip.getHeight(); + clipRect[2] = clip.getLoX(); + clipRect[3] = clip.getLoX() + clip.getWidth(); + + // Enable clipping: + rdrCtx.doClip = true; + } + // Test if at is identity: final AffineTransform _at = (at != null && !at.isIdentity()) ? at : null; @@ -790,18 +832,36 @@ final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx, s.getPathIterator(_at)); + final int windingRule = pi.getWindingRule(); + // note: Winding rule may be EvenOdd ONLY for fill operations ! r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), clip.getWidth(), clip.getHeight(), - pi.getWindingRule()); + windingRule); + + PathConsumer2D pc2d = r; + + if (DO_CLIP_FILL && rdrCtx.doClip) { + if (DO_TRACE_PATH) { + // trace Filler: + pc2d = rdrCtx.transformerPC2D.traceFiller(pc2d); + } + pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d); + } + + if (DO_TRACE_PATH) { + // trace Input: + pc2d = rdrCtx.transformerPC2D.traceInput(pc2d); + } // TODO: subdivide quad/cubic curves into monotonic curves ? - pathTo(rdrCtx, pi, r); + pathTo(rdrCtx, pi, pc2d); + } else { // draw shape with given stroke: r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), clip.getWidth(), clip.getHeight(), - PathIterator.WIND_NON_ZERO); + WIND_NON_ZERO); strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r); } @@ -824,12 +884,12 @@ } @Override - public final AATileGenerator getAATileGenerator(double x, double y, - double dx1, double dy1, - double dx2, double dy2, - double lw1, double lw2, - Region clip, - int[] bbox) + public AATileGenerator getAATileGenerator(double x, double y, + double dx1, double dy1, + double dx2, double dy2, + double lw1, double lw2, + Region clip, + int[] bbox) { // REMIND: Deal with large coordinates! double ldx1, ldy1, ldx2, ldy2; @@ -860,8 +920,8 @@ final RendererContext rdrCtx = getRendererContext(); try { r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), - clip.getWidth(), clip.getHeight(), - Renderer.WIND_EVEN_ODD); + clip.getWidth(), clip.getHeight(), + WIND_EVEN_ODD); r.moveTo((float) x, (float) y); r.lineTo((float) (x+dx1), (float) (y+dy1)); @@ -913,14 +973,14 @@ } static { - if (PathIterator.WIND_NON_ZERO != Renderer.WIND_NON_ZERO || - PathIterator.WIND_EVEN_ODD != Renderer.WIND_EVEN_ODD || - BasicStroke.JOIN_MITER != Stroker.JOIN_MITER || - BasicStroke.JOIN_ROUND != Stroker.JOIN_ROUND || - BasicStroke.JOIN_BEVEL != Stroker.JOIN_BEVEL || - BasicStroke.CAP_BUTT != Stroker.CAP_BUTT || - BasicStroke.CAP_ROUND != Stroker.CAP_ROUND || - BasicStroke.CAP_SQUARE != Stroker.CAP_SQUARE) + if (PathIterator.WIND_NON_ZERO != WIND_NON_ZERO || + PathIterator.WIND_EVEN_ODD != WIND_EVEN_ODD || + BasicStroke.JOIN_MITER != JOIN_MITER || + BasicStroke.JOIN_ROUND != JOIN_ROUND || + BasicStroke.JOIN_BEVEL != JOIN_BEVEL || + BasicStroke.CAP_BUTT != CAP_BUTT || + BasicStroke.CAP_ROUND != CAP_ROUND || + BasicStroke.CAP_SQUARE != CAP_SQUARE) { throw new InternalError("mismatched renderer constants"); } @@ -1045,6 +1105,8 @@ // optimisation parameters logInfo("sun.java2d.renderer.useSimplifier = " + MarlinConst.USE_SIMPLIFIER); + logInfo("sun.java2d.renderer.clip = " + + MarlinProperties.isDoClip()); // debugging parameters logInfo("sun.java2d.renderer.doStats = " --- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinTileGenerator.java 2017-11-14 22:42:42.206432666 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinTileGenerator.java 2017-11-14 22:42:42.078429883 +0100 @@ -31,12 +31,14 @@ final class MarlinTileGenerator implements AATileGenerator, MarlinConst { + private static final boolean DISABLE_BLEND = false; + private static final int MAX_TILE_ALPHA_SUM = TILE_W * TILE_H * MAX_AA_ALPHA; private static final int TH_AA_ALPHA_FILL_EMPTY = ((MAX_AA_ALPHA + 1) / 3); // 33% private static final int TH_AA_ALPHA_FILL_FULL = ((MAX_AA_ALPHA + 1) * 2 / 3); // 66% - private static final int FILL_TILE_W = TILE_W >> 1; // half tile width + private static final int FILL_TILE_W = Math.max(16, TILE_W >> 2); // 1/4th tile width static { if (MAX_TILE_ALPHA_SUM <= 0) { @@ -141,6 +143,10 @@ */ @Override public int getTypicalAlpha() { + if (DISABLE_BLEND) { + // always return empty tiles to disable blending operations + return 0x00; + } int al = cache.alphaSumInTile(x); // Note: if we have a filled rectangle that doesn't end on a tile // border, we could still return 0xff, even though al!=maxTileAlphaSum --- old/src/java.desktop/share/classes/sun/java2d/marlin/Renderer.java 2017-11-14 22:42:42.514439363 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Renderer.java 2017-11-14 22:42:42.382436494 +0100 @@ -47,6 +47,9 @@ static final int SUBPIXEL_MASK_X = SUBPIXEL_POSITIONS_X - 1; static final int SUBPIXEL_MASK_Y = SUBPIXEL_POSITIONS_Y - 1; + static final float RDR_OFFSET_X = 0.5f / SUBPIXEL_SCALE_X; + static final float RDR_OFFSET_Y = 0.5f / SUBPIXEL_SCALE_Y; + // number of subpixels corresponding to a tile line private static final int SUBPIXEL_TILE = TILE_H << SUBPIXEL_LG_POSITIONS_Y; @@ -58,9 +61,6 @@ // crossing capacity = edges count / 4 ~ 1024 static final int INITIAL_CROSSING_COUNT = INITIAL_EDGES_COUNT >> 2; - public static final int WIND_EVEN_ODD = 0; - public static final int WIND_NON_ZERO = 1; - // common to all types of input path segments. // OFFSET as bytes // only integer values: @@ -672,7 +672,7 @@ } @Override - public void moveTo(float pix_x0, float pix_y0) { + public void moveTo(final float pix_x0, final float pix_y0) { closePath(); final float sx = tosubpixx(pix_x0); final float sy = tosubpixy(pix_y0); @@ -683,7 +683,7 @@ } @Override - public void lineTo(float pix_x1, float pix_y1) { + public void lineTo(final float pix_x1, final float pix_y1) { final float x1 = tosubpixx(pix_x1); final float y1 = tosubpixy(pix_y1); addLine(x0, y0, x1, y1); @@ -692,24 +692,26 @@ } @Override - public void curveTo(float x1, float y1, - float x2, float y2, - float x3, float y3) + public void curveTo(final float pix_x1, final float pix_y1, + final float pix_x2, final float pix_y2, + final float pix_x3, final float pix_y3) { - final float xe = tosubpixx(x3); - final float ye = tosubpixy(y3); - curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), - tosubpixx(x2), tosubpixy(y2), xe, ye); + final float xe = tosubpixx(pix_x3); + final float ye = tosubpixy(pix_y3); + curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), + tosubpixx(pix_x2), tosubpixy(pix_y2), xe, ye); curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; } @Override - public void quadTo(float x1, float y1, float x2, float y2) { - final float xe = tosubpixx(x2); - final float ye = tosubpixy(y2); - curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), xe, ye); + public void quadTo(final float pix_x1, final float pix_y1, + final float pix_x2, final float pix_y2) + { + final float xe = tosubpixx(pix_x2); + final float ye = tosubpixy(pix_y2); + curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), xe, ye); quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; @@ -717,9 +719,11 @@ @Override public void closePath() { - addLine(x0, y0, sx0, sy0); - x0 = sx0; - y0 = sy0; + if (x0 != sx0 || y0 != sy0) { + addLine(x0, y0, sx0, sy0); + x0 = sx0; + y0 = sy0; + } } @Override --- old/src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java 2017-11-14 22:42:42.830446235 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java 2017-11-14 22:42:42.698443365 +0100 @@ -75,16 +75,22 @@ final MarlinCache cache; // flag indicating the shape is stroked (1) or filled (0) int stroking = 0; + // flag indicating to clip the shape + boolean doClip = false; + // flag indicating if the path is closed or not (in advance) to handle properly caps + boolean closedPath = false; + // clip rectangle (ymin, ymax, xmin, xmax): + final float[] clipRect = new float[4]; // Array caches: /* clean int[] cache (zero-filled) = 5 refs */ private final IntArrayCache cleanIntCache = new IntArrayCache(true, 5); - /* dirty int[] cache = 4 refs */ - private final IntArrayCache dirtyIntCache = new IntArrayCache(false, 4); - /* dirty float[] cache = 3 refs */ - private final FloatArrayCache dirtyFloatCache = new FloatArrayCache(false, 3); - /* dirty byte[] cache = 1 ref */ - private final ByteArrayCache dirtyByteCache = new ByteArrayCache(false, 1); + /* dirty int[] cache = 5 refs */ + private final IntArrayCache dirtyIntCache = new IntArrayCache(false, 5); + /* dirty float[] cache = 4 refs (2 polystack) */ + private final FloatArrayCache dirtyFloatCache = new FloatArrayCache(false, 4); + /* dirty byte[] cache = 2 ref (2 polystack) */ + private final ByteArrayCache dirtyByteCache = new ByteArrayCache(false, 2); // RendererContext statistics final RendererStats stats; @@ -116,7 +122,7 @@ nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(float6); // MarlinRenderingEngine.TransformingPathConsumer2D - transformerPC2D = new TransformingPathConsumer2D(); + transformerPC2D = new TransformingPathConsumer2D(this); // Renderer: cache = new MarlinCache(this); @@ -138,7 +144,10 @@ } stats.totalOffHeap = 0L; } - stroking = 0; + stroking = 0; + doClip = false; + closedPath = false; + // if context is maked as DIRTY: if (dirty) { // may happen if an exception if thrown in the pipeline processing: @@ -164,7 +173,7 @@ // create a new Path2D ? if (p2d == null) { - p2d = new Path2D.Float(Path2D.WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K + p2d = new Path2D.Float(WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K // update weak reference: refPath2D = new WeakReference(p2d); --- old/src/java.desktop/share/classes/sun/java2d/marlin/RendererStats.java 2017-11-14 22:42:43.138452931 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/RendererStats.java 2017-11-14 22:42:43.006450062 +0100 @@ -66,10 +66,6 @@ = new StatLong("cache.rowAAChunk"); final StatLong stat_cache_tiles = new StatLong("cache.tiles"); - final StatLong stat_rdr_poly_stack_curves - = new StatLong("renderer.poly.stack.curves"); - final StatLong stat_rdr_poly_stack_types - = new StatLong("renderer.poly.stack.types"); final StatLong stat_rdr_addLine = new StatLong("renderer.addLine"); final StatLong stat_rdr_addLine_skip @@ -106,15 +102,21 @@ = new StatLong("renderer.crossings.bsearch"); final StatLong stat_rdr_crossings_msorts = new StatLong("renderer.crossings.msorts"); + final StatLong stat_str_polystack_curves + = new StatLong("stroker.polystack.curves"); + final StatLong stat_str_polystack_types + = new StatLong("stroker.polystack.types"); + final StatLong stat_cpd_polystack_curves + = new StatLong("closedPathDetector.polystack.curves"); + final StatLong stat_cpd_polystack_types + = new StatLong("closedPathDetector.polystack.types"); + final StatLong stat_pcf_idxstack_indices + = new StatLong("pathClipFilter.stack.indices"); // growable arrays final StatLong stat_array_dasher_dasher = new StatLong("array.dasher.dasher.d_float"); final StatLong stat_array_dasher_firstSegmentsBuffer = new StatLong("array.dasher.firstSegmentsBuffer.d_float"); - final StatLong stat_array_stroker_polystack_curves - = new StatLong("array.stroker.polystack.curves.d_float"); - final StatLong stat_array_stroker_polystack_curveTypes - = new StatLong("array.stroker.polystack.curveTypes.d_byte"); final StatLong stat_array_marlincache_rowAAChunk = new StatLong("array.marlincache.rowAAChunk.resize"); final StatLong stat_array_marlincache_touchedTile @@ -133,11 +135,19 @@ = new StatLong("array.renderer.edgePtrs.int"); final StatLong stat_array_renderer_aux_edgePtrs = new StatLong("array.renderer.aux_edgePtrs.int"); + final StatLong stat_array_str_polystack_curves + = new StatLong("array.stroker.polystack.curves.d_float"); + final StatLong stat_array_str_polystack_types + = new StatLong("array.stroker.polystack.curveTypes.d_byte"); + final StatLong stat_array_cpd_polystack_curves + = new StatLong("array.closedPathDetector.polystack.curves.d_float"); + final StatLong stat_array_cpd_polystack_types + = new StatLong("array.closedPathDetector.polystack.curveTypes.d_byte"); + final StatLong stat_array_pcf_idxstack_indices + = new StatLong("array.pathClipFilter.stack.indices.d_int"); // histograms final Histogram hist_rdr_edges_count = new Histogram("renderer.edges.count"); - final Histogram hist_rdr_poly_stack_curves - = new Histogram("renderer.polystack.curves"); final Histogram hist_rdr_crossings = new Histogram("renderer.crossings"); final Histogram hist_rdr_crossings_ratio @@ -148,6 +158,8 @@ = new Histogram("renderer.crossings.msorts"); final Histogram hist_rdr_crossings_msorts_adds = new Histogram("renderer.crossings.msorts.adds"); + final Histogram hist_str_polystack_curves + = new Histogram("stroker.polystack.curves"); final Histogram hist_tile_generator_alpha = new Histogram("tile_generator.alpha"); final Histogram hist_tile_generator_encoding @@ -158,13 +170,15 @@ = new Histogram("tile_generator.encoding.ratio"); final Histogram hist_tile_generator_encoding_runLen = new Histogram("tile_generator.encoding.runLen"); + final Histogram hist_cpd_polystack_curves + = new Histogram("closedPathDetector.polystack.curves"); + final Histogram hist_pcf_idxstack_indices + = new Histogram("pathClipFilter.stack.indices"); // all stats final StatLong[] statistics = new StatLong[]{ stat_cache_rowAA, stat_cache_rowAAChunk, stat_cache_tiles, - stat_rdr_poly_stack_types, - stat_rdr_poly_stack_curves, stat_rdr_addLine, stat_rdr_addLine_skip, stat_rdr_curveBreak, @@ -183,8 +197,12 @@ stat_rdr_crossings_sorts, stat_rdr_crossings_bsearch, stat_rdr_crossings_msorts, + stat_str_polystack_types, + stat_str_polystack_curves, + stat_cpd_polystack_curves, + stat_cpd_polystack_types, + stat_pcf_idxstack_indices, hist_rdr_edges_count, - hist_rdr_poly_stack_curves, hist_rdr_crossings, hist_rdr_crossings_ratio, hist_rdr_crossings_adds, @@ -195,10 +213,11 @@ hist_tile_generator_encoding_dist, hist_tile_generator_encoding_ratio, hist_tile_generator_encoding_runLen, + hist_str_polystack_curves, + hist_cpd_polystack_curves, + hist_pcf_idxstack_indices, stat_array_dasher_dasher, stat_array_dasher_firstSegmentsBuffer, - stat_array_stroker_polystack_curves, - stat_array_stroker_polystack_curveTypes, stat_array_marlincache_rowAAChunk, stat_array_marlincache_touchedTile, stat_array_renderer_alphaline, @@ -207,7 +226,12 @@ stat_array_renderer_edgeBuckets, stat_array_renderer_edgeBucketCounts, stat_array_renderer_edgePtrs, - stat_array_renderer_aux_edgePtrs + stat_array_renderer_aux_edgePtrs, + stat_array_str_polystack_curves, + stat_array_str_polystack_types, + stat_array_cpd_polystack_curves, + stat_array_cpd_polystack_types, + stat_array_pcf_idxstack_indices }; // monitors final Monitor mon_pre_getAATileGenerator --- old/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java 2017-11-14 22:42:43.438459453 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java 2017-11-14 22:42:43.310456671 +0100 @@ -28,6 +28,7 @@ import java.util.Arrays; import sun.awt.geom.PathConsumer2D; +import sun.java2d.marlin.Helpers.PolyStack; // TODO: some of the arithmetic here is too verbose and prone to hard to // debug typos. We should consider making a small Point/Vector class that @@ -38,42 +39,16 @@ private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad private static final int CLOSE = 2; - /** - * Constant value for join style. - */ - public static final int JOIN_MITER = 0; - - /** - * Constant value for join style. - */ - public static final int JOIN_ROUND = 1; - - /** - * Constant value for join style. - */ - public static final int JOIN_BEVEL = 2; - - /** - * Constant value for end cap style. - */ - public static final int CAP_BUTT = 0; - - /** - * Constant value for end cap style. - */ - public static final int CAP_ROUND = 1; - - /** - * Constant value for end cap style. - */ - public static final int CAP_SQUARE = 2; - // pisces used to use fixed point arithmetic with 16 decimal digits. I // didn't want to change the values of the constant below when I converted // it to floating point, so that's why the divisions by 2^16 are there. private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f; - private static final float C = 0.5522847498307933f; + // kappa = (4/3) * (SQRT(2) - 1) + private static final float C = (float)(4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d); + + // SQRT(2) + private static final float SQRT_2 = (float)Math.sqrt(2.0d); private static final int MAX_N_CURVES = 11; @@ -120,6 +95,20 @@ // dirty curve final Curve curve; + // Bounds of the drawing region, at pixel precision. + private float[] clipRect; + + // the outcode of the current point + private int cOutCode = 0; + + // the outcode of the starting point + private int sOutCode = 0; + + // flag indicating if the path is opened (clipped) + private boolean opened = false; + // flag indicating if the starting point's cap is done + private boolean capStart = false; + /** * Constructs a Stroker. * @param rdrCtx per-thread renderer context @@ -127,7 +116,15 @@ Stroker(final RendererContext rdrCtx) { this.rdrCtx = rdrCtx; - this.reverse = new PolyStack(rdrCtx); + this.reverse = (rdrCtx.stats != null) ? + new PolyStack(rdrCtx, + rdrCtx.stats.stat_str_polystack_types, + rdrCtx.stats.stat_str_polystack_curves, + rdrCtx.stats.hist_str_polystack_curves, + rdrCtx.stats.stat_array_str_polystack_curves, + rdrCtx.stats.stat_array_str_polystack_types) + : new PolyStack(rdrCtx); + this.curve = rdrCtx.curve; } @@ -143,13 +140,15 @@ * JOIN_MITER, JOIN_ROUND or * JOIN_BEVEL. * @param miterLimit the desired miter limit + * @param scale scaling factor applied to clip boundaries * @return this instance */ - Stroker init(PathConsumer2D pc2d, - float lineWidth, - int capStyle, - int joinStyle, - float miterLimit) + Stroker init(final PathConsumer2D pc2d, + final float lineWidth, + final int capStyle, + final int joinStyle, + final float miterLimit, + final float scale) { this.out = pc2d; @@ -158,13 +157,45 @@ this.capStyle = capStyle; this.joinStyle = joinStyle; - float limit = miterLimit * lineWidth2; + final float limit = miterLimit * lineWidth2; this.miterLimitSq = limit * limit; this.prev = CLOSE; rdrCtx.stroking = 1; + if (rdrCtx.doClip) { + // Adjust the clipping rectangle with the stroker margin (miter limit, width) + float rdrOffX = 0.0f, rdrOffY = 0.0f; + float margin = lineWidth2; + + if (capStyle == CAP_SQUARE) { + margin *= SQRT_2; + } + if ((joinStyle == JOIN_MITER) && (margin < limit)) { + margin = limit; + } + if (scale != 1.0f) { + margin *= scale; + rdrOffX = scale * Renderer.RDR_OFFSET_X; + rdrOffY = scale * Renderer.RDR_OFFSET_Y; + } + // add a small rounding error: + margin += 1e-3f; + + // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY + // adjust clip rectangle (ymin, ymax, xmin, xmax): + final float[] _clipRect = rdrCtx.clipRect; + _clipRect[0] -= margin - rdrOffY; + _clipRect[1] += margin + rdrOffY; + _clipRect[2] -= margin - rdrOffX; + _clipRect[3] += margin + rdrOffX; + this.clipRect = _clipRect; + } else { + this.clipRect = null; + this.cOutCode = 0; + this.sOutCode = 0; + } return this; // fluent API } @@ -175,6 +206,9 @@ void dispose() { reverse.dispose(); + opened = false; + capStart = false; + if (DO_CLEAN_DIRTY) { // Force zero-fill dirty arrays: Arrays.fill(offset0, 0.0f); @@ -445,19 +479,62 @@ } @Override - public void moveTo(float x0, float y0) { - if (prev == DRAWING_OP_TO) { - finish(); + public void moveTo(final float x0, final float y0) { + moveTo(x0, y0, cOutCode); + // update starting point: + this.sx0 = x0; + this.sy0 = y0; + this.sdx = 1.0f; + this.sdy = 0.0f; + this.opened = false; + this.capStart = false; + + if (clipRect != null) { + final int outcode = Helpers.outcode(x0, y0, clipRect); + this.cOutCode = outcode; + this.sOutCode = outcode; + } + } + + private void moveTo(final float x0, final float y0, + final int outcode) + { + if (prev == MOVE_TO) { + this.cx0 = x0; + this.cy0 = y0; + } else { + if (prev == DRAWING_OP_TO) { + finish(outcode); + } + this.prev = MOVE_TO; + this.cx0 = x0; + this.cy0 = y0; + this.cdx = 1.0f; + this.cdy = 0.0f; } - this.sx0 = this.cx0 = x0; - this.sy0 = this.cy0 = y0; - this.cdx = this.sdx = 1.0f; - this.cdy = this.sdy = 0.0f; - this.prev = MOVE_TO; } @Override - public void lineTo(float x1, float y1) { + public void lineTo(final float x1, final float y1) { + lineTo(x1, y1, false); + } + + private void lineTo(final float x1, final float y1, + final boolean force) + { + final int outcode0 = this.cOutCode; + if (!force && clipRect != null) { + final int outcode1 = Helpers.outcode(x1, y1, clipRect); + this.cOutCode = outcode1; + + // basic rejection criteria + if ((outcode0 & outcode1) != 0) { + moveTo(x1, y1, outcode0); + opened = true; + return; + } + } + float dx = x1 - cx0; float dy = y1 - cy0; if (dx == 0.0f && dy == 0.0f) { @@ -467,7 +544,7 @@ final float mx = offset0[0]; final float my = offset0[1]; - drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my); + drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0); emitLineTo(cx0 + mx, cy0 + my); emitLineTo( x1 + mx, y1 + my); @@ -475,43 +552,65 @@ emitLineToRev(cx0 - mx, cy0 - my); emitLineToRev( x1 - mx, y1 - my); - this.cmx = mx; - this.cmy = my; - this.cdx = dx; - this.cdy = dy; + this.prev = DRAWING_OP_TO; this.cx0 = x1; this.cy0 = y1; - this.prev = DRAWING_OP_TO; + this.cdx = dx; + this.cdy = dy; + this.cmx = mx; + this.cmy = my; } @Override public void closePath() { - if (prev != DRAWING_OP_TO) { + // distinguish empty path at all vs opened path ? + if (prev != DRAWING_OP_TO && !opened) { if (prev == CLOSE) { return; } emitMoveTo(cx0, cy0 - lineWidth2); - this.cmx = this.smx = 0.0f; - this.cmy = this.smy = -lineWidth2; - this.cdx = this.sdx = 1.0f; - this.cdy = this.sdy = 0.0f; - finish(); + + this.sdx = 1.0f; + this.sdy = 0.0f; + this.cdx = 1.0f; + this.cdy = 0.0f; + + this.smx = 0.0f; + this.smy = -lineWidth2; + this.cmx = 0.0f; + this.cmy = -lineWidth2; + + finish(cOutCode); return; } - if (cx0 != sx0 || cy0 != sy0) { - lineTo(sx0, sy0); - } + // basic acceptance criteria + if ((sOutCode & cOutCode) == 0) { + if (cx0 != sx0 || cy0 != sy0) { + lineTo(sx0, sy0, true); + } - drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy); + drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode); - emitLineTo(sx0 + smx, sy0 + smy); + emitLineTo(sx0 + smx, sy0 + smy); - emitMoveTo(sx0 - smx, sy0 - smy); + if (opened) { + emitLineTo(sx0 - smx, sy0 - smy); + } else { + emitMoveTo(sx0 - smx, sy0 - smy); + } + } + // Ignore caps like finish(false) emitReverse(); this.prev = CLOSE; - emitClose(); + + if (opened) { + // do not emit close + opened = false; + } else { + emitClose(); + } } private void emitReverse() { @@ -521,7 +620,7 @@ @Override public void pathDone() { if (prev == DRAWING_OP_TO) { - finish(); + finish(cOutCode); } out.pathDone(); @@ -534,23 +633,39 @@ dispose(); } - private void finish() { - if (capStyle == CAP_ROUND) { - drawRoundCap(cx0, cy0, cmx, cmy); - } else if (capStyle == CAP_SQUARE) { - emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); - emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); - } + private void finish(final int outcode) { + // Problem: impossible to guess if the path will be closed in advance + // i.e. if caps must be drawn or not ? + // Solution: use the ClosedPathDetector before Stroker to determine + // if the path is a closed path or not + if (!rdrCtx.closedPath) { + if (outcode == 0) { + // current point = end's cap: + if (capStyle == CAP_ROUND) { + drawRoundCap(cx0, cy0, cmx, cmy); + } else if (capStyle == CAP_SQUARE) { + emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); + emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); + } + } + emitReverse(); - emitReverse(); + if (!capStart) { + capStart = true; - if (capStyle == CAP_ROUND) { - drawRoundCap(sx0, sy0, -smx, -smy); - } else if (capStyle == CAP_SQUARE) { - emitLineTo(sx0 + smy - smx, sy0 - smx - smy); - emitLineTo(sx0 + smy + smx, sy0 - smx + smy); + if (sOutCode == 0) { + // starting point = initial cap: + if (capStyle == CAP_ROUND) { + drawRoundCap(sx0, sy0, -smx, -smy); + } else if (capStyle == CAP_SQUARE) { + emitLineTo(sx0 + smy - smx, sy0 - smx - smy); + emitLineTo(sx0 + smy + smx, sy0 - smx + smy); + } + } + } + } else { + emitReverse(); } - emitClose(); } @@ -622,23 +737,28 @@ float x0, float y0, float dx, float dy, float omx, float omy, - float mx, float my) + float mx, float my, + final int outcode) { if (prev != DRAWING_OP_TO) { emitMoveTo(x0 + mx, y0 + my); - this.sdx = dx; - this.sdy = dy; - this.smx = mx; - this.smy = my; + if (!opened) { + this.sdx = dx; + this.sdy = dy; + this.smx = mx; + this.smy = my; + } } else { - boolean cw = isCW(pdx, pdy, dx, dy); - if (joinStyle == JOIN_MITER) { - drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); - } else if (joinStyle == JOIN_ROUND) { - drawRoundJoin(x0, y0, - omx, omy, - mx, my, cw, - ROUND_JOIN_THRESHOLD); + final boolean cw = isCW(pdx, pdy, dx, dy); + if (outcode == 0) { + if (joinStyle == JOIN_MITER) { + drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); + } else if (joinStyle == JOIN_ROUND) { + drawRoundJoin(x0, y0, + omx, omy, + mx, my, cw, + ROUND_JOIN_THRESHOLD); + } } emitLineTo(x0, y0, !cw); } @@ -943,10 +1063,29 @@ return ret; } - @Override public void curveTo(float x1, float y1, - float x2, float y2, - float x3, float y3) - { + @Override + public void curveTo(final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3) + { + final int outcode0 = this.cOutCode; + if (clipRect != null) { + final int outcode3 = Helpers.outcode(x3, y3, clipRect); + this.cOutCode = outcode3; + + if ((outcode0 & outcode3) != 0) { + final int outcode1 = Helpers.outcode(x1, y1, clipRect); + final int outcode2 = Helpers.outcode(x2, y2, clipRect); + + // basic rejection criteria + if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) { + moveTo(x3, y3, outcode0); + opened = true; + return; + } + } + } + final float[] mid = middle; mid[0] = cx0; mid[1] = cy0; @@ -955,7 +1094,7 @@ mid[6] = x3; mid[7] = y3; // need these so we can update the state at the end of this method - final float xf = mid[6], yf = mid[7]; + final float xf = x3, yf = y3; float dxs = mid[2] - mid[0]; float dys = mid[3] - mid[1]; float dxf = mid[6] - mid[4]; @@ -981,6 +1120,10 @@ } if (dxs == 0.0f && dys == 0.0f) { // this happens if the "curve" is just a point + // fix outcode0 for lineTo() call: + if (clipRect != null) { + this.cOutCode = outcode0; + } lineTo(mid[0], mid[1]); return; } @@ -999,7 +1142,7 @@ } computeOffset(dxs, dys, lineWidth2, offset0); - drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]); + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2); @@ -1034,16 +1177,36 @@ emitLineToRev(r[kind - 2], r[kind - 1]); } - this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; - this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; - this.cdx = dxf; - this.cdy = dyf; + this.prev = DRAWING_OP_TO; this.cx0 = xf; this.cy0 = yf; - this.prev = DRAWING_OP_TO; + this.cdx = dxf; + this.cdy = dyf; + this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; + this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; } - @Override public void quadTo(float x1, float y1, float x2, float y2) { + @Override + public void quadTo(final float x1, final float y1, + final float x2, final float y2) + { + final int outcode0 = this.cOutCode; + if (clipRect != null) { + final int outcode2 = Helpers.outcode(x2, y2, clipRect); + this.cOutCode = outcode2; + + if ((outcode0 & outcode2) != 0) { + final int outcode1 = Helpers.outcode(x1, y1, clipRect); + + // basic rejection criteria + if ((outcode0 & outcode1 & outcode2) != 0) { + moveTo(x2, y2, outcode0); + opened = true; + return; + } + } + } + final float[] mid = middle; mid[0] = cx0; mid[1] = cy0; @@ -1051,7 +1214,7 @@ mid[4] = x2; mid[5] = y2; // need these so we can update the state at the end of this method - final float xf = mid[4], yf = mid[5]; + final float xf = x2, yf = y2; float dxs = mid[2] - mid[0]; float dys = mid[3] - mid[1]; float dxf = mid[4] - mid[2]; @@ -1062,6 +1225,10 @@ } if (dxs == 0.0f && dys == 0.0f) { // this happens if the "curve" is just a point + // fix outcode0 for lineTo() call: + if (clipRect != null) { + this.cOutCode = outcode0; + } lineTo(mid[0], mid[1]); return; } @@ -1079,7 +1246,7 @@ } computeOffset(dxs, dys, lineWidth2, offset0); - drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]); + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2); @@ -1114,214 +1281,16 @@ emitLineToRev(r[kind - 2], r[kind - 1]); } - this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; - this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; - this.cdx = dxf; - this.cdy = dyf; + this.prev = DRAWING_OP_TO; this.cx0 = xf; this.cy0 = yf; - this.prev = DRAWING_OP_TO; + this.cdx = dxf; + this.cdy = dyf; + this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; + this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; } @Override public long getNativeConsumer() { throw new InternalError("Stroker doesn't use a native consumer"); } - - // a stack of polynomial curves where each curve shares endpoints with - // adjacent ones. - static final class PolyStack { - private static final byte TYPE_LINETO = (byte) 0; - private static final byte TYPE_QUADTO = (byte) 1; - private static final byte TYPE_CUBICTO = (byte) 2; - - // curves capacity = edges count (8192) = edges x 2 (coords) - private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1; - - // types capacity = edges count (4096) - private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT; - - float[] curves; - int end; - byte[] curveTypes; - int numCurves; - - // per-thread renderer context - final RendererContext rdrCtx; - - // curves ref (dirty) - final FloatArrayCache.Reference curves_ref; - // curveTypes ref (dirty) - final ByteArrayCache.Reference curveTypes_ref; - - // used marks (stats only) - int curveTypesUseMark; - int curvesUseMark; - - /** - * Constructor - * @param rdrCtx per-thread renderer context - */ - PolyStack(final RendererContext rdrCtx) { - this.rdrCtx = rdrCtx; - - curves_ref = rdrCtx.newDirtyFloatArrayRef(INITIAL_CURVES_COUNT); // 32K - curves = curves_ref.initial; - - curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K - curveTypes = curveTypes_ref.initial; - numCurves = 0; - end = 0; - - if (DO_STATS) { - curveTypesUseMark = 0; - curvesUseMark = 0; - } - } - - /** - * Disposes this PolyStack: - * clean up before reusing this instance - */ - void dispose() { - end = 0; - numCurves = 0; - - if (DO_STATS) { - rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark); - rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark); - rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark); - - // reset marks - curveTypesUseMark = 0; - curvesUseMark = 0; - } - - // Return arrays: - // curves and curveTypes are kept dirty - curves = curves_ref.putArray(curves); - curveTypes = curveTypes_ref.putArray(curveTypes); - } - - private void ensureSpace(final int n) { - // use substraction to avoid integer overflow: - if (curves.length - end < n) { - if (DO_STATS) { - rdrCtx.stats.stat_array_stroker_polystack_curves - .add(end + n); - } - curves = curves_ref.widenArray(curves, end, end + n); - } - if (curveTypes.length <= numCurves) { - if (DO_STATS) { - rdrCtx.stats.stat_array_stroker_polystack_curveTypes - .add(numCurves + 1); - } - curveTypes = curveTypes_ref.widenArray(curveTypes, - numCurves, - numCurves + 1); - } - } - - void pushCubic(float x0, float y0, - float x1, float y1, - float x2, float y2) - { - ensureSpace(6); - curveTypes[numCurves++] = TYPE_CUBICTO; - // we reverse the coordinate order to make popping easier - final float[] _curves = curves; - int e = end; - _curves[e++] = x2; _curves[e++] = y2; - _curves[e++] = x1; _curves[e++] = y1; - _curves[e++] = x0; _curves[e++] = y0; - end = e; - } - - void pushQuad(float x0, float y0, - float x1, float y1) - { - ensureSpace(4); - curveTypes[numCurves++] = TYPE_QUADTO; - final float[] _curves = curves; - int e = end; - _curves[e++] = x1; _curves[e++] = y1; - _curves[e++] = x0; _curves[e++] = y0; - end = e; - } - - void pushLine(float x, float y) { - ensureSpace(2); - curveTypes[numCurves++] = TYPE_LINETO; - curves[end++] = x; curves[end++] = y; - } - - void popAll(PathConsumer2D io) { - if (DO_STATS) { - // update used marks: - if (numCurves > curveTypesUseMark) { - curveTypesUseMark = numCurves; - } - if (end > curvesUseMark) { - curvesUseMark = end; - } - } - final byte[] _curveTypes = curveTypes; - final float[] _curves = curves; - int nc = numCurves; - int e = end; - - while (nc != 0) { - switch(_curveTypes[--nc]) { - case TYPE_LINETO: - e -= 2; - io.lineTo(_curves[e], _curves[e+1]); - continue; - case TYPE_QUADTO: - e -= 4; - io.quadTo(_curves[e+0], _curves[e+1], - _curves[e+2], _curves[e+3]); - continue; - case TYPE_CUBICTO: - e -= 6; - io.curveTo(_curves[e+0], _curves[e+1], - _curves[e+2], _curves[e+3], - _curves[e+4], _curves[e+5]); - continue; - default: - } - } - numCurves = 0; - end = 0; - } - - @Override - public String toString() { - String ret = ""; - int nc = numCurves; - int last = end; - int len; - while (nc != 0) { - switch(curveTypes[--nc]) { - case TYPE_LINETO: - len = 2; - ret += "line: "; - break; - case TYPE_QUADTO: - len = 4; - ret += "quad: "; - break; - case TYPE_CUBICTO: - len = 6; - ret += "cubic: "; - break; - default: - len = 0; - } - last -= len; - ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len)) - + "\n"; - } - return ret; - } - } } --- old/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java 2017-11-14 22:42:43.774466758 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java 2017-11-14 22:42:43.646463975 +0100 @@ -28,24 +28,73 @@ import sun.awt.geom.PathConsumer2D; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; +import sun.java2d.marlin.Helpers.IndexStack; +import sun.java2d.marlin.Helpers.PolyStack; final class TransformingPathConsumer2D { - TransformingPathConsumer2D() { - // used by RendererContext - } + private final RendererContext rdrCtx; + + // recycled ClosedPathDetector instance from detectClosedPath() + private final ClosedPathDetector cpDetector; + + // recycled PathClipFilter instance from pathClipper() + private final PathClipFilter pathClipper; - // recycled PathConsumer2D instance from wrapPath2d() + // recycled PathConsumer2D instance from wrapPath2D() private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper(); - PathConsumer2D wrapPath2d(Path2D.Float p2d) + // recycled PathConsumer2D instances from deltaTransformConsumer() + private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter(); + private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter(); + + // recycled PathConsumer2D instances from inverseDeltaTransformConsumer() + private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter(); + private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter(); + + // 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"); + + TransformingPathConsumer2D(final RendererContext rdrCtx) { + // used by RendererContext + this.rdrCtx = rdrCtx; + this.cpDetector = new ClosedPathDetector(rdrCtx); + this.pathClipper = new PathClipFilter(rdrCtx); + } + + PathConsumer2D wrapPath2D(Path2D.Float p2d) { return wp_Path2DWrapper.init(p2d); } - // recycled PathConsumer2D instances from deltaTransformConsumer() - private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter(); - private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter(); + PathConsumer2D traceInput(PathConsumer2D out) { + return tracerInput.init(out); + } + + PathConsumer2D traceClosedPathDetector(PathConsumer2D out) { + return tracerCPDetector.init(out); + } + + PathConsumer2D traceFiller(PathConsumer2D out) { + return tracerFiller.init(out); + } + + PathConsumer2D traceStroker(PathConsumer2D out) { + return tracerStroker.init(out); + } + + PathConsumer2D detectClosedPath(PathConsumer2D out) + { + return cpDetector.init(out); + } + + PathConsumer2D pathClipper(PathConsumer2D out) + { + return pathClipper.init(out); + } PathConsumer2D deltaTransformConsumer(PathConsumer2D out, AffineTransform at) @@ -53,25 +102,98 @@ if (at == null) { return out; } - float mxx = (float) at.getScaleX(); - float mxy = (float) at.getShearX(); - float myx = (float) at.getShearY(); - float myy = (float) at.getScaleY(); + final float mxx = (float) at.getScaleX(); + final float mxy = (float) at.getShearX(); + final float myx = (float) at.getShearY(); + final float myy = (float) at.getScaleY(); if (mxy == 0.0f && myx == 0.0f) { if (mxx == 1.0f && myy == 1.0f) { return out; } else { + // Scale only + if (rdrCtx.doClip) { + // adjust clip rectangle (ymin, ymax, xmin, xmax): + adjustClipScale(rdrCtx.clipRect, mxx, myy); + } return dt_DeltaScaleFilter.init(out, mxx, myy); } } else { + if (rdrCtx.doClip) { + // adjust clip rectangle (ymin, ymax, xmin, xmax): + adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy); + } return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy); } } - // recycled PathConsumer2D instances from inverseDeltaTransformConsumer() - private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter(); - private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter(); + private static void adjustClipOffset(final float[] clipRect) { + clipRect[0] += Renderer.RDR_OFFSET_Y; + clipRect[1] += Renderer.RDR_OFFSET_Y; + clipRect[2] += Renderer.RDR_OFFSET_X; + clipRect[3] += Renderer.RDR_OFFSET_X; + } + + private static void adjustClipScale(final float[] clipRect, + final float mxx, final float myy) + { + adjustClipOffset(clipRect); + + // Adjust the clipping rectangle (iv_DeltaScaleFilter): + clipRect[0] /= myy; + clipRect[1] /= myy; + clipRect[2] /= mxx; + clipRect[3] /= mxx; + } + + private static void adjustClipInverseDelta(final float[] clipRect, + final float mxx, final float mxy, + final float myx, final float myy) + { + adjustClipOffset(clipRect); + + // Adjust the clipping rectangle (iv_DeltaTransformFilter): + final float det = mxx * myy - mxy * myx; + final float imxx = myy / det; + final float imxy = -mxy / det; + final float imyx = -myx / det; + final float imyy = mxx / det; + + float xmin, xmax, ymin, ymax; + float x, y; + // xmin, ymin: + x = clipRect[2] * imxx + clipRect[0] * imxy; + y = clipRect[2] * imyx + clipRect[0] * imyy; + + xmin = xmax = x; + ymin = ymax = y; + + // xmax, ymin: + x = clipRect[3] * imxx + clipRect[0] * imxy; + y = clipRect[3] * imyx + clipRect[0] * imyy; + + if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } + if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } + + // xmin, ymax: + x = clipRect[2] * imxx + clipRect[1] * imxy; + y = clipRect[2] * imyx + clipRect[1] * imyy; + + if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } + if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } + + // xmax, ymax: + x = clipRect[3] * imxx + clipRect[1] * imxy; + y = clipRect[3] * imyx + clipRect[1] * imyy; + + if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } + if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } + + clipRect[0] = ymin; + clipRect[1] = ymax; + clipRect[2] = xmin; + clipRect[3] = xmax; + } PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out, AffineTransform at) @@ -91,7 +213,7 @@ return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy); } } else { - float det = mxx * myy - mxy * myx; + final float det = mxx * myy - mxy * myx; return iv_DeltaTransformFilter.init(out, myy / det, -mxy / det, @@ -100,7 +222,6 @@ } } - static final class DeltaScaleFilter implements PathConsumer2D { private PathConsumer2D out; private float sx, sy; @@ -271,6 +392,429 @@ } @Override + public long getNativeConsumer() { + throw new InternalError("Not using a native peer"); + } + } + + static final class ClosedPathDetector implements PathConsumer2D { + + private final RendererContext rdrCtx; + private final PolyStack stack; + + private PathConsumer2D out; + + ClosedPathDetector(final RendererContext rdrCtx) { + this.rdrCtx = rdrCtx; + this.stack = (rdrCtx.stats != null) ? + new PolyStack(rdrCtx, + rdrCtx.stats.stat_cpd_polystack_types, + rdrCtx.stats.stat_cpd_polystack_curves, + rdrCtx.stats.hist_cpd_polystack_curves, + rdrCtx.stats.stat_array_cpd_polystack_curves, + rdrCtx.stats.stat_array_cpd_polystack_types) + : new PolyStack(rdrCtx); + } + + ClosedPathDetector init(PathConsumer2D out) { + this.out = out; + return this; // fluent API + } + + /** + * Disposes this instance: + * clean up before reusing this instance + */ + void dispose() { + stack.dispose(); + } + + @Override + public void pathDone() { + // previous path is not closed: + finish(false); + out.pathDone(); + + // TODO: fix possible leak if exception happened + // Dispose this instance: + dispose(); + } + + @Override + public void closePath() { + // path is closed + finish(true); + out.closePath(); + } + + @Override + public void moveTo(float x0, float y0) { + // previous path is not closed: + finish(false); + out.moveTo(x0, y0); + } + + private void finish(final boolean closed) { + rdrCtx.closedPath = closed; + stack.pullAll(out); + } + + @Override + public void lineTo(float x1, float y1) { + stack.pushLine(x1, y1); + } + + @Override + public void curveTo(float x3, float y3, + float x2, float y2, + float x1, float y1) + { + stack.pushCubic(x1, y1, x2, y2, x3, y3); + } + + @Override + public void quadTo(float x2, float y2, float x1, float y1) { + stack.pushQuad(x1, y1, x2, y2); + } + + @Override + public long getNativeConsumer() { + throw new InternalError("Not using a native peer"); + } + } + + static final class PathClipFilter implements PathConsumer2D { + + private PathConsumer2D out; + + // Bounds of the drawing region, at pixel precision. + private final float[] clipRect; + + private final float[] corners = new float[8]; + private boolean init_corners = false; + + private final IndexStack stack; + + // the current outcode of the current sub path + private int cOutCode = 0; + + // 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 float cx0, cy0; + + PathClipFilter(final RendererContext 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 PathConsumer2D out) { + this.out = out; + + // Adjust the clipping rectangle with the renderer offsets + final float rdrOffX = Renderer.RDR_OFFSET_X; + final float rdrOffY = Renderer.RDR_OFFSET_Y; + + // add a small rounding error: + final float margin = 1e-3f; + + final float[] _clipRect = this.clipRect; + _clipRect[0] -= margin - rdrOffY; + _clipRect[1] += margin + rdrOffY; + _clipRect[2] -= margin - rdrOffX; + _clipRect[3] += margin + rdrOffX; + + this.init_corners = true; + this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R; + + return this; // fluent API + } + + /** + * Disposes this instance: + * clean up before reusing this instance + */ + void dispose() { + stack.dispose(); + } + + private void finishPath() { + if (outside) { + // criteria: inside or totally outside ? + if (gOutCode == 0) { + finish(); + } else { + this.outside = false; + stack.reset(); + } + } + } + + private void finish() { + this.outside = false; + + if (!stack.isEmpty()) { + if (init_corners) { + init_corners = false; + + final float[] _corners = corners; + final float[] _clipRect = clipRect; + // 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 pathDone() { + finishPath(); + + out.pathDone(); + + // TODO: fix possible leak if exception happened + // Dispose this instance: + dispose(); + } + + @Override + public void closePath() { + finishPath(); + + out.closePath(); + } + + @Override + public void moveTo(final float x0, final float y0) { + finishPath(); + + final int outcode = Helpers.outcode(x0, y0, clipRect); + this.cOutCode = outcode; + this.outside = false; + out.moveTo(x0, y0); + } + + @Override + public void lineTo(final float xe, final float ye) { + final int outcode0 = this.cOutCode; + final int outcode1 = Helpers.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) + { + // corner or cross-boundary on left or right side: + if ((outcode0 != outcode1) + && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0)) + { + // combine outcodes: + final int mergeCode = (outcode0 | outcode1); + final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B; + final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R; + 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 + } + } + } + } + + @Override + public void curveTo(final float x1, final float y1, + final float x2, final float y2, + final float xe, final float ye) + { + final int outcode0 = this.cOutCode; + final int outcode3 = Helpers.outcode(xe, ye, clipRect); + this.cOutCode = outcode3; + + int sideCode = outcode0 & outcode3; + + if (sideCode == 0) { + this.gOutCode = 0; + } else { + sideCode &= Helpers.outcode(x1, y1, clipRect); + sideCode &= Helpers.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 float x1, final float y1, + final float xe, final float ye) + { + final int outcode0 = this.cOutCode; + final int outcode2 = Helpers.outcode(xe, ye, clipRect); + this.cOutCode = outcode2; + + int sideCode = outcode0 & outcode2; + + if (sideCode == 0) { + this.gOutCode = 0; + } else { + sideCode &= Helpers.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); + } + + @Override + public long getNativeConsumer() { + throw new InternalError("Not using a native peer"); + } + } + + static final class PathTracer implements PathConsumer2D { + private final String prefix; + private PathConsumer2D out; + + PathTracer(String name) { + this.prefix = name + ": "; + } + + PathTracer init(PathConsumer2D out) { + this.out = out; + return this; // fluent API + } + + @Override + public void moveTo(float x0, float y0) { + log("moveTo (" + x0 + ", " + y0 + ')'); + out.moveTo(x0, y0); + } + + @Override + public void lineTo(float x1, float y1) { + log("lineTo (" + x1 + ", " + y1 + ')'); + out.lineTo(x1, y1); + } + + @Override + public void curveTo(float x1, float y1, + float x2, float y2, + float x3, float y3) + { + log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ')'); + out.curveTo(x1, y1, x2, y2, x3, y3); + } + + @Override + public void quadTo(float x1, float y1, float x2, float y2) { + log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')'); + out.quadTo(x1, y1, x2, y2); + } + + @Override + public void closePath() { + log("closePath"); + out.closePath(); + } + + @Override + public void pathDone() { + log("pathDone"); + out.pathDone(); + } + + private void log(final String message) { + System.out.println(prefix + message); + } + + @Override public long getNativeConsumer() { throw new InternalError("Not using a native peer"); } --- old/src/java.desktop/share/classes/sun/java2d/marlin/Version.java 2017-11-14 22:42:44.162475191 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Version.java 2017-11-14 22:42:44.034472409 +0100 @@ -27,7 +27,7 @@ public final class Version { - private static final String VERSION = "marlin-0.7.5-Unsafe-OpenJDK"; + private static final String VERSION = "marlin-0.8.2-Unsafe-OpenJDK"; public static String getVersion() { return VERSION; --- /dev/null 2017-11-14 21:21:11.022959889 +0100 +++ new/test/jdk/sun/java2d/marlin/ClipShapeTests.java 2017-11-14 22:42:44.422480842 +0100 @@ -0,0 +1,822 @@ +/* + * Copyright (c) 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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferInt; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageOutputStream; + +/** + * @test + * @bug 8184429 + * @summary Verifies that Marlin rendering generates the same + * images with and without clipping optimization with all possible + * stroke (cap/join) and fill modes (EO rules) + * Use the following setting to use Float or Double variant: + * -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine + * -Dsun.java2d.renderer=org.marlin.pisces.DMarlinRenderingEngine + * @run main ClipShapeTests + * @ignore tests that take a long time (huge number of polygons) + * @run main/othervm -ms1g -mx1g ClipShapeTests -slow + */ +public final class ClipShapeTests { + + static final boolean TEST_STROKER = true; + static final boolean TEST_FILLER = true; + + static final boolean USE_DASHES = false; // really slower + + static int NUM_TESTS = 500; + static final int TESTW = 100; + static final int TESTH = 100; + static final ShapeMode SHAPE_MODE = ShapeMode.NINE_LINE_POLYS; + static final boolean SHAPE_REPEAT = true; + + // dump path on console: + static final boolean DUMP_SHAPE = true; + + static final boolean SHOW_DETAILS = true; + static final boolean SHOW_OUTLINE = true; + static final boolean SHOW_POINTS = true; + static final boolean SHOW_INFO = false; + + static final int MAX_SHOW_FRAMES = 10; + + static final boolean FIXED_SEED = false; + static final double RAND_SCALE = 3.0; + static final double RANDW = TESTW * RAND_SCALE; + static final double OFFW = (TESTW - RANDW) / 2.0; + static final double RANDH = TESTH * RAND_SCALE; + static final double OFFH = (TESTH - RANDH) / 2.0; + + static enum ShapeMode { + TWO_CUBICS, + FOUR_QUADS, + FIVE_LINE_POLYS, + NINE_LINE_POLYS, + FIFTY_LINE_POLYS, + MIXED + } + + static final long SEED = 1666133789L; + static final Random RANDOM; + + static { + // disable static clipping setting: + System.setProperty("sun.java2d.renderer.clip", "false"); + System.setProperty("sun.java2d.renderer.clip.runtime.enable", "true"); + + // Fixed seed to avoid any difference between runs: + RANDOM = new Random(SEED); + } + + static final File OUTPUT_DIR = new File("."); + + /** + * Test + * @param args + */ + public static void main(String[] args) { + boolean runSlowTests = (args.length != 0 && "-slow".equals(args[0])); + + if (runSlowTests) { + NUM_TESTS = 10000; // 10000 or 100000 (very slow) + } + + // First display which renderer is tested: + System.setProperty("sun.java2d.renderer.verbose", "true"); + + System.out.println("ClipShapeTests: image = " + TESTW + " x " + TESTH); + + int failures = 0; + final long start = System.nanoTime(); + try { + // TODO: test affine transforms ? + + if (TEST_STROKER) { + final float[][] dashArrays = (USE_DASHES) + ? new float[][]{null, new float[]{1f, 2f}} + : new float[][]{null}; + + // Stroker tests: + for (float width = 0.1f; width < 110f; width *= 5f) { + for (int cap = 0; cap <= 2; cap++) { + for (int join = 0; join <= 2; join++) { + for (float[] dashes : dashArrays) { + failures += paintPaths(new TestSetup(SHAPE_MODE, false, width, cap, join, dashes)); + failures += paintPaths(new TestSetup(SHAPE_MODE, true, width, cap, join, dashes)); + } + } + } + } + } + + if (TEST_FILLER) { + // Filler tests: + failures += paintPaths(new TestSetup(SHAPE_MODE, false, Path2D.WIND_NON_ZERO)); + failures += paintPaths(new TestSetup(SHAPE_MODE, true, Path2D.WIND_NON_ZERO)); + + failures += paintPaths(new TestSetup(SHAPE_MODE, false, Path2D.WIND_EVEN_ODD)); + failures += paintPaths(new TestSetup(SHAPE_MODE, true, Path2D.WIND_EVEN_ODD)); + } + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + System.out.println("main: duration= " + (1e-6 * (System.nanoTime() - start)) + " ms."); + if (failures != 0) { + throw new RuntimeException("Clip test failures : " + failures); + } + } + + public static int paintPaths(final TestSetup ts) throws IOException { + final long start = System.nanoTime(); + + if (FIXED_SEED) { + // Reset seed for random numbers: + RANDOM.setSeed(SEED); + } + + System.out.println("paintPaths: " + NUM_TESTS + + " paths (" + SHAPE_MODE + ") - setup: " + ts); + + final boolean fill = !ts.isStroke(); + final Path2D p2d = new Path2D.Double(ts.windingRule); + + final BufferedImage imgOn = newImage(TESTW, TESTH); + final Graphics2D g2dOn = initialize(imgOn, ts); + + final BufferedImage imgOff = newImage(TESTW, TESTH); + final Graphics2D g2dOff = initialize(imgOff, ts); + + final BufferedImage imgDiff = newImage(TESTW, TESTH); + + final DiffContext globalCtx = new DiffContext("All tests"); + + int nd = 0; + try { + final DiffContext testCtx = new DiffContext("Test"); + BufferedImage diffImage; + + for (int n = 0; n < NUM_TESTS; n++) { + genShape(p2d, ts); + + // Runtime clip setting OFF: + paintShape(p2d, g2dOff, fill, false); + + // Runtime clip setting ON: + paintShape(p2d, g2dOn, fill, true); + + /* compute image difference if possible */ + diffImage = computeDiffImage(testCtx, imgOn, imgOff, imgDiff, globalCtx); + + final String testName = "Setup_" + ts.id + "_test_" + n; + + if (diffImage != null) { + nd++; + + final double ratio = (100.0 * testCtx.histPix.count) / testCtx.histAll.count; + System.out.println("Diff ratio: " + testName + " = " + trimTo3Digits(ratio) + " %"); + + if (false) { + saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png"); + } + + if (DUMP_SHAPE) { + dumpShape(p2d); + } + if (nd < MAX_SHOW_FRAMES) { + if (SHOW_DETAILS) { + paintShapeDetails(g2dOff, p2d); + paintShapeDetails(g2dOn, p2d); + } + + saveImage(imgOff, OUTPUT_DIR, testName + "-off.png"); + saveImage(imgOn, OUTPUT_DIR, testName + "-on.png"); + saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png"); + } + } + } + } finally { + g2dOff.dispose(); + g2dOn.dispose(); + + if (nd != 0) { + System.out.println("paintPaths: " + NUM_TESTS + " paths - " + + "Number of differences = " + nd + + " ratio = " + (100f * nd) / NUM_TESTS + " %"); + } + + globalCtx.dump(); + } + System.out.println("paintPaths: duration= " + (1e-6 * (System.nanoTime() - start)) + " ms."); + return nd; + } + + private static void paintShape(final Path2D p2d, final Graphics2D g2d, + final boolean fill, final boolean clip) { + reset(g2d); + + setClip(g2d, clip); + + if (fill) { + g2d.fill(p2d); + } else { + g2d.draw(p2d); + } + } + + private static Graphics2D initialize(final BufferedImage img, + final TestSetup ts) { + final Graphics2D g2d = (Graphics2D) img.getGraphics(); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_PURE); + + if (ts.isStroke()) { + g2d.setStroke(createStroke(ts)); + } + g2d.setColor(Color.GRAY); + + return g2d; + } + + private static void reset(final Graphics2D g2d) { + // Disable antialiasing: + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_OFF); + g2d.setBackground(Color.WHITE); + g2d.clearRect(0, 0, TESTW, TESTH); + } + + private static void setClip(final Graphics2D g2d, final boolean clip) { + // Enable antialiasing: + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + // Enable or Disable clipping: + System.setProperty("sun.java2d.renderer.clip.runtime", (clip) ? "true" : "false"); + } + + static void genShape(final Path2D p2d, final TestSetup ts) { + p2d.reset(); + + final int end = (SHAPE_REPEAT) ? 2 : 1; + + for (int p = 0; p < end; p++) { + p2d.moveTo(randX(), randY()); + + switch (ts.shapeMode) { + case MIXED: + case FIFTY_LINE_POLYS: + case NINE_LINE_POLYS: + case FIVE_LINE_POLYS: + p2d.lineTo(randX(), randY()); + p2d.lineTo(randX(), randY()); + p2d.lineTo(randX(), randY()); + p2d.lineTo(randX(), randY()); + if (ts.shapeMode == ShapeMode.FIVE_LINE_POLYS) { + // And an implicit close makes 5 lines + break; + } + p2d.lineTo(randX(), randY()); + p2d.lineTo(randX(), randY()); + p2d.lineTo(randX(), randY()); + p2d.lineTo(randX(), randY()); + if (ts.shapeMode == ShapeMode.NINE_LINE_POLYS) { + // And an implicit close makes 9 lines + break; + } + if (ts.shapeMode == ShapeMode.FIFTY_LINE_POLYS) { + for (int i = 0; i < 41; i++) { + p2d.lineTo(randX(), randY()); + } + // And an implicit close makes 50 lines + break; + } + case TWO_CUBICS: + p2d.curveTo(randX(), randY(), randX(), randY(), randX(), randY()); + p2d.curveTo(randX(), randY(), randX(), randY(), randX(), randY()); + if (ts.shapeMode == ShapeMode.TWO_CUBICS) { + break; + } + case FOUR_QUADS: + p2d.quadTo(randX(), randY(), randX(), randY()); + p2d.quadTo(randX(), randY(), randX(), randY()); + p2d.quadTo(randX(), randY(), randX(), randY()); + p2d.quadTo(randX(), randY(), randX(), randY()); + if (ts.shapeMode == ShapeMode.FOUR_QUADS) { + break; + } + default: + } + + if (ts.closed) { + p2d.closePath(); + } + } + } + + static final float POINT_RADIUS = 2f; + static final float LINE_WIDTH = 1f; + + static final Stroke OUTLINE_STROKE = new BasicStroke(LINE_WIDTH); + static final int COLOR_ALPHA = 128; + static final Color COLOR_MOVETO = new Color(255, 0, 0, COLOR_ALPHA); + static final Color COLOR_LINETO_ODD = new Color(0, 0, 255, COLOR_ALPHA); + static final Color COLOR_LINETO_EVEN = new Color(0, 255, 0, COLOR_ALPHA); + + static final Ellipse2D.Float ELL_POINT = new Ellipse2D.Float(); + + private static void paintShapeDetails(final Graphics2D g2d, final Shape shape) { + + final Stroke oldStroke = g2d.getStroke(); + final Color oldColor = g2d.getColor(); + + setClip(g2d, false); + + if (SHOW_OUTLINE) { + g2d.setStroke(OUTLINE_STROKE); + g2d.setColor(COLOR_LINETO_ODD); + g2d.draw(shape); + } + + final float[] coords = new float[6]; + float px, py; + + int nMove = 0; + int nLine = 0; + int n = 0; + + for (final PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) { + int type = it.currentSegment(coords); + switch (type) { + case PathIterator.SEG_MOVETO: + if (SHOW_POINTS) { + g2d.setColor(COLOR_MOVETO); + } + break; + case PathIterator.SEG_LINETO: + if (SHOW_POINTS) { + g2d.setColor((nLine % 2 == 0) ? COLOR_LINETO_ODD : COLOR_LINETO_EVEN); + } + nLine++; + break; + case PathIterator.SEG_CLOSE: + continue; + default: + System.out.println("unsupported segment type= " + type); + continue; + } + px = coords[0]; + py = coords[1]; + + if (SHOW_INFO) { + System.out.println("point[" + (n++) + "|seg=" + type + "]: " + px + " " + py); + } + + if (SHOW_POINTS) { + ELL_POINT.setFrame(px - POINT_RADIUS, py - POINT_RADIUS, + POINT_RADIUS * 2f, POINT_RADIUS * 2f); + g2d.fill(ELL_POINT); + } + } + if (SHOW_INFO) { + System.out.println("Path moveTo=" + nMove + ", lineTo=" + nLine); + System.out.println("--------------------------------------------------"); + } + + g2d.setStroke(oldStroke); + g2d.setColor(oldColor); + } + + private static void dumpShape(final Shape shape) { + final float[] coords = new float[6]; + + for (final PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) { + final int type = it.currentSegment(coords); + switch (type) { + case PathIterator.SEG_MOVETO: + System.out.println("p2d.moveTo(" + coords[0] + ", " + coords[1] + ");"); + break; + case PathIterator.SEG_LINETO: + System.out.println("p2d.lineTo(" + coords[0] + ", " + coords[1] + ");"); + break; + case PathIterator.SEG_CLOSE: + System.out.println("p2d.closePath();"); + break; + default: + System.out.println("// Unsupported segment type= " + type); + } + } + System.out.println("--------------------------------------------------"); + } + + public static double randX() { + return RANDOM.nextDouble() * RANDW + OFFW; + } + + public static double randY() { + return RANDOM.nextDouble() * RANDH + OFFH; + } + + private static BasicStroke createStroke(final TestSetup ts) { + return new BasicStroke(ts.strokeWidth, ts.strokeCap, ts.strokeJoin, 10.0f, ts.dashes, 0.0f); + } + + private final static class TestSetup { + + static final AtomicInteger COUNT = new AtomicInteger(); + + final int id; + final ShapeMode shapeMode; + final boolean closed; + // stroke + final float strokeWidth; + final int strokeCap; + final int strokeJoin; + final float[] dashes; + // fill + final int windingRule; + + TestSetup(ShapeMode shapeMode, final boolean closed, + final float strokeWidth, final int strokeCap, final int strokeJoin, final float[] dashes) { + this.id = COUNT.incrementAndGet(); + this.shapeMode = shapeMode; + this.closed = closed; + this.strokeWidth = strokeWidth; + this.strokeCap = strokeCap; + this.strokeJoin = strokeJoin; + this.dashes = dashes; + this.windingRule = Path2D.WIND_NON_ZERO; + } + + TestSetup(ShapeMode shapeMode, final boolean closed, final int windingRule) { + this.id = COUNT.incrementAndGet(); + this.shapeMode = shapeMode; + this.closed = closed; + this.strokeWidth = 0f; + this.strokeCap = this.strokeJoin = -1; // invalid + this.dashes = null; + this.windingRule = windingRule; + } + + boolean isStroke() { + return this.strokeWidth > 0f; + } + + @Override + public String toString() { + return "TestSetup{id=" + id + ", shapeMode=" + shapeMode + ", closed=" + closed + + ", strokeWidth=" + strokeWidth + ", strokeCap=" + strokeCap + ", strokeJoin=" + strokeJoin + + ((dashes != null) ? ", dashes: " + Arrays.toString(dashes) : "") + + ", windingRule=" + windingRule + '}'; + } + } + + // --- utilities --- + private static final boolean USE_GRAPHICS_ACCELERATION = false; + private static final boolean NORMALIZE_DIFF = true; + private static final int DCM_ALPHA_MASK = 0xff000000; + + private final static GraphicsConfiguration gc = (USE_GRAPHICS_ACCELERATION) + ? GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration() : null; + + public static BufferedImage newImage(final int w, final int h) { + if (USE_GRAPHICS_ACCELERATION) { + return gc.createCompatibleImage(w, h); + } + return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE); + } + + public static BufferedImage computeDiffImage(final DiffContext localCtx, final BufferedImage tstImage, final BufferedImage refImage, + final BufferedImage diffImage, final DiffContext globalCtx) { + final int width = tstImage.getWidth(); + final int height = tstImage.getHeight(); + + final DataBuffer refDataBuffer = refImage.getRaster().getDataBuffer(); + final DataBuffer tstDataBuffer = tstImage.getRaster().getDataBuffer(); + + if (refDataBuffer instanceof DataBufferInt) { + final BufferedImage dImage = (diffImage != null) ? diffImage : newImage(width, height); + + final int aRefPix[] = ((DataBufferInt) refDataBuffer).getData(); + final int aTstPix[] = ((DataBufferInt) tstDataBuffer).getData(); + final int aDifPix[] = ((DataBufferInt) dImage.getRaster().getDataBuffer()).getData(); + + // reset local diff context: + localCtx.reset(); + + int dr, dg, db, v, max = 0; + for (int i = 0, len = aRefPix.length; i < len; i++) { + + /* put condition out of loop */ + // grayscale diff: + dg = (r(aRefPix[i]) + g(aRefPix[i]) + b(aRefPix[i])) + - (r(aTstPix[i]) + g(aTstPix[i]) + b(aTstPix[i])); + + // max difference on grayscale values: + v = (int) Math.ceil(Math.abs(dg / 3.0)); + + if (v > max) { + max = v; + } + aDifPix[i] = toInt(v, v, v); + + localCtx.add(v); + globalCtx.add(v); + } + + if (!localCtx.isDiff()) { + return null; + } + + if (NORMALIZE_DIFF) { + /* normalize diff image vs mean(diff) */ + if ((max > 0) && (max < 255)) { + final float factor = 255f / max; + for (int i = 0, len = aDifPix.length; i < len; i++) { + v = (int) Math.ceil(factor * b(aDifPix[i])); + aDifPix[i] = toInt(v, v, v); + } + } + } + + return dImage; + } + return null; + } + + public static void saveImage(final BufferedImage image, final File resDirectory, final String imageFileName) throws IOException { + final Iterator itWriters = ImageIO.getImageWritersByFormatName("PNG"); + if (itWriters.hasNext()) { + final ImageWriter writer = itWriters.next(); + + final ImageWriteParam writerParams = writer.getDefaultWriteParam(); + writerParams.setProgressiveMode(ImageWriteParam.MODE_DISABLED); + + final File imgFile = new File(resDirectory, imageFileName); + + if (!imgFile.exists() || imgFile.canWrite()) { + System.out.println("saveImage: saving image as PNG [" + imgFile + "]..."); + imgFile.delete(); + + // disable cache in temporary files: + ImageIO.setUseCache(false); + + final long start = System.nanoTime(); + + // PNG uses already buffering: + final ImageOutputStream imgOutStream = ImageIO.createImageOutputStream(new FileOutputStream(imgFile)); + + writer.setOutput(imgOutStream); + try { + writer.write(null, new IIOImage(image, null, null), writerParams); + } finally { + imgOutStream.close(); + + final long time = System.nanoTime() - start; + System.out.println("saveImage: duration= " + (time / 1000000l) + " ms."); + } + } + } + } + + public static int r(final int v) { + return (v >> 16 & 0xff); + } + + public static int g(final int v) { + return (v >> 8 & 0xff); + } + + public static int b(final int v) { + return (v & 0xff); + } + + public static int clamp127(final int v) { + return (v < 128) ? (v > -127 ? (v + 127) : 0) : 255; + } + + public static int toInt(final int r, final int g, final int b) { + return DCM_ALPHA_MASK | (r << 16) | (g << 8) | b; + } + + /* stats */ + static class StatInteger { + + public final String name; + public long count = 0l; + public long sum = 0l; + public long min = Integer.MAX_VALUE; + public long max = Integer.MIN_VALUE; + + StatInteger(String name) { + this.name = name; + } + + void reset() { + count = 0l; + sum = 0l; + min = Integer.MAX_VALUE; + max = Integer.MIN_VALUE; + } + + void add(int val) { + count++; + sum += val; + if (val < min) { + min = val; + } + if (val > max) { + max = val; + } + } + + void add(long val) { + count++; + sum += val; + if (val < min) { + min = val; + } + if (val > max) { + max = val; + } + } + + public final double average() { + return ((double) sum) / count; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(128); + toString(sb); + return sb.toString(); + } + + public final StringBuilder toString(final StringBuilder sb) { + sb.append(name).append("[n: ").append(count); + sb.append("] sum: ").append(sum).append(" avg: ").append(trimTo3Digits(average())); + sb.append(" [").append(min).append(" | ").append(max).append("]"); + return sb; + } + + } + + public final static class Histogram extends StatInteger { + + static final int BUCKET = 2; + static final int MAX = 20; + static final int LAST = MAX - 1; + static final int[] STEPS = new int[MAX]; + + static { + STEPS[0] = 0; + STEPS[1] = 1; + + for (int i = 2; i < MAX; i++) { + STEPS[i] = STEPS[i - 1] * BUCKET; + } +// System.out.println("Histogram.STEPS = " + Arrays.toString(STEPS)); + } + + static int bucket(int val) { + for (int i = 1; i < MAX; i++) { + if (val < STEPS[i]) { + return i - 1; + } + } + return LAST; + } + + private final StatInteger[] stats = new StatInteger[MAX]; + + public Histogram(String name) { + super(name); + for (int i = 0; i < MAX; i++) { + stats[i] = new StatInteger(String.format("%5s .. %5s", STEPS[i], ((i + 1 < MAX) ? STEPS[i + 1] : "~"))); + } + } + + @Override + final void reset() { + super.reset(); + for (int i = 0; i < MAX; i++) { + stats[i].reset(); + } + } + + @Override + final void add(int val) { + super.add(val); + stats[bucket(val)].add(val); + } + + @Override + final void add(long val) { + add((int) val); + } + + @Override + public final String toString() { + final StringBuilder sb = new StringBuilder(2048); + super.toString(sb).append(" { "); + + for (int i = 0; i < MAX; i++) { + if (stats[i].count != 0l) { + sb.append("\n ").append(stats[i].toString()); + } + } + + return sb.append(" }").toString(); + } + } + + /** + * Adjust the given double value to keep only 3 decimal digits + * @param value value to adjust + * @return double value with only 3 decimal digits + */ + public static double trimTo3Digits(final double value) { + return ((long) (1e3d * value)) / 1e3d; + } + + public static final class DiffContext { + + public final Histogram histAll; + public final Histogram histPix; + + public DiffContext(String name) { + histAll = new Histogram("All Pixels [" + name + "]"); + histPix = new Histogram("Diff Pixels [" + name + "]"); + } + + public void reset() { + histAll.reset(); + histPix.reset(); + } + + public void dump() { + if (isDiff()) { + System.out.println("Differences [" + histAll.name + "]:"); + System.out.println("Total [all pixels]:\n" + histAll.toString()); + System.out.println("Total [different pixels]:\n" + histPix.toString()); + } else { + System.out.println("No difference for [" + histAll.name + "]."); + } + } + + void add(int val) { + histAll.add(val); + if (val != 0) { + histPix.add(val); + } + } + + boolean isDiff() { + return histAll.sum != 0l; + } + } +}