1 /* 2 * Copyright (c) 2011, 2018, 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.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 */ 62 private DMarlinPrismUtils() { 63 } 64 65 private static DPathConsumer2D initStroker( 66 final DRendererContext rdrCtx, 67 final BasicStroke stroke, 68 final float lineWidth, 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 } 202 203 // If no dash and clip is enabled: 204 // detect closedPaths (polygons) for caps 205 pc = transformerPC2D.detectClosedPath(pc); 206 } 207 pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx); 208 209 if (DO_TRACE_PATH) { 210 // trace Input: 211 pc = transformerPC2D.traceInput(pc); 212 } 213 /* 214 * Pipeline seems to be: 215 * shape.getPathIterator(tx) 216 * -> (inverseDeltaTransformConsumer) 217 * -> (Dasher) 218 * -> Stroker 219 * -> (deltaTransformConsumer) 220 * 221 * -> (CollinearSimplifier) to remove redundant segments 222 * 223 * -> pc2d = Renderer (bounding box) 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; 298 299 final DMarlinRenderer r = (!FORCE_NO_AA && antialiasedShape) ? 300 rdrCtx.renderer : rdrCtx.getRendererNoAA(); 301 302 if (shape instanceof Path2D) { 303 final Path2D p2d = (Path2D)shape; 304 final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r); 305 feedConsumer(rdrCtx, p2d, tf, pc2d); 306 } else { 307 final PathIterator pi = shape.getPathIterator(tf); 308 final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r); 309 feedConsumer(rdrCtx, pi, pc2d); 310 } 311 return r; 312 } 313 314 public static void strokeTo( 315 final DRendererContext rdrCtx, 316 final Shape shape, 317 final BasicStroke stroke, 318 final float lineWidth, 319 final DPathConsumer2D out) 320 { 321 final DPathConsumer2D pc2d = initStroker(rdrCtx, stroke, lineWidth, null, out); 322 323 if (shape instanceof Path2D) { 324 feedConsumer(rdrCtx, (Path2D)shape, null, pc2d); 325 } else { 326 feedConsumer(rdrCtx, shape.getPathIterator(null), pc2d); 327 } 328 } 329 330 private static void feedConsumer(final DRendererContext rdrCtx, final PathIterator pi, 331 DPathConsumer2D pc2d) 332 { 333 if (MarlinConst.USE_PATH_SIMPLIFIER) { 334 // Use path simplifier at the first step 335 // to remove useless points 336 pc2d = rdrCtx.pathSimplifier.init(pc2d); 337 } 338 339 // mark context as DIRTY: 340 rdrCtx.dirty = true; 341 342 final float[] coords = rdrCtx.float6; 343 344 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 345 // - removed skip flag = !subpathStarted 346 // - removed pathClosed (ie subpathStarted not set to false) 347 boolean subpathStarted = false; 348 349 for (; !pi.isDone(); pi.next()) { 350 switch (pi.currentSegment(coords)) { 351 case PathIterator.SEG_MOVETO: 352 /* Checking SEG_MOVETO coordinates if they are out of the 353 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 354 * and Infinity values. Skipping next path segment in case of 355 * invalid data. 356 */ 357 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 358 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 359 { 360 pc2d.moveTo(coords[0], coords[1]); 361 subpathStarted = true; 362 } 363 break; 364 case PathIterator.SEG_LINETO: 365 /* Checking SEG_LINETO coordinates if they are out of the 366 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 367 * and Infinity values. Ignoring current path segment in case 368 * of invalid data. If segment is skipped its endpoint 369 * (if valid) is used to begin new subpath. 370 */ 371 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 372 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 373 { 374 if (subpathStarted) { 375 pc2d.lineTo(coords[0], coords[1]); 376 } else { 377 pc2d.moveTo(coords[0], coords[1]); 378 subpathStarted = true; 379 } 380 } 381 break; 382 case PathIterator.SEG_QUADTO: 383 // Quadratic curves take two points 384 /* Checking SEG_QUADTO coordinates if they are out of the 385 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 386 * and Infinity values. Ignoring current path segment in case 387 * of invalid endpoints's data. Equivalent to the SEG_LINETO 388 * if endpoint coordinates are valid but there are invalid data 389 * among other coordinates 390 */ 391 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND && 392 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 393 { 394 if (subpathStarted) { 395 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 396 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 397 { 398 pc2d.quadTo(coords[0], coords[1], 399 coords[2], coords[3]); 400 } else { 401 pc2d.lineTo(coords[2], coords[3]); 402 } 403 } else { 404 pc2d.moveTo(coords[2], coords[3]); 405 subpathStarted = true; 406 } 407 } 408 break; 409 case PathIterator.SEG_CUBICTO: 410 // Cubic curves take three points 411 /* Checking SEG_CUBICTO coordinates if they are out of the 412 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 413 * and Infinity values. Ignoring current path segment in case 414 * of invalid endpoints's data. Equivalent to the SEG_LINETO 415 * if endpoint coordinates are valid but there are invalid data 416 * among other coordinates 417 */ 418 if (coords[4] < UPPER_BND && coords[4] > LOWER_BND && 419 coords[5] < UPPER_BND && coords[5] > LOWER_BND) 420 { 421 if (subpathStarted) { 422 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 423 coords[1] < UPPER_BND && coords[1] > LOWER_BND && 424 coords[2] < UPPER_BND && coords[2] > LOWER_BND && 425 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 426 { 427 pc2d.curveTo(coords[0], coords[1], 428 coords[2], coords[3], 429 coords[4], coords[5]); 430 } else { 431 pc2d.lineTo(coords[4], coords[5]); 432 } 433 } else { 434 pc2d.moveTo(coords[4], coords[5]); 435 subpathStarted = true; 436 } 437 } 438 break; 439 case PathIterator.SEG_CLOSE: 440 if (subpathStarted) { 441 pc2d.closePath(); 442 // do not set subpathStarted to false 443 // in case of missing moveTo() after close() 444 } 445 break; 446 default: 447 } 448 } 449 pc2d.pathDone(); 450 451 // mark context as CLEAN: 452 rdrCtx.dirty = false; 453 } 454 455 private static void feedConsumer(final DRendererContext rdrCtx, 456 final Path2D p2d, 457 final BaseTransform xform, 458 DPathConsumer2D pc2d) 459 { 460 if (MarlinConst.USE_PATH_SIMPLIFIER) { 461 // Use path simplifier at the first step 462 // to remove useless points 463 pc2d = rdrCtx.pathSimplifier.init(pc2d); 464 } 465 466 // mark context as DIRTY: 467 rdrCtx.dirty = true; 468 469 final float[] coords = rdrCtx.float6; 470 471 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 472 // - removed skip flag = !subpathStarted 473 // - removed pathClosed (ie subpathStarted not set to false) 474 boolean subpathStarted = false; 475 476 final float[] pCoords = p2d.getFloatCoordsNoClone(); 477 final byte[] pTypes = p2d.getCommandsNoClone(); 478 final int nsegs = p2d.getNumCommands(); 479 480 for (int i = 0, coff = 0; i < nsegs; i++) { 481 switch (pTypes[i]) { 482 case PathIterator.SEG_MOVETO: 483 if (xform == null) { 484 coords[0] = pCoords[coff]; 485 coords[1] = pCoords[coff+1]; 486 } else { 487 xform.transform(pCoords, coff, coords, 0, 1); 488 } 489 coff += 2; 490 /* Checking SEG_MOVETO coordinates if they are out of the 491 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 492 * and Infinity values. Skipping next path segment in case of 493 * invalid data. 494 */ 495 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 496 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 497 { 498 pc2d.moveTo(coords[0], coords[1]); 499 subpathStarted = true; 500 } 501 break; 502 case PathIterator.SEG_LINETO: 503 if (xform == null) { 504 coords[0] = pCoords[coff]; 505 coords[1] = pCoords[coff+1]; 506 } else { 507 xform.transform(pCoords, coff, coords, 0, 1); 508 } 509 coff += 2; 510 /* Checking SEG_LINETO coordinates if they are out of the 511 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 512 * and Infinity values. Ignoring current path segment in case 513 * of invalid data. If segment is skipped its endpoint 514 * (if valid) is used to begin new subpath. 515 */ 516 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 517 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 518 { 519 if (subpathStarted) { 520 pc2d.lineTo(coords[0], coords[1]); 521 } else { 522 pc2d.moveTo(coords[0], coords[1]); 523 subpathStarted = true; 524 } 525 } 526 break; 527 case PathIterator.SEG_QUADTO: 528 if (xform == null) { 529 coords[0] = pCoords[coff]; 530 coords[1] = pCoords[coff+1]; 531 coords[2] = pCoords[coff+2]; 532 coords[3] = pCoords[coff+3]; 533 } else { 534 xform.transform(pCoords, coff, coords, 0, 2); 535 } 536 coff += 4; 537 // Quadratic curves take two points 538 /* Checking SEG_QUADTO coordinates if they are out of the 539 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 540 * and Infinity values. Ignoring current path segment in case 541 * of invalid endpoints's data. Equivalent to the SEG_LINETO 542 * if endpoint coordinates are valid but there are invalid data 543 * among other coordinates 544 */ 545 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND && 546 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 547 { 548 if (subpathStarted) { 549 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 550 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 551 { 552 pc2d.quadTo(coords[0], coords[1], 553 coords[2], coords[3]); 554 } else { 555 pc2d.lineTo(coords[2], coords[3]); 556 } 557 } else { 558 pc2d.moveTo(coords[2], coords[3]); 559 subpathStarted = true; 560 } 561 } 562 break; 563 case PathIterator.SEG_CUBICTO: 564 if (xform == null) { 565 coords[0] = pCoords[coff]; 566 coords[1] = pCoords[coff+1]; 567 coords[2] = pCoords[coff+2]; 568 coords[3] = pCoords[coff+3]; 569 coords[4] = pCoords[coff+4]; 570 coords[5] = pCoords[coff+5]; 571 } else { 572 xform.transform(pCoords, coff, coords, 0, 3); 573 } 574 coff += 6; 575 // Cubic curves take three points 576 /* Checking SEG_CUBICTO coordinates if they are out of the 577 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 578 * and Infinity values. Ignoring current path segment in case 579 * of invalid endpoints's data. Equivalent to the SEG_LINETO 580 * if endpoint coordinates are valid but there are invalid data 581 * among other coordinates 582 */ 583 if (coords[4] < UPPER_BND && coords[4] > LOWER_BND && 584 coords[5] < UPPER_BND && coords[5] > LOWER_BND) 585 { 586 if (subpathStarted) { 587 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 588 coords[1] < UPPER_BND && coords[1] > LOWER_BND && 589 coords[2] < UPPER_BND && coords[2] > LOWER_BND && 590 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 591 { 592 pc2d.curveTo(coords[0], coords[1], 593 coords[2], coords[3], 594 coords[4], coords[5]); 595 } else { 596 pc2d.lineTo(coords[4], coords[5]); 597 } 598 } else { 599 pc2d.moveTo(coords[4], coords[5]); 600 subpathStarted = true; 601 } 602 } 603 break; 604 case PathIterator.SEG_CLOSE: 605 if (subpathStarted) { 606 pc2d.closePath(); 607 // do not set subpathStarted to false 608 // in case of missing moveTo() after close() 609 } 610 break; 611 default: 612 } 613 } 614 pc2d.pathDone(); 615 616 // mark context as CLEAN: 617 rdrCtx.dirty = false; 618 } 619 }