34 import com.sun.marlin.MarlinConst; 35 import com.sun.marlin.DMarlinRenderer; 36 import com.sun.marlin.DPathConsumer2D; 37 import com.sun.marlin.DRendererContext; 38 import com.sun.marlin.DTransformingPathConsumer2D; 39 import com.sun.prism.BasicStroke; 40 41 public final class DMarlinPrismUtils { 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 DMarlinPrismUtils() { 52 } 53 54 private static DPathConsumer2D initRenderer( 55 final DRendererContext rdrCtx, 56 final BasicStroke stroke, 57 final BaseTransform tx, 58 final Rectangle clip, 59 final int pirule, 60 final DMarlinRenderer renderer) 61 { 62 final int oprule = (stroke == null && pirule == PathIterator.WIND_EVEN_ODD) ? 63 DMarlinRenderer.WIND_EVEN_ODD : DMarlinRenderer.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 double width = 0.0f, dashphase = 0.0f; 84 double[] dashesD = null; 85 86 if (stroke != null) { 87 width = stroke.getLineWidth(); 88 final float[] dashes = stroke.getDashArray(); 89 dashphase = stroke.getDashPhase(); 90 91 // Ensure converting dashes to double precision: 92 if (dashes != null) { 93 recycleDashes = true; 94 dashLen = dashes.length; 95 dashesD = rdrCtx.dasher.copyDashArray(dashes); 96 } 97 98 if (tx != null && !tx.isIdentity()) { 99 final double a = tx.getMxx(); 100 final double b = tx.getMxy(); 101 final double c = tx.getMyx(); 102 final double d = tx.getMyy(); 103 104 // If the transform is a constant multiple of an orthogonal transformation 105 // then every length is just multiplied by a constant, so we just 106 // need to transform input paths to stroker and tell stroker 107 // the scaled width. This condition is satisfied if 108 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 109 // leave a bit of room for error. 110 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { 111 final double scale = Math.sqrt(a*a + c*c); 112 113 if (dashesD != null) { 114 for (int i = 0; i < dashLen; i++) { 115 dashesD[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 } 141 } 142 143 DPathConsumer2D pc = renderer.init(clip.x, clip.y, clip.width, clip.height, oprule); 144 145 if (MarlinConst.USE_SIMPLIFIER) { 146 // Use simplifier after stroker before Renderer 147 // to remove collinear segments (notably due to cap square) 148 pc = rdrCtx.simplifier.init(pc); 149 } 150 151 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 152 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx); 153 154 if (stroke != null) { 155 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), 156 stroke.getLineJoin(), stroke.getMiterLimit()); 157 158 if (dashesD != null) { 159 pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase, recycleDashes); 160 } 161 } 162 163 pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx); 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 boolean nearZero(final double num) { 181 return Math.abs(num) < 2.0d * Math.ulp(num); 182 } 183 184 public static DMarlinRenderer setupRenderer( 185 final DRendererContext rdrCtx, 186 final Shape shape, 187 final BasicStroke stroke, 188 final BaseTransform xform, 189 final Rectangle rclip, 190 final boolean antialiasedShape) 191 { 192 // Test if transform is identity: 193 final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null; 194 195 final PathIterator pi = shape.getPathIterator(tf); 196 197 final DMarlinRenderer r = (!FORCE_NO_AA && antialiasedShape) ? 198 rdrCtx.renderer : rdrCtx.getRendererNoAA(); 199 200 final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r); 201 202 feedConsumer(rdrCtx, pi, pc2d); 203 204 return r; 205 } 206 207 public static DMarlinRenderer setupRenderer( 208 final DRendererContext rdrCtx, 209 final Path2D p2d, 210 final BasicStroke stroke, 211 final BaseTransform xform, 212 final Rectangle rclip, 213 final boolean antialiasedShape) 214 { 215 // Test if transform is identity: 216 final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null; 217 218 final DMarlinRenderer r = (!FORCE_NO_AA && antialiasedShape) ? 219 rdrCtx.renderer : rdrCtx.getRendererNoAA(); 220 221 final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r); 222 223 feedConsumer(rdrCtx, p2d, tf, pc2d); 224 225 return r; 226 } 227 228 private static void feedConsumer(final DRendererContext rdrCtx, final PathIterator pi, 229 final DPathConsumer2D pc2d) 230 { 231 // mark context as DIRTY: 232 rdrCtx.dirty = true; 233 234 final float[] coords = rdrCtx.float6; 235 236 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 237 // - removed skip flag = !subpathStarted 238 // - removed pathClosed (ie subpathStarted not set to false) 239 boolean subpathStarted = false; 240 241 for (; !pi.isDone(); pi.next()) { 242 switch (pi.currentSegment(coords)) { 243 case PathIterator.SEG_MOVETO: 244 /* Checking SEG_MOVETO coordinates if they are out of the 245 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 342 343 // mark context as CLEAN: 344 rdrCtx.dirty = false; 345 } 346 347 private static void feedConsumer(final DRendererContext rdrCtx, 348 final Path2D p2d, 349 final BaseTransform xform, 350 final DPathConsumer2D pc2d) 351 { 352 // mark context as DIRTY: 353 rdrCtx.dirty = true; 354 355 final float[] coords = rdrCtx.float6; 356 357 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 358 // - removed skip flag = !subpathStarted 359 // - removed pathClosed (ie subpathStarted not set to false) 360 boolean subpathStarted = false; 361 362 final float pCoords[] = p2d.getFloatCoordsNoClone(); 363 final byte pTypes[] = p2d.getCommandsNoClone(); 364 final int nsegs = p2d.getNumCommands(); 365 366 for (int i = 0, coff = 0; i < nsegs; i++) { 367 switch (pTypes[i]) { 368 case PathIterator.SEG_MOVETO: 369 if (xform == null) { 370 coords[0] = pCoords[coff]; 371 coords[1] = pCoords[coff+1]; 372 } else { 373 xform.transform(pCoords, coff, coords, 0, 1); 374 } 375 coff += 2; 376 /* Checking SEG_MOVETO coordinates if they are out of the 377 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 378 * and Infinity values. Skipping next path segment in case of 379 * invalid data. 380 */ 381 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 382 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 383 { | 34 import com.sun.marlin.MarlinConst; 35 import com.sun.marlin.DMarlinRenderer; 36 import com.sun.marlin.DPathConsumer2D; 37 import com.sun.marlin.DRendererContext; 38 import com.sun.marlin.DTransformingPathConsumer2D; 39 import com.sun.prism.BasicStroke; 40 41 public final class DMarlinPrismUtils { 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 DMarlinPrismUtils() { 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 DPathConsumer2D initPipeline( 59 final DRendererContext rdrCtx, 60 final BasicStroke stroke, 61 final float lineWidth, 62 final BaseTransform tx, 63 final DPathConsumer2D 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 double scale = 1.0d; 83 double width = 0.0f, dashphase = 0.0f; 84 double[] dashesD = null; 85 86 if (stroke != null) { 87 width = lineWidth; 88 final float[] dashes = stroke.getDashArray(); 89 dashphase = stroke.getDashPhase(); 90 91 // Ensure converting dashes to double precision: 92 if (dashes != null) { 93 recycleDashes = true; 94 dashLen = dashes.length; 95 dashesD = rdrCtx.dasher.copyDashArray(dashes); 96 } 97 98 if ((tx != null) && !tx.isIdentity()) { 99 final double a = tx.getMxx(); 100 final double b = tx.getMxy(); 101 final double c = tx.getMyx(); 102 final double d = tx.getMyy(); 103 104 // If the transform is a constant multiple of an orthogonal transformation 105 // then every length is just multiplied by a constant, so we just 106 // need to transform input paths to stroker and tell stroker 107 // the scaled width. This condition is satisfied if 108 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 109 // leave a bit of room for error. 110 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { 111 scale = Math.sqrt(a*a + c*c); 112 113 if (dashesD != null) { 114 for (int i = 0; i < dashLen; i++) { 115 dashesD[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 } 141 } 142 143 // Prepare the pipeline: 144 DPathConsumer2D pc = out; 145 146 if (MarlinConst.USE_SIMPLIFIER) { 147 // Use simplifier after stroker before Renderer 148 // to remove collinear segments (notably due to cap square) 149 pc = rdrCtx.simplifier.init(pc); 150 } 151 152 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 153 154 if (stroke != null) { 155 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx); 156 157 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), 158 stroke.getLineJoin(), stroke.getMiterLimit()); 159 160 if (dashesD != null) { 161 pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase, recycleDashes); 162 } 163 pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx); 164 } 165 166 /* 167 * Pipeline seems to be: 168 * shape.getPathIterator(tx) 169 * -> (inverseDeltaTransformConsumer) 170 * -> (Dasher) 171 * -> Stroker 172 * -> (deltaTransformConsumer) 173 * 174 * -> (CollinearSimplifier) to remove redundant segments 175 * 176 * -> pc2d = Renderer (bounding box) 177 */ 178 return pc; 179 } 180 181 private static DPathConsumer2D initRenderer( 182 final DRendererContext rdrCtx, 183 final BasicStroke stroke, 184 final BaseTransform tx, 185 final Rectangle clip, 186 final int piRule, 187 final DMarlinRenderer renderer) 188 { 189 final int oprule = ((stroke == null) && (piRule == PathIterator.WIND_EVEN_ODD)) ? 190 DMarlinRenderer.WIND_EVEN_ODD : DMarlinRenderer.WIND_NON_ZERO; 191 192 renderer.init(clip.x, clip.y, clip.width, clip.height, oprule); 193 194 float lw = 0.0f; 195 196 if (stroke != null) { 197 lw = stroke.getLineWidth(); 198 } 199 200 return initPipeline(rdrCtx, stroke, lw, tx, renderer); 201 } 202 203 public static DMarlinRenderer setupRenderer( 204 final DRendererContext rdrCtx, 205 final Shape shape, 206 final BasicStroke stroke, 207 final BaseTransform xform, 208 final Rectangle rclip, 209 final boolean antialiasedShape) 210 { 211 // Test if transform is identity: 212 final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null; 213 214 final DMarlinRenderer r = (!FORCE_NO_AA && antialiasedShape) ? 215 rdrCtx.renderer : rdrCtx.getRendererNoAA(); 216 217 if (shape instanceof Path2D) { 218 final Path2D p2d = (Path2D)shape; 219 final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r); 220 feedConsumer(rdrCtx, p2d, tf, pc2d); 221 } else { 222 final PathIterator pi = shape.getPathIterator(tf); 223 final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r); 224 feedConsumer(rdrCtx, pi, pc2d); 225 } 226 return r; 227 } 228 229 public static void strokeTo( 230 final DRendererContext rdrCtx, 231 final Shape shape, 232 final BasicStroke stroke, 233 final float lineWidth, 234 final DPathConsumer2D out) 235 { 236 final DPathConsumer2D pc2d = initPipeline(rdrCtx, stroke, lineWidth, null, out); 237 238 if (shape instanceof Path2D) { 239 feedConsumer(rdrCtx, (Path2D)shape, null, pc2d); 240 } else { 241 feedConsumer(rdrCtx, shape.getPathIterator(null), pc2d); 242 } 243 } 244 245 private static void feedConsumer(final DRendererContext rdrCtx, final PathIterator pi, 246 final DPathConsumer2D pc2d) 247 { 248 // mark context as DIRTY: 249 rdrCtx.dirty = true; 250 251 final float[] coords = rdrCtx.float6; 252 253 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 254 // - removed skip flag = !subpathStarted 255 // - removed pathClosed (ie subpathStarted not set to false) 256 boolean subpathStarted = false; 257 258 for (; !pi.isDone(); pi.next()) { 259 switch (pi.currentSegment(coords)) { 260 case PathIterator.SEG_MOVETO: 261 /* Checking SEG_MOVETO coordinates if they are out of the 262 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 359 360 // mark context as CLEAN: 361 rdrCtx.dirty = false; 362 } 363 364 private static void feedConsumer(final DRendererContext rdrCtx, 365 final Path2D p2d, 366 final BaseTransform xform, 367 final DPathConsumer2D pc2d) 368 { 369 // mark context as DIRTY: 370 rdrCtx.dirty = true; 371 372 final float[] coords = rdrCtx.float6; 373 374 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 375 // - removed skip flag = !subpathStarted 376 // - removed pathClosed (ie subpathStarted not set to false) 377 boolean subpathStarted = false; 378 379 final float[] pCoords = p2d.getFloatCoordsNoClone(); 380 final byte[] pTypes = p2d.getCommandsNoClone(); 381 final int nsegs = p2d.getNumCommands(); 382 383 for (int i = 0, coff = 0; i < nsegs; i++) { 384 switch (pTypes[i]) { 385 case PathIterator.SEG_MOVETO: 386 if (xform == null) { 387 coords[0] = pCoords[coff]; 388 coords[1] = pCoords[coff+1]; 389 } else { 390 xform.transform(pCoords, coff, coords, 0, 1); 391 } 392 coff += 2; 393 /* Checking SEG_MOVETO coordinates if they are out of the 394 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 395 * and Infinity values. Skipping next path segment in case of 396 * invalid data. 397 */ 398 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 399 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 400 { |