18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 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.PathConsumer2D; 30 import com.sun.javafx.geom.PathIterator; 31 import com.sun.javafx.geom.Path2D; 32 import com.sun.javafx.geom.Rectangle; 33 import com.sun.javafx.geom.Shape; 34 import com.sun.javafx.geom.transform.BaseTransform; 35 import com.sun.marlin.MarlinConst; 36 import com.sun.marlin.MarlinProperties; 37 import com.sun.marlin.MarlinRenderer; 38 import com.sun.marlin.RendererContext; 39 import com.sun.marlin.Stroker; 40 import com.sun.marlin.TransformingPathConsumer2D; 41 import com.sun.prism.BasicStroke; 42 43 public final class MarlinPrismUtils { 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 PathConsumer2D 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 float scale = 1.0f; 90 float width = lineWidth; 91 float[] dashes = stroke.getDashArray(); 92 float dashphase = stroke.getDashPhase(); 93 94 if ((tx != null) && !tx.isIdentity()) { 95 final double a = tx.getMxx(); 96 final double b = tx.getMxy(); 97 final double c = tx.getMyx(); 98 final double d = tx.getMyy(); 99 100 // If the transform is a constant multiple of an orthogonal transformation 101 // then every length is just multiplied by a constant, so we just 102 // need to transform input paths to stroker and tell stroker 103 // the scaled width. This condition is satisfied if 104 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 105 // leave a bit of room for error. 106 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { 107 scale = (float) Math.sqrt(a*a + c*c); 108 109 if (dashes != null) { 110 recycleDashes = true; 111 dashLen = dashes.length; 112 dashes = rdrCtx.dasher.copyDashArray(dashes); 113 for (int i = 0; i < dashLen; i++) { 114 dashes[i] *= scale; 115 } 116 dashphase *= scale; 117 } 118 width *= scale; 119 120 // by now strokerat == null. Input paths to 121 // stroker (and maybe dasher) will have the full transform tx 122 // applied to them and nothing will happen to the output paths. 123 } else { 124 strokerTx = tx; 125 126 // by now strokerat == tx. Input paths to 127 // stroker (and maybe dasher) will have the full transform tx 128 // applied to them, then they will be normalized, and then 129 // the inverse of *only the non translation part of tx* will 130 // be applied to the normalized paths. This won't cause problems 131 // in stroker, because, suppose tx = T*A, where T is just the 132 // translation part of tx, and A is the rest. T*A has already 133 // been applied to Stroker/Dasher's input. Then Ainv will be 134 // applied. Ainv*T*A is not equal to T, but it is a translation, 135 // which means that none of stroker's assumptions about its 136 // input will be violated. After all this, A will be applied 137 // to stroker's output. 138 } 139 } else { 140 // either tx is null or it's the identity. In either case 141 // we don't transform the path. 142 tx = null; 143 } 144 145 // Get renderer offsets: 146 float rdrOffX = 0.0f, rdrOffY = 0.0f; 147 148 if (rdrCtx.doClip && (tx != null)) { 149 final MarlinRenderer renderer = (MarlinRenderer)out; 150 rdrOffX = renderer.getOffsetX(); 151 rdrOffY = renderer.getOffsetY(); 152 } 153 154 // Prepare the pipeline: 155 PathConsumer2D pc = out; 156 157 final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 158 159 if (DO_TRACE_PATH) { 160 // trace Stroker: 161 pc = transformerPC2D.traceStroker(pc); 162 } 163 164 if (MarlinConst.USE_SIMPLIFIER) { 165 // Use simplifier after stroker before Renderer 166 // to remove collinear segments (notably due to cap square) 167 pc = rdrCtx.simplifier.init(pc); 168 } 169 170 // deltaTransformConsumer may adjust the clip rectangle: 171 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx, rdrOffX, rdrOffY); 172 173 // stroker will adjust the clip rectangle (width / miter limit): 174 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), 175 stroke.getLineJoin(), stroke.getMiterLimit(), 176 scale, rdrOffX, rdrOffY, (dashes == null)); 177 178 // Curve Monotizer: 179 rdrCtx.monotonizer.init(width); 180 181 if (dashes != null) { 182 if (!recycleDashes) { 183 dashLen = dashes.length; 184 } 185 if (DO_TRACE_PATH) { 186 pc = transformerPC2D.traceDasher(pc); 187 } 188 pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase, 189 recycleDashes); 190 191 if (DISABLE_2ND_STROKER_CLIPPING) { 192 // disable stoker clipping: 193 rdrCtx.stroker.disableClipping(); 194 } 195 196 } else if (rdrCtx.doClip && (stroke.getEndCap() != Stroker.CAP_BUTT)) { 222 */ 223 return pc; 224 } 225 226 private static boolean nearZero(final double num) { 227 return Math.abs(num) < 2.0d * Math.ulp(num); 228 } 229 230 private static PathConsumer2D initRenderer( 231 final RendererContext rdrCtx, 232 final BasicStroke stroke, 233 final BaseTransform tx, 234 final Rectangle clip, 235 final int piRule, 236 final MarlinRenderer renderer) 237 { 238 if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) { 239 // Define the initial clip bounds: 240 final float[] clipRect = rdrCtx.clipRect; 241 242 clipRect[0] = clip.y; 243 clipRect[1] = clip.y + clip.height; 244 clipRect[2] = clip.x; 245 clipRect[3] = clip.x + clip.width; 246 247 // Enable clipping: 248 rdrCtx.doClip = true; 249 } 250 251 if (stroke != null) { 252 renderer.init(clip.x, clip.y, clip.width, clip.height, 253 MarlinConst.WIND_NON_ZERO); 254 255 return initStroker(rdrCtx, stroke, stroke.getLineWidth(), tx, renderer); 256 } else { 257 // Filler: 258 final int oprule = (piRule == PathIterator.WIND_EVEN_ODD) ? 259 MarlinConst.WIND_EVEN_ODD : MarlinConst.WIND_NON_ZERO; 260 261 renderer.init(clip.x, clip.y, clip.width, clip.height, oprule); 262 263 PathConsumer2D pc = renderer; 264 265 final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 266 267 if (DO_CLIP_FILL && rdrCtx.doClip) { 268 float rdrOffX = renderer.getOffsetX(); 269 float rdrOffY = renderer.getOffsetY(); 270 271 if (DO_TRACE_PATH) { 272 // trace Filler: 273 pc = rdrCtx.transformerPC2D.traceFiller(pc); 274 } 275 pc = rdrCtx.transformerPC2D.pathClipper(pc, rdrOffX, rdrOffY); 276 } 277 278 if (DO_TRACE_PATH) { 279 // trace Input: 280 pc = transformerPC2D.traceInput(pc); 281 } 282 return pc; 283 } 284 } 285 286 public static MarlinRenderer setupRenderer( 287 final RendererContext rdrCtx, 288 final Shape shape, 289 final BasicStroke stroke, 290 final BaseTransform xform, 291 final Rectangle rclip, 292 final boolean antialiasedShape) 293 { 294 // Test if transform is identity: 295 final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null; | 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 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.PathConsumer2D; 30 import com.sun.javafx.geom.PathIterator; 31 import com.sun.javafx.geom.Path2D; 32 import com.sun.javafx.geom.Rectangle; 33 import com.sun.javafx.geom.Shape; 34 import com.sun.javafx.geom.transform.BaseTransform; 35 import com.sun.marlin.MarlinConst; 36 import com.sun.marlin.MarlinProperties; 37 import com.sun.marlin.MarlinRenderer; 38 import com.sun.marlin.MarlinUtils; 39 import com.sun.marlin.RendererContext; 40 import com.sun.marlin.Stroker; 41 import com.sun.marlin.TransformingPathConsumer2D; 42 import com.sun.prism.BasicStroke; 43 import java.util.Arrays; 44 45 public final class MarlinPrismUtils { 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 PathConsumer2D 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 float width = lineWidth; 92 float[] dashes = stroke.getDashArray(); 93 float dashphase = stroke.getDashPhase(); 94 95 if ((tx != null) && !tx.isIdentity()) { 96 final double a = tx.getMxx(); 97 final double b = tx.getMxy(); 98 final double c = tx.getMyx(); 99 final double d = tx.getMyy(); 100 101 // If the transform is a constant multiple of an orthogonal transformation 102 // then every length is just multiplied by a constant, so we just 103 // need to transform input paths to stroker and tell stroker 104 // the scaled width. This condition is satisfied if 105 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 106 // leave a bit of room for error. 107 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { 108 final float scale = (float) Math.sqrt(a*a + c*c); 109 110 if (dashes != null) { 111 recycleDashes = true; 112 dashLen = dashes.length; 113 dashes = rdrCtx.dasher.copyDashArray(dashes); 114 for (int i = 0; i < dashLen; i++) { 115 dashes[i] *= scale; 116 } 117 dashphase *= scale; 118 } 119 width *= scale; 120 121 // by now strokerat == null. Input paths to 122 // stroker (and maybe dasher) will have the full transform tx 123 // applied to them and nothing will happen to the output paths. 124 } else { 125 strokerTx = tx; 126 127 // by now strokerat == tx. Input paths to 128 // stroker (and maybe dasher) will have the full transform tx 129 // applied to them, then they will be normalized, and then 130 // the inverse of *only the non translation part of tx* will 131 // be applied to the normalized paths. This won't cause problems 132 // in stroker, because, suppose tx = T*A, where T is just the 133 // translation part of tx, and A is the rest. T*A has already 134 // been applied to Stroker/Dasher's input. Then Ainv will be 135 // applied. Ainv*T*A is not equal to T, but it is a translation, 136 // which means that none of stroker's assumptions about its 137 // input will be violated. After all this, A will be applied 138 // to stroker's output. 139 } 140 } else { 141 // either tx is null or it's the identity. In either case 142 // we don't transform the path. 143 tx = null; 144 } 145 146 // Prepare the pipeline: 147 PathConsumer2D pc = out; 148 149 final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 150 151 if (DO_TRACE_PATH) { 152 // trace Stroker: 153 pc = transformerPC2D.traceStroker(pc); 154 } 155 156 if (MarlinConst.USE_SIMPLIFIER) { 157 // Use simplifier after stroker before Renderer 158 // to remove collinear segments (notably due to cap square) 159 pc = rdrCtx.simplifier.init(pc); 160 } 161 162 // deltaTransformConsumer may adjust the clip rectangle: 163 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx); 164 165 // stroker will adjust the clip rectangle (width / miter limit): 166 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), 167 stroke.getLineJoin(), stroke.getMiterLimit(), 168 (dashes == null)); 169 170 // Curve Monotizer: 171 rdrCtx.monotonizer.init(width); 172 173 if (dashes != null) { 174 if (!recycleDashes) { 175 dashLen = dashes.length; 176 } 177 if (DO_TRACE_PATH) { 178 pc = transformerPC2D.traceDasher(pc); 179 } 180 pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase, 181 recycleDashes); 182 183 if (DISABLE_2ND_STROKER_CLIPPING) { 184 // disable stoker clipping: 185 rdrCtx.stroker.disableClipping(); 186 } 187 188 } else if (rdrCtx.doClip && (stroke.getEndCap() != Stroker.CAP_BUTT)) { 214 */ 215 return pc; 216 } 217 218 private static boolean nearZero(final double num) { 219 return Math.abs(num) < 2.0d * Math.ulp(num); 220 } 221 222 private static PathConsumer2D initRenderer( 223 final RendererContext rdrCtx, 224 final BasicStroke stroke, 225 final BaseTransform tx, 226 final Rectangle clip, 227 final int piRule, 228 final MarlinRenderer renderer) 229 { 230 if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) { 231 // Define the initial clip bounds: 232 final float[] clipRect = rdrCtx.clipRect; 233 234 // Adjust the clipping rectangle with the renderer offsets 235 final float rdrOffX = renderer.getOffsetX(); 236 final float rdrOffY = renderer.getOffsetY(); 237 238 // add a small rounding error: 239 final float margin = 1e-3f; 240 241 clipRect[0] = clip.y 242 - margin + rdrOffY; 243 clipRect[1] = clip.y + clip.height 244 + margin + rdrOffY; 245 clipRect[2] = clip.x 246 - margin + rdrOffX; 247 clipRect[3] = clip.x + clip.width 248 + margin + rdrOffX; 249 250 if (MarlinConst.DO_LOG_CLIP) { 251 MarlinUtils.logInfo("clipRect (clip): " 252 + Arrays.toString(rdrCtx.clipRect)); 253 } 254 255 // Enable clipping: 256 rdrCtx.doClip = true; 257 } 258 259 if (stroke != null) { 260 renderer.init(clip.x, clip.y, clip.width, clip.height, 261 MarlinConst.WIND_NON_ZERO); 262 263 return initStroker(rdrCtx, stroke, stroke.getLineWidth(), tx, renderer); 264 } else { 265 // Filler: 266 final int oprule = (piRule == PathIterator.WIND_EVEN_ODD) ? 267 MarlinConst.WIND_EVEN_ODD : MarlinConst.WIND_NON_ZERO; 268 269 renderer.init(clip.x, clip.y, clip.width, clip.height, oprule); 270 271 PathConsumer2D pc = renderer; 272 273 final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 274 275 if (DO_CLIP_FILL && rdrCtx.doClip) { 276 if (DO_TRACE_PATH) { 277 // trace Filler: 278 pc = rdrCtx.transformerPC2D.traceFiller(pc); 279 } 280 pc = rdrCtx.transformerPC2D.pathClipper(pc); 281 } 282 283 if (DO_TRACE_PATH) { 284 // trace Input: 285 pc = transformerPC2D.traceInput(pc); 286 } 287 return pc; 288 } 289 } 290 291 public static MarlinRenderer setupRenderer( 292 final RendererContext rdrCtx, 293 final Shape shape, 294 final BasicStroke stroke, 295 final BaseTransform xform, 296 final Rectangle rclip, 297 final boolean antialiasedShape) 298 { 299 // Test if transform is identity: 300 final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null; |