diff --git a/.gitattributes b/.gitattributes index 1e6091ddb7a..ad862878918 100644 --- a/.gitattributes +++ b/.gitattributes @@ -172,6 +172,7 @@ src/forge/Gui_Welcome.java svneol=native#text/plain src/forge/Gui_WinLose.java svneol=native#text/plain src/forge/IO.java svneol=native#text/plain src/forge/ImageCache.java svneol=native#text/plain +src/forge/ImageCache2.java svneol=native#text/plain src/forge/ImageEditor.java -text svneol=native#text/plain src/forge/ImageJPanel.java -text svneol=native#text/plain src/forge/ImagePreviewPanel.java -text svneol=native#text/plain diff --git a/src/forge/ImageCache2.java b/src/forge/ImageCache2.java new file mode 100644 index 00000000000..78433a9eed0 --- /dev/null +++ b/src/forge/ImageCache2.java @@ -0,0 +1,255 @@ + +package forge; + + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridLayout; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.Map; + +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.border.Border; + +import com.google.common.base.Function; +import com.google.common.collect.ComputationException; +import com.google.common.collect.MapMaker; + +import forge.properties.ForgeProps; +import forge.properties.NewConstants; + + +/** + * This class stores ALL card images in a cache with soft values. this means that the images may be collected when + * they are not needed any more, but will be kept as long as possible. + * + * The keys are the following: + * + */ +public class ImageCache2 implements NewConstants { + public static void main(String[] args) { + JFrame jf = new JFrame(); + jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + jf.setLayout(new GridLayout(1, 0)); + JPanel[] panels = {new JPanel() { + @Override + protected void paintComponent(Graphics g) { + g.fillRect(0, 0, 50, 80); + } + }, new JPanel() { + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g.create(); + g2d.scale(.5, .5); +// g2d.translate(80, 0); +// g2d.rotate(PI / 2); + g2d.fillRect(0, 0, 50, 80); + g2d.dispose(); + } + }}; + + Border b = BorderFactory.createLineBorder(Color.BLACK); + for(JPanel p:panels) { + p.setBorder(b); + jf.add(p); + } + + jf.pack(); + jf.setVisible(true); + } + + private static final Map imageCache; + + private static final String TOKEN = "#Token", NORMAL = "#Normal", TAPPED = "#Tapped", + FULL = "#Full"; + + static { + imageCache = new MapMaker().softValues().makeComputingMap(new Function() { + public BufferedImage apply(String key) { + if(key.endsWith(NORMAL)) { + //normal + key = key.substring(0, key.length() - NORMAL.length()); + return getNormalSizeImage(imageCache.get(key)); + } else if(key.endsWith(TAPPED)) { + //tapped + key = key.substring(0, key.length() - TAPPED.length()); + return getTappedSizeImage(imageCache.get(key)); + } else if(key.endsWith(FULL)) { + //full size + key = key.substring(0, key.length() - FULL.length()); + return getFullSizeImage(imageCache.get(key)); + } else { + //original + File path; + if(key.endsWith(TOKEN)) { + key = key.substring(0, key.length() - TOKEN.length()); + path = ForgeProps.getFile(IMAGE_TOKEN); + } else path = ForgeProps.getFile(IMAGE_BASE); + File file = new File(path, key + ".jpg"); + + try { + BufferedImage image = ImageIO.read(file); + return image; + } catch(IOException ex) { + throw new ComputationException(ex); + } + } + } + }); + } + + /** + * Returns the image appropriate to display the card in a zone + */ + public static BufferedImage getImage(Card card) { + String key = getKey(card); + if(card.isTapped()) key += TAPPED; + else key += NORMAL; + return getImage(key); + } + + /** + * Returns the image appropriate to display the card in the picture panel + */ + public static BufferedImage getFullImage(Card card) { + String key = getKey(card) + FULL; + return getImage(key); + } + + /** + * Returns the Image corresponding to the key + */ + private static BufferedImage getImage(String key) { + try { + BufferedImage image = imageCache.get(key); + //if an image is still cached and it was not the expected size, drop it + if(!isExpectedSize(key, image)) { + imageCache.remove(key); + image = imageCache.get(key); + } + return image; + } catch(ComputationException ex) { + //legitimate, happens when a card has no image + return null; + } + } + + /** + * Returns if the image for the key is the proper size. + */ + private static boolean isExpectedSize(String key, BufferedImage image) { + if(key.endsWith(NORMAL)) { + //normal + return image.getWidth() == Constant.Runtime.width[0] + && image.getHeight() == Constant.Runtime.height[0]; + } else if(key.endsWith(TAPPED)) { + //tapped + return image.getHeight() == Constant.Runtime.width[0] + && image.getWidth() == Constant.Runtime.height[0]; + } else if(key.endsWith(FULL)) { + //full size + //TODO what's good in this case? + return true; + } else { + //original is never wrong + return true; + } + } + + /** + * Returns the map key for a card, without any suffixes for the image size + */ + private static String getKey(Card card) { + if(card.isFaceDown()) return "Morph"; + String key = card.getImageName(); + if(card.isBasicLand() && card.getRandomPicture() != 0) key += card.getRandomPicture(); + if(card.isToken()) key += TOKEN; + return GuiDisplayUtil.cleanString(key); + } + + /** + * Returns an image scaled to the size given in {@link Constant.Runtime} + */ + private static BufferedImage getNormalSizeImage(BufferedImage original) { + int srcWidth = original.getWidth(); + int srcHeight = original.getHeight(); + int tgtWidth = Constant.Runtime.width[0]; + int tgtHeight = Constant.Runtime.height[0]; + + 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); + + + BufferedImage image = new BufferedImage(tgtWidth, tgtHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = (Graphics2D) image.getGraphics(); + g2d.drawImage(original, at, null); + g2d.dispose(); + return image; + } + + /** + * Returns an image scaled to the size given in {@link Constant.Runtime}, but rotated + */ + private static BufferedImage getTappedSizeImage(BufferedImage original) { + int srcWidth = original.getWidth(); + int srcHeight = original.getHeight(); + int tgtWidth = Constant.Runtime.height[0]; + int tgtHeight = Constant.Runtime.width[0]; + + AffineTransform at = new AffineTransform(); + at.scale((double) tgtWidth / srcWidth, (double) tgtHeight / srcHeight); + at.translate(srcHeight, 0); + at.rotate(Math.PI / 2); + + + BufferedImage image = new BufferedImage(tgtWidth, tgtHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = (Graphics2D) image.getGraphics(); + g2d.drawImage(original, at, null); + g2d.dispose(); + return image; + } + + /** + * Returns an image scaled to the size appropriate for the card picture panel + */ + private static BufferedImage getFullSizeImage(BufferedImage original) { + int srcWidth = original.getWidth(); + int srcHeight = original.getHeight(); + //TODO size for the picture panel? + int tgtWidth = 0; + int tgtHeight = 0; + + if(srcWidth == tgtWidth && srcHeight == tgtHeight) return original; + + + AffineTransform at = new AffineTransform(); + at.scale((double) tgtWidth / srcWidth, (double) tgtHeight / srcHeight); + + + BufferedImage image = new BufferedImage(tgtWidth, tgtHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = (Graphics2D) image.getGraphics(); + g2d.drawImage(original, at, null); + g2d.dispose(); + return image; + } +}