diff --git a/src/java/com/threerings/cast/CharacterManager.java b/src/java/com/threerings/cast/CharacterManager.java index 3850b279..27b889e8 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.ImageManager; +import com.threerings.media.image.BaseImageManager; import com.threerings.util.DirectionCodes; import com.threerings.cast.CompositedActionFrames.ComponentFrames; @@ -52,7 +52,7 @@ public class CharacterManager /** * Constructs the character manager. */ - public CharacterManager (ImageManager imgr, ComponentRepository crepo) + public CharacterManager (BaseImageManager imgr, ComponentRepository crepo) { // keep these around _imgr = imgr; @@ -421,7 +421,7 @@ public class CharacterManager } /** The image manager with whom we interact. */ - protected ImageManager _imgr; + protected BaseImageManager _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 4010b4b6..3e1c0c07 100644 --- a/src/java/com/threerings/cast/CompositedActionFrames.java +++ b/src/java/com/threerings/cast/CompositedActionFrames.java @@ -24,8 +24,8 @@ 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.Colorization; -import com.threerings.media.image.ImageManager; import com.threerings.media.util.MultiFrameImage; import com.threerings.cast.CharacterComponent; @@ -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 (ImageManager imgr, LRUHashMap frameCache, + public CompositedActionFrames (BaseImageManager 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 ImageManager _imgr; + protected BaseImageManager _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 3df352c4..a1acef81 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.ImageManager; +import com.threerings.media.image.BaseImageManager; 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 ( - ImageManager imgr, ComponentFrames[] sources, String action, + BaseImageManager 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 c080f38e..794608c5 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.ImageManager; +import com.threerings.media.image.BaseImageManager; import com.threerings.media.image.Mirage; import com.threerings.media.image.VolatileMirage; @@ -43,7 +43,7 @@ public class CompositedMultiFrameImage implements TrimmedMultiFrameImage { public CompositedMultiFrameImage ( - ImageManager imgr, ComponentFrames[] sources, + BaseImageManager 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 ImageManager _imgr; + protected BaseImageManager _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 f7d34942..13f7174d 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.ImageManager; +import com.threerings.media.image.BaseImageManager; 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 (ImageManager imgr, ComponentFrames[] sources, + public CompositedShadowImage (BaseImageManager 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 22362d0c..1e219e23 100644 --- a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java +++ b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java @@ -40,10 +40,10 @@ 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.BufferedMirage; import com.threerings.media.image.Colorization; import com.threerings.media.image.ImageDataProvider; -import com.threerings.media.image.ImageManager; import com.threerings.media.image.ImageUtil; import com.threerings.media.image.Mirage; @@ -85,7 +85,7 @@ public class BundledComponentRepository * resource bundles. */ public BundledComponentRepository ( - ResourceManager rmgr, ImageManager imgr, String name) + ResourceManager rmgr, BaseImageManager 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 (ImageManager imgr, ResourceBundle bundle) + public ResourceBundleProvider (BaseImageManager imgr, ResourceBundle bundle) { super(imgr, (String)null); _dprov = this; @@ -486,7 +486,7 @@ public class BundledComponentRepository return _set.getTile(getTileIndex(orient, index)); } - public Mirage getTileMirage(int orient, int index) { + public Mirage getTileMirage (int orient, int index) { return _set.getTileMirage(getTileIndex(orient, index)); } @@ -508,7 +508,7 @@ public class BundledComponentRepository } /** We use the image manager to decode and cache images. */ - protected ImageManager _imgr; + protected BaseImageManager _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 ef28adfe..18c581f1 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 ImageManager.OptimalImageCreator + implements BaseImageManager.OptimalImageCreator { /** * Create an image creator that will rely on the AWT to determine the @@ -53,7 +53,7 @@ public class AWTImageCreator } } - // documentation inherited from interface ImageManager.OptimalImageCreator + // documentation inherited from interface BaseImageManager.OptimalImageCreator public BufferedImage createImage (int width, int height, int trans) { // DEBUG: override transparency for the moment on all images diff --git a/src/java/com/threerings/media/image/BackedVolatileMirage.java b/src/java/com/threerings/media/image/BackedVolatileMirage.java index b4dadf5b..c53f2166 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 (ImageManager imgr, BufferedImage source) + public BackedVolatileMirage (BaseImageManager 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 new file mode 100644 index 00000000..364c8fb9 --- /dev/null +++ b/src/java/com/threerings/media/image/BaseImageManager.java @@ -0,0 +1,538 @@ +// $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>(); + } + + // 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 = _colorized.get(ii); + Colorization[] tzations = tup.left; + if (Arrays.equals(zations, tzations)) { + return tup.right; + } + } + + try { + BufferedImage cimage = ImageUtil.recolorImage(_source, zations); + _colorized.add(new Tuple(zations, cimage)); + cache.adjustSize((int)ImageUtil.getEstimatedMemoryUsage(cimage)); + return cimage; + + } catch (Exception re) { + Log.warning("Failure recoloring image [source" + _key + + ", zations=" + StringUtil.toString(zations) + ", error=" + re + "]."); + // return the uncolorized version + return _source; + } + } + + public long getEstimatedMemoryUsage () + { + long usage = ImageUtil.getEstimatedMemoryUsage(_source); + if (_colorized != null) { + for (Tuple tup : _colorized) { + usage += ImageUtil.getEstimatedMemoryUsage(tup.right); + } + } + return usage; + } + + public String toString () + { + return "[key=" + _key + ", wid=" + _source.getWidth() + ", hei=" + _source.getHeight() + + ", ccount=" + ((_colorized == null) ? 0 : _colorized.size()) + "]"; + } + + protected ImageKey _key; + protected BufferedImage _source; + protected ArrayList> _colorized; + } + + /** A reference to the resource manager via which we load image data by default. */ + protected ResourceManager _rmgr; + + /** We use this to create images optimized for rendering. */ + protected OptimalImageCreator _icreator; + + /** A cache of loaded images. */ + 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 300 seconds. */ + protected Throttle _cacheStatThrottle = new Throttle(1, 300000L); + + /** Our default data provider. */ + protected ImageDataProvider _defaultProvider = new ImageDataProvider() { + public BufferedImage loadImage (String path) throws IOException { + return _rmgr.getImageResource(path); + } + public String getIdent () { + return "rmgr:default"; + } + }; + + /** Data providers for different resource sets. */ + protected HashMap _providers = new HashMap(); + + /** Default amount of data we'll store in our image cache. */ + protected static int DEFAULT_CACHE_SIZE = 32768; +} \ No newline at end of file diff --git a/src/java/com/threerings/media/image/CachedVolatileMirage.java b/src/java/com/threerings/media/image/CachedVolatileMirage.java index 21315c00..0d1588f5 100644 --- a/src/java/com/threerings/media/image/CachedVolatileMirage.java +++ b/src/java/com/threerings/media/image/CachedVolatileMirage.java @@ -35,7 +35,7 @@ import com.threerings.media.Log; * for some other reason. * *

These objects are never created directly, but always obtained from - * the {@link ImageManager}. + * the {@link BaseImageManager}. */ public class CachedVolatileMirage extends VolatileMirage { @@ -44,7 +44,7 @@ public class CachedVolatileMirage extends VolatileMirage * prepared image. */ protected CachedVolatileMirage ( - ImageManager imgr, ImageManager.ImageKey source, + BaseImageManager imgr, BaseImageManager.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 ImageManager.ImageKey _source; + protected BaseImageManager.ImageKey _source; /** Optional colorizations that are applied to our source image when * creating our mirage. */ diff --git a/src/java/com/threerings/media/image/ImageManager.java b/src/java/com/threerings/media/image/ImageManager.java index b75a1074..def0148f 100644 --- a/src/java/com/threerings/media/image/ImageManager.java +++ b/src/java/com/threerings/media/image/ImageManager.java @@ -22,324 +22,42 @@ 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.swing.RuntimeAdjust; -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.media.MediaPrefs; import com.threerings.resource.ResourceManager; /** - * Provides a single point of access for image retrieval and caching. + * 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. */ -public class ImageManager - implements ImageUtil.ImageCreator +public class ImageManager extends BaseImageManager { - /** - * 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) { - _rmgr = rmgr; - _icreator = icreator; - - // create our image cache - int icsize = _cacheSize.getValue(); - 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); + super(rmgr, icreator); } - /** - * A convenience constructor that creates an {@link AWTImageCreator} for use by the image - * manager. - */ public ImageManager (ResourceManager rmgr, Component context) { - this(rmgr, new AWTImageCreator(context)); + super(rmgr, context); } - /** - * Clears all images out of the cache. - */ - public void clearCache () + @Override + public int getCacheSize () { - Log.info("Clearing image manager cache."); - - _ccache.clear(); + return _cacheSize.getValue(); } - /** - * 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. - */ + @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; @@ -365,200 +83,10 @@ public class ImageManager } } - /** - * 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>(); - } - - // 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 = _colorized.get(ii); - Colorization[] tzations = tup.left; - if (Arrays.equals(zations, tzations)) { - return tup.right; - } - } - - try { - BufferedImage cimage = ImageUtil.recolorImage(_source, zations); - _colorized.add(new Tuple(zations, cimage)); - cache.adjustSize((int)ImageUtil.getEstimatedMemoryUsage(cimage)); - return cimage; - - } catch (Exception re) { - Log.warning("Failure recoloring image [source" + _key + - ", zations=" + StringUtil.toString(zations) + ", error=" + re + "]."); - // return the uncolorized version - return _source; - } - } - - public long getEstimatedMemoryUsage () - { - long usage = ImageUtil.getEstimatedMemoryUsage(_source); - if (_colorized != null) { - for (Tuple tup : _colorized) { - usage += ImageUtil.getEstimatedMemoryUsage(tup.right); - } - } - return usage; - } - - public String toString () - { - return "[key=" + _key + ", wid=" + _source.getWidth() + ", hei=" + _source.getHeight() + - ", ccount=" + ((_colorized == null) ? 0 : _colorized.size()) + "]"; - } - - protected ImageKey _key; - protected BufferedImage _source; - protected ArrayList> _colorized; - } - - /** A reference to the resource manager via which we load image data by default. */ - protected ResourceManager _rmgr; - - /** We use this to create images optimized for rendering. */ - protected OptimalImageCreator _icreator; - - /** A cache of loaded images. */ - 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 300 seconds. */ - protected Throttle _cacheStatThrottle = new Throttle(1, 300000L); - - /** Our default data provider. */ - protected ImageDataProvider _defaultProvider = new ImageDataProvider() { - public BufferedImage loadImage (String path) throws IOException { - return _rmgr.getImageResource(path); - } - public String getIdent () { - return "rmgr:default"; - } - }; - - /** Data providers for different resource sets. */ - protected HashMap _providers = new HashMap(); - /** 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, 32768); + "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( diff --git a/src/java/com/threerings/media/image/VolatileMirage.java b/src/java/com/threerings/media/image/VolatileMirage.java index 425690e1..d3ff0578 100644 --- a/src/java/com/threerings/media/image/VolatileMirage.java +++ b/src/java/com/threerings/media/image/VolatileMirage.java @@ -39,7 +39,7 @@ public abstract class VolatileMirage implements Mirage /** * Informs the base class of its image manager and image bounds. */ - protected VolatileMirage (ImageManager imgr, Rectangle bounds) + protected VolatileMirage (BaseImageManager imgr, Rectangle bounds) { _imgr = imgr; _bounds = bounds; @@ -186,7 +186,7 @@ public abstract class VolatileMirage implements Mirage } /** The image manager with whom we interoperate. */ - protected ImageManager _imgr; + protected BaseImageManager _imgr; /** The bounds of the region of our source image which we desire for * this mirage (possibly the whole thing). */ diff --git a/src/java/com/threerings/media/tile/IMImageProvider.java b/src/java/com/threerings/media/tile/IMImageProvider.java index de7cd845..f82225bc 100644 --- a/src/java/com/threerings/media/tile/IMImageProvider.java +++ b/src/java/com/threerings/media/tile/IMImageProvider.java @@ -24,9 +24,9 @@ package com.threerings.media.tile; import java.awt.Rectangle; import java.awt.image.BufferedImage; +import com.threerings.media.image.BaseImageManager; import com.threerings.media.image.Colorization; import com.threerings.media.image.ImageDataProvider; -import com.threerings.media.image.ImageManager; import com.threerings.media.image.Mirage; /** @@ -35,13 +35,13 @@ import com.threerings.media.image.Mirage; */ public class IMImageProvider implements ImageProvider { - public IMImageProvider (ImageManager imgr, ImageDataProvider dprov) + public IMImageProvider (BaseImageManager imgr, ImageDataProvider dprov) { _imgr = imgr; _dprov = dprov; } - public IMImageProvider (ImageManager imgr, String rset) + public IMImageProvider (BaseImageManager imgr, String rset) { _imgr = imgr; _rset = rset; @@ -59,12 +59,12 @@ public class IMImageProvider implements ImageProvider return _imgr.getMirage(getImageKey(path), bounds, zations); } - protected final ImageManager.ImageKey getImageKey (String path) + protected final BaseImageManager.ImageKey getImageKey (String path) { return (_dprov == null) ? _imgr.getImageKey(_rset, path) : _imgr.getImageKey(_dprov, path); } - protected ImageManager _imgr; + protected BaseImageManager _imgr; protected ImageDataProvider _dprov; protected String _rset; } diff --git a/src/java/com/threerings/media/tile/TileManager.java b/src/java/com/threerings/media/tile/TileManager.java index 2e3b66f3..fe33b7e7 100644 --- a/src/java/com/threerings/media/tile/TileManager.java +++ b/src/java/com/threerings/media/tile/TileManager.java @@ -28,7 +28,7 @@ import java.util.HashMap; import com.samskivert.io.PersistenceException; import com.threerings.media.Log; -import com.threerings.media.image.ImageManager; +import com.threerings.media.image.BaseImageManager; /** * The tile manager provides a simplified interface for retrieving and @@ -55,7 +55,7 @@ public class TileManager * @param imgr the image manager via which the tile manager will * decode and cache images. */ - public TileManager (ImageManager imgr) + public TileManager (BaseImageManager imgr) { _imgr = imgr; _defaultProvider = new IMImageProvider(_imgr, (String)null); @@ -242,7 +242,7 @@ public class TileManager } /** The entity through which we decode and cache images. */ - protected ImageManager _imgr; + protected BaseImageManager _imgr; /** A cache of tilesets that have been loaded by hand. */ protected HashMap _handcache = new HashMap(); diff --git a/src/java/com/threerings/media/tile/bundle/BundledTileSetRepository.java b/src/java/com/threerings/media/tile/bundle/BundledTileSetRepository.java index 3f93b3a5..f701456c 100644 --- a/src/java/com/threerings/media/tile/bundle/BundledTileSetRepository.java +++ b/src/java/com/threerings/media/tile/bundle/BundledTileSetRepository.java @@ -30,7 +30,7 @@ import com.threerings.resource.ResourceBundle; import com.threerings.resource.ResourceManager; import com.threerings.media.Log; -import com.threerings.media.image.ImageManager; +import com.threerings.media.image.BaseImageManager; import com.threerings.media.tile.IMImageProvider; import com.threerings.media.tile.NoSuchTileSetException; import com.threerings.media.tile.TileSet; @@ -57,7 +57,7 @@ public class BundledTileSetRepository * loading our tile data. */ public BundledTileSetRepository (final ResourceManager rmgr, - final ImageManager imgr, + final BaseImageManager imgr, final String name) { _imgr = imgr; @@ -222,7 +222,7 @@ public class BundledTileSetRepository } /** The image manager via which we load our images. */ - protected ImageManager _imgr; + protected BaseImageManager _imgr; /** A mapping from tileset id to tileset. */ protected HashIntMap _idmap; diff --git a/src/java/com/threerings/miso/tile/AutoFringer.java b/src/java/com/threerings/miso/tile/AutoFringer.java index 02a99821..4b107f9c 100644 --- a/src/java/com/threerings/miso/tile/AutoFringer.java +++ b/src/java/com/threerings/miso/tile/AutoFringer.java @@ -30,8 +30,8 @@ import java.util.HashMap; import com.samskivert.util.CheapIntMap; import com.samskivert.util.QuickSort; +import com.threerings.media.image.BaseImageManager; import com.threerings.media.image.BufferedMirage; -import com.threerings.media.image.ImageManager; import com.threerings.media.image.ImageUtil; import com.threerings.media.tile.NoSuchTileSetException; @@ -53,7 +53,7 @@ public class AutoFringer * Constructs an instance that will fringe according to the rules in * the supplied fringe configuration. */ - public AutoFringer (FringeConfiguration fringeconf, ImageManager imgr, + public AutoFringer (FringeConfiguration fringeconf, BaseImageManager imgr, TileManager tmgr) { _fringeconf = fringeconf; @@ -399,7 +399,7 @@ public class AutoFringer } } - protected ImageManager _imgr; + protected BaseImageManager _imgr; protected TileManager _tmgr; protected FringeConfiguration _fringeconf; protected CheapIntMap _fringers = new CheapIntMap(16); diff --git a/src/java/com/threerings/miso/tile/MisoTileManager.java b/src/java/com/threerings/miso/tile/MisoTileManager.java index 256194b4..bb9fbee5 100644 --- a/src/java/com/threerings/miso/tile/MisoTileManager.java +++ b/src/java/com/threerings/miso/tile/MisoTileManager.java @@ -29,7 +29,7 @@ import com.samskivert.io.StreamUtil; import com.threerings.resource.ResourceManager; import com.threerings.util.CompiledConfig; -import com.threerings.media.image.ImageManager; +import com.threerings.media.image.BaseImageManager; import com.threerings.media.tile.TileManager; import com.threerings.miso.Log; @@ -47,7 +47,7 @@ public class MisoTileManager extends TileManager * @param imgr the image manager via which the tile manager will * decode and cache images. */ - public MisoTileManager (ResourceManager rmgr, ImageManager imgr) + public MisoTileManager (ResourceManager rmgr, BaseImageManager imgr) { super(imgr);