001/* BufferedImage.java --
002   Copyright (C) 2000, 2002, 2003, 2004, 2005, 2006,  Free Software Foundation
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.awt.image;
040
041import gnu.java.awt.Buffers;
042import gnu.java.awt.ClasspathGraphicsEnvironment;
043import gnu.java.awt.ComponentDataBlitOp;
044import gnu.java.lang.CPStringBuilder;
045
046import java.awt.Graphics;
047import java.awt.Graphics2D;
048import java.awt.GraphicsEnvironment;
049import java.awt.Image;
050import java.awt.Point;
051import java.awt.Rectangle;
052import java.awt.Transparency;
053import java.awt.color.ColorSpace;
054import java.util.Hashtable;
055import java.util.Vector;
056
057/**
058 * A buffered image always starts at coordinates (0, 0).
059 *
060 * The buffered image is not subdivided into multiple tiles. Instead,
061 * the image consists of one large tile (0,0) with the width and
062 * height of the image. This tile is always considered to be checked
063 * out.
064 *
065 * @author Rolf W. Rasmussen (rolfwr@ii.uib.no)
066 */
067public class BufferedImage extends Image
068  implements WritableRenderedImage, Transparency
069{
070  public static final int TYPE_CUSTOM         =  0,
071                          TYPE_INT_RGB        =  1,
072                          TYPE_INT_ARGB       =  2,
073                          TYPE_INT_ARGB_PRE   =  3,
074                          TYPE_INT_BGR        =  4,
075                          TYPE_3BYTE_BGR      =  5,
076                          TYPE_4BYTE_ABGR     =  6,
077                          TYPE_4BYTE_ABGR_PRE =  7,
078                          TYPE_USHORT_565_RGB =  8,
079                          TYPE_USHORT_555_RGB =  9,
080                          TYPE_BYTE_GRAY      = 10,
081                          TYPE_USHORT_GRAY    = 11,
082                          TYPE_BYTE_BINARY    = 12,
083                          TYPE_BYTE_INDEXED   = 13;
084
085  /**
086   * Vector of TileObservers (or null)
087   */
088  Vector<TileObserver> tileObservers;
089
090  /**
091   * The image's WritableRaster
092   */
093  WritableRaster raster;
094
095  /**
096   * The associated ColorModel
097   */
098  ColorModel colorModel;
099
100  /**
101   * The image's properties (or null)
102   */
103  Hashtable properties;
104
105  /**
106   * Whether alpha is premultiplied
107   */
108  boolean isPremultiplied;
109
110  /**
111   * The predefined type, if any.
112   */
113  int type;
114
115  /**
116   * Creates a new <code>BufferedImage</code> with the specified width, height
117   * and type.  Valid <code>type</code> values are:
118   *
119   * <ul>
120   *   <li>{@link #TYPE_INT_RGB}</li>
121   *   <li>{@link #TYPE_INT_ARGB}</li>
122   *   <li>{@link #TYPE_INT_ARGB_PRE}</li>
123   *   <li>{@link #TYPE_INT_BGR}</li>
124   *   <li>{@link #TYPE_3BYTE_BGR}</li>
125   *   <li>{@link #TYPE_4BYTE_ABGR}</li>
126   *   <li>{@link #TYPE_4BYTE_ABGR_PRE}</li>
127   *   <li>{@link #TYPE_USHORT_565_RGB}</li>
128   *   <li>{@link #TYPE_USHORT_555_RGB}</li>
129   *   <li>{@link #TYPE_BYTE_GRAY}</li>
130   *   <li>{@link #TYPE_USHORT_GRAY}</li>
131   *   <li>{@link #TYPE_BYTE_BINARY}</li>
132   *   <li>{@link #TYPE_BYTE_INDEXED}</li>
133   * </ul>
134   *
135   * @param width the width (must be > 0).
136   * @param height the height (must be > 0).
137   * @param type  the image type (see the list of valid types above).
138   *
139   * @throws IllegalArgumentException if <code>width</code> or
140   *     <code>height</code> is less than or equal to zero.
141   * @throws IllegalArgumentException if <code>type</code> is not one of the
142   *     specified values.
143   */
144  public BufferedImage(int width, int height, int type)
145  {
146    SampleModel sm = null;
147    ColorModel cm = null;
148    boolean premultiplied = (type == BufferedImage.TYPE_INT_ARGB_PRE
149                            || type == BufferedImage.TYPE_4BYTE_ABGR_PRE);
150
151    switch( type )
152      {
153      case BufferedImage.TYPE_INT_RGB:
154        sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT,
155                                               width, height,
156                                               new int[]{ 0x00FF0000,
157                                                          0x0000FF00,
158                                                          0x000000FF } ) ;
159        cm = new DirectColorModel( 24, 0xff0000, 0xff00, 0xff );
160        break;
161
162      case BufferedImage.TYPE_3BYTE_BGR:
163        sm = new PixelInterleavedSampleModel( DataBuffer.TYPE_BYTE,
164                                              width, height,
165                                              3, width * 3,
166                                              new int[]{ 2, 1, 0 } );
167        cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
168                                     false, false,
169                                     BufferedImage.OPAQUE,
170                                     DataBuffer.TYPE_BYTE);
171        break;
172
173      case BufferedImage.TYPE_INT_ARGB:
174      case BufferedImage.TYPE_INT_ARGB_PRE:
175        sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT,
176                                               width, height,
177                                               new int[]{ 0x00FF0000,
178                                                          0x0000FF00,
179                                                          0x000000FF,
180                                                          0xFF000000 } );
181        if (premultiplied)
182          cm = new DirectColorModel( ColorSpace.getInstance(ColorSpace.CS_sRGB),
183                                     32, 0xff0000, 0xff00, 0xff, 0xff000000,
184                                     true,
185                                     Buffers.smallestAppropriateTransferType(32));
186        else
187          cm = new DirectColorModel( 32, 0xff0000, 0xff00, 0xff, 0xff000000 );
188
189        break;
190
191      case BufferedImage.TYPE_4BYTE_ABGR:
192      case BufferedImage.TYPE_4BYTE_ABGR_PRE:
193        sm = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE,
194                                             width, height,
195                                             4, 4*width,
196                                             new int[]{3, 2, 1, 0});
197        cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
198                                     true, premultiplied,
199                                     BufferedImage.TRANSLUCENT,
200                                     DataBuffer.TYPE_BYTE);
201        break;
202
203      case BufferedImage.TYPE_INT_BGR:
204        sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT,
205                                               width, height,
206                                               new int[]{ 0x000000FF,
207                                                          0x0000FF00,
208                                                          0x00FF0000 } ) ;
209        cm = new DirectColorModel( 24, 0xff, 0xff00, 0xff0000 );
210        break;
211
212      case BufferedImage.TYPE_USHORT_565_RGB:
213        sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_USHORT,
214                                               width, height,
215                                               new int[]{ 0xF800,
216                                                          0x7E0,
217                                                          0x1F } ) ;
218        cm = new DirectColorModel( 16, 0xF800, 0x7E0, 0x1F );
219        break;
220
221      case BufferedImage.TYPE_USHORT_555_RGB:
222        sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_USHORT,
223                                               width, height,
224                                               new int[]{ 0x7C00,
225                                                          0x3E0,
226                                                          0x1F } ) ;
227        cm = new DirectColorModel( 15, 0x7C00, 0x3E0, 0x1F );
228        break;
229
230      case BufferedImage.TYPE_BYTE_INDEXED:
231        cm = createDefaultIndexedColorModel( false );
232
233      case BufferedImage.TYPE_BYTE_GRAY:
234        sm = new PixelInterleavedSampleModel( DataBuffer.TYPE_BYTE,
235                                              width, height,
236                                              1, width, new int[]{ 0 } );
237        break;
238
239      case BufferedImage.TYPE_USHORT_GRAY:
240        sm = new PixelInterleavedSampleModel( DataBuffer.TYPE_USHORT,
241                                              width, height,
242                                              1, width, new int[]{ 0 } );
243        break;
244
245      case BufferedImage.TYPE_BYTE_BINARY:
246        cm = createDefaultIndexedColorModel( true );
247        sm = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
248                                             width, height, 1);
249        break;
250
251      default:
252        sm = null;
253      }
254
255    if( sm == null )
256      throw new IllegalArgumentException("Unknown predefined image type.");
257
258    if( cm == null ) // only for the grayscale types
259      {
260        int buftype;
261        int[] bits = new int[1];
262        if( type == BufferedImage.TYPE_BYTE_GRAY )
263          {
264            buftype = DataBuffer.TYPE_BYTE;
265            bits[0] = 8;
266          }
267        else
268          {
269            buftype = DataBuffer.TYPE_USHORT;
270            bits[0] = 16;
271          }
272        ColorSpace graySpace = ColorSpace.getInstance( ColorSpace.CS_GRAY );
273
274        cm = new ComponentColorModel( graySpace, bits, false, false,
275                                      Transparency.OPAQUE, buftype );
276      }
277
278    WritableRaster rst = null;
279
280    // Attempt to create an accelerated backend for this image
281    GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
282    if (env instanceof ClasspathGraphicsEnvironment)
283      rst = ((ClasspathGraphicsEnvironment)env).createRaster(cm, sm);
284
285    // Default to a standard Java raster & databuffer if needed
286    if (rst == null)
287      rst = Raster.createWritableRaster(sm, new Point( 0, 0 ) );
288
289    init(cm, rst, premultiplied,
290         null, // no properties
291         type );
292  }
293
294  public BufferedImage(int w, int h, int type, IndexColorModel indexcolormodel)
295  {
296    if ((type != TYPE_BYTE_BINARY) && (type != TYPE_BYTE_INDEXED))
297      throw new IllegalArgumentException("Type must be TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED");
298    if( indexcolormodel.getMapSize() > 16 && type == TYPE_BYTE_BINARY )
299      throw new IllegalArgumentException("Type TYPE_BYTE_BINARY cannot have a larger than 16-color palette.");
300    if( indexcolormodel.getMapSize() > 256 )
301      throw new IllegalArgumentException("Byte type cannot have a larger than 256-color palette.");
302
303    init( indexcolormodel,
304          indexcolormodel.createCompatibleWritableRaster(w, h),
305          indexcolormodel.isAlphaPremultiplied(),
306          null, // no properties
307          type );
308  }
309
310  public BufferedImage(ColorModel colormodel, WritableRaster writableraster,
311                       boolean premultiplied, Hashtable<?,?> properties)
312  {
313    init(colormodel, writableraster, premultiplied, properties, TYPE_CUSTOM);
314  }
315
316
317  private void init(ColorModel cm, WritableRaster writableraster,
318                    boolean premultiplied, Hashtable properties, int type)
319  {
320    raster = writableraster;
321    colorModel = cm;
322    this.properties = properties;
323    isPremultiplied = premultiplied;
324    this.type = type;
325  }
326
327  /**
328   * Creates the default palettes for the predefined indexed color types
329   * (256-color or black-and-white)
330   *
331   * @param binary - If <code>true</code>, a black and white palette,
332   * otherwise a default 256-color palette is returned.
333   */
334  private IndexColorModel createDefaultIndexedColorModel( boolean binary )
335  {
336    if( binary )
337      {
338        byte[] t = new byte[]{ 0, (byte)255 };
339        return new IndexColorModel( 1, 2, t, t, t );
340      }
341
342    byte[] r = new byte[256];
343    byte[] g = new byte[256];
344    byte[] b = new byte[256];
345
346    int index = 0;
347    for( int i = 0; i < 6; i++ )
348      for( int j = 0; j < 6; j++ )
349        for( int k = 0; k < 6; k++ )
350          {
351            r[ index ] = (byte)(i * 51);
352            g[ index ] = (byte)(j * 51);
353            b[ index ] = (byte)(k * 51);
354            index++;
355          }
356
357    while( index < 256 )
358      {
359        r[ index ] = g[ index ] = b[ index ] =
360          (byte)(18 + (index - 216) * 6);
361        index++;
362      }
363
364    return new IndexColorModel( 8, 256, r, g, b );
365  }
366
367  public void coerceData(boolean premultiplied)
368  {
369    colorModel = colorModel.coerceData(raster, premultiplied);
370    isPremultiplied = premultiplied;
371  }
372
373  public WritableRaster copyData(WritableRaster dest)
374  {
375    if (dest == null)
376      dest = raster.createCompatibleWritableRaster(getMinX(), getMinY(),
377                                                   getWidth(),getHeight());
378
379    int x = dest.getMinX();
380    int y = dest.getMinY();
381    int w = dest.getWidth();
382    int h = dest.getHeight();
383
384    // create a src child that has the right bounds...
385    WritableRaster src =
386      raster.createWritableChild(x, y, w, h, x, y,
387                                 null);  // same bands
388
389    if (src.getSampleModel () instanceof ComponentSampleModel
390        && dest.getSampleModel () instanceof ComponentSampleModel)
391      // Refer to ComponentDataBlitOp for optimized data blitting:
392      ComponentDataBlitOp.INSTANCE.filter(src, dest);
393
394    else
395      {
396        // slower path
397        int samples[] = src.getPixels (x, y, w, h, (int [])null);
398        dest.setPixels (x, y, w, h, samples);
399      }
400    return dest;
401  }
402
403  public Graphics2D createGraphics()
404  {
405    GraphicsEnvironment env;
406    env = GraphicsEnvironment.getLocalGraphicsEnvironment ();
407    return env.createGraphics (this);
408  }
409
410  public void flush()
411  {
412  }
413
414  public WritableRaster getAlphaRaster()
415  {
416    return colorModel.getAlphaRaster(raster);
417  }
418
419  public ColorModel getColorModel()
420  {
421    return colorModel;
422  }
423
424  public Raster getData()
425  {
426    return copyData(null);
427    /* TODO: this might be optimized by returning the same
428       raster (not writable) as long as image data doesn't change. */
429  }
430
431  public Raster getData(Rectangle rectangle)
432  {
433    WritableRaster dest =
434      raster.createCompatibleWritableRaster(rectangle);
435    return copyData(dest);
436  }
437
438  public Graphics getGraphics()
439  {
440    return createGraphics();
441  }
442
443  public int getHeight()
444  {
445    return raster.getHeight();
446  }
447
448  public int getHeight(ImageObserver imageobserver)
449  {
450    return getHeight();
451  }
452
453  public int getMinTileX()
454  {
455    return 0;
456  }
457
458  public int getMinTileY()
459  {
460    return 0;
461  }
462
463  public int getMinX()
464  {
465    return 0;
466  }
467
468  public int getMinY()
469  {
470    return 0;
471  }
472
473  public int getNumXTiles()
474  {
475    return 1;
476  }
477
478  public int getNumYTiles()
479  {
480        return 1;
481  }
482
483  /**
484   * Returns the value of the specified property, or
485   * {@link Image#UndefinedProperty} if the property is not defined.
486   *
487   * @param string  the property key (<code>null</code> not permitted).
488   *
489   * @return The property value.
490   *
491   * @throws NullPointerException if <code>string</code> is <code>null</code>.
492   */
493  public Object getProperty(String string)
494  {
495    if (string == null)
496      throw new NullPointerException("The property name cannot be null.");
497    Object result = Image.UndefinedProperty;
498    if (properties != null)
499      {
500        Object v = properties.get(string);
501        if (v != null)
502          result = v;
503      }
504    return result;
505  }
506
507  public Object getProperty(String string, ImageObserver imageobserver)
508  {
509    return getProperty(string);
510  }
511
512  /**
513   * Returns <code>null</code> always.
514   *
515   * @return <code>null</code> always.
516   */
517  public String[] getPropertyNames()
518  {
519    // This method should always return null, see:
520    // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4640609
521    return null;
522  }
523
524  public int getRGB(int x, int y)
525  {
526    Object rgbElem = raster.getDataElements(x, y, null);
527    return colorModel.getRGB(rgbElem);
528  }
529
530  public int[] getRGB(int startX, int startY, int w, int h, int[] rgbArray,
531                      int offset, int scanlineStride)
532  {
533    if (rgbArray == null)
534      {
535        /*
536              000000000000000000
537              00000[#######-----   [ = start
538              -----########-----   ] = end
539              -----#######]00000
540              000000000000000000
541        */
542        int size = (h-1)*scanlineStride + w;
543        rgbArray = new int[size];
544    }
545
546    int endX = startX + w;
547    int endY = startY + h;
548
549    /* *TODO*:
550       Opportunity for optimization by examining color models...
551
552       Perhaps wrap the rgbArray up in a WritableRaster with packed
553       sRGB color model and perform optimized rendering into the
554       array. */
555
556    Object rgbElem = null;
557    for (int y=startY; y<endY; y++)
558      {
559        int xoffset = offset;
560        for (int x=startX; x<endX; x++)
561          {
562            int rgb;
563            rgbElem = raster.getDataElements(x, y, rgbElem);
564            rgb = colorModel.getRGB(rgbElem);
565            rgbArray[xoffset++] = rgb;
566          }
567        offset += scanlineStride;
568      }
569    return rgbArray;
570  }
571
572  public WritableRaster getRaster()
573  {
574    return raster;
575  }
576
577  public SampleModel getSampleModel()
578  {
579    return raster.getSampleModel();
580  }
581
582  public ImageProducer getSource()
583  {
584    return new ImageProducer()
585      {
586        Vector<ImageConsumer> consumers = new Vector<ImageConsumer>();
587
588        public void addConsumer(ImageConsumer ic)
589        {
590          if(!consumers.contains(ic))
591            consumers.add(ic);
592        }
593
594        public boolean isConsumer(ImageConsumer ic)
595        {
596          return consumers.contains(ic);
597        }
598
599        public void removeConsumer(ImageConsumer ic)
600        {
601          consumers.remove(ic);
602        }
603
604        public void startProduction(ImageConsumer ic)
605        {
606          int x = 0;
607          int y = 0;
608          int width = getWidth();
609          int height = getHeight();
610          int stride = width;
611          int offset = 0;
612          int[] pixels = getRGB(x, y,
613                                width, height,
614                                (int[])null, offset, stride);
615          // We already convert the color to RGB in the getRGB call, so
616          // we pass a simple RGB color model to the consumers.
617          ColorModel model = new DirectColorModel(32, 0xff0000, 0xff00, 0xff,
618                                                  0xff000000);
619
620          consumers.add(ic);
621
622          for(int i = 0; i < consumers.size(); i++)
623            {
624              ImageConsumer c = consumers.elementAt(i);
625              c.setHints(ImageConsumer.SINGLEPASS);
626              c.setDimensions(getWidth(), getHeight());
627              c.setPixels(x, y, width, height, model, pixels, offset, stride);
628              c.imageComplete(ImageConsumer.STATICIMAGEDONE);
629            }
630        }
631
632        public void requestTopDownLeftRightResend(ImageConsumer ic)
633        {
634          startProduction(ic);
635        }
636
637      };
638  }
639
640  public Vector<RenderedImage> getSources()
641  {
642    return null;
643  }
644
645  public BufferedImage getSubimage(int x, int y, int w, int h)
646  {
647    WritableRaster subRaster =
648      getRaster().createWritableChild(x, y, w, h, 0, 0, null);
649
650    return new BufferedImage(getColorModel(), subRaster, isPremultiplied,
651                             properties);
652  }
653
654  public Raster getTile(int tileX, int tileY)
655  {
656    return getWritableTile(tileX, tileY);
657  }
658
659  public int getTileGridXOffset()
660  {
661    return 0; // according to javadocs
662  }
663
664  public int getTileGridYOffset()
665  {
666    return 0; // according to javadocs
667  }
668
669  public int getTileHeight()
670  {
671    return getHeight(); // image is one big tile
672  }
673
674  public int getTileWidth()
675  {
676    return getWidth(); // image is one big tile
677  }
678
679  public int getType()
680  {
681    return type;
682  }
683
684  public int getWidth()
685  {
686    return raster.getWidth();
687  }
688
689  public int getWidth(ImageObserver imageobserver)
690  {
691    return getWidth();
692  }
693
694  public WritableRaster getWritableTile(int tileX, int tileY)
695  {
696    isTileWritable(tileX, tileY);  // for exception
697    return raster;
698  }
699
700  private static final Point[] tileIndices = { new Point() };
701
702  public Point[] getWritableTileIndices()
703  {
704    return tileIndices;
705  }
706
707  public boolean hasTileWriters()
708  {
709    return true;
710  }
711
712  public boolean isAlphaPremultiplied()
713  {
714    return isPremultiplied;
715  }
716
717  public boolean isTileWritable(int tileX, int tileY)
718  {
719    if ((tileX != 0) || (tileY != 0))
720      throw new ArrayIndexOutOfBoundsException("only tile is (0,0)");
721    return true;
722  }
723
724  public void releaseWritableTile(int tileX, int tileY)
725  {
726    isTileWritable(tileX, tileY);  // for exception
727  }
728
729  //public void removeTileObserver(TileObserver tileobserver) {}
730
731  public void setData(Raster src)
732  {
733    int x = src.getMinX();
734    int y = src.getMinY();
735    int w = src.getWidth();
736    int h = src.getHeight();
737
738    // create a dest child that has the right bounds...
739    WritableRaster dest =
740      raster.createWritableChild(x, y, w, h, x, y, null);
741
742    if (src.getSampleModel () instanceof ComponentSampleModel
743        && dest.getSampleModel () instanceof ComponentSampleModel)
744
745      // Refer to ComponentDataBlitOp for optimized data blitting:
746      ComponentDataBlitOp.INSTANCE.filter(src, dest);
747    else
748      {
749        // slower path
750        int samples[] = src.getPixels (x, y, w, h, (int [])null);
751        dest.setPixels (x, y, w, h, samples);
752      }
753  }
754
755  public void setRGB(int x, int y, int argb)
756  {
757    Object rgbElem = colorModel.getDataElements(argb, null);
758    raster.setDataElements(x, y, rgbElem);
759  }
760
761  public void setRGB(int startX, int startY, int w, int h,
762                     int[] argbArray, int offset, int scanlineStride)
763  {
764    int endX = startX + w;
765    int endY = startY + h;
766
767    Object rgbElem = null;
768    for (int y=startY; y<endY; y++)
769      {
770        int xoffset = offset;
771        for (int x=startX; x<endX; x++)
772          {
773            int argb = argbArray[xoffset++];
774            rgbElem = colorModel.getDataElements(argb, rgbElem);
775            raster.setDataElements(x, y, rgbElem);
776          }
777        offset += scanlineStride;
778      }
779  }
780
781  public String toString()
782  {
783    CPStringBuilder buf;
784
785    buf = new CPStringBuilder(/* estimated length */ 120);
786    buf.append("BufferedImage@");
787    buf.append(Integer.toHexString(hashCode()));
788    buf.append(": type=");
789    buf.append(type);
790    buf.append(' ');
791    buf.append(colorModel);
792    buf.append(' ');
793    buf.append(raster);
794
795    return buf.toString();
796  }
797
798
799  /**
800   * Adds a tile observer. If the observer is already present, it receives
801   * multiple notifications.
802   *
803   * @param to The TileObserver to add.
804   */
805  public void addTileObserver (TileObserver to)
806  {
807    if (tileObservers == null)
808      tileObservers = new Vector<TileObserver>();
809
810    tileObservers.add (to);
811  }
812
813  /**
814   * Removes a tile observer. If the observer was not registered,
815   * nothing happens. If the observer was registered for multiple
816   * notifications, it is now registered for one fewer notification.
817   *
818   * @param to The TileObserver to remove.
819   */
820  public void removeTileObserver (TileObserver to)
821  {
822    if (tileObservers == null)
823      return;
824
825    tileObservers.remove (to);
826  }
827
828  /**
829   * Return the transparency type.
830   *
831   * @return One of {@link #OPAQUE}, {@link #BITMASK}, or {@link #TRANSLUCENT}.
832   * @see Transparency#getTransparency()
833   * @since 1.5
834   */
835  public int getTransparency()
836  {
837    return colorModel.getTransparency();
838  }
839}