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 }