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