Source for java.awt.image.BufferedImage

   1: /* BufferedImage.java --
   2:    Copyright (C) 2000, 2002, 2003, 2004, 2005, 2006,  Free Software Foundation
   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 java.awt.image;
  40: 
  41: import gnu.java.awt.ComponentDataBlitOp;
  42: 
  43: import java.awt.Graphics;
  44: import java.awt.Graphics2D;
  45: import java.awt.GraphicsEnvironment;
  46: import java.awt.Image;
  47: import java.awt.Point;
  48: import java.awt.Rectangle;
  49: import java.awt.Transparency;
  50: import java.awt.color.ColorSpace;
  51: import java.util.Hashtable;
  52: import java.util.Vector;
  53: 
  54: /**
  55:  * A buffered image always starts at coordinates (0, 0).
  56:  *
  57:  * The buffered image is not subdivided into multiple tiles. Instead,
  58:  * the image consists of one large tile (0,0) with the width and
  59:  * height of the image. This tile is always considered to be checked
  60:  * out.
  61:  * 
  62:  * @author Rolf W. Rasmussen (rolfwr@ii.uib.no)
  63:  */
  64: public class BufferedImage extends Image
  65:   implements WritableRenderedImage, Transparency
  66: {
  67:   public static final int TYPE_CUSTOM         =  0,
  68:                           TYPE_INT_RGB        =  1,
  69:                           TYPE_INT_ARGB       =  2,
  70:                           TYPE_INT_ARGB_PRE   =  3,
  71:                           TYPE_INT_BGR        =  4,
  72:                           TYPE_3BYTE_BGR      =  5,
  73:                           TYPE_4BYTE_ABGR     =  6,
  74:                           TYPE_4BYTE_ABGR_PRE =  7,
  75:                           TYPE_USHORT_565_RGB =  8,
  76:                           TYPE_USHORT_555_RGB =  9,
  77:                           TYPE_BYTE_GRAY      = 10,
  78:                           TYPE_USHORT_GRAY    = 11,
  79:                           TYPE_BYTE_BINARY    = 12,
  80:                           TYPE_BYTE_INDEXED   = 13;
  81:   
  82:   static final int[] bits3 = { 8, 8, 8 };
  83:   static final int[] bits4 = { 8, 8, 8, 8 };
  84:   static final int[] bits1byte = { 8 };
  85:   static final int[] bits1ushort = { 16 };
  86:   
  87:   static final int[] masks_int = { 0x00ff0000,
  88:                    0x0000ff00,
  89:                    0x000000ff,
  90:                    DataBuffer.TYPE_INT };
  91:   static final int[] masks_565 = { 0xf800,
  92:                    0x07e0,
  93:                    0x001f,
  94:                    DataBuffer.TYPE_USHORT};
  95:   static final int[] masks_555 = { 0x7c00,
  96:                    0x03e0,
  97:                    0x001f,
  98:                    DataBuffer.TYPE_USHORT};
  99: 
 100:   Vector observers;
 101:   
 102:   /**
 103:    * Creates a new <code>BufferedImage</code> with the specified width, height
 104:    * and type.  Valid <code>type</code> values are:
 105:    * 
 106:    * <ul>
 107:    *   <li>{@link #TYPE_INT_RGB}</li>
 108:    *   <li>{@link #TYPE_INT_ARGB}</li>
 109:    *   <li>{@link #TYPE_INT_ARGB_PRE}</li>
 110:    *   <li>{@link #TYPE_INT_BGR}</li>
 111:    *   <li>{@link #TYPE_3BYTE_BGR}</li>
 112:    *   <li>{@link #TYPE_4BYTE_ABGR}</li>
 113:    *   <li>{@link #TYPE_4BYTE_ABGR_PRE}</li>
 114:    *   <li>{@link #TYPE_USHORT_565_RGB}</li>
 115:    *   <li>{@link #TYPE_USHORT_555_RGB}</li>
 116:    *   <li>{@link #TYPE_BYTE_GRAY}</li>
 117:    *   <li>{@link #TYPE_USHORT_GRAY}</li>
 118:    *   <li>{@link #TYPE_BYTE_BINARY}</li>
 119:    *   <li>{@link #TYPE_BYTE_INDEXED}</li>
 120:    * </ul>
 121:    * 
 122:    * @param w  the width (must be > 0).
 123:    * @param h  the height (must be > 0).
 124:    * @param type  the image type (see the list of valid types above).
 125:    * 
 126:    * @throws IllegalArgumentException if <code>w</code> or <code>h</code> is
 127:    *     less than or equal to zero.
 128:    * @throws IllegalArgumentException if <code>type</code> is not one of the
 129:    *     specified values.
 130:    */
 131:   public BufferedImage(int w, int h, int type)
 132:   {
 133:     ColorModel cm = null;
 134:     
 135:     boolean alpha = false;
 136:     boolean premultiplied = false;
 137:     switch (type)
 138:       {
 139:       case TYPE_4BYTE_ABGR_PRE:
 140:       case TYPE_INT_ARGB_PRE:
 141:     premultiplied = true;
 142:     // fall through
 143:       case TYPE_INT_ARGB:
 144:       case TYPE_4BYTE_ABGR:
 145:     alpha = true;
 146:       }
 147:     
 148:     ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
 149:     switch (type)
 150:       {
 151:       case TYPE_INT_RGB:
 152:       case TYPE_INT_ARGB:
 153:       case TYPE_INT_ARGB_PRE:
 154:       case TYPE_USHORT_565_RGB:
 155:       case TYPE_USHORT_555_RGB:
 156:     int[] masks = null;
 157:     switch (type)
 158:       {
 159:       case TYPE_INT_RGB:
 160:       case TYPE_INT_ARGB:
 161:       case TYPE_INT_ARGB_PRE:
 162:         masks = masks_int;
 163:         break;
 164:       case TYPE_USHORT_565_RGB:
 165:         masks = masks_565;
 166:         break;
 167:       case TYPE_USHORT_555_RGB:
 168:         masks = masks_555;
 169:         break;
 170:       }
 171:     
 172:     cm = new DirectColorModel(cs,
 173:                   32, // 32 bits in an int
 174:                   masks[0], // r
 175:                   masks[1], // g
 176:                   masks[2], // b
 177:                   alpha ? 0xff000000 : 0,
 178:                   premultiplied,
 179:                   masks[3] // data type
 180:                   );
 181:     break;
 182:     
 183:       case TYPE_INT_BGR:
 184:     String msg =
 185:       "FIXME: Programmer is confused. Why (and how) does a " +
 186:       "TYPE_INT_BGR image use ComponentColorModel to store " +
 187:       "8-bit values? Is data type TYPE_INT or TYPE_BYTE. What " +
 188:       "is the difference between TYPE_INT_BGR and TYPE_3BYTE_BGR?";
 189:     throw new UnsupportedOperationException(msg);
 190:     
 191:       case TYPE_3BYTE_BGR:
 192:       case TYPE_4BYTE_ABGR:
 193:       case TYPE_4BYTE_ABGR_PRE:
 194:       case TYPE_BYTE_GRAY:
 195:       case TYPE_USHORT_GRAY:
 196:     int[] bits = null;
 197:     int dataType = DataBuffer.TYPE_BYTE;
 198:     switch (type) {
 199:     case TYPE_3BYTE_BGR:
 200:       bits = bits3;
 201:       break;
 202:     case TYPE_4BYTE_ABGR:
 203:     case TYPE_4BYTE_ABGR_PRE:
 204:       bits = bits4;
 205:       break;
 206:         case TYPE_BYTE_GRAY:
 207:           bits = bits1byte;
 208:           cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
 209:           break;
 210:         case TYPE_USHORT_GRAY:
 211:           bits = bits1ushort;
 212:           cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
 213:           dataType = DataBuffer.TYPE_USHORT;
 214:           break;
 215:     }
 216:     cm = new ComponentColorModel(cs, bits, alpha, premultiplied,
 217:                      alpha ?
 218:                      Transparency.TRANSLUCENT:
 219:                      Transparency.OPAQUE,
 220:                      dataType);
 221:     break;
 222:       case TYPE_BYTE_BINARY:
 223:     byte[] vals = { 0, (byte) 0xff };
 224:     cm = new IndexColorModel(8, 2, vals, vals, vals);
 225:     break;
 226:       case TYPE_BYTE_INDEXED:
 227:     String msg2 = "type not implemented yet";
 228:     throw new UnsupportedOperationException(msg2);
 229:     // FIXME: build color-cube and create color model
 230:       default:
 231:         throw new IllegalArgumentException("Unknown image type " + type);
 232:       }
 233:     
 234:     init(cm,
 235:      cm.createCompatibleWritableRaster(w, h),
 236:      premultiplied,
 237:      null, // no properties
 238:      type
 239:      );
 240:   }
 241: 
 242:   public BufferedImage(int w, int h, int type,
 243:                IndexColorModel indexcolormodel)
 244:   {
 245:     if ((type != TYPE_BYTE_BINARY) && (type != TYPE_BYTE_INDEXED))
 246:       throw new IllegalArgumentException("type must be binary or indexed");
 247: 
 248:     init(indexcolormodel,
 249:      indexcolormodel.createCompatibleWritableRaster(w, h),
 250:      false, // not premultiplied (guess)
 251:      null, // no properties
 252:      type);
 253:   }
 254: 
 255:   public BufferedImage(ColorModel colormodel, 
 256:                WritableRaster writableraster,
 257:                boolean premultiplied,
 258:                Hashtable properties)
 259:   {
 260:     init(colormodel, writableraster, premultiplied, properties,
 261:      TYPE_CUSTOM);
 262:     // TODO: perhaps try to identify type?
 263:   }
 264:  
 265:   WritableRaster raster;
 266:   ColorModel colorModel;
 267:   Hashtable properties;
 268:   boolean isPremultiplied;
 269:   int type;
 270:   
 271:   private void init(ColorModel cm,
 272:             WritableRaster writableraster,
 273:             boolean premultiplied,
 274:             Hashtable properties,
 275:             int type)
 276:   {
 277:     raster = writableraster;
 278:     colorModel = cm;
 279:     this.properties = properties;
 280:     isPremultiplied = premultiplied;
 281:     this.type = type;
 282:   }
 283:     
 284:   //public void addTileObserver(TileObserver tileobserver) {}
 285:   
 286:   public void coerceData(boolean premultiplied)
 287:   {
 288:     colorModel = colorModel.coerceData(raster, premultiplied);
 289:   }
 290: 
 291:   public WritableRaster copyData(WritableRaster dest)
 292:   {
 293:     if (dest == null)
 294:       dest = raster.createCompatibleWritableRaster(getMinX(), getMinY(),
 295:                                                    getWidth(),getHeight());
 296: 
 297:     int x = dest.getMinX();
 298:     int y = dest.getMinY();
 299:     int w = dest.getWidth();
 300:     int h = dest.getHeight();
 301:     
 302:     // create a src child that has the right bounds...
 303:     WritableRaster src =
 304:       raster.createWritableChild(x, y, w, h, x, y,
 305:                  null  // same bands
 306:                  );
 307:     if (src.getSampleModel () instanceof ComponentSampleModel
 308:         && dest.getSampleModel () instanceof ComponentSampleModel)
 309:       // Refer to ComponentDataBlitOp for optimized data blitting:
 310:       ComponentDataBlitOp.INSTANCE.filter(src, dest);
 311:     else
 312:       {
 313:         // slower path
 314:         int samples[] = src.getPixels (x, y, w, h, (int [])null);
 315:         dest.setPixels (x, y, w, h, samples);
 316:       }
 317:     return dest;
 318:   }
 319: 
 320:   public Graphics2D createGraphics()
 321:   {
 322:     GraphicsEnvironment env;
 323:     env = GraphicsEnvironment.getLocalGraphicsEnvironment ();
 324:     return env.createGraphics (this);
 325:   }
 326: 
 327:   public void flush() {
 328:   }
 329:   
 330:   public WritableRaster getAlphaRaster()
 331:   {
 332:     return colorModel.getAlphaRaster(raster);
 333:   }
 334:   
 335:   public ColorModel getColorModel()
 336:   {
 337:     return colorModel;
 338:   }
 339:   
 340:   public Raster getData()
 341:   {
 342:     return copyData(null);
 343:     /* TODO: this might be optimized by returning the same
 344:        raster (not writable) as long as image data doesn't change. */
 345:   }
 346: 
 347:   public Raster getData(Rectangle rectangle)
 348:   {
 349:     WritableRaster dest =
 350:       raster.createCompatibleWritableRaster(rectangle);
 351:     return copyData(dest);
 352:   }
 353:   
 354:   public Graphics getGraphics()
 355:   {
 356:     return createGraphics();
 357:   }
 358: 
 359:   public int getHeight()
 360:   {
 361:     return raster.getHeight();
 362:   }
 363:   
 364:   public int getHeight(ImageObserver imageobserver)
 365:   {
 366:     return getHeight();
 367:   }
 368:     
 369:   public int getMinTileX()
 370:   {
 371:     return 0;
 372:   }
 373:   
 374:   public int getMinTileY()
 375:   {
 376:     return 0;
 377:   }
 378: 
 379:   public int getMinX()
 380:   {
 381:     return 0; 
 382:   }
 383: 
 384:   public int getMinY() 
 385:   {
 386:     return 0;
 387:   }
 388:   
 389:   public int getNumXTiles()
 390:   {
 391:     return 1;
 392:   }
 393: 
 394:   public int getNumYTiles()
 395:   {
 396:     return 1;
 397:   }
 398: 
 399:   /**
 400:    * Returns the value of the specified property, or 
 401:    * {@link Image#UndefinedProperty} if the property is not defined.
 402:    * 
 403:    * @param string  the property key (<code>null</code> not permitted).
 404:    * 
 405:    * @return The property value.
 406:    * 
 407:    * @throws NullPointerException if <code>string</code> is <code>null</code>.
 408:    */
 409:   public Object getProperty(String string)
 410:   {
 411:     if (string == null)
 412:       throw new NullPointerException("The property name cannot be null.");
 413:     Object result = Image.UndefinedProperty;
 414:     if (properties != null)
 415:       {
 416:         Object v = properties.get(string);
 417:         if (v != null)
 418:           result = v;
 419:       }
 420:     return result;
 421:   }
 422: 
 423:   public Object getProperty(String string, ImageObserver imageobserver)
 424:   {
 425:     return getProperty(string);
 426:   }
 427: 
 428:   /**
 429:    * Returns <code>null</code> always.
 430:    * 
 431:    * @return <code>null</code> always.
 432:    */
 433:   public String[] getPropertyNames()
 434:   {
 435:     // This method should always return null, see:
 436:     // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4640609
 437:     return null;
 438:   }
 439: 
 440:   public int getRGB(int x, int y)
 441:   {
 442:     Object rgbElem = raster.getDataElements(x, y,
 443:                         null // create as needed
 444:                         );
 445:     return colorModel.getRGB(rgbElem);
 446:   }
 447:     
 448:   public int[] getRGB(int startX, int startY, int w, int h,
 449:               int[] rgbArray,
 450:               int offset, int scanlineStride)
 451:   {
 452:     if (rgbArray == null)
 453:     {
 454:       /*
 455:     000000000000000000
 456:     00000[#######-----   [ = start
 457:     -----########-----   ] = end
 458:     -----#######]00000
 459:     000000000000000000  */
 460:       int size = (h-1)*scanlineStride + w;
 461:       rgbArray = new int[size];
 462:     }
 463:     
 464:     int endX = startX + w;
 465:     int endY = startY + h;
 466:     
 467:     /* *TODO*:
 468:        Opportunity for optimization by examining color models...
 469:        
 470:        Perhaps wrap the rgbArray up in a WritableRaster with packed
 471:        sRGB color model and perform optimized rendering into the
 472:        array. */
 473: 
 474:     Object rgbElem = null;
 475:     for (int y=startY; y<endY; y++)
 476:       {
 477:     int xoffset = offset;
 478:     for (int x=startX; x<endX; x++)
 479:       {
 480:         int rgb;
 481:         rgbElem = raster.getDataElements(x, y, rgbElem);
 482:         rgb = colorModel.getRGB(rgbElem);
 483:         rgbArray[xoffset++] = rgb;
 484:       }
 485:     offset += scanlineStride;
 486:       }
 487:     return rgbArray;
 488:   }
 489: 
 490:   public WritableRaster getRaster()
 491:   {
 492:     return raster;
 493:   }
 494:   
 495:   public SampleModel getSampleModel()
 496:   {
 497:     return raster.getSampleModel();
 498:   }
 499:     
 500:   public ImageProducer getSource()
 501:   {
 502:     return new ImageProducer() {
 503:         
 504:     Vector consumers = new Vector();
 505: 
 506:         public void addConsumer(ImageConsumer ic)
 507:         {
 508:       if(!consumers.contains(ic))
 509:         consumers.add(ic);
 510:         }
 511: 
 512:         public boolean isConsumer(ImageConsumer ic)
 513:         {
 514:           return consumers.contains(ic);
 515:         }
 516: 
 517:         public void removeConsumer(ImageConsumer ic)
 518:         {
 519:       consumers.remove(ic);
 520:         }
 521: 
 522:         public void startProduction(ImageConsumer ic)
 523:         {
 524:           int x = 0;
 525:           int y = 0;
 526:           int width = getWidth();
 527:           int height = getHeight();
 528:           int stride = width;
 529:           int offset = 0;
 530:           int[] pixels = getRGB(x, y, 
 531:                                 width, height, 
 532:                                 (int[])null, offset, stride);
 533:           // We already convert the color to RGB in the getRGB call, so
 534:           // we pass a simple RGB color model to the consumers.
 535:           ColorModel model = new DirectColorModel(32, 0xff0000, 0xff00, 0xff,
 536:                                                   0xff000000);
 537: 
 538:           consumers.add(ic);
 539: 
 540:       for(int i=0;i<consumers.size();i++)
 541:             {
 542:               ImageConsumer c = (ImageConsumer) consumers.elementAt(i);
 543:               c.setHints(ImageConsumer.SINGLEPASS);
 544:               c.setDimensions(getWidth(), getHeight());
 545:               c.setPixels(x, y, width, height, model, pixels, offset, stride);
 546:               c.imageComplete(ImageConsumer.STATICIMAGEDONE);
 547:             }
 548:         }
 549: 
 550:         public void requestTopDownLeftRightResend(ImageConsumer ic)
 551:         {
 552:           startProduction(ic);
 553:         }
 554: 
 555:       };
 556:   }
 557:   
 558:   public Vector getSources()
 559:   {
 560:     return null;
 561:   }
 562:   
 563:   public BufferedImage getSubimage(int x, int y, int w, int h)
 564:   {
 565:     WritableRaster subRaster = 
 566:       getRaster().createWritableChild(x, y, w, h, 0, 0, null);
 567:     
 568:     return new BufferedImage(getColorModel(),
 569:                  subRaster,
 570:                  isPremultiplied,
 571:                  properties);
 572:   }
 573: 
 574:   public Raster getTile(int tileX, int tileY)
 575:   {
 576:     return getWritableTile(tileX, tileY);
 577:   }
 578:     
 579:   public int getTileGridXOffset()
 580:   {
 581:     return 0; // according to javadocs
 582:   }
 583: 
 584:   public int getTileGridYOffset()
 585:   {
 586:     return 0; // according to javadocs
 587:   }
 588: 
 589:   public int getTileHeight()
 590:   {
 591:     return getHeight(); // image is one big tile
 592:   }
 593: 
 594:   public int getTileWidth()
 595:   {
 596:     return getWidth(); // image is one big tile
 597:   }
 598: 
 599:   public int getType()
 600:   {
 601:     return type;
 602:   }
 603: 
 604:   public int getWidth()
 605:   {
 606:     return raster.getWidth();
 607:   }
 608: 
 609:   public int getWidth(ImageObserver imageobserver)
 610:   {
 611:     return getWidth();
 612:   }
 613: 
 614:   public WritableRaster getWritableTile(int tileX, int tileY)
 615:   {
 616:     isTileWritable(tileX, tileY);  // for exception
 617:     return raster;
 618:   }
 619: 
 620:   private static final Point[] tileIndices = { new Point() };
 621:     
 622:   public Point[] getWritableTileIndices()
 623:   {
 624:     return tileIndices;
 625:   }
 626: 
 627:   public boolean hasTileWriters()
 628:   {
 629:     return true;
 630:   }
 631:   
 632:   public boolean isAlphaPremultiplied()
 633:   {
 634:     return isPremultiplied;
 635:   }
 636: 
 637:   public boolean isTileWritable(int tileX, int tileY)
 638:   {
 639:     if ((tileX != 0) || (tileY != 0))
 640:       throw new ArrayIndexOutOfBoundsException("only tile is (0,0)");
 641:     return true;
 642:   }
 643: 
 644:   public void releaseWritableTile(int tileX, int tileY)
 645:   {
 646:     isTileWritable(tileX, tileY);  // for exception
 647:   }
 648: 
 649:   //public void removeTileObserver(TileObserver tileobserver) {}
 650: 
 651:   public void setData(Raster src)
 652:   {
 653:     int x = src.getMinX();
 654:     int y = src.getMinY();
 655:     int w = src.getWidth();
 656:     int h = src.getHeight();
 657:     
 658:     // create a dest child that has the right bounds...
 659:     WritableRaster dest =
 660:       raster.createWritableChild(x, y, w, h, x, y,
 661:                  null  // same bands
 662:                  );
 663: 
 664:     if (src.getSampleModel () instanceof ComponentSampleModel
 665:         && dest.getSampleModel () instanceof ComponentSampleModel)
 666: 
 667:       // Refer to ComponentDataBlitOp for optimized data blitting:
 668:       ComponentDataBlitOp.INSTANCE.filter(src, dest);
 669:     else
 670:       {
 671:         // slower path
 672:         int samples[] = src.getPixels (x, y, w, h, (int [])null);
 673:         dest.setPixels (x, y, w, h, samples);
 674:       }
 675:   }
 676: 
 677:   public void setRGB(int x, int y, int argb)
 678:   {
 679:     Object rgbElem = colorModel.getDataElements(argb, null);
 680:     raster.setDataElements(x, y, rgbElem);
 681:   }
 682:   
 683:   public void setRGB(int startX, int startY, int w, int h,
 684:              int[] argbArray, int offset, int scanlineStride)
 685:   {
 686:     int endX = startX + w;
 687:     int endY = startY + h;
 688:     
 689:     Object rgbElem = null;
 690:     for (int y=startY; y<endY; y++)
 691:       {
 692:     int xoffset = offset;
 693:     for (int x=startX; x<endX; x++)
 694:       {
 695:         int argb = argbArray[xoffset++];
 696:         rgbElem = colorModel.getDataElements(argb, rgbElem);
 697:         raster.setDataElements(x, y, rgbElem);
 698:       }
 699:     offset += scanlineStride;    
 700:       }
 701:   }
 702:     
 703:   public String toString()
 704:   {
 705:     StringBuffer buf;
 706: 
 707:     buf = new StringBuffer(/* estimated length */ 120);
 708:     buf.append("BufferedImage@");
 709:     buf.append(Integer.toHexString(hashCode()));
 710:     buf.append(": type=");
 711:     buf.append(type);
 712:     buf.append(' ');
 713:     buf.append(colorModel);
 714:     buf.append(' ');
 715:     buf.append(raster);
 716: 
 717:     return buf.toString();
 718:   }
 719: 
 720: 
 721:   /**
 722:    * Adds a tile observer. If the observer is already present, it receives
 723:    * multiple notifications.
 724:    *
 725:    * @param to The TileObserver to add.
 726:    */
 727:   public void addTileObserver (TileObserver to)
 728:   {
 729:     if (observers == null)
 730:       observers = new Vector ();
 731:     
 732:     observers.add (to);
 733:   }
 734:     
 735:   /**
 736:    * Removes a tile observer. If the observer was not registered,
 737:    * nothing happens. If the observer was registered for multiple
 738:    * notifications, it is now registered for one fewer notification.
 739:    *
 740:    * @param to The TileObserver to remove.
 741:    */
 742:   public void removeTileObserver (TileObserver to)
 743:   {
 744:     if (observers == null)
 745:       return;
 746:     
 747:     observers.remove (to);
 748:   }
 749: 
 750:   /**
 751:    * Return the transparency type.
 752:    *
 753:    * @return One of {@link #OPAQUE}, {@link #BITMASK}, or {@link #TRANSLUCENT}.
 754:    * @see Transparency#getTransparency()
 755:    * @since 1.5
 756:    */
 757:   public int getTransparency()
 758:   {
 759:     return colorModel.getTransparency();
 760:   }
 761: }