Add support for animated images to ImageResource.
This specifically enables using @sprite with animated .gif files.
Patch by: bobv
Review by: jgw
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5841 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/resources/client/ImageResource.java b/user/src/com/google/gwt/resources/client/ImageResource.java
index 75c2572..4a30dd7 100644
--- a/user/src/com/google/gwt/resources/client/ImageResource.java
+++ b/user/src/com/google/gwt/resources/client/ImageResource.java
@@ -35,6 +35,14 @@
@Target(ElementType.METHOD)
public @interface ImageOptions {
/**
+ * If <code>true</code>, the image will be flipped about the y-axis when
+ * {@link com.google.gwt.i18n.client.LocaleInfo#isRTL()} returns
+ * <code>true</code>. This is intended to be used by graphics that are
+ * sensitive to layout direction, such as arrows and disclosure indicators.
+ */
+ boolean flipRtl() default false;
+
+ /**
* This option affects the image bundling optimization to allow the image to
* be used with the {@link CssResource} {@code @sprite} rule where
* repetition of the image is desired.
@@ -42,14 +50,6 @@
* @see "CssResource documentation"
*/
RepeatStyle repeatStyle() default RepeatStyle.None;
-
- /**
- * If <code>true</code>, the image will be flipped along the y-axis when
- * {@link com.google.gwt.i18n.client.LocaleInfo#isRTL()} returns
- * <code>true</code>. This is intended to be used by graphics that are
- * sensitive to layout direction, such as arrows and disclosure indicators.
- */
- boolean flipRtl() default false;
}
/**
@@ -103,4 +103,9 @@
* Returns the width of the image.
*/
int getWidth();
+
+ /**
+ * Return <code>true</code> if the image contains multiple frames.
+ */
+ boolean isAnimated();
}
diff --git a/user/src/com/google/gwt/resources/client/impl/ImageResourcePrototype.java b/user/src/com/google/gwt/resources/client/impl/ImageResourcePrototype.java
index 11bcd64..6bf0136 100644
--- a/user/src/com/google/gwt/resources/client/impl/ImageResourcePrototype.java
+++ b/user/src/com/google/gwt/resources/client/impl/ImageResourcePrototype.java
@@ -23,6 +23,7 @@
*/
public class ImageResourcePrototype implements ImageResource {
+ private final boolean animated;
private final String name;
private final String url;
private final int left;
@@ -30,14 +31,18 @@
private final int width;
private final int height;
+ /**
+ * Only called by generated code.
+ */
public ImageResourcePrototype(String name, String url, int left, int top,
- int width, int height) {
+ int width, int height, boolean animated) {
this.name = name;
this.left = left;
this.top = top;
this.height = height;
this.width = width;
this.url = url;
+ this.animated = animated;
}
/**
@@ -78,4 +83,8 @@
public int getWidth() {
return width;
}
+
+ public boolean isAnimated() {
+ return animated;
+ }
}
diff --git a/user/src/com/google/gwt/resources/rg/ImageBundleBuilder.java b/user/src/com/google/gwt/resources/rg/ImageBundleBuilder.java
index 941fc68..7abe8e0 100644
--- a/user/src/com/google/gwt/resources/rg/ImageBundleBuilder.java
+++ b/user/src/com/google/gwt/resources/rg/ImageBundleBuilder.java
@@ -31,11 +31,14 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.MemoryCacheImageInputStream;
/**
* Accumulates state for the bundled image.
@@ -213,6 +216,8 @@
BufferedImage getImage();
+ BufferedImage[] getImages();
+
int getLeft();
String getName();
@@ -287,7 +292,7 @@
private boolean hasBeenPositioned;
private final int height, width;
- private final BufferedImage image;
+ private final BufferedImage[] images;
private int left, top;
private final String name;
private final AffineTransform transform = new AffineTransform();
@@ -299,7 +304,7 @@
this.name = other.getName();
this.height = other.getHeight();
this.width = other.getWidth();
- this.image = other.getImage();
+ this.images = other.getImages();
this.left = other.getLeft();
this.top = other.getTop();
setTransform(other.getTransform());
@@ -307,17 +312,28 @@
public ImageRect(String name, BufferedImage image) {
this.name = name;
- this.image = image;
+ this.images = new BufferedImage[] {image};
this.width = image.getWidth();
this.height = image.getHeight();
}
+ public ImageRect(String name, BufferedImage[] images) {
+ this.name = name;
+ this.images = images;
+ this.width = images[0].getWidth();
+ this.height = images[0].getHeight();
+ }
+
public int getHeight() {
return height;
}
public BufferedImage getImage() {
- return image;
+ return images[0];
+ }
+
+ public BufferedImage[] getImages() {
+ return images;
}
public int getLeft() {
@@ -344,6 +360,10 @@
return hasBeenPositioned;
}
+ public boolean isAnimated() {
+ return images.length > 1;
+ }
+
public void setPosition(int left, int top) {
hasBeenPositioned = true;
this.left = left;
@@ -603,10 +623,40 @@
logger = logger.branch(TreeLogger.TRACE,
"Adding image '" + imageName + "'", null);
- BufferedImage image;
+ BufferedImage image = null;
// Load the image
try {
- image = ImageIO.read(imageUrl);
+ String path = imageUrl.getPath();
+ String suffix = path.substring(path.lastIndexOf('.') + 1);
+
+ /*
+ * ImageIO uses an SPI pattern API. We don't care about the particulars of
+ * the implementation, so just choose the first ImageReader.
+ */
+ Iterator<ImageReader> it = ImageIO.getImageReadersBySuffix(suffix);
+ if (it.hasNext()) {
+ ImageReader reader = it.next();
+
+ reader.setInput(new MemoryCacheImageInputStream(imageUrl.openStream()));
+
+ int numImages = reader.getNumImages(true);
+ if (numImages == 0) {
+ // Fall through
+
+ } else if (numImages == 1) {
+ image = reader.read(0);
+
+ } else {
+ // Read all contained images
+ BufferedImage[] images = new BufferedImage[numImages];
+ for (int i = 0; i < numImages; i++) {
+ images[i] = reader.read(i);
+ }
+
+ ImageRect rect = new ImageRect(imageName, images);
+ throw new UnsuitableForStripException(rect);
+ }
+ }
} catch (IllegalArgumentException iex) {
if (imageName.toLowerCase().endsWith("png")
&& iex.getMessage() != null
@@ -625,7 +675,7 @@
throw iex;
}
} catch (IOException e) {
- logger.log(TreeLogger.ERROR, "Unable to read image resource", null);
+ logger.log(TreeLogger.ERROR, "Unable to read image resource", e);
throw new UnableToCompleteException();
}
diff --git a/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java b/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java
index e9c38a5..b0df671 100644
--- a/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java
@@ -86,7 +86,7 @@
+ urlExpressions[1] + " : " + urlExpressions[0] + ",");
}
sw.println(rect.getLeft() + ", " + rect.getTop() + ", " + rect.getWidth()
- + ", " + rect.getHeight());
+ + ", " + rect.getHeight() + ", " + rect.isAnimated());
sw.outdent();
sw.print(")");
@@ -224,9 +224,17 @@
} catch (UnsuitableForStripException e) {
// Add the image to the output as a separate resource
rect = e.getImageRect();
- byte[] imageBytes = ImageBundleBuilder.toPng(logger, rect);
- String urlExpression = context.deploy(rect.getName() + ".png",
- "image/png", imageBytes, false);
+
+ String urlExpression;
+ if (rect.isAnimated()) {
+ // Can't re-encode animated images, so we emit it as-is
+ urlExpression = context.deploy(resource, false);
+ } else {
+ // Re-encode the image as a PNG to strip random header data
+ byte[] imageBytes = ImageBundleBuilder.toPng(logger, rect);
+ urlExpression = context.deploy(rect.getName() + ".png", "image/png",
+ imageBytes, false);
+ }
urlsByExternalImageRect.put(rect, new String[] {urlExpression, null});
}
diff --git a/user/test/com/google/gwt/resources/client/ImageResourceTest.java b/user/test/com/google/gwt/resources/client/ImageResourceTest.java
index f16ba77..f209e68 100644
--- a/user/test/com/google/gwt/resources/client/ImageResourceTest.java
+++ b/user/test/com/google/gwt/resources/client/ImageResourceTest.java
@@ -31,6 +31,9 @@
*/
public class ImageResourceTest extends GWTTestCase {
static interface Resources extends ClientBundle {
+ @Source("animated.gif")
+ ImageResource animated();
+
@Source("16x16.png")
ImageResource i16x16();
@@ -74,6 +77,21 @@
return "com.google.gwt.resources.Resources";
}
+ public void testAnimated() {
+ Resources r = GWT.create(Resources.class);
+
+ ImageResource a = r.animated();
+
+ assertTrue(a.isAnimated());
+ assertEquals(16, a.getWidth());
+ assertEquals(16, a.getHeight());
+ assertEquals(0, a.getLeft());
+ assertEquals(0, a.getTop());
+
+ // Make sure the animated image is encoded separately
+ assertFalse(a.getURL().equals(r.i16x16().getURL()));
+ }
+
public void testDedup() {
Resources r = GWT.create(Resources.class);
diff --git a/user/test/com/google/gwt/resources/client/animated.gif b/user/test/com/google/gwt/resources/client/animated.gif
new file mode 100644
index 0000000..a0e7007
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/animated.gif
Binary files differ