OSDN Git Service

libjava/classpath/ChangeLog.gcj:
[pf3gnuchains/gcc-fork.git] / libjava / classpath / java / awt / image / BufferedImage.java
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.Buffers;
42 import gnu.java.awt.ClasspathGraphicsEnvironment;
43 import gnu.java.awt.ComponentDataBlitOp;
44 import gnu.java.awt.peer.gtk.CairoSurface;
45
46 import java.awt.Graphics;
47 import java.awt.Graphics2D;
48 import java.awt.GraphicsEnvironment;
49 import java.awt.Image;
50 import java.awt.Point;
51 import java.awt.Rectangle;
52 import java.awt.Transparency;
53 import java.awt.color.ColorSpace;
54 import java.util.Hashtable;
55 import java.util.Vector;
56
57 /**
58  * A buffered image always starts at coordinates (0, 0).
59  *
60  * The buffered image is not subdivided into multiple tiles. Instead,
61  * the image consists of one large tile (0,0) with the width and
62  * height of the image. This tile is always considered to be checked
63  * out.
64  * 
65  * @author Rolf W. Rasmussen (rolfwr@ii.uib.no)
66  */
67 public class BufferedImage extends Image
68   implements WritableRenderedImage, Transparency
69 {
70   public static final int TYPE_CUSTOM         =  0,
71                           TYPE_INT_RGB        =  1,
72                           TYPE_INT_ARGB       =  2,
73                           TYPE_INT_ARGB_PRE   =  3,
74                           TYPE_INT_BGR        =  4,
75                           TYPE_3BYTE_BGR      =  5,
76                           TYPE_4BYTE_ABGR     =  6,
77                           TYPE_4BYTE_ABGR_PRE =  7,
78                           TYPE_USHORT_565_RGB =  8,
79                           TYPE_USHORT_555_RGB =  9,
80                           TYPE_BYTE_GRAY      = 10,
81                           TYPE_USHORT_GRAY    = 11,
82                           TYPE_BYTE_BINARY    = 12,
83                           TYPE_BYTE_INDEXED   = 13;
84   
85   /**
86    * Vector of TileObservers (or null)
87    */
88   Vector<TileObserver> tileObservers;
89   
90   /**
91    * The image's WritableRaster
92    */
93   WritableRaster raster;
94
95   /**
96    * The associated ColorModel
97    */
98   ColorModel colorModel;
99
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     StringBuffer buf;
784
785     buf = new StringBuffer(/* 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 }