Moved Bang's handy ImageCache into our handy JME extensions library.
Note: This marks my official conversion to a 100 character line width. We've been experimenting with it on MetaSOY for a while now and it reduces wrapping by what feels to be a substantial amount. We also tried 120 which ended up feeling too wide. git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@82 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
//
|
||||
// $Id: Log.java 3099 2004-08-27 02:21:06Z mdb $
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
|
||||
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
@@ -21,19 +21,21 @@
|
||||
|
||||
package com.threerings.jme;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* A placeholder class that contains a reference to the log object used by
|
||||
* this package.
|
||||
* A placeholder class that contains a reference to the log object used by this package.
|
||||
*/
|
||||
public class Log
|
||||
{
|
||||
public static com.samskivert.util.Log log =
|
||||
new com.samskivert.util.Log("narya.jme");
|
||||
/** We dispatch our log messages through this logger. */
|
||||
public static Logger log = Logger.getLogger("com.threerings.nenya.jme");
|
||||
|
||||
/** Convenience function. */
|
||||
public static void debug (String message)
|
||||
{
|
||||
log.debug(message);
|
||||
log.fine(message);
|
||||
}
|
||||
|
||||
/** Convenience function. */
|
||||
@@ -51,6 +53,6 @@ public class Log
|
||||
/** Convenience function. */
|
||||
public static void logStackTrace (Throwable t)
|
||||
{
|
||||
log.logStackTrace(com.samskivert.util.Log.WARNING, t);
|
||||
log.log(Level.WARNING, t.getMessage(), t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,395 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.jme.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BandCombineOp;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ComponentColorModel;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.Raster;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import com.jme.image.Image;
|
||||
import com.jmex.bui.BImage;
|
||||
|
||||
import com.threerings.media.image.Colorization;
|
||||
import com.threerings.media.image.ImageUtil;
|
||||
|
||||
import com.threerings.resource.ResourceManager;
|
||||
|
||||
import static com.threerings.jme.Log.log;
|
||||
|
||||
/**
|
||||
* Manages a weak cache of image data to make life simpler for callers that don't want to worry
|
||||
* about coordinating shared use of the same images.
|
||||
*/
|
||||
public class ImageCache
|
||||
{
|
||||
/**
|
||||
* Creates a JME-compatible image from the supplied buffered image.
|
||||
*/
|
||||
public static Image createImage (BufferedImage bufimg, boolean flip)
|
||||
{
|
||||
return createImage(bufimg, 1f, flip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorizes the supplied buffered image (which must be an 8-bit colormapped image), then
|
||||
* converts the colorized image into a form that JME can display.
|
||||
*/
|
||||
public static Image createImage (BufferedImage bufimg, Colorization[] zations, boolean flip)
|
||||
{
|
||||
return createImage(bufimg, zations, 1f, flip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorizes the supplied buffered image (which must be an 8-bit colormapped image), then
|
||||
* converts the colorized image into a form that JME can display.
|
||||
*/
|
||||
public static Image createImage (BufferedImage bufimg, Colorization[] zations, float scale,
|
||||
boolean flip)
|
||||
{
|
||||
return createImage(ImageUtil.recolorImage(bufimg, zations), scale, flip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JME-compatible image from the supplied buffered image.
|
||||
*/
|
||||
public static Image createImage (BufferedImage bufimg, float scale, boolean flip)
|
||||
{
|
||||
// make sure images are square powers of two
|
||||
int width = (int)(bufimg.getWidth() * scale);
|
||||
int height = (int)(bufimg.getHeight() * scale);
|
||||
int tsize = nextPOT(Math.max(width, height));
|
||||
|
||||
// convert the the image to the format that OpenGL prefers
|
||||
BufferedImage dispimg = createCompatibleImage(
|
||||
tsize, tsize, bufimg.getColorModel().hasAlpha());
|
||||
|
||||
// flip the image to convert into OpenGL's coordinate system
|
||||
AffineTransform tx = null;
|
||||
if (flip) {
|
||||
tx = AffineTransform.getScaleInstance(scale, -scale);
|
||||
tx.translate((tsize - width) / 2, (height - tsize) / 2 - bufimg.getHeight());
|
||||
}
|
||||
|
||||
// "convert" the image by rendering the old into the new
|
||||
Graphics2D gfx = (Graphics2D)dispimg.getGraphics();
|
||||
gfx.drawImage(bufimg, tx, null);
|
||||
gfx.dispose();
|
||||
|
||||
// now extract the image data into a JME image
|
||||
return convertImage(dispimg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a buffered image in a format compatible with LWJGL with the specified dimensions.
|
||||
*
|
||||
* @param transparent if true, the image will be four bytes per pixel (RGBA), if false it will
|
||||
* be three (and have no alpha channel).
|
||||
*/
|
||||
public static BufferedImage createCompatibleImage (int width, int height, boolean transparent)
|
||||
{
|
||||
if (transparent) {
|
||||
return new BufferedImage(GL_ALPHA_MODEL, Raster.createInterleavedRaster(
|
||||
DataBuffer.TYPE_BYTE, width, height, 4, null),
|
||||
false, null);
|
||||
} else {
|
||||
return new BufferedImage(GL_OPAQUE_MODEL, Raster.createInterleavedRaster(
|
||||
DataBuffer.TYPE_BYTE, width, height, 3, null),
|
||||
false, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an image that was created with {@link #createCompatibleImage} into a JME {@link
|
||||
* Image}. The data is assumed to have already been "flipped".
|
||||
*/
|
||||
public static Image convertImage (BufferedImage bufimg)
|
||||
{
|
||||
Image image = new Image();
|
||||
image.setType(bufimg.getColorModel().hasAlpha() ? Image.RGBA8888 : Image.RGB888);
|
||||
image.setWidth(bufimg.getWidth());
|
||||
image.setHeight(bufimg.getHeight());
|
||||
image.setData(convertImage(bufimg, null));
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the supplied image (which must have been created with {@link
|
||||
* #createCompatibleImage}) into a {@link ByteBuffer} that can be passed to {@link
|
||||
* Image#setData}.
|
||||
*
|
||||
* @param target the results of a previous call to {@link #convertImage} that will be
|
||||
* overwritten or null if a new buffer should be allocated. Of course a reused buffer must be
|
||||
* used with the same image or one with the exact same configuration.
|
||||
*/
|
||||
public static ByteBuffer convertImage (BufferedImage image, ByteBuffer target)
|
||||
{
|
||||
DataBufferByte dbuf = (DataBufferByte)image.getRaster().getDataBuffer();
|
||||
byte[] data = dbuf.getData();
|
||||
if (target == null) {
|
||||
target = ByteBuffer.allocateDirect(data.length);
|
||||
target.order(ByteOrder.nativeOrder());
|
||||
}
|
||||
target.clear();
|
||||
target.put(data);
|
||||
target.flip();
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an image cache that will obtain its image data from the supplied resource manager.
|
||||
*/
|
||||
public ImageCache (ResourceManager rsrcmgr)
|
||||
{
|
||||
_rsrcmgr = rsrcmgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #getImage(String,boolean)}.
|
||||
*/
|
||||
public Image getImage (String rsrcPath)
|
||||
{
|
||||
return getImage(rsrcPath, 1f, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #getImage(String,float,boolean)}.
|
||||
*/
|
||||
public Image getImage (String rsrcPath, float scale)
|
||||
{
|
||||
return getImage(rsrcPath, scale, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up an image from the cache if possible or from the resource manager otherwise, in
|
||||
* which case it is prepared for use by JME and OpenGL. <em>Note:</em> these images are cached
|
||||
* separately from the {@link BImage} and {@link BufferedImage} caches.
|
||||
*
|
||||
* @param flip whether or not to convert the image from normal computer coordinates into OpenGL
|
||||
* coordinates when loading. <em>Note:</em> this information is not cached, an image must
|
||||
* <em>always</em> be requested as flipped or not flipped.
|
||||
*/
|
||||
public Image getImage (String rsrcPath, boolean flip)
|
||||
{
|
||||
return getImage(rsrcPath, 1f, flip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up an image from the cache if possible or from the resource manager otherwise, in
|
||||
* which case it is prepared for use by JME and OpenGL. <em>Note:</em> these images are cached
|
||||
* separately from the {@link BImage} and {@link BufferedImage} caches.
|
||||
*
|
||||
* @param flip whether or not to convert the image from normal computer coordinates into OpenGL
|
||||
* coordinates when loading. <em>Note:</em> this information is not cached, an image must
|
||||
* <em>always</em> be requested as flipped or not flipped.
|
||||
* @param scale a scale factor to apply to the image. As with the flip setting, the scale
|
||||
* factor is not cached.
|
||||
*/
|
||||
public Image getImage (String rsrcPath, float scale, boolean flip)
|
||||
{
|
||||
// first check the cache
|
||||
WeakReference<Image> iref = _imgcache.get(rsrcPath);
|
||||
Image image;
|
||||
if (iref != null && (image = iref.get()) != null) {
|
||||
return image;
|
||||
}
|
||||
|
||||
// load the image data from the resource manager
|
||||
BufferedImage bufimg;
|
||||
File ifile = _rsrcmgr.getResourceFile(rsrcPath);
|
||||
try {
|
||||
bufimg = ImageIO.read(ifile);
|
||||
} catch (Throwable t) {
|
||||
log.log(Level.WARNING, "Unable to load image resource " +
|
||||
"[path=" + ifile + "].", t);
|
||||
// cope; return an error image of abitrary size
|
||||
bufimg = ImageUtil.createErrorImage(64, 64);
|
||||
}
|
||||
|
||||
// create and cache a new JME image with the appropriate data
|
||||
image = createImage(bufimg, scale, flip);
|
||||
_imgcache.put(rsrcPath, new WeakReference<Image>(image));
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up an image from the cache if possible or from the resource manager otherwise, in
|
||||
* which case it is prepared for use by BUI. <em>Note:</em> these images are cached separately
|
||||
* from the {@link Image} and {@link BufferedImage} caches.
|
||||
*/
|
||||
public BImage getBImage (String rsrcPath)
|
||||
{
|
||||
return getBImage(rsrcPath, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up an image from the cache if possible or from the resource manager otherwise, in
|
||||
* which case it is prepared for use by BUI. <em>Note:</em> these images are cached separately
|
||||
* from the {@link Image} and {@link BufferedImage} caches.
|
||||
*
|
||||
* @param returnNull If set to true, will return a null instead of generating an error image on
|
||||
* failure
|
||||
*/
|
||||
public BImage getBImage (String rsrcPath, boolean returnNull)
|
||||
{
|
||||
// first check the cache
|
||||
WeakReference<BImage> iref = _buicache.get(rsrcPath);
|
||||
BImage image;
|
||||
if (iref != null && (image = iref.get()) != null) {
|
||||
return image;
|
||||
}
|
||||
|
||||
// load the image data from the resource manager
|
||||
BufferedImage bufimg;
|
||||
File ifile = _rsrcmgr.getResourceFile(rsrcPath);
|
||||
try {
|
||||
bufimg = ImageIO.read(ifile);
|
||||
} catch (Throwable t) {
|
||||
if (returnNull) {
|
||||
return null;
|
||||
}
|
||||
log.log(Level.WARNING, "Unable to load image resource [path=" + ifile + "].", t);
|
||||
// cope; return an error image of abitrary size
|
||||
bufimg = ImageUtil.createErrorImage(64, 64);
|
||||
}
|
||||
|
||||
// create and cache a new BUI image with the appropriate data
|
||||
image = new BImage(bufimg, true);
|
||||
_buicache.put(rsrcPath, new WeakReference<BImage>(image));
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up a silhouette image (in which all non-transparent pixels are set to black) from the
|
||||
* cache if possible or from the resource manager otherwise, in which case it is prepared for
|
||||
* use by BUI.
|
||||
*/
|
||||
public BImage getSilhouetteBImage (String rsrcPath, boolean returnNull)
|
||||
{
|
||||
// first check the cache
|
||||
String key = "silhouette:" + rsrcPath;
|
||||
WeakReference<BImage> iref = _buicache.get(key);
|
||||
BImage image;
|
||||
if (iref != null && (image = iref.get()) != null) {
|
||||
return image;
|
||||
}
|
||||
|
||||
// load the image data from the resource manager
|
||||
BufferedImage bufimg, silimg;
|
||||
File ifile = _rsrcmgr.getResourceFile(rsrcPath);
|
||||
try {
|
||||
bufimg = ImageIO.read(ifile);
|
||||
|
||||
// now turn it into a silhouette
|
||||
silimg = new BufferedImage(bufimg.getWidth(), bufimg.getHeight(),
|
||||
BufferedImage.TYPE_4BYTE_ABGR);
|
||||
new BandCombineOp(NON_ALPHA_TO_BROWN, null).filter(
|
||||
bufimg.getRaster(), silimg.getRaster());
|
||||
|
||||
} catch (Throwable t) {
|
||||
if (returnNull) {
|
||||
return null;
|
||||
}
|
||||
log.log(Level.WARNING, "Unable to load image resource [path=" + ifile + "].", t);
|
||||
// cope; return an error image of abitrary size
|
||||
silimg = ImageUtil.createErrorImage(64, 64);
|
||||
}
|
||||
|
||||
// create and cache a new BUI image with the appropriate data
|
||||
image = new BImage(silimg, true);
|
||||
_buicache.put(key, new WeakReference<BImage>(image));
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorizes the image with supplied path (which must be an 8-bit colormapped image), then
|
||||
* converts the colorized image into a form that JME can display.
|
||||
*/
|
||||
public BImage createColorizedBImage (String path, Colorization[] zations, boolean flip)
|
||||
{
|
||||
return new BImage(ImageUtil.recolorImage(getBufferedImage(path), zations), flip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up a buffered image from the cache if possible or from the resource manager
|
||||
* otherwise. <em>Note:</em> these images are cached separately from the {@link Image} and
|
||||
* {@link BImage} caches.
|
||||
*/
|
||||
public BufferedImage getBufferedImage (String rsrcPath)
|
||||
{
|
||||
// first check the cache
|
||||
WeakReference<BufferedImage> iref = _bufcache.get(rsrcPath);
|
||||
BufferedImage image;
|
||||
if (iref != null && (image = iref.get()) != null) {
|
||||
return image;
|
||||
}
|
||||
|
||||
// load the image data from the resource manager
|
||||
File ifile = _rsrcmgr.getResourceFile(rsrcPath);
|
||||
try {
|
||||
image = ImageIO.read(ifile);
|
||||
} catch (Throwable t) {
|
||||
log.log(Level.WARNING, "Unable to load image resource [path=" + ifile + "].", t);
|
||||
// cope; return an error image of abitrary size
|
||||
image = ImageUtil.createErrorImage(64, 64);
|
||||
}
|
||||
|
||||
_bufcache.put(rsrcPath, new WeakReference<BufferedImage>(image));
|
||||
return image;
|
||||
}
|
||||
|
||||
/** Rounds the supplied value up to a power of two. */
|
||||
protected static int nextPOT (int value)
|
||||
{
|
||||
return (Integer.bitCount(value) > 1) ? (Integer.highestOneBit(value) << 1) : value;
|
||||
}
|
||||
|
||||
/** Provides access to our image data. */
|
||||
protected ResourceManager _rsrcmgr;
|
||||
|
||||
/** A cache of {@link Image} instances. */
|
||||
protected HashMap<String,WeakReference<Image>> _imgcache =
|
||||
new HashMap<String,WeakReference<Image>>();
|
||||
|
||||
/** A cache of {@link BImage} instances. */
|
||||
protected HashMap<String,WeakReference<BImage>> _buicache =
|
||||
new HashMap<String,WeakReference<BImage>>();
|
||||
|
||||
/** A cache of {@link BufferedImage} instances. */
|
||||
protected HashMap<String,WeakReference<BufferedImage>> _bufcache =
|
||||
new HashMap<String,WeakReference<BufferedImage>>();
|
||||
|
||||
/** Used to create buffered images in a format compatible with OpenGL. */
|
||||
protected static ComponentColorModel GL_ALPHA_MODEL = new ComponentColorModel(
|
||||
ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] { 8, 8, 8, 8 },
|
||||
true, false, ComponentColorModel.TRANSLUCENT, DataBuffer.TYPE_BYTE);
|
||||
|
||||
/** Used to create buffered images in a format compatible with OpenGL. */
|
||||
protected static ComponentColorModel GL_OPAQUE_MODEL = new ComponentColorModel(
|
||||
ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] { 8, 8, 8, 0 },
|
||||
false, false, ComponentColorModel.OPAQUE, DataBuffer.TYPE_BYTE);
|
||||
|
||||
/** An operation that converts all channels except alpha to brown. */
|
||||
protected static final float[][] NON_ALPHA_TO_BROWN = {
|
||||
{ 0, 0, 0, 0x66/255f },
|
||||
{ 0, 0, 0, 0x57/255f },
|
||||
{ 0, 0, 0, 0x34/255f },
|
||||
{ 0, 0, 0, 0.5f }
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user