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.MarlinRenderer; 37 import com.sun.marlin.RendererContext; 38 import com.sun.marlin.TransformingPathConsumer2D; 39 import com.sun.prism.BasicStroke; 40 41 public final class MarlinPrismUtils { 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 MarlinPrismUtils() { 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 PathConsumer2D initPipeline( 59 final RendererContext rdrCtx, 60 final BasicStroke stroke, 61 final float lineWidth, 62 final BaseTransform tx, 63 final PathConsumer2D 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 float scale = 1.0f; 83 float width = 0.0f, dashphase = 0.0f; 84 float[] dashes = null; 85 86 if (stroke != null) { 87 width = lineWidth; 88 dashes = stroke.getDashArray(); 89 dashphase = stroke.getDashPhase(); 90 91 if ((tx != null) && !tx.isIdentity()) { 92 final double a = tx.getMxx(); 93 final double b = tx.getMxy(); 94 final double c = tx.getMyx(); 95 final double d = tx.getMyy(); 96 97 // If the transform is a constant multiple of an orthogonal transformation 98 // then every length is just multiplied by a constant, so we just 99 // need to transform input paths to stroker and tell stroker 100 // the scaled width. This condition is satisfied if 101 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 102 // leave a bit of room for error. 103 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { 104 scale = (float) Math.sqrt(a*a + c*c); 105 106 if (dashes != null) { 107 recycleDashes = true; 108 dashLen = dashes.length; 109 dashes = rdrCtx.dasher.copyDashArray(dashes); 110 for (int i = 0; i < dashLen; i++) { 111 dashes[i] *= scale; 112 } 113 dashphase *= scale; 114 } 115 width *= scale; 116 117 // by now strokerat == null. Input paths to 118 // stroker (and maybe dasher) will have the full transform tx 119 // applied to them and nothing will happen to the output paths. 120 } else { 121 strokerTx = tx; 122 123 // by now strokerat == tx. Input paths to 124 // stroker (and maybe dasher) will have the full transform tx 125 // applied to them, then they will be normalized, and then 126 // the inverse of *only the non translation part of tx* will 127 // be applied to the normalized paths. This won't cause problems 128 // in stroker, because, suppose tx = T*A, where T is just the 129 // translation part of tx, and A is the rest. T*A has already 130 // been applied to Stroker/Dasher's input. Then Ainv will be 131 // applied. Ainv*T*A is not equal to T, but it is a translation, 132 // which means that none of stroker's assumptions about its 133 // input will be violated. After all this, A will be applied 134 // to stroker's output. 135 } 136 } 137 } 138 139 // Prepare the pipeline: 140 PathConsumer2D pc = out; 141 142 if (MarlinConst.USE_SIMPLIFIER) { 143 // Use simplifier after stroker before Renderer 144 // to remove collinear segments (notably due to cap square) 145 pc = rdrCtx.simplifier.init(pc); 146 } 147 148 final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 149 150 if (stroke != null) { 151 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx); 152 153 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), 154 stroke.getLineJoin(), stroke.getMiterLimit()); 155 156 if (dashes != null) { 157 if (!recycleDashes) { 158 dashLen = dashes.length; 159 } 160 pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase, recycleDashes); 161 } 162 pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx); 163 } 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 PathConsumer2D initRenderer( 181 final RendererContext rdrCtx, 182 final BasicStroke stroke, 183 final BaseTransform tx, 184 final Rectangle clip, 185 final int piRule, 186 final MarlinRenderer renderer) 187 { 188 final int oprule = ((stroke == null) && (piRule == PathIterator.WIND_EVEN_ODD)) ? 189 MarlinRenderer.WIND_EVEN_ODD : MarlinRenderer.WIND_NON_ZERO; 190 191 renderer.init(clip.x, clip.y, clip.width, clip.height, oprule); 192 193 float lw = 0.0f; 194 195 if (stroke != null) { 196 lw = stroke.getLineWidth(); 197 } 198 199 return initPipeline(rdrCtx, stroke, lw, tx, renderer); 200 } 201 202 public static MarlinRenderer setupRenderer( 203 final RendererContext rdrCtx, 204 final Shape shape, 205 final BasicStroke stroke, 206 final BaseTransform xform, 207 final Rectangle rclip, 208 final boolean antialiasedShape) 209 { 210 // Test if transform is identity: 211 final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null; 212 213 final MarlinRenderer r = (!FORCE_NO_AA && antialiasedShape) ? 214 rdrCtx.renderer : rdrCtx.getRendererNoAA(); 215 216 if (shape instanceof Path2D) { 217 final Path2D p2d = (Path2D)shape; 218 final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r); 219 feedConsumer(rdrCtx, p2d, tf, pc2d); 220 } else { 221 final PathIterator pi = shape.getPathIterator(tf); 222 final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r); 223 feedConsumer(rdrCtx, pi, pc2d); 224 } 225 return r; 226 } 227 228 public static void strokeTo( 229 final RendererContext rdrCtx, 230 final Shape shape, 231 final BasicStroke stroke, 232 final float lineWidth, 233 final PathConsumer2D out) 234 { 235 final PathConsumer2D pc2d = initPipeline(rdrCtx, stroke, lineWidth, null, out); 236 237 if (shape instanceof Path2D) { 238 feedConsumer(rdrCtx, (Path2D)shape, null, pc2d); 239 } else { 240 feedConsumer(rdrCtx, shape.getPathIterator(null), pc2d); 241 } 242 } 243 244 private static void feedConsumer(final RendererContext rdrCtx, final PathIterator pi, 245 final PathConsumer2D pc2d) 246 { 247 // mark context as DIRTY: 248 rdrCtx.dirty = true; 249 250 final float[] coords = rdrCtx.float6; 251 252 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 253 // - removed skip flag = !subpathStarted 254 // - removed pathClosed (ie subpathStarted not set to false) 255 boolean subpathStarted = false; 256 257 for (; !pi.isDone(); pi.next()) { 258 switch (pi.currentSegment(coords)) { 259 case PathIterator.SEG_MOVETO: 260 /* Checking SEG_MOVETO coordinates if they are out of the 261 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 262 * and Infinity values. Skipping next path segment in case of 263 * invalid data. 264 */ 265 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 266 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 267 { 268 pc2d.moveTo(coords[0], coords[1]); 269 subpathStarted = true; 270 } 271 break; 272 case PathIterator.SEG_LINETO: 273 /* Checking SEG_LINETO coordinates if they are out of the 274 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 275 * and Infinity values. Ignoring current path segment in case 276 * of invalid data. If segment is skipped its endpoint 277 * (if valid) is used to begin new subpath. 278 */ 279 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 280 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 281 { 282 if (subpathStarted) { 283 pc2d.lineTo(coords[0], coords[1]); 284 } else { 285 pc2d.moveTo(coords[0], coords[1]); 286 subpathStarted = true; 287 } 288 } 289 break; 290 case PathIterator.SEG_QUADTO: 291 // Quadratic curves take two points 292 /* Checking SEG_QUADTO coordinates if they are out of the 293 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 294 * and Infinity values. Ignoring current path segment in case 295 * of invalid endpoints's data. Equivalent to the SEG_LINETO 296 * if endpoint coordinates are valid but there are invalid data 297 * among other coordinates 298 */ 299 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND && 300 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 301 { 302 if (subpathStarted) { 303 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 304 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 305 { 306 pc2d.quadTo(coords[0], coords[1], 307 coords[2], coords[3]); 308 } else { 309 pc2d.lineTo(coords[2], coords[3]); 310 } 311 } else { 312 pc2d.moveTo(coords[2], coords[3]); 313 subpathStarted = true; 314 } 315 } 316 break; 317 case PathIterator.SEG_CUBICTO: 318 // Cubic curves take three points 319 /* Checking SEG_CUBICTO 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[4] < UPPER_BND && coords[4] > LOWER_BND && 327 coords[5] < UPPER_BND && coords[5] > 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 coords[2] < UPPER_BND && coords[2] > LOWER_BND && 333 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 334 { 335 pc2d.curveTo(coords[0], coords[1], 336 coords[2], coords[3], 337 coords[4], coords[5]); 338 } else { 339 pc2d.lineTo(coords[4], coords[5]); 340 } 341 } else { 342 pc2d.moveTo(coords[4], coords[5]); 343 subpathStarted = true; 344 } 345 } 346 break; 347 case PathIterator.SEG_CLOSE: 348 if (subpathStarted) { 349 pc2d.closePath(); 350 // do not set subpathStarted to false 351 // in case of missing moveTo() after close() 352 } 353 break; 354 default: 355 } 356 } 357 pc2d.pathDone(); 358 359 // mark context as CLEAN: 360 rdrCtx.dirty = false; 361 } 362 363 private static void feedConsumer(final RendererContext rdrCtx, 364 final Path2D p2d, 365 final BaseTransform xform, 366 final PathConsumer2D pc2d) 367 { 368 // mark context as DIRTY: 369 rdrCtx.dirty = true; 370 371 final float[] coords = rdrCtx.float6; 372 373 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 374 // - removed skip flag = !subpathStarted 375 // - removed pathClosed (ie subpathStarted not set to false) 376 boolean subpathStarted = false; 377 378 final float[] pCoords = p2d.getFloatCoordsNoClone(); 379 final byte[] pTypes = p2d.getCommandsNoClone(); 380 final int nsegs = p2d.getNumCommands(); 381 382 for (int i = 0, coff = 0; i < nsegs; i++) { 383 switch (pTypes[i]) { 384 case PathIterator.SEG_MOVETO: 385 if (xform == null) { 386 coords[0] = pCoords[coff]; 387 coords[1] = pCoords[coff+1]; 388 } else { 389 xform.transform(pCoords, coff, coords, 0, 1); 390 } 391 coff += 2; 392 /* Checking SEG_MOVETO coordinates if they are out of the 393 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 394 * and Infinity values. Skipping next path segment in case of 395 * invalid data. 396 */ 397 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 398 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 399 { 400 pc2d.moveTo(coords[0], coords[1]); 401 subpathStarted = true; 402 } 403 break; 404 case PathIterator.SEG_LINETO: 405 if (xform == null) { 406 coords[0] = pCoords[coff]; 407 coords[1] = pCoords[coff+1]; 408 } else { 409 xform.transform(pCoords, coff, coords, 0, 1); 410 } 411 coff += 2; 412 /* Checking SEG_LINETO coordinates if they are out of the 413 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 414 * and Infinity values. Ignoring current path segment in case 415 * of invalid data. If segment is skipped its endpoint 416 * (if valid) is used to begin new subpath. 417 */ 418 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 419 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 420 { 421 if (subpathStarted) { 422 pc2d.lineTo(coords[0], coords[1]); 423 } else { 424 pc2d.moveTo(coords[0], coords[1]); 425 subpathStarted = true; 426 } 427 } 428 break; 429 case PathIterator.SEG_QUADTO: 430 if (xform == null) { 431 coords[0] = pCoords[coff]; 432 coords[1] = pCoords[coff+1]; 433 coords[2] = pCoords[coff+2]; 434 coords[3] = pCoords[coff+3]; 435 } else { 436 xform.transform(pCoords, coff, coords, 0, 2); 437 } 438 coff += 4; 439 // Quadratic curves take two points 440 /* Checking SEG_QUADTO coordinates if they are out of the 441 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 442 * and Infinity values. Ignoring current path segment in case 443 * of invalid endpoints's data. Equivalent to the SEG_LINETO 444 * if endpoint coordinates are valid but there are invalid data 445 * among other coordinates 446 */ 447 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND && 448 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 449 { 450 if (subpathStarted) { 451 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 452 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 453 { 454 pc2d.quadTo(coords[0], coords[1], 455 coords[2], coords[3]); 456 } else { 457 pc2d.lineTo(coords[2], coords[3]); 458 } 459 } else { 460 pc2d.moveTo(coords[2], coords[3]); 461 subpathStarted = true; 462 } 463 } 464 break; 465 case PathIterator.SEG_CUBICTO: 466 if (xform == null) { 467 coords[0] = pCoords[coff]; 468 coords[1] = pCoords[coff+1]; 469 coords[2] = pCoords[coff+2]; 470 coords[3] = pCoords[coff+3]; 471 coords[4] = pCoords[coff+4]; 472 coords[5] = pCoords[coff+5]; 473 } else { 474 xform.transform(pCoords, coff, coords, 0, 3); 475 } 476 coff += 6; 477 // Cubic curves take three points 478 /* Checking SEG_CUBICTO coordinates if they are out of the 479 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 480 * and Infinity values. Ignoring current path segment in case 481 * of invalid endpoints's data. Equivalent to the SEG_LINETO 482 * if endpoint coordinates are valid but there are invalid data 483 * among other coordinates 484 */ 485 if (coords[4] < UPPER_BND && coords[4] > LOWER_BND && 486 coords[5] < UPPER_BND && coords[5] > LOWER_BND) 487 { 488 if (subpathStarted) { 489 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 490 coords[1] < UPPER_BND && coords[1] > LOWER_BND && 491 coords[2] < UPPER_BND && coords[2] > LOWER_BND && 492 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 493 { 494 pc2d.curveTo(coords[0], coords[1], 495 coords[2], coords[3], 496 coords[4], coords[5]); 497 } else { 498 pc2d.lineTo(coords[4], coords[5]); 499 } 500 } else { 501 pc2d.moveTo(coords[4], coords[5]); 502 subpathStarted = true; 503 } 504 } 505 break; 506 case PathIterator.SEG_CLOSE: 507 if (subpathStarted) { 508 pc2d.closePath(); 509 // do not set subpathStarted to false 510 // in case of missing moveTo() after close() 511 } 512 break; 513 default: 514 } 515 } 516 pc2d.pathDone(); 517 518 // mark context as CLEAN: 519 rdrCtx.dirty = false; 520 } 521 }