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

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