package org.mage.plugins.card.images;
import com.google.common.base.Function;
import com.google.common.collect.ComputationException;
import com.google.common.collect.MapMaker;
import com.mortennobel.imagescaling.ResampleOp;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import mage.client.dialog.PreferencesDialog;
import mage.view.CardView;
import net.java.truevfs.access.TFile;
import net.java.truevfs.access.TFileInputStream;
import net.java.truevfs.access.TFileOutputStream;
import org.apache.log4j.Logger;
import org.mage.plugins.card.constants.Constants;
import org.mage.plugins.card.dl.sources.DirectLinksForDownload;
import org.mage.plugins.card.utils.CardImageUtils;
/**
* This class stores ALL card images in a cache with soft values. this means
* that the images may be garbage collected when they are not needed any more, but will
* be kept as long as possible.
*
* Key format: "<cardname>#<setname>#<type>#<collectorID>#<param>"
*
* where param is:
*
* <ul>
* <li>#Normal: request for unrotated image</li>
* <li>#Tapped: request for rotated image</li>
* <li>#Cropped: request for cropped image that is used for Shandalar like card
* look</li>
* </ul>
*/
public class ImageCache {
private static final Logger log = Logger.getLogger(ImageCache.class);
private static final Map<String, BufferedImage> imageCache;
/**
* Common pattern for keys.
* Format: "<cardname>#<setname>#<collectorID>"
*/
private static final Pattern KEY_PATTERN = Pattern.compile("(.*)#(.*)#(.*)#(.*)#(.*)");
static {
imageCache = new MapMaker().softValues().makeComputingMap(new Function<String, BufferedImage>() {
@Override
public BufferedImage apply(String key) {
try {
boolean usesVariousArt = false;
if (key.endsWith("#usesVariousArt")) {
usesVariousArt = true;
key = key.replace("#usesVariousArt", "");
}
boolean thumbnail = false;
if (key.endsWith("#thumb")) {
thumbnail = true;
key = key.replace("#thumb", "");
}
Matcher m = KEY_PATTERN.matcher(key);
if (m.matches()) {
String name = m.group(1);
String set = m.group(2);
Integer type = Integer.parseInt(m.group(3));
Integer collectorId = Integer.parseInt(m.group(4));
String tokenSetCode = m.group(5);
CardDownloadData info = new CardDownloadData(name, set, collectorId, usesVariousArt, type, tokenSetCode);
String path;
if (collectorId == 0) {
info.setToken(true);
path = CardImageUtils.generateTokenImagePath(info);
if (path == null) {
path = DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename;
}
} else {
path = CardImageUtils.generateImagePath(info);
}
if (path == null) {
return null;
}
TFile file = new TFile(path);
if (!file.exists()) {
log.debug("File does not exist: " + file.toString());
return null;
}
if (thumbnail && path.endsWith(".jpg")) {
String thumbnailPath = buildThumbnailPath(path);
TFile thumbnailFile = new TFile(thumbnailPath);
if (thumbnailFile.exists()) {
log.debug("loading thumbnail for " + key + ", path="+thumbnailPath);
return loadImage(thumbnailFile);
} else {
BufferedImage image = loadImage(file);
image = getWizardsCard(image);
if (image == null) {
return null;
}
log.debug("creating thumbnail for " + key);
return makeThumbnail(image, thumbnailPath);
}
} else {
return getWizardsCard(loadImage(file));
}
} else {
throw new RuntimeException(
"Requested image doesn't fit the requirement for key (<cardname>#<setname>#<collectorID>): " + key);
}
} catch (Exception ex) {
if (ex instanceof ComputationException) {
throw (ComputationException) ex;
} else {
throw new ComputationException(ex);
}
}
}
});
}
public static BufferedImage getMorphImage() {
CardDownloadData info = new CardDownloadData("Morph", "KTK", 0, false, 0, "KTK");
info.setToken(true);
String path = CardImageUtils.generateTokenImagePath(info);
TFile file = new TFile(path);
return loadImage(file);
}
private static String buildThumbnailPath(String path) {
String thumbnailPath;
if (PreferencesDialog.isSaveImagesToZip()) {
thumbnailPath = path.replace(".zip", ".thumb.zip");
} else {
thumbnailPath = path.replace(".jpg", ".thumb.jpg");
}
return thumbnailPath;
}
public static BufferedImage getWizardsCard(BufferedImage image) {
if (image.getWidth() == 265 && image.getHeight() == 370) {
BufferedImage crop = new BufferedImage(256, 360, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = crop.createGraphics();
graphics2D.drawImage(image, 0, 0, 255, 360, 5, 5, 261, 365, null);
graphics2D.dispose();
return crop;
} else {
return image;
}
}
public static BufferedImage getThumbnail(CardView card) {
String key = getKey(card) + "#thumb";
if (card.getUsesVariousArt()) {
key += "#usesVariousArt";
}
// log.debug("#key: " + key);
return getImage(key);
}
public static BufferedImage getImageOriginal(CardView card) {
String key = getKey(card);
if (card.getUsesVariousArt()) {
key += "#usesVariousArt";
}
// log.warn("#key: " + key);
return getImage(key);
}
public static BufferedImage getImageOriginalAlternateName(CardView card) {
String key = getKeyAlternateName(card, card.getAlternateName());
if (card.getUsesVariousArt()) {
key += "#usesVariousArt";
}
// log.warn("#key: " + key);
return getImage(key);
}
// /**
// * Returns the Image corresponding to the Path
// */
// private static BufferedImage getImageByPath(String path) {
// if (path == null) {
// return null;
// }
// TFile file = new TFile(path);
// if (!file.exists()) {
// log.warn("File does not exist: " + file.toString());
// return null;
// }
// return getWizardsCard(loadImage(file));
//
// }
/**
* Returns the Image corresponding to the key
*/
private static BufferedImage getImage(String key) {
try {
BufferedImage image = imageCache.get(key);
return image;
} catch (NullPointerException ex) {
// unfortunately NullOutputException, thrown when apply() returns
// null, is not public
// NullOutputException is a subclass of NullPointerException
// legitimate, happens when a card has no image
return null;
} catch (ComputationException ex) {
if (ex.getCause() instanceof NullPointerException) {
return null;
}
log.error(ex,ex);
return null;
}
}
/**
* Returns the map key for a card, without any suffixes for the image size.
*/
private static String getKey(CardView card) {
StringBuilder sb = new StringBuilder(card.getName()).append("#");
sb.append(card.getExpansionSetCode()).append("#");
sb.append(card.getType()).append("#");
sb.append(card.getCardNumber()).append("#");
sb.append(card.getTokenSetCode() == null ? "":card.getTokenSetCode());
return sb.toString();
}
/**
* Returns the map key for the flip image of a card, without any suffixes for the image size.
*/
private static String getKeyAlternateName(CardView card, String alternateName) {
StringBuilder sb = new StringBuilder(alternateName).append("#");
sb.append(card.getExpansionSetCode()).append("#");
sb.append(card.getType()).append("#");
sb.append(card.getCardNumber()).append("#");
sb.append(card.getTokenSetCode() == null ? "":card.getTokenSetCode());
return sb.toString();
}
/**
* Load image from file
*
* @param file
* file to load image from
* @return {@link BufferedImage}
*/
public static BufferedImage loadImage(TFile file) {
BufferedImage image = null;
if (!file.exists()) {
log.debug("File does not exist: " + file.toString());
return null;
}
try {
TFileInputStream inputStream = new TFileInputStream(file);
image = ImageIO.read(inputStream);
inputStream.close();
} catch (Exception e) {
log.error(e, e);
}
return image;
}
public static BufferedImage makeThumbnail(BufferedImage original, String path) {
BufferedImage image = getResizedImage(original, Constants.THUMBNAIL_SIZE_FULL);
TFile imageFile = new TFile(path);
try {
TFileOutputStream outputStream = null;
try {
//log.debug("thumbnail path:"+path);
outputStream = new TFileOutputStream(imageFile);
ImageIO.write(image, "jpg", outputStream);
} finally {
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
log.error(e,e);
}
return image;
}
/**
* Returns an image scaled to the size given
* @param original
* @return
*/
public static BufferedImage getNormalSizeImage(BufferedImage original) {
if (original == null) {
return null;
}
int srcWidth = original.getWidth();
int srcHeight = original.getHeight();
int tgtWidth = Constants.CARD_SIZE_FULL.width;
int tgtHeight = Constants.CARD_SIZE_FULL.height;
if (srcWidth == tgtWidth && srcHeight == tgtHeight) {
return original;
}
ResampleOp resampleOp = new ResampleOp(tgtWidth, tgtHeight);
BufferedImage image = resampleOp.filter(original, null);
return image;
}
/**
* Returns an image scaled to the size appropriate for the card picture
* panel For future use.
*/
private static BufferedImage getFullSizeImage(BufferedImage original, double scale) {
if (scale == 1) {
return original;
}
ResampleOp resampleOp = new ResampleOp((int) (original.getWidth() * scale), (int) (original.getHeight() * scale));
BufferedImage image = resampleOp.filter(original, null);
return image;
}
/**
* Returns an image scaled to the size appropriate for the card picture
* panel
* @param original
* @param sizeNeed
* @return
*/
public static BufferedImage getResizedImage(BufferedImage original, Rectangle sizeNeed) {
ResampleOp resampleOp = new ResampleOp(sizeNeed.width, sizeNeed.height);
BufferedImage image = resampleOp.filter(original, null);
return image;
}
/**
* Returns the image appropriate to display the card in the picture panel
* @param card
* @param width
* @param height
* @return
*/
public static BufferedImage getImage(CardView card, int width, int height) {
if (Constants.THUMBNAIL_SIZE_FULL.width + 10 > width) {
return getThumbnail(card);
}
String key = getKey(card);
if (card.getUsesVariousArt()) {
key += "#usesVariousArt";
}
// log.warn("getImage: " + key);
BufferedImage original = getImage(key);
if (original == null) {
log.debug(key + " not found");
return null;
}
double scale = Math.min((double) width / original.getWidth(), (double) height / original.getHeight());
if (scale > 1) {
scale = 1;
}
return getFullSizeImage(original, scale);
}
}