diff --git a/src/java/com/threerings/cast/CharacterManager.java b/src/java/com/threerings/cast/CharacterManager.java index 27b889e8..3850b279 100644 --- a/src/java/com/threerings/cast/CharacterManager.java +++ b/src/java/com/threerings/cast/CharacterManager.java @@ -35,7 +35,7 @@ import com.samskivert.util.Throttle; import com.samskivert.util.Tuple; import com.threerings.media.image.Colorization; -import com.threerings.media.image.BaseImageManager; +import com.threerings.media.image.ImageManager; import com.threerings.util.DirectionCodes; import com.threerings.cast.CompositedActionFrames.ComponentFrames; @@ -52,7 +52,7 @@ public class CharacterManager /** * Constructs the character manager. */ - public CharacterManager (BaseImageManager imgr, ComponentRepository crepo) + public CharacterManager (ImageManager imgr, ComponentRepository crepo) { // keep these around _imgr = imgr; @@ -421,7 +421,7 @@ public class CharacterManager } /** The image manager with whom we interact. */ - protected BaseImageManager _imgr; + protected ImageManager _imgr; /** The component repository. */ protected ComponentRepository _crepo; diff --git a/src/java/com/threerings/cast/CompositedActionFrames.java b/src/java/com/threerings/cast/CompositedActionFrames.java index 3e1c0c07..d0ab5029 100644 --- a/src/java/com/threerings/cast/CompositedActionFrames.java +++ b/src/java/com/threerings/cast/CompositedActionFrames.java @@ -24,7 +24,7 @@ package com.threerings.cast; import com.samskivert.util.LRUHashMap; import com.samskivert.util.StringUtil; -import com.threerings.media.image.BaseImageManager; +import com.threerings.media.image.ImageManager; import com.threerings.media.image.Colorization; import com.threerings.media.util.MultiFrameImage; @@ -65,7 +65,7 @@ public class CompositedActionFrames * source frames and colorization configuration. The actual component * frame images will not be composited until they are requested. */ - public CompositedActionFrames (BaseImageManager imgr, LRUHashMap frameCache, + public CompositedActionFrames (ImageManager imgr, LRUHashMap frameCache, String action, ComponentFrames[] sources) { // sanity check @@ -177,7 +177,7 @@ public class CompositedActionFrames /** The image manager from whom we can obtain prepared volatile images * onto which to render our composited actions. */ - protected BaseImageManager _imgr; + protected ImageManager _imgr; /** Used to cache our composited action frame images. */ protected LRUHashMap _frameCache; diff --git a/src/java/com/threerings/cast/CompositedMaskedImage.java b/src/java/com/threerings/cast/CompositedMaskedImage.java index a1acef81..3df352c4 100644 --- a/src/java/com/threerings/cast/CompositedMaskedImage.java +++ b/src/java/com/threerings/cast/CompositedMaskedImage.java @@ -28,7 +28,7 @@ import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.Transparency; -import com.threerings.media.image.BaseImageManager; +import com.threerings.media.image.ImageManager; import com.threerings.media.image.Mirage; import com.threerings.media.image.VolatileMirage; @@ -40,7 +40,7 @@ import com.threerings.cast.CompositedActionFrames.ComponentFrames; public class CompositedMaskedImage extends CompositedMultiFrameImage { public CompositedMaskedImage ( - BaseImageManager imgr, ComponentFrames[] sources, String action, + ImageManager imgr, ComponentFrames[] sources, String action, int orient) { super(imgr, sources, action, orient); diff --git a/src/java/com/threerings/cast/CompositedMultiFrameImage.java b/src/java/com/threerings/cast/CompositedMultiFrameImage.java index 794608c5..c080f38e 100644 --- a/src/java/com/threerings/cast/CompositedMultiFrameImage.java +++ b/src/java/com/threerings/cast/CompositedMultiFrameImage.java @@ -31,7 +31,7 @@ import java.util.Comparator; import com.threerings.cast.CompositedActionFrames.ComponentFrames; import com.threerings.cast.bundle.BundledComponentRepository.TileSetFrameImage; -import com.threerings.media.image.BaseImageManager; +import com.threerings.media.image.ImageManager; import com.threerings.media.image.Mirage; import com.threerings.media.image.VolatileMirage; @@ -43,7 +43,7 @@ public class CompositedMultiFrameImage implements TrimmedMultiFrameImage { public CompositedMultiFrameImage ( - BaseImageManager imgr, ComponentFrames[] sources, + ImageManager imgr, ComponentFrames[] sources, String action, int orient) { _imgr = imgr; @@ -190,7 +190,7 @@ public class CompositedMultiFrameImage } /** The image manager from whom we load our images. */ - protected BaseImageManager _imgr; + protected ImageManager _imgr; /** The action frames from which we obtain our source imagery. */ protected ComponentFrames[] _sources; diff --git a/src/java/com/threerings/cast/CompositedShadowImage.java b/src/java/com/threerings/cast/CompositedShadowImage.java index 13f7174d..f7d34942 100644 --- a/src/java/com/threerings/cast/CompositedShadowImage.java +++ b/src/java/com/threerings/cast/CompositedShadowImage.java @@ -26,7 +26,7 @@ import java.awt.Composite; import java.awt.Graphics2D; import java.awt.Rectangle; -import com.threerings.media.image.BaseImageManager; +import com.threerings.media.image.ImageManager; import com.threerings.media.image.Mirage; import com.threerings.media.image.VolatileMirage; @@ -38,7 +38,7 @@ import com.threerings.cast.CompositedActionFrames.ComponentFrames; */ public class CompositedShadowImage extends CompositedMultiFrameImage { - public CompositedShadowImage (BaseImageManager imgr, ComponentFrames[] sources, + public CompositedShadowImage (ImageManager imgr, ComponentFrames[] sources, String action, int orient, float shadowAlpha) { super(imgr, sources, action, orient); diff --git a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java index 1e219e23..9d098937 100644 --- a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java +++ b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java @@ -40,7 +40,7 @@ import com.threerings.resource.FileResourceBundle; import com.threerings.resource.ResourceBundle; import com.threerings.resource.ResourceManager; -import com.threerings.media.image.BaseImageManager; +import com.threerings.media.image.ImageManager; import com.threerings.media.image.BufferedMirage; import com.threerings.media.image.Colorization; import com.threerings.media.image.ImageDataProvider; @@ -85,7 +85,7 @@ public class BundledComponentRepository * resource bundles. */ public BundledComponentRepository ( - ResourceManager rmgr, BaseImageManager imgr, String name) + ResourceManager rmgr, ImageManager imgr, String name) throws IOException { // keep this guy around @@ -261,7 +261,7 @@ public class BundledComponentRepository /** * Constructs an instance that will obtain image data from the specified resource bundle. */ - public ResourceBundleProvider (BaseImageManager imgr, ResourceBundle bundle) + public ResourceBundleProvider (ImageManager imgr, ResourceBundle bundle) { super(imgr, (String)null); _dprov = this; @@ -508,7 +508,7 @@ public class BundledComponentRepository } /** We use the image manager to decode and cache images. */ - protected BaseImageManager _imgr; + protected ImageManager _imgr; /** A table of action sequences. */ protected HashMap _actions; diff --git a/src/java/com/threerings/media/image/AWTImageCreator.java b/src/java/com/threerings/media/image/AWTImageCreator.java index 18c581f1..64013135 100644 --- a/src/java/com/threerings/media/image/AWTImageCreator.java +++ b/src/java/com/threerings/media/image/AWTImageCreator.java @@ -33,7 +33,7 @@ import java.awt.image.BufferedImage; * AWT to determine the optimal image format. */ public class AWTImageCreator - implements BaseImageManager.OptimalImageCreator + implements ImageManager.OptimalImageCreator { /** * Create an image creator that will rely on the AWT to determine the diff --git a/src/java/com/threerings/media/image/BackedVolatileMirage.java b/src/java/com/threerings/media/image/BackedVolatileMirage.java index c53f2166..b4dadf5b 100644 --- a/src/java/com/threerings/media/image/BackedVolatileMirage.java +++ b/src/java/com/threerings/media/image/BackedVolatileMirage.java @@ -39,7 +39,7 @@ public class BackedVolatileMirage extends VolatileMirage * Creates a mirage with the supplied regeneration informoation and * prepared image. */ - public BackedVolatileMirage (BaseImageManager imgr, BufferedImage source) + public BackedVolatileMirage (ImageManager imgr, BufferedImage source) { super(imgr, new Rectangle(0, 0, source.getWidth(), source.getHeight())); _source = source; diff --git a/src/java/com/threerings/media/image/BaseImageManager.java b/src/java/com/threerings/media/image/BaseImageManager.java deleted file mode 100644 index 364c8fb9..00000000 --- a/src/java/com/threerings/media/image/BaseImageManager.java +++ /dev/null @@ -1,538 +0,0 @@ -// $Id$ - -package com.threerings.media.image; - -import java.awt.Component; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.Transparency; -import java.awt.image.BufferedImage; - -import java.io.FileNotFoundException; -import java.io.IOException; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; - -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; - -/** - * Provides a single point of access for image retrieval and caching. This does not include - * any tie-in to runtime adjustments to control caching and mirage creation. - */ -public class BaseImageManager - implements ImageUtil.ImageCreator -{ - /** - * 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; - } - } - - /** - * This interface allows the image manager to create images that are in a format optimal for - * rendering to the screen. - */ - public interface OptimalImageCreator - { - /** - * Requests that a blank image be created that is in a format and of a depth that are - * optimal for rendering to the screen. - */ - public BufferedImage createImage (int width, int height, int trans); - } - - /** - * Construct an image manager with the specified {@link ResourceManager} from which it will - * obtain its data. - */ - public BaseImageManager (ResourceManager rmgr, OptimalImageCreator icreator) - { - _rmgr = rmgr; - _icreator = icreator; - - // create our image cache - int icsize = getCacheSize(); - Log.debug("Creating image cache [size=" + icsize + "k]."); - _ccache = new LRUHashMap(icsize * 1024, new LRUHashMap.ItemSizer() { - public int computeSize (Object value) { - return (int)((CacheRecord)value).getEstimatedMemoryUsage(); - } - }); - _ccache.setTracking(true); - } - - /** - * A convenience constructor that creates an {@link AWTImageCreator} for use by the image - * manager. - */ - public BaseImageManager (ResourceManager rmgr, Component context) - { - this(rmgr, new AWTImageCreator(context)); - } - - /** - * Returns how much space we're willing to use for caching images. - */ - public int getCacheSize () - { - return DEFAULT_CACHE_SIZE; - } - - /** - * Clears all images out of the cache. - */ - public void clearCache () - { - Log.info("Clearing image manager cache."); - - _ccache.clear(); - } - - /** - * Creates a buffered image, optimized for display on our graphics device. - */ - public BufferedImage createImage (int width, int height, int transparency) - { - return _icreator.createImage(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.isBlank(path)) { - String errmsg = "Invalid image path [rset=" + rset + ", path=" + path + "]"; - throw new IllegalArgumentException(errmsg); - } - return getImage(getImageKey(rset, path), zations); - } - - /** - * Loads (and caches) the specified image from the resource manager using the supplied path to - * identify the image. - * - *
Additionally the image is optimized for display in the current graphics - * configuration. Consider using {@link #getMirage(ImageKey)} instead of prepared images as - * they (some day) will automatically use volatile images to increase performance. - */ - public BufferedImage getPreparedImage (String path) - { - return getPreparedImage(null, path, null); - } - - /** - * Loads (and caches) the specified image from the resource manager, obtaining the image from - * the supplied resource set. - * - *
Additionally the image is optimized for display in the current graphics - * configuration. Consider using {@link #getMirage(ImageKey)} instead of prepared images as - * they (some day) will automatically use volatile images to increase performance. - */ - public BufferedImage getPreparedImage (String rset, String path) - { - return getPreparedImage(rset, path, null); - } - - /** - * Loads (and caches) the specified image from the resource manager, obtaining the image from - * the supplied resource set and applying the using the supplied path to identify the image. - * - *
Additionally the image is optimized for display in the current graphics
- * configuration. Consider using {@link #getMirage(ImageKey,Colorization[])} instead of
- * prepared images as they (some day) will automatically use volatile images to increase
- * performance.
- */
- public BufferedImage getPreparedImage (String rset, String path, Colorization[] zations)
- {
- BufferedImage image = getImage(rset, path, zations);
- BufferedImage prepped = null;
- if (image != null) {
- prepped = createImage(image.getWidth(), image.getHeight(),
- image.getColorModel().getTransparency());
- Graphics2D pg = prepped.createGraphics();
- pg.drawImage(image, 0, 0, null);
- pg.dispose();
- }
- return prepped;
- }
-
- /**
- * Returns an image key that can be used to fetch the image identified by the specified
- * resource set and image 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 = null;
- synchronized (_ccache) {
- crec = (CacheRecord)_ccache.get(key);
- }
- if (crec != null) {
-// Log.info("Cache hit [key=" + key + ", crec=" + crec + "].");
- return crec.getImage(zations, _ccache);
- }
-// 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 blank image instead
- image = new BufferedImage(10, 10, BufferedImage.TYPE_BYTE_INDEXED);
- }
-
-// Log.info("Loaded " + key.path + ", image=" + image +
-// ", size=" + ImageUtil.getEstimatedMemoryUsage(image));
-
- // create a cache record
- crec = new CacheRecord(key, image);
- synchronized (_ccache) {
- _ccache.put(key, crec);
- }
- _keySet.add(key);
-
- // periodically report our image cache performance
- reportCachePerformance();
-
- return crec.getImage(zations, _ccache);
- }
-
- /**
- * 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 (String rsrcPath)
- {
- return getMirage(getImageKey(_defaultProvider, rsrcPath), null, 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)
- {
- BufferedImage src = null;
-
- float percentageOfDataBuffer = 1;
- if (bounds == null) {
- // if they specified no bounds, we need to load up the raw image and determine its
- // bounds so that we can pass those along to the created mirage
- src = getImage(key, zations);
- bounds = new Rectangle(0, 0, src.getWidth(), src.getHeight());
-
- }
-
- return new CachedVolatileMirage(this, key, bounds, zations);
-
- }
-
- /**
- * Returns the image creator that can be used to create buffered images optimized for rendering
- * to the screen.
- */
- public OptimalImageCreator getImageCreator ()
- {
- return _icreator;
- }
-
- /**
- * 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;
- }
-
- ImageDataProvider dprov = (ImageDataProvider)_providers.get(rset);
- if (dprov == null) {
- dprov = new ImageDataProvider() {
- public BufferedImage loadImage (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)
- {
-// if (EventQueue.isDispatchThread()) {
-// Log.info("Loading image on AWT thread " + key + ".");
-// }
-
- BufferedImage image = null;
- try {
- Log.debug("Loading image " + key + ".");
- image = key.daprov.loadImage(key.path);
- if (image == null) {
- Log.warning("ImageDataProvider.loadImage(" + key + ") returned null.");
- }
-
- } 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);
- }
-
- return image;
- }
-
- /**
- * Reports statistics detailing the image manager cache performance and the current size of the
- * cached images.
- */
- protected void reportCachePerformance ()
- {
- if (/* Log.getLevel() != Log.log.DEBUG || */
- _cacheStatThrottle.throttleOp()) {
- return;
- }
-
- // compute our estimated memory usage
- long size = 0;
-
- int[] eff = null;
- synchronized (_ccache) {
- Iterator iter = _ccache.values().iterator();
- while (iter.hasNext()) {
- size += ((CacheRecord)iter.next()).getEstimatedMemoryUsage();
- }
- 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 image cache. */
- protected static class CacheRecord
- {
- public CacheRecord (ImageKey key, BufferedImage source)
- {
- _key = key;
- _source = source;
- }
-
- public BufferedImage getImage (Colorization[] zations, LRUHashMap cache)
- {
- if (zations == null) {
- return _source;
- }
-
- if (_colorized == null) {
- _colorized = new ArrayList These objects are never created directly, but always obtained from
- * the {@link BaseImageManager}.
+ * the {@link ImageManager}.
*/
public class CachedVolatileMirage extends VolatileMirage
{
@@ -44,7 +44,7 @@ public class CachedVolatileMirage extends VolatileMirage
* prepared image.
*/
protected CachedVolatileMirage (
- BaseImageManager imgr, BaseImageManager.ImageKey source,
+ ImageManager imgr, ImageManager.ImageKey source,
Rectangle bounds, Colorization[] zations)
{
super(imgr, bounds);
@@ -96,7 +96,7 @@ public class CachedVolatileMirage extends VolatileMirage
/** The key that identifies the image data used to create our volatile
* image. */
- protected BaseImageManager.ImageKey _source;
+ protected ImageManager.ImageKey _source;
/** Optional colorizations that are applied to our source image when
* creating our mirage. */
diff --git a/src/java/com/threerings/media/image/ClientImageManager.java b/src/java/com/threerings/media/image/ClientImageManager.java
new file mode 100644
index 00000000..9a0f0860
--- /dev/null
+++ b/src/java/com/threerings/media/image/ClientImageManager.java
@@ -0,0 +1,100 @@
+//
+// $Id: ImageManager.java 433 2008-03-06 18:31:04Z mthomas $
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.threerings.media.image;
+
+import java.awt.Component;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+
+import com.samskivert.swing.RuntimeAdjust;
+import com.threerings.media.MediaPrefs;
+import com.threerings.resource.ResourceManager;
+
+/**
+ * Provides a single point of access for image retrieval and caching - just like the
+ * ImageManager but adds a tie in to the RuntimeAdjust system to control caching and
+ * mirage creation.
+ */
+public class ClientImageManager extends ImageManager
+{
+ public ClientImageManager (ResourceManager rmgr, OptimalImageCreator icreator)
+ {
+ super(rmgr, icreator);
+ }
+
+ public ClientImageManager (ResourceManager rmgr, Component context)
+ {
+ super(rmgr, context);
+ }
+
+ @Override
+ public int getCacheSize ()
+ {
+ return _cacheSize.getValue();
+ }
+
+ @Override
+ public Mirage getMirage (ImageKey key, Rectangle bounds, Colorization[] zations)
+ {
+ // We need to do something more complicated than the BaseImageManager because our
+ // runtime adjustments may affect how we create our mirages.
+
+ BufferedImage src = null;
+
+ float percentageOfDataBuffer = 1;
+ if (bounds == null) {
+ // if they specified no bounds, we need to load up the raw image and determine its
+ // bounds so that we can pass those along to the created mirage
+ src = getImage(key, zations);
+ bounds = new Rectangle(0, 0, src.getWidth(), src.getHeight());
+
+ } else if (!_prepareImages.getValue()) {
+ src = getImage(key, zations);
+ percentageOfDataBuffer =
+ (bounds.width * bounds.height)/(float)(src.getHeight() * src.getWidth());
+ src = src.getSubimage(bounds.x, bounds.y, bounds.width, bounds.height);
+ }
+
+ if (_runBlank.getValue()) {
+ return new BlankMirage(bounds.width, bounds.height);
+ } else if (_prepareImages.getValue()) {
+ return new CachedVolatileMirage(this, key, bounds, zations);
+ } else {
+ return new BufferedMirage(src, percentageOfDataBuffer);
+ }
+ }
+
+ /** Register our image cache size with the runtime adjustments framework. */
+ protected static RuntimeAdjust.IntAdjust _cacheSize = new RuntimeAdjust.IntAdjust(
+ "Size (in kb of memory used) of the image manager LRU cache [requires restart]",
+ "narya.media.image.cache_size", MediaPrefs.config, DEFAULT_CACHE_SIZE);
+
+ /** Controls whether or not we prepare images or use raw versions. */
+ protected static RuntimeAdjust.BooleanAdjust _prepareImages = new RuntimeAdjust.BooleanAdjust(
+ "Cause image manager to optimize all images for display.",
+ "narya.media.image.prep_images", MediaPrefs.config, true);
+
+ /** A debug toggle for running entirely without rendering images. */
+ protected static RuntimeAdjust.BooleanAdjust _runBlank = new RuntimeAdjust.BooleanAdjust(
+ "Cause image manager to return blank images.",
+ "narya.media.image.run_blank", MediaPrefs.config, false);
+}
diff --git a/src/java/com/threerings/media/image/ImageManager.java b/src/java/com/threerings/media/image/ImageManager.java
index def0148f..d156af88 100644
--- a/src/java/com/threerings/media/image/ImageManager.java
+++ b/src/java/com/threerings/media/image/ImageManager.java
@@ -1,63 +1,333 @@
-//
// $Id$
-//
-// Nenya library - tools for developing networked games
-// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
-// http://www.threerings.net/code/nenya/
-//
-// This library is free software; you can redistribute it and/or modify it
-// under the terms of the GNU Lesser General Public License as published
-// by the Free Software Foundation; either version 2.1 of the License, or
-// (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.image;
import java.awt.Component;
+import java.awt.Graphics2D;
import java.awt.Rectangle;
+import java.awt.Transparency;
import java.awt.image.BufferedImage;
-import com.samskivert.swing.RuntimeAdjust;
-import com.threerings.media.MediaPrefs;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+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;
/**
- * Provides a single point of access for image retrieval and caching - just like the
- * BaseImageManager but adds a tie in to the RuntimeAdjust system to control caching and
- * mirage creation.
+ * Provides a single point of access for image retrieval and caching. This does not include
+ * any tie-in to runtime adjustments to control caching and mirage creation.
*/
-public class ImageManager extends BaseImageManager
+public class ImageManager
+ implements ImageUtil.ImageCreator
{
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * This interface allows the image manager to create images that are in a format optimal for
+ * rendering to the screen.
+ */
+ public interface OptimalImageCreator
+ {
+ /**
+ * Requests that a blank image be created that is in a format and of a depth that are
+ * optimal for rendering to the screen.
+ */
+ public BufferedImage createImage (int width, int height, int trans);
+ }
+
+ /**
+ * Construct an image manager with the specified {@link ResourceManager} from which it will
+ * obtain its data.
+ */
public ImageManager (ResourceManager rmgr, OptimalImageCreator icreator)
{
- super(rmgr, icreator);
+ _rmgr = rmgr;
+ _icreator = icreator;
+
+ // create our image cache
+ int icsize = getCacheSize();
+ Log.debug("Creating image cache [size=" + icsize + "k].");
+ _ccache = new LRUHashMap(icsize * 1024, new LRUHashMap.ItemSizer() {
+ public int computeSize (Object value) {
+ return (int)((CacheRecord)value).getEstimatedMemoryUsage();
+ }
+ });
+ _ccache.setTracking(true);
}
+ /**
+ * A convenience constructor that creates an {@link AWTImageCreator} for use by the image
+ * manager.
+ */
public ImageManager (ResourceManager rmgr, Component context)
{
- super(rmgr, context);
+ this(rmgr, new AWTImageCreator(context));
}
- @Override
+ /**
+ * Returns how much space we're willing to use for caching images.
+ */
public int getCacheSize ()
{
- return _cacheSize.getValue();
+ return DEFAULT_CACHE_SIZE;
}
- @Override
+ /**
+ * Clears all images out of the cache.
+ */
+ public void clearCache ()
+ {
+ Log.info("Clearing image manager cache.");
+
+ _ccache.clear();
+ }
+
+ /**
+ * Creates a buffered image, optimized for display on our graphics device.
+ */
+ public BufferedImage createImage (int width, int height, int transparency)
+ {
+ return _icreator.createImage(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.isBlank(path)) {
+ String errmsg = "Invalid image path [rset=" + rset + ", path=" + path + "]";
+ throw new IllegalArgumentException(errmsg);
+ }
+ return getImage(getImageKey(rset, path), zations);
+ }
+
+ /**
+ * Loads (and caches) the specified image from the resource manager using the supplied path to
+ * identify the image.
+ *
+ * Additionally the image is optimized for display in the current graphics
+ * configuration. Consider using {@link #getMirage(ImageKey)} instead of prepared images as
+ * they (some day) will automatically use volatile images to increase performance.
+ */
+ public BufferedImage getPreparedImage (String path)
+ {
+ return getPreparedImage(null, path, null);
+ }
+
+ /**
+ * Loads (and caches) the specified image from the resource manager, obtaining the image from
+ * the supplied resource set.
+ *
+ * Additionally the image is optimized for display in the current graphics
+ * configuration. Consider using {@link #getMirage(ImageKey)} instead of prepared images as
+ * they (some day) will automatically use volatile images to increase performance.
+ */
+ public BufferedImage getPreparedImage (String rset, String path)
+ {
+ return getPreparedImage(rset, path, null);
+ }
+
+ /**
+ * Loads (and caches) the specified image from the resource manager, obtaining the image from
+ * the supplied resource set and applying the using the supplied path to identify the image.
+ *
+ * Additionally the image is optimized for display in the current graphics
+ * configuration. Consider using {@link #getMirage(ImageKey,Colorization[])} instead of
+ * prepared images as they (some day) will automatically use volatile images to increase
+ * performance.
+ */
+ public BufferedImage getPreparedImage (String rset, String path, Colorization[] zations)
+ {
+ BufferedImage image = getImage(rset, path, zations);
+ BufferedImage prepped = null;
+ if (image != null) {
+ prepped = createImage(image.getWidth(), image.getHeight(),
+ image.getColorModel().getTransparency());
+ Graphics2D pg = prepped.createGraphics();
+ pg.drawImage(image, 0, 0, null);
+ pg.dispose();
+ }
+ return prepped;
+ }
+
+ /**
+ * Returns an image key that can be used to fetch the image identified by the specified
+ * resource set and image 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 = null;
+ synchronized (_ccache) {
+ crec = (CacheRecord)_ccache.get(key);
+ }
+ if (crec != null) {
+// Log.info("Cache hit [key=" + key + ", crec=" + crec + "].");
+ return crec.getImage(zations, _ccache);
+ }
+// 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 blank image instead
+ image = new BufferedImage(10, 10, BufferedImage.TYPE_BYTE_INDEXED);
+ }
+
+// Log.info("Loaded " + key.path + ", image=" + image +
+// ", size=" + ImageUtil.getEstimatedMemoryUsage(image));
+
+ // create a cache record
+ crec = new CacheRecord(key, image);
+ synchronized (_ccache) {
+ _ccache.put(key, crec);
+ }
+ _keySet.add(key);
+
+ // periodically report our image cache performance
+ reportCachePerformance();
+
+ return crec.getImage(zations, _ccache);
+ }
+
+ /**
+ * 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 (String rsrcPath)
+ {
+ return getMirage(getImageKey(_defaultProvider, rsrcPath), null, 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)
{
- // We need to do something more complicated than the BaseImageManager because our
- // runtime adjustments may affect how we create our mirages.
-
BufferedImage src = null;
float percentageOfDataBuffer = 1;
@@ -67,34 +337,202 @@ public class ImageManager extends BaseImageManager
src = getImage(key, zations);
bounds = new Rectangle(0, 0, src.getWidth(), src.getHeight());
- } else if (!_prepareImages.getValue()) {
- src = getImage(key, zations);
- percentageOfDataBuffer =
- (bounds.width * bounds.height)/(float)(src.getHeight() * src.getWidth());
- src = src.getSubimage(bounds.x, bounds.y, bounds.width, bounds.height);
}
- if (_runBlank.getValue()) {
- return new BlankMirage(bounds.width, bounds.height);
- } else if (_prepareImages.getValue()) {
- return new CachedVolatileMirage(this, key, bounds, zations);
- } else {
- return new BufferedMirage(src, percentageOfDataBuffer);
- }
+ return new CachedVolatileMirage(this, key, bounds, zations);
+
}
- /** Register our image cache size with the runtime adjustments framework. */
- protected static RuntimeAdjust.IntAdjust _cacheSize = new RuntimeAdjust.IntAdjust(
- "Size (in kb of memory used) of the image manager LRU cache [requires restart]",
- "narya.media.image.cache_size", MediaPrefs.config, DEFAULT_CACHE_SIZE);
+ /**
+ * Returns the image creator that can be used to create buffered images optimized for rendering
+ * to the screen.
+ */
+ public OptimalImageCreator getImageCreator ()
+ {
+ return _icreator;
+ }
- /** Controls whether or not we prepare images or use raw versions. */
- protected static RuntimeAdjust.BooleanAdjust _prepareImages = new RuntimeAdjust.BooleanAdjust(
- "Cause image manager to optimize all images for display.",
- "narya.media.image.prep_images", MediaPrefs.config, true);
+ /**
+ * 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;
+ }
- /** A debug toggle for running entirely without rendering images. */
- protected static RuntimeAdjust.BooleanAdjust _runBlank = new RuntimeAdjust.BooleanAdjust(
- "Cause image manager to return blank images.",
- "narya.media.image.run_blank", MediaPrefs.config, false);
-}
+ ImageDataProvider dprov = (ImageDataProvider)_providers.get(rset);
+ if (dprov == null) {
+ dprov = new ImageDataProvider() {
+ public BufferedImage loadImage (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)
+ {
+// if (EventQueue.isDispatchThread()) {
+// Log.info("Loading image on AWT thread " + key + ".");
+// }
+
+ BufferedImage image = null;
+ try {
+ Log.debug("Loading image " + key + ".");
+ image = key.daprov.loadImage(key.path);
+ if (image == null) {
+ Log.warning("ImageDataProvider.loadImage(" + key + ") returned null.");
+ }
+
+ } 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);
+ }
+
+ return image;
+ }
+
+ /**
+ * Reports statistics detailing the image manager cache performance and the current size of the
+ * cached images.
+ */
+ protected void reportCachePerformance ()
+ {
+ if (/* Log.getLevel() != Log.log.DEBUG || */
+ _cacheStatThrottle.throttleOp()) {
+ return;
+ }
+
+ // compute our estimated memory usage
+ long size = 0;
+
+ int[] eff = null;
+ synchronized (_ccache) {
+ Iterator iter = _ccache.values().iterator();
+ while (iter.hasNext()) {
+ size += ((CacheRecord)iter.next()).getEstimatedMemoryUsage();
+ }
+ 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 image cache. */
+ protected static class CacheRecord
+ {
+ public CacheRecord (ImageKey key, BufferedImage source)
+ {
+ _key = key;
+ _source = source;
+ }
+
+ public BufferedImage getImage (Colorization[] zations, LRUHashMap cache)
+ {
+ if (zations == null) {
+ return _source;
+ }
+
+ if (_colorized == null) {
+ _colorized = new ArrayList