Source for gnu.java.awt.peer.gtk.GtkImage

   1: /* GtkImage.java
   2:    Copyright (C) 2005, 2006 Free Software Foundation, Inc.
   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 gnu.java.awt.peer.gtk;
  40: 
  41: import java.awt.Graphics;
  42: import java.awt.Image;
  43: import java.awt.image.ColorModel;
  44: import java.awt.image.DirectColorModel;
  45: import java.awt.image.MemoryImageSource;
  46: import java.awt.image.ImageObserver;
  47: import java.awt.image.ImageProducer;
  48: import java.io.File;
  49: import java.io.IOException;
  50: import java.util.Hashtable;
  51: import java.util.Vector;
  52: import java.io.ByteArrayOutputStream;
  53: import java.io.BufferedInputStream;
  54: import java.net.URL;
  55: import gnu.classpath.Pointer;
  56: 
  57: /**
  58:  * GtkImage - wraps a GdkPixbuf.
  59:  *
  60:  * A GdkPixbuf is 'on-screen' and the gdk cannot draw to it,
  61:  * this is used for the other constructors (and other createImage methods), and
  62:  * corresponds to the Image implementations returned by the Toolkit.createImage
  63:  * methods, and is basically immutable.
  64:  *
  65:  * @author Sven de Marothy
  66:  */
  67: public class GtkImage extends Image
  68: {
  69:   int width = -1, height = -1;
  70: 
  71:   /**
  72:    * Properties.
  73:    */
  74:   Hashtable<?,?> props;
  75: 
  76:   /**
  77:    * Loaded or not flag, for asynchronous compatibility.
  78:    */
  79:   boolean isLoaded;
  80: 
  81:   /**
  82:    * Pointer to the GdkPixbuf -
  83:    * don't change the name without changing the native code.
  84:    */
  85:   Pointer pixbuf;
  86: 
  87:   /**
  88:    * Observer queue.
  89:    */
  90:   Vector<ImageObserver> observers;
  91: 
  92:   /**
  93:    * Error flag for loading.
  94:    */
  95:   boolean errorLoading;
  96: 
  97:   /**
  98:    * Original source, if created from an ImageProducer.
  99:    */
 100:   ImageProducer source;
 101: 
 102:   /*
 103:    * The 32-bit AABBGGRR format the GDK uses.
 104:    */
 105:   static ColorModel nativeModel = new DirectColorModel(32,
 106:                                                        0x000000FF,
 107:                                                        0x0000FF00,
 108:                                                        0x00FF0000,
 109:                                                        0xFF000000);
 110: 
 111:   /**
 112:    * The singleton GtkImage that is returned on errors by GtkToolkit.
 113:    */
 114:   private static GtkImage errorImage;
 115: 
 116:   /**
 117:    * Lock that should be held for all gdkpixbuf operations. We don't use
 118:    * the global gdk_threads_enter/leave functions in most places since
 119:    * most gdkpixbuf operations can be done in parallel to drawing and
 120:    * manipulating gtk widgets.
 121:    */
 122:   static Object pixbufLock = new Object();
 123: 
 124:   /**
 125:    * Allocate a PixBuf from a given ARGB32 buffer pointer.
 126:    */
 127:   private native void initFromBuffer( long bufferPointer );
 128: 
 129:   /**
 130:    * Returns a copy of the pixel data as a java array.
 131:    * Should be called with the pixbufLock held.
 132:    */
 133:   native int[] getPixels();
 134: 
 135:   /**
 136:    * Sets the pixel data from a java array.
 137:    * Should be called with the pixbufLock held.
 138:    */
 139:   private native void setPixels(int[] pixels);
 140: 
 141:   /**
 142:    * Loads an image using gdk-pixbuf from a file.
 143:    * Should be called with the pixbufLock held.
 144:    */
 145:   private native boolean loadPixbuf(String name);
 146: 
 147:   /**
 148:    * Loads an image using gdk-pixbuf from data.
 149:    * Should be called with the pixbufLock held.
 150:    */
 151:   private native boolean loadImageFromData(byte[] data);
 152: 
 153:   /**
 154:    * Allocates a Gtk Pixbuf
 155:    * Should be called with the pixbufLock held.
 156:    */
 157:   private native void createPixbuf();
 158: 
 159:   /**
 160:    * Frees the above.
 161:    * Should be called with the pixbufLock held.
 162:    */
 163:   private native void freePixbuf();
 164: 
 165:   /**
 166:    * Sets the pixbuf to scaled copy of src image. hints are rendering hints.
 167:    * Should be called with the pixbufLock held.
 168:    */
 169:   private native void createScaledPixbuf(GtkImage src, int hints);
 170: 
 171:   /**
 172:    * Constructs a GtkImage from an ImageProducer. Asynchronity is handled in
 173:    * the following manner:
 174:    * A GtkImageConsumer gets the image data, and calls setImage() when
 175:    * completely finished. The GtkImage is not considered loaded until the
 176:    * GtkImageConsumer is completely finished. We go for all "all or nothing".
 177:    */
 178:   public GtkImage (ImageProducer producer)
 179:   {
 180:     isLoaded = false;
 181:     observers = new Vector<ImageObserver>();
 182:     source = producer;
 183:     errorLoading = false;
 184:     source.startProduction(new GtkImageConsumer(this, source));
 185:   }
 186: 
 187:   /**
 188:    * Constructs a blank GtkImage.  This is called when
 189:    * GtkToolkit.createImage (String) is called with an empty string
 190:    * argument ("").  A blank image is loaded immediately upon
 191:    * construction and has width -1 and height -1.
 192:    */
 193:   public GtkImage ()
 194:   {
 195:     isLoaded = true;
 196:     observers = null;
 197:     props = new Hashtable<String,Object>();
 198:     errorLoading = false;
 199:   }
 200: 
 201:   /**
 202:    * Constructs a GtkImage by loading a given file.
 203:    *
 204:    * @throws IllegalArgumentException if the image could not be loaded.
 205:    */
 206:   public GtkImage (String filename)
 207:   {
 208:     File f = new File(filename);
 209:     try
 210:       {
 211:         String path = f.getCanonicalPath();
 212:         synchronized(pixbufLock)
 213:           {
 214:             if (loadPixbuf(f.getCanonicalPath()) != true)
 215:               throw new IllegalArgumentException("Couldn't load image: "
 216:                                                  + filename);
 217:           }
 218:       }
 219:     catch(IOException e)
 220:       {
 221:         IllegalArgumentException iae;
 222:         iae = new IllegalArgumentException("Couldn't load image: "
 223:                                            + filename);
 224:         iae.initCause(e);
 225:         throw iae;
 226:       }
 227: 
 228:     isLoaded = true;
 229:     observers = null;
 230:     props = new Hashtable<String,Object>();
 231:   }
 232: 
 233:   /**
 234:    * Constructs a GtkImage from a byte array of an image file.
 235:    *
 236:    * @throws IllegalArgumentException if the image could not be
 237:    * loaded.
 238:    */
 239:   public GtkImage (byte[] data)
 240:   {
 241:     synchronized(pixbufLock)
 242:       {
 243:         if (loadImageFromData (data) != true)
 244:           throw new IllegalArgumentException ("Couldn't load image.");
 245:       }
 246: 
 247:     isLoaded = true;
 248:     observers = null;
 249:     props = new Hashtable<String,Object>();
 250:     errorLoading = false;
 251:   }
 252: 
 253:   /**
 254:    * Constructs a GtkImage from a URL. May result in an error image.
 255:    */
 256:   public GtkImage (URL url)
 257:   {
 258:     isLoaded = false;
 259:     observers = new Vector<ImageObserver>();
 260:     errorLoading = false;
 261:     if( url == null)
 262:       return;
 263:     ByteArrayOutputStream baos = new ByteArrayOutputStream (5000);
 264:     try
 265:       {
 266:         BufferedInputStream bis = new BufferedInputStream (url.openStream());
 267: 
 268:         byte[] buf = new byte[5000];
 269:         int n = 0;
 270: 
 271:         while ((n = bis.read(buf)) != -1)
 272:           baos.write(buf, 0, n);
 273:         bis.close();
 274:       }
 275:     catch(IOException e)
 276:       {
 277:         throw new IllegalArgumentException ("Couldn't load image.");
 278:       }
 279:     byte[] array = baos.toByteArray();
 280:     synchronized(pixbufLock)
 281:       {
 282:         if (loadImageFromData(array) != true)
 283:           throw new IllegalArgumentException ("Couldn't load image.");
 284:       }
 285: 
 286:     isLoaded = true;
 287:     observers = null;
 288:     props = new Hashtable<String,Object>();
 289:   }
 290: 
 291:   /**
 292:    * Constructs a scaled version of the src bitmap, using the GDK.
 293:    */
 294:   private GtkImage (GtkImage src, int width, int height, int hints)
 295:   {
 296:     this.width = width;
 297:     this.height = height;
 298:     props = new Hashtable<String,Object>();
 299:     isLoaded = true;
 300:     observers = null;
 301: 
 302:     // Use the GDK scaling method.
 303:     synchronized(pixbufLock)
 304:       {
 305:         createScaledPixbuf(src, hints);
 306:       }
 307:   }
 308: 
 309:   /**
 310:    * Package private constructor to create a GtkImage from a given
 311:    * PixBuf pointer.
 312:    */
 313:   GtkImage (Pointer pixbuf)
 314:   {
 315:     this.pixbuf = pixbuf;
 316:     synchronized(pixbufLock)
 317:       {
 318:         createFromPixbuf();
 319:       }
 320:     isLoaded = true;
 321:     observers = null;
 322:     props = new Hashtable<String,Object>();
 323:   }
 324: 
 325:   /**
 326:    * Wraps a buffer with a GtkImage.
 327:    *
 328:    * @param bufferPointer a pointer to an ARGB32 buffer
 329:    */
 330:   GtkImage(int width, int height, long bufferPointer)
 331:   {
 332:     this.width = width;
 333:     this.height = height;
 334:     props = new Hashtable<String,Object>();
 335:     isLoaded = true;
 336:     observers = null;
 337:     initFromBuffer( bufferPointer );
 338:   }
 339: 
 340:   /**
 341:    * Returns an empty GtkImage with the errorLoading flag set.
 342:    * Called from GtkToolKit when some error occured, but an image needs
 343:    * to be returned anyway.
 344:    */
 345:   static synchronized GtkImage getErrorImage()
 346:   {
 347:     if (errorImage == null)
 348:       {
 349:         errorImage = new GtkImage();
 350:         errorImage.errorLoading = true;
 351:       }
 352:     return errorImage;
 353:   }
 354: 
 355:   /**
 356:    * Native helper function for constructor that takes a pixbuf Pointer.
 357:    * Should be called with the pixbufLock held.
 358:    */
 359:   private native void createFromPixbuf();
 360: 
 361:   /**
 362:    * Callback from the image consumer.
 363:    */
 364:   public void setImage(int width, int height,
 365:                        int[] pixels, Hashtable<?,?> properties)
 366:   {
 367:     this.width = width;
 368:     this.height = height;
 369:     props = (properties != null) ? properties : new Hashtable<String,Object>();
 370: 
 371:     if (width <= 0 || height <= 0 || pixels == null)
 372:       {
 373:         errorLoading = true;
 374:         return;
 375:       }
 376: 
 377:     synchronized(pixbufLock)
 378:       {
 379:         createPixbuf();
 380:         setPixels(pixels);
 381:       }
 382:     isLoaded = true;
 383:     deliver();
 384:   }
 385: 
 386:   // java.awt.Image methods ////////////////////////////////////////////////
 387: 
 388:   public synchronized int getWidth (ImageObserver observer)
 389:   {
 390:     if (addObserver(observer))
 391:       return -1;
 392: 
 393:     return width;
 394:   }
 395: 
 396:   public synchronized int getHeight (ImageObserver observer)
 397:   {
 398:     if (addObserver(observer))
 399:       return -1;
 400: 
 401:     return height;
 402:   }
 403: 
 404:   public synchronized Object getProperty (String name, ImageObserver observer)
 405:   {
 406:     if (addObserver(observer))
 407:       return UndefinedProperty;
 408: 
 409:     Object value = props.get (name);
 410:     return (value == null) ? UndefinedProperty : value;
 411:   }
 412: 
 413:   /**
 414:    * Returns the source of this image.
 415:    */
 416:   public ImageProducer getSource ()
 417:   {
 418:     if (!isLoaded)
 419:       return null;
 420: 
 421:     int[] pixels;
 422:     synchronized (pixbufLock)
 423:       {
 424:         if (!errorLoading)
 425:           pixels = getPixels();
 426:         else
 427:           return null;
 428:       }
 429:     return new MemoryImageSource(width, height, nativeModel, pixels,
 430:                                  0, width);
 431:   }
 432: 
 433:   /**
 434:    * Does nothing. Should not be called.
 435:    */
 436:   public Graphics getGraphics ()
 437:   {
 438:     throw new IllegalAccessError("This method only works for off-screen"
 439:                                  +" Images.");
 440:   }
 441: 
 442:   /**
 443:    * Returns a scaled instance of this pixbuf.
 444:    */
 445:   public Image getScaledInstance(int width,
 446:                                  int height,
 447:                                  int hints)
 448:   {
 449:     if (width <= 0 || height <= 0)
 450:       throw new IllegalArgumentException("Width and height of scaled bitmap"
 451:                                          + "must be >= 0");
 452: 
 453:     return new GtkImage(this, width, height, hints);
 454:   }
 455: 
 456:   /**
 457:    * If the image is loaded and comes from an ImageProducer,
 458:    * regenerate the image from there.
 459:    *
 460:    * I have no idea if this is ever actually used. Since GtkImage can't be
 461:    * instantiated directly, how is the user to know if it was created from
 462:    * an ImageProducer or not?
 463:    */
 464:   public synchronized void flush ()
 465:   {
 466:     if (isLoaded && source != null)
 467:       {
 468:         observers = new Vector<ImageObserver>();
 469:         isLoaded = false;
 470:         synchronized(pixbufLock)
 471:           {
 472:             freePixbuf();
 473:           }
 474:         source.startProduction(new GtkImageConsumer(this, source));
 475:       }
 476:   }
 477: 
 478:   public void finalize()
 479:   {
 480:     if (isLoaded)
 481:       {
 482:         synchronized(pixbufLock)
 483:           {
 484:             freePixbuf();
 485:           }
 486:       }
 487:   }
 488: 
 489:   /**
 490:    * Returns the image status, used by GtkToolkit
 491:    */
 492:   public int checkImage (ImageObserver observer)
 493:   {
 494:     if (addObserver(observer))
 495:       {
 496:         if (errorLoading == true)
 497:           return ImageObserver.ERROR;
 498:         else
 499:           return 0;
 500:       }
 501: 
 502:     return ImageObserver.ALLBITS | ImageObserver.WIDTH | ImageObserver.HEIGHT;
 503:   }
 504: 
 505: 
 506:   // Private methods ////////////////////////////////////////////////
 507: 
 508:   /**
 509:    * Delivers notifications to all queued observers.
 510:    */
 511:   private void deliver()
 512:   {
 513:     int flags = ImageObserver.HEIGHT |
 514:       ImageObserver.WIDTH |
 515:       ImageObserver.PROPERTIES |
 516:       ImageObserver.ALLBITS;
 517: 
 518:     if (observers != null)
 519:       for(int i=0; i < observers.size(); i++)
 520:         ((ImageObserver)observers.elementAt(i)).imageUpdate(this, flags, 0, 0,
 521:                                                             width, height);
 522: 
 523:     observers = null;
 524:   }
 525: 
 526:   /**
 527:    * Adds an observer, if we need to.
 528:    * @return true if an observer was added.
 529:    */
 530:   private boolean addObserver(ImageObserver observer)
 531:   {
 532:     if (!isLoaded)
 533:       {
 534:         if(observer != null)
 535:           if (!observers.contains (observer))
 536:             observers.addElement (observer);
 537:         return true;
 538:       }
 539:     return false;
 540:   }
 541: }