21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.prism.impl.shape; 27 28 29 import com.sun.javafx.geom.PathIterator; 30 import com.sun.javafx.geom.Path2D; 31 import com.sun.javafx.geom.Rectangle; 32 import com.sun.javafx.geom.Shape; 33 import com.sun.javafx.geom.transform.BaseTransform; 34 import com.sun.marlin.MarlinConst; 35 import com.sun.marlin.MarlinProperties; 36 import com.sun.marlin.DMarlinRenderer; 37 import com.sun.marlin.DPathConsumer2D; 38 import com.sun.marlin.DRendererContext; 39 import com.sun.marlin.DStroker; 40 import com.sun.marlin.DTransformingPathConsumer2D; 41 import com.sun.prism.BasicStroke; 42 43 public final class DMarlinPrismUtils { 44 45 private static final boolean FORCE_NO_AA = false; 46 47 // slightly slower ~2% if enabled stroker clipping (lines) but skipping cap / join handling is few percents faster in specific cases 48 static final boolean DISABLE_2ND_STROKER_CLIPPING = true; 49 50 static final boolean DO_TRACE_PATH = false; 51 52 static final boolean DO_CLIP = MarlinProperties.isDoClip(); 53 static final boolean DO_CLIP_FILL = true; 54 static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag(); 55 56 static final float UPPER_BND = Float.MAX_VALUE / 2.0f; 57 static final float LOWER_BND = -UPPER_BND; 58 59 /** 60 * Private constructor to prevent instantiation. 61 */ 69 BaseTransform tx, 70 final DPathConsumer2D out) 71 { 72 // We use strokerat so that in Stroker and Dasher we can work only 73 // with the pre-transformation coordinates. This will repeat a lot of 74 // computations done in the path iterator, but the alternative is to 75 // work with transformed paths and compute untransformed coordinates 76 // as needed. This would be faster but I do not think the complexity 77 // of working with both untransformed and transformed coordinates in 78 // the same code is worth it. 79 // However, if a path's width is constant after a transformation, 80 // we can skip all this untransforming. 81 82 // As pathTo() will check transformed coordinates for invalid values 83 // (NaN / Infinity) to ignore such points, it is necessary to apply the 84 // transformation before the path processing. 85 BaseTransform strokerTx = null; 86 87 int dashLen = -1; 88 boolean recycleDashes = false; 89 double scale = 1.0d; 90 double width = lineWidth; 91 float[] dashes = stroke.getDashArray(); 92 double[] dashesD = null; 93 double dashphase = stroke.getDashPhase(); 94 95 // Ensure converting dashes to double precision: 96 if (dashes != null) { 97 recycleDashes = true; 98 dashLen = dashes.length; 99 dashesD = rdrCtx.dasher.copyDashArray(dashes); 100 } 101 102 if ((tx != null) && !tx.isIdentity()) { 103 final double a = tx.getMxx(); 104 final double b = tx.getMxy(); 105 final double c = tx.getMyx(); 106 final double d = tx.getMyy(); 107 108 // If the transform is a constant multiple of an orthogonal transformation 109 // then every length is just multiplied by a constant, so we just 110 // need to transform input paths to stroker and tell stroker 111 // the scaled width. This condition is satisfied if 112 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 113 // leave a bit of room for error. 114 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { 115 scale = Math.sqrt(a*a + c*c); 116 117 if (dashesD != null) { 118 for (int i = 0; i < dashLen; i++) { 119 dashesD[i] *= scale; 120 } 121 dashphase *= scale; 122 } 123 width *= scale; 124 125 // by now strokerat == null. Input paths to 126 // stroker (and maybe dasher) will have the full transform tx 127 // applied to them and nothing will happen to the output paths. 128 } else { 129 strokerTx = tx; 130 131 // by now strokerat == tx. Input paths to 132 // stroker (and maybe dasher) will have the full transform tx 133 // applied to them, then they will be normalized, and then 134 // the inverse of *only the non translation part of tx* will 135 // be applied to the normalized paths. This won't cause problems 136 // in stroker, because, suppose tx = T*A, where T is just the 137 // translation part of tx, and A is the rest. T*A has already 138 // been applied to Stroker/Dasher's input. Then Ainv will be 139 // applied. Ainv*T*A is not equal to T, but it is a translation, 140 // which means that none of stroker's assumptions about its 141 // input will be violated. After all this, A will be applied 142 // to stroker's output. 143 } 144 } else { 145 // either tx is null or it's the identity. In either case 146 // we don't transform the path. 147 tx = null; 148 } 149 150 // Get renderer offsets: 151 double rdrOffX = 0.0d, rdrOffY = 0.0d; 152 153 if (rdrCtx.doClip && (tx != null)) { 154 final DMarlinRenderer renderer = (DMarlinRenderer)out; 155 rdrOffX = renderer.getOffsetX(); 156 rdrOffY = renderer.getOffsetY(); 157 } 158 159 // Prepare the pipeline: 160 DPathConsumer2D pc = out; 161 162 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 163 164 if (DO_TRACE_PATH) { 165 // trace Stroker: 166 pc = transformerPC2D.traceStroker(pc); 167 } 168 169 if (MarlinConst.USE_SIMPLIFIER) { 170 // Use simplifier after stroker before Renderer 171 // to remove collinear segments (notably due to cap square) 172 pc = rdrCtx.simplifier.init(pc); 173 } 174 175 // deltaTransformConsumer may adjust the clip rectangle: 176 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx, rdrOffX, rdrOffY); 177 178 // stroker will adjust the clip rectangle (width / miter limit): 179 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), 180 stroke.getLineJoin(), stroke.getMiterLimit(), 181 scale, rdrOffX, rdrOffY, (dashesD == null)); 182 183 // Curve Monotizer: 184 rdrCtx.monotonizer.init(width); 185 186 if (dashesD != null) { 187 if (DO_TRACE_PATH) { 188 pc = transformerPC2D.traceDasher(pc); 189 } 190 pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase, 191 recycleDashes); 192 193 if (DISABLE_2ND_STROKER_CLIPPING) { 194 // disable stoker clipping: 195 rdrCtx.stroker.disableClipping(); 196 } 197 198 } else if (rdrCtx.doClip && (stroke.getEndCap() != DStroker.CAP_BUTT)) { 199 if (DO_TRACE_PATH) { 200 pc = transformerPC2D.traceClosedPathDetector(pc); 201 } 224 */ 225 return pc; 226 } 227 228 private static boolean nearZero(final double num) { 229 return Math.abs(num) < 2.0d * Math.ulp(num); 230 } 231 232 private static DPathConsumer2D initRenderer( 233 final DRendererContext rdrCtx, 234 final BasicStroke stroke, 235 final BaseTransform tx, 236 final Rectangle clip, 237 final int piRule, 238 final DMarlinRenderer renderer) 239 { 240 if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) { 241 // Define the initial clip bounds: 242 final double[] clipRect = rdrCtx.clipRect; 243 244 clipRect[0] = clip.y; 245 clipRect[1] = clip.y + clip.height; 246 clipRect[2] = clip.x; 247 clipRect[3] = clip.x + clip.width; 248 249 // Enable clipping: 250 rdrCtx.doClip = true; 251 } 252 253 if (stroke != null) { 254 renderer.init(clip.x, clip.y, clip.width, clip.height, 255 MarlinConst.WIND_NON_ZERO); 256 257 return initStroker(rdrCtx, stroke, stroke.getLineWidth(), tx, renderer); 258 } else { 259 // Filler: 260 final int oprule = (piRule == PathIterator.WIND_EVEN_ODD) ? 261 MarlinConst.WIND_EVEN_ODD : MarlinConst.WIND_NON_ZERO; 262 263 renderer.init(clip.x, clip.y, clip.width, clip.height, oprule); 264 265 DPathConsumer2D pc = renderer; 266 267 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 268 269 if (DO_CLIP_FILL && rdrCtx.doClip) { 270 double rdrOffX = renderer.getOffsetX(); 271 double rdrOffY = renderer.getOffsetY(); 272 273 if (DO_TRACE_PATH) { 274 // trace Filler: 275 pc = rdrCtx.transformerPC2D.traceFiller(pc); 276 } 277 pc = rdrCtx.transformerPC2D.pathClipper(pc, rdrOffX, rdrOffY); 278 } 279 280 if (DO_TRACE_PATH) { 281 // trace Input: 282 pc = transformerPC2D.traceInput(pc); 283 } 284 return pc; 285 } 286 } 287 288 public static DMarlinRenderer setupRenderer( 289 final DRendererContext rdrCtx, 290 final Shape shape, 291 final BasicStroke stroke, 292 final BaseTransform xform, 293 final Rectangle rclip, 294 final boolean antialiasedShape) 295 { 296 // Test if transform is identity: 297 final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null; | 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.prism.impl.shape; 27 28 29 import com.sun.javafx.geom.PathIterator; 30 import com.sun.javafx.geom.Path2D; 31 import com.sun.javafx.geom.Rectangle; 32 import com.sun.javafx.geom.Shape; 33 import com.sun.javafx.geom.transform.BaseTransform; 34 import com.sun.marlin.MarlinConst; 35 import com.sun.marlin.MarlinProperties; 36 import com.sun.marlin.DMarlinRenderer; 37 import com.sun.marlin.DPathConsumer2D; 38 import com.sun.marlin.DRendererContext; 39 import com.sun.marlin.DStroker; 40 import com.sun.marlin.DTransformingPathConsumer2D; 41 import com.sun.marlin.MarlinUtils; 42 import com.sun.prism.BasicStroke; 43 import java.util.Arrays; 44 45 public final class DMarlinPrismUtils { 46 47 private static final boolean FORCE_NO_AA = false; 48 49 // slightly slower ~2% if enabled stroker clipping (lines) but skipping cap / join handling is few percents faster in specific cases 50 static final boolean DISABLE_2ND_STROKER_CLIPPING = true; 51 52 static final boolean DO_TRACE_PATH = false; 53 54 static final boolean DO_CLIP = MarlinProperties.isDoClip(); 55 static final boolean DO_CLIP_FILL = true; 56 static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag(); 57 58 static final float UPPER_BND = Float.MAX_VALUE / 2.0f; 59 static final float LOWER_BND = -UPPER_BND; 60 61 /** 62 * Private constructor to prevent instantiation. 63 */ 71 BaseTransform tx, 72 final DPathConsumer2D out) 73 { 74 // We use strokerat so that in Stroker and Dasher we can work only 75 // with the pre-transformation coordinates. This will repeat a lot of 76 // computations done in the path iterator, but the alternative is to 77 // work with transformed paths and compute untransformed coordinates 78 // as needed. This would be faster but I do not think the complexity 79 // of working with both untransformed and transformed coordinates in 80 // the same code is worth it. 81 // However, if a path's width is constant after a transformation, 82 // we can skip all this untransforming. 83 84 // As pathTo() will check transformed coordinates for invalid values 85 // (NaN / Infinity) to ignore such points, it is necessary to apply the 86 // transformation before the path processing. 87 BaseTransform strokerTx = null; 88 89 int dashLen = -1; 90 boolean recycleDashes = false; 91 double width = lineWidth; 92 float[] dashes = stroke.getDashArray(); 93 double[] dashesD = null; 94 double dashphase = stroke.getDashPhase(); 95 96 // Ensure converting dashes to double precision: 97 if (dashes != null) { 98 recycleDashes = true; 99 dashLen = dashes.length; 100 dashesD = rdrCtx.dasher.copyDashArray(dashes); 101 } 102 103 if ((tx != null) && !tx.isIdentity()) { 104 final double a = tx.getMxx(); 105 final double b = tx.getMxy(); 106 final double c = tx.getMyx(); 107 final double d = tx.getMyy(); 108 109 // If the transform is a constant multiple of an orthogonal transformation 110 // then every length is just multiplied by a constant, so we just 111 // need to transform input paths to stroker and tell stroker 112 // the scaled width. This condition is satisfied if 113 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 114 // leave a bit of room for error. 115 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { 116 final double scale = Math.sqrt(a*a + c*c); 117 118 if (dashesD != null) { 119 for (int i = 0; i < dashLen; i++) { 120 dashesD[i] *= scale; 121 } 122 dashphase *= scale; 123 } 124 width *= scale; 125 126 // by now strokerat == null. Input paths to 127 // stroker (and maybe dasher) will have the full transform tx 128 // applied to them and nothing will happen to the output paths. 129 } else { 130 strokerTx = tx; 131 132 // by now strokerat == tx. Input paths to 133 // stroker (and maybe dasher) will have the full transform tx 134 // applied to them, then they will be normalized, and then 135 // the inverse of *only the non translation part of tx* will 136 // be applied to the normalized paths. This won't cause problems 137 // in stroker, because, suppose tx = T*A, where T is just the 138 // translation part of tx, and A is the rest. T*A has already 139 // been applied to Stroker/Dasher's input. Then Ainv will be 140 // applied. Ainv*T*A is not equal to T, but it is a translation, 141 // which means that none of stroker's assumptions about its 142 // input will be violated. After all this, A will be applied 143 // to stroker's output. 144 } 145 } else { 146 // either tx is null or it's the identity. In either case 147 // we don't transform the path. 148 tx = null; 149 } 150 151 // Prepare the pipeline: 152 DPathConsumer2D pc = out; 153 154 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 155 156 if (DO_TRACE_PATH) { 157 // trace Stroker: 158 pc = transformerPC2D.traceStroker(pc); 159 } 160 161 if (MarlinConst.USE_SIMPLIFIER) { 162 // Use simplifier after stroker before Renderer 163 // to remove collinear segments (notably due to cap square) 164 pc = rdrCtx.simplifier.init(pc); 165 } 166 167 // deltaTransformConsumer may adjust the clip rectangle: 168 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx); 169 170 // stroker will adjust the clip rectangle (width / miter limit): 171 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), 172 stroke.getLineJoin(), stroke.getMiterLimit(), 173 (dashesD == null)); 174 175 // Curve Monotizer: 176 rdrCtx.monotonizer.init(width); 177 178 if (dashesD != null) { 179 if (DO_TRACE_PATH) { 180 pc = transformerPC2D.traceDasher(pc); 181 } 182 pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase, 183 recycleDashes); 184 185 if (DISABLE_2ND_STROKER_CLIPPING) { 186 // disable stoker clipping: 187 rdrCtx.stroker.disableClipping(); 188 } 189 190 } else if (rdrCtx.doClip && (stroke.getEndCap() != DStroker.CAP_BUTT)) { 191 if (DO_TRACE_PATH) { 192 pc = transformerPC2D.traceClosedPathDetector(pc); 193 } 216 */ 217 return pc; 218 } 219 220 private static boolean nearZero(final double num) { 221 return Math.abs(num) < 2.0d * Math.ulp(num); 222 } 223 224 private static DPathConsumer2D initRenderer( 225 final DRendererContext rdrCtx, 226 final BasicStroke stroke, 227 final BaseTransform tx, 228 final Rectangle clip, 229 final int piRule, 230 final DMarlinRenderer renderer) 231 { 232 if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) { 233 // Define the initial clip bounds: 234 final double[] clipRect = rdrCtx.clipRect; 235 236 // Adjust the clipping rectangle with the renderer offsets 237 final double rdrOffX = renderer.getOffsetX(); 238 final double rdrOffY = renderer.getOffsetY(); 239 240 // add a small rounding error: 241 final double margin = 1e-3d; 242 243 clipRect[0] = clip.y 244 - margin + rdrOffY; 245 clipRect[1] = clip.y + clip.height 246 + margin + rdrOffY; 247 clipRect[2] = clip.x 248 - margin + rdrOffX; 249 clipRect[3] = clip.x + clip.width 250 + margin + rdrOffX; 251 252 if (MarlinConst.DO_LOG_CLIP) { 253 MarlinUtils.logInfo("clipRect (clip): " 254 + Arrays.toString(rdrCtx.clipRect)); 255 } 256 257 // Enable clipping: 258 rdrCtx.doClip = true; 259 } 260 261 if (stroke != null) { 262 renderer.init(clip.x, clip.y, clip.width, clip.height, 263 MarlinConst.WIND_NON_ZERO); 264 265 return initStroker(rdrCtx, stroke, stroke.getLineWidth(), tx, renderer); 266 } else { 267 // Filler: 268 final int oprule = (piRule == PathIterator.WIND_EVEN_ODD) ? 269 MarlinConst.WIND_EVEN_ODD : MarlinConst.WIND_NON_ZERO; 270 271 renderer.init(clip.x, clip.y, clip.width, clip.height, oprule); 272 273 DPathConsumer2D pc = renderer; 274 275 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 276 277 if (DO_CLIP_FILL && rdrCtx.doClip) { 278 if (DO_TRACE_PATH) { 279 // trace Filler: 280 pc = rdrCtx.transformerPC2D.traceFiller(pc); 281 } 282 pc = rdrCtx.transformerPC2D.pathClipper(pc); 283 } 284 285 if (DO_TRACE_PATH) { 286 // trace Input: 287 pc = transformerPC2D.traceInput(pc); 288 } 289 return pc; 290 } 291 } 292 293 public static DMarlinRenderer setupRenderer( 294 final DRendererContext rdrCtx, 295 final Shape shape, 296 final BasicStroke stroke, 297 final BaseTransform xform, 298 final Rectangle rclip, 299 final boolean antialiasedShape) 300 { 301 // Test if transform is identity: 302 final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null; |