1 /* 2 * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 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.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.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 246 * and Infinity values. Skipping next path segment in case of 247 * invalid data. 248 */ 249 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 250 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 251 { 252 pc2d.moveTo(coords[0], coords[1]); 253 subpathStarted = true; 254 } 255 break; 256 case PathIterator.SEG_LINETO: 257 /* Checking SEG_LINETO coordinates if they are out of the 258 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 259 * and Infinity values. Ignoring current path segment in case 260 * of invalid data. If segment is skipped its endpoint 261 * (if valid) is used to begin new subpath. 262 */ 263 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 264 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 265 { 266 if (subpathStarted) { 267 pc2d.lineTo(coords[0], coords[1]); 268 } else { 269 pc2d.moveTo(coords[0], coords[1]); 270 subpathStarted = true; 271 } 272 } 273 break; 274 case PathIterator.SEG_QUADTO: 275 // Quadratic curves take two points 276 /* Checking SEG_QUADTO coordinates if they are out of the 277 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 278 * and Infinity values. Ignoring current path segment in case 279 * of invalid endpoints's data. Equivalent to the SEG_LINETO 280 * if endpoint coordinates are valid but there are invalid data 281 * among other coordinates 282 */ 283 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND && 284 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 285 { 286 if (subpathStarted) { 287 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 288 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 289 { 290 pc2d.quadTo(coords[0], coords[1], 291 coords[2], coords[3]); 292 } else { 293 pc2d.lineTo(coords[2], coords[3]); 294 } 295 } else { 296 pc2d.moveTo(coords[2], coords[3]); 297 subpathStarted = true; 298 } 299 } 300 break; 301 case PathIterator.SEG_CUBICTO: 302 // Cubic curves take three points 303 /* Checking SEG_CUBICTO coordinates if they are out of the 304 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 305 * and Infinity values. Ignoring current path segment in case 306 * of invalid endpoints's data. Equivalent to the SEG_LINETO 307 * if endpoint coordinates are valid but there are invalid data 308 * among other coordinates 309 */ 310 if (coords[4] < UPPER_BND && coords[4] > LOWER_BND && 311 coords[5] < UPPER_BND && coords[5] > LOWER_BND) 312 { 313 if (subpathStarted) { 314 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 315 coords[1] < UPPER_BND && coords[1] > LOWER_BND && 316 coords[2] < UPPER_BND && coords[2] > LOWER_BND && 317 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 318 { 319 pc2d.curveTo(coords[0], coords[1], 320 coords[2], coords[3], 321 coords[4], coords[5]); 322 } else { 323 pc2d.lineTo(coords[4], coords[5]); 324 } 325 } else { 326 pc2d.moveTo(coords[4], coords[5]); 327 subpathStarted = true; 328 } 329 } 330 break; 331 case PathIterator.SEG_CLOSE: 332 if (subpathStarted) { 333 pc2d.closePath(); 334 // do not set subpathStarted to false 335 // in case of missing moveTo() after close() 336 } 337 break; 338 default: 339 } 340 } 341 pc2d.pathDone(); 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 { 384 pc2d.moveTo(coords[0], coords[1]); 385 subpathStarted = true; 386 } 387 break; 388 case PathIterator.SEG_LINETO: 389 if (xform == null) { 390 coords[0] = pCoords[coff]; 391 coords[1] = pCoords[coff+1]; 392 } else { 393 xform.transform(pCoords, coff, coords, 0, 1); 394 } 395 coff += 2; 396 /* Checking SEG_LINETO coordinates if they are out of the 397 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 398 * and Infinity values. Ignoring current path segment in case 399 * of invalid data. If segment is skipped its endpoint 400 * (if valid) is used to begin new subpath. 401 */ 402 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 403 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 404 { 405 if (subpathStarted) { 406 pc2d.lineTo(coords[0], coords[1]); 407 } else { 408 pc2d.moveTo(coords[0], coords[1]); 409 subpathStarted = true; 410 } 411 } 412 break; 413 case PathIterator.SEG_QUADTO: 414 if (xform == null) { 415 coords[0] = pCoords[coff]; 416 coords[1] = pCoords[coff+1]; 417 coords[2] = pCoords[coff+2]; 418 coords[3] = pCoords[coff+3]; 419 } else { 420 xform.transform(pCoords, coff, coords, 0, 2); 421 } 422 coff += 4; 423 // Quadratic curves take two points 424 /* Checking SEG_QUADTO coordinates if they are out of the 425 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 426 * and Infinity values. Ignoring current path segment in case 427 * of invalid endpoints's data. Equivalent to the SEG_LINETO 428 * if endpoint coordinates are valid but there are invalid data 429 * among other coordinates 430 */ 431 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND && 432 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 433 { 434 if (subpathStarted) { 435 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 436 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 437 { 438 pc2d.quadTo(coords[0], coords[1], 439 coords[2], coords[3]); 440 } else { 441 pc2d.lineTo(coords[2], coords[3]); 442 } 443 } else { 444 pc2d.moveTo(coords[2], coords[3]); 445 subpathStarted = true; 446 } 447 } 448 break; 449 case PathIterator.SEG_CUBICTO: 450 if (xform == null) { 451 coords[0] = pCoords[coff]; 452 coords[1] = pCoords[coff+1]; 453 coords[2] = pCoords[coff+2]; 454 coords[3] = pCoords[coff+3]; 455 coords[4] = pCoords[coff+4]; 456 coords[5] = pCoords[coff+5]; 457 } else { 458 xform.transform(pCoords, coff, coords, 0, 3); 459 } 460 coff += 6; 461 // Cubic curves take three points 462 /* Checking SEG_CUBICTO coordinates if they are out of the 463 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 464 * and Infinity values. Ignoring current path segment in case 465 * of invalid endpoints's data. Equivalent to the SEG_LINETO 466 * if endpoint coordinates are valid but there are invalid data 467 * among other coordinates 468 */ 469 if (coords[4] < UPPER_BND && coords[4] > LOWER_BND && 470 coords[5] < UPPER_BND && coords[5] > LOWER_BND) 471 { 472 if (subpathStarted) { 473 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 474 coords[1] < UPPER_BND && coords[1] > LOWER_BND && 475 coords[2] < UPPER_BND && coords[2] > LOWER_BND && 476 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 477 { 478 pc2d.curveTo(coords[0], coords[1], 479 coords[2], coords[3], 480 coords[4], coords[5]); 481 } else { 482 pc2d.lineTo(coords[4], coords[5]); 483 } 484 } else { 485 pc2d.moveTo(coords[4], coords[5]); 486 subpathStarted = true; 487 } 488 } 489 break; 490 case PathIterator.SEG_CLOSE: 491 if (subpathStarted) { 492 pc2d.closePath(); 493 // do not set subpathStarted to false 494 // in case of missing moveTo() after close() 495 } 496 break; 497 default: 498 } 499 } 500 pc2d.pathDone(); 501 502 // mark context as CLEAN: 503 rdrCtx.dirty = false; 504 } 505 }