1 /*
   2  * Copyright (c) 2008, 2011, 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.pipe;
  27 
  28 import java.awt.Shape;
  29 import java.awt.BasicStroke;
  30 import java.awt.geom.Line2D;
  31 import java.awt.geom.Rectangle2D;
  32 import java.awt.geom.AffineTransform;
  33 import sun.java2d.SunGraphics2D;
  34 import sun.awt.SunHints;
  35 
  36 /**
  37  * This class converts calls to the basic pixel rendering methods
  38  * into calls to the methods on a ParallelogramPipe.
  39  * Most calls are transformed into calls to the fill(Shape) method
  40  * by the parent PixelToShapeConverter class, but some calls are
  41  * transformed into calls to fill/drawParallelogram().
  42  */
  43 public class PixelToParallelogramConverter extends PixelToShapeConverter
  44     implements ShapeDrawPipe
  45 {
  46     ParallelogramPipe outrenderer;
  47     double minPenSize;
  48     double normPosition;
  49     double normRoundingBias;
  50     boolean adjustfill;
  51 
  52     /**
  53      * @param shapepipe pipeline to forward shape calls to
  54      * @param pgrampipe pipeline to forward parallelogram calls to
  55      *                  (and drawLine calls if possible)
  56      * @param minPenSize minimum pen size for dropout control
  57      * @param normPosition sub-pixel location to normalize endpoints
  58      *                     for STROKE_NORMALIZE cases
  59      * @param adjustFill boolean to control whethere normalization
  60      *                   constants are also applied to fill operations
  61      *                   (normally true for non-AA, false for AA)
  62      */
  63     public PixelToParallelogramConverter(ShapeDrawPipe shapepipe,
  64                                          ParallelogramPipe pgrampipe,
  65                                          double minPenSize,
  66                                          double normPosition,
  67                                          boolean adjustfill)
  68     {
  69         super(shapepipe);
  70         outrenderer = pgrampipe;
  71         this.minPenSize = minPenSize;
  72         this.normPosition = normPosition;
  73         this.normRoundingBias = 0.5 - normPosition;
  74         this.adjustfill = adjustfill;
  75     }
  76 
  77     public void drawLine(SunGraphics2D sg2d,
  78                          int x1, int y1, int x2, int y2)
  79     {
  80         if (!drawGeneralLine(sg2d, x1, y1, x2, y2)) {
  81             super.drawLine(sg2d, x1, y1, x2, y2);
  82         }
  83     }
  84 
  85     public void drawRect(SunGraphics2D sg2d,
  86                          int x, int y, int w, int h)
  87     {
  88         if (w >= 0 && h >= 0) {
  89             if (sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM) {
  90                 BasicStroke bs = ((BasicStroke) sg2d.stroke);
  91                 if (w > 0 && h > 0) {
  92                     if (bs.getLineJoin() == BasicStroke.JOIN_MITER &&
  93                         bs.getDashArray() == null)
  94                     {
  95                         double lw = bs.getLineWidth();
  96                         drawRectangle(sg2d, x, y, w, h, lw);
  97                         return;
  98                     }
  99                 } else {
 100                     // Note: This calls the integer version which
 101                     // will verify that the local drawLine optimizations
 102                     // work and call super.drawLine(), if not.
 103                     drawLine(sg2d, x, y, x+w, y+h);
 104                     return;
 105                 }
 106             }
 107             super.drawRect(sg2d, x, y, w, h);
 108         }
 109     }
 110 
 111     public void fillRect(SunGraphics2D sg2d,
 112                          int x, int y, int w, int h)
 113     {
 114         if (w > 0 && h > 0) {
 115             fillRectangle(sg2d, x, y, w, h);
 116         }
 117     }
 118 
 119     public void draw(SunGraphics2D sg2d, Shape s) {
 120         if (sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM) {
 121             BasicStroke bs = ((BasicStroke) sg2d.stroke);
 122             if (s instanceof Rectangle2D) {
 123                 if (bs.getLineJoin() == BasicStroke.JOIN_MITER &&
 124                     bs.getDashArray() == null)
 125                 {
 126                     Rectangle2D r2d = (Rectangle2D) s;
 127                     double w = r2d.getWidth();
 128                     double h = r2d.getHeight();
 129                     double x = r2d.getX();
 130                     double y = r2d.getY();
 131                     if (w >= 0 && h >= 0) {
 132                         double lw = bs.getLineWidth();
 133                         drawRectangle(sg2d, x, y, w, h, lw);
 134                     }
 135                     return;
 136                 }
 137             } else if (s instanceof Line2D) {
 138                 Line2D l2d = (Line2D) s;
 139                 if (drawGeneralLine(sg2d,
 140                                     l2d.getX1(), l2d.getY1(),
 141                                     l2d.getX2(), l2d.getY2()))
 142                 {
 143                     return;
 144                 }
 145             }
 146         }
 147 
 148         outpipe.draw(sg2d, s);
 149     }
 150 
 151     public void fill(SunGraphics2D sg2d, Shape s) {
 152         if (s instanceof Rectangle2D) {
 153             Rectangle2D r2d = (Rectangle2D) s;
 154             double w = r2d.getWidth();
 155             double h = r2d.getHeight();
 156             if (w > 0 && h > 0) {
 157                 double x = r2d.getX();
 158                 double y = r2d.getY();
 159                 fillRectangle(sg2d, x, y, w, h);
 160             }
 161             return;
 162         }
 163 
 164         outpipe.fill(sg2d, s);
 165     }
 166 
 167     static double len(double x, double y) {
 168         return ((x == 0) ? Math.abs(y)
 169                 : ((y == 0) ? Math.abs(x)
 170                    : Math.sqrt(x * x + y * y)));
 171     }
 172 
 173     double normalize(double v) {
 174         return Math.floor(v + normRoundingBias) + normPosition;
 175     }
 176 
 177     public boolean drawGeneralLine(SunGraphics2D sg2d,
 178                                    double ux1, double uy1,
 179                                    double ux2, double uy2)
 180     {
 181         if (sg2d.strokeState == SunGraphics2D.STROKE_CUSTOM ||
 182             sg2d.strokeState == SunGraphics2D.STROKE_THINDASHED)
 183         {
 184             return false;
 185         }
 186         BasicStroke bs = (BasicStroke) sg2d.stroke;
 187         int cap = bs.getEndCap();
 188         if (cap == BasicStroke.CAP_ROUND || bs.getDashArray() != null) {
 189             // TODO: we could construct the GeneralPath directly
 190             // for CAP_ROUND and save a lot of processing in that case...
 191             // And again, we would need to deal with dropout control...
 192             return false;
 193         }
 194         double lw = bs.getLineWidth();
 195         // Save the original dx, dy in case we need it to transform
 196         // the linewidth as a perpendicular vector below
 197         double dx = ux2 - ux1;
 198         double dy = uy2 - uy1;
 199         double x1, y1, x2, y2;
 200         switch (sg2d.transformState) {
 201         case SunGraphics2D.TRANSFORM_GENERIC:
 202         case SunGraphics2D.TRANSFORM_TRANSLATESCALE:
 203             {
 204                 double coords[] = {ux1, uy1, ux2, uy2};
 205                 sg2d.transform.transform(coords, 0, coords, 0, 2);
 206                 x1 = coords[0];
 207                 y1 = coords[1];
 208                 x2 = coords[2];
 209                 y2 = coords[3];
 210             }
 211             break;
 212         case SunGraphics2D.TRANSFORM_ANY_TRANSLATE:
 213         case SunGraphics2D.TRANSFORM_INT_TRANSLATE:
 214             {
 215                 double tx = sg2d.transform.getTranslateX();
 216                 double ty = sg2d.transform.getTranslateY();
 217                 x1 = ux1 + tx;
 218                 y1 = uy1 + ty;
 219                 x2 = ux2 + tx;
 220                 y2 = uy2 + ty;
 221             }
 222             break;
 223         case SunGraphics2D.TRANSFORM_ISIDENT:
 224             x1 = ux1;
 225             y1 = uy1;
 226             x2 = ux2;
 227             y2 = uy2;
 228             break;
 229         default:
 230             throw new InternalError("unknown TRANSFORM state...");
 231         }
 232         if (sg2d.strokeHint != SunHints.INTVAL_STROKE_PURE) {
 233             if (sg2d.strokeState == SunGraphics2D.STROKE_THIN &&
 234                 outrenderer instanceof PixelDrawPipe)
 235             {
 236                 // PixelDrawPipes will add sg2d.transXY so we need to factor
 237                 // that out...
 238                 int ix1 = (int) Math.floor(x1 - sg2d.transX);
 239                 int iy1 = (int) Math.floor(y1 - sg2d.transY);
 240                 int ix2 = (int) Math.floor(x2 - sg2d.transX);
 241                 int iy2 = (int) Math.floor(y2 - sg2d.transY);
 242                 ((PixelDrawPipe)outrenderer).drawLine(sg2d, ix1, iy1, ix2, iy2);
 243                 return true;
 244             }
 245             x1 = normalize(x1);
 246             y1 = normalize(y1);
 247             x2 = normalize(x2);
 248             y2 = normalize(y2);
 249         }
 250         if (sg2d.transformState >= SunGraphics2D.TRANSFORM_TRANSLATESCALE) {
 251             // Transform the linewidth...
 252             // calculate the scaling factor for a unit vector
 253             // perpendicular to the original user space line.
 254             double len = len(dx, dy);
 255             if (len == 0) {
 256                 dx = len = 1;
 257                 // dy = 0; already
 258             }
 259             // delta transform the transposed (90 degree rotated) unit vector
 260             double unitvector[] = {dy/len, -dx/len};
 261             sg2d.transform.deltaTransform(unitvector, 0, unitvector, 0, 1);
 262             lw *= len(unitvector[0], unitvector[1]);
 263         }
 264         lw = Math.max(lw, minPenSize);
 265         dx = x2 - x1;
 266         dy = y2 - y1;
 267         double len = len(dx, dy);
 268         double udx, udy;
 269         if (len == 0) {
 270             if (cap == BasicStroke.CAP_BUTT) {
 271                 return true;
 272             }
 273             udx = lw;
 274             udy = 0;
 275         } else {
 276             udx = lw * dx / len;
 277             udy = lw * dy / len;
 278         }
 279         double px = x1 + udy / 2.0;
 280         double py = y1 - udx / 2.0;
 281         if (cap == BasicStroke.CAP_SQUARE) {
 282             px -= udx / 2.0;
 283             py -= udy / 2.0;
 284             dx += udx;
 285             dy += udy;
 286         }
 287         outrenderer.fillParallelogram(sg2d, ux1, uy1, ux2, uy2,
 288                                       px, py, -udy, udx, dx, dy);
 289         return true;
 290     }
 291 
 292     public void fillRectangle(SunGraphics2D sg2d,
 293                               double rx, double ry,
 294                               double rw, double rh)
 295     {
 296         double px, py;
 297         double dx1, dy1, dx2, dy2;
 298         AffineTransform txform = sg2d.transform;
 299         dx1 = txform.getScaleX();
 300         dy1 = txform.getShearY();
 301         dx2 = txform.getShearX();
 302         dy2 = txform.getScaleY();
 303         px = rx * dx1 + ry * dx2 + txform.getTranslateX();
 304         py = rx * dy1 + ry * dy2 + txform.getTranslateY();
 305         dx1 *= rw;
 306         dy1 *= rw;
 307         dx2 *= rh;
 308         dy2 *= rh;
 309         if (adjustfill &&
 310             sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM &&
 311             sg2d.strokeHint != SunHints.INTVAL_STROKE_PURE)
 312         {
 313             double newx = normalize(px);
 314             double newy = normalize(py);
 315             dx1 = normalize(px + dx1) - newx;
 316             dy1 = normalize(py + dy1) - newy;
 317             dx2 = normalize(px + dx2) - newx;
 318             dy2 = normalize(py + dy2) - newy;
 319             px = newx;
 320             py = newy;
 321         }
 322         outrenderer.fillParallelogram(sg2d, rx, ry, rx+rw, ry+rh,
 323                                       px, py, dx1, dy1, dx2, dy2);
 324     }
 325 
 326     public void drawRectangle(SunGraphics2D sg2d,
 327                               double rx, double ry,
 328                               double rw, double rh,
 329                               double lw)
 330     {
 331         double px, py;
 332         double dx1, dy1, dx2, dy2;
 333         double lw1, lw2;
 334         AffineTransform txform = sg2d.transform;
 335         dx1 = txform.getScaleX();
 336         dy1 = txform.getShearY();
 337         dx2 = txform.getShearX();
 338         dy2 = txform.getScaleY();
 339         px = rx * dx1 + ry * dx2 + txform.getTranslateX();
 340         py = rx * dy1 + ry * dy2 + txform.getTranslateY();
 341         // lw along dx1,dy1 scale by transformed length of dx2,dy2 vectors
 342         // and vice versa
 343         lw1 = len(dx1, dy1) * lw;
 344         lw2 = len(dx2, dy2) * lw;
 345         dx1 *= rw;
 346         dy1 *= rw;
 347         dx2 *= rh;
 348         dy2 *= rh;
 349         if (sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM &&
 350             sg2d.strokeHint != SunHints.INTVAL_STROKE_PURE)
 351         {
 352             double newx = normalize(px);
 353             double newy = normalize(py);
 354             dx1 = normalize(px + dx1) - newx;
 355             dy1 = normalize(py + dy1) - newy;
 356             dx2 = normalize(px + dx2) - newx;
 357             dy2 = normalize(py + dy2) - newy;
 358             px = newx;
 359             py = newy;
 360         }
 361         lw1 = Math.max(lw1, minPenSize);
 362         lw2 = Math.max(lw2, minPenSize);
 363         double len1 = len(dx1, dy1);
 364         double len2 = len(dx2, dy2);
 365         if (lw1 >= len1 || lw2 >= len2) {
 366             // The line widths are large enough to consume the
 367             // entire hole in the middle of the parallelogram
 368             // so we can just fill the outer parallelogram.
 369             fillOuterParallelogram(sg2d,
 370                                    rx, ry, rx+rw, ry+rh,
 371                                    px, py, dx1, dy1, dx2, dy2,
 372                                    len1, len2, lw1, lw2);
 373         } else {
 374             outrenderer.drawParallelogram(sg2d,
 375                                           rx, ry, rx+rw, ry+rh,
 376                                           px, py, dx1, dy1, dx2, dy2,
 377                                           lw1 / len1, lw2 / len2);
 378         }
 379     }
 380 
 381     /**
 382      * This utility function handles the case where a drawRectangle
 383      * operation discovered that the interior hole in the rectangle
 384      * or parallelogram has been completely filled in by the stroke
 385      * width.  It calculates the outer parallelogram of the stroke
 386      * and issues a single fillParallelogram request to fill it.
 387      */
 388     public void fillOuterParallelogram(SunGraphics2D sg2d,
 389                                        double ux1, double uy1,
 390                                        double ux2, double uy2,
 391                                        double px, double py,
 392                                        double dx1, double dy1,
 393                                        double dx2, double dy2,
 394                                        double len1, double len2,
 395                                        double lw1, double lw2)
 396     {
 397         double udx1 = dx1 / len1;
 398         double udy1 = dy1 / len1;
 399         double udx2 = dx2 / len2;
 400         double udy2 = dy2 / len2;
 401         if (len1 == 0) {
 402             // len1 is 0, replace udxy1 with perpendicular of udxy2
 403             if (len2 == 0) {
 404                 // both are 0, use a unit Y vector for udxy2
 405                 udx2 = 0;
 406                 udy2 = 1;
 407             }
 408             udx1 = udy2;
 409             udy1 = -udx2;
 410         } else if (len2 == 0) {
 411             // len2 is 0, replace udxy2 with perpendicular of udxy1
 412             udx2 = udy1;
 413             udy2 = -udx1;
 414         }
 415         udx1 *= lw1;
 416         udy1 *= lw1;
 417         udx2 *= lw2;
 418         udy2 *= lw2;
 419         px -= (udx1 + udx2) / 2;
 420         py -= (udy1 + udy2) / 2;
 421         dx1 += udx1;
 422         dy1 += udy1;
 423         dx2 += udx2;
 424         dy2 += udy2;
 425 
 426         outrenderer.fillParallelogram(sg2d, ux1, uy1, ux2, uy2,
 427                                       px, py, dx1, dy1, dx2, dy2);
 428     }
 429 }