001    /* BufferedImage.java --
002       Copyright (C) 2000, 2002, 2003, 2004, 2005, 2006,  Free Software Foundation
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.awt.image;
040    
041    import gnu.java.awt.Buffers;
042    import gnu.java.awt.ClasspathGraphicsEnvironment;
043    import gnu.java.awt.ComponentDataBlitOp;
044    import gnu.java.awt.peer.gtk.CairoSurface;
045    
046    import java.awt.Graphics;
047    import java.awt.Graphics2D;
048    import java.awt.GraphicsEnvironment;
049    import java.awt.Image;
050    import java.awt.Point;
051    import java.awt.Rectangle;
052    import java.awt.Transparency;
053    import java.awt.color.ColorSpace;
054    import java.util.Hashtable;
055    import 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     */
067    public 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        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    }