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 = 0f, dashphase = 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.0 * 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 }