34 import com.sun.javafx.geom.transform.BaseTransform; 35 import com.sun.marlin.MarlinConst; 36 import com.sun.marlin.MarlinRenderer; 37 import com.sun.marlin.RendererContext; 38 import com.sun.marlin.TransformingPathConsumer2D; 39 import com.sun.prism.BasicStroke; 40 41 public final class MarlinPrismUtils { 42 43 private static final boolean FORCE_NO_AA = false; 44 45 static final float UPPER_BND = Float.MAX_VALUE / 2.0f; 46 static final float LOWER_BND = -UPPER_BND; 47 48 /** 49 * Private constructor to prevent instantiation. 50 */ 51 private MarlinPrismUtils() { 52 } 53 54 private static PathConsumer2D initRenderer( 55 final RendererContext rdrCtx, 56 final BasicStroke stroke, 57 final BaseTransform tx, 58 final Rectangle clip, 59 final int pirule, 60 final MarlinRenderer renderer) 61 { 62 final int oprule = (stroke == null && pirule == PathIterator.WIND_EVEN_ODD) ? 63 MarlinRenderer.WIND_EVEN_ODD : MarlinRenderer.WIND_NON_ZERO; 64 65 // We use strokerat so that in Stroker and Dasher we can work only 66 // with the pre-transformation coordinates. This will repeat a lot of 67 // computations done in the path iterator, but the alternative is to 68 // work with transformed paths and compute untransformed coordinates 69 // as needed. This would be faster but I do not think the complexity 70 // of working with both untransformed and transformed coordinates in 71 // the same code is worth it. 72 // However, if a path's width is constant after a transformation, 73 // we can skip all this untransforming. 74 75 // As pathTo() will check transformed coordinates for invalid values 76 // (NaN / Infinity) to ignore such points, it is necessary to apply the 77 // transformation before the path processing. 78 BaseTransform strokerTx = null; 79 80 int dashLen = -1; 81 boolean recycleDashes = false; 82 83 float width = 0.0f, dashphase = 0.0f; 84 float[] dashes = null; 85 86 if (stroke != null) { 87 width = stroke.getLineWidth(); 88 dashes = stroke.getDashArray(); 89 dashphase = stroke.getDashPhase(); 90 91 if (tx != null && !tx.isIdentity()) { 92 final double a = tx.getMxx(); 93 final double b = tx.getMxy(); 94 final double c = tx.getMyx(); 95 final double d = tx.getMyy(); 96 97 // If the transform is a constant multiple of an orthogonal transformation 98 // then every length is just multiplied by a constant, so we just 99 // need to transform input paths to stroker and tell stroker 100 // the scaled width. This condition is satisfied if 101 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 102 // leave a bit of room for error. 103 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { 104 final float scale = (float) Math.sqrt(a*a + c*c); 105 106 if (dashes != null) { 107 recycleDashes = true; 108 dashLen = dashes.length; 109 dashes = rdrCtx.dasher.copyDashArray(dashes); 110 for (int i = 0; i < dashLen; i++) { 111 dashes[i] *= scale; 112 } 113 dashphase *= scale; 114 } 115 width *= scale; 116 117 // by now strokerat == null. Input paths to 118 // stroker (and maybe dasher) will have the full transform tx 119 // applied to them and nothing will happen to the output paths. 120 } else { 121 strokerTx = tx; 122 123 // by now strokerat == tx. Input paths to 124 // stroker (and maybe dasher) will have the full transform tx 125 // applied to them, then they will be normalized, and then 126 // the inverse of *only the non translation part of tx* will 127 // be applied to the normalized paths. This won't cause problems 128 // in stroker, because, suppose tx = T*A, where T is just the 129 // translation part of tx, and A is the rest. T*A has already 130 // been applied to Stroker/Dasher's input. Then Ainv will be 131 // applied. Ainv*T*A is not equal to T, but it is a translation, 132 // which means that none of stroker's assumptions about its 133 // input will be violated. After all this, A will be applied 134 // to stroker's output. 135 } 136 } 137 } 138 139 PathConsumer2D pc = renderer.init(clip.x, clip.y, clip.width, clip.height, oprule); 140 141 if (MarlinConst.USE_SIMPLIFIER) { 142 // Use simplifier after stroker before Renderer 143 // to remove collinear segments (notably due to cap square) 144 pc = rdrCtx.simplifier.init(pc); 145 } 146 147 final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 148 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx); 149 150 if (stroke != null) { 151 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), 152 stroke.getLineJoin(), stroke.getMiterLimit()); 153 154 if (dashes != null) { 155 if (!recycleDashes) { 156 dashLen = dashes.length; 157 } 158 pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase, recycleDashes); 159 } 160 } 161 162 pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx); 163 164 /* 165 * Pipeline seems to be: 166 * shape.getPathIterator(tx) 167 * -> (inverseDeltaTransformConsumer) 168 * -> (Dasher) 169 * -> Stroker 170 * -> (deltaTransformConsumer) 171 * 172 * -> (CollinearSimplifier) to remove redundant segments 173 * 174 * -> pc2d = Renderer (bounding box) 175 */ 176 return pc; 177 } 178 179 private static boolean nearZero(final double num) { 180 return Math.abs(num) < 2.0d * Math.ulp(num); 181 } 182 183 public static MarlinRenderer setupRenderer( 184 final RendererContext rdrCtx, 185 final Shape shape, 186 final BasicStroke stroke, 187 final BaseTransform xform, 188 final Rectangle rclip, 189 final boolean antialiasedShape) 190 { 191 // Test if transform is identity: 192 final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null; 193 194 final PathIterator pi = shape.getPathIterator(tf); 195 196 final MarlinRenderer r = (!FORCE_NO_AA && antialiasedShape) ? 197 rdrCtx.renderer : rdrCtx.getRendererNoAA(); 198 199 final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r); 200 201 feedConsumer(rdrCtx, pi, pc2d); 202 203 return r; 204 } 205 206 public static MarlinRenderer setupRenderer( 207 final RendererContext rdrCtx, 208 final Path2D p2d, 209 final BasicStroke stroke, 210 final BaseTransform xform, 211 final Rectangle rclip, 212 final boolean antialiasedShape) 213 { 214 // Test if transform is identity: 215 final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null; 216 217 final MarlinRenderer r = (!FORCE_NO_AA && antialiasedShape) ? 218 rdrCtx.renderer : rdrCtx.getRendererNoAA(); 219 220 final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r); 221 222 feedConsumer(rdrCtx, p2d, tf, pc2d); 223 224 return r; 225 } 226 227 private static void feedConsumer(final RendererContext rdrCtx, final PathIterator pi, 228 final PathConsumer2D pc2d) 229 { 230 // mark context as DIRTY: 231 rdrCtx.dirty = true; 232 233 final float[] coords = rdrCtx.float6; 234 235 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 236 // - removed skip flag = !subpathStarted 237 // - removed pathClosed (ie subpathStarted not set to false) 238 boolean subpathStarted = false; 239 240 for (; !pi.isDone(); pi.next()) { 241 switch (pi.currentSegment(coords)) { 242 case PathIterator.SEG_MOVETO: 243 /* Checking SEG_MOVETO coordinates if they are out of the 244 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 341 342 // mark context as CLEAN: 343 rdrCtx.dirty = false; 344 } 345 346 private static void feedConsumer(final RendererContext rdrCtx, 347 final Path2D p2d, 348 final BaseTransform xform, 349 final PathConsumer2D pc2d) 350 { 351 // mark context as DIRTY: 352 rdrCtx.dirty = true; 353 354 final float[] coords = rdrCtx.float6; 355 356 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 357 // - removed skip flag = !subpathStarted 358 // - removed pathClosed (ie subpathStarted not set to false) 359 boolean subpathStarted = false; 360 361 final float pCoords[] = p2d.getFloatCoordsNoClone(); 362 final byte pTypes[] = p2d.getCommandsNoClone(); 363 final int nsegs = p2d.getNumCommands(); 364 365 for (int i = 0, coff = 0; i < nsegs; i++) { 366 switch (pTypes[i]) { 367 case PathIterator.SEG_MOVETO: 368 if (xform == null) { 369 coords[0] = pCoords[coff]; 370 coords[1] = pCoords[coff+1]; 371 } else { 372 xform.transform(pCoords, coff, coords, 0, 1); 373 } 374 coff += 2; 375 /* Checking SEG_MOVETO coordinates if they are out of the 376 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 377 * and Infinity values. Skipping next path segment in case of 378 * invalid data. 379 */ 380 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 381 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 382 { | 34 import com.sun.javafx.geom.transform.BaseTransform; 35 import com.sun.marlin.MarlinConst; 36 import com.sun.marlin.MarlinRenderer; 37 import com.sun.marlin.RendererContext; 38 import com.sun.marlin.TransformingPathConsumer2D; 39 import com.sun.prism.BasicStroke; 40 41 public final class MarlinPrismUtils { 42 43 private static final boolean FORCE_NO_AA = false; 44 45 static final float UPPER_BND = Float.MAX_VALUE / 2.0f; 46 static final float LOWER_BND = -UPPER_BND; 47 48 /** 49 * Private constructor to prevent instantiation. 50 */ 51 private MarlinPrismUtils() { 52 } 53 54 private static boolean nearZero(final double num) { 55 return Math.abs(num) < 2.0d * Math.ulp(num); 56 } 57 58 private static PathConsumer2D initPipeline( 59 final RendererContext rdrCtx, 60 final BasicStroke stroke, 61 final float lineWidth, 62 final BaseTransform tx, 63 final PathConsumer2D out) 64 { 65 // We use strokerat so that in Stroker and Dasher we can work only 66 // with the pre-transformation coordinates. This will repeat a lot of 67 // computations done in the path iterator, but the alternative is to 68 // work with transformed paths and compute untransformed coordinates 69 // as needed. This would be faster but I do not think the complexity 70 // of working with both untransformed and transformed coordinates in 71 // the same code is worth it. 72 // However, if a path's width is constant after a transformation, 73 // we can skip all this untransforming. 74 75 // As pathTo() will check transformed coordinates for invalid values 76 // (NaN / Infinity) to ignore such points, it is necessary to apply the 77 // transformation before the path processing. 78 BaseTransform strokerTx = null; 79 80 int dashLen = -1; 81 boolean recycleDashes = false; 82 float scale = 1.0f; 83 float width = 0.0f, dashphase = 0.0f; 84 float[] dashes = null; 85 86 if (stroke != null) { 87 width = lineWidth; 88 dashes = stroke.getDashArray(); 89 dashphase = stroke.getDashPhase(); 90 91 if ((tx != null) && !tx.isIdentity()) { 92 final double a = tx.getMxx(); 93 final double b = tx.getMxy(); 94 final double c = tx.getMyx(); 95 final double d = tx.getMyy(); 96 97 // If the transform is a constant multiple of an orthogonal transformation 98 // then every length is just multiplied by a constant, so we just 99 // need to transform input paths to stroker and tell stroker 100 // the scaled width. This condition is satisfied if 101 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 102 // leave a bit of room for error. 103 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { 104 scale = (float) Math.sqrt(a*a + c*c); 105 106 if (dashes != null) { 107 recycleDashes = true; 108 dashLen = dashes.length; 109 dashes = rdrCtx.dasher.copyDashArray(dashes); 110 for (int i = 0; i < dashLen; i++) { 111 dashes[i] *= scale; 112 } 113 dashphase *= scale; 114 } 115 width *= scale; 116 117 // by now strokerat == null. Input paths to 118 // stroker (and maybe dasher) will have the full transform tx 119 // applied to them and nothing will happen to the output paths. 120 } else { 121 strokerTx = tx; 122 123 // by now strokerat == tx. Input paths to 124 // stroker (and maybe dasher) will have the full transform tx 125 // applied to them, then they will be normalized, and then 126 // the inverse of *only the non translation part of tx* will 127 // be applied to the normalized paths. This won't cause problems 128 // in stroker, because, suppose tx = T*A, where T is just the 129 // translation part of tx, and A is the rest. T*A has already 130 // been applied to Stroker/Dasher's input. Then Ainv will be 131 // applied. Ainv*T*A is not equal to T, but it is a translation, 132 // which means that none of stroker's assumptions about its 133 // input will be violated. After all this, A will be applied 134 // to stroker's output. 135 } 136 } 137 } 138 139 // Prepare the pipeline: 140 PathConsumer2D pc = out; 141 142 if (MarlinConst.USE_SIMPLIFIER) { 143 // Use simplifier after stroker before Renderer 144 // to remove collinear segments (notably due to cap square) 145 pc = rdrCtx.simplifier.init(pc); 146 } 147 148 final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 149 150 if (stroke != null) { 151 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx); 152 153 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), 154 stroke.getLineJoin(), stroke.getMiterLimit()); 155 156 if (dashes != null) { 157 if (!recycleDashes) { 158 dashLen = dashes.length; 159 } 160 pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase, recycleDashes); 161 } 162 pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx); 163 } 164 165 /* 166 * Pipeline seems to be: 167 * shape.getPathIterator(tx) 168 * -> (inverseDeltaTransformConsumer) 169 * -> (Dasher) 170 * -> Stroker 171 * -> (deltaTransformConsumer) 172 * 173 * -> (CollinearSimplifier) to remove redundant segments 174 * 175 * -> pc2d = Renderer (bounding box) 176 */ 177 return pc; 178 } 179 180 private static PathConsumer2D initRenderer( 181 final RendererContext rdrCtx, 182 final BasicStroke stroke, 183 final BaseTransform tx, 184 final Rectangle clip, 185 final int piRule, 186 final MarlinRenderer renderer) 187 { 188 final int oprule = ((stroke == null) && (piRule == PathIterator.WIND_EVEN_ODD)) ? 189 MarlinRenderer.WIND_EVEN_ODD : MarlinRenderer.WIND_NON_ZERO; 190 191 renderer.init(clip.x, clip.y, clip.width, clip.height, oprule); 192 193 float lw = 0.0f; 194 195 if (stroke != null) { 196 lw = stroke.getLineWidth(); 197 } 198 199 return initPipeline(rdrCtx, stroke, lw, tx, renderer); 200 } 201 202 public static MarlinRenderer setupRenderer( 203 final RendererContext rdrCtx, 204 final Shape shape, 205 final BasicStroke stroke, 206 final BaseTransform xform, 207 final Rectangle rclip, 208 final boolean antialiasedShape) 209 { 210 // Test if transform is identity: 211 final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null; 212 213 final MarlinRenderer r = (!FORCE_NO_AA && antialiasedShape) ? 214 rdrCtx.renderer : rdrCtx.getRendererNoAA(); 215 216 if (shape instanceof Path2D) { 217 final Path2D p2d = (Path2D)shape; 218 final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r); 219 feedConsumer(rdrCtx, p2d, tf, pc2d); 220 } else { 221 final PathIterator pi = shape.getPathIterator(tf); 222 final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r); 223 feedConsumer(rdrCtx, pi, pc2d); 224 } 225 return r; 226 } 227 228 public static void strokeTo( 229 final RendererContext rdrCtx, 230 final Shape shape, 231 final BasicStroke stroke, 232 final float lineWidth, 233 final PathConsumer2D out) 234 { 235 final PathConsumer2D pc2d = initPipeline(rdrCtx, stroke, lineWidth, null, out); 236 237 if (shape instanceof Path2D) { 238 feedConsumer(rdrCtx, (Path2D)shape, null, pc2d); 239 } else { 240 feedConsumer(rdrCtx, shape.getPathIterator(null), pc2d); 241 } 242 } 243 244 private static void feedConsumer(final RendererContext rdrCtx, final PathIterator pi, 245 final PathConsumer2D pc2d) 246 { 247 // mark context as DIRTY: 248 rdrCtx.dirty = true; 249 250 final float[] coords = rdrCtx.float6; 251 252 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 253 // - removed skip flag = !subpathStarted 254 // - removed pathClosed (ie subpathStarted not set to false) 255 boolean subpathStarted = false; 256 257 for (; !pi.isDone(); pi.next()) { 258 switch (pi.currentSegment(coords)) { 259 case PathIterator.SEG_MOVETO: 260 /* Checking SEG_MOVETO coordinates if they are out of the 261 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 358 359 // mark context as CLEAN: 360 rdrCtx.dirty = false; 361 } 362 363 private static void feedConsumer(final RendererContext rdrCtx, 364 final Path2D p2d, 365 final BaseTransform xform, 366 final PathConsumer2D pc2d) 367 { 368 // mark context as DIRTY: 369 rdrCtx.dirty = true; 370 371 final float[] coords = rdrCtx.float6; 372 373 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 374 // - removed skip flag = !subpathStarted 375 // - removed pathClosed (ie subpathStarted not set to false) 376 boolean subpathStarted = false; 377 378 final float[] pCoords = p2d.getFloatCoordsNoClone(); 379 final byte[] pTypes = p2d.getCommandsNoClone(); 380 final int nsegs = p2d.getNumCommands(); 381 382 for (int i = 0, coff = 0; i < nsegs; i++) { 383 switch (pTypes[i]) { 384 case PathIterator.SEG_MOVETO: 385 if (xform == null) { 386 coords[0] = pCoords[coff]; 387 coords[1] = pCoords[coff+1]; 388 } else { 389 xform.transform(pCoords, coff, coords, 0, 1); 390 } 391 coff += 2; 392 /* Checking SEG_MOVETO coordinates if they are out of the 393 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 394 * and Infinity values. Skipping next path segment in case of 395 * invalid data. 396 */ 397 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 398 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 399 { |