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