1 /* 2 * Copyright (c) 2011, 2016, 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 PathConsumer2D initRenderer( 55 final RendererContext rdrCtx, 56 final BasicStroke stroke, 57 final BaseTransform tx, 58 final Rectangle clip, 59 final int pirule, 60 final MarlinRenderer renderer) 61 { 62 final int oprule = (stroke == null && pirule == PathIterator.WIND_EVEN_ODD) ? 63 MarlinRenderer.WIND_EVEN_ODD : MarlinRenderer.WIND_NON_ZERO; 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 83 float width = 0.0f, dashphase = 0.0f; 84 float[] dashes = null; 85 86 if (stroke != null) { 87 width = stroke.getLineWidth(); 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 final float 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 PathConsumer2D pc = renderer.init(clip.x, clip.y, clip.width, clip.height, oprule); 140 141 if (MarlinConst.USE_SIMPLIFIER) { 142 // Use simplifier after stroker before Renderer 143 // to remove collinear segments (notably due to cap square) 144 pc = rdrCtx.simplifier.init(pc); 145 } 146 147 final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 148 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx); 149 150 if (stroke != null) { 151 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), 152 stroke.getLineJoin(), stroke.getMiterLimit()); 153 154 if (dashes != null) { 155 if (!recycleDashes) { 156 dashLen = dashes.length; 157 } 158 pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase, recycleDashes); 159 } 160 } 161 162 pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx); 163 164 /* 165 * Pipeline seems to be: 166 * shape.getPathIterator(tx) 167 * -> (inverseDeltaTransformConsumer) 168 * -> (Dasher) 169 * -> Stroker 170 * -> (deltaTransformConsumer) 171 * 172 * -> (CollinearSimplifier) to remove redundant segments 173 * 174 * -> pc2d = Renderer (bounding box) 175 */ 176 return pc; 177 } 178 179 private static boolean nearZero(final double num) { 180 return Math.abs(num) < 2.0d * Math.ulp(num); 181 } 182 183 public static MarlinRenderer setupRenderer( 184 final RendererContext rdrCtx, 185 final Shape shape, 186 final BasicStroke stroke, 187 final BaseTransform xform, 188 final Rectangle rclip, 189 final boolean antialiasedShape) 190 { 191 // Test if transform is identity: 192 final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null; 193 194 final PathIterator pi = shape.getPathIterator(tf); 195 196 final MarlinRenderer r = (!FORCE_NO_AA && antialiasedShape) ? 197 rdrCtx.renderer : rdrCtx.getRendererNoAA(); 198 199 final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r); 200 201 feedConsumer(rdrCtx, pi, pc2d); 202 203 return r; 204 } 205 206 public static MarlinRenderer setupRenderer( 207 final RendererContext rdrCtx, 208 final Path2D p2d, 209 final BasicStroke stroke, 210 final BaseTransform xform, 211 final Rectangle rclip, 212 final boolean antialiasedShape) 213 { 214 // Test if transform is identity: 215 final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null; 216 217 final MarlinRenderer r = (!FORCE_NO_AA && antialiasedShape) ? 218 rdrCtx.renderer : rdrCtx.getRendererNoAA(); 219 220 final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r); 221 222 feedConsumer(rdrCtx, p2d, tf, pc2d); 223 224 return r; 225 } 226 227 private static void feedConsumer(final RendererContext rdrCtx, final PathIterator pi, 228 final PathConsumer2D pc2d) 229 { 230 // mark context as DIRTY: 231 rdrCtx.dirty = true; 232 233 final float[] coords = rdrCtx.float6; 234 235 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 236 // - removed skip flag = !subpathStarted 237 // - removed pathClosed (ie subpathStarted not set to false) 238 boolean subpathStarted = false; 239 240 for (; !pi.isDone(); pi.next()) { 241 switch (pi.currentSegment(coords)) { 242 case PathIterator.SEG_MOVETO: 243 /* Checking SEG_MOVETO coordinates if they are out of the 244 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 245 * and Infinity values. Skipping next path segment in case of 246 * invalid data. 247 */ 248 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 249 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 250 { 251 pc2d.moveTo(coords[0], coords[1]); 252 subpathStarted = true; 253 } 254 break; 255 case PathIterator.SEG_LINETO: 256 /* Checking SEG_LINETO coordinates if they are out of the 257 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 258 * and Infinity values. Ignoring current path segment in case 259 * of invalid data. If segment is skipped its endpoint 260 * (if valid) is used to begin new subpath. 261 */ 262 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 263 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 264 { 265 if (subpathStarted) { 266 pc2d.lineTo(coords[0], coords[1]); 267 } else { 268 pc2d.moveTo(coords[0], coords[1]); 269 subpathStarted = true; 270 } 271 } 272 break; 273 case PathIterator.SEG_QUADTO: 274 // Quadratic curves take two points 275 /* Checking SEG_QUADTO coordinates if they are out of the 276 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 277 * and Infinity values. Ignoring current path segment in case 278 * of invalid endpoints's data. Equivalent to the SEG_LINETO 279 * if endpoint coordinates are valid but there are invalid data 280 * among other coordinates 281 */ 282 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND && 283 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 284 { 285 if (subpathStarted) { 286 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 287 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 288 { 289 pc2d.quadTo(coords[0], coords[1], 290 coords[2], coords[3]); 291 } else { 292 pc2d.lineTo(coords[2], coords[3]); 293 } 294 } else { 295 pc2d.moveTo(coords[2], coords[3]); 296 subpathStarted = true; 297 } 298 } 299 break; 300 case PathIterator.SEG_CUBICTO: 301 // Cubic curves take three points 302 /* Checking SEG_CUBICTO coordinates if they are out of the 303 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 304 * and Infinity values. Ignoring current path segment in case 305 * of invalid endpoints's data. Equivalent to the SEG_LINETO 306 * if endpoint coordinates are valid but there are invalid data 307 * among other coordinates 308 */ 309 if (coords[4] < UPPER_BND && coords[4] > LOWER_BND && 310 coords[5] < UPPER_BND && coords[5] > LOWER_BND) 311 { 312 if (subpathStarted) { 313 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 314 coords[1] < UPPER_BND && coords[1] > LOWER_BND && 315 coords[2] < UPPER_BND && coords[2] > LOWER_BND && 316 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 317 { 318 pc2d.curveTo(coords[0], coords[1], 319 coords[2], coords[3], 320 coords[4], coords[5]); 321 } else { 322 pc2d.lineTo(coords[4], coords[5]); 323 } 324 } else { 325 pc2d.moveTo(coords[4], coords[5]); 326 subpathStarted = true; 327 } 328 } 329 break; 330 case PathIterator.SEG_CLOSE: 331 if (subpathStarted) { 332 pc2d.closePath(); 333 // do not set subpathStarted to false 334 // in case of missing moveTo() after close() 335 } 336 break; 337 default: 338 } 339 } 340 pc2d.pathDone(); 341 342 // mark context as CLEAN: 343 rdrCtx.dirty = false; 344 } 345 346 private static void feedConsumer(final RendererContext rdrCtx, 347 final Path2D p2d, 348 final BaseTransform xform, 349 final PathConsumer2D pc2d) 350 { 351 // mark context as DIRTY: 352 rdrCtx.dirty = true; 353 354 final float[] coords = rdrCtx.float6; 355 356 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 357 // - removed skip flag = !subpathStarted 358 // - removed pathClosed (ie subpathStarted not set to false) 359 boolean subpathStarted = false; 360 361 final float pCoords[] = p2d.getFloatCoordsNoClone(); 362 final byte pTypes[] = p2d.getCommandsNoClone(); 363 final int nsegs = p2d.getNumCommands(); 364 365 for (int i = 0, coff = 0; i < nsegs; i++) { 366 switch (pTypes[i]) { 367 case PathIterator.SEG_MOVETO: 368 if (xform == null) { 369 coords[0] = pCoords[coff]; 370 coords[1] = pCoords[coff+1]; 371 } else { 372 xform.transform(pCoords, coff, coords, 0, 1); 373 } 374 coff += 2; 375 /* Checking SEG_MOVETO coordinates if they are out of the 376 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 377 * and Infinity values. Skipping next path segment in case of 378 * invalid data. 379 */ 380 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 381 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 382 { 383 pc2d.moveTo(coords[0], coords[1]); 384 subpathStarted = true; 385 } 386 break; 387 case PathIterator.SEG_LINETO: 388 if (xform == null) { 389 coords[0] = pCoords[coff]; 390 coords[1] = pCoords[coff+1]; 391 } else { 392 xform.transform(pCoords, coff, coords, 0, 1); 393 } 394 coff += 2; 395 /* Checking SEG_LINETO coordinates if they are out of the 396 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 397 * and Infinity values. Ignoring current path segment in case 398 * of invalid data. If segment is skipped its endpoint 399 * (if valid) is used to begin new subpath. 400 */ 401 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 402 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 403 { 404 if (subpathStarted) { 405 pc2d.lineTo(coords[0], coords[1]); 406 } else { 407 pc2d.moveTo(coords[0], coords[1]); 408 subpathStarted = true; 409 } 410 } 411 break; 412 case PathIterator.SEG_QUADTO: 413 if (xform == null) { 414 coords[0] = pCoords[coff]; 415 coords[1] = pCoords[coff+1]; 416 coords[2] = pCoords[coff+2]; 417 coords[3] = pCoords[coff+3]; 418 } else { 419 xform.transform(pCoords, coff, coords, 0, 2); 420 } 421 coff += 4; 422 // Quadratic curves take two points 423 /* Checking SEG_QUADTO coordinates if they are out of the 424 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 425 * and Infinity values. Ignoring current path segment in case 426 * of invalid endpoints's data. Equivalent to the SEG_LINETO 427 * if endpoint coordinates are valid but there are invalid data 428 * among other coordinates 429 */ 430 if (coords[2] < UPPER_BND && coords[2] > LOWER_BND && 431 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 432 { 433 if (subpathStarted) { 434 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 435 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 436 { 437 pc2d.quadTo(coords[0], coords[1], 438 coords[2], coords[3]); 439 } else { 440 pc2d.lineTo(coords[2], coords[3]); 441 } 442 } else { 443 pc2d.moveTo(coords[2], coords[3]); 444 subpathStarted = true; 445 } 446 } 447 break; 448 case PathIterator.SEG_CUBICTO: 449 if (xform == null) { 450 coords[0] = pCoords[coff]; 451 coords[1] = pCoords[coff+1]; 452 coords[2] = pCoords[coff+2]; 453 coords[3] = pCoords[coff+3]; 454 coords[4] = pCoords[coff+4]; 455 coords[5] = pCoords[coff+5]; 456 } else { 457 xform.transform(pCoords, coff, coords, 0, 3); 458 } 459 coff += 6; 460 // Cubic curves take three points 461 /* Checking SEG_CUBICTO coordinates if they are out of the 462 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 463 * and Infinity values. Ignoring current path segment in case 464 * of invalid endpoints's data. Equivalent to the SEG_LINETO 465 * if endpoint coordinates are valid but there are invalid data 466 * among other coordinates 467 */ 468 if (coords[4] < UPPER_BND && coords[4] > LOWER_BND && 469 coords[5] < UPPER_BND && coords[5] > LOWER_BND) 470 { 471 if (subpathStarted) { 472 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 473 coords[1] < UPPER_BND && coords[1] > LOWER_BND && 474 coords[2] < UPPER_BND && coords[2] > LOWER_BND && 475 coords[3] < UPPER_BND && coords[3] > LOWER_BND) 476 { 477 pc2d.curveTo(coords[0], coords[1], 478 coords[2], coords[3], 479 coords[4], coords[5]); 480 } else { 481 pc2d.lineTo(coords[4], coords[5]); 482 } 483 } else { 484 pc2d.moveTo(coords[4], coords[5]); 485 subpathStarted = true; 486 } 487 } 488 break; 489 case PathIterator.SEG_CLOSE: 490 if (subpathStarted) { 491 pc2d.closePath(); 492 // do not set subpathStarted to false 493 // in case of missing moveTo() after close() 494 } 495 break; 496 default: 497 } 498 } 499 pc2d.pathDone(); 500 501 // mark context as CLEAN: 502 rdrCtx.dirty = false; 503 } 504 }