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 }