ImageCache cleared from old unused stuff

This commit is contained in:
Maxmtg
2013-02-20 23:06:41 +00:00
parent 12ae0de97f
commit 2af6395649
4 changed files with 115 additions and 419 deletions

2
.gitattributes vendored
View File

@@ -13630,6 +13630,7 @@ src/main/java/forge/CounterType.java svneol=native#text/plain
src/main/java/forge/GameEntity.java -text src/main/java/forge/GameEntity.java -text
src/main/java/forge/GameLog.java -text src/main/java/forge/GameLog.java -text
src/main/java/forge/ImageCache.java svneol=native#text/plain src/main/java/forge/ImageCache.java svneol=native#text/plain
src/main/java/forge/ImageLoader.java -text
src/main/java/forge/Singletons.java svneol=native#text/plain src/main/java/forge/Singletons.java svneol=native#text/plain
src/main/java/forge/StaticEffect.java svneol=native#text/plain src/main/java/forge/StaticEffect.java svneol=native#text/plain
src/main/java/forge/StaticEffects.java svneol=native#text/plain src/main/java/forge/StaticEffects.java svneol=native#text/plain
@@ -14473,7 +14474,6 @@ src/main/java/forge/view/arcane/package-info.java svneol=native#text/plain
src/main/java/forge/view/arcane/util/Animation.java svneol=native#text/plain src/main/java/forge/view/arcane/util/Animation.java svneol=native#text/plain
src/main/java/forge/view/arcane/util/CardPanelMouseListener.java svneol=native#text/plain src/main/java/forge/view/arcane/util/CardPanelMouseListener.java svneol=native#text/plain
src/main/java/forge/view/arcane/util/GlowText.java svneol=native#text/plain src/main/java/forge/view/arcane/util/GlowText.java svneol=native#text/plain
src/main/java/forge/view/arcane/util/ImageUtil.java svneol=native#text/plain
src/main/java/forge/view/arcane/util/UI.java svneol=native#text/plain src/main/java/forge/view/arcane/util/UI.java svneol=native#text/plain
src/main/java/forge/view/arcane/util/package-info.java svneol=native#text/plain src/main/java/forge/view/arcane/util/package-info.java svneol=native#text/plain
src/main/java/forge/view/package-info.java svneol=native#text/plain src/main/java/forge/view/package-info.java svneol=native#text/plain

View File

@@ -17,29 +17,16 @@
*/ */
package forge; package forge;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheLoader.InvalidCacheLoadException; import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.google.common.collect.ComputationException;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.mortennobel.imagescaling.ResampleOp; import com.mortennobel.imagescaling.ResampleOp;
import forge.gui.GuiDisplayUtil; import forge.gui.GuiDisplayUtil;
import forge.item.InventoryItem; import forge.item.InventoryItem;
import forge.properties.ForgePreferences.FPref; import forge.properties.ForgePreferences.FPref;
import forge.properties.ForgeProps;
import forge.properties.NewConstants;
import forge.view.arcane.util.ImageUtil;
/** /**
* This class stores ALL card images in a cache with soft values. this means * This class stores ALL card images in a cache with soft values. this means
@@ -50,11 +37,6 @@ import forge.view.arcane.util.ImageUtil;
* <ul> * <ul>
* <li>Keys start with the file name, extension is skipped</li> * <li>Keys start with the file name, extension is skipped</li>
* <li>The key without suffix belongs to the unmodified image from the file</li> * <li>The key without suffix belongs to the unmodified image from the file</li>
* <li>If the key belongs to a token, "#Token" is appended</li>
* <li>If the key belongs to the unrotated image, "#Normal" is appended</li>
* <li>If the key belongs to the rotated image, "#Tapped" is appended</li>
* <li>If the key belongs to the large preview image, "#<i>scale</i>" is
* appended, where scale is a double precision floating point number</li>
* </ul> * </ul>
* *
* @author Forge * @author Forge
@@ -62,157 +44,64 @@ import forge.view.arcane.util.ImageUtil;
*/ */
public class ImageCache { public class ImageCache {
/** Constant <code>imageCache</code>. */ /** Constant <code>imageCache</code>. */
private static final LoadingCache<String, BufferedImage> IMAGE_CACHE; static final LoadingCache<String, BufferedImage> CACHE = CacheBuilder.newBuilder().softValues().build(new ImageLoader());
/** Constant <code>FULL_SIZE</code>. */ /** Constant <code>FULL_SIZE</code>. */
private static final Pattern FULL_SIZE = Pattern.compile("(.*)#([0-9.]+)");
private static final String NORMAL = "#Normal", TAPPED = "#Tapped";
public static final String SEALED_PRODUCT = "sealed://"; public static final String SEALED_PRODUCT = "sealed://";
private static final String TOKEN = "token://"; static final String TOKEN = "token://";
static {
IMAGE_CACHE = CacheBuilder.newBuilder()
.softValues()
.build(new CacheLoader<String, BufferedImage>() {
@Override
public BufferedImage load(String key) {
try {
// DEBUG
/*
System.out.printf("Currently %d %s in the cache%n",
IMAGE_CACHE.size(), IMAGE_CACHE.size() == 1 ?
"image":"images");
System.out.println("Contents: "+IMAGE_CACHE.toString());
System.out.println("Stats: " + IMAGE_CACHE.stats());
*/
// DEBUG
// System.out.printf("New Image for key: %s%n", key);
if (key.endsWith(ImageCache.NORMAL)) {
// normal
key = key.substring(0, key.length() - ImageCache.NORMAL.length());
return ImageCache.getNormalSizeImage(ImageCache.IMAGE_CACHE.get(key));
} else if (key.endsWith(ImageCache.TAPPED)) {
// tapped
key = key.substring(0, key.length() - ImageCache.TAPPED.length());
return ImageCache.getTappedSizeImage(ImageCache.IMAGE_CACHE.get(key));
}
final Matcher m = ImageCache.FULL_SIZE.matcher(key);
if (m.matches()) {
// full size
key = m.group(1);
return ImageCache.getFullSizeImage(ImageCache.IMAGE_CACHE.get(key),
Double.parseDouble(m.group(2)));
} else {
// original
File path;
String filename = key;
if (key.startsWith(ImageCache.TOKEN)) {
filename = key.substring(ImageCache.TOKEN.length());
path = ForgeProps.getFile(NewConstants.IMAGE_TOKEN);
} else if (key.startsWith(SEALED_PRODUCT)) {
filename = key.substring(SEALED_PRODUCT.length());
path = ForgeProps.getFile(NewConstants.IMAGE_SEALED_PRODUCT);
} else {
path = ForgeProps.getFile(NewConstants.IMAGE_BASE);
}
File file = null;
final String fName = filename.endsWith(".png") || filename.endsWith(".jpg") ? filename : filename + ".jpg";
file = new File(path, fName);
if (!file.exists()) {
// DEBUG
//System.out.println("File not found, no image created: "
//+ file);
return null;
}
final BufferedImage image = ImageUtil.getImage(file);
IMAGE_CACHE.put(key, image);
return image;
}
} catch (final Exception ex) {
// DEBUG
// System.err.println("Exception, no image created");
if (ex instanceof ComputationException) {
throw (ComputationException) ex;
} else {
throw new ComputationException(ex);
}
}
}
});
}
/**
* Returns the image appropriate to display the card in a zone.
*
* @param card
* a {@link forge.Card} object.
* @return a {@link java.awt.image.BufferedImage} object.
*/
public static BufferedImage getImage(final Card card) {
String key = card.isFaceDown() ? "Morph" : ImageCache.getKey(card);
if (card.isTapped()) {
key += ImageCache.TAPPED;
} else {
key += ImageCache.NORMAL;
}
return ImageCache.getImage(key);
}
/**
* Returns the image appropriate to display the card in the picture panel.
*
* @param card
* a {@link forge.Card} object.
* @param width
* a int.
* @param height
* a int.
* @return a {@link java.awt.image.BufferedImage} object.
*/
public static BufferedImage getImage(final Card card, final int width, final int height) { public static BufferedImage getImage(final Card card, final int width, final int height) {
//SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
//System.out.printf("%s - load '%s' (%d x %d)\n", sdf.format(new Date()), card.getImageFilename(), width, height );
final String key = card.canBeShownTo(Singletons.getControl().getPlayer()) ? ImageCache.getKey(card) : "Morph"; final String key = card.canBeShownTo(Singletons.getControl().getPlayer()) ? ImageCache.getKey(card) : "Morph";
final BufferedImage original = ImageCache.getImage(key); return scaleImage(key, width, height);
if (original == null) { }
return null;
}
double scale = Math.min((double) width / original.getWidth(), (double) height / original.getHeight());
// here would be the place to limit the scaling, scaling option in menu
// ?
if ((scale > 1) && !Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_SCALE_LARGER)) {
scale = 1;
}
return ImageCache.getImage(key + "#" + scale); public static BufferedImage getImage(final InventoryItem card, final int width, final int height) {
String key = card.getImageFilename();
return scaleImage(key, width, height);
} }
/** /**
* Gets the image. * TODO: Write javadoc for this method.
* * @param original
* @param card * @param scale
* the card * @return
* @param width
* the width
* @param height
* the height
* @return the image
*/ */
public static BufferedImage getImage(final InventoryItem card, final int width, final int height) { private static BufferedImage scaleImage(String key, final int width, final int height) {
final String key = card.getImageFilename(); StringBuilder rsKey = new StringBuilder(key);
final BufferedImage original = ImageCache.getImage(key); rsKey.append("#").append(width).append('x').append(height);
if (original == null) { String resizedKey = rsKey.toString();
return null;
}
final BufferedImage ready = CACHE.getIfPresent(resizedKey);
if ( null != ready )
return ready;
BufferedImage original = getImage(key);
//return original;
double scale = Math.min((double) width / original.getWidth(), (double) height / original.getHeight()); double scale = Math.min((double) width / original.getWidth(), (double) height / original.getHeight());
// here would be the place to limit the scaling option in menu ? // here would be the place to limit the scaling option in menu ?
if ((scale > 1) && !Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_SCALE_LARGER)) { if ((scale > 1) && !Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_SCALE_LARGER)) {
scale = 1; scale = 1;
} }
return ImageCache.getImage(key + "#" + scale); BufferedImage result;
if ( 1 == scale ) {
result = original;
} else {
int destWidth = (int) (original.getWidth() * scale);
int destHeight = (int) (original.getHeight() * scale);
ResampleOp resampler = new ResampleOp(destWidth, destHeight);
result = resampler.filter(original, null);
}
CACHE.put(resizedKey, result);
return result;
} }
/** /**
@@ -238,14 +127,7 @@ public class ImageCache {
*/ */
private static BufferedImage getImage(final String key) { private static BufferedImage getImage(final String key) {
try { try {
BufferedImage image = ImageCache.IMAGE_CACHE.get(key); return ImageCache.CACHE.get(key);
// if an image is still cached and it was not the expected size,
// drop it
if (!ImageCache.isExpectedSize(key, image)) {
ImageCache.IMAGE_CACHE.invalidate(key);
image = ImageCache.IMAGE_CACHE.get(key);
}
return image;
} catch (final ExecutionException ex) { } catch (final ExecutionException ex) {
if (ex.getCause() instanceof NullPointerException) { if (ex.getCause() instanceof NullPointerException) {
return null; return null;
@@ -255,38 +137,6 @@ public class ImageCache {
} catch (final InvalidCacheLoadException ex) { } catch (final InvalidCacheLoadException ex) {
// should be when a card legitimately has no image // should be when a card legitimately has no image
return null; return null;
} catch (final ComputationException ce) {
if (ce.getCause() instanceof NullPointerException) {
return null;
}
ce.printStackTrace();
return null;
} catch (final UncheckedExecutionException uee) {
//this is for the case where "Player" shows up as a Card in GuiMultipleBlockers
//when human attacker has Trample
return null;
}
}
/**
* Returns if the image for the key is the proper size.
*
* @param key
* a {@link java.lang.String} object.
* @param image
* a {@link java.awt.image.BufferedImage} object.
* @return a boolean.
*/
private static boolean isExpectedSize(final String key, final BufferedImage image) {
if (key.endsWith(ImageCache.NORMAL)) {
// normal
return (image.getWidth() == Constant.Runtime.WIDTH) && (image.getHeight() == Constant.Runtime.HEIGHT);
} else if (key.endsWith(ImageCache.TAPPED)) {
// tapped
return (image.getWidth() == Constant.Runtime.HEIGHT) && (image.getHeight() == Constant.Runtime.WIDTH);
} else {
// original & full is never wrong
return true;
} }
} }
@@ -306,129 +156,4 @@ public class ImageCache {
return card.getImageFilename(); // key; return card.getImageFilename(); // key;
} }
/**
* Returns an image scaled to the size given in {@link Constant.Runtime}.
*
* @param original
* a {@link java.awt.image.BufferedImage} object.
* @return a {@link java.awt.image.BufferedImage} object.
*/
private static BufferedImage getNormalSizeImage(final BufferedImage original) {
final int srcWidth = original.getWidth();
final int srcHeight = original.getHeight();
final int tgtWidth = Constant.Runtime.WIDTH;
final int tgtHeight = Constant.Runtime.HEIGHT;
if ((srcWidth == tgtWidth) && (srcHeight == tgtHeight)) {
return original;
}
// AffineTransform at = new AffineTransform();
// at.scale((double) tgtWidth / srcWidth, (double) tgtHeight /
// srcHeight);
// // at.translate(srcHeight, 0);
// // at.rotate(PI / 2);
// double scale = min((double) tgtWidth / srcWidth, (double) tgtHeight /
// srcHeight);
//
// BufferedImage image = new BufferedImage(tgtWidth, tgtHeight,
// BufferedImage.TYPE_INT_ARGB);
// Graphics2D g2d = (Graphics2D) image.getGraphics();
// g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
// RenderingHints.VALUE_INTERPOLATION_BICUBIC);
// g2d.drawImage(scale < 0.5? ImageUtil.getBlurredImage(original, 6,
// 1.0f):original, at, null);
// g2d.dispose();
final ResampleOp resampleOp = new ResampleOp(tgtWidth, tgtHeight); // defaults
// to
// Lanczos3
final BufferedImage image = resampleOp.filter(original, null);
return image;
}
/**
* Returns an image scaled to the size given in {@link Constant.Runtime},
* but rotated.
*
* @param original
* a {@link java.awt.image.BufferedImage} object.
* @return a {@link java.awt.image.BufferedImage} object.
*/
private static BufferedImage getTappedSizeImage(final BufferedImage original) {
/*
* int srcWidth = original.getWidth(); int srcHeight =
* original.getHeight();
*/
final int tgtWidth = Constant.Runtime.WIDTH;
final int tgtHeight = Constant.Runtime.HEIGHT;
final AffineTransform at = new AffineTransform();
// at.scale((double) tgtWidth / srcWidth, (double) tgtHeight /
// srcHeight);
at.translate(tgtHeight, 0);
at.rotate(Math.PI / 2);
//
// double scale = min((double) tgtWidth / srcWidth, (double) tgtHeight /
// srcHeight);
//
// BufferedImage image = new BufferedImage(tgtHeight, tgtWidth,
// BufferedImage.TYPE_INT_ARGB);
// Graphics2D g2d = (Graphics2D) image.getGraphics();
// g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
// RenderingHints.VALUE_INTERPOLATION_BICUBIC);
// g2d.drawImage(scale < 0.5? ImageUtil.getBlurredImage(original, 6,
// 1.0f):original, at, null);
// g2d.dispose();
final ResampleOp resampleOp = new ResampleOp(tgtWidth, tgtHeight); // defaults
// to
// Lanczos3
final BufferedImage image = resampleOp.filter(original, null);
final BufferedImage rotatedImage = new BufferedImage(tgtHeight, tgtWidth, BufferedImage.TYPE_INT_ARGB);
final Graphics2D g2d = (Graphics2D) rotatedImage.getGraphics();
g2d.drawImage(image, at, null);
g2d.dispose();
return rotatedImage;
}
/**
* Returns an image scaled to the size appropriate for the card picture
* panel.
*
* @param original
* a {@link java.awt.image.BufferedImage} object.
* @param scale
* a double.
* @return a {@link java.awt.image.BufferedImage} object.
*/
private static BufferedImage getFullSizeImage(final BufferedImage original, final double scale) {
if (scale == 1) {
return original;
}
// AffineTransform at = new AffineTransform();
// at.scale(scale, scale);
//
// BufferedImage image = new BufferedImage((int) (original.getWidth() *
// scale),
// (int) (original.getHeight() * scale), BufferedImage.TYPE_INT_ARGB);
// Graphics2D g2d = (Graphics2D) image.getGraphics();
// g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
// RenderingHints.VALUE_INTERPOLATION_BICUBIC);
// g2d.drawImage(scale < 0.5? ImageUtil.getBlurredImage(original, 6,
// 1.0f):original, at, null);
// g2d.dispose();
final ResampleOp resampleOp = new ResampleOp((int) (original.getWidth() * scale),
(int) (original.getHeight() * scale)); // defaults to Lanczos3
final BufferedImage image = resampleOp.filter(original, null);
return image;
}
/**
* <p>
* clear.
* </p>
*/
public static void clear() {
ImageCache.IMAGE_CACHE.invalidateAll();
}
} }

View File

@@ -0,0 +1,72 @@
package forge;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import com.google.common.cache.CacheLoader;
import forge.error.BugReporter;
import forge.properties.ForgeProps;
import forge.properties.NewConstants;
/**
* TODO: Write javadoc for this type.
*
*/
final class ImageLoader extends CacheLoader<String, BufferedImage> {
@Override
public BufferedImage load(String key) {
// original
File path;
String filename;
if (key.startsWith(ImageCache.TOKEN)) {
filename = key.substring(ImageCache.TOKEN.length());
path = ForgeProps.getFile(NewConstants.IMAGE_TOKEN);
} else if (key.startsWith(ImageCache.SEALED_PRODUCT)) {
filename = key.substring(ImageCache.SEALED_PRODUCT.length());
path = ForgeProps.getFile(NewConstants.IMAGE_SEALED_PRODUCT);
} else {
filename = key;
path = ForgeProps.getFile(NewConstants.IMAGE_BASE);
}
File file = null;
boolean isPng = filename.endsWith(".png");
final String fName = isPng || filename.endsWith(".jpg") ? filename : filename + ".jpg";
file = new File(path, fName);
if (!file.exists()) {
// DEBUG
//System.out.println("File not found, no image created: "
//+ file);
return null;
}
final BufferedImage image = getImage(file);
//ImageCache.IMAGE_CACHE.put(key, image);
return image;
}
/**
* <p>
* getImage.
* </p>
*
* @param file a {@link java.io.File} object.
* @return a {@link java.awt.image.BufferedImage} object.
* @throws IOException Signals that an I/O exception has occurred.
*/
public static BufferedImage getImage(final File file) {
//System.out.printf("Loading from disk: %s\n", file.toString());
BufferedImage image;
//int format = useAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB;
try {
image = ImageIO.read(file);
} catch (IOException ex) {
BugReporter.reportException(ex, "Could not read image file " + file.getAbsolutePath() + " ");
return null;
}
return image;
}
}

View File

@@ -1,101 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Nate
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.view.arcane.util;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
/**
* <p>
* ImageUtil class.
* </p>
*
* @author Forge
* @version $Id$
*/
public class ImageUtil {
/**
* <p>
* getImage.
* </p>
*
* @param stream a {@link java.io.InputStream} object.
* @return a {@link java.awt.image.BufferedImage} object.
* @throws IOException Signals that an I/O exception has occurred.
*/
public static BufferedImage getImage(final InputStream stream) throws IOException {
Image tempImage = ImageIO.read(stream);
BufferedImage image = new BufferedImage(tempImage.getWidth(null), tempImage.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = image.createGraphics();
g2.drawImage(tempImage, 0, 0, null);
g2.dispose();
return image;
}
/**
* <p>
* getImage.
* </p>
*
* @param file a {@link java.io.File} object.
* @return a {@link java.awt.image.BufferedImage} object.
* @throws IOException Signals that an I/O exception has occurred.
*/
public static BufferedImage getImage(final File file) throws IOException {
Image tempImage = ImageIO.read(file);
BufferedImage image = new BufferedImage(tempImage.getWidth(null), tempImage.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = image.createGraphics();
g2.drawImage(tempImage, 0, 0, null);
g2.dispose();
return image;
}
/**
* <p>
* getBlurredImage.
* </p>
*
* @param image
* a {@link java.awt.image.BufferedImage} object.
* @param radius
* a int.
* @param intensity
* a float.
* @return a {@link java.awt.image.BufferedImage} object.
*/
public static BufferedImage getBlurredImage(final BufferedImage image, final int radius, final float intensity) {
float weight = intensity / (radius * radius);
float[] elements = new float[radius * radius];
for (int i = 0, n = radius * radius; i < n; i++) {
elements[i] = weight;
}
ConvolveOp blurOp = new ConvolveOp(new Kernel(radius, radius, elements));
return blurOp.filter(image, null);
}
}