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.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 static final float UPPER_BND = Float.MAX_VALUE / 2.0f; 48 static final float LOWER_BND = -UPPER_BND; 49 50 static final boolean DO_CLIP = MarlinProperties.isDoClip(); 51 static final boolean DO_CLIP_FILL = true; 52 53 static final boolean DO_TRACE_PATH = false; 54 55 static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag(); 56 57 /** 58 * Private constructor to prevent instantiation. 59 */ 60 private DMarlinPrismUtils() { 61 } 62 63 private static boolean nearZero(final double num) { 64 return Math.abs(num) < 2.0d * Math.ulp(num); 65 } 66 67 private static DPathConsumer2D initStroker( 68 final DRendererContext rdrCtx, 69 final BasicStroke stroke, 70 final float lineWidth, 71 final BaseTransform tx, 72 final DPathConsumer2D 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 double scale = 1.0d; 92 double width = lineWidth; 93 float[] dashes = stroke.getDashArray(); 94 double[] dashesD = null; 95 double dashphase = stroke.getDashPhase(); 96 97 // Ensure converting dashes to double precision: 98 if (dashes != null) { 99 recycleDashes = true; 100 dashLen = dashes.length; 101 dashesD = rdrCtx.dasher.copyDashArray(dashes); 102 } 103 104 if ((tx != null) && !tx.isIdentity()) { 105 final double a = tx.getMxx(); 106 final double b = tx.getMxy(); 107 final double c = tx.getMyx(); 108 final double d = tx.getMyy(); 109 110 // If the transform is a constant multiple of an orthogonal transformation 111 // then every length is just multiplied by a constant, so we just 112 // need to transform input paths to stroker and tell stroker 113 // the scaled width. This condition is satisfied if 114 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 115 // leave a bit of room for error. 116 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { 117 scale = Math.sqrt(a*a + c*c); 118 119 if (dashesD != null) { 120 for (int i = 0; i < dashLen; i++) { 121 dashesD[i] *= scale; 122 } 123 dashphase *= scale; 124 } 125 width *= scale; 126 127 // by now strokerat == null. Input paths to 128 // stroker (and maybe dasher) will have the full transform tx 129 // applied to them and nothing will happen to the output paths. 130 } else { 131 strokerTx = tx; 132 133 // by now strokerat == tx. Input paths to 134 // stroker (and maybe dasher) will have the full transform tx 135 // applied to them, then they will be normalized, and then 136 // the inverse of *only the non translation part of tx* will 137 // be applied to the normalized paths. This won't cause problems 138 // in stroker, because, suppose tx = T*A, where T is just the 139 // translation part of tx, and A is the rest. T*A has already 140 // been applied to Stroker/Dasher's input. Then Ainv will be 141 // applied. Ainv*T*A is not equal to T, but it is a translation, 142 // which means that none of stroker's assumptions about its 143 // input will be violated. After all this, A will be applied 144 // to stroker's output. 145 } 146 } 147 148 // Get renderer offsets: 149 double rdrOffX = 0.0d, rdrOffY = 0.0d; 150 151 if (rdrCtx.doClip && (tx != null)) { 152 final DMarlinRenderer renderer = (DMarlinRenderer)out; 153 rdrOffX = renderer.getOffsetX(); 154 rdrOffY = renderer.getOffsetY(); 155 } 156 157 // Prepare the pipeline: 158 DPathConsumer2D pc = out; 159 160 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 161 162 if (DO_TRACE_PATH) { 163 // trace Stroker: 164 pc = transformerPC2D.traceStroker(pc); 165 } 166 167 if (MarlinConst.USE_SIMPLIFIER) { 168 // Use simplifier after stroker before Renderer 169 // to remove collinear segments (notably due to cap square) 170 pc = rdrCtx.simplifier.init(pc); 171 } 172 173 // deltaTransformConsumer may adjust the clip rectangle: 174 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx, rdrOffX, rdrOffY); 175 176 // stroker will adjust the clip rectangle (width / miter limit): 177 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), 178 stroke.getLineJoin(), stroke.getMiterLimit(), 179 scale, rdrOffX, rdrOffY); 180 181 if (dashesD != null) { 182 pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase, recycleDashes); 183 } else if (rdrCtx.doClip && (stroke.getEndCap() != DStroker.CAP_BUTT)) { 184 if (DO_TRACE_PATH) { 185 pc = transformerPC2D.traceClosedPathDetector(pc); 186 } 187 188 // If no dash and clip is enabled: 189 // detect closedPaths (polygons) for caps 190 pc = transformerPC2D.detectClosedPath(pc); 191 } 192 pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx); 193 194 if (DO_TRACE_PATH) { 195 // trace Input: 196 pc = transformerPC2D.traceInput(pc); 197 } 198 /* 199 * Pipeline seems to be: 200 * shape.getPathIterator(tx) 201 * -> (inverseDeltaTransformConsumer) 202 * -> (Dasher) 203 * -> Stroker 204 * -> (deltaTransformConsumer) 205 * 206 * -> (CollinearSimplifier) to remove redundant segments 207 * 208 * -> pc2d = Renderer (bounding box) 209 */ 210 return pc; 211 } 212 213 private static DPathConsumer2D initRenderer( 214 final DRendererContext rdrCtx, 215 final BasicStroke stroke, 216 final BaseTransform tx, 217 final Rectangle clip, 218 final int piRule, 219 final DMarlinRenderer renderer) 220 { 221 if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) { 222 // Define the initial clip bounds: 223 final double[] clipRect = rdrCtx.clipRect; 224 225 clipRect[0] = clip.y; 226 clipRect[1] = clip.y + clip.height; 227 clipRect[2] = clip.x; 228 clipRect[3] = clip.x + clip.width; 229 230 // Enable clipping: 231 rdrCtx.doClip = true; 232 } 233 234 if (stroke != null) { 235 renderer.init(clip.x, clip.y, clip.width, clip.height, 236 MarlinConst.WIND_NON_ZERO); 237 238 return initStroker(rdrCtx, stroke, stroke.getLineWidth(), tx, renderer); 239 } else { 240 // Filler: 241 final int oprule = (piRule == PathIterator.WIND_EVEN_ODD) ? 242 MarlinConst.WIND_EVEN_ODD : MarlinConst.WIND_NON_ZERO; 243 244 renderer.init(clip.x, clip.y, clip.width, clip.height, oprule); 245 246 DPathConsumer2D pc = renderer; 247 248 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 249 250 if (DO_CLIP_FILL && rdrCtx.doClip) { 251 double rdrOffX = renderer.getOffsetX(); 252 double rdrOffY = renderer.getOffsetY(); 253 254 if (DO_TRACE_PATH) { 255 // trace Filler: 256 pc = rdrCtx.transformerPC2D.traceFiller(pc); 257 } 258 pc = rdrCtx.transformerPC2D.pathClipper(pc, rdrOffX, rdrOffY); 259 } 260 261 if (DO_TRACE_PATH) { 262 // trace Input: 263 pc = transformerPC2D.traceInput(pc); 264 } 265 return pc; 266 } 267 } 268 269 public static DMarlinRenderer setupRenderer( 270 final DRendererContext rdrCtx, 271 final Shape shape, 272 final BasicStroke stroke, 273 final BaseTransform xform, 274 final Rectangle rclip, 275 final boolean antialiasedShape) 276 { 277 // Test if transform is identity: 278 final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null; 279 280 final DMarlinRenderer r = (!FORCE_NO_AA && antialiasedShape) ? 281 rdrCtx.renderer : rdrCtx.getRendererNoAA(); 282 283 if (shape instanceof Path2D) { 284 final Path2D p2d = (Path2D)shape; 285 final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r); 286 feedConsumer(rdrCtx, p2d, tf, pc2d); 287 } else { 288 final PathIterator pi = shape.getPathIterator(tf); 289 final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r); 290 feedConsumer(rdrCtx, pi, pc2d); 291 } 292 return r; 293 } 294 295 public static void strokeTo( 296 final DRendererContext rdrCtx, 297 final Shape shape, 298 final BasicStroke stroke, 299 final float lineWidth, 300 final DPathConsumer2D out) 301 { 302 final DPathConsumer2D pc2d = initStroker(rdrCtx, stroke, lineWidth, null, out); 303 304 if (shape instanceof Path2D) { 305 feedConsumer(rdrCtx, (Path2D)shape, null, pc2d); 306 } else { 307 feedConsumer(rdrCtx, shape.getPathIterator(null), pc2d); 308 } 309 } 310 311 private static void feedConsumer(final DRendererContext rdrCtx, final PathIterator pi, 312 final DPathConsumer2D pc2d) 313 { 314 // mark context as DIRTY: 315 rdrCtx.dirty = true; 316 317 final float[] coords = rdrCtx.float6; 318 319 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 320 // - removed skip flag = !subpathStarted 321 // - removed pathClosed (ie subpathStarted not set to false) 322 boolean subpathStarted = false; 323 324 for (; !pi.isDone(); pi.next()) { 325 switch (pi.currentSegment(coords)) { 326 case PathIterator.SEG_MOVETO: 327 /* Checking SEG_MOVETO coordinates if they are out of the 328 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 329 * and Infinity values. Skipping next path segment in case of 330 * invalid data. 331 */ 332 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 333 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 334 { 335 pc2d.moveTo(coords[0], coords[1]); 336 subpathStarted = true; 337 } 338 break; 339 case PathIterator.SEG_LINETO: 340 /* Checking SEG_LINETO coordinates if they are out of the 341 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 342 * and Infinity values. Ignoring current path segment in case 343 * of invalid data. If segment is skipped its endpoint 344 * (if valid) is used to begin new subpath. 345 */ 346 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 347 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 348 { 349 if (subpathStarted) { 350 pc2d.lineTo(coords[0], coords[1]); 351 } else { 352 pc2d.moveTo(coords[0], coords[1]); 353 subpathStarted = true; 354 } 355 } 356 break; 357 case PathIterator.SEG_QUADTO: 358 // Quadratic curves take two points 359 /* Checking SEG_QUADTO coordinates if they are out of the 360 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 361 * and Infinity values. Ignoring current path segment in case 362 * of invalid endpoints's data. Equivalent to the SEG_LINETO 363 * if endpoint coordinates are valid but there are invalid data 364 * among other coordinates 365 */ 366 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND && 367 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 368 { 369 if (subpathStarted) { 370 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 371 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 372 { 373 pc2d.quadTo(coords[0], coords[1], 374 coords[2], coords[3]); 375 } else { 376 pc2d.lineTo(coords[2], coords[3]); 377 } 378 } else { 379 pc2d.moveTo(coords[2], coords[3]); 380 subpathStarted = true; 381 } 382 } 383 break; 384 case PathIterator.SEG_CUBICTO: 385 // Cubic curves take three points 386 /* Checking SEG_CUBICTO coordinates if they are out of the 387 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 388 * and Infinity values. Ignoring current path segment in case 389 * of invalid endpoints's data. Equivalent to the SEG_LINETO 390 * if endpoint coordinates are valid but there are invalid data 391 * among other coordinates 392 */ 393 if (coords[4] < UPPER_BND && coords[4] > LOWER_BND && 394 coords[5] < UPPER_BND && coords[5] > LOWER_BND) 395 { 396 if (subpathStarted) { 397 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 398 coords[1] < UPPER_BND && coords[1] > LOWER_BND && 399 coords[2] < UPPER_BND && coords[2] > LOWER_BND && 400 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 401 { 402 pc2d.curveTo(coords[0], coords[1], 403 coords[2], coords[3], 404 coords[4], coords[5]); 405 } else { 406 pc2d.lineTo(coords[4], coords[5]); 407 } 408 } else { 409 pc2d.moveTo(coords[4], coords[5]); 410 subpathStarted = true; 411 } 412 } 413 break; 414 case PathIterator.SEG_CLOSE: 415 if (subpathStarted) { 416 pc2d.closePath(); 417 // do not set subpathStarted to false 418 // in case of missing moveTo() after close() 419 } 420 break; 421 default: 422 } 423 } 424 pc2d.pathDone(); 425 426 // mark context as CLEAN: 427 rdrCtx.dirty = false; 428 } 429 430 private static void feedConsumer(final DRendererContext rdrCtx, 431 final Path2D p2d, 432 final BaseTransform xform, 433 final DPathConsumer2D pc2d) 434 { 435 // mark context as DIRTY: 436 rdrCtx.dirty = true; 437 438 final float[] coords = rdrCtx.float6; 439 440 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 441 // - removed skip flag = !subpathStarted 442 // - removed pathClosed (ie subpathStarted not set to false) 443 boolean subpathStarted = false; 444 445 final float[] pCoords = p2d.getFloatCoordsNoClone(); 446 final byte[] pTypes = p2d.getCommandsNoClone(); 447 final int nsegs = p2d.getNumCommands(); 448 449 for (int i = 0, coff = 0; i < nsegs; i++) { 450 switch (pTypes[i]) { 451 case PathIterator.SEG_MOVETO: 452 if (xform == null) { 453 coords[0] = pCoords[coff]; 454 coords[1] = pCoords[coff+1]; 455 } else { 456 xform.transform(pCoords, coff, coords, 0, 1); 457 } 458 coff += 2; 459 /* Checking SEG_MOVETO coordinates if they are out of the 460 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 461 * and Infinity values. Skipping next path segment in case of 462 * invalid data. 463 */ 464 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 465 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 466 { 467 pc2d.moveTo(coords[0], coords[1]); 468 subpathStarted = true; 469 } 470 break; 471 case PathIterator.SEG_LINETO: 472 if (xform == null) { 473 coords[0] = pCoords[coff]; 474 coords[1] = pCoords[coff+1]; 475 } else { 476 xform.transform(pCoords, coff, coords, 0, 1); 477 } 478 coff += 2; 479 /* Checking SEG_LINETO coordinates if they are out of the 480 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 481 * and Infinity values. Ignoring current path segment in case 482 * of invalid data. If segment is skipped its endpoint 483 * (if valid) is used to begin new subpath. 484 */ 485 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 486 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 487 { 488 if (subpathStarted) { 489 pc2d.lineTo(coords[0], coords[1]); 490 } else { 491 pc2d.moveTo(coords[0], coords[1]); 492 subpathStarted = true; 493 } 494 } 495 break; 496 case PathIterator.SEG_QUADTO: 497 if (xform == null) { 498 coords[0] = pCoords[coff]; 499 coords[1] = pCoords[coff+1]; 500 coords[2] = pCoords[coff+2]; 501 coords[3] = pCoords[coff+3]; 502 } else { 503 xform.transform(pCoords, coff, coords, 0, 2); 504 } 505 coff += 4; 506 // Quadratic curves take two points 507 /* Checking SEG_QUADTO coordinates if they are out of the 508 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 509 * and Infinity values. Ignoring current path segment in case 510 * of invalid endpoints's data. Equivalent to the SEG_LINETO 511 * if endpoint coordinates are valid but there are invalid data 512 * among other coordinates 513 */ 514 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND && 515 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 516 { 517 if (subpathStarted) { 518 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 519 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 520 { 521 pc2d.quadTo(coords[0], coords[1], 522 coords[2], coords[3]); 523 } else { 524 pc2d.lineTo(coords[2], coords[3]); 525 } 526 } else { 527 pc2d.moveTo(coords[2], coords[3]); 528 subpathStarted = true; 529 } 530 } 531 break; 532 case PathIterator.SEG_CUBICTO: 533 if (xform == null) { 534 coords[0] = pCoords[coff]; 535 coords[1] = pCoords[coff+1]; 536 coords[2] = pCoords[coff+2]; 537 coords[3] = pCoords[coff+3]; 538 coords[4] = pCoords[coff+4]; 539 coords[5] = pCoords[coff+5]; 540 } else { 541 xform.transform(pCoords, coff, coords, 0, 3); 542 } 543 coff += 6; 544 // Cubic curves take three points 545 /* Checking SEG_CUBICTO coordinates if they are out of the 546 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 547 * and Infinity values. Ignoring current path segment in case 548 * of invalid endpoints's data. Equivalent to the SEG_LINETO 549 * if endpoint coordinates are valid but there are invalid data 550 * among other coordinates 551 */ 552 if (coords[4] < UPPER_BND && coords[4] > LOWER_BND && 553 coords[5] < UPPER_BND && coords[5] > LOWER_BND) 554 { 555 if (subpathStarted) { 556 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 557 coords[1] < UPPER_BND && coords[1] > LOWER_BND && 558 coords[2] < UPPER_BND && coords[2] > LOWER_BND && 559 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 560 { 561 pc2d.curveTo(coords[0], coords[1], 562 coords[2], coords[3], 563 coords[4], coords[5]); 564 } else { 565 pc2d.lineTo(coords[4], coords[5]); 566 } 567 } else { 568 pc2d.moveTo(coords[4], coords[5]); 569 subpathStarted = true; 570 } 571 } 572 break; 573 case PathIterator.SEG_CLOSE: 574 if (subpathStarted) { 575 pc2d.closePath(); 576 // do not set subpathStarted to false 577 // in case of missing moveTo() after close() 578 } 579 break; 580 default: 581 } 582 } 583 pc2d.pathDone(); 584 585 // mark context as CLEAN: 586 rdrCtx.dirty = false; 587 } 588 }