Major media subsystem revamp:

- All images are loaded through the image manager
- Architected to allow the use of prepared or unprepared images (and
  volatile images when the day comes that they support alpha)
- Tunable caches for images and tiles
- Resource manager caches unpacked resources on the filesystem
- Various and sundry other cleanups


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@2116 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2003-01-13 22:49:47 +00:00
parent 8743be2131
commit 100aa3ace3
46 changed files with 1602 additions and 888 deletions
@@ -1,16 +1,16 @@
// //
// $Id: BobbleAnimation.java,v 1.5 2002/11/20 02:18:49 mdb Exp $ // $Id: BobbleAnimation.java,v 1.6 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.animation; package com.threerings.media.animation;
import java.awt.Color; import java.awt.Color;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle; import java.awt.Rectangle;
import com.threerings.util.RandomUtil; import com.threerings.util.RandomUtil;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.image.Mirage;
/** /**
* An animation that bobbles an image around within a specific horizontal * An animation that bobbles an image around within a specific horizontal
@@ -29,10 +29,10 @@ public class BobbleAnimation extends Animation
* @param duration the time to animate in milliseconds. * @param duration the time to animate in milliseconds.
*/ */
public BobbleAnimation ( public BobbleAnimation (
Image image, int sx, int sy, int rx, int ry, int duration) Mirage image, int sx, int sy, int rx, int ry, int duration)
{ {
super(new Rectangle(sx - rx, sy - ry, sx + image.getWidth(null) + rx, super(new Rectangle(sx - rx, sy - ry, sx + image.getWidth() + rx,
sy + image.getHeight(null) + ry)); sy + image.getHeight() + ry));
// save things off // save things off
_image = image; _image = image;
@@ -72,7 +72,7 @@ public class BobbleAnimation extends Animation
// documentation inherited // documentation inherited
public void paint (Graphics2D gfx) public void paint (Graphics2D gfx)
{ {
gfx.drawImage(_image, _x, _y, null); _image.paint(gfx, _x, _y);
} }
// documentation inherited // documentation inherited
@@ -98,7 +98,7 @@ public class BobbleAnimation extends Animation
protected int _sx, _sy; protected int _sx, _sy;
/** The image to animate. */ /** The image to animate. */
protected Image _image; protected Mirage _image;
/** Animation ending timing information. */ /** Animation ending timing information. */
protected long _duration, _end; protected long _duration, _end;
@@ -1,5 +1,5 @@
// //
// $Id: ExplodeAnimation.java,v 1.13 2002/10/01 04:24:34 shaper Exp $ // $Id: ExplodeAnimation.java,v 1.14 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.animation; package com.threerings.media.animation;
@@ -7,14 +7,14 @@ import java.awt.AlphaComposite;
import java.awt.Color; import java.awt.Color;
import java.awt.Composite; import java.awt.Composite;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Shape; import java.awt.Shape;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.threerings.util.RandomUtil;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.util.RandomUtil; import com.threerings.media.image.Mirage;
/** /**
* An animation that displays an object exploding into chunks, fading out * An animation that displays an object exploding into chunks, fading out
@@ -88,7 +88,7 @@ public class ExplodeAnimation extends Animation
* @param height the height of the object. * @param height the height of the object.
*/ */
public ExplodeAnimation ( public ExplodeAnimation (
Image image, ExplodeInfo info, int x, int y, int width, int height) Mirage image, ExplodeInfo info, int x, int y, int width, int height)
{ {
super(info.bounds); super(info.bounds);
@@ -233,7 +233,7 @@ public class ExplodeAnimation extends Animation
if (_image != null) { if (_image != null) {
// draw the image chunk // draw the image chunk
gfx.clipRect(-_hcwid, -_hchei, _cwid, _chei); gfx.clipRect(-_hcwid, -_hchei, _cwid, _chei);
gfx.drawImage(_image, -_hcwid + xoff, -_hchei + yoff, null); _image.paint(gfx, -_hcwid + xoff, -_hchei + yoff);
} else { } else {
// draw the color chunk // draw the color chunk
@@ -303,7 +303,7 @@ public class ExplodeAnimation extends Animation
protected Color _color; protected Color _color;
/** The image to animate if we're using an image. */ /** The image to animate if we're using an image. */
protected Image _image; protected Mirage _image;
/** The starting animation time. */ /** The starting animation time. */
protected long _start; protected long _start;
@@ -1,5 +1,5 @@
// //
// $Id: FadeAnimation.java,v 1.5 2002/10/01 04:24:34 shaper Exp $ // $Id: FadeAnimation.java,v 1.6 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.animation; package com.threerings.media.animation;
@@ -7,10 +7,10 @@ import java.awt.AlphaComposite;
import java.awt.Color; import java.awt.Color;
import java.awt.Composite; import java.awt.Composite;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle; import java.awt.Rectangle;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.image.Mirage;
/** /**
* An animation that displays an image fading from one alpha level to * An animation that displays an image fading from one alpha level to
@@ -30,9 +30,9 @@ public class FadeAnimation extends Animation
* @param target the target alpha level. * @param target the target alpha level.
*/ */
public FadeAnimation ( public FadeAnimation (
Image image, int x, int y, float alpha, float step, float target) Mirage image, int x, int y, float alpha, float step, float target)
{ {
super(new Rectangle(x, y, image.getWidth(null), image.getHeight(null))); super(new Rectangle(x, y, image.getWidth(), image.getHeight()));
// save things off // save things off
_image = image; _image = image;
@@ -90,7 +90,7 @@ public class FadeAnimation extends Animation
} else { } else {
gfx.setComposite(_comp); gfx.setComposite(_comp);
} }
gfx.drawImage(_image, _x, _y, null); _image.paint(gfx, _x, _y);
gfx.setComposite(ocomp); gfx.setComposite(ocomp);
} }
@@ -126,7 +126,7 @@ public class FadeAnimation extends Animation
protected int _x, _y; protected int _x, _y;
/** The image to animate. */ /** The image to animate. */
protected Image _image; protected Mirage _image;
/** The starting animation time. */ /** The starting animation time. */
protected long _start; protected long _start;
@@ -1,20 +1,20 @@
// //
// $Id: SparkAnimation.java,v 1.1 2002/11/27 01:37:14 shaper Exp $ // $Id: SparkAnimation.java,v 1.2 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.animation; package com.threerings.media.animation;
import java.awt.AlphaComposite; import java.awt.AlphaComposite;
import java.awt.Composite; import java.awt.Composite;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.util.Arrays; import java.util.Arrays;
import com.threerings.media.animation.Animation;
import com.threerings.util.RandomUtil; import com.threerings.util.RandomUtil;
import com.threerings.media.animation.Animation;
import com.threerings.media.image.Mirage;
/** /**
* Displays a set of spark images originating from a specified position * Displays a set of spark images originating from a specified position
* and flying outward in random directions, fading out as they go, for a * and flying outward in random directions, fading out as they go, for a
@@ -41,7 +41,7 @@ public class SparkAnimation extends Animation
public SparkAnimation (Rectangle bounds, int x, int y, int jog, public SparkAnimation (Rectangle bounds, int x, int y, int jog,
float minxvel, float minyvel, float minxvel, float minyvel,
float xvel, float yvel, float xvel, float yvel,
float g, Image[] images, long delay) float g, Mirage[] images, long delay)
{ {
super(bounds); super(bounds);
@@ -141,7 +141,7 @@ public class SparkAnimation extends Animation
gfx.setComposite(_comp); gfx.setComposite(_comp);
// draw all sparks // draw all sparks
for (int ii = 0; ii < _icount; ii++) { for (int ii = 0; ii < _icount; ii++) {
gfx.drawImage(_images[ii], _xpos[ii], _ypos[ii], null); _images[ii].paint(gfx, _xpos[ii], _ypos[ii]);
} }
// restore the original composite // restore the original composite
gfx.setComposite(ocomp); gfx.setComposite(ocomp);
@@ -158,7 +158,7 @@ public class SparkAnimation extends Animation
} }
/** The spark images we're animating. */ /** The spark images we're animating. */
protected Image[] _images; protected Mirage[] _images;
/** The number of images we're animating. */ /** The number of images we're animating. */
protected int _icount; protected int _icount;
@@ -0,0 +1,58 @@
//
// $Id: BackedVolatileMirage.java,v 1.1 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.image;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import com.threerings.media.Log;
/**
* Provides a volatile mirage that is backed by a buffered image that is
* not obtained from the image manager but is instead provided at
* construct time and completely circumvents the image manager's cache. As
* such, this should not be used unless you know what you're doing.
*/
public class BackedVolatileMirage extends VolatileMirage
{
/**
* Creates a mirage with the supplied regeneration informoation and
* prepared image.
*/
public BackedVolatileMirage (ImageManager imgr, BufferedImage source)
{
super(imgr, new Rectangle(0, 0, source.getWidth(), source.getHeight()));
_source = source;
// create our volatile image for the first time
createVolatileImage();
}
// documentation inherited
protected void refreshVolatileImage ()
{
Graphics gfx = null;
try {
gfx = _image.getGraphics();
gfx.drawImage(_source, -_bounds.x, -_bounds.y, null);
} catch (Exception e) {
Log.warning("Failure refreshing mirage " + this + ".");
Log.logStackTrace(e);
} finally {
gfx.dispose();
}
}
// documentation inherited
protected void toString (StringBuffer buf)
{
super.toString(buf);
buf.append(", src=").append(_source);
}
protected BufferedImage _source;
}
@@ -0,0 +1,53 @@
//
// $Id: BlankMirage.java,v 1.1 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.image;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
/**
* A mirage implementation that contains no image data. Generally only
* useful for testing.
*/
public class BlankMirage implements Mirage
{
public BlankMirage (int width, int height)
{
_width = width;
_height = height;
}
// documentation inherited from interface
public void paint (Graphics2D gfx, int x, int y)
{
// nothing doing
}
// documentation inherited from interface
public int getWidth ()
{
return _width;
}
// documentation inherited from interface
public int getHeight ()
{
return _height;
}
// documentation inherited from interface
public boolean hitTest (int x, int y)
{
return false;
}
// documentation inherited from interface
public BufferedImage getSnapshot ()
{
return null;
}
protected int _width;
protected int _height;
}
@@ -0,0 +1,50 @@
//
// $Id: BufferedMirage.java,v 1.1 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.image;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
/**
* A simple mirage implementation that uses a buffered image.
*/
public class BufferedMirage implements Mirage
{
public BufferedMirage (BufferedImage image)
{
_image = image;
}
// documentation inherited from interface
public void paint (Graphics2D gfx, int x, int y)
{
gfx.drawImage(_image, x, y, null);
}
// documentation inherited from interface
public int getWidth ()
{
return _image.getWidth();
}
// documentation inherited from interface
public int getHeight ()
{
return _image.getHeight();
}
// documentation inherited from interface
public boolean hitTest (int x, int y)
{
return ImageUtil.hitTest(_image, x, y);
}
// documentation inherited from interface
public BufferedImage getSnapshot ()
{
return _image;
}
protected BufferedImage _image;
}
@@ -0,0 +1,78 @@
//
// $Id: CachedVolatileMirage.java,v 1.1 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.image;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import com.threerings.media.Log;
import com.samskivert.util.StringUtil;
/**
* A mirage implementation which allows the image to be maintained in
* video memory and refetched from the image manager in the event that our
* target screen resolution changes or we are flushed from video memory
* for some other reason.
*
* <p> These objects are never created directly, but always obtained from
* the {@link ImageManager}.
*/
public class CachedVolatileMirage extends VolatileMirage
{
/**
* Creates a mirage with the supplied regeneration informoation and
* prepared image.
*/
protected CachedVolatileMirage (
ImageManager imgr, ImageManager.ImageKey source,
Rectangle bounds, Colorization[] zations)
{
super(imgr, bounds);
_source = source;
_zations = zations;
// create our volatile image for the first time
createVolatileImage();
}
/**
* Rerenders our volatile image from the its source image data.
*/
protected void refreshVolatileImage ()
{
Graphics gfx = null;
try {
BufferedImage source = _imgr.getImage(_source, _zations);
gfx = _image.getGraphics();
gfx.drawImage(source, -_bounds.x, -_bounds.y, null);
} catch (Exception e) {
Log.warning("Failure refreshing mirage " + this + ".");
Log.logStackTrace(e);
} finally {
if (gfx != null) {
gfx.dispose();
}
}
}
// documentation inherited
protected void toString (StringBuffer buf)
{
super.toString(buf);
buf.append(", key=").append(_source);
buf.append(", zations=").append(_zations);
}
/** The key that identifies the image data used to create our volatile
* image. */
protected ImageManager.ImageKey _source;
/** Optional colorizations that are applied to our source image when
* creating our mirage. */
protected Colorization[] _zations;
}
@@ -1,5 +1,5 @@
// //
// $Id: Colorization.java,v 1.6 2003/01/08 04:09:02 mdb Exp $ // $Id: Colorization.java,v 1.7 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.image; package com.threerings.media.image;
@@ -100,6 +100,12 @@ public class Colorization
return true; return true;
} }
// documentation inherited
public int hashCode ()
{
return colorizationId ^ rootColor.hashCode();
}
/** /**
* Compares this colorization to another based on id. * Compares this colorization to another based on id.
*/ */
@@ -0,0 +1,29 @@
//
// $Id: ImageDataProvider.java,v 1.1 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.image;
import java.io.IOException;
import javax.imageio.stream.ImageInputStream;
/**
* Provides access to image data for the image with the specified
* path. Images loaded from different data providers (which are
* differentiated by reference equality) will be considered distinct
* images with respect to caching.
*/
public interface ImageDataProvider
{
/**
* Returns a string identifier for this image data provider which wil
* be used to differentiate it from other providers and thus should be
* unique.
*/
public String getIdent ();
/**
* Returns an input stream from which the image data for the specified
* image may be loaded.
*/
public ImageInputStream loadImageData (String path) throws IOException;
}
@@ -1,13 +1,22 @@
// //
// $Id: ImageIOLoader.java,v 1.5 2003/01/08 04:09:02 mdb Exp $ // $Id: ImageIOLoader.java,v 1.6 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.image; package com.threerings.media.image;
import java.awt.Image; import java.awt.Image;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Iterator;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import com.samskivert.util.StringUtil;
import java.io.RandomAccessFile;
import com.threerings.media.Log;
/** /**
* Loads images using the <code>ImageIO</code> services provided by J2SE * Loads images using the <code>ImageIO</code> services provided by J2SE
@@ -20,6 +29,16 @@ public class ImageIOLoader implements ImageLoader
// we need to reference ImageIO in the constructor to force the // we need to reference ImageIO in the constructor to force the
// classloader to attempt to load the ImageIO classes // classloader to attempt to load the ImageIO classes
ImageIO.setUseCache(true); ImageIO.setUseCache(true);
// String[] fmts = ImageIO.getReaderFormatNames();
// Log.info("Got formats " + StringUtil.toString(fmts) + ".");
Iterator iter = ImageIO.getImageReadersByFormatName("png");
if (iter.hasNext()) {
_reader = (ImageReader)iter.next();
} else {
Log.warning("Aiya! No reader.");
}
} }
// documentation inherited // documentation inherited
@@ -29,10 +48,18 @@ public class ImageIOLoader implements ImageLoader
// this seems to choke when decoding the compressed image data // this seems to choke when decoding the compressed image data
// which may mean it's a JDK bug or something, but I'd like to see // which may mean it's a JDK bug or something, but I'd like to see
// it resolved so that the image manager will work on applets // it resolved so that the image manager will work on applets
//
// ImageInputStream iis = new MemoryCacheImageInputStream(source);
// return ImageIO.read(iis);
// ImageInputStream iis = null;
// // // iis = ImageIO.createImageInputStream(source);
// iis = new MemoryCacheImageInputStream(source);
// // // Log.info("Created stream " + iis + "/" + source);
// _reader.setInput(iis, true, false);
// return _reader.read(0);
// return ImageIO.read(new MemoryCacheImageInputStream(source));
return ImageIO.read(source); return ImageIO.read(source);
} }
protected ImageReader _reader;
} }
@@ -1,21 +1,37 @@
// //
// $Id: ImageManager.java,v 1.34 2003/01/08 05:17:35 ray Exp $ // $Id: ImageManager.java,v 1.35 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.image; package com.threerings.media.image;
import java.awt.Component; import java.awt.Component;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Image; import java.awt.GraphicsConfiguration;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import com.sun.imageio.plugins.png.PNGImageReaderSpi;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import com.samskivert.io.NestableIOException; import com.samskivert.io.NestableIOException;
import com.samskivert.util.ConfigUtil;
import com.samskivert.util.LRUHashMap; import com.samskivert.util.LRUHashMap;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Throttle; import com.samskivert.util.Throttle;
import com.samskivert.util.Tuple;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.resource.ResourceManager; import com.threerings.resource.ResourceManager;
@@ -25,6 +41,45 @@ import com.threerings.resource.ResourceManager;
*/ */
public class ImageManager public class ImageManager
{ {
/**
* Used to identify an image for caching and reconstruction.
*/
public static class ImageKey
{
/** The data provider from which this image's data is loaded. */
public ImageDataProvider daprov;
/** The path used to identify the image to the data provider. */
public String path;
protected ImageKey (ImageDataProvider daprov, String path)
{
this.daprov = daprov;
this.path = path;
}
public int hashCode ()
{
return path.hashCode() ^ daprov.getIdent().hashCode();
}
public boolean equals (Object other)
{
if (other == null || !(other instanceof ImageKey)) {
return false;
}
ImageKey okey = (ImageKey)other;
return ((okey.daprov.getIdent().equals(daprov.getIdent())) &&
(okey.path.equals(path)));
}
public String toString ()
{
return daprov.getIdent() + ":" + path;
}
}
/** /**
* Construct an image manager with the specified {@link * Construct an image manager with the specified {@link
* ResourceManager} from which it will obtain its data. A non-null * ResourceManager} from which it will obtain its data. A non-null
@@ -37,6 +92,12 @@ public class ImageManager
{ {
_rmgr = rmgr; _rmgr = rmgr;
// create our image cache
int icsize = ConfigUtil.getSystemProperty(
"narya.media.image.cache_size", DEFAULT_IMAGE_CACHE_SIZE);
Log.debug("Creating image cache [size=" + icsize + "].");
_ccache = new LRUHashMap(icsize);
// try to figure out which image loader we'll be using // try to figure out which image loader we'll be using
try { try {
_loader = (ImageLoader)Class.forName(IMAGEIO_LOADER).newInstance(); _loader = (ImageLoader)Class.forName(IMAGEIO_LOADER).newInstance();
@@ -45,123 +106,253 @@ public class ImageManager
"Falling back to Toolkit [error=" + t + "]."); "Falling back to Toolkit [error=" + t + "].");
_loader = new ToolkitLoader(context); _loader = new ToolkitLoader(context);
} }
// obtain our graphics configuration
if (context != null) {
_gc = context.getGraphicsConfiguration();
} else {
_gc = ImageUtil.getDefGC();
}
} }
/** /**
* Loads up the requested image from the specified resource set and * Creates a buffered image, optimized for display on our graphics
* caches it for faster retrieval henceforth. * device.
*/ */
public Image getImage (String rset, String path) public BufferedImage createImage (int width, int height, int transparency)
{ {
if (rset == null || path == null) { return _gc.createCompatibleImage(width, height, transparency);
String errmsg = "Must supply non-null resource set and path " + }
"[rset=" + rset + ", path=" + path + "]";
/**
* Loads (and caches) the specified image from the resource manager
* using the supplied path to identify the image.
*/
public BufferedImage getImage (String path)
{
return getImage(null, path, null);
}
/**
* Like {@link #getImage(String)} but the specified colorizations are
* applied to the image before it is returned.
*/
public BufferedImage getImage (String path, Colorization[] zations)
{
return getImage(null, path, zations);
}
/**
* Like {@link #getImage(String)} but the image is loaded from the
* specified resource set rathern than the default resource set.
*/
public BufferedImage getImage (String rset, String path)
{
return getImage(rset, path, null);
}
/**
* Like {@link #getImage(String,String)} but the specified
* colorizations are applied to the image before it is returned.
*/
public BufferedImage getImage (String rset, String path,
Colorization[] zations)
{
if (StringUtil.blank(path)) {
String errmsg = "Invalid image path [rset=" + rset +
", path=" + path + "]";
throw new IllegalArgumentException(errmsg); throw new IllegalArgumentException(errmsg);
} }
// periodically report our image cache performance return getImage(getImageKey(rset, path), zations);
reportCachePerformance();
String key = rset + ":" + path;
Image img = (Image)_imgs.get(key);
if (img != null) {
// Log.info("Retrieved image from cache [path=" + path + "].");
return img;
}
InputStream imgin = null;
try {
// first attempt to load the image from the specified resource set
try {
imgin = _rmgr.getResource(rset, path);
} catch (FileNotFoundException fnfe) {
// fall through and try the classpath
}
// if that fails, attempt to load the image from the classpath
if (imgin == null) {
imgin = _rmgr.getResource(path);
// Log.info("Fell back to classpath [rset=" + rset +
// ", path=" + path + "].");
}
// now load up and optimize the image for display
img = createImage(imgin);
} catch (IOException ioe) {
Log.warning("Failure loading image [rset=" + rset +
", path=" + path + ", error=" + ioe + "].");
} finally {
if (imgin != null) {
try {
imgin.close();
} catch (IOException ioe) {
Log.warning("Failure closing image input stream " +
"[rset=" + rset + ", path=" + path +
", error=" + ioe + "].");
}
}
}
// Log.info("Loading image into cache [path=" + path + "].");
if (img != null) {
_imgs.put(key, img);
} else {
Log.warning("Unable to load image " +
"[rset=" + rset + ", path=" + path + "].");
Thread.dumpStack();
// fake it so that we don't crap out
img = ImageUtil.createImage(1, 1);
}
return img;
} }
/** /**
* Loads the image via the resource manager using the specified path * Returns an image key that can be used to fetch the image identified
* and caches it for faster retrieval henceforth. * by the specified resource set and image path.
*/ */
public Image getImage (String path) public ImageKey getImageKey (String rset, String path)
{ {
return getImageKey(getDataProvider(rset), path);
}
/**
* Returns an image key that can be used to fetch the image identified
* by the specified data provider and image path.
*/
public ImageKey getImageKey (ImageDataProvider daprov, String path)
{
return new ImageKey(daprov, path);
}
/**
* Obtains the image identified by the specified key, caching if
* possible. The image will be recolored using the supplied
* colorizations if requested.
*/
public BufferedImage getImage (ImageKey key, Colorization[] zations)
{
CacheRecord crec = (CacheRecord)_ccache.get(key);
if (crec != null) {
// Log.info("Cache hit [key=" + key + ", crec=" + crec + "].");
return crec.getImage(zations);
}
// Log.info("Cache miss [key=" + key + ", crec=" + crec + "].");
// load up the raw image
BufferedImage image = loadImage(key);
if (image == null) {
Log.warning("Failed to load image " + key + ".");
}
// create a cache record
crec = new CacheRecord(image);
_ccache.put(key, crec);
_keySet.add(key);
// periodically report our image cache performance // periodically report our image cache performance
reportCachePerformance(); reportCachePerformance();
Image img = (Image)_imgs.get(path); return crec.getImage(zations);
if (img != null) { }
// Log.info("Retrieved image from cache [path=" + path + "].");
return img;
}
InputStream imgin = null; /**
try { * Returns the data provider configured to obtain image data from the
imgin = _rmgr.getResource(path); * specified resource set.
img = createImage(imgin); */
protected ImageDataProvider getDataProvider (final String rset)
{
if (rset == null) {
return _defaultProvider;
}
} catch (IOException ioe) { ImageDataProvider dprov = (ImageDataProvider)_providers.get(rset);
Log.warning("Failure loading image [path=" + path + if (dprov == null) {
", error=" + ioe + "]."); dprov = new ImageDataProvider() {
public ImageInputStream loadImageData (String path)
} finally { throws IOException {
if (imgin != null) { // first attempt to load the image from the specified
try { // resource set
imgin.close(); try {
} catch (IOException ioe) { return _rmgr.getImageResource(rset, path);
Log.warning("Failure closing image input stream " + } catch (FileNotFoundException fnfe) {
"[path=" + path + ", error=" + ioe + "]."); // fall back to trying the classpath
return _rmgr.getImageResource(path);
}
} }
public String getIdent () {
return "rmgr:" + rset;
}
};
_providers.put(rset, dprov);
}
return dprov;
}
/**
* Loads and returns the image with the specified key from the
* supplied data provider.
*/
protected BufferedImage loadImage (ImageKey key)
{
BufferedImage image = null;
ImageInputStream imgin = null;
try {
Log.debug("Loading image " + key + ".");
imgin = key.daprov.loadImageData(key.path);
image = loadImage(imgin);
} catch (Exception e) {
Log.warning("Unable to load image '" + key + "'.");
Log.logStackTrace(e);
// create a blank image in its stead
image = createImage(1, 1, Transparency.OPAQUE);
}
if (imgin != null) {
try {
imgin.close();
} catch (IOException ioe) {
Log.warning("Failure closing image input '" + key + "'.");
Log.logStackTrace(ioe);
} }
} }
// Log.info("Loading image into cache [path=" + path + "]."); return image;
if (img != null) { }
_imgs.put(path, img);
} else { /**
Log.warning("Unable to load image [path=" + path + "]."); * Called by {@link #loadImage(ImageKey)} to do the actual image
Thread.dumpStack(); * loading.
// fake it so that we don't crap out */
img = ImageUtil.createImage(1, 1); protected BufferedImage loadImage (ImageInputStream imgin)
throws IOException
{
return ImageIO.read(imgin);
// _reader.setInput(imgin, false, false);
// return _reader.read(0, null);
}
/**
* Creates a mirage which is an image optimized for display on our
* current display device and which will be stored into video memory
* if possible.
*/
public Mirage getMirage (ImageKey key)
{
return getMirage(key, null, null);
}
/**
* Like {@link #getMirage(ImageKey)} but that only the specified
* subimage of the source image is used to build the mirage.
*/
public Mirage getMirage (ImageKey key, Rectangle bounds)
{
return getMirage(key, bounds, null);
}
/**
* Like {@link #getMirage(ImageKey)} but the supplied colorizations
* are applied to the source image before creating the mirage.
*/
public Mirage getMirage (ImageKey key, Colorization[] zations)
{
return getMirage(key, null, zations);
}
/**
* Like {@link #getMirage(ImageKey,Colorization[])} except that the
* mirage is created using only the specified subset of the original
* image.
*/
public Mirage getMirage (ImageKey key, Rectangle bounds,
Colorization[] zations)
{
if (bounds == null) {
BufferedImage src = getImage(key, zations);
bounds = new Rectangle(0, 0, src.getWidth(), src.getHeight());
// return new BufferedMirage(src);
// } else {
// BufferedImage src = getImage(key, zations);
// src = src.getSubimage(bounds.x, bounds.y,
// bounds.width, bounds.height);
// return new BufferedMirage(src);
} }
return img; return new CachedVolatileMirage(this, key, bounds, zations);
// return new BlankMirage(bounds.width, bounds.height);
}
/**
* Returns the graphics configuration that should be used to optimize
* images for display.
*/
public GraphicsConfiguration getGraphicsConfiguration ()
{
return _gc;
} }
/** /**
@@ -170,125 +361,74 @@ public class ImageManager
*/ */
protected void reportCachePerformance () protected void reportCachePerformance ()
{ {
if (!_cacheStatThrottle.throttleOp()) { if (/* Log.getLevel() != Log.log.DEBUG || */
long size = ImageUtil.getEstimatedMemoryUsage( _cacheStatThrottle.throttleOp()) {
_imgs.values().iterator()); return;
int[] eff = _imgs.getTrackedEffectiveness();
Log.debug("ImageManager LRU [mem=" + (size / 1024) + "k" +
", size=" + _imgs.size() + ", hits=" + eff[0] +
", misses=" + eff[1] + "].");
} }
// compute our estimated memory usage
long size = 0;
Iterator iter = _ccache.values().iterator();
while (iter.hasNext()) {
size += ((CacheRecord)iter.next()).getEstimatedMemoryUsage();
}
int[] eff = _ccache.getTrackedEffectiveness();
Log.info("ImageManager LRU [mem=" + (size / 1024) + "k" +
", size=" + _ccache.size() + ", hits=" + eff[0] +
", misses=" + eff[1] + ", totalKeys=" + _keySet.size() + "].");
} }
/** /** Maintains a source image and a set of colorized versions in the
* Loads the image via the resource manager using the specified * image cache. */
* path. Does no caching and does not convert the image for optimized protected static class CacheRecord
* display on the target graphics configuration. Instead the original
* image as returned by the image loader is returned.
*/
public Image loadImage (String path)
throws IOException
{ {
InputStream imgin = null; public CacheRecord (BufferedImage source)
try { {
imgin = _rmgr.getResource(path); _source = source;
return _loader.loadImage(new BufferedInputStream(imgin)); }
} catch (IllegalArgumentException iae) { public BufferedImage getImage (Colorization[] zations)
String errmsg = "Error loading image [path=" + path + "]"; {
throw new NestableIOException(errmsg, iae); if (zations == null) {
return _source;
} finally {
if (imgin != null) {
imgin.close();
} }
}
}
/** if (_colorized == null) {
* Loads the image from the given resource set using the specified _colorized = new ArrayList();
* path. Does no caching and does not convert the image for optimized
* display on the target graphics configuration. Instead the original
* image as returned by the image loader is returned.
*/
public Image loadImage (String rset, String path)
throws IOException
{
InputStream imgin = null;
try {
imgin = _rmgr.getResource(rset, path);
return _loader.loadImage(new BufferedInputStream(imgin));
} catch (Throwable t) {
String errmsg = "Error loading image " +
"[rset=" + rset + ", path=" + path + "]";
throw new NestableIOException(errmsg, t);
} finally {
if (imgin != null) {
imgin.close();
} }
}
}
/** // we search linearly through our list of colorized copies
* Loads the image from the supplied input stream. Does no caching and // because it is not likely to be very long
* does not convert the image for optimized display on the target int csize = _colorized.size();
* graphics configuration. Instead the original image as returned by for (int ii = 0; ii < csize; ii++) {
* the image loader is returned. Tuple tup = (Tuple)_colorized.get(ii);
*/ Colorization[] tzations = (Colorization[])tup.left;
public Image loadImage (InputStream source) if (Arrays.equals(zations, tzations)) {
throws IOException return (BufferedImage)tup.right;
{ }
try { }
return _loader.loadImage(new BufferedInputStream(source));
} catch (IllegalArgumentException iae) {
String errmsg = "Error loading image";
throw new NestableIOException(errmsg, iae);
}
}
/** BufferedImage cimage = ImageUtil.recolorImage(_source, zations);
* Creates an image that is optimized for rendering in the client's _colorized.add(new Tuple(zations, cimage));
* environment and decodes the image data from the specified source return cimage;
* image into that target image. The resulting image is not cached.
*/
public Image createImage (InputStream source)
throws IOException
{
return createImage(loadImage(source));
}
/**
* Creates an image that is optimized for rendering in the client's
* environment and renders the supplied image into the optimized
* image. The resulting image is not cached.
*/
public Image createImage (Image src)
{
// not to freak out if we get this far and have no image
if (src == null) {
return null;
} }
int swidth = src.getWidth(null); public long getEstimatedMemoryUsage ()
int sheight = src.getHeight(null); {
BufferedImage dest = null; return ImageUtil.getEstimatedMemoryUsage(_source);
// use the same kind of transparency as the source image if we can
// when converting the image to a format optimized for display
if (src instanceof BufferedImage) {
int trans = ((BufferedImage)src).getColorModel().getTransparency();
dest = ImageUtil.createImage(swidth, sheight, trans);
} else {
dest = ImageUtil.createImage(swidth, sheight);
} }
Graphics2D gfx = dest.createGraphics(); public String toString ()
gfx.drawImage(src, 0, 0, null); {
gfx.dispose(); return "[wid=" + _source.getWidth() +
", hei=" + _source.getHeight() +
", ccount=" + ((_colorized == null) ? 0 : _colorized.size()) +
"]";
}
return dest; protected BufferedImage _source;
protected ArrayList _colorized;
} }
/** A reference to the resource manager via which we load image data /** A reference to the resource manager via which we load image data
@@ -300,11 +440,37 @@ public class ImageManager
protected ImageLoader _loader; protected ImageLoader _loader;
/** A cache of loaded images. */ /** A cache of loaded images. */
protected LRUHashMap _imgs = new LRUHashMap(IMAGE_CACHE_SIZE); protected LRUHashMap _ccache;
/** The set of all keys we've ever seen. */
protected HashSet _keySet = new HashSet();
/** Throttle our cache status logging to once every 30 seconds. */ /** Throttle our cache status logging to once every 30 seconds. */
protected Throttle _cacheStatThrottle = new Throttle(1, 30000L); protected Throttle _cacheStatThrottle = new Throttle(1, 30000L);
/** The graphics configuration for the default screen device. */
protected GraphicsConfiguration _gc;
/** Our default data provider. */
protected ImageDataProvider _defaultProvider = new ImageDataProvider() {
public ImageInputStream loadImageData (String path)
throws IOException {
return _rmgr.getImageResource(path);
}
public String getIdent () {
return "rmgr:default";
}
};
// /** Temporary hacked PNG reader that actually closes its
// * InflaterInputStream when it's done with it. */
// protected PNGImageReader _reader =
// new PNGImageReader(new PNGImageReaderSpi());
/** Data providers for different resource sets. */
protected HashMap _providers = new HashMap();
/** The classname of the ImageIO-based image loader which we attempt /** The classname of the ImageIO-based image loader which we attempt
* to use but fallback from if we're not running a JVM that has * to use but fallback from if we're not running a JVM that has
* ImageIO support. */ * ImageIO support. */
@@ -312,5 +478,5 @@ public class ImageManager
"com.threerings.media.image.ImageIOLoader"; "com.threerings.media.image.ImageIOLoader";
/** The maximum number of images that may be cached at any one time. */ /** The maximum number of images that may be cached at any one time. */
protected static final int IMAGE_CACHE_SIZE = 30; protected static final int DEFAULT_IMAGE_CACHE_SIZE = 100;
} }
@@ -1,5 +1,5 @@
// //
// $Id: ImageUtil.java,v 1.21 2003/01/08 04:28:13 ray Exp $ // $Id: ImageUtil.java,v 1.22 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.image; package com.threerings.media.image;
@@ -426,8 +426,8 @@ public class ImageUtil
// copy the RGB from the base image // copy the RGB from the base image
for (int ii=0; ii < 3; ii++) { for (int ii=0; ii < 3; ii++) {
int[] cdata = basedata.getSamples(0, 0, wid, hei, int[] cdata = basedata.getSamples(
ii, (int[]) null); 0, 0, wid, hei, ii, (int[]) null);
target.setSamples(0, 0, wid, hei, ii, cdata); target.setSamples(0, 0, wid, hei, ii, cdata);
} }
@@ -490,22 +490,11 @@ public class ImageUtil
* Returns true if the supplied image contains a non-transparent pixel * Returns true if the supplied image contains a non-transparent pixel
* at the specified coordinates, false otherwise. * at the specified coordinates, false otherwise.
*/ */
public static boolean hitTest (Image image, int x, int y) public static boolean hitTest (BufferedImage image, int x, int y)
{ {
if (image instanceof BufferedImage) { // it's only a hit if the pixel is non-transparent
BufferedImage bimage = (BufferedImage)image; int argb = image.getRGB(x, y);
int argb = bimage.getRGB(x, y); return (argb >> 24) != 0;
// int alpha = argb >> 24;
// Log.info("Checking [x=" + x + ", y=" + y + ", " + alpha);
// it's only a hit if the pixel is non-transparent
return (argb >> 24) != 0;
} else {
Log.warning("Can't check for transparent pixel " +
"[image=" + image + "].");
return true;
}
} }
/** /**
@@ -0,0 +1,43 @@
//
// $Id: Mirage.java,v 1.1 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.image;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
/**
* Provides an interface via which images can be accessed in a way that
* allows them to optionally be located in video memory where that affords
* performance improvements.
*/
public interface Mirage
{
/**
* Renders this mirage at the specified position in the supplied
* graphics context.
*/
public void paint (Graphics2D gfx, int x, int y);
/**
* Returns the width of this mirage.
*/
public int getWidth ();
/**
* Returns the height of this mirage.
*/
public int getHeight ();
/**
* Returns true if this mirage contains a non-transparent pixel at the
* specified coordinate.
*/
public boolean hitTest (int x, int y);
/**
* Returns a snapshot of this mirage as a buffered image. The snapshot
* should <em>not</em> be modified by the caller.
*/
public BufferedImage getSnapshot ();
}
@@ -0,0 +1,42 @@
//
// $Id: MirageIcon.java,v 1.1 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.image;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.Graphics;
import javax.swing.Icon;
/**
* Implements the Swing {@link Icon} interface with a mirage providing the
* image information.
*/
public class MirageIcon implements Icon
{
public MirageIcon (Mirage mirage)
{
_mirage = mirage;
}
// documentation inherited from interface
public void paintIcon (Component c, Graphics g, int x, int y)
{
_mirage.paint((Graphics2D)g, x, y);
}
// documentation inherited from interface
public int getIconWidth()
{
return _mirage.getWidth();
}
// documentation inherited from interface
public int getIconHeight()
{
return _mirage.getHeight();
}
protected Mirage _mirage;
}
@@ -0,0 +1,144 @@
//
// $Id: VolatileMirage.java,v 1.1 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.image;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import com.samskivert.util.StringUtil;
/**
* A mirage implementation which allows the image to be maintained in
* video memory and rebuilt from some source image or images in the event
* that our target screen resolution changes or we are flushed from video
* memory for some other reason.
*/
public abstract class VolatileMirage implements Mirage
{
/**
* Informs the base class of its image manager and image bounds.
*/
protected VolatileMirage (ImageManager imgr, Rectangle bounds)
{
_imgr = imgr;
_bounds = bounds;
}
// documentation inherited from interface
public void paint (Graphics2D gfx, int x, int y)
{
// create our volatile image for the first time if necessary
if (_image == null) {
createVolatileImage();
}
// int renders = 0;
// do {
// // validate that our image is compatible with the target GC
// switch (_image.validate(_imgr.getGraphicsConfiguration())) {
// case VolatileImage.IMAGE_RESTORED:
// refreshVolatileImage(); // need to rerender it
// break;
// case VolatileImage.IMAGE_INCOMPATIBLE:
// createVolatileImage(); // need to recreate it
// break;
// }
// now we can render it
gfx.drawImage(_image, x, y, null);
// renders++;
// // don't try forever
// } while (_image.contentsLost() && (renders < 10));
// TODO: note number of attempted renders for performance
}
// documentation inherited from interface
public int getWidth ()
{
return _bounds.width;
}
// documentation inherited from interface
public int getHeight ()
{
return _bounds.height;
}
// documentation inherited from interface
public boolean hitTest (int x, int y)
{
// return ImageUtil.hitTest(_image.getSnapshot(), x, y);
return ImageUtil.hitTest(_image, x, y);
}
// documentation inherited from interface
public BufferedImage getSnapshot ()
{
// return _image.getSnapshot();
return _image;
}
/**
* Creates our volatile image from the information in our source
* image.
*/
protected void createVolatileImage ()
{
// release any previous volatile image we might hold
if (_image != null) {
_image.flush();
}
// create a new, compatible, volatile image
GraphicsConfiguration gc = _imgr.getGraphicsConfiguration();
// _image = gc.createCompatibleVolatileImage(
// _bounds.width, _bounds.height);
_image = gc.createCompatibleImage(
_bounds.width, _bounds.height, Transparency.BITMASK);
// render our source image into the volatile image
refreshVolatileImage();
}
/**
* Rerenders our volatile image from the its source image data.
*/
protected abstract void refreshVolatileImage ();
/**
* Generates a string representation of this instance.
*/
public String toString ()
{
StringBuffer buf = new StringBuffer("[");
toString(buf);
return buf.append("]").toString();
}
/**
* Generates a string representation of this instance.
*/
protected void toString (StringBuffer buf)
{
buf.append("bounds=").append(StringUtil.toString(_bounds));
}
/** The image manager with whom we interoperate. */
protected ImageManager _imgr;
/** The bounds of the region of our source image which we desire for
* this mirage (possibly the whole thing). */
protected Rectangle _bounds;
/** Our volatile image which lives in video memory and can go away at
* any time. */
// protected VolatileImage _image;
protected BufferedImage _image;
}
@@ -1,5 +1,5 @@
// //
// $Id: ImageSprite.java,v 1.16 2002/11/20 05:33:20 mdb Exp $ // $Id: ImageSprite.java,v 1.17 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.sprite; package com.threerings.media.sprite;
@@ -8,7 +8,6 @@ import java.awt.Color;
import java.awt.Composite; import java.awt.Composite;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
@@ -16,6 +15,7 @@ import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.image.Mirage;
import com.threerings.media.util.MultiFrameImage; import com.threerings.media.util.MultiFrameImage;
import com.threerings.media.util.SingleFrameImageImpl; import com.threerings.media.util.SingleFrameImageImpl;
@@ -75,7 +75,7 @@ public class ImageSprite extends Sprite
* Constructs an image sprite that will display the supplied single * Constructs an image sprite that will display the supplied single
* image when rendering itself. * image when rendering itself.
*/ */
public ImageSprite (Image image) public ImageSprite (Mirage image)
{ {
this(new SingleFrameImageImpl(image)); this(new SingleFrameImageImpl(image));
} }
@@ -85,8 +85,9 @@ public class ImageSprite extends Sprite
{ {
super.init(); super.init();
// now that we have our spritemanager, we can initialize our frames // now that we have our sprite manager, we can lay ourselves out
setFrameIndex(0, true); // and initialize our frames
layout();
} }
/** /**
@@ -157,7 +158,19 @@ public class ImageSprite extends Sprite
// set and init our frames // set and init our frames
_frames = frames; _frames = frames;
setFrameIndex(0, true); _frameIdx = 0;
layout();
}
/**
* Instructs this sprite to lay out its current frame and any
* accoutrements.
*/
public void layout ()
{
if (_frames != null) {
setFrameIndex(_frameIdx, true);
}
} }
/** /**
@@ -0,0 +1,55 @@
//
// $Id: IMImageProvider.java,v 1.1 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.ImageDataProvider;
import com.threerings.media.image.ImageManager;
import com.threerings.media.image.Mirage;
/**
* Provides images to a tileset given a reference to the image manager and
* an image data provider.
*/
public class IMImageProvider implements ImageProvider
{
public IMImageProvider (ImageManager imgr, ImageDataProvider dprov)
{
_imgr = imgr;
_dprov = dprov;
}
public IMImageProvider (ImageManager imgr, String rset)
{
_imgr = imgr;
_rset = rset;
}
// documentation inherited from interface
public BufferedImage getTileSetImage (String path, Colorization[] zations)
{
return _imgr.getImage(getImageKey(path), zations);
}
// documentation inherited from interface
public Mirage getTileImage (String path, Rectangle bounds,
Colorization[] zations)
{
return _imgr.getMirage(getImageKey(path), bounds, zations);
}
protected final ImageManager.ImageKey getImageKey (String path)
{
return (_dprov == null) ?
_imgr.getImageKey(_rset, path) :
_imgr.getImageKey(_dprov, path);
}
protected ImageManager _imgr;
protected ImageDataProvider _dprov;
protected String _rset;
}
@@ -1,26 +1,45 @@
// //
// $Id: ImageProvider.java,v 1.2 2001/12/07 01:33:29 mdb Exp $ // $Id: ImageProvider.java,v 1.3 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.awt.Image; import java.awt.Rectangle;
import java.io.IOException; import java.awt.image.BufferedImage;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.Mirage;
/** /**
* A tileset needs to load its images from some location. That will * Provides a generic interface via which tileset images may be loaded. In
* generally either be via the {@link TileManager} that constructed it or * most cases, a running application will want to obtain images via the
* the {@link TileSetRepository} that constructed it. The tile manager * {@link ImageManager}, but in some circumstances a simpler image
* loads images via the resource manager, whereas the tileset repository * provider may be desirable to avoid the overhead of the image manager
* will likely obtain images via its own resource bundles. * infrastructure when simple image loading is all that is desired.
*/ */
public interface ImageProvider public interface ImageProvider
{ {
/** /**
* Loads the image with the specified path. * Returns the raw tileset image with the specified path.
* *
* @exception IOException thrown if an error occurs loading the image * @param path the path that identifies the desired image (corresponds
* data. * to the image path from the tileset).
* @param zations if non-null, colorizations to apply to the source
* image before returning it.
*/ */
public Image loadImage (String path) public BufferedImage getTileSetImage (String path, Colorization[] zations);
throws IOException;
/**
* Obtains the tile image with the specified path in the form of a
* {@link Mirage}. It should be cropped from the tileset image
* identified by the supplied path.
*
* @param path the path that identifies the desired image (corresponds
* to the image path from the tileset).
* @param bounds if non-null, the region of the image to be returned
* as a mirage. If null, the entire image should be returned.
* @param zations if non-null, colorizations to apply to the image
* before converting it into a mirage.
*/
public Mirage getTileImage (String path, Rectangle bounds,
Colorization[] zations);
} }
@@ -1,5 +1,5 @@
// //
// $Id: NoSuchTileException.java,v 1.1 2001/10/11 00:41:26 shaper Exp $ // $Id: NoSuchTileException.java,v 1.2 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
@@ -7,7 +7,7 @@ package com.threerings.media.tile;
* Thrown when an attempt is made to retrieve a non-existent tile from the * Thrown when an attempt is made to retrieve a non-existent tile from the
* tile manager. * tile manager.
*/ */
public class NoSuchTileException extends TileException public class NoSuchTileException extends RuntimeException
{ {
public NoSuchTileException (int tid) public NoSuchTileException (int tid)
{ {
@@ -1,24 +1,21 @@
// //
// $Id: NoSuchTileSetException.java,v 1.1 2001/10/11 00:41:26 shaper Exp $ // $Id: NoSuchTileSetException.java,v 1.2 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
/** /**
* Thrown when an attempt is made to retrieve a non-existent tile set * Thrown when an attempt is made to retrieve a non-existent tile set from
* from the tile set manager. * the tile set manager.
*/ */
public class NoSuchTileSetException extends TileException public class NoSuchTileSetException extends Exception
{ {
public NoSuchTileSetException (int tsid) public NoSuchTileSetException (String tileSetName)
{ {
super("No such tile set [tsid=" + tsid + "]"); super("No tile set named '" + tileSetName + "'");
_tsid = tsid;
} }
public int getTileSetId () public NoSuchTileSetException (int tileSetId)
{ {
return _tsid; super("No tile set with id '" + tileSetId + "'");
} }
protected int _tsid;
} }
@@ -1,15 +1,24 @@
// //
// $Id: ObjectTile.java,v 1.12 2003/01/08 04:09:02 mdb Exp $ // $Id: ObjectTile.java,v 1.13 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.awt.Image; import com.threerings.media.image.Mirage;
import java.awt.Rectangle;
/** /**
* An object tile extends the base tile to provide support for objects * An object tile extends the base tile to provide support for objects
* whose image spans more than one unit tile. An object tile has * whose image spans more than one unit tile.
* dimensions (in tile units) that represent its footprint or "shadow". *
* <p> An object tile is generally positioned based on its origin rather
* than the upper left of its image. Generally this origin is in the
* bottom center of the object image, but can be configured to be anywhere
* that the natural center point of "contact" is for the object. Note that
* this does not automatically adjust the semantics of {@link #paint}, it
* is just expected that the caller will account for the object tile's
* origin when painting, if appropriate.
*
* <p> An object tile has dimensions (in tile units) that represent its
* footprint or "shadow".
*/ */
public class ObjectTile extends Tile public class ObjectTile extends Tile
{ {
@@ -17,21 +26,9 @@ public class ObjectTile extends Tile
* Constructs a new object tile with the specified image. The base * Constructs a new object tile with the specified image. The base
* width and height should be set before using this tile. * width and height should be set before using this tile.
*/ */
public ObjectTile (Image image, Rectangle bounds) public ObjectTile (Mirage image)
{ {
super(image, bounds); super(image);
}
/**
* Constructs a new object tile with the specified base width and
* height.
*/
public ObjectTile (Image image, Rectangle bounds,
int baseWidth, int baseHeight)
{
super(image, bounds);
_baseWidth = baseWidth;
_baseHeight = baseHeight;
} }
/** /**
@@ -53,7 +50,7 @@ public class ObjectTile extends Tile
/** /**
* Sets the object footprint in tile units. * Sets the object footprint in tile units.
*/ */
public void setBase (int width, int height) protected void setBase (int width, int height)
{ {
_baseWidth = width; _baseWidth = width;
_baseHeight = height; _baseHeight = height;
@@ -91,7 +88,7 @@ public class ObjectTile extends Tile
* the origin tile and the left side of the image is aligned with the * the origin tile and the left side of the image is aligned with the
* left edge of the left-most base tile. * left edge of the left-most base tile.
*/ */
public void setOrigin (int x, int y) protected void setOrigin (int x, int y)
{ {
_originX = x; _originX = x;
_originY = y; _originY = y;
@@ -1,14 +1,11 @@
// //
// $Id: ObjectTileSet.java,v 1.8 2003/01/08 04:09:02 mdb Exp $ // $Id: ObjectTileSet.java,v 1.9 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.awt.Image;
import java.awt.Rectangle;
import java.util.Arrays;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.threerings.media.image.ImageManager;
import com.threerings.media.image.Mirage;
/** /**
* The object tileset supports the specification of object information for * The object tileset supports the specification of object information for
@@ -69,24 +66,15 @@ public class ObjectTileSet extends SwissArmyTileSet
* Creates instances of {@link ObjectTile}, which can span more than a * Creates instances of {@link ObjectTile}, which can span more than a
* single tile's space in a display. * single tile's space in a display.
*/ */
protected Tile createTile (int tileIndex, Image tilesetImage) protected Tile createTile (int tileIndex, Mirage image)
{ {
// default object dimensions to (1, 1) ObjectTile tile = new ObjectTile(image);
int wid = 1, hei = 1;
// retrieve object dimensions if known
if (_owidths != null) { if (_owidths != null) {
wid = _owidths[tileIndex]; tile.setBase(_owidths[tileIndex], _oheights[tileIndex]);
hei = _oheights[tileIndex];
} }
Rectangle bounds = computeTileBounds(tileIndex, tilesetImage);
ObjectTile tile = new ObjectTile(tilesetImage, bounds, wid, hei);
if (_xorigins != null) { if (_xorigins != null) {
tile.setOrigin(_xorigins[tileIndex], _yorigins[tileIndex]); tile.setOrigin(_xorigins[tileIndex], _yorigins[tileIndex]);
} }
return tile; return tile;
} }
@@ -0,0 +1,56 @@
//
// $Id: SimpleCachingImageProvider.java,v 1.1 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.IOException;
import com.samskivert.util.LRUHashMap;
import com.threerings.media.Log;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.Mirage;
/**
* An image provider that can be used by command line tools to load images
* and provide them to tilesets when doing things like preprocessing
* tileset images.
*/
public abstract class SimpleCachingImageProvider implements ImageProvider
{
// documentation inherited from interface
public BufferedImage getTileSetImage (String path, Colorization[] zations)
{
BufferedImage image = (BufferedImage)_cache.get(path);
if (image == null) {
try {
image = loadImage(path);
_cache.put(path, image);
} catch (IOException ioe) {
Log.warning("Failed to load image [path=" + path +
", ioe=" + ioe + "].");
}
}
return image;
}
// documentation inherited from interface
public Mirage getTileImage (String path, Rectangle bounds,
Colorization[] zations)
{
// since we're just doing batch processing, we don't prepare
// images for the screen
throw new UnsupportedOperationException();
}
/**
* Derived classes must implement this method to actually load the raw
* source images.
*/
protected abstract BufferedImage loadImage (String path)
throws IOException;
protected LRUHashMap _cache = new LRUHashMap(10);
}
@@ -1,10 +1,9 @@
// //
// $Id: SwissArmyTileSet.java,v 1.8 2002/05/06 18:08:32 mdb Exp $ // $Id: SwissArmyTileSet.java,v 1.9 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point; import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
@@ -128,7 +127,7 @@ public class SwissArmyTileSet extends TileSet
} }
// documentation inherited // documentation inherited
protected Rectangle computeTileBounds (int tileIndex, Image tilesetImage) protected Rectangle computeTileBounds (int tileIndex)
{ {
// find the row number containing the sought-after tile // find the row number containing the sought-after tile
int ridx, tcount, ty, tx; int ridx, tcount, ty, tx;
+16 -72
View File
@@ -1,18 +1,11 @@
// //
// $Id: Tile.java,v 1.25 2003/01/08 04:09:02 mdb Exp $ // $Id: Tile.java,v 1.26 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.awt.Graphics; import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.image.BufferedImage;
import com.samskivert.util.StringUtil; import com.threerings.media.image.Mirage;
import com.threerings.media.Log;
import com.threerings.media.image.ImageUtil;
/** /**
* A tile represents a single square in a single layer in a scene. * A tile represents a single square in a single layer in a scene.
@@ -20,40 +13,14 @@ import com.threerings.media.image.ImageUtil;
public class Tile implements Cloneable public class Tile implements Cloneable
{ {
/** /**
* Constructs a tile with the specified tileset image. * Constructs a tile with the specified image.
* *
* @param image the tileset image from which our tile image is * @param image our tile image in mirage form (which is optimized for
* extracted. * screen rendering).
* @param bounds the bounds of the tile image within the greater
* tileset image.
*/ */
public Tile (Image image, Rectangle bounds) public Tile (Mirage image)
{ {
_image = image; _mirage = image;
_bounds = bounds;
}
/**
* Called to create our subimage.
*/
protected void createSubImage ()
{
if (_image instanceof BufferedImage) {
try {
_subimage = ImageUtil.getSubimage(
_image, _bounds.x, _bounds.y,
_bounds.width, _bounds.height);
} catch (RuntimeException rte) {
Log.warning("Failure creating subimage [src=" + _image +
", bounds=" + StringUtil.toString(_bounds) +
", error=" + rte + "].");
throw rte;
}
} else {
String errmsg = "Can't obtain tile image [tile=" + this + "].";
throw new RuntimeException(errmsg);
}
} }
/** /**
@@ -61,7 +28,7 @@ public class Tile implements Cloneable
*/ */
public int getWidth () public int getWidth ()
{ {
return _bounds.width; return _mirage.getWidth();
} }
/** /**
@@ -69,16 +36,16 @@ public class Tile implements Cloneable
*/ */
public int getHeight () public int getHeight ()
{ {
return _bounds.height; return _mirage.getHeight();
} }
/** /**
* Render the tile image at the specified position in the given * Render the tile image at the specified position in the given
* graphics context. * graphics context.
*/ */
public void paint (Graphics gfx, int x, int y) public void paint (Graphics2D gfx, int x, int y)
{ {
gfx.drawImage(getImage(), x, y, null); _mirage.paint(gfx, x, y);
} }
/** /**
@@ -87,22 +54,7 @@ public class Tile implements Cloneable
*/ */
public boolean hitTest (int x, int y) public boolean hitTest (int x, int y)
{ {
return ImageUtil.hitTest(_image, _bounds.x + x, _bounds.y + y); return _mirage.hitTest(x, y);
}
/**
* Returns the image used to render this tile, if access to that image
* can be obtained. The tile must have been created with a {@link
* BufferedImage} and derived classes may refuse to implement this
* function if they do special stuff that hampers their ability to
* return a single image describing the tile.
*/
public Image getImage ()
{
if (_subimage == null) {
createSubImage();
}
return _subimage;
} }
/** /**
@@ -135,18 +87,10 @@ public class Tile implements Cloneable
*/ */
protected void toString (StringBuffer buf) protected void toString (StringBuffer buf)
{ {
buf.append("srcimg=").append(_image.getClass().getName()); buf.append(_mirage.getWidth()).append("x");
buf.append("[").append(_image.getWidth(null)).append("x"); buf.append(_mirage.getHeight());
buf.append(_image.getHeight(null)).append("]");
buf.append(", bounds=").append(StringUtil.toString(_bounds));
} }
/** Our tileset image. */ /** Our tileset image. */
protected Image _image; protected Mirage _mirage;
/** Our cropped tileset image. */
protected Image _subimage;
/** The bounds of the tile image within the tileset image. */
protected Rectangle _bounds;
} }
@@ -1,15 +0,0 @@
//
// $Id: TileException.java,v 1.1 2001/10/11 00:41:26 shaper Exp $
package com.threerings.media.tile;
/**
* Thrown when an error occurs while working with tiles.
*/
public class TileException extends Exception
{
public TileException (String message)
{
super(message);
}
}
@@ -1,9 +1,10 @@
// //
// $Id: TileIcon.java,v 1.1 2002/05/06 18:08:32 mdb Exp $ // $Id: TileIcon.java,v 1.2 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.awt.Component; import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.Graphics; import java.awt.Graphics;
import javax.swing.Icon; import javax.swing.Icon;
@@ -25,7 +26,7 @@ public class TileIcon implements Icon
// documentation inherited from interface // documentation inherited from interface
public void paintIcon (Component c, Graphics g, int x, int y) public void paintIcon (Component c, Graphics g, int x, int y)
{ {
_tile.paint(g, x, y); _tile.paint((Graphics2D)g, x, y);
} }
// documentation inherited from interface // documentation inherited from interface
@@ -1,16 +1,24 @@
// //
// $Id: TileManager.java,v 1.26 2003/01/08 04:09:02 mdb Exp $ // $Id: TileManager.java,v 1.27 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.awt.Image; import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import com.samskivert.io.PersistenceException; import com.samskivert.io.PersistenceException;
import com.samskivert.util.HashIntMap; import com.samskivert.util.HashIntMap;
import com.samskivert.util.IntTuple;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.ImageDataProvider;
import com.threerings.media.image.ImageManager; import com.threerings.media.image.ImageManager;
import com.threerings.media.image.Mirage;
import com.samskivert.util.LRUHashMap;
/** /**
* The tile manager provides a simplified interface for retrieving and * The tile manager provides a simplified interface for retrieving and
@@ -27,17 +35,8 @@ import com.threerings.media.image.ImageManager;
* Loading tilesets from a repository supports games with vast numbers of * Loading tilesets from a repository supports games with vast numbers of
* tiles to which more tiles may be added on the fly (think the tiles for * tiles to which more tiles may be added on the fly (think the tiles for
* an isometric-display graphical MUD). * an isometric-display graphical MUD).
*
* <p> When the tile manager is used to load tiles via the tileset
* repository, it caches the resulting tile instance so that they can be
* fetched again without rebuilding the tile image. Tilesets that are
* fetched by hand are not cached and it is assumed that the requesting
* application will cache the tile objects itself (probably by retaining
* references directly to the tile instances in which it is interested).
* The tile creation process is not hugely expensive, but does involve
* extracting the tile image from the larger tileset image.
*/ */
public class TileManager implements ImageProvider public class TileManager
{ {
/** /**
* Creates a tile manager and provides it with a reference to the * Creates a tile manager and provides it with a reference to the
@@ -48,8 +47,8 @@ public class TileManager implements ImageProvider
*/ */
public TileManager (ImageManager imgr) public TileManager (ImageManager imgr)
{ {
// keep this guy around for later
_imgr = imgr; _imgr = imgr;
_defaultProvider = new IMImageProvider(_imgr, (String)null);
} }
/** /**
@@ -59,13 +58,7 @@ public class TileManager implements ImageProvider
public UniformTileSet loadTileSet ( public UniformTileSet loadTileSet (
String imgPath, int count, int width, int height) String imgPath, int count, int width, int height)
{ {
UniformTileSet uts = new UniformTileSet(); return loadCachedTileSet("", imgPath, count, width, height);
uts.setImageProvider(this);
uts.setImagePath(imgPath);
uts.setTileCount(count);
uts.setWidth(width);
uts.setHeight(height);
return uts;
} }
/** /**
@@ -73,15 +66,40 @@ public class TileManager implements ImageProvider
* specified resource set) with the specified metadata parameters. * specified resource set) with the specified metadata parameters.
*/ */
public UniformTileSet loadTileSet ( public UniformTileSet loadTileSet (
final String rset, String imgPath, int count, int width, int height) String rset, String imgPath, int count, int width, int height)
{ {
UniformTileSet uts = loadTileSet(imgPath, count, width, height); UniformTileSet uts = loadCachedTileSet(
// load up the image data from the image manager rset, imgPath, count, width, height);
uts.setImageProvider(new ImageProvider() { uts.setImageProvider(getImageProvider(rset));
public Image loadImage (String path) throws IOException { return uts;
return _imgr.getImage(rset, path); }
}
}); /**
* Returns an image provider that will load images from the specified
* resource set.
*/
public ImageProvider getImageProvider (String rset)
{
return new IMImageProvider(_imgr, rset);
}
/**
* Used to load and cache tilesets loaded via {@link #loadTileSet}.
*/
protected UniformTileSet loadCachedTileSet (
String bundle, String imgPath, int count, int width, int height)
{
String key = bundle + "::" + imgPath;
UniformTileSet uts = (UniformTileSet)_handcache.get(key);
if (uts == null) {
uts = new UniformTileSet();
uts.setImageProvider(_defaultProvider);
uts.setImagePath(imgPath);
uts.setTileCount(count);
uts.setWidth(width);
uts.setHeight(height);
_handcache.put(key, uts);
}
return uts; return uts;
} }
@@ -122,20 +140,49 @@ public class TileManager implements ImageProvider
} }
try { try {
TileSet set = (TileSet)_cache.get(tileSetId); TileSet set = (TileSet)_setcache.get(tileSetId);
if (set == null) { if (set == null) {
set = _setrep.getTileSet(tileSetId); set = _setrep.getTileSet(tileSetId);
_cache.put(tileSetId, set); _setcache.put(tileSetId, set);
} }
return set; return set;
} catch (PersistenceException pe) { } catch (PersistenceException pe) {
Log.warning("Unable to load tileset [id=" + tileSetId + Log.warning("Failure loading tileset [id=" + tileSetId +
", error=" + pe + "]."); ", error=" + pe + "].");
throw new NoSuchTileSetException(tileSetId); throw new NoSuchTileSetException(tileSetId);
} }
} }
/**
* Returns the tileset with the specified name.
*
* @throws NoSuchTileSetException if no tileset with the specified
* name is available via our configured tile set repository.
*/
public TileSet getTileSet (String name)
throws NoSuchTileSetException
{
// make sure we have a repository configured
if (_setrep == null) {
throw new NoSuchTileSetException(name);
}
try {
TileSet set = (TileSet)_byname.get(name);
if (set == null) {
set = _setrep.getTileSet(name);
_byname.put(name, set);
}
return set;
} catch (PersistenceException pe) {
Log.warning("Failure loading tileset [name=" + name +
", error=" + pe + "].");
throw new NoSuchTileSetException(name);
}
}
/** /**
* Returns the {@link Tile} object from the specified tileset at the * Returns the {@link Tile} object from the specified tileset at the
* specified index. * specified index.
@@ -152,20 +199,21 @@ public class TileManager implements ImageProvider
return set.getTile(tileIndex); return set.getTile(tileIndex);
} }
// documentation inherited
public Image loadImage (String path)
throws IOException
{
// load up the image data from the resource manager
return _imgr.getImage(path);
}
/** The entity through which we decode and cache images. */ /** The entity through which we decode and cache images. */
protected ImageManager _imgr; protected ImageManager _imgr;
/** Cache of tilesets that have been requested thus far. */ /** Cache of tilesets that have been requested thus far. */
protected HashIntMap _cache = new HashIntMap(); protected HashIntMap _setcache = new HashIntMap();
/** A cache of tilesets that have been loaded by hand. */
protected HashMap _handcache = new HashMap();
/** A mapping from tileset name to tileset. */
protected HashMap _byname = new HashMap();
/** The tile set repository. */ /** The tile set repository. */
protected TileSetRepository _setrep; protected TileSetRepository _setrep;
/** Used to load tileset images from the default resource source. */
protected ImageProvider _defaultProvider;
} }
@@ -1,9 +1,9 @@
// //
// $Id: TileMultiFrameImage.java,v 1.1 2002/09/17 20:39:03 mdb Exp $ // $Id: TileMultiFrameImage.java,v 1.2 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.awt.Graphics; import java.awt.Graphics2D;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.util.MultiFrameImage; import com.threerings.media.util.MultiFrameImage;
@@ -32,52 +32,25 @@ public class TileMultiFrameImage implements MultiFrameImage
// documentation inherited from interface // documentation inherited from interface
public int getWidth (int index) public int getWidth (int index)
{ {
try { return _source.getTile(index).getWidth();
return _source.getTile(index).getWidth();
} catch (NoSuchTileException nste) {
Log.warning("Eh? Tile set reported 'no such tile' " +
"[tcount=" + _source.getTileCount() +
", tindex=" + index + "].");
return -1;
}
} }
// documentation inherited from interface // documentation inherited from interface
public int getHeight (int index) public int getHeight (int index)
{ {
try { return _source.getTile(index).getHeight();
return _source.getTile(index).getHeight();
} catch (NoSuchTileException nste) {
Log.warning("Eh? Tile set reported 'no such tile' " +
"[tcount=" + _source.getTileCount() +
", tindex=" + index + "].");
return -1;
}
} }
// documentation inherited from interface // documentation inherited from interface
public void paintFrame (Graphics g, int index, int x, int y) public void paintFrame (Graphics2D g, int index, int x, int y)
{ {
try { _source.getTile(index).paint(g, x, y);
_source.getTile(index).paint(g, x, y);
} catch (NoSuchTileException nste) {
Log.warning("Eh? Tile set reported 'no such tile' " +
"[tcount=" + _source.getTileCount() +
", tindex=" + index + "].");
}
} }
// documentation inherited from interface // documentation inherited from interface
public boolean hitTest (int index, int x, int y) public boolean hitTest (int index, int x, int y)
{ {
try { return _source.getTile(index).hitTest(x, y);
return _source.getTile(index).hitTest(x, y);
} catch (NoSuchTileException nste) {
Log.warning("Eh? Tile set reported 'no such tile' " +
"[tcount=" + _source.getTileCount() +
", tindex=" + index + "].");
return false;
}
} }
protected TileSet _source; protected TileSet _source;
+94 -123
View File
@@ -1,22 +1,21 @@
// //
// $Id: TileSet.java,v 1.35 2003/01/08 04:09:02 mdb Exp $ // $Id: TileSet.java,v 1.36 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.awt.Color; import java.awt.Color;
import java.awt.Image;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import com.samskivert.util.ConfigUtil;
import com.samskivert.util.LRUHashMap;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.samskivert.util.Tuple;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.image.Colorization; import com.threerings.media.image.Colorization;
import com.threerings.media.image.ImageUtil; import com.threerings.media.image.Mirage;
/** /**
* A tileset stores information on a single logical set of tiles. It * A tileset stores information on a single logical set of tiles. It
@@ -70,10 +69,6 @@ public abstract class TileSet
public void setImagePath (String imagePath) public void setImagePath (String imagePath)
{ {
_imagePath = imagePath; _imagePath = imagePath;
// clear out any reference to a loaded image and cached tiles
_tilesetImg = null;
_tiles = null;
} }
/** /**
@@ -90,14 +85,16 @@ public abstract class TileSet
public abstract int getTileCount (); public abstract int getTileCount ();
/** /**
* Creates a copy of this tileset with the supplied colorizations * Creates a copy of this tileset which will apply the supplied
* applied to its source image. * colorizations to its tileset image when creating tiles.
*/ */
public TileSet cloneColorized (Colorization[] zations) public TileSet clone (Colorization[] zations)
{ {
TileSet tset = null;
try { try {
tset = (TileSet)clone(); TileSet tset = (TileSet)clone();
tset._zations = zations;
return tset;
} catch (CloneNotSupportedException cnse) { } catch (CloneNotSupportedException cnse) {
Log.warning("Unable to clone tileset prior to colorization " + Log.warning("Unable to clone tileset prior to colorization " +
"[tset=" + this + "[tset=" + this +
@@ -105,37 +102,6 @@ public abstract class TileSet
", error=" + cnse + "]."); ", error=" + cnse + "].");
return null; return null;
} }
// make sure the tileset was able to load its image
Image timg = tset.getTileSetImage();
if (timg == null) {
Log.warning("Failed to load tileset image in preparation " +
"for colorization [tset=" + tset + "].");
// return the uncolorized tileset since it has no freaking
// source image anyway
return tset;
} else if (!(timg instanceof BufferedImage)) {
Log.warning("Can't recolor tileset with non-buffered " +
"image source [source=" + timg + "].");
return tset;
}
// create the recolored image and update the tileset
tset._tilesetImg =
ImageUtil.recolorImage((BufferedImage)timg, zations);
return tset;
}
// documentation inherited
public Object clone ()
throws CloneNotSupportedException
{
TileSet dup = (TileSet)super.clone();
// clear out the tileset cache
dup._tiles = null;
return dup;
} }
/** /**
@@ -170,35 +136,81 @@ public abstract class TileSet
public Tile getTile (int tileIndex) public Tile getTile (int tileIndex)
throws NoSuchTileException throws NoSuchTileException
{ {
int tcount = getTileCount(); checkTileIndex(tileIndex);
// create our tile cache if necessary
if (_tiles == null) {
int tcsize = ConfigUtil.getSystemProperty(
"narya.media.tile.cache_size", DEFAULT_TILE_CACHE_SIZE);
Log.debug("Creating tile cache [size=" + tcsize + "].");
_tiles = new LRUHashMap(tcsize);
}
// fetch and cache the tile
Tuple key = new Tuple(this, new Integer(tileIndex));
Tile tile = (Tile)_tiles.get(key);
if (tile == null) {
// create and initialize the tile object
tile = createTile(tileIndex, getTileMirage(tileIndex));
initTile(tile);
_tiles.put(key, tile);
}
return tile;
}
/**
* Returns the entire, raw, uncut, unprepared tileset source image.
*/
public BufferedImage getTileSetImage ()
{
return _improv.getTileSetImage(_imagePath, _zations);
}
/**
* Returns the raw (unprepared) image that would be used by the tile
* at the specified index.
*/
public BufferedImage getTileImage (int tileIndex)
throws NoSuchTileException
{
checkTileIndex(tileIndex);
Rectangle bounds = computeTileBounds(tileIndex);
BufferedImage timg = getTileSetImage();
return timg.getSubimage(bounds.x, bounds.y,
bounds.width, bounds.height);
}
/**
* Returns a prepared version of the image that would be used by the
* tile at the specified index. Because tilesets are often used simply
* to provide access to a collection of uniform images, this method is
* provided to bypass the creation of a {@link Tile} object when all
* that is desired is access to the underlying image.
*/
public Mirage getTileMirage (int tileIndex)
throws NoSuchTileException
{
checkTileIndex(tileIndex);
Rectangle bounds = computeTileBounds(tileIndex);
if (_improv == null) {
Log.warning("Aiya! Tile set missing image provider " +
"[path=" + _imagePath + "].");
}
return _improv.getTileImage(_imagePath, bounds, _zations);
}
/**
* Used to ensure that the specified tile index is valid.
*/
protected void checkTileIndex (int tileIndex)
throws NoSuchTileException
{
// bail if there's no such tile // bail if there's no such tile
int tcount = getTileCount();
if (tileIndex < 0 || tileIndex >= tcount) { if (tileIndex < 0 || tileIndex >= tcount) {
throw new NoSuchTileException(tileIndex); throw new NoSuchTileException(tileIndex);
} }
// create our tile cache array if necessary
if (_tiles == null) {
_tiles = new Tile[tcount];
}
// fill in the cache if necessary
if (_tiles[tileIndex] == null) {
// get our tileset image
Image tsimg = getTileSetImage();
if (tsimg == null) {
// we already logged an error, so we can just freak out
throw new NoSuchTileException(tileIndex);
}
// create, initialize and cache the tile object
Tile tile = createTile(tileIndex, tsimg);
initTile(tile);
// _tiles[tileIndex] = tile;
return tile;
}
return _tiles[tileIndex];
} }
/** /**
@@ -210,25 +222,20 @@ public abstract class TileSet
* *
* @param tileIndex the index of the tile whose bounds are to be * @param tileIndex the index of the tile whose bounds are to be
* computed. * computed.
* @param tilesetImage the tileset image that contains the imagery for
* the tile in question.
*/ */
protected abstract Rectangle computeTileBounds ( protected abstract Rectangle computeTileBounds (int tileIndex);
int tileIndex, Image tilesetImage);
/** /**
* Creates a tile for the specified tile index. * Creates a tile for the specified tile index.
* *
* @param tileIndex the index of the tile to be created. * @param tileIndex the index of the tile to be created.
* @param tilesetImage the tileset image that contains the imagery for * @param image the tile image in the form of a {@link Mirage}.
* the tile to be created.
* *
* @return a configured tile. * @return a configured tile.
*/ */
protected Tile createTile (int tileIndex, Image tilesetImage) protected Tile createTile (int tileIndex, Mirage image)
{ {
return new Tile(tilesetImage, return new Tile(image);
computeTileBounds(tileIndex, tilesetImage));
} }
/** /**
@@ -243,42 +250,6 @@ public abstract class TileSet
// nothing for now // nothing for now
} }
/**
* Returns the tileset image (which is loaded if it has not yet been
* loaded). Generally this is not called by external entities, rather
* {@link #getTile} is used and the image rendered by rendering the
* tile.
*
* @return the tileset image or null if an error occurred loading the
* image.
*/
public Image getTileSetImage ()
{
// return it straight away if it's already loaded
if (_tilesetImg != null) {
return _tilesetImg;
}
// load up the tileset image via the image provider
try {
_tilesetImg = _improv.loadImage(_imagePath);
// HACKOLA: if we're a vessel tileset, colorize ourselves
if (_name != null && (this instanceof ObjectTileSet) &&
_name.indexOf("Vessel") != -1) {
_tilesetImg = ImageUtil.recolorImage(
(BufferedImage)_tilesetImg, ZATIONS);
}
} catch (IOException ioe) {
Log.warning("Failed to retrieve tileset image " +
"[name=" + _name + ", path=" + _imagePath +
", error=" + ioe + "].");
}
return _tilesetImg;
}
/** /**
* Generates a string representation of the tileset information. * Generates a string representation of the tileset information.
*/ */
@@ -307,18 +278,12 @@ public abstract class TileSet
/** The tileset name. */ /** The tileset name. */
protected String _name; protected String _name;
/** A sparse array containing the tiles that have been requested from /** Colorizations to be applied to tiles created from this tileset. */
* this tileset. */ protected transient Colorization[] _zations;
protected transient Tile[] _tiles;
/** The entity from which we obtain our tile image. */ /** The entity from which we obtain our tile image. */
protected transient ImageProvider _improv; protected transient ImageProvider _improv;
/** The image containing all tile images for this set. This is private
* because it should be accessed via {@link #getTileSetImage} even by
* derived classes. */
private transient Image _tilesetImg;
/** Increase this value when object's serialized state is impacted by /** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */ * a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1; private static final long serialVersionUID = 1;
@@ -332,4 +297,10 @@ public abstract class TileSet
new float[] { .1f, .3f, 0.6f }, new float[] { .1f, .3f, 0.6f },
new float[] { .71f, 0f, .07f }), new float[] { .71f, 0f, .07f }),
}; };
/** A weak cache of our tiles. */
protected static LRUHashMap _tiles;
/** The default tile cache size. */
protected static final int DEFAULT_TILE_CACHE_SIZE = 500;
} }
@@ -1,11 +1,13 @@
// //
// $Id: TileSetRepository.java,v 1.3 2001/11/29 00:12:11 mdb Exp $ // $Id: TileSetRepository.java,v 1.4 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.util.Iterator; import java.util.Iterator;
import com.samskivert.io.PersistenceException; import com.samskivert.io.PersistenceException;
import com.threerings.media.image.ImageDataProvider;
/** /**
* The tileset repository interface should be implemented by classes that * The tileset repository interface should be implemented by classes that
* provide access to tilesets keyed on a unique tileset identifier. The * provide access to tilesets keyed on a unique tileset identifier. The
@@ -28,7 +30,9 @@ public interface TileSetRepository
throws PersistenceException; throws PersistenceException;
/** /**
* Returns the {@link TileSet} with the specified tile set identifier. * Returns the {@link TileSet} with the specified tile set
* identifier. The repository is responsible for configuring the tile
* set with an image provider.
* *
* @exception NoSuchTileSetException thrown if no tileset exists with * @exception NoSuchTileSetException thrown if no tileset exists with
* the specified identifier. * the specified identifier.
@@ -37,4 +41,17 @@ public interface TileSetRepository
*/ */
public TileSet getTileSet (int tileSetId) public TileSet getTileSet (int tileSetId)
throws NoSuchTileSetException, PersistenceException; throws NoSuchTileSetException, PersistenceException;
/**
* Returns the {@link TileSet} with the specified tile set name. The
* repository is responsible for configuring the tile set with an
* image provider.
*
* @exception NoSuchTileSetException thrown if no tileset exists with
* the specified name.
* @exception PersistenceException thrown if an error occurs
* communicating with the underlying persistence mechanism.
*/
public TileSet getTileSet (String setName)
throws NoSuchTileSetException, PersistenceException;
} }
@@ -1,40 +1,13 @@
// //
// $Id: TileUtil.java,v 1.3 2002/05/06 18:08:32 mdb Exp $ // $Id: TileUtil.java,v 1.4 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import com.threerings.media.Log;
/** /**
* Miscellaneous utility routines for working with tiles. * Miscellaneous utility routines for working with tiles.
*/ */
public class TileUtil public class TileUtil
{ {
/**
* Returns the image associated with the given tile from the given
* tile manager, or null if an error occurred. Any exceptions that
* occur are logged.
*
* @param tilemgr the tile manager via which the tile should be
* fetched.
* @param tileSetId the id of the tileset from which to fetch the
* tile.
* @param tileIndex the index of the tile to be fetched.
*/
public static Tile getTile (
TileManager tilemgr, int tileSetId, int tileIndex)
{
try {
TileSet set = tilemgr.getTileSet(tileSetId);
return set.getTile(tileIndex);
} catch (TileException te) {
Log.warning("Error retrieving tile [tsid=" + tileSetId +
", tidx=" + tileIndex + ", error=" + te + "].");
return null;
}
}
/** /**
* Generates a fully-qualified tileid given the supplied tileset id * Generates a fully-qualified tileid given the supplied tileset id
* and tile index. This fully-qualified id can be used to fetch the * and tile index. This fully-qualified id can be used to fetch the
@@ -1,9 +1,8 @@
// //
// $Id: TrimmedObjectTileSet.java,v 1.2 2003/01/12 01:19:58 shaper Exp $ // $Id: TrimmedObjectTileSet.java,v 1.3 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.awt.Image;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.io.IOException; import java.io.IOException;
@@ -12,6 +11,7 @@ import java.io.OutputStream;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.image.Mirage;
import com.threerings.media.tile.util.TileSetTrimmer; import com.threerings.media.tile.util.TileSetTrimmer;
/** /**
@@ -30,16 +30,15 @@ public class TrimmedObjectTileSet extends TileSet
} }
// documentation inherited // documentation inherited
protected Rectangle computeTileBounds (int tileIndex, Image tilesetImage) protected Rectangle computeTileBounds (int tileIndex)
{ {
// N/A return _bounds[tileIndex];
return null;
} }
// documentation inherited // documentation inherited
protected Tile createTile (int tileIndex, Image tilesetImage) protected Tile createTile (int tileIndex, Mirage tileImage)
{ {
ObjectTile tile = new ObjectTile(tilesetImage, _bounds[tileIndex]); ObjectTile tile = new ObjectTile(tileImage);
tile.setBase(_ometrics[tileIndex].width, _ometrics[tileIndex].height); tile.setBase(_ometrics[tileIndex].width, _ometrics[tileIndex].height);
tile.setOrigin(_ometrics[tileIndex].x, _ometrics[tileIndex].y); tile.setOrigin(_ometrics[tileIndex].x, _ometrics[tileIndex].y);
return tile; return tile;
@@ -1,18 +1,14 @@
// //
// $Id: TrimmedTile.java,v 1.4 2003/01/08 04:09:02 mdb Exp $ // $Id: TrimmedTile.java,v 1.5 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.awt.Graphics; import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.image.BufferedImage;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.threerings.media.image.ImageUtil; import com.threerings.media.image.Mirage;
/** /**
* Behaves just like a regular tile, but contains a "trimmed" image which * Behaves just like a regular tile, but contains a "trimmed" image which
@@ -28,61 +24,48 @@ public class TrimmedTile extends Tile
* *
* @param tilesetSource the tileset image that contains our trimmed * @param tilesetSource the tileset image that contains our trimmed
* tile image. * tile image.
* @param bounds contains the width and height of the * @param tbounds contains the width and height of the
* <em>untrimmed</em> tile, but the x and y offset of the * <em>untrimmed</em> tile, but the x and y offset of the
* <em>trimmed</em> tile image in the supplied tileset source image. * <em>trimmed</em> tile image in the original untrimmed tile image.
* @param tbounds the bounds of the trimmed image in the coordinate
* system defined by the untrimmed image.
*/ */
public TrimmedTile (Image image, Rectangle bounds, Rectangle tbounds) public TrimmedTile (Mirage image, Rectangle tbounds)
{ {
super(image, bounds); super(image);
_tbounds = tbounds; _tbounds = tbounds;
} }
// documentation inherited // documentation inherited
public void paint (Graphics gfx, int x, int y) public int getWidth ()
{ {
if (_subimage == null) { return _tbounds.width;
createSubImage();
}
gfx.drawImage(_subimage, x + _tbounds.x, y + _tbounds.y, null);
}
/**
* Returns the bounds of the trimmed image within the coordinate
* system defined by the complete virtual tile. The returned rectangle
* should <em>not</em> be modified.
*/
public Rectangle getTrimmedBounds ()
{
return _tbounds;
} }
// documentation inherited // documentation inherited
public Image getImage () public int getHeight ()
{ {
String errmsg = "Can't convert trimmed tile to image " + return _tbounds.height;
"[tile=" + this + "]."; }
throw new RuntimeException(errmsg);
// documentation inherited
public void paint (Graphics2D gfx, int x, int y)
{
_mirage.paint(gfx, x + _tbounds.x, y + _tbounds.y);
}
/**
* Fills in the bounds of the trimmed image within the coordinate
* system defined by the complete virtual tile.
*/
public void getTrimmedBounds (Rectangle tbounds)
{
tbounds.setBounds(_tbounds.x, _tbounds.y,
_mirage.getWidth(), _mirage.getHeight());
} }
// documentation inherited // documentation inherited
public boolean hitTest (int x, int y) public boolean hitTest (int x, int y)
{ {
return ImageUtil.hitTest(_image, _bounds.x + x, _bounds.y + y); return super.hitTest(x - _tbounds.x, y - _tbounds.y);
}
// documentation inherited
protected void createSubImage ()
{
if (_image instanceof BufferedImage) {
_subimage = ImageUtil.getSubimage(_image, _bounds.x, _bounds.y,
_tbounds.width, _tbounds.height);
} else {
String errmsg = "Can't obtain tile image [tile=" + this + "].";
throw new RuntimeException(errmsg);
}
} }
// documentation inherited // documentation inherited
@@ -91,7 +74,6 @@ public class TrimmedTile extends Tile
buf.append(", tbounds=").append(StringUtil.toString(_tbounds)); buf.append(", tbounds=").append(StringUtil.toString(_tbounds));
} }
/** The dimensions of the trimmed image in the coordinate space /** Our extra trimmed image dimension information. */
* defined by the untrimmed image. */
protected Rectangle _tbounds; protected Rectangle _tbounds;
} }
@@ -1,14 +1,14 @@
// //
// $Id: TrimmedTileSet.java,v 1.5 2002/08/19 22:58:15 mdb Exp $ // $Id: TrimmedTileSet.java,v 1.6 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.awt.Image;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import com.threerings.media.image.Mirage;
import com.threerings.media.tile.util.TileSetTrimmer; import com.threerings.media.tile.util.TileSetTrimmer;
/** /**
@@ -24,17 +24,15 @@ public class TrimmedTileSet extends TileSet
} }
// documentation inherited // documentation inherited
protected Rectangle computeTileBounds (int tileIndex, Image tilesetImage) protected Rectangle computeTileBounds (int tileIndex)
{ {
// N/A return _obounds[tileIndex];
return null;
} }
// documentation inherited // documentation inherited
protected Tile createTile (int tileIndex, Image tilesetImage) protected Tile createTile (int tileIndex, Mirage image)
{ {
return new TrimmedTile( return new TrimmedTile(image, _tbounds[tileIndex]);
tilesetImage, _obounds[tileIndex], _tbounds[tileIndex]);
} }
/** /**
@@ -51,23 +49,13 @@ public class TrimmedTileSet extends TileSet
final TrimmedTileSet tset = new TrimmedTileSet(); final TrimmedTileSet tset = new TrimmedTileSet();
tset.setName(source.getName()); tset.setName(source.getName());
int tcount = source.getTileCount(); int tcount = source.getTileCount();
tset._tbounds = new Rectangle[tcount];
tset._obounds = new Rectangle[tcount];
// grab the dimensions of the original tiles // grab the dimensions of the original tiles
tset._obounds = new Rectangle[tcount];
for (int ii = 0; ii < tcount; ii++) { for (int ii = 0; ii < tcount; ii++) {
try { tset._tbounds[ii] = source.computeTileBounds(ii);
Tile tile = source.getTile(ii);
tset._obounds[ii] = new Rectangle();
tset._obounds[ii].width = tile.getWidth();
tset._obounds[ii].height = tile.getHeight();
} catch (NoSuchTileException nste) {
String errmsg = "Urk! TileSet is ill-behaved. " +
"Claimed to have " + tcount + " tiles, but choked when " +
"we asked for tile " + ii + " [tset=" + source + "].";
throw new RuntimeException(errmsg);
}
} }
tset._tbounds = new Rectangle[tcount];
// create the trimmed tileset image // create the trimmed tileset image
TileSetTrimmer.TrimMetricsReceiver tmr = TileSetTrimmer.TrimMetricsReceiver tmr =
@@ -75,10 +63,10 @@ public class TrimmedTileSet extends TileSet
public void trimmedTile (int tileIndex, int imageX, int imageY, public void trimmedTile (int tileIndex, int imageX, int imageY,
int trimX, int trimY, int trimX, int trimY,
int trimWidth, int trimHeight) { int trimWidth, int trimHeight) {
tset._obounds[tileIndex].x = imageX; tset._tbounds[tileIndex].x = trimX;
tset._obounds[tileIndex].y = imageY; tset._tbounds[tileIndex].y = trimY;
tset._tbounds[tileIndex] = tset._obounds[tileIndex] =
new Rectangle(trimX, trimY, trimWidth, trimHeight); new Rectangle(imageX, imageY, trimWidth, trimHeight);
} }
}; };
TileSetTrimmer.trimTileSet(source, destImage, tmr); TileSetTrimmer.trimTileSet(source, destImage, tmr);
@@ -86,11 +74,11 @@ public class TrimmedTileSet extends TileSet
return tset; return tset;
} }
/** The width and height of the untrimmed tile, and the x and y offset /** The width and height of the trimmed tile, and the x and y offset
* of the trimmed image within our tileset image. */ * of the trimmed image within our tileset image. */
protected Rectangle[] _obounds; protected Rectangle[] _obounds;
/** The width and height of the trimmed image and the x and y offset /** The width and height of the untrimmed image and the x and y offset
* within the untrimmed image at which the trimmed image should be * within the untrimmed image at which the trimmed image should be
* rendered. */ * rendered. */
protected Rectangle[] _tbounds; protected Rectangle[] _tbounds;
@@ -1,13 +1,10 @@
// //
// $Id: UniformTileSet.java,v 1.10 2003/01/08 04:09:02 mdb Exp $ // $Id: UniformTileSet.java,v 1.11 2003/01/13 22:49:46 mdb Exp $
package com.threerings.media.tile; package com.threerings.media.tile;
import java.awt.Image;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import com.threerings.media.Log;
import com.threerings.media.image.ImageUtil;
/** /**
* A uniform tileset is one that is composed of tiles that are all the * A uniform tileset is one that is composed of tiles that are all the
@@ -65,28 +62,13 @@ public class UniformTileSet extends TileSet
return _height; return _height;
} }
/**
* Returns the image that would be used by the tile at the specified
* index. Because the uniform tileset is often used simply to provide
* access to a collection of uniform images, this method is provided
* to bypass the creation of a {@link Tile} object when all that is
* desired is access to the underlying image.
*/
public Image getTileImage (int tileIndex)
{
Image tsimg = getTileSetImage();
if (tsimg == null) {
return null;
}
Rectangle tb = computeTileBounds(tileIndex, tsimg);
return ImageUtil.getSubimage(tsimg, tb.x, tb.y, tb.width, tb.height);
}
// documentation inherited // documentation inherited
protected Rectangle computeTileBounds (int tileIndex, Image tilesetImage) protected Rectangle computeTileBounds (int tileIndex)
{ {
BufferedImage tsimg = getTileSetImage();
// figure out from whence to crop the tile // figure out from whence to crop the tile
int tilesPerRow = tilesetImage.getWidth(null) / _width; int tilesPerRow = tsimg.getWidth() / _width;
int row = tileIndex / tilesPerRow; int row = tileIndex / tilesPerRow;
int col = tileIndex % tilesPerRow; int col = tileIndex % tilesPerRow;
@@ -1,5 +1,5 @@
// //
// $Id: BundledTileSetRepository.java,v 1.8 2003/01/08 04:09:02 mdb Exp $ // $Id: BundledTileSetRepository.java,v 1.9 2003/01/13 22:49:47 mdb Exp $
package com.threerings.media.tile.bundle; package com.threerings.media.tile.bundle;
@@ -16,7 +16,10 @@ import com.threerings.resource.ResourceBundle;
import com.threerings.resource.ResourceManager; import com.threerings.resource.ResourceManager;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.image.ImageDataProvider;
import com.threerings.media.image.ImageManager; import com.threerings.media.image.ImageManager;
import com.threerings.media.tile.IMImageProvider;
import com.threerings.media.tile.NoSuchTileSetException; import com.threerings.media.tile.NoSuchTileSetException;
import com.threerings.media.tile.TileSet; import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TileSetRepository; import com.threerings.media.tile.TileSetRepository;
@@ -35,13 +38,13 @@ public class BundledTileSetRepository
* *
* @param rmgr the resource manager from which to obtain our resource * @param rmgr the resource manager from which to obtain our resource
* set. * set.
* @param imgr the image manager that we'll use to decode and cache * @param imgr the image manager through which we will configure the
* images. * tile sets to load their images.
* @param name the name of the resource set from which we will be * @param name the name of the resource set from which we will be
* loading our tile data. * loading our tile data.
*/ */
public BundledTileSetRepository ( public BundledTileSetRepository (ResourceManager rmgr, ImageManager imgr,
ResourceManager rmgr, ImageManager imgr, String name) String name)
{ {
// first we obtain the resource set from which we will load up our // first we obtain the resource set from which we will load up our
// tileset bundles // tileset bundles
@@ -64,7 +67,7 @@ public class BundledTileSetRepository
// unserialize our tileset bundle // unserialize our tileset bundle
TileSetBundle tsb = BundleUtil.extractBundle(rbundles[i]); TileSetBundle tsb = BundleUtil.extractBundle(rbundles[i]);
// initialize it and add it to the list // initialize it and add it to the list
tsb.init(rbundles[i], imgr); tsb.init(rbundles[i]);
tbundles.add(tsb); tbundles.add(tsb);
} catch (Exception e) { } catch (Exception e) {
@@ -78,43 +81,15 @@ public class BundledTileSetRepository
// finally create one big fat array of all of the tileset bundles // finally create one big fat array of all of the tileset bundles
_bundles = new TileSetBundle[tbundles.size()]; _bundles = new TileSetBundle[tbundles.size()];
tbundles.toArray(_bundles); tbundles.toArray(_bundles);
}
/** // create image providers for our bundles
* Searches for the tileset with the specified name, which must reside _improvs = new IMImageProvider[_bundles.length];
* in a tileset bundle identified by the supplied bundle identifying for (int ii = 0; ii < _bundles.length; ii++) {
* string. _improvs[ii] = new IMImageProvider(imgr, _bundles[ii]);
*
* @param bundleId a string that will be substring matched against the
* names of all known tileset bundles. Only bundles whose bundle file
* path contains this string will be searched.
* @param setName the name of the tileset to be located.
*
* @return The first tileset matching the supplied parameters or null
* if no matching tileset could be found.
*/
public TileSet locateTileSet (String bundleId, String setName)
{
int bcount = _bundles.length;
for (int ii = 0; ii < bcount; ii++) {
TileSetBundle tsb = _bundles[ii];
// skip non-matching bundles
if (tsb.getSource().getPath().indexOf(bundleId) == -1) {
continue;
}
// search for the tileset in this bundle
Iterator tsiter = tsb.enumerateTileSets();
while (tsiter.hasNext()) {
TileSet set = (TileSet)tsiter.next();
if (set.getName().equals(setName)) {
return set;
}
}
} }
return null;
} }
// documentation inherited // documentation inherited from interface
public Iterator enumerateTileSetIds () public Iterator enumerateTileSetIds ()
throws PersistenceException throws PersistenceException
{ {
@@ -130,7 +105,7 @@ public class BundledTileSetRepository
}); });
} }
// documentation inherited // documentation inherited from interface
public Iterator enumerateTileSets () public Iterator enumerateTileSets ()
throws PersistenceException throws PersistenceException
{ {
@@ -146,7 +121,7 @@ public class BundledTileSetRepository
}); });
} }
// documentation inherited // documentation inherited from interface
public TileSet getTileSet (int tileSetId) public TileSet getTileSet (int tileSetId)
throws NoSuchTileSetException, PersistenceException throws NoSuchTileSetException, PersistenceException
{ {
@@ -155,12 +130,36 @@ public class BundledTileSetRepository
for (int i = 0; i < blength; i++) { for (int i = 0; i < blength; i++) {
tset = _bundles[i].getTileSet(tileSetId); tset = _bundles[i].getTileSet(tileSetId);
if (tset != null) { if (tset != null) {
tset.setImageProvider(_improvs[i]);
return tset; return tset;
} }
} }
throw new NoSuchTileSetException(tileSetId); throw new NoSuchTileSetException(tileSetId);
} }
// documentation inherited from interface
public TileSet getTileSet (String setName)
throws NoSuchTileSetException, PersistenceException
{
int bcount = _bundles.length;
for (int ii = 0; ii < bcount; ii++) {
TileSetBundle tsb = _bundles[ii];
// search for the tileset in this bundle
Iterator tsiter = tsb.enumerateTileSets();
while (tsiter.hasNext()) {
TileSet set = (TileSet)tsiter.next();
if (set.getName().equals(setName)) {
set.setImageProvider(_improvs[ii]);
return set;
}
}
}
return null;
}
/** An array of tileset bundles from which we obtain tilesets. */ /** An array of tileset bundles from which we obtain tilesets. */
protected TileSetBundle[] _bundles; protected TileSetBundle[] _bundles;
/** Image providers for each of our tile set bundles. */
protected IMImageProvider[] _improvs;
} }
@@ -1,10 +1,12 @@
// //
// $Id: TileSetBundle.java,v 1.12 2003/01/08 04:09:02 mdb Exp $ // $Id: TileSetBundle.java,v 1.13 2003/01/13 22:49:47 mdb Exp $
package com.threerings.media.tile.bundle; package com.threerings.media.tile.bundle;
import java.awt.Image; import java.awt.Image;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.ImageInputStream;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -20,8 +22,7 @@ import com.samskivert.util.HashIntMap;
import com.threerings.resource.ResourceBundle; import com.threerings.resource.ResourceBundle;
import com.threerings.media.image.ImageManager; import com.threerings.media.image.ImageDataProvider;
import com.threerings.media.tile.ImageProvider;
import com.threerings.media.tile.TileSet; import com.threerings.media.tile.TileSet;
/** /**
@@ -29,17 +30,16 @@ import com.threerings.media.tile.TileSet;
* bundle of tilesets stored on the local filesystem. * bundle of tilesets stored on the local filesystem.
*/ */
public class TileSetBundle extends HashIntMap public class TileSetBundle extends HashIntMap
implements Serializable, ImageProvider implements Serializable, ImageDataProvider
{ {
/** /**
* Initializes this resource bundle with a reference to the jarfile * Initializes this resource bundle with a reference to the jarfile
* from which it was loaded and from which it can load image data. The * from which it was loaded and from which it can load image data. The
* image manager will be used to decode the images. * image manager will be used to decode the images.
*/ */
public void init (ResourceBundle bundle, ImageManager imgr) public void init (ResourceBundle bundle)
{ {
_bundle = bundle; _bundle = bundle;
_imgr = imgr;
} }
/** /**
@@ -82,19 +82,17 @@ public class TileSetBundle extends HashIntMap
return values().iterator(); return values().iterator();
} }
// documentation inherited // documentation inherited from interface
public Image loadImage (String path) public String getIdent ()
{
return "tsb:" + _bundle.getSource();
}
// documentation inherited from interface
public ImageInputStream loadImageData (String path)
throws IOException throws IOException
{ {
// obtain the image data from our jarfile return new FileImageInputStream(_bundle.getResourceFile(path));
InputStream imgin = _bundle.getResource(path);
if (imgin == null) {
String errmsg = "No such image in resource bundle " +
"[bundle=" + _bundle + ", path=" + path + "].";
throw new FileNotFoundException(errmsg);
}
// return _imgr.createImage(imgin);
return ImageIO.read(imgin);
} }
// custom serialization process // custom serialization process
@@ -120,7 +118,6 @@ public class TileSetBundle extends HashIntMap
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
int tileSetId = in.readInt(); int tileSetId = in.readInt();
TileSet set = (TileSet)in.readObject(); TileSet set = (TileSet)in.readObject();
set.setImageProvider(this);
put(tileSetId, set); put(tileSetId, set);
} }
} }
@@ -128,10 +125,7 @@ public class TileSetBundle extends HashIntMap
/** That from which we load our tile images. */ /** That from which we load our tile images. */
protected transient ResourceBundle _bundle; protected transient ResourceBundle _bundle;
/** We use the image manager to decode our images. */
protected transient ImageManager _imgr;
/** Increase this value when object's serialized state is impacted by /** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */ * a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1; private static final long serialVersionUID = 2;
} }
@@ -1,5 +1,5 @@
// //
// $Id: DumpBundle.java,v 1.8 2003/01/08 04:09:02 mdb Exp $ // $Id: DumpBundle.java,v 1.9 2003/01/13 22:49:47 mdb Exp $
package com.threerings.media.tile.bundle.tools; package com.threerings.media.tile.bundle.tools;
@@ -9,8 +9,6 @@ import java.util.Iterator;
import com.threerings.resource.ResourceManager; import com.threerings.resource.ResourceManager;
import com.threerings.resource.ResourceBundle; import com.threerings.resource.ResourceBundle;
import com.threerings.media.image.ImageManager;
import com.threerings.media.tile.TileSet; import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.bundle.BundleUtil; import com.threerings.media.tile.bundle.BundleUtil;
import com.threerings.media.tile.bundle.TileSetBundle; import com.threerings.media.tile.bundle.TileSetBundle;
@@ -34,7 +32,6 @@ public class DumpBundle
// create a resource and image manager in case they want to dump // create a resource and image manager in case they want to dump
// the tiles // the tiles
ResourceManager rmgr = new ResourceManager("rsrc"); ResourceManager rmgr = new ResourceManager("rsrc");
ImageManager imgr = new ImageManager(rmgr, null);
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
// oh the hackery // oh the hackery
@@ -47,7 +44,7 @@ public class DumpBundle
try { try {
ResourceBundle bundle = new ResourceBundle(file); ResourceBundle bundle = new ResourceBundle(file);
TileSetBundle tsb = BundleUtil.extractBundle(bundle); TileSetBundle tsb = BundleUtil.extractBundle(bundle);
tsb.init(bundle, imgr); tsb.init(bundle);
Iterator tsids = tsb.enumerateTileSetIds(); Iterator tsids = tsb.enumerateTileSetIds();
while (tsids.hasNext()) { while (tsids.hasNext()) {
@@ -1,9 +1,10 @@
// //
// $Id: TileSetBundler.java,v 1.9 2003/01/12 01:19:33 shaper Exp $ // $Id: TileSetBundler.java,v 1.10 2003/01/13 22:49:47 mdb Exp $
package com.threerings.media.tile.bundle.tools; package com.threerings.media.tile.bundle.tools;
import java.awt.Image; import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.io.File; import java.io.File;
@@ -31,9 +32,11 @@ import com.samskivert.io.NestableIOException;
import com.samskivert.io.PersistenceException; import com.samskivert.io.PersistenceException;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.Mirage;
import com.threerings.media.tile.ImageProvider;
import com.threerings.media.tile.ObjectTileSet; import com.threerings.media.tile.ObjectTileSet;
import com.threerings.media.tile.SimpleCachingImageProvider;
import com.threerings.media.tile.TileSet; import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TileSetIDBroker; import com.threerings.media.tile.TileSetIDBroker;
import com.threerings.media.tile.TrimmedObjectTileSet; import com.threerings.media.tile.TrimmedObjectTileSet;
@@ -54,39 +57,39 @@ import com.threerings.media.tile.tools.xml.TileSetRuleSet;
* parsers. An example configuration follows: * parsers. An example configuration follows:
* *
* <pre> * <pre>
* <bundler-config> * &lt;bundler-config&gt;
* <mapping> * &lt;mapping&gt;
* <path>bundle.tilesets.uniform</path> * &lt;path&gt;bundle.tilesets.uniform&lt;/path&gt;
* <ruleset> * &lt;ruleset&gt;
* com.threerings.media.tile.tools.xml.UniformTileSetRuleSet * com.threerings.media.tile.tools.xml.UniformTileSetRuleSet
* </ruleset> * &lt;/ruleset&gt;
* </mapping> * &lt;/mapping&gt;
* <mapping> * &lt;mapping&gt;
* <path>bundle.tilesets.object</path> * &lt;path&gt;bundle.tilesets.object&lt;/path&gt;
* <ruleset> * &lt;ruleset&gt;
* com.threerings.media.tile.tools.xml.ObjectTileSetRuleSet * com.threerings.media.tile.tools.xml.ObjectTileSetRuleSet
* </ruleset> * &lt;/ruleset&gt;
* </mapping> * &lt;/mapping&gt;
* </bundler-config> * &lt;/bundler-config&gt;
* </pre> * </pre>
* *
* This configuration would be used to parse a bundle description that * This configuration would be used to parse a bundle description that
* looked something like the following: * looked something like the following:
* *
* <pre> * <pre>
* <bundle> * &lt;bundle&gt;
* <tilesets> * &lt;tilesets&gt;
* <uniform> * &lt;uniform&gt;
* <tileset> * &lt;tileset&gt;
* <!-- ... --> * &lt;!-- ... --&gt;
* </tileset> * &lt;/tileset&gt;
* </uniform> * &lt;/uniform&gt;
* <object> * &lt;object&gt;
* <tileset> * &lt;tileset&gt;
* <!-- ... --> * &lt;!-- ... --&gt;
* </tileset> * &lt;/tileset&gt;
* </object> * &lt;/object&gt;
* </tilesets> * &lt;/tilesets&gt;
* </pre> * </pre>
* *
* The class specified in the <code>ruleset</code> element must derive * The class specified in the <code>ruleset</code> element must derive
@@ -277,6 +280,14 @@ public class TileSetBundler
Manifest manifest = new Manifest(); Manifest manifest = new Manifest();
JarOutputStream jar = new JarOutputStream(fout, manifest); JarOutputStream jar = new JarOutputStream(fout, manifest);
// create an image provider for loading our tileset images
SimpleCachingImageProvider improv = new SimpleCachingImageProvider() {
protected BufferedImage loadImage (String path)
throws IOException {
return ImageIO.read(new File(bundleDesc.getParent(), path));
}
};
try { try {
// write all of the image files to the bundle, converting the // write all of the image files to the bundle, converting the
// tilesets to trimmed tilesets in the process // tilesets to trimmed tilesets in the process
@@ -300,14 +311,8 @@ public class TileSetBundler
if (set instanceof ObjectTileSet) { if (set instanceof ObjectTileSet) {
// set the tileset up with an image provider; we need to // set the tileset up with an image provider; we need to
// do this so that we can trim it! // do this so that we can trim it!
set.setImageProvider(new ImageProvider() { set.setImageProvider(improv);
public Image loadImage (String path)
throws IOException {
File source = new File(bundleDesc.getParent(),
path);
return ImageIO.read(source);
}
});
// create a trimmed object tileset, which will write // create a trimmed object tileset, which will write
// the trimmed tileset image to the jar output stream // the trimmed tileset image to the jar output stream
TrimmedObjectTileSet tset = TrimmedObjectTileSet tset =
@@ -1,5 +1,5 @@
// //
// $Id: TileSetTrimmer.java,v 1.5 2003/01/08 04:09:03 mdb Exp $ // $Id: TileSetTrimmer.java,v 1.6 2003/01/13 22:49:47 mdb Exp $
package com.threerings.media.tile.util; package com.threerings.media.tile.util;
@@ -20,7 +20,6 @@ import com.samskivert.util.StringUtil;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.image.ImageUtil; import com.threerings.media.image.ImageUtil;
import com.threerings.media.tile.NoSuchTileException;
import com.threerings.media.tile.Tile; import com.threerings.media.tile.Tile;
import com.threerings.media.tile.TileSet; import com.threerings.media.tile.TileSet;
@@ -88,17 +87,11 @@ public class TileSetTrimmer
for (int ii = 0; ii < tcount; ii++) { for (int ii = 0; ii < tcount; ii++) {
// extract the image from the original tileset // extract the image from the original tileset
try { try {
Tile tile = source.getTile(ii); timgs[ii] = source.getTileImage(ii);
timgs[ii] = (BufferedImage)tile.getImage();
} catch (RasterFormatException rfe) { } catch (RasterFormatException rfe) {
throw new IOException("Failed to get tile image " + throw new IOException("Failed to get tile image " +
"[tidx=" + ii + ", tset=" + source + "[tidx=" + ii + ", tset=" + source +
", rfe=" + rfe + "]."); ", rfe=" + rfe + "].");
} catch (NoSuchTileException nste) {
throw new RuntimeException("WTF? No such tile [tset=" + source +
", tidx=" + ii + "]");
} }
// figure out how tightly we can trim it // figure out how tightly we can trim it
@@ -1,10 +1,9 @@
// //
// $Id: MultiFrameImage.java,v 1.3 2002/09/17 19:11:13 mdb Exp $ // $Id: MultiFrameImage.java,v 1.4 2003/01/13 22:49:47 mdb Exp $
package com.threerings.media.util; package com.threerings.media.util;
import java.awt.Graphics; import java.awt.Graphics2D;
import java.awt.Image;
/** /**
* The multi-frame image interface provides encapsulated access to a set * The multi-frame image interface provides encapsulated access to a set
@@ -31,7 +30,7 @@ public interface MultiFrameImage
* Renders the specified frame into the specified graphics object at * Renders the specified frame into the specified graphics object at
* the specified coordinates. * the specified coordinates.
*/ */
public void paintFrame (Graphics g, int index, int x, int y); public void paintFrame (Graphics2D g, int index, int x, int y);
/** /**
* Returns true if the specified frame contains a non-transparent * Returns true if the specified frame contains a non-transparent
@@ -1,12 +1,11 @@
// //
// $Id: MultiFrameImageImpl.java,v 1.4 2003/01/08 04:09:03 mdb Exp $ // $Id: MultiFrameImageImpl.java,v 1.5 2003/01/13 22:49:47 mdb Exp $
package com.threerings.media.util; package com.threerings.media.util;
import java.awt.Graphics; import java.awt.Graphics2D;
import java.awt.Image;
import com.threerings.media.image.ImageUtil; import com.threerings.media.image.Mirage;
/** /**
* A basic implementation of the {@link MultiFrameImage} interface * A basic implementation of the {@link MultiFrameImage} interface
@@ -18,41 +17,41 @@ public class MultiFrameImageImpl implements MultiFrameImage
/** /**
* Constructs a multiple frame image object. * Constructs a multiple frame image object.
*/ */
public MultiFrameImageImpl (Image[] imgs) public MultiFrameImageImpl (Mirage[] mirages)
{ {
_imgs = imgs; _mirages = mirages;
} }
// documentation inherited // documentation inherited
public int getFrameCount () public int getFrameCount ()
{ {
return _imgs.length; return _mirages.length;
} }
// documentation inherited from interface // documentation inherited from interface
public int getWidth (int index) public int getWidth (int index)
{ {
return _imgs[index].getWidth(null); return _mirages[index].getWidth();
} }
// documentation inherited from interface // documentation inherited from interface
public int getHeight (int index) public int getHeight (int index)
{ {
return _imgs[index].getHeight(null); return _mirages[index].getHeight();
} }
// documentation inherited from interface // documentation inherited from interface
public void paintFrame (Graphics g, int index, int x, int y) public void paintFrame (Graphics2D g, int index, int x, int y)
{ {
g.drawImage(_imgs[index], x, y, null); _mirages[index].paint(g, x, y);
} }
// documentation inherited from interface // documentation inherited from interface
public boolean hitTest (int index, int x, int y) public boolean hitTest (int index, int x, int y)
{ {
return ImageUtil.hitTest(_imgs[index], x, y); return _mirages[index].hitTest(x, y);
} }
/** The frame images. */ /** The frame images. */
protected Image _imgs[]; protected Mirage[] _mirages;
} }
@@ -1,12 +1,11 @@
// //
// $Id: SingleFrameImageImpl.java,v 1.4 2003/01/08 04:09:03 mdb Exp $ // $Id: SingleFrameImageImpl.java,v 1.5 2003/01/13 22:49:47 mdb Exp $
package com.threerings.media.util; package com.threerings.media.util;
import java.awt.Graphics; import java.awt.Graphics2D;
import java.awt.Image;
import com.threerings.media.image.ImageUtil; import com.threerings.media.image.Mirage;
/** /**
* The single frame image class is a basic implementation of the {@link * The single frame image class is a basic implementation of the {@link
@@ -18,9 +17,9 @@ public class SingleFrameImageImpl implements MultiFrameImage
/** /**
* Constructs a single frame image object. * Constructs a single frame image object.
*/ */
public SingleFrameImageImpl (Image img) public SingleFrameImageImpl (Mirage mirage)
{ {
_img = img; _mirage = mirage;
} }
// documentation inherited // documentation inherited
@@ -32,27 +31,27 @@ public class SingleFrameImageImpl implements MultiFrameImage
// documentation inherited from interface // documentation inherited from interface
public int getWidth (int index) public int getWidth (int index)
{ {
return _img.getWidth(null); return _mirage.getWidth();
} }
// documentation inherited from interface // documentation inherited from interface
public int getHeight (int index) public int getHeight (int index)
{ {
return _img.getHeight(null); return _mirage.getHeight();
} }
// documentation inherited from interface // documentation inherited from interface
public void paintFrame (Graphics g, int index, int x, int y) public void paintFrame (Graphics2D g, int index, int x, int y)
{ {
g.drawImage(_img, x, y, null); _mirage.paint(g, x, y);
} }
// documentation inherited from interface // documentation inherited from interface
public boolean hitTest (int index, int x, int y) public boolean hitTest (int index, int x, int y)
{ {
return ImageUtil.hitTest(_img, x, y); return _mirage.hitTest(x, y);
} }
/** The frame image. */ /** The frame image. */
protected Image _img; protected Mirage _mirage;
} }
@@ -0,0 +1,57 @@
//
// $Id: SingleTileImageImpl.java,v 1.1 2003/01/13 22:49:47 mdb Exp $
package com.threerings.media.util;
import java.awt.Graphics2D;
import com.threerings.media.tile.Tile;
/**
* The single frame image class is a basic implementation of the {@link
* MultiFrameImage} interface intended to facilitate the creation of MFIs
* whose display frames consist of only a single tile image.
*/
public class SingleTileImageImpl implements MultiFrameImage
{
/**
* Constructs a single frame image object.
*/
public SingleTileImageImpl (Tile tile)
{
_tile = tile;
}
// documentation inherited
public int getFrameCount ()
{
return 1;
}
// documentation inherited from interface
public int getWidth (int index)
{
return _tile.getWidth();
}
// documentation inherited from interface
public int getHeight (int index)
{
return _tile.getHeight();
}
// documentation inherited from interface
public void paintFrame (Graphics2D g, int index, int x, int y)
{
_tile.paint(g, x, y);
}
// documentation inherited from interface
public boolean hitTest (int index, int x, int y)
{
return _tile.hitTest(x, y);
}
/** The frame image. */
protected Tile _tile;
}