1 /*
   2  * Copyright (c) 2011, 2012, 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;
  27 
  28 import java.awt.*;
  29 import java.awt.font.*;
  30 import java.awt.geom.*;
  31 import java.awt.image.*;
  32 
  33 import sun.awt.image.*;
  34 import sun.java2d.loops.*;
  35 import sun.java2d.pipe.*;
  36 
  37 public class CompositeCRenderer extends CRenderer implements PixelDrawPipe, PixelFillPipe, ShapeDrawPipe, DrawImagePipe, TextPipe {
  38     final static int fPadding = 4;
  39     final static int fPaddingHalf = fPadding / 2;
  40 
  41     private static AffineTransform sIdentityMatrix = new AffineTransform();
  42 
  43     AffineTransform ShapeTM = new AffineTransform();
  44     Rectangle2D ShapeBounds = new Rectangle2D.Float();
  45 
  46     Line2D line = new Line2D.Float();
  47     Rectangle2D rectangle = new Rectangle2D.Float();
  48     RoundRectangle2D roundrectangle = new RoundRectangle2D.Float();
  49     Ellipse2D ellipse = new Ellipse2D.Float();
  50     Arc2D arc = new Arc2D.Float();
  51 
  52     public synchronized void drawLine(SunGraphics2D sg2d, int x1, int y1, int x2, int y2) {
  53         // create shape corresponding to this primitive
  54         line.setLine(x1, y1, x2, y2);
  55 
  56         draw(sg2d, line);
  57     }
  58 
  59     public synchronized void drawRect(SunGraphics2D sg2d, int x, int y, int width, int height) {
  60         // create shape corresponding to this primitive
  61         rectangle.setRect(x, y, width, height);
  62 
  63         draw(sg2d, rectangle);
  64     }
  65 
  66     public synchronized void drawRoundRect(SunGraphics2D sg2d, int x, int y, int width, int height, int arcWidth, int arcHeight) {
  67         // create shape corresponding to this primitive
  68         roundrectangle.setRoundRect(x, y, width, height, arcWidth, arcHeight);
  69 
  70         draw(sg2d, roundrectangle);
  71     }
  72 
  73     public synchronized void drawOval(SunGraphics2D sg2d, int x, int y, int width, int height) {
  74         // create shape corresponding to this primitive
  75         ellipse.setFrame(x, y, width, height);
  76 
  77         draw(sg2d, ellipse);
  78     }
  79 
  80     public synchronized void drawArc(SunGraphics2D sg2d, int x, int y, int width, int height, int startAngle, int arcAngle) {
  81         // create shape corresponding to this primitive
  82         arc.setArc(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN);
  83 
  84         draw(sg2d, arc);
  85     }
  86 
  87     public synchronized void drawPolyline(SunGraphics2D sg2d, int xpoints[], int ypoints[], int npoints) {
  88         doPolygon(sg2d, xpoints, ypoints, npoints, false, false);
  89     }
  90 
  91     public synchronized void drawPolygon(SunGraphics2D sg2d, int xpoints[], int ypoints[], int npoints) {
  92         doPolygon(sg2d, xpoints, ypoints, npoints, true, false);
  93     }
  94 
  95     public synchronized void fillRect(SunGraphics2D sg2d, int x, int y, int width, int height) {
  96         // create shape corresponding to this primitive
  97         rectangle.setRect(x, y, width, height);
  98 
  99         fill(sg2d, rectangle);
 100     }
 101 
 102     public synchronized void fillRoundRect(SunGraphics2D sg2d, int x, int y, int width, int height, int arcWidth, int arcHeight) {
 103         // create shape corresponding to this primitive
 104         roundrectangle.setRoundRect(x, y, width, height, arcWidth, arcHeight);
 105 
 106         fill(sg2d, roundrectangle);
 107     }
 108 
 109     public synchronized void fillOval(SunGraphics2D sg2d, int x, int y, int width, int height) {
 110         // create shape corresponding to this primitive
 111         ellipse.setFrame(x, y, width, height);
 112 
 113         fill(sg2d, ellipse);
 114     }
 115 
 116     public synchronized void fillArc(SunGraphics2D sg2d, int x, int y, int width, int height, int startAngle, int arcAngle) {
 117         // create shape corresponding to this primitive
 118         arc.setArc(x, y, width, height, startAngle, arcAngle, Arc2D.PIE);
 119 
 120         fill(sg2d, arc);
 121     }
 122 
 123     public synchronized void fillPolygon(SunGraphics2D sg2d, int xpoints[], int ypoints[], int npoints) {
 124         doPolygon(sg2d, xpoints, ypoints, npoints, true, true);
 125     }
 126 
 127     public synchronized void doPolygon(SunGraphics2D sg2d, int xpoints[], int ypoints[], int npoints, boolean ispolygon, boolean isfill) {
 128         GeneralPath gp = new GeneralPath(Path2D.WIND_NON_ZERO, npoints);
 129         gp.moveTo(xpoints[0], ypoints[0]);
 130         for (int i = 1; i < npoints; i++) {
 131             gp.lineTo(xpoints[i], ypoints[i]);
 132         }
 133         if (ispolygon) {
 134             // according to the specs (only applies to polygons, not polylines)
 135             if ((xpoints[0] != xpoints[npoints - 1]) || (ypoints[0] != ypoints[npoints - 1])) {
 136                 gp.lineTo(xpoints[0], ypoints[0]);
 137             }
 138         }
 139 
 140         doShape(sg2d, (OSXSurfaceData) sg2d.getSurfaceData(), (Shape) gp, isfill);
 141     }
 142 
 143     public synchronized void draw(SunGraphics2D sg2d, Shape shape) {
 144         doShape(sg2d, (OSXSurfaceData) sg2d.getSurfaceData(), shape, false);
 145     }
 146 
 147     public synchronized void fill(SunGraphics2D sg2d, Shape shape) {
 148         doShape(sg2d, (OSXSurfaceData) sg2d.getSurfaceData(), shape, true);
 149     }
 150 
 151     void doShape(SunGraphics2D sg2d, OSXSurfaceData surfaceData, Shape shape, boolean isfill) {
 152         Rectangle2D shapeBounds = shape.getBounds2D();
 153 
 154         // We don't want to draw with negative width and height (CRender doesn't do it and Windows doesn't do it either)
 155         // Drawing with negative w and h, can cause CG problems down the line <rdar://3960579> (vm)
 156         if ((shapeBounds.getWidth() < 0) || (shapeBounds.getHeight() < 0)) { return; }
 157 
 158         // get final destination compositing bounds (after all transformations if needed)
 159         Rectangle2D compositingBounds = padBounds(sg2d, shape);
 160 
 161         // constrain the bounds to be within surface bounds
 162         clipBounds(sg2d, compositingBounds);
 163 
 164         // if the compositing region is empty we skip all remaining compositing work:
 165         if (compositingBounds.isEmpty() == false) {
 166             BufferedImage srcPixels;
 167             // create a matching surface into which we'll render the primitive to be composited
 168             // with the desired dimension
 169             srcPixels = surfaceData.getCompositingSrcImage((int) (compositingBounds.getWidth()),
 170                     (int) (compositingBounds.getHeight()));
 171 
 172             Graphics2D g = srcPixels.createGraphics();
 173 
 174             // sync up graphics state
 175             ShapeTM.setToTranslation(-compositingBounds.getX(), -compositingBounds.getY());
 176             ShapeTM.concatenate(sg2d.transform);
 177             g.setTransform(ShapeTM);
 178             g.setRenderingHints(sg2d.getRenderingHints());
 179             g.setPaint(sg2d.getPaint());
 180             g.setStroke(sg2d.getStroke());
 181 
 182             // render the primitive to be composited
 183             if (isfill) {
 184                 g.fill(shape);
 185             } else {
 186                 g.draw(shape);
 187             }
 188 
 189             g.dispose();
 190 
 191             composite(sg2d, surfaceData, srcPixels, compositingBounds);
 192         }
 193     }
 194 
 195     public synchronized void drawString(SunGraphics2D sg2d, String str, double x, double y) {
 196         drawGlyphVector(sg2d, sg2d.getFont().createGlyphVector(sg2d.getFontRenderContext(), str), x, y);
 197     }
 198 
 199     public synchronized void drawChars(SunGraphics2D sg2d, char data[], int offset, int length, int x, int y) {
 200         drawString(sg2d, new String(data, offset, length), x, y);
 201     }
 202 
 203     public synchronized void drawGlyphVector(SunGraphics2D sg2d, GlyphVector glyphVector, double x, double y) {
 204         drawGlyphVector(sg2d, glyphVector, (float) x, (float) y);
 205     }
 206 
 207     public synchronized void drawGlyphVector(SunGraphics2D sg2d, GlyphVector glyphVector, float x, float y) {
 208         OSXSurfaceData surfaceData = (OSXSurfaceData) sg2d.getSurfaceData();
 209 
 210         Shape shape = glyphVector.getOutline(x, y);
 211 
 212         // get final destination compositing bounds (after all transformations if needed)
 213         Rectangle2D compositingBounds = padBounds(sg2d, shape);
 214 
 215         // constrain the bounds to be within surface bounds
 216         clipBounds(sg2d, compositingBounds);
 217 
 218         // if the compositing region is empty we skip all remaining compositing work:
 219         if (compositingBounds.isEmpty() == false) {
 220             BufferedImage srcPixels;
 221             {
 222                 // create matching image into which we'll render the primitive to be composited
 223                 srcPixels = surfaceData.getCompositingSrcImage((int) compositingBounds.getWidth(), (int) compositingBounds.getHeight());
 224 
 225                 Graphics2D g = srcPixels.createGraphics();
 226 
 227                 // sync up graphics state
 228                 ShapeTM.setToTranslation(-compositingBounds.getX(), -compositingBounds.getY());
 229                 ShapeTM.concatenate(sg2d.transform);
 230                 g.setTransform(ShapeTM);
 231                 g.setPaint(sg2d.getPaint());
 232                 g.setStroke(sg2d.getStroke());
 233                 g.setFont(sg2d.getFont());
 234                 g.setRenderingHints(sg2d.getRenderingHints());
 235 
 236                 // render the primitive to be composited
 237                 g.drawGlyphVector(glyphVector, x, y);
 238                 g.dispose();
 239             }
 240 
 241             composite(sg2d, surfaceData, srcPixels, compositingBounds);
 242         }
 243     }
 244 
 245     protected boolean blitImage(SunGraphics2D sg2d, Image img, boolean fliph, boolean flipv, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh, Color bgColor) {
 246         OSXSurfaceData surfaceData = (OSXSurfaceData) sg2d.getSurfaceData();
 247 
 248         // get final destination compositing bounds (after all transformations if needed)
 249         dx = (flipv == false) ? dx : dx - dw;
 250         dy = (fliph == false) ? dy : dy - dh;
 251         ShapeBounds.setFrame(dx, dy, dw, dh);
 252         Rectangle2D compositingBounds = ShapeBounds;
 253         boolean complexTransform = (sg2d.transformState >= SunGraphics2D.TRANSFORM_TRANSLATESCALE);
 254         if (complexTransform == false) {
 255             double newX = Math.floor(compositingBounds.getX() + sg2d.transX);
 256             double newY = Math.floor(compositingBounds.getY() + sg2d.transY);
 257             double newW = Math.ceil(compositingBounds.getWidth()) + (newX < compositingBounds.getX() ? 1 : 0);
 258             double newH = Math.ceil(compositingBounds.getHeight()) + (newY < compositingBounds.getY() ? 1 : 0);
 259             compositingBounds.setRect(newX, newY, newW, newH);
 260         } else {
 261             Shape transformedShape = sg2d.transform.createTransformedShape(compositingBounds);
 262             compositingBounds = transformedShape.getBounds2D();
 263             double newX = Math.floor(compositingBounds.getX());
 264             double newY = Math.floor(compositingBounds.getY());
 265             double newW = Math.ceil(compositingBounds.getWidth()) + (newX < compositingBounds.getX() ? 1 : 0);
 266             double newH = Math.ceil(compositingBounds.getHeight()) + (newY < compositingBounds.getY() ? 1 : 0);
 267             compositingBounds.setRect(newX, newY, newW, newH);
 268         }
 269 
 270         // constrain the bounds to be within surface bounds
 271         clipBounds(sg2d, compositingBounds);
 272 
 273         // if the compositing region is empty we skip all remaining compositing work:
 274         if (compositingBounds.isEmpty() == false) {
 275             BufferedImage srcPixels;
 276             {
 277                 // create matching image into which we'll render the primitive to be composited
 278                 srcPixels = surfaceData.getCompositingSrcImage((int) compositingBounds.getWidth(), (int) compositingBounds.getHeight());
 279 
 280                 Graphics2D g = srcPixels.createGraphics();
 281 
 282                 // sync up graphics state
 283                 ShapeTM.setToTranslation(-compositingBounds.getX(), -compositingBounds.getY());
 284                 ShapeTM.concatenate(sg2d.transform);
 285                 g.setTransform(ShapeTM);
 286                 g.setRenderingHints(sg2d.getRenderingHints());
 287                 g.setComposite(AlphaComposite.Src);
 288 
 289                 int sx2 = (flipv == false) ? sx + sw : sx - sw;
 290                 int sy2 = (fliph == false) ? sy + sh : sy - sh;
 291                 g.drawImage(img, dx, dy, dx + dw, dy + dh, sx, sy, sx2, sy2, null);
 292 
 293                 g.dispose();
 294             }
 295 
 296             composite(sg2d, surfaceData, srcPixels, compositingBounds);
 297         }
 298 
 299         return true;
 300     }
 301 
 302     Rectangle2D padBounds(SunGraphics2D sg2d, Shape shape) {
 303         shape = sg2d.transformShape(shape);
 304 
 305         int paddingHalf = fPaddingHalf;
 306         int padding = fPadding;
 307         if (sg2d.stroke != null) {
 308             if (sg2d.stroke instanceof BasicStroke) {
 309                 int width = (int) (((BasicStroke) sg2d.stroke).getLineWidth() + 0.5f);
 310                 int widthHalf = width / 2 + 1;
 311                 paddingHalf += widthHalf;
 312                 padding += 2 * widthHalf;
 313             } else {
 314                 shape = sg2d.stroke.createStrokedShape(shape);
 315             }
 316         }
 317         Rectangle2D bounds = shape.getBounds2D();
 318         bounds.setRect(bounds.getX() - paddingHalf, bounds.getY() - paddingHalf, bounds.getWidth() + padding, bounds.getHeight() + padding);
 319 
 320         double newX = Math.floor(bounds.getX());
 321         double newY = Math.floor(bounds.getY());
 322         double newW = Math.ceil(bounds.getWidth()) + (newX < bounds.getX() ? 1 : 0);
 323         double newH = Math.ceil(bounds.getHeight()) + (newY < bounds.getY() ? 1 : 0);
 324         bounds.setRect(newX, newY, newW, newH);
 325 
 326         return bounds;
 327     }
 328 
 329     void clipBounds(SunGraphics2D sg2d, Rectangle2D bounds) {
 330         /*
 331          * System.err.println("clipBounds"); System.err.println("    transform="+sg2d.transform);
 332          * System.err.println("    getTransform()="+sg2d.getTransform());
 333          * System.err.println("    complexTransform="+(sg2d.transformState > SunGraphics2D.TRANSFORM_TRANSLATESCALE));
 334          * System.err.println("    transX="+sg2d.transX+" transY="+sg2d.transX);
 335          * System.err.println("    sg2d.constrainClip="+sg2d.constrainClip); if (sg2d.constrainClip != null) {
 336          * System.err
 337          * .println("    constrainClip: x="+sg2d.constrainClip.getLoX()+" y="+sg2d.constrainClip.getLoY()+" w="
 338          * +sg2d.constrainClip.getWidth()+" h="+sg2d.constrainClip.getHeight());}
 339          * System.err.println("    constrainX="+sg2d.constrainX+" constrainY="+sg2d.constrainY);
 340          * System.err.println("    usrClip="+sg2d.usrClip);
 341          * System.err.println("    devClip: x="+sg2d.devClip.getLoX()+" y="
 342          * +sg2d.devClip.getLoY()+" w="+sg2d.devClip.getWidth()+" h="+sg2d.devClip.getHeight());
 343          */
 344         Region intersection = sg2d.clipRegion.getIntersectionXYWH((int) bounds.getX(), (int) bounds.getY(), (int) bounds.getWidth(), (int) bounds.getHeight());
 345         bounds.setRect(intersection.getLoX(), intersection.getLoY(), intersection.getWidth(), intersection.getHeight());
 346     }
 347 
 348     BufferedImage getSurfacePixels(SunGraphics2D sg2d, OSXSurfaceData surfaceData, int x, int y, int w, int h) {
 349         // create an image to copy the surface pixels into
 350         BufferedImage dstInPixels = surfaceData.getCompositingDstInImage(w, h);
 351 
 352         // get the pixels from the dst surface
 353         return surfaceData.copyArea(sg2d, x, y, w, h, dstInPixels);
 354     }
 355 
 356     void composite(SunGraphics2D sg2d, OSXSurfaceData surfaceData, BufferedImage srcPixels, Rectangle2D compositingBounds) {
 357         // Thread.dumpStack();
 358         // System.err.println("composite");
 359         // System.err.println("    compositingBounds="+compositingBounds);
 360         int x = (int) compositingBounds.getX();
 361         int y = (int) compositingBounds.getY();
 362         int w = (int) compositingBounds.getWidth();
 363         int h = (int) compositingBounds.getHeight();
 364 
 365         boolean succeded = false;
 366 
 367         Composite composite = sg2d.getComposite();
 368         if (composite instanceof XORComposite) {
 369             // 1st native XOR try
 370             // we try to perform XOR using surface pixels directly
 371             try {
 372                 succeded = surfaceData.xorSurfacePixels(sg2d, srcPixels, x, y, w, h, ((XORComposite) composite).getXorColor().getRGB());
 373             } catch (Exception e) {
 374                 succeded = false;
 375             }
 376         }
 377 
 378         if (succeded == false) {
 379             // create image with the original pixels of surface
 380             BufferedImage dstInPixels = getSurfacePixels(sg2d, surfaceData, x, y, w, h);
 381             BufferedImage dstOutPixels = null;
 382 
 383             if (composite instanceof XORComposite) {
 384                 // 2nd native XOR try
 385                 // we try to perform XOR on image's pixels (which were copied from surface first)
 386                 try {
 387                     OSXSurfaceData osxsd = (OSXSurfaceData) (BufImgSurfaceData.createData(dstInPixels));
 388                     succeded = osxsd.xorSurfacePixels(sg2d, srcPixels, 0, 0, w, h, ((XORComposite) composite).getXorColor().getRGB());
 389                     dstOutPixels = dstInPixels;
 390                 } catch (Exception e) {
 391                     succeded = false;
 392                 }
 393             }
 394 
 395             // either 2nd native XOR failed OR we have a case of custom compositing
 396             if (succeded == false) {
 397                 // create an image into which we'll composite result: we MUST use a different destination (compositing
 398                 // is NOT "in place" operation)
 399                 dstOutPixels = surfaceData.getCompositingDstOutImage(w, h);
 400 
 401                 // prepare rasters for compositing
 402                 WritableRaster srcRaster = srcPixels.getRaster();
 403                 WritableRaster dstInRaster = dstInPixels.getRaster();
 404                 WritableRaster dstOutRaster = dstOutPixels.getRaster();
 405 
 406                 CompositeContext compositeContext = composite.createContext(srcPixels.getColorModel(), dstOutPixels.getColorModel(), sg2d.getRenderingHints());
 407                 compositeContext.compose(srcRaster, dstInRaster, dstOutRaster);
 408                 compositeContext.dispose();
 409 
 410                 // gznote: radar bug number
 411                 // "cut out" the shape we're interested in
 412                 // applyMask(BufImgSurfaceData.createData(dstOutPixels), BufImgSurfaceData.createData(srcPixels), w, h);
 413             }
 414 
 415             // blit the results back to the dst surface
 416             Composite savedComposite = sg2d.getComposite();
 417             AffineTransform savedTM = sg2d.getTransform();
 418             int savedCX = sg2d.constrainX;
 419             int savedCY = sg2d.constrainY;
 420             {
 421                 sg2d.setComposite(AlphaComposite.SrcOver);
 422                 // all the compositing is done in the coordinate space of the component. the x and the y are the
 423                 // position of that component in the surface
 424                 // so we need to set the sg2d.transform to identity and we must set the contrainX/Y to 0 for the
 425                 // setTransform() to not be constrained
 426                 sg2d.constrainX = 0;
 427                 sg2d.constrainY = 0;
 428                 sg2d.setTransform(sIdentityMatrix);
 429                 sg2d.drawImage(dstOutPixels, x, y, x + w, y + h, 0, 0, w, h, null);
 430             }
 431             sg2d.constrainX = savedCX;
 432             sg2d.constrainY = savedCY;
 433             sg2d.setTransform(savedTM);
 434             sg2d.setComposite(savedComposite);
 435         }
 436     }
 437 }