Source for java.awt.image.BufferedImage

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