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