Source for java.awt.image.BufferedImage

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