1 /* BufferedImageGraphics.java 2 Copyright (C) 2006 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package gnu.java.awt.peer.gtk; 40 41 import java.awt.AlphaComposite; 42 import java.awt.Color; 43 import java.awt.Composite; 44 import java.awt.Graphics; 45 import java.awt.Graphics2D; 46 import java.awt.GraphicsConfiguration; 47 import java.awt.Image; 48 import java.awt.Rectangle; 49 import java.awt.Shape; 50 import java.awt.Toolkit; 51 import java.awt.font.GlyphVector; 52 import java.awt.geom.AffineTransform; 53 import java.awt.geom.Rectangle2D; 54 import java.awt.image.BufferedImage; 55 import java.awt.image.ColorModel; 56 import java.awt.image.DataBufferInt; 57 import java.awt.image.ImageObserver; 58 import java.awt.image.ImageProducer; 59 import java.awt.image.Raster; 60 import java.awt.image.RenderedImage; 61 import java.awt.image.SinglePixelPackedSampleModel; 62 import java.util.WeakHashMap; 63 64 /** 65 * Implementation of Graphics2D on a Cairo surface. 66 * 67 * Simutanously maintains a CairoSurface and updates the 68 * BufferedImage from that after each drawing operation. 69 */ 70 public class BufferedImageGraphics extends CairoGraphics2D 71 { 72 /** 73 * the buffered Image. 74 */ 75 private BufferedImage image, buffer; 76 77 /** 78 * Image size. 79 */ 80 private int imageWidth, imageHeight; 81 82 /** 83 * The cairo surface that we actually draw on. 84 */ 85 CairoSurface surface; 86 87 /** 88 * Cache BufferedImageGraphics surfaces. 89 */ 90 static WeakHashMap<BufferedImage, CairoSurface> bufferedImages 91 = new WeakHashMap<BufferedImage, CairoSurface>(); 92 93 /** 94 * Its corresponding cairo_t. 95 */ 96 private long cairo_t; 97 98 private boolean hasFastCM; 99 private boolean hasAlpha; 100 101 BufferedImageGraphics(BufferedImage bi)102 public BufferedImageGraphics(BufferedImage bi) 103 { 104 this.image = bi; 105 imageWidth = bi.getWidth(); 106 imageHeight = bi.getHeight(); 107 108 if (!(image.getSampleModel() instanceof SinglePixelPackedSampleModel)) 109 hasFastCM = false; 110 else if(bi.getColorModel().equals(CairoSurface.cairoCM_opaque)) 111 { 112 hasFastCM = true; 113 hasAlpha = false; 114 } 115 else if(bi.getColorModel().equals(CairoSurface.cairoColorModel) 116 || bi.getColorModel().equals(CairoSurface.cairoCM_pre)) 117 { 118 hasFastCM = true; 119 hasAlpha = true; 120 } 121 else 122 hasFastCM = false; 123 124 // Cache surfaces. 125 if( bufferedImages.get( bi ) != null ) 126 surface = bufferedImages.get( bi ); 127 else 128 { 129 surface = new CairoSurface( imageWidth, imageHeight ); 130 bufferedImages.put(bi, surface); 131 } 132 133 cairo_t = surface.newCairoContext(); 134 135 // Get pixels out of buffered image and set in cairo surface 136 Raster raster = bi.getRaster(); 137 int[] pixels; 138 139 if (hasFastCM) 140 { 141 SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel)image.getSampleModel(); 142 int minX = image.getRaster().getSampleModelTranslateX(); 143 int minY = image.getRaster().getSampleModelTranslateY(); 144 145 // Pull pixels directly out of data buffer 146 pixels = ((DataBufferInt)raster.getDataBuffer()).getData(); 147 148 // Discard pixels that fall outside of the image's bounds 149 // (ie, this image is actually a subimage of a different image) 150 if (!(sm.getScanlineStride() == imageWidth && minX == 0 && minY == 0)) 151 { 152 int[] pixels2 = new int[imageWidth * imageHeight]; 153 int scanline = sm.getScanlineStride(); 154 155 for (int i = 0; i < imageHeight; i++) 156 System.arraycopy(pixels, (i - minY) * scanline - minX, pixels2, 157 i * imageWidth, imageWidth); 158 159 pixels = pixels2; 160 } 161 162 // Fill the alpha channel as opaque if image does not have alpha 163 if( !hasAlpha ) 164 for(int i = 0; i < pixels.length; i++) 165 pixels[i] &= 0xFFFFFFFF; 166 } 167 else 168 { 169 pixels = CairoGraphics2D.findSimpleIntegerArray(image.getColorModel(), 170 image.getData()); 171 if (pixels != null) 172 System.arraycopy(pixels, 0, surface.getData(), 173 0, pixels.length); 174 } 175 176 setup( cairo_t ); 177 setClip(0, 0, imageWidth, imageHeight); 178 } 179 BufferedImageGraphics(BufferedImageGraphics copyFrom)180 BufferedImageGraphics(BufferedImageGraphics copyFrom) 181 { 182 image = copyFrom.image; 183 surface = copyFrom.surface; 184 cairo_t = surface.newCairoContext(); 185 imageWidth = copyFrom.imageWidth; 186 imageHeight = copyFrom.imageHeight; 187 188 hasFastCM = copyFrom.hasFastCM; 189 hasAlpha = copyFrom.hasAlpha; 190 191 copy( copyFrom, cairo_t ); 192 } 193 194 /** 195 * Update a rectangle of the bufferedImage. This can be improved upon a lot. 196 */ updateBufferedImage(int x, int y, int width, int height)197 private void updateBufferedImage(int x, int y, int width, int height) 198 { 199 Rectangle bounds = new Rectangle(x, y, width, height); 200 bounds = getTransformedBounds(bounds, transform).getBounds(); 201 x = bounds.x; 202 y = bounds.y; 203 width = bounds.width; 204 height = bounds.height; 205 206 int[] pixels = surface.getData(); 207 208 if( x > imageWidth || y > imageHeight ) 209 return; 210 211 // Deal with negative width/height. 212 if (height < 0) 213 { 214 y += height; 215 height = -height; 216 } 217 if (width < 0) 218 { 219 x += width; 220 width = -width; 221 } 222 223 // Clip edges. 224 if( x < 0 ) 225 x = 0; 226 if( y < 0 ) 227 y = 0; 228 229 if( x + width > imageWidth ) 230 width = imageWidth - x; 231 if( y + height > imageHeight ) 232 height = imageHeight - y; 233 234 if(!hasFastCM) 235 { 236 image.setRGB(x, y, width, height, pixels, 237 x + y * imageWidth, imageWidth); 238 // The setRGB method assumes (or should assume) that pixels are NOT 239 // alpha-premultiplied, but Cairo stores data with premultiplication 240 // (thus the pixels returned in getPixels are premultiplied). 241 // This is ignored for consistency, however, since in 242 // CairoGrahpics2D.drawImage we also use non-premultiplied data 243 244 } 245 else 246 { 247 int[] db = ((DataBufferInt)image.getRaster().getDataBuffer()). 248 getData(); 249 250 // This should not fail, as we check the image sample model when we 251 // set the hasFastCM flag 252 SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel)image.getSampleModel() ; 253 254 int minX = image.getRaster().getSampleModelTranslateX(); 255 int minY = image.getRaster().getSampleModelTranslateY(); 256 257 if (sm.getScanlineStride() == imageWidth && minX == 0) 258 { 259 System.arraycopy(pixels, y * imageWidth, 260 db, (y - minY) * imageWidth, 261 height * imageWidth); 262 } 263 else 264 { 265 int scanline = sm.getScanlineStride(); 266 for (int i = y; i < (height + y); i++) 267 System.arraycopy(pixels, i * imageWidth + x, db, 268 (i - minY) * scanline + x - minX, width); 269 270 } 271 } 272 } 273 274 /** 275 * Abstract methods. 276 */ create()277 public Graphics create() 278 { 279 return new BufferedImageGraphics(this); 280 } 281 getDeviceConfiguration()282 public GraphicsConfiguration getDeviceConfiguration() 283 { 284 return null; 285 } 286 getRealBounds()287 protected Rectangle2D getRealBounds() 288 { 289 return new Rectangle2D.Double(0.0, 0.0, imageWidth, imageHeight); 290 } 291 copyAreaImpl(int x, int y, int width, int height, int dx, int dy)292 public void copyAreaImpl(int x, int y, int width, int height, int dx, int dy) 293 { 294 surface.copyAreaNative(x, y, width, height, dx, dy, surface.width); 295 updateBufferedImage(x + dx, y + dy, width, height); 296 } 297 298 /** 299 * Overloaded methods that do actual drawing need to enter the gdk threads 300 * and also do certain things before and after. 301 */ draw(Shape s)302 public void draw(Shape s) 303 { 304 // Find total bounds of shape 305 Rectangle r = findStrokedBounds(s); 306 if (shiftDrawCalls) 307 { 308 r.width++; 309 r.height++; 310 } 311 312 // Do the drawing 313 if (comp == null || comp instanceof AlphaComposite) 314 { 315 super.draw(s); 316 updateBufferedImage(r.x, r.y, r.width, r.height); 317 } 318 else 319 { 320 createBuffer(); 321 322 Graphics2D g2d = (Graphics2D)buffer.getGraphics(); 323 g2d.setStroke(this.getStroke()); 324 g2d.setColor(this.getColor()); 325 g2d.setTransform(transform); 326 g2d.draw(s); 327 328 drawComposite(r.getBounds2D(), null); 329 } 330 } 331 fill(Shape s)332 public void fill(Shape s) 333 { 334 if (comp == null || comp instanceof AlphaComposite) 335 { 336 super.fill(s); 337 Rectangle r = s.getBounds(); 338 updateBufferedImage(r.x, r.y, r.width, r.height); 339 } 340 else 341 { 342 createBuffer(); 343 344 Graphics2D g2d = (Graphics2D)buffer.getGraphics(); 345 g2d.setPaint(this.getPaint()); 346 g2d.setColor(this.getColor()); 347 g2d.setTransform(transform); 348 g2d.fill(s); 349 350 drawComposite(s.getBounds2D(), null); 351 } 352 } 353 drawRenderedImage(RenderedImage image, AffineTransform xform)354 public void drawRenderedImage(RenderedImage image, AffineTransform xform) 355 { 356 if (comp == null || comp instanceof AlphaComposite) 357 { 358 super.drawRenderedImage(image, xform); 359 updateBufferedImage(0, 0, imageWidth, imageHeight); 360 } 361 else 362 { 363 createBuffer(); 364 365 Graphics2D g2d = (Graphics2D)buffer.getGraphics(); 366 g2d.setRenderingHints(this.getRenderingHints()); 367 g2d.setTransform(transform); 368 g2d.drawRenderedImage(image, xform); 369 370 drawComposite(buffer.getRaster().getBounds(), null); 371 } 372 373 } 374 drawImage(Image img, AffineTransform xform, Color bgcolor, ImageObserver obs)375 protected boolean drawImage(Image img, AffineTransform xform, 376 Color bgcolor, ImageObserver obs) 377 { 378 if (comp == null || comp instanceof AlphaComposite) 379 { 380 boolean rv = super.drawImage(img, xform, bgcolor, obs); 381 updateBufferedImage(0, 0, imageWidth, imageHeight); 382 return rv; 383 } 384 else 385 { 386 // Get buffered image of source 387 if( !(img instanceof BufferedImage) ) 388 { 389 ImageProducer source = img.getSource(); 390 if (source == null) 391 return false; 392 img = Toolkit.getDefaultToolkit().createImage(source); 393 } 394 BufferedImage bImg = (BufferedImage) img; 395 396 // Find translated bounds 397 Rectangle2D bounds = new Rectangle(bImg.getMinX(), bImg.getMinY(), 398 bImg.getWidth(), bImg.getHeight()); 399 if (xform != null) 400 bounds = getTransformedBounds(bounds, xform); 401 402 // Create buffer and draw image 403 createBuffer(); 404 405 Graphics2D g2d = (Graphics2D)buffer.getGraphics(); 406 g2d.setRenderingHints(this.getRenderingHints()); 407 g2d.drawImage(img, xform, obs); 408 409 // Perform compositing 410 return drawComposite(bounds, obs); 411 } 412 } 413 drawGlyphVector(GlyphVector gv, float x, float y)414 public void drawGlyphVector(GlyphVector gv, float x, float y) 415 { 416 // Find absolute bounds, in user-space, of this glyph vector 417 Rectangle2D bounds = gv.getLogicalBounds(); 418 bounds = new Rectangle2D.Double(x + bounds.getX(), y + bounds.getY(), 419 bounds.getWidth(), bounds.getHeight()); 420 421 // Perform draw operation 422 if (comp == null || comp instanceof AlphaComposite) 423 { 424 super.drawGlyphVector(gv, x, y); 425 426 // this returns an integer-based Rectangle (rather than a 427 // Rectangle2D), which takes care of any necessary rounding for us. 428 bounds = bounds.getBounds(); 429 430 updateBufferedImage((int)bounds.getX(), (int)bounds.getY(), 431 (int)bounds.getWidth(), (int)bounds.getHeight()); 432 } 433 else 434 { 435 createBuffer(); 436 437 Graphics2D g2d = (Graphics2D)buffer.getGraphics(); 438 g2d.setPaint(this.getPaint()); 439 g2d.setStroke(this.getStroke()); 440 g2d.setTransform(transform); 441 g2d.drawGlyphVector(gv, x, y); 442 443 drawComposite(bounds, null); 444 } 445 } 446 447 /** 448 * Perform composite drawing from the buffer onto the main image. 449 * 450 * The image to be composited should already be drawn into the buffer, in the 451 * proper place, after all necessary transforms have been applied. 452 * 453 * @param bounds The bounds to draw, in user-space. 454 * @param observer The image observer, if any (may be null). 455 * @return True on success, false on failure. 456 */ drawComposite(Rectangle2D bounds, ImageObserver observer)457 private boolean drawComposite(Rectangle2D bounds, ImageObserver observer) 458 { 459 // Find bounds in device space 460 bounds = getTransformedBounds(bounds, transform); 461 462 // Clip bounds by the stored clip, and by the internal buffer 463 Rectangle2D devClip = this.getClipInDevSpace(); 464 Rectangle2D.intersect(bounds, devClip, bounds); 465 devClip = new Rectangle(buffer.getMinX(), buffer.getMinY(), 466 buffer.getWidth(), buffer.getHeight()); 467 Rectangle2D.intersect(bounds, devClip, bounds); 468 469 // Round bounds as needed, but be careful in our rounding 470 // (otherwise it may leave unpainted stripes) 471 double x = bounds.getX(); 472 double y = bounds.getY(); 473 double maxX = x + bounds.getWidth(); 474 double maxY = y + bounds.getHeight(); 475 x = Math.round(x); 476 y = Math.round(y); 477 bounds.setRect(x, y, Math.round(maxX - x), Math.round(maxY - y)); 478 479 // Find subimage of internal buffer for updating 480 BufferedImage buffer2 = buffer; 481 if (!bounds.equals(buffer2.getRaster().getBounds())) 482 buffer2 = buffer2.getSubimage((int)bounds.getX(), (int)bounds.getY(), 483 (int)bounds.getWidth(), 484 (int)bounds.getHeight()); 485 486 // Find subimage of main image for updating 487 BufferedImage current = image; 488 current = current.getSubimage((int)bounds.getX(), (int)bounds.getY(), 489 (int)bounds.getWidth(), 490 (int)bounds.getHeight()); 491 492 // Perform actual composite operation 493 compCtx.compose(buffer2.getRaster(), current.getRaster(), 494 current.getRaster()); 495 496 // Set cairo's composite to direct SRC, since we've already done our own 497 // compositing 498 Composite oldcomp = comp; 499 setComposite(AlphaComposite.Src); 500 501 // This MUST call directly into the "action" method in CairoGraphics2D, 502 // not one of the wrappers, to ensure that the composite isn't processed 503 // more than once! 504 boolean rv = super.drawImage(current, 505 AffineTransform.getTranslateInstance(bounds.getX(), 506 bounds.getY()), 507 null, null); 508 setComposite(oldcomp); 509 updateColor(); 510 return rv; 511 } 512 createBuffer()513 private void createBuffer() 514 { 515 if (buffer == null) 516 { 517 buffer = new BufferedImage(image.getWidth(), image.getHeight(), 518 BufferedImage.TYPE_INT_ARGB); 519 } 520 else 521 { 522 Graphics2D g2d = ((Graphics2D)buffer.getGraphics()); 523 524 g2d.setBackground(new Color(0,0,0,0)); 525 g2d.clearRect(0, 0, buffer.getWidth(), buffer.getHeight()); 526 } 527 } 528 getNativeCM()529 protected ColorModel getNativeCM() 530 { 531 return image.getColorModel(); 532 } 533 getBufferCM()534 protected ColorModel getBufferCM() 535 { 536 return ColorModel.getRGBdefault(); 537 } 538 } 539