My favorites | Sign in
Google
                
Search
for
Updated Aug 30, 2009 by fredsa
ImageBundleDesign  
ImageBundle Generator, Image Clipping, and Image Request Batching

Automatic Compile-Time Image Bundling

Bruce Johnson, Rajeev Dayal

Introduction

Making AJAX apps very fast requires minimizing the number of HTTP round-trips. Images, particularly icon-style images, are a major cause of multiple small requests. What's worse, such image requests typically happen at startup, which creates a sluggish startup experience for the end user, working against our first design axiom. That's no good, but fortunately GWT has the machinery to create a highly-optimized solution to this recurring problem.

Motivation

Why optimize treatment of images?

Use Case: Toolbar

Toolbars containing image (or "icon") buttons are commonly needed, and they often have these characteristics:

At startup, each toolbar icon typically causes an HTTP freshness check (i.e. "If-Modified-Since"), which has several disadvantages:

Goals

Non-Goals

Solution

With a few tweaks to the existing Image class, the addition of a deferred binding generator and a ImageBundle tag interface, we can achieve all of the goals above in a way that actually simplifies life for GWT developers.

ImageBundleGenerator and ImageBundle

The two key types are ImageBundleGenerator and ImageBundle.

Additional Documentation

The GWT 1.6 javadoc contains additional ImageBundle documentation:

http://google-web-toolkit.googlecode.com/svn/javadoc/1.6/com/google/gwt/user/client/ui/ImageBundle.html

Defining an Image Bundle

This is a similar design to the Constants interface. ImageBundle is an empty tag interface that can be extended to create custom image bundles.

Derived interfaces contain zero or more methods, each of which must have

  • A return type of AbstractImagePrototype
  • No parameters
  • An optional annotation that specifies @Resource that names an image file in the module's classpath (should this be the public path instead?). If @Resource is not specified, then a file whose base filename matches the method name itself is sought (the file extension can be gif, png, or jpg, but if multiple such files are present, which file is chosen is unspecified in the current implementation).

For example, to create an image bundle for a word processor toolbar, you could define an image bundle as follows:

interface WordProcessorImageBundle extends ImageBundle {

  /**
   * Would match either file 'newFileIcon.gif' or 'newFileIcon.png' in the same package as this type. Note that other file extensions may also be recognized.
   */
  AbstractImagePrototype newFileIcon();

  /**
   * Would bundle the file 'open-file-icon.gif' residing in the same package as this type.
   */
  @Resource("open-file-icon.gif")
  AbstractImagePrototype openFileIcon();

  /**
   * Would bundle the file 'savefile.gif' residing in the package 'org.example.icons'.
   */
  @Resource("org/example/icons/savefile.gif")
  AbstractImagePrototype saveFileIcon();
}

Using an Image Bundle

Create the image bundle object using GWT.create() as would for any other object subject to deferred binding. For example,

  WordProcessImageBundle wpib = 
    (WordProcessImageBundle)GWT.create(WordProcessImageBundle.class);
  Toolbar tb = new Toolbar();
  tb.addImageButton(wpib.newFileIcon());  
  tb.addImageButton(wpib.openFileIcon());  
  tb.addImageButton(wpib.saveFileIcon());  

Configuring Your Module for Image Bundles

To use image bundling, your module needs to explicitly inherit ImageBundle.

Behavior/Restrictions/Caveats

For the ImageBundle-compatible type T specified in GWT.create(T.class), the following must be true:

The generated image bundle output file will
  • have same type "png"; files referenced via @Resource will be converted
  • be named md5.cache.ext, where md5 is a hash of the bytes in all the consistuent images and ext is the image file type
  • be written into the output directory for the module (that is, the same location into which compiled JS is written

Clipping Constructor for Image

To make the new image bundling functionality work well with the existing Image class, a new "clipped" mode is introduced, allowing an image to refer to a sub-image within a larger image. There are a variety of formulations of this idea, but the one that should have the least impact is to

A clipped image might appear like this in the DOM:

  <img src="clear.cache.gif" style="background-image:md5.cache.ext; background-position:-Xpx -Ypx; width:Wpx; height:Hpx">

The other more obvious implementation technique requires you to encase the in a

, then set "overflow:hidden", width and height on the
. However, this adds weight and makes the DOM structure for Image less predictable to client code. Some browsers (not naming any names, but...Internet Explorer) may require extra weirdness to support transparency.

Caveats

Image Request Batching

TBD -- IE6 has a bug when the same image URL is requested multiple times from code that causes it to issue all the requests instead of realizing a single result image can be shared. We could solve this through code in Image or in the DOM.

Implications

Design Goals Achieved

How does this approach address the goals?

Interaction With Localization

Image bundles are (in the current implementation) orthogonal to localization and do not use localization concepts directly. It is possible to localize image bundles using a locale-specific factory.

Suppose that we have the following ImageBundle:

public interface MyImageBundle extends ImageBundle {
   /**  
    * The default icon if no locale-specific image is specified.
    */
   @Resource("help_icon.gif")
   AbstractImagePrototype helpIcon();


   /**  
    * The default icon if no locale-specific image is specified.
    */
   @Resource("compose_new_message_icon.gif")
   AbstractImagePrototype composeNewMessageIcon();
}

We can define French and English variations of each ImageBundle image by extending MyImageBundle for each locale:

public interface MyImageBundle_en extends MyImageBundle {
   // Note that we are not re-declaring helpIcon(), so this bundle
   // uses the inherited metadata.

   /**
    * The English version of this icon.
    */
   @Resource("compose_new_message_icon_en.gif")
   AbstractImagePrototype composeNewMessageIcon();
}

public interface MyImageBundle_fr extends MyImageBundle {
   /**
    * The French version of this icon.
    */
   @Resource("help_icon_fr.gif")
   AbstractImagePrototype helpIcon();

   /**
    * The French version of this icon.
    */
   @Resource("compose_new_message_icon_fr.gif")
   AbstractImagePrototype composeNewMessageIcon();
}

By extending Localizable, we can create a locale-sensitive factory to select the appropriate MyImageBundle-derived interface based on the user's locale.

public interface MyImageBundleFactory extends Localizable {
    MyImageBundle createImageBundle();
}

public class MyImageBundleFactory_en implements MyImageBundleFactory {
    MyImageBundle createImageBundle() {
        return (MyImageBundle) GWT.create(MyImageBundle_en.class);
    }
}

public class MyImageBundleFactory_fr implements MyImageBundleFactory {
    MyImageBundle createImageBundle() {
        return (MyImageBundle) GWT.create(MyImageBundle_fr.class);
    }
}

The application code looks something like the following:

// Create a locale-specific MyImageBundleFactory.
MyImageBundleFactory myImageBundleFactory = (MyImageBundleFactory) GWT.create(MyImageBundleFactory.class);

// This will return a locale-specific MyImageBundle, since we are using a locale-specific
// factory to create it.
MyImageBundle myImageBundle = myImageBundleFactory.createImageBundle();

// Get the image prototype for the icon we are interested in.
AbstractImagePrototype helpIconProto = myImageBundle.helpIcon();

// Create an Image object from the prototype.
somePanel.add(helpIcon.createImage());

Comment by b.q.mulder, Aug 07, 2007

Begin quote: public class MyImageBundleFactory?_en extends MyImageBundleFactory? {

MyImageBundle? createImageBundle() {
return (MyImageBundle?) GWT.create(MyImageBundle?_en.class);
}
} : End Quote

This cannot be correct. A class cannot extend an interface. The class should implement the interface.

Comment by gwt.team.bruce, Aug 14, 2007

@b.q.mulder: thanks for catching that. updated.

Comment by t.weit...@web.de, Sep 17, 2007

It's not working for me as described above. Look here: http://code.google.com/p/google-web-toolkit/issues/detail?id=1666.

Comment by rehak.michal, Sep 23, 2007

is this approach (<img src="clear.cache.gif" style="background-image:md5.cache.ext; background-position:-Xpx -Ypx; width:Wpx; height:Hpx">) used in version 1.4.60? because in IE DOM I can see filter: progid:DXImageTransform.Microsoft.AlphaImageLoader?(src='md5.cache.ext',sizingMethod='crop')

Comment by elmar.sonnenschein, Sep 24, 2007

Why is it necessary to introduce separate factory classes for localization? I've not tried it yet but wouldn't it be sufficient to let the application's base ImageBundle? interfaces extend Localizable instead and then let GWT.create() choose the correct localized variant?

Comment by rkoshlyak, Jan 24, 2009

ImageBundle? for css, is there are any way to use ImageBundle? for style?

Comment by mark.renouf, Feb 10, 2009
Comment by Nitin.sawant2008, Mar 24, 2009

GWT is really Great!! I'll soon develop WebSoftwareApp? on it! thanx to gooogle

Comment by salvin.f...@gmail.com, May 09, 2009

Question : I have created a AbstractImagePrototype? image with a png image, how do i use it as a Background ???? i wish thre was a setBackground function

following code does not work: //myImages is an interface implementing ImageBundle?

Image menuShadow=myImages.menuShadowTile().createImage(); DOM.setStyleAttribute(simplePanel.getElement(), "background-image","url('"+menuShadow.getUrl()+"')");

Comment by zh.chinese, Aug 26, 2009

so i will change into chinese

Comment by ice.tweety, Nov 12, 2009

Hello Salvin,

did you get it meantime? DOM.setStyleAttribute(simplePanel.getElement(), "background-image","url('"+menuShadow.getUrl()+"')");

I need it too.

This also won't work: DOM.setStyleAttribute(this.getElement(), "backgroundImage", "chili.jpg");

Comment by ice.tweety, Nov 12, 2009

Sorry, got it right yet:

DOM.setStyleAttribute(this.getElement(), "backgroundImage", "url(chili.jpg)");

is it!


Sign in to add a comment