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