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 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 263 * and Infinity values. Skipping next path segment in case of 264 * invalid data. 265 */ 266 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 267 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 268 { 269 pc2d.moveTo(coords[0], coords[1]); 270 subpathStarted = true; 271 } 272 break; 273 case PathIterator.SEG_LINETO: 274 /* Checking SEG_LINETO coordinates if they are out of the 275 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 276 * and Infinity values. Ignoring current path segment in case 277 * of invalid data. If segment is skipped its endpoint 278 * (if valid) is used to begin new subpath. 279 */ 280 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 281 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 282 { 283 if (subpathStarted) { 284 pc2d.lineTo(coords[0], coords[1]); 285 } else { 286 pc2d.moveTo(coords[0], coords[1]); 287 subpathStarted = true; 288 } 289 } 290 break; 291 case PathIterator.SEG_QUADTO: 292 // Quadratic curves take two points 293 /* Checking SEG_QUADTO coordinates if they are out of the 294 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 295 * and Infinity values. Ignoring current path segment in case 296 * of invalid endpoints's data. Equivalent to the SEG_LINETO 297 * if endpoint coordinates are valid but there are invalid data 298 * among other coordinates 299 */ 300 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND && 301 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 302 { 303 if (subpathStarted) { 304 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 305 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 306 { 307 pc2d.quadTo(coords[0], coords[1], 308 coords[2], coords[3]); 309 } else { 310 pc2d.lineTo(coords[2], coords[3]); 311 } 312 } else { 313 pc2d.moveTo(coords[2], coords[3]); 314 subpathStarted = true; 315 } 316 } 317 break; 318 case PathIterator.SEG_CUBICTO: 319 // Cubic curves take three points 320 /* Checking SEG_CUBICTO coordinates if they are out of the 321 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 322 * and Infinity values. Ignoring current path segment in case 323 * of invalid endpoints's data. Equivalent to the SEG_LINETO 324 * if endpoint coordinates are valid but there are invalid data 325 * among other coordinates 326 */ 327 if (coords[4] < UPPER_BND && coords[4] > LOWER_BND && 328 coords[5] < UPPER_BND && coords[5] > LOWER_BND) 329 { 330 if (subpathStarted) { 331 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 332 coords[1] < UPPER_BND && coords[1] > LOWER_BND && 333 coords[2] < UPPER_BND && coords[2] > LOWER_BND && 334 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 335 { 336 pc2d.curveTo(coords[0], coords[1], 337 coords[2], coords[3], 338 coords[4], coords[5]); 339 } else { 340 pc2d.lineTo(coords[4], coords[5]); 341 } 342 } else { 343 pc2d.moveTo(coords[4], coords[5]); 344 subpathStarted = true; 345 } 346 } 347 break; 348 case PathIterator.SEG_CLOSE: 349 if (subpathStarted) { 350 pc2d.closePath(); 351 // do not set subpathStarted to false 352 // in case of missing moveTo() after close() 353 } 354 break; 355 default: 356 } 357 } 358 pc2d.pathDone(); 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 { 401 pc2d.moveTo(coords[0], coords[1]); 402 subpathStarted = true; 403 } 404 break; 405 case PathIterator.SEG_LINETO: 406 if (xform == null) { 407 coords[0] = pCoords[coff]; 408 coords[1] = pCoords[coff+1]; 409 } else { 410 xform.transform(pCoords, coff, coords, 0, 1); 411 } 412 coff += 2; 413 /* Checking SEG_LINETO coordinates if they are out of the 414 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 415 * and Infinity values. Ignoring current path segment in case 416 * of invalid data. If segment is skipped its endpoint 417 * (if valid) is used to begin new subpath. 418 */ 419 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 420 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 421 { 422 if (subpathStarted) { 423 pc2d.lineTo(coords[0], coords[1]); 424 } else { 425 pc2d.moveTo(coords[0], coords[1]); 426 subpathStarted = true; 427 } 428 } 429 break; 430 case PathIterator.SEG_QUADTO: 431 if (xform == null) { 432 coords[0] = pCoords[coff]; 433 coords[1] = pCoords[coff+1]; 434 coords[2] = pCoords[coff+2]; 435 coords[3] = pCoords[coff+3]; 436 } else { 437 xform.transform(pCoords, coff, coords, 0, 2); 438 } 439 coff += 4; 440 // Quadratic curves take two points 441 /* Checking SEG_QUADTO coordinates if they are out of the 442 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 443 * and Infinity values. Ignoring current path segment in case 444 * of invalid endpoints's data. Equivalent to the SEG_LINETO 445 * if endpoint coordinates are valid but there are invalid data 446 * among other coordinates 447 */ 448 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND && 449 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 450 { 451 if (subpathStarted) { 452 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 453 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 454 { 455 pc2d.quadTo(coords[0], coords[1], 456 coords[2], coords[3]); 457 } else { 458 pc2d.lineTo(coords[2], coords[3]); 459 } 460 } else { 461 pc2d.moveTo(coords[2], coords[3]); 462 subpathStarted = true; 463 } 464 } 465 break; 466 case PathIterator.SEG_CUBICTO: 467 if (xform == null) { 468 coords[0] = pCoords[coff]; 469 coords[1] = pCoords[coff+1]; 470 coords[2] = pCoords[coff+2]; 471 coords[3] = pCoords[coff+3]; 472 coords[4] = pCoords[coff+4]; 473 coords[5] = pCoords[coff+5]; 474 } else { 475 xform.transform(pCoords, coff, coords, 0, 3); 476 } 477 coff += 6; 478 // Cubic curves take three points 479 /* Checking SEG_CUBICTO 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 endpoints's data. Equivalent to the SEG_LINETO 483 * if endpoint coordinates are valid but there are invalid data 484 * among other coordinates 485 */ 486 if (coords[4] < UPPER_BND && coords[4] > LOWER_BND && 487 coords[5] < UPPER_BND && coords[5] > LOWER_BND) 488 { 489 if (subpathStarted) { 490 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 491 coords[1] < UPPER_BND && coords[1] > LOWER_BND && 492 coords[2] < UPPER_BND && coords[2] > LOWER_BND && 493 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 494 { 495 pc2d.curveTo(coords[0], coords[1], 496 coords[2], coords[3], 497 coords[4], coords[5]); 498 } else { 499 pc2d.lineTo(coords[4], coords[5]); 500 } 501 } else { 502 pc2d.moveTo(coords[4], coords[5]); 503 subpathStarted = true; 504 } 505 } 506 break; 507 case PathIterator.SEG_CLOSE: 508 if (subpathStarted) { 509 pc2d.closePath(); 510 // do not set subpathStarted to false 511 // in case of missing moveTo() after close() 512 } 513 break; 514 default: 515 } 516 } 517 pc2d.pathDone(); 518 519 // mark context as CLEAN: 520 rdrCtx.dirty = false; 521 } 522 }