--- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DRendererContext.java 2018-10-29 19:02:50.000000000 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DRendererContext.java 2018-10-29 19:02:50.000000000 +0100 @@ -79,6 +79,8 @@ boolean closedPath = false; // clip rectangle (ymin, ymax, xmin, xmax): public final double[] clipRect = new double[4]; + // clip inverse scale (mean) to adjust length checks + public double clipInvScale = 0.0d; // CurveBasicMonotonizer instance public final CurveBasicMonotonizer monotonizer; // CurveClipSplitter instance @@ -159,6 +161,7 @@ stroking = 0; doClip = false; closedPath = false; + clipInvScale = 0.0d; // if context is maked as DIRTY: if (dirty) { --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DStroker.java 2018-10-29 19:02:51.000000000 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DStroker.java 2018-10-29 19:02:50.000000000 +0100 @@ -139,9 +139,6 @@ * JOIN_MITER, JOIN_ROUND or * JOIN_BEVEL. * @param miterLimit the desired miter limit - * @param scale scaling factor applied to clip boundaries - * @param rdrOffX renderer's coordinate offset on X axis - * @param rdrOffY renderer's coordinate offset on Y axis * @param subdivideCurves true to indicate to subdivide curves, false if dasher does * @return this instance */ @@ -150,9 +147,6 @@ final int capStyle, final int joinStyle, final double miterLimit, - final double scale, - double rdrOffX, - double rdrOffY, final boolean subdivideCurves) { this.out = pc2d; @@ -181,23 +175,21 @@ if ((joinStyle == JOIN_MITER) && (margin < limit)) { margin = limit; } - if (scale != 1.0d) { - margin *= scale; - rdrOffX *= scale; - rdrOffY *= scale; - } - // 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; + _clipRect[0] -= margin; + _clipRect[1] += margin; + _clipRect[2] -= margin; + _clipRect[3] += margin; this.clipRect = _clipRect; + if (MarlinConst.DO_LOG_CLIP) { + MarlinUtils.logInfo("clipRect (stroker): " + + Arrays.toString(rdrCtx.clipRect)); + } + // initialize curve splitter here for stroker & dasher: if (DO_CLIP_SUBDIVIDER) { subdivide = subdivideCurves; --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DTransformingPathConsumer2D.java 2018-10-29 19:02:51.000000000 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DTransformingPathConsumer2D.java 2018-10-29 19:02:51.000000000 +0100 @@ -97,17 +97,13 @@ return cpDetector.init(out); } - public DPathConsumer2D pathClipper(DPathConsumer2D out, - final double rdrOffX, - final double rdrOffY) + public DPathConsumer2D pathClipper(DPathConsumer2D out) { - return pathClipper.init(out, rdrOffX, rdrOffY); + return pathClipper.init(out); } public DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out, - BaseTransform at, - final double rdrOffX, - final double rdrOffY) + BaseTransform at) { if (at == null) { return out; @@ -124,44 +120,55 @@ // Scale only if (rdrCtx.doClip) { // adjust clip rectangle (ymin, ymax, xmin, xmax): - adjustClipOffset(rdrCtx.clipRect, rdrOffX, rdrOffY); - adjustClipScale(rdrCtx.clipRect, mxx, myy); + rdrCtx.clipInvScale = adjustClipScale(rdrCtx.clipRect, + mxx, myy); } return dt_DeltaScaleFilter.init(out, mxx, myy); } } else { if (rdrCtx.doClip) { // adjust clip rectangle (ymin, ymax, xmin, xmax): - adjustClipOffset(rdrCtx.clipRect, rdrOffX, rdrOffY); - adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy); + rdrCtx.clipInvScale = adjustClipInverseDelta(rdrCtx.clipRect, + mxx, mxy, myx, myy); } return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy); } } - private static void adjustClipOffset(final double[] clipRect, - final double rdrOffX, - final double rdrOffY) - { - clipRect[0] += rdrOffY; - clipRect[1] += rdrOffY; - clipRect[2] += rdrOffX; - clipRect[3] += rdrOffX; - } - - private static void adjustClipScale(final double[] clipRect, - final double mxx, final double myy) + private static double adjustClipScale(final double[] clipRect, + final double mxx, final double myy) { // Adjust the clipping rectangle (iv_DeltaScaleFilter): - clipRect[0] /= myy; - clipRect[1] /= myy; - clipRect[2] /= mxx; - clipRect[3] /= mxx; + final double scaleY = 1.0d / myy; + clipRect[0] *= scaleY; + clipRect[1] *= scaleY; + + if (clipRect[1] < clipRect[0]) { + double tmp = clipRect[0]; + clipRect[0] = clipRect[1]; + clipRect[1] = tmp; + } + + final double scaleX = 1.0d / mxx; + clipRect[2] *= scaleX; + clipRect[3] *= scaleX; + + if (clipRect[3] < clipRect[2]) { + double tmp = clipRect[2]; + clipRect[2] = clipRect[3]; + clipRect[3] = tmp; + } + + if (MarlinConst.DO_LOG_CLIP) { + MarlinUtils.logInfo("clipRect (ClipScale): " + + Arrays.toString(clipRect)); + } + return 0.5d * (Math.abs(scaleX) + Math.abs(scaleY)); } - private static void adjustClipInverseDelta(final double[] clipRect, - final double mxx, final double mxy, - final double myx, final double myy) + private static double adjustClipInverseDelta(final double[] clipRect, + final double mxx, final double mxy, + final double myx, final double myy) { // Adjust the clipping rectangle (iv_DeltaTransformFilter): final double det = mxx * myy - mxy * myx; @@ -204,6 +211,16 @@ clipRect[1] = ymax; clipRect[2] = xmin; clipRect[3] = xmax; + + if (MarlinConst.DO_LOG_CLIP) { + MarlinUtils.logInfo("clipRect (ClipInverseDelta): " + + Arrays.toString(clipRect)); + } + + final double scaleX = Math.sqrt(imxx * imxx + imxy * imxy); + final double scaleY = Math.sqrt(imyx * imyx + imyy * imyy); + + return 0.5d * (scaleX + scaleY); } public DPathConsumer2D inverseDeltaTransformConsumer(DPathConsumer2D out, @@ -221,7 +238,7 @@ if (mxx == 1.0d && myy == 1.0d) { return out; } else { - return iv_DeltaScaleFilter.init(out, 1.0d/mxx, 1.0d/myy); + return iv_DeltaScaleFilter.init(out, 1.0d / mxx, 1.0d / myy); } } else { final double det = mxx * myy - mxy * myx; @@ -516,22 +533,9 @@ : new IndexStack(rdrCtx); } - PathClipFilter init(final DPathConsumer2D out, - final double rdrOffX, - final double rdrOffY) - { + PathClipFilter init(final DPathConsumer2D out) { this.out = out; - // add a small rounding error: - final double margin = 1e-3d; - - final double[] _clipRect = this.clipRect; - // Adjust the clipping rectangle with the renderer offsets - _clipRect[0] -= margin - rdrOffY; - _clipRect[1] += margin + rdrOffY; - _clipRect[2] -= margin - rdrOffX; - _clipRect[3] += margin + rdrOffX; - if (MarlinConst.DO_CLIP_SUBDIVIDER) { // adjust padded clip rectangle: curveSplitter.init(); @@ -849,6 +853,11 @@ private static final int MAX_N_CURVES = 3 * 4; + private final DRendererContext rdrCtx; + + // scaled length threshold: + private double minLength; + // clip rectangle (ymin, ymax, xmin, xmax): final double[] clipRect; @@ -866,12 +875,23 @@ private final DCurve curve; CurveClipSplitter(final DRendererContext rdrCtx) { + this.rdrCtx = rdrCtx; this.clipRect = rdrCtx.clipRect; this.curve = rdrCtx.curve; } void init() { this.init_clipRectPad = true; + + if (DO_CHECK_LENGTH) { + this.minLength = (this.rdrCtx.clipInvScale == 0.0d) ? LEN_TH + : (LEN_TH * this.rdrCtx.clipInvScale); + + if (MarlinConst.DO_LOG_CLIP) { + MarlinUtils.logInfo("CurveClipSplitter.minLength = " + + minLength); + } + } } private void initPaddedClip() { @@ -888,7 +908,7 @@ if (TRACE) { MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] " - + "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]"); + + "Y [" + _clipRectPad[0] + " .. " + _clipRectPad[1] +"]"); } } @@ -901,7 +921,7 @@ MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")"); } - if (DO_CHECK_LENGTH && DHelpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) { + if (DO_CHECK_LENGTH && DHelpers.fastLineLen(x0, y0, x1, y1) <= minLength) { return false; } @@ -922,7 +942,7 @@ MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")"); } - if (DO_CHECK_LENGTH && DHelpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) { + if (DO_CHECK_LENGTH && DHelpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= minLength) { return false; } @@ -945,7 +965,7 @@ MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")"); } - if (DO_CHECK_LENGTH && DHelpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) { + if (DO_CHECK_LENGTH && DHelpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= minLength) { return false; } @@ -973,7 +993,7 @@ outCodeOR, clipRectPad); if (TRACE) { - MarlinUtils.logInfo("nSplits: "+ nSplits); + MarlinUtils.logInfo("nSplits: " + nSplits); MarlinUtils.logInfo("subTs: " + Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits))); } if (nSplits == 0) { --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinConst.java 2018-10-29 19:02:52.000000000 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinConst.java 2018-10-29 19:02:52.000000000 +0100 @@ -82,9 +82,12 @@ static final boolean DO_CLIP_SUBDIVIDER = MarlinProperties.isDoClipSubdivider(); - // flag to enable logs related bounds checks + // flag to enable logs related to bounds checks static final boolean DO_LOG_BOUNDS = ENABLE_LOGS && false; + // flag to enable logs related to clip rect + static final boolean DO_LOG_CLIP = ENABLE_LOGS && false; + // Initial Array sizing (initial context capacity) ~ 450K // 4096 pixels (width) for initial capacity --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererContext.java 2018-10-29 19:02:53.000000000 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererContext.java 2018-10-29 19:02:52.000000000 +0100 @@ -79,6 +79,8 @@ boolean closedPath = false; // clip rectangle (ymin, ymax, xmin, xmax): public final float[] clipRect = new float[4]; + // clip inverse scale (mean) to adjust length checks + public float clipInvScale = 0.0f; // CurveBasicMonotonizer instance public final CurveBasicMonotonizer monotonizer; // CurveClipSplitter instance @@ -159,6 +161,7 @@ stroking = 0; doClip = false; closedPath = false; + clipInvScale = 0.0f; // if context is maked as DIRTY: if (dirty) { --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/Stroker.java 2018-10-29 19:02:53.000000000 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/Stroker.java 2018-10-29 19:02:53.000000000 +0100 @@ -141,9 +141,6 @@ * JOIN_MITER, JOIN_ROUND or * JOIN_BEVEL. * @param miterLimit the desired miter limit - * @param scale scaling factor applied to clip boundaries - * @param rdrOffX renderer's coordinate offset on X axis - * @param rdrOffY renderer's coordinate offset on Y axis * @param subdivideCurves true to indicate to subdivide curves, false if dasher does * @return this instance */ @@ -152,9 +149,6 @@ final int capStyle, final int joinStyle, final float miterLimit, - final float scale, - double rdrOffX, - double rdrOffY, final boolean subdivideCurves) { this.out = pc2d; @@ -183,23 +177,21 @@ if ((joinStyle == JOIN_MITER) && (margin < limit)) { margin = limit; } - if (scale != 1.0f) { - margin *= scale; - rdrOffX *= scale; - rdrOffY *= scale; - } - // 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; + _clipRect[0] -= margin; + _clipRect[1] += margin; + _clipRect[2] -= margin; + _clipRect[3] += margin; this.clipRect = _clipRect; + if (MarlinConst.DO_LOG_CLIP) { + MarlinUtils.logInfo("clipRect (stroker): " + + Arrays.toString(rdrCtx.clipRect)); + } + // initialize curve splitter here for stroker & dasher: if (DO_CLIP_SUBDIVIDER) { subdivide = subdivideCurves; --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/TransformingPathConsumer2D.java 2018-10-29 19:02:54.000000000 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/TransformingPathConsumer2D.java 2018-10-29 19:02:54.000000000 +0100 @@ -98,17 +98,12 @@ return cpDetector.init(out); } - public PathConsumer2D pathClipper(PathConsumer2D out, - final float rdrOffX, - final float rdrOffY) - { - return pathClipper.init(out, rdrOffX, rdrOffY); + public PathConsumer2D pathClipper(PathConsumer2D out) { + return pathClipper.init(out); } public PathConsumer2D deltaTransformConsumer(PathConsumer2D out, - BaseTransform at, - final float rdrOffX, - final float rdrOffY) + BaseTransform at) { if (at == null) { return out; @@ -125,44 +120,55 @@ // Scale only if (rdrCtx.doClip) { // adjust clip rectangle (ymin, ymax, xmin, xmax): - adjustClipOffset(rdrCtx.clipRect, rdrOffX, rdrOffY); - adjustClipScale(rdrCtx.clipRect, mxx, myy); + rdrCtx.clipInvScale = adjustClipScale(rdrCtx.clipRect, + mxx, myy); } return dt_DeltaScaleFilter.init(out, mxx, myy); } } else { if (rdrCtx.doClip) { // adjust clip rectangle (ymin, ymax, xmin, xmax): - adjustClipOffset(rdrCtx.clipRect, rdrOffX, rdrOffY); - adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy); + rdrCtx.clipInvScale = adjustClipInverseDelta(rdrCtx.clipRect, + mxx, mxy, myx, myy); } return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy); } } - private static void adjustClipOffset(final float[] clipRect, - final float rdrOffX, - final float rdrOffY) - { - clipRect[0] += rdrOffY; - clipRect[1] += rdrOffY; - clipRect[2] += rdrOffX; - clipRect[3] += rdrOffX; - } - - private static void adjustClipScale(final float[] clipRect, - final float mxx, final float myy) + private static float adjustClipScale(final float[] clipRect, + final float mxx, final float myy) { // Adjust the clipping rectangle (iv_DeltaScaleFilter): - clipRect[0] /= myy; - clipRect[1] /= myy; - clipRect[2] /= mxx; - clipRect[3] /= mxx; + final float scaleY = 1.0f / myy; + clipRect[0] *= scaleY; + clipRect[1] *= scaleY; + + if (clipRect[1] < clipRect[0]) { + float tmp = clipRect[0]; + clipRect[0] = clipRect[1]; + clipRect[1] = tmp; + } + + final float scaleX = 1.0f / mxx; + clipRect[2] *= scaleX; + clipRect[3] *= scaleX; + + if (clipRect[3] < clipRect[2]) { + float tmp = clipRect[2]; + clipRect[2] = clipRect[3]; + clipRect[3] = tmp; + } + + if (MarlinConst.DO_LOG_CLIP) { + MarlinUtils.logInfo("clipRect (ClipScale): " + + Arrays.toString(clipRect)); + } + return 0.5f * (Math.abs(scaleX) + Math.abs(scaleY)); } - private static void adjustClipInverseDelta(final float[] clipRect, - final float mxx, final float mxy, - final float myx, final float myy) + private static float adjustClipInverseDelta(final float[] clipRect, + final float mxx, final float mxy, + final float myx, final float myy) { // Adjust the clipping rectangle (iv_DeltaTransformFilter): final float det = mxx * myy - mxy * myx; @@ -205,6 +211,16 @@ clipRect[1] = ymax; clipRect[2] = xmin; clipRect[3] = xmax; + + if (MarlinConst.DO_LOG_CLIP) { + MarlinUtils.logInfo("clipRect (ClipInverseDelta): " + + Arrays.toString(clipRect)); + } + + final float scaleX = (float) Math.sqrt(imxx * imxx + imxy * imxy); + final float scaleY = (float) Math.sqrt(imyx * imyx + imyy * imyy); + + return 0.5f * (scaleX + scaleY); } public PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out, @@ -222,7 +238,7 @@ if (mxx == 1.0f && myy == 1.0f) { return out; } else { - return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy); + return iv_DeltaScaleFilter.init(out, 1.0f / mxx, 1.0f / myy); } } else { final float det = mxx * myy - mxy * myx; @@ -516,22 +532,9 @@ : new IndexStack(rdrCtx); } - PathClipFilter init(final PathConsumer2D out, - final double rdrOffX, - final double rdrOffY) - { + PathClipFilter init(final PathConsumer2D out) { this.out = out; - // add a small rounding error: - final float margin = 1e-3f; - - final float[] _clipRect = this.clipRect; - // Adjust the clipping rectangle with the renderer offsets - _clipRect[0] -= margin - rdrOffY; - _clipRect[1] += margin + rdrOffY; - _clipRect[2] -= margin - rdrOffX; - _clipRect[3] += margin + rdrOffX; - if (MarlinConst.DO_CLIP_SUBDIVIDER) { // adjust padded clip rectangle: curveSplitter.init(); @@ -849,6 +852,11 @@ private static final int MAX_N_CURVES = 3 * 4; + private final RendererContext rdrCtx; + + // scaled length threshold: + private float minLength; + // clip rectangle (ymin, ymax, xmin, xmax): final float[] clipRect; @@ -866,12 +874,23 @@ private final Curve curve; CurveClipSplitter(final RendererContext rdrCtx) { + this.rdrCtx = rdrCtx; this.clipRect = rdrCtx.clipRect; this.curve = rdrCtx.curve; } void init() { this.init_clipRectPad = true; + + if (DO_CHECK_LENGTH) { + this.minLength = (this.rdrCtx.clipInvScale == 0.0f) ? LEN_TH + : (LEN_TH * this.rdrCtx.clipInvScale); + + if (MarlinConst.DO_LOG_CLIP) { + MarlinUtils.logInfo("CurveClipSplitter.minLength = " + + minLength); + } + } } private void initPaddedClip() { @@ -888,7 +907,7 @@ if (TRACE) { MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] " - + "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]"); + + "Y [" + _clipRectPad[0] + " .. " + _clipRectPad[1] +"]"); } } @@ -901,7 +920,7 @@ MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")"); } - if (DO_CHECK_LENGTH && Helpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) { + if (DO_CHECK_LENGTH && Helpers.fastLineLen(x0, y0, x1, y1) <= minLength) { return false; } @@ -922,7 +941,7 @@ MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")"); } - if (DO_CHECK_LENGTH && Helpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) { + if (DO_CHECK_LENGTH && Helpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= minLength) { return false; } @@ -945,7 +964,7 @@ MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")"); } - if (DO_CHECK_LENGTH && Helpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) { + if (DO_CHECK_LENGTH && Helpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= minLength) { return false; } @@ -973,7 +992,7 @@ outCodeOR, clipRectPad); if (TRACE) { - MarlinUtils.logInfo("nSplits: "+ nSplits); + MarlinUtils.logInfo("nSplits: " + nSplits); MarlinUtils.logInfo("subTs: " + Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits))); } if (nSplits == 0) { --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/Version.java 2018-10-29 19:02:55.000000000 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/Version.java 2018-10-29 19:02:55.000000000 +0100 @@ -27,7 +27,7 @@ public final class Version { - private static final String VERSION = "marlinFX-0.9.2-Unsafe-OpenJDK"; + private static final String VERSION = "marlinFX-0.9.3-Unsafe-OpenJDK"; public static String getVersion() { return VERSION; --- old/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/DMarlinPrismUtils.java 2018-10-29 19:02:55.000000000 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/DMarlinPrismUtils.java 2018-10-29 19:02:55.000000000 +0100 @@ -38,7 +38,9 @@ import com.sun.marlin.DRendererContext; import com.sun.marlin.DStroker; import com.sun.marlin.DTransformingPathConsumer2D; +import com.sun.marlin.MarlinUtils; import com.sun.prism.BasicStroke; +import java.util.Arrays; public final class DMarlinPrismUtils { @@ -86,7 +88,6 @@ int dashLen = -1; boolean recycleDashes = false; - double scale = 1.0d; double width = lineWidth; float[] dashes = stroke.getDashArray(); double[] dashesD = null; @@ -112,7 +113,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))) { - scale = Math.sqrt(a*a + c*c); + final double scale = Math.sqrt(a*a + c*c); if (dashesD != null) { for (int i = 0; i < dashLen; i++) { @@ -147,15 +148,6 @@ tx = null; } - // Get renderer offsets: - double rdrOffX = 0.0d, rdrOffY = 0.0d; - - if (rdrCtx.doClip && (tx != null)) { - final DMarlinRenderer renderer = (DMarlinRenderer)out; - rdrOffX = renderer.getOffsetX(); - rdrOffY = renderer.getOffsetY(); - } - // Prepare the pipeline: DPathConsumer2D pc = out; @@ -173,12 +165,12 @@ } // deltaTransformConsumer may adjust the clip rectangle: - pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx, rdrOffX, rdrOffY); + pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx); // stroker will adjust the clip rectangle (width / miter limit): pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), stroke.getLineJoin(), stroke.getMiterLimit(), - scale, rdrOffX, rdrOffY, (dashesD == null)); + (dashesD == null)); // Curve Monotizer: rdrCtx.monotonizer.init(width); @@ -241,10 +233,26 @@ // Define the initial clip bounds: final double[] clipRect = rdrCtx.clipRect; - clipRect[0] = clip.y; - clipRect[1] = clip.y + clip.height; - clipRect[2] = clip.x; - clipRect[3] = clip.x + clip.width; + // Adjust the clipping rectangle with the renderer offsets + final double rdrOffX = renderer.getOffsetX(); + final double rdrOffY = renderer.getOffsetY(); + + // add a small rounding error: + final double margin = 1e-3d; + + clipRect[0] = clip.y + - margin + rdrOffY; + clipRect[1] = clip.y + clip.height + + margin + rdrOffY; + clipRect[2] = clip.x + - margin + rdrOffX; + clipRect[3] = clip.x + clip.width + + margin + rdrOffX; + + if (MarlinConst.DO_LOG_CLIP) { + MarlinUtils.logInfo("clipRect (clip): " + + Arrays.toString(rdrCtx.clipRect)); + } // Enable clipping: rdrCtx.doClip = true; @@ -267,14 +275,11 @@ final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; if (DO_CLIP_FILL && rdrCtx.doClip) { - double rdrOffX = renderer.getOffsetX(); - double rdrOffY = renderer.getOffsetY(); - if (DO_TRACE_PATH) { // trace Filler: pc = rdrCtx.transformerPC2D.traceFiller(pc); } - pc = rdrCtx.transformerPC2D.pathClipper(pc, rdrOffX, rdrOffY); + pc = rdrCtx.transformerPC2D.pathClipper(pc); } if (DO_TRACE_PATH) { --- old/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/MarlinPrismUtils.java 2018-10-29 19:02:56.000000000 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/MarlinPrismUtils.java 2018-10-29 19:02:56.000000000 +0100 @@ -35,10 +35,12 @@ import com.sun.marlin.MarlinConst; import com.sun.marlin.MarlinProperties; import com.sun.marlin.MarlinRenderer; +import com.sun.marlin.MarlinUtils; import com.sun.marlin.RendererContext; import com.sun.marlin.Stroker; import com.sun.marlin.TransformingPathConsumer2D; import com.sun.prism.BasicStroke; +import java.util.Arrays; public final class MarlinPrismUtils { @@ -86,7 +88,6 @@ int dashLen = -1; boolean recycleDashes = false; - float scale = 1.0f; float width = lineWidth; float[] dashes = stroke.getDashArray(); float dashphase = stroke.getDashPhase(); @@ -104,7 +105,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))) { - scale = (float) Math.sqrt(a*a + c*c); + final float scale = (float) Math.sqrt(a*a + c*c); if (dashes != null) { recycleDashes = true; @@ -142,15 +143,6 @@ tx = null; } - // Get renderer offsets: - float rdrOffX = 0.0f, rdrOffY = 0.0f; - - if (rdrCtx.doClip && (tx != null)) { - final MarlinRenderer renderer = (MarlinRenderer)out; - rdrOffX = renderer.getOffsetX(); - rdrOffY = renderer.getOffsetY(); - } - // Prepare the pipeline: PathConsumer2D pc = out; @@ -168,12 +160,12 @@ } // deltaTransformConsumer may adjust the clip rectangle: - pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx, rdrOffX, rdrOffY); + pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx); // stroker will adjust the clip rectangle (width / miter limit): pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), stroke.getLineJoin(), stroke.getMiterLimit(), - scale, rdrOffX, rdrOffY, (dashes == null)); + (dashes == null)); // Curve Monotizer: rdrCtx.monotonizer.init(width); @@ -239,10 +231,26 @@ // Define the initial clip bounds: final float[] clipRect = rdrCtx.clipRect; - clipRect[0] = clip.y; - clipRect[1] = clip.y + clip.height; - clipRect[2] = clip.x; - clipRect[3] = clip.x + clip.width; + // Adjust the clipping rectangle with the renderer offsets + final float rdrOffX = renderer.getOffsetX(); + final float rdrOffY = renderer.getOffsetY(); + + // add a small rounding error: + final float margin = 1e-3f; + + clipRect[0] = clip.y + - margin + rdrOffY; + clipRect[1] = clip.y + clip.height + + margin + rdrOffY; + clipRect[2] = clip.x + - margin + rdrOffX; + clipRect[3] = clip.x + clip.width + + margin + rdrOffX; + + if (MarlinConst.DO_LOG_CLIP) { + MarlinUtils.logInfo("clipRect (clip): " + + Arrays.toString(rdrCtx.clipRect)); + } // Enable clipping: rdrCtx.doClip = true; @@ -265,14 +273,11 @@ final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; if (DO_CLIP_FILL && rdrCtx.doClip) { - float rdrOffX = renderer.getOffsetX(); - float rdrOffY = renderer.getOffsetY(); - if (DO_TRACE_PATH) { // trace Filler: pc = rdrCtx.transformerPC2D.traceFiller(pc); } - pc = rdrCtx.transformerPC2D.pathClipper(pc, rdrOffX, rdrOffY); + pc = rdrCtx.transformerPC2D.pathClipper(pc); } if (DO_TRACE_PATH) { --- /dev/null 2018-10-29 19:02:57.000000000 +0100 +++ new/tests/system/src/test/java/test/com/sun/marlin/ScaleClipTest.java 2018-10-29 19:02:57.000000000 +0100 @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package test.com.sun.marlin; + +import java.awt.Color; +import java.io.File; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.geometry.Rectangle2D; +import javafx.scene.Group; +import javafx.scene.Scene; +import javafx.scene.SnapshotParameters; +import javafx.scene.image.PixelReader; +import javafx.scene.image.WritableImage; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; +import javafx.scene.shape.StrokeLineCap; +import javafx.scene.shape.StrokeLineJoin; +import javafx.scene.transform.Transform; +import javafx.stage.Stage; +import junit.framework.AssertionFailedError; +import org.junit.AfterClass; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import org.junit.BeforeClass; +import org.junit.Test; +import test.util.Util; +import static test.util.Util.TIMEOUT; + +/** + * Scaled Line Clipping rendering test + */ +public class ScaleClipTest { + + static final int SIZE = 50; + + enum SCALE_MODE { + ORTHO, + NON_ORTHO, + COMPLEX + }; + + // Used to launch the application before running any test + private static final CountDownLatch launchLatch = new CountDownLatch(1); + + // Singleton Application instance + static MyApp myApp; + + // Application class. An instance is created and initialized before running + // the first test, and it lives through the execution of all tests. + public static class MyApp extends Application { + + Stage stage = null; + + public MyApp() { + super(); + } + + @Override + public void init() { + ScaleClipTest.myApp = this; + } + + @Override + public void start(Stage primaryStage) throws Exception { + this.stage = primaryStage; + + stage.setScene(new Scene(new Group())); + stage.setTitle("ScaleClipTest"); + stage.show(); + + launchLatch.countDown(); + } + } + + @BeforeClass + public static void setupOnce() { + // Start the Application + new Thread(() -> Application.launch(MyApp.class, (String[]) null)).start(); + + try { + if (!launchLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { + throw new AssertionFailedError("Timeout waiting for Application to launch"); + } + + } catch (InterruptedException ex) { + AssertionFailedError err = new AssertionFailedError("Unexpected exception"); + err.initCause(ex); + throw err; + } + + assertEquals(0, launchLatch.getCount()); + + System.out.println("ScaleClipTest: size = " + SIZE); + } + + @AfterClass + public static void teardownOnce() { + Platform.exit(); + } + + @Test(timeout = 10000) + public void TestNegativeScaleClipPath() throws InterruptedException { + final AtomicBoolean fail = new AtomicBoolean(); + + for (SCALE_MODE mode : SCALE_MODE.values()) { + Util.runAndWait(() -> { + try { + testNegativeScale(mode); + } catch (AssertionError ae) { + System.err.println("testNegativeScale[" + mode + "] failed:"); + ae.printStackTrace(); + fail.set(true); + } + }); + } + + // Fail at the end: + if (fail.get()) { + fail("TestNegativeScaleClipPath has failures."); + } + } + + @Test(timeout = 10000) + public void TestMarginScaleClipPath() throws InterruptedException { + final AtomicBoolean fail = new AtomicBoolean(); + + // testMarginScale: + for (SCALE_MODE mode : SCALE_MODE.values()) { + Util.runAndWait(() -> { + try { + testMarginScale(mode); + } catch (AssertionError ae) { + System.err.println("testMarginScale[" + mode + "] failed:"); + ae.printStackTrace(); + fail.set(true); + } + }); + } + + // Fail at the end: + if (fail.get()) { + fail("TestMarginScaleClipPath has failures."); + } + } + + private void testNegativeScale(final SCALE_MODE mode) { + + // Bug in TransformingPathConsumer2D.adjustClipScale() + // non ortho scale only + final double scale = -1.0; + + final Transform t; + switch (mode) { + default: + case ORTHO: + t = Transform.scale(scale, scale); + break; + case NON_ORTHO: + t = Transform.scale(scale, scale + 1e-5); + break; + case COMPLEX: + t = Transform.affine(scale, 1e-4, 1e-4, scale, 0, 0); + break; + } + + final Path p = new Path(); + p.getElements().addAll( + new MoveTo(scale * 10, scale * 10), + new LineTo(scale * (SIZE - 10), scale * (SIZE - 10)) + ); + + // Set cap/join to reduce clip margin: + p.setFill(null); + p.setStroke(javafx.scene.paint.Color.BLACK); + p.setStrokeWidth(2); + p.setStrokeLineCap(StrokeLineCap.BUTT); + p.setStrokeLineJoin(StrokeLineJoin.BEVEL); + + Scene scene = new Scene(new Group(p)); + myApp.stage.setScene(scene); + + final SnapshotParameters sp = new SnapshotParameters(); + sp.setViewport(new Rectangle2D(0, 0, SIZE, SIZE)); + sp.setTransform(t); + + final WritableImage img = scene.getRoot().snapshot(sp, new WritableImage(SIZE, SIZE)); + + // Check image: + // 25, 25 = black + checkPixel(img.getPixelReader(), 25, 25, Color.BLACK.getRGB()); + } + + private static void testMarginScale(final SCALE_MODE mode) { + + // Bug in Stroker.init() + // ortho scale only: scale used twice ! + final double scale = 1e-2; + + final Transform t; + switch (mode) { + default: + case ORTHO: + t = Transform.scale(scale, scale); + break; + case NON_ORTHO: + t = Transform.scale(scale, scale + 1e-5); + break; + case COMPLEX: + t = Transform.affine(scale, 1e-4, 1e-4, scale, 0, 0); + break; + } + + final double invScale = 1.0 / scale; + + final Path p = new Path(); + p.getElements().addAll( + new MoveTo(invScale * -0.5, invScale * 10), + new LineTo(invScale * -0.5, invScale * (SIZE - 10)) + ); + + // Set cap/join to reduce clip margin: + p.setFill(null); + p.setStroke(javafx.scene.paint.Color.BLACK); + p.setStrokeWidth(3.0 * invScale); + p.setStrokeLineCap(StrokeLineCap.BUTT); + p.setStrokeLineJoin(StrokeLineJoin.BEVEL); + + Scene scene = new Scene(new Group(p)); + myApp.stage.setScene(scene); + + final SnapshotParameters sp = new SnapshotParameters(); + sp.setViewport(new Rectangle2D(0, 0, SIZE, SIZE)); + sp.setTransform(t); + + final WritableImage img = scene.getRoot().snapshot(sp, new WritableImage(SIZE, SIZE)); + + // Check image: + // 0, 25 = black + checkPixel(img.getPixelReader(), 0, 25, Color.BLACK.getRGB()); + } + + private static void checkPixel(final PixelReader pr, + final int x, final int y, + final int expected) { + + final int rgb = pr.getArgb(x, y); + if (rgb != expected) { + fail("bad pixel at (" + x + ", " + y + + ") = " + rgb + " expected: " + expected); + } + } +}