Source for java.awt.image.BufferedImage

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