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:
@@ -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;
|
||||
|
||||
@@ -100,6 +100,12 @@ public class Colorization
|
||||
return true;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public int hashCode ()
|
||||
{
|
||||
return colorizationId ^ rootColor.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Iterator;
|
||||
|
||||
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
|
||||
@@ -20,6 +29,16 @@ public class ImageIOLoader implements ImageLoader
|
||||
// we need to reference ImageIO in the constructor to force the
|
||||
// classloader to attempt to load the ImageIO classes
|
||||
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
|
||||
@@ -29,10 +48,18 @@ public class ImageIOLoader implements ImageLoader
|
||||
// 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
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import java.awt.Component;
|
||||
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.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
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.util.ConfigUtil;
|
||||
import com.samskivert.util.LRUHashMap;
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.util.Throttle;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
import com.threerings.media.Log;
|
||||
import com.threerings.resource.ResourceManager;
|
||||
@@ -25,6 +41,45 @@ import com.threerings.resource.ResourceManager;
|
||||
*/
|
||||
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
|
||||
* ResourceManager} from which it will obtain its data. A non-null
|
||||
@@ -37,6 +92,12 @@ public class ImageManager
|
||||
{
|
||||
_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 {
|
||||
_loader = (ImageLoader)Class.forName(IMAGEIO_LOADER).newInstance();
|
||||
@@ -45,123 +106,253 @@ public class ImageManager
|
||||
"Falling back to Toolkit [error=" + t + "].");
|
||||
_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
|
||||
* caches it for faster retrieval henceforth.
|
||||
* Creates a buffered image, optimized for display on our graphics
|
||||
* device.
|
||||
*/
|
||||
public Image getImage (String rset, String path)
|
||||
public BufferedImage createImage (int width, int height, int transparency)
|
||||
{
|
||||
if (rset == null || path == null) {
|
||||
String errmsg = "Must supply non-null resource set and path " +
|
||||
"[rset=" + rset + ", path=" + path + "]";
|
||||
return _gc.createCompatibleImage(width, height, transparency);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// periodically report our image cache performance
|
||||
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;
|
||||
return getImage(getImageKey(rset, path), zations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the image via the resource manager using the specified path
|
||||
* and caches it for faster retrieval henceforth.
|
||||
* Returns an image key that can be used to fetch the image identified
|
||||
* 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
|
||||
reportCachePerformance();
|
||||
|
||||
Image img = (Image)_imgs.get(path);
|
||||
if (img != null) {
|
||||
// Log.info("Retrieved image from cache [path=" + path + "].");
|
||||
return img;
|
||||
}
|
||||
return crec.getImage(zations);
|
||||
}
|
||||
|
||||
InputStream imgin = null;
|
||||
try {
|
||||
imgin = _rmgr.getResource(path);
|
||||
img = createImage(imgin);
|
||||
/**
|
||||
* Returns the data provider configured to obtain image data from the
|
||||
* specified resource set.
|
||||
*/
|
||||
protected ImageDataProvider getDataProvider (final String rset)
|
||||
{
|
||||
if (rset == null) {
|
||||
return _defaultProvider;
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
Log.warning("Failure loading image [path=" + path +
|
||||
", error=" + ioe + "].");
|
||||
|
||||
} finally {
|
||||
if (imgin != null) {
|
||||
try {
|
||||
imgin.close();
|
||||
} catch (IOException ioe) {
|
||||
Log.warning("Failure closing image input stream " +
|
||||
"[path=" + path + ", error=" + ioe + "].");
|
||||
ImageDataProvider dprov = (ImageDataProvider)_providers.get(rset);
|
||||
if (dprov == null) {
|
||||
dprov = new ImageDataProvider() {
|
||||
public ImageInputStream loadImageData (String path)
|
||||
throws IOException {
|
||||
// first attempt to load the image from the specified
|
||||
// resource set
|
||||
try {
|
||||
return _rmgr.getImageResource(rset, path);
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
// 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 + "].");
|
||||
if (img != null) {
|
||||
_imgs.put(path, img);
|
||||
} else {
|
||||
Log.warning("Unable to load image [path=" + path + "].");
|
||||
Thread.dumpStack();
|
||||
// fake it so that we don't crap out
|
||||
img = ImageUtil.createImage(1, 1);
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link #loadImage(ImageKey)} to do the actual image
|
||||
* loading.
|
||||
*/
|
||||
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 ()
|
||||
{
|
||||
if (!_cacheStatThrottle.throttleOp()) {
|
||||
long size = ImageUtil.getEstimatedMemoryUsage(
|
||||
_imgs.values().iterator());
|
||||
int[] eff = _imgs.getTrackedEffectiveness();
|
||||
Log.debug("ImageManager LRU [mem=" + (size / 1024) + "k" +
|
||||
", size=" + _imgs.size() + ", hits=" + eff[0] +
|
||||
", misses=" + eff[1] + "].");
|
||||
if (/* Log.getLevel() != Log.log.DEBUG || */
|
||||
_cacheStatThrottle.throttleOp()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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() + "].");
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the image via the resource manager using the specified
|
||||
* 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 path)
|
||||
throws IOException
|
||||
/** Maintains a source image and a set of colorized versions in the
|
||||
* image cache. */
|
||||
protected static class CacheRecord
|
||||
{
|
||||
InputStream imgin = null;
|
||||
try {
|
||||
imgin = _rmgr.getResource(path);
|
||||
return _loader.loadImage(new BufferedInputStream(imgin));
|
||||
public CacheRecord (BufferedImage source)
|
||||
{
|
||||
_source = source;
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException iae) {
|
||||
String errmsg = "Error loading image [path=" + path + "]";
|
||||
throw new NestableIOException(errmsg, iae);
|
||||
|
||||
} finally {
|
||||
if (imgin != null) {
|
||||
imgin.close();
|
||||
public BufferedImage getImage (Colorization[] zations)
|
||||
{
|
||||
if (zations == null) {
|
||||
return _source;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the image from the given resource set using the specified
|
||||
* 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();
|
||||
if (_colorized == null) {
|
||||
_colorized = new ArrayList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the image from the supplied input stream. 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 (InputStream source)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
return _loader.loadImage(new BufferedInputStream(source));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
String errmsg = "Error loading image";
|
||||
throw new NestableIOException(errmsg, iae);
|
||||
}
|
||||
}
|
||||
// we search linearly through our list of colorized copies
|
||||
// because it is not likely to be very long
|
||||
int csize = _colorized.size();
|
||||
for (int ii = 0; ii < csize; ii++) {
|
||||
Tuple tup = (Tuple)_colorized.get(ii);
|
||||
Colorization[] tzations = (Colorization[])tup.left;
|
||||
if (Arrays.equals(zations, tzations)) {
|
||||
return (BufferedImage)tup.right;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an image that is optimized for rendering in the client's
|
||||
* environment and decodes the image data from the specified source
|
||||
* 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;
|
||||
BufferedImage cimage = ImageUtil.recolorImage(_source, zations);
|
||||
_colorized.add(new Tuple(zations, cimage));
|
||||
return cimage;
|
||||
}
|
||||
|
||||
int swidth = src.getWidth(null);
|
||||
int sheight = src.getHeight(null);
|
||||
BufferedImage dest = null;
|
||||
|
||||
// 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);
|
||||
public long getEstimatedMemoryUsage ()
|
||||
{
|
||||
return ImageUtil.getEstimatedMemoryUsage(_source);
|
||||
}
|
||||
|
||||
Graphics2D gfx = dest.createGraphics();
|
||||
gfx.drawImage(src, 0, 0, null);
|
||||
gfx.dispose();
|
||||
public String toString ()
|
||||
{
|
||||
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
|
||||
@@ -300,11 +440,37 @@ public class ImageManager
|
||||
protected ImageLoader _loader;
|
||||
|
||||
/** 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. */
|
||||
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
|
||||
* to use but fallback from if we're not running a JVM that has
|
||||
* ImageIO support. */
|
||||
@@ -312,5 +478,5 @@ public class ImageManager
|
||||
"com.threerings.media.image.ImageIOLoader";
|
||||
|
||||
/** 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;
|
||||
|
||||
@@ -426,8 +426,8 @@ public class ImageUtil
|
||||
|
||||
// copy the RGB from the base image
|
||||
for (int ii=0; ii < 3; ii++) {
|
||||
int[] cdata = basedata.getSamples(0, 0, wid, hei,
|
||||
ii, (int[]) null);
|
||||
int[] cdata = basedata.getSamples(
|
||||
0, 0, wid, hei, ii, (int[]) null);
|
||||
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
|
||||
* 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) {
|
||||
BufferedImage bimage = (BufferedImage)image;
|
||||
int argb = bimage.getRGB(x, y);
|
||||
// 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;
|
||||
}
|
||||
// it's only a hit if the pixel is non-transparent
|
||||
int argb = image.getRGB(x, y);
|
||||
return (argb >> 24) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user