Source for gnu.java.awt.peer.gtk.BufferedImageGraphics

   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: 
 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: 
 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:    */
 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:    */
 277:   public Graphics create()
 278:   {
 279:     return new BufferedImageGraphics(this);
 280:   }
 281: 
 282:   public GraphicsConfiguration getDeviceConfiguration()
 283:   {
 284:     return null;
 285:   }
 286: 
 287:   protected Rectangle2D getRealBounds()
 288:   {
 289:     return new Rectangle2D.Double(0.0, 0.0, imageWidth, imageHeight);
 290:   }
 291: 
 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:    */
 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: 
 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: 
 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: 
 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: 
 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:    */
 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: 
 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: 
 529:   protected ColorModel getNativeCM()
 530:   {
 531:     return image.getColorModel();
 532:   }
 533: 
 534:   protected ColorModel getBufferCM()
 535:   {
 536:     return ColorModel.getRGBdefault();
 537:   }
 538: }