I implemented asynchronous image loading for the
GtkToolkit.createImage(URL) method. This greatly enhances performance of
the HTML browser as it can now leverage CPU vs. IO load more
intelligently. When this turns out to be stable we might want to do this
for createImage(String) too.

This is _not_ for the release branch as it is relativly experimental.

2006-12-05  Roman Kennke  <[EMAIL PROTECTED]>

        * gnu/java/awt/peer/gtk/AsyncImage.java: New class. Supports
        asynchronous loading of images.
        * gnu/java/awt/peer/gtk/CairoGraphics2D.java
        (drawImage): Fetch real image from possibly AsyncImage.
        * gnu/java/awt/peer/gtk/ComponentGraphics.java
        (drawImage): Fetch real image from possibly AsyncImage.
        * gnu/java/awt/peer/gtk/GtkToolkit.java
        (createImage(URL)): Create async image.
        (imageOrError): Made method static for easy access from AsyncImage.
        (prepareImage): For async images, register the observer to the
        image.

/Roman

Index: gnu/java/awt/peer/gtk/AsyncImage.java
===================================================================
RCS file: gnu/java/awt/peer/gtk/AsyncImage.java
diff -N gnu/java/awt/peer/gtk/AsyncImage.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnu/java/awt/peer/gtk/AsyncImage.java	5 Dec 2006 11:10:43 -0000
@@ -0,0 +1,257 @@
+/* AsyncImage.java -- Loads images asynchronously
+   Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt.peer.gtk;
+
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.image.ImageConsumer;
+import java.awt.image.ImageObserver;
+import java.awt.image.ImageProducer;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * Supports asynchronous loading of images.
+ */
+public class AsyncImage
+  extends Image
+{
+
+  /**
+   * Returned as source as long as the image is not complete.
+   */
+  private class NullImageSource
+    implements ImageProducer
+  {
+    private ArrayList consumers;
+
+    NullImageSource()
+    {
+      consumers = new ArrayList();
+    }
+
+    public void addConsumer(ImageConsumer ic)
+    {
+      consumers.add(ic);
+    }
+
+    public boolean isConsumer(ImageConsumer ic)
+    {
+      return consumers.contains(ic);
+    }
+
+    public void removeConsumer(ImageConsumer ic)
+    {
+      consumers.remove(ic);
+    }
+
+    public void requestTopDownLeftRightResend(ImageConsumer ic)
+    {
+      startProduction(ic);
+    }
+
+    public void startProduction(ImageConsumer ic)
+    {
+      consumers.add(ic);
+      for (int i = consumers.size() - 1; i >= 0; i--)
+        {
+          ImageConsumer c = (ImageConsumer) consumers.get(i);
+          c.setDimensions(1, 1);
+          ic.imageComplete(ImageConsumer.SINGLEFRAMEDONE);
+        }
+    }
+    
+  }
+
+  /**
+   * Loads the image asynchronously.
+   */
+  private class Loader
+    implements Runnable
+  {
+    private URL url;
+    Loader(URL u)
+    {
+      url = u;
+    }
+
+    public void run()
+    {
+      Image image;
+      try
+        {
+          GtkImage gtkImage = new GtkImage(url);
+          image = CairoSurface.getBufferedImage(gtkImage);
+        }
+      catch (IllegalArgumentException iae)
+        {
+          image = null;
+        }
+      realImage = GtkToolkit.imageOrError(image);
+      notifyObservers(ImageObserver.ALLBITS | ImageObserver.HEIGHT
+                      | ImageObserver.WIDTH | ImageObserver.PROPERTIES);
+    }
+  }
+
+  /**
+   * The real image. This is null as long as the image is not complete.
+   */
+  Image realImage;
+
+  private HashSet observers;
+
+  /**
+   * Creates a new AsyncImage that loads from the specified URL.
+   */
+  AsyncImage(URL url)
+  {
+    observers = new HashSet();
+    Loader l = new Loader(url);
+    Thread t = new Thread(l);
+    t.start();
+  }
+
+  public void flush()
+  {
+    // Nothing to do here.
+  }
+
+  public Graphics getGraphics()
+  {
+    Image r = realImage;
+    Graphics g = null;
+    if (r != null)
+      g = r.getGraphics(); // Should we return some dummy graphics instead?
+    return g;
+  }
+
+  public int getHeight(ImageObserver observer)
+  {
+    synchronized (observers)
+      {
+        observers.add(observer);
+      }
+    int height = 0;
+    Image r = realImage;
+    if (r != null)
+      height = r.getHeight(observer);
+    return height;
+  }
+
+  public Object getProperty(String name, ImageObserver observer)
+  {
+    synchronized (observers)
+      {
+        observers.add(observer);
+      }
+    Image r = realImage;
+    Object prop = null;
+    if (r != null)
+      prop = r.getProperty(name, observer);
+    return prop;
+  }
+
+  public ImageProducer getSource()
+  {
+    Image r = realImage;
+    ImageProducer source;
+    if (r == null)
+      source = new NullImageSource();
+    else
+      source = r.getSource();
+    return source;
+  }
+
+  public int getWidth(ImageObserver observer)
+  {
+    synchronized (observers)
+      {
+        observers.add(observer);
+      }
+    int width = 0;
+    Image r = realImage;
+    if (r != null)
+      width = r.getWidth(observer);
+    return width;
+  }
+
+  void addObserver(ImageObserver obs)
+  {
+    if (realImage != null && ! observers.contains(obs))
+      {
+        obs.imageUpdate(this, ImageObserver.WIDTH | ImageObserver.HEIGHT
+                        |ImageObserver.ALLBITS | ImageObserver.PROPERTIES,
+                        0, 0, realImage.getWidth(null),
+                        realImage.getHeight(null));
+      }
+    synchronized (observers)
+      {
+        observers.add(obs);
+      }
+  }
+
+  static Image realImage(Image img, ImageObserver obs)
+  {
+    if (img instanceof AsyncImage)
+      {
+        ((AsyncImage) img).addObserver(obs);
+        Image r = ((AsyncImage) img).realImage;
+        if (r != null)
+          img = r;
+      }
+    return img;
+  }
+
+  void notifyObservers(int status)
+  {
+    synchronized (observers)
+      {
+        Image r = realImage;
+        Iterator i = observers.iterator();
+        while (i.hasNext())
+          {
+            ImageObserver obs = (ImageObserver) i.next();
+            obs.imageUpdate(this, status, 0, 0, r.getWidth(null),
+                            r.getHeight(null));
+          }
+      }
+  }
+}
Index: gnu/java/awt/peer/gtk/CairoGraphics2D.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/CairoGraphics2D.java,v
retrieving revision 1.56
diff -u -1 -5 -r1.56 CairoGraphics2D.java
--- gnu/java/awt/peer/gtk/CairoGraphics2D.java	30 Nov 2006 18:44:44 -0000	1.56
+++ gnu/java/awt/peer/gtk/CairoGraphics2D.java	5 Dec 2006 11:10:43 -0000
@@ -1451,31 +1451,31 @@
 
     try
       {
 	invertedXform = xform.createInverse();
       }
     catch (NoninvertibleTransformException e)
       {
 	throw new ImagingOpException("Unable to invert transform "
 				     + xform.toString());
       }
 
     // Unrecognized image - convert to a BufferedImage
     // Note - this can get us in trouble when the gdk lock is re-acquired.
     // for example by VolatileImage. See ComponentGraphics for how we work
     // around this.
-    
+    img = AsyncImage.realImage(img, obs);
     if( !(img instanceof BufferedImage) )
       {
 	ImageProducer source = img.getSource();
 	if (source == null)
 	  return false;
 	img = Toolkit.getDefaultToolkit().createImage(source);
       }
 
     BufferedImage b = (BufferedImage) img;
     Raster raster;
     double[] i2u = new double[6];
     int width = b.getWidth();
     int height = b.getHeight();
     
     // If this BufferedImage has a BufferedImageGraphics object, 
Index: gnu/java/awt/peer/gtk/ComponentGraphics.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/ComponentGraphics.java,v
retrieving revision 1.22
diff -u -1 -5 -r1.22 ComponentGraphics.java
--- gnu/java/awt/peer/gtk/ComponentGraphics.java	21 Nov 2006 21:21:36 -0000	1.22
+++ gnu/java/awt/peer/gtk/ComponentGraphics.java	5 Dec 2006 11:10:43 -0000
@@ -456,30 +456,31 @@
             y += transform.getTranslateY();
             drawVolatile(component, vimg.nativePointer,
                          x, y, width, height,
                          (int) (r.getX() + transform.getTranslateX()),
                          (int) (r.getY() + transform.getTranslateY()),
                          (int) r.getWidth(),
                          (int) r.getHeight());
             return true;
           }
 	else
 	  return super.drawImage(vimg.getSnapshot(), x, y,
 				 width, height, observer);
       }
 
     BufferedImage bimg;
+    img = AsyncImage.realImage(img, observer);
     if (img instanceof BufferedImage)
       bimg = (BufferedImage) img;
     else
       {
 	ImageProducer source = img.getSource();
         if (source == null)
           return false;
         bimg = (BufferedImage) Toolkit.getDefaultToolkit().createImage(source);
       }
     return super.drawImage(bimg, x, y, width, height, observer);
   }
 
   public void setClip(Shape s)
   {
     lock();
Index: gnu/java/awt/peer/gtk/GtkToolkit.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/GtkToolkit.java,v
retrieving revision 1.96
diff -u -1 -5 -r1.96 GtkToolkit.java
--- gnu/java/awt/peer/gtk/GtkToolkit.java	23 Nov 2006 20:30:28 -0000	1.96
+++ gnu/java/awt/peer/gtk/GtkToolkit.java	5 Dec 2006 11:10:43 -0000
@@ -177,67 +177,58 @@
 	return ((GtkImage) image).checkImage (observer);
 
     if (observer != null)
       observer.imageUpdate (image, status,
                             -1, -1,
                             image.getWidth (observer),
                             image.getHeight (observer));
     
     return status;
   }
 
   /** 
    * Helper to return either a Image -- the argument -- or a
    * GtkImage with the errorLoading flag set if the argument is null.
    */
-  private Image imageOrError(Image b)
+  static Image imageOrError(Image b)
   {
     if (b == null) 
       return GtkImage.getErrorImage();
     else
       return b;
   }
 
   public Image createImage (String filename)
   {
     if (filename.length() == 0)
       return new GtkImage ();
     
     Image image;
     try
       {
 	image = CairoSurface.getBufferedImage( new GtkImage( filename ) );
       }
     catch (IllegalArgumentException iae)
       {
 	image = null;
       }
     return imageOrError(image);
   }
 
   public Image createImage (URL url)
   {
-    Image image;
-    try
-      {
-	image = CairoSurface.getBufferedImage( new GtkImage( url ) );
-      }
-    catch (IllegalArgumentException iae)
-      {
-	image = null;
-      }
-    return imageOrError(image);
+    return new AsyncImage(url);
   }
 
   public Image createImage (ImageProducer producer) 
   {
     if (producer == null)
       return null;
       
     Image image;
     try
       {
 	image = CairoSurface.getBufferedImage( new GtkImage( producer ) );
       }
     catch (IllegalArgumentException iae)
       {
 	image = null;
@@ -379,30 +370,37 @@
     return GtkClipboard.getSelectionInstance();
   }
 
   /**
    * Prepares a GtkImage. For every other kind of Image it just
    * assumes the image is already prepared for rendering.
    */
   public boolean prepareImage (Image image, int width, int height, 
 			       ImageObserver observer) 
   {
     /* GtkImages are always prepared, as long as they're loaded. */
     if (image instanceof GtkImage)
       return ((((GtkImage)image).checkImage (observer) & 
 	       ImageObserver.ALLBITS) != 0);
 
+    if (image instanceof AsyncImage)
+      {
+        AsyncImage aImg = (AsyncImage) image;
+        aImg.addObserver(observer);
+        return aImg.realImage != null;
+      }
+
     /* Assume anything else is too */
     return true;
   }
 
   public native void sync();
 
   protected void setComponentState (Component c, GtkComponentPeer cp)
   {
     /* Make the Component reflect Peer defaults */
     if (c.getForeground () == null)
       c.setForeground (cp.getForeground ());
     if (c.getBackground () == null)
       c.setBackground (cp.getBackground ());
     //        if (c.getFont () == null)
     //  	c.setFont (cp.getFont ());

Reply via email to