1 /*
   2  * Copyright (c) 2007, 2010, 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 sun.java2d.pisces;
  27 
  28 import java.awt.Shape;
  29 import java.awt.BasicStroke;
  30 import java.awt.geom.NoninvertibleTransformException;
  31 import java.awt.geom.Path2D;
  32 import java.awt.geom.AffineTransform;
  33 import java.awt.geom.PathIterator;
  34 
  35 import sun.awt.geom.PathConsumer2D;
  36 import sun.java2d.pipe.Region;
  37 import sun.java2d.pipe.RenderingEngine;
  38 import sun.java2d.pipe.AATileGenerator;
  39 
  40 public class PiscesRenderingEngine extends RenderingEngine {
  41     private static enum NormMode {OFF, ON_NO_AA, ON_WITH_AA}
  42 
  43     /**
  44      * Create a widened path as specified by the parameters.
  45      * <p>
  46      * The specified {@code src} {@link Shape} is widened according
  47      * to the specified attribute parameters as per the
  48      * {@link BasicStroke} specification.
  49      *
  50      * @param src the source path to be widened
  51      * @param width the width of the widened path as per {@code BasicStroke}
  52      * @param caps the end cap decorations as per {@code BasicStroke}
  53      * @param join the segment join decorations as per {@code BasicStroke}
  54      * @param miterlimit the miter limit as per {@code BasicStroke}
  55      * @param dashes the dash length array as per {@code BasicStroke}
  56      * @param dashphase the initial dash phase as per {@code BasicStroke}
  57      * @return the widened path stored in a new {@code Shape} object
  58      * @since 1.7
  59      */
  60     public Shape createStrokedShape(Shape src,
  61                                     float width,
  62                                     int caps,
  63                                     int join,
  64                                     float miterlimit,
  65                                     float dashes[],
  66                                     float dashphase)
  67     {
  68         final Path2D p2d = new Path2D.Float();
  69 
  70         strokeTo(src,
  71                  null,
  72                  width,
  73                  NormMode.OFF,
  74                  caps,
  75                  join,
  76                  miterlimit,
  77                  dashes,
  78                  dashphase,
  79                  new PathConsumer2D() {
  80                      public void moveTo(float x0, float y0) {
  81                          p2d.moveTo(x0, y0);
  82                      }
  83                      public void lineTo(float x1, float y1) {
  84                          p2d.lineTo(x1, y1);
  85                      }
  86                      public void closePath() {
  87                          p2d.closePath();
  88                      }
  89                      public void pathDone() {}
  90                      public void curveTo(float x1, float y1,
  91                                          float x2, float y2,
  92                                          float x3, float y3) {
  93                          p2d.curveTo(x1, y1, x2, y2, x3, y3);
  94                      }
  95                      public void quadTo(float x1, float y1, float x2, float y2) {
  96                          p2d.quadTo(x1, y1, x2, y2);
  97                      }
  98                      public long getNativeConsumer() {
  99                          throw new InternalError("Not using a native peer");
 100                      }
 101                  });
 102         return p2d;
 103     }
 104 
 105     /**
 106      * Sends the geometry for a widened path as specified by the parameters
 107      * to the specified consumer.
 108      * <p>
 109      * The specified {@code src} {@link Shape} is widened according
 110      * to the parameters specified by the {@link BasicStroke} object.
 111      * Adjustments are made to the path as appropriate for the
 112      * {@link VALUE_STROKE_NORMALIZE} hint if the {@code normalize}
 113      * boolean parameter is true.
 114      * Adjustments are made to the path as appropriate for the
 115      * {@link VALUE_ANTIALIAS_ON} hint if the {@code antialias}
 116      * boolean parameter is true.
 117      * <p>
 118      * The geometry of the widened path is forwarded to the indicated
 119      * {@link PathConsumer2D} object as it is calculated.
 120      *
 121      * @param src the source path to be widened
 122      * @param bs the {@code BasicSroke} object specifying the
 123      *           decorations to be applied to the widened path
 124      * @param normalize indicates whether stroke normalization should
 125      *                  be applied
 126      * @param antialias indicates whether or not adjustments appropriate
 127      *                  to antialiased rendering should be applied
 128      * @param consumer the {@code PathConsumer2D} instance to forward
 129      *                 the widened geometry to
 130      * @since 1.7
 131      */
 132     public void strokeTo(Shape src,
 133                          AffineTransform at,
 134                          BasicStroke bs,
 135                          boolean thin,
 136                          boolean normalize,
 137                          boolean antialias,
 138                          final PathConsumer2D consumer)
 139     {
 140         NormMode norm = (normalize) ?
 141                 ((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA)
 142                 : NormMode.OFF;
 143         strokeTo(src, at, bs, thin, norm, antialias, consumer);
 144     }
 145 
 146     void strokeTo(Shape src,
 147                   AffineTransform at,
 148                   BasicStroke bs,
 149                   boolean thin,
 150                   NormMode normalize,
 151                   boolean antialias,
 152                   PathConsumer2D pc2d)
 153     {
 154         float lw;
 155         if (thin) {
 156             if (antialias) {
 157                 lw = userSpaceLineWidth(at, 0.5f);
 158             } else {
 159                 lw = userSpaceLineWidth(at, 1.0f);
 160             }
 161         } else {
 162             lw = bs.getLineWidth();
 163         }
 164         strokeTo(src,
 165                  at,
 166                  lw,
 167                  normalize,
 168                  bs.getEndCap(),
 169                  bs.getLineJoin(),
 170                  bs.getMiterLimit(),
 171                  bs.getDashArray(),
 172                  bs.getDashPhase(),
 173                  pc2d);
 174     }
 175 
 176     private float userSpaceLineWidth(AffineTransform at, float lw) {
 177 
 178         double widthScale;
 179 
 180         if ((at.getType() & (AffineTransform.TYPE_GENERAL_TRANSFORM |
 181                             AffineTransform.TYPE_GENERAL_SCALE)) != 0) {
 182             widthScale = Math.sqrt(at.getDeterminant());
 183         } else {
 184             /* First calculate the "maximum scale" of this transform. */
 185             double A = at.getScaleX();       // m00
 186             double C = at.getShearX();       // m01
 187             double B = at.getShearY();       // m10
 188             double D = at.getScaleY();       // m11
 189 
 190             /*
 191              * Given a 2 x 2 affine matrix [ A B ] such that
 192              *                             [ C D ]
 193              * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to
 194              * find the maximum magnitude (norm) of the vector v'
 195              * with the constraint (x^2 + y^2 = 1).
 196              * The equation to maximize is
 197              *     |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2)
 198              * or  |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2).
 199              * Since sqrt is monotonic we can maximize |v'|^2
 200              * instead and plug in the substitution y = sqrt(1 - x^2).
 201              * Trigonometric equalities can then be used to get
 202              * rid of most of the sqrt terms.
 203              */
 204 
 205             double EA = A*A + B*B;          // x^2 coefficient
 206             double EB = 2*(A*C + B*D);      // xy coefficient
 207             double EC = C*C + D*D;          // y^2 coefficient
 208 
 209             /*
 210              * There is a lot of calculus omitted here.
 211              *
 212              * Conceptually, in the interests of understanding the
 213              * terms that the calculus produced we can consider
 214              * that EA and EC end up providing the lengths along
 215              * the major axes and the hypot term ends up being an
 216              * adjustment for the additional length along the off-axis
 217              * angle of rotated or sheared ellipses as well as an
 218              * adjustment for the fact that the equation below
 219              * averages the two major axis lengths.  (Notice that
 220              * the hypot term contains a part which resolves to the
 221              * difference of these two axis lengths in the absence
 222              * of rotation.)
 223              *
 224              * In the calculus, the ratio of the EB and (EA-EC) terms
 225              * ends up being the tangent of 2*theta where theta is
 226              * the angle that the long axis of the ellipse makes
 227              * with the horizontal axis.  Thus, this equation is
 228              * calculating the length of the hypotenuse of a triangle
 229              * along that axis.
 230              */
 231 
 232             double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC));
 233             /* sqrt omitted, compare to squared limits below. */
 234             double widthsquared = ((EA + EC + hypot)/2.0);
 235 
 236             widthScale = Math.sqrt(widthsquared);
 237         }
 238 
 239         return (float) (lw / widthScale);
 240     }
 241 
 242     void strokeTo(Shape src,
 243                   AffineTransform at,
 244                   float width,
 245                   NormMode normalize,
 246                   int caps,
 247                   int join,
 248                   float miterlimit,
 249                   float dashes[],
 250                   float dashphase,
 251                   PathConsumer2D pc2d)
 252     {
 253         // We use inat and outat so that in Stroker and Dasher we can work only
 254         // with the pre-transformation coordinates. This will repeat a lot of
 255         // computations done in the path iterator, but the alternative is to
 256         // work with transformed paths and compute untransformed coordinates
 257         // as needed. This would be faster but I do not think the complexity
 258         // of working with both untransformed and transformed coordinates in
 259         // the same code is worth it.
 260         // However, if a path's width is constant after a transformation,
 261         // we can skip all this untransforming.
 262 
 263         // If normalization is off we save some transformations by not
 264         // transforming the input to pisces. Instead, we apply the
 265         // transformation after the path processing has been done.
 266         // We can't do this if normalization is on, because it isn't a good
 267         // idea to normalize before the transformation is applied.
 268         AffineTransform inat = null;
 269         AffineTransform outat = null;
 270 
 271         PathIterator pi = null;
 272 
 273         if (at != null && !at.isIdentity()) {
 274             final double a = at.getScaleX();
 275             final double b = at.getShearX();
 276             final double c = at.getShearY();
 277             final double d = at.getScaleY();
 278             final double det = a * d - c * b;
 279             if (Math.abs(det) <= 2 * Float.MIN_VALUE) {
 280                 // this rendering engine takes one dimensional curves and turns
 281                 // them into 2D shapes by giving them width.
 282                 // However, if everything is to be passed through a singular
 283                 // transformation, these 2D shapes will be squashed down to 1D
 284                 // again so, nothing can be drawn.
 285 
 286                 // Every path needs an initial moveTo and a pathDone. If these
 287                 // aren't there this causes a SIGSEV in libawt.so (at the time
 288                 // of writing of this comment (September 16, 2010)). Actually,
 289                 // I'm not sure if the moveTo is necessary to avoid the SIGSEV
 290                 // but the pathDone is definitely needed.
 291                 pc2d.moveTo(0, 0);
 292                 pc2d.pathDone();
 293                 return;
 294             }
 295 
 296             // If the transform is a constant multiple of an orthogonal transformation
 297             // then every length is just multiplied by a constant, so we just
 298             // need to transform input paths to stroker and tell stroker
 299             // the scaled width. This condition is satisfied if
 300             // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
 301             // leave a bit of room for error.
 302             if (nearZero(a*b + c*d, 2) && nearZero(a*a+c*c - (b*b+d*d), 2)) {
 303                 double scale = Math.sqrt(a*a + c*c);
 304                 if (dashes != null) {
 305                     dashes = java.util.Arrays.copyOf(dashes, dashes.length);
 306                     for (int i = 0; i < dashes.length; i++) {
 307                         dashes[i] = (float)(scale * dashes[i]);
 308                     }
 309                     dashphase = (float)(scale * dashphase);
 310                 }
 311                 width = (float)(scale * width);
 312                 pi = src.getPathIterator(at);
 313                 if (normalize != NormMode.OFF) {
 314                     pi = new NormalizingPathIterator(pi, normalize);
 315                 }
 316                 // leave inat and outat null.
 317             } else {
 318                 // We only need the inverse if normalization is on. Otherwise
 319                 // we just don't transform the input paths, do all the stroking
 320                 // and then transform out output (instead of making PathIterator
 321                 // apply the transformation, us applying the inverse, and then
 322                 // us applying the transform again to our output).
 323                 outat = at;
 324                 if (normalize != NormMode.OFF) {
 325                     try {
 326                         inat = outat.createInverse();
 327                     } catch (NoninvertibleTransformException e) {
 328                         // we made sure this can't happen
 329                         e.printStackTrace();
 330                     }
 331                     pi = src.getPathIterator(at);
 332                     pi = new NormalizingPathIterator(pi, normalize);
 333                 } else {
 334                     pi = src.getPathIterator(null);
 335                 }
 336             }
 337         } else {
 338             // either at is null or it's the identity. In either case
 339             // we don't transform the path.
 340             pi = src.getPathIterator(null);
 341             if (normalize != NormMode.OFF) {
 342                 pi = new NormalizingPathIterator(pi, normalize);
 343             }
 344         }
 345 
 346         pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, outat);
 347         pc2d = new Stroker(pc2d, width, caps, join, miterlimit);
 348         if (dashes != null) {
 349             pc2d = new Dasher(pc2d, dashes, dashphase);
 350         }
 351         pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, inat);
 352 
 353         pathTo(pi, pc2d);
 354     }
 355 
 356     private static boolean nearZero(double num, int nulps) {
 357         return Math.abs(num) < nulps * Math.ulp(num);
 358     }
 359 
 360     private static class NormalizingPathIterator implements PathIterator {
 361 
 362         private final PathIterator src;
 363 
 364         // the adjustment applied to the current position.
 365         private float curx_adjust, cury_adjust;
 366         // the adjustment applied to the last moveTo position.
 367         private float movx_adjust, movy_adjust;
 368 
 369         // constants used in normalization computations
 370         private final float lval, rval;
 371 
 372         NormalizingPathIterator(PathIterator src, NormMode mode) {
 373             this.src = src;
 374             switch (mode) {
 375             case ON_NO_AA:
 376                 // round to nearest (0.25, 0.25) pixel
 377                 lval = rval = 0.25f;
 378                 break;
 379             case ON_WITH_AA:
 380                 // round to nearest pixel center
 381                 lval = 0f;
 382                 rval = 0.5f;
 383                 break;
 384             case OFF:
 385                 throw new InternalError("A NormalizingPathIterator should " +
 386                          "not be created if no normalization is being done");
 387             default:
 388                 throw new InternalError("Unrecognized normalization mode");
 389             }
 390         }
 391 
 392         public int currentSegment(float[] coords) {
 393             int type = src.currentSegment(coords);
 394 
 395             int lastCoord;
 396             switch(type) {
 397             case PathIterator.SEG_CUBICTO:
 398                 lastCoord = 4;
 399                 break;
 400             case PathIterator.SEG_QUADTO:
 401                 lastCoord = 2;
 402                 break;
 403             case PathIterator.SEG_LINETO:
 404             case PathIterator.SEG_MOVETO:
 405                 lastCoord = 0;
 406                 break;
 407             case PathIterator.SEG_CLOSE:
 408                 // we don't want to deal with this case later. We just exit now
 409                 curx_adjust = movx_adjust;
 410                 cury_adjust = movy_adjust;
 411                 return type;
 412             default:
 413                 throw new InternalError("Unrecognized curve type");
 414             }
 415 
 416             // normalize endpoint
 417             float x_adjust = (float)Math.floor(coords[lastCoord] + lval) +
 418                          rval - coords[lastCoord];
 419             float y_adjust = (float)Math.floor(coords[lastCoord+1] + lval) +
 420                          rval - coords[lastCoord + 1];
 421 
 422             coords[lastCoord    ] += x_adjust;
 423             coords[lastCoord + 1] += y_adjust;
 424 
 425             // now that the end points are done, normalize the control points
 426             switch(type) {
 427             case PathIterator.SEG_CUBICTO:
 428                 coords[0] += curx_adjust;
 429                 coords[1] += cury_adjust;
 430                 coords[2] += x_adjust;
 431                 coords[3] += y_adjust;
 432                 break;
 433             case PathIterator.SEG_QUADTO:
 434                 coords[0] += (curx_adjust + x_adjust) / 2;
 435                 coords[1] += (cury_adjust + y_adjust) / 2;
 436                 break;
 437             case PathIterator.SEG_LINETO:
 438                 break;
 439             case PathIterator.SEG_MOVETO:
 440                 movx_adjust = x_adjust;
 441                 movy_adjust = y_adjust;
 442                 break;
 443             case PathIterator.SEG_CLOSE:
 444                 throw new InternalError("This should be handled earlier.");
 445             }
 446             curx_adjust = x_adjust;
 447             cury_adjust = y_adjust;
 448             return type;
 449         }
 450 
 451         public int currentSegment(double[] coords) {
 452             float[] tmp = new float[6];
 453             int type = this.currentSegment(tmp);
 454             for (int i = 0; i < 6; i++) {
 455                 coords[i] = (float) tmp[i];
 456             }
 457             return type;
 458         }
 459 
 460         public int getWindingRule() {
 461             return src.getWindingRule();
 462         }
 463 
 464         public boolean isDone() {
 465             return src.isDone();
 466         }
 467 
 468         public void next() {
 469             src.next();
 470         }
 471     }
 472 
 473     static void pathTo(PathIterator pi, PathConsumer2D pc2d) {
 474         RenderingEngine.feedConsumer(pi, pc2d);
 475         pc2d.pathDone();
 476     }
 477 
 478     /**
 479      * Construct an antialiased tile generator for the given shape with
 480      * the given rendering attributes and store the bounds of the tile
 481      * iteration in the bbox parameter.
 482      * The {@code at} parameter specifies a transform that should affect
 483      * both the shape and the {@code BasicStroke} attributes.
 484      * The {@code clip} parameter specifies the current clip in effect
 485      * in device coordinates and can be used to prune the data for the
 486      * operation, but the renderer is not required to perform any
 487      * clipping.
 488      * If the {@code BasicStroke} parameter is null then the shape
 489      * should be filled as is, otherwise the attributes of the
 490      * {@code BasicStroke} should be used to specify a draw operation.
 491      * The {@code thin} parameter indicates whether or not the
 492      * transformed {@code BasicStroke} represents coordinates smaller
 493      * than the minimum resolution of the antialiasing rasterizer as
 494      * specified by the {@code getMinimumAAPenWidth()} method.
 495      * <p>
 496      * Upon returning, this method will fill the {@code bbox} parameter
 497      * with 4 values indicating the bounds of the iteration of the
 498      * tile generator.
 499      * The iteration order of the tiles will be as specified by the
 500      * pseudo-code:
 501      * <pre>
 502      *     for (y = bbox[1]; y < bbox[3]; y += tileheight) {
 503      *         for (x = bbox[0]; x < bbox[2]; x += tilewidth) {
 504      *         }
 505      *     }
 506      * </pre>
 507      * If there is no output to be rendered, this method may return
 508      * null.
 509      *
 510      * @param s the shape to be rendered (fill or draw)
 511      * @param at the transform to be applied to the shape and the
 512      *           stroke attributes
 513      * @param clip the current clip in effect in device coordinates
 514      * @param bs if non-null, a {@code BasicStroke} whose attributes
 515      *           should be applied to this operation
 516      * @param thin true if the transformed stroke attributes are smaller
 517      *             than the minimum dropout pen width
 518      * @param normalize true if the {@code VALUE_STROKE_NORMALIZE}
 519      *                  {@code RenderingHint} is in effect
 520      * @param bbox returns the bounds of the iteration
 521      * @return the {@code AATileGenerator} instance to be consulted
 522      *         for tile coverages, or null if there is no output to render
 523      * @since 1.7
 524      */
 525     public AATileGenerator getAATileGenerator(Shape s,
 526                                               AffineTransform at,
 527                                               Region clip,
 528                                               BasicStroke bs,
 529                                               boolean thin,
 530                                               boolean normalize,
 531                                               int bbox[])
 532     {
 533         Renderer r;
 534         NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF;
 535         if (bs == null) {
 536             PathIterator pi;
 537             if (normalize) {
 538                 pi = new NormalizingPathIterator(s.getPathIterator(at), norm);
 539             } else {
 540                 pi = s.getPathIterator(at);
 541             }
 542             r = new Renderer(3, 3,
 543                              clip.getLoX(), clip.getLoY(),
 544                              clip.getWidth(), clip.getHeight(),
 545                              pi.getWindingRule());
 546             pathTo(pi, r);
 547         } else {
 548             r = new Renderer(3, 3,
 549                              clip.getLoX(), clip.getLoY(),
 550                              clip.getWidth(), clip.getHeight(),
 551                              PathIterator.WIND_NON_ZERO);
 552             strokeTo(s, at, bs, thin, norm, true, r);
 553         }
 554         r.endRendering();
 555         PiscesTileGenerator ptg = new PiscesTileGenerator(r, r.MAX_AA_ALPHA);
 556         ptg.getBbox(bbox);
 557         return ptg;
 558     }
 559 
 560     /**
 561      * Returns the minimum pen width that the antialiasing rasterizer
 562      * can represent without dropouts occuring.
 563      * @since 1.7
 564      */
 565     public float getMinimumAAPenSize() {
 566         return 0.5f;
 567     }
 568 
 569     static {
 570         if (PathIterator.WIND_NON_ZERO != Renderer.WIND_NON_ZERO ||
 571             PathIterator.WIND_EVEN_ODD != Renderer.WIND_EVEN_ODD ||
 572             BasicStroke.JOIN_MITER != Stroker.JOIN_MITER ||
 573             BasicStroke.JOIN_ROUND != Stroker.JOIN_ROUND ||
 574             BasicStroke.JOIN_BEVEL != Stroker.JOIN_BEVEL ||
 575             BasicStroke.CAP_BUTT != Stroker.CAP_BUTT ||
 576             BasicStroke.CAP_ROUND != Stroker.CAP_ROUND ||
 577             BasicStroke.CAP_SQUARE != Stroker.CAP_SQUARE)
 578         {
 579             throw new InternalError("mismatched renderer constants");
 580         }
 581     }
 582 }
 583