mirror of
https://github.com/correl/mage.git
synced 2024-12-25 03:00:15 +00:00
Cache resized and rotated images
RotatedResizedImageCache and MultiRotatedResizedImageCache contain the caching machinery. A cache of rotated and resized images is added to ImageCache, and is used by the resizing functions there. All the resizing and rotation functions in ImageHelper are redirected to the ones in ImageCache. This is slightly inefficient because it will cache some calls that are never repeated, but it prevents developers from mistakenly using uncached functions when calls are repeated, seriously impacting performance. Also resizing functions that only take a width or an height have been removed, and their calls fixed to provide the other dimension. It's still possible to specify -1 as width or height to ignore constraints in that dimension, though. Greatly speeds up UI performance.
This commit is contained in:
parent
1999dfe5c0
commit
e3d84ca212
9 changed files with 179 additions and 157 deletions
|
@ -54,6 +54,7 @@ import mage.client.plugins.impl.Plugins;
|
|||
import mage.client.util.ImageHelper;
|
||||
import mage.constants.EnlargeMode;
|
||||
import org.jdesktop.swingx.JXPanel;
|
||||
import mage.client.util.TransformedImageCache;
|
||||
|
||||
/**
|
||||
* Class for displaying big image of the card
|
||||
|
@ -103,7 +104,13 @@ public class BigCard extends JComponent {
|
|||
|
||||
}
|
||||
|
||||
public void setCard(UUID cardId, EnlargeMode enlargeMode, Image image, List<String> strings) {
|
||||
public void setCard(UUID cardId, EnlargeMode enlargeMode, Image image, List<String> strings, boolean rotate) {
|
||||
if (rotate && getWidth() > getHeight()) {
|
||||
image = TransformedImageCache.getRotatedResizedImage((BufferedImage)image, getHeight(), getWidth(), Math.toRadians(90.0));
|
||||
} else {
|
||||
image = TransformedImageCache.getResizedImage((BufferedImage)image, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
if (this.cardId == null || !enlargeMode.equals(this.enlargeMode) || !this.cardId.equals(cardId)) {
|
||||
if (this.panel != null) {
|
||||
remove(this.panel);
|
||||
|
|
|
@ -374,7 +374,7 @@ public class Card extends MagePermanent implements MouseMotionListener, MouseLis
|
|||
@Override
|
||||
public void mouseMoved(MouseEvent arg0) {
|
||||
this.bigCard.showTextComponent();
|
||||
this.bigCard.setCard(card.getId(), EnlargeMode.NORMAL, image, getRules());
|
||||
this.bigCard.setCard(card.getId(), EnlargeMode.NORMAL, image, getRules(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -56,6 +56,8 @@ import mage.client.util.ImageHelper;
|
|||
import mage.constants.CardType;
|
||||
import mage.view.CounterView;
|
||||
import mage.view.PermanentView;
|
||||
import org.mage.plugins.card.images.ImageCache;
|
||||
import mage.client.util.TransformedImageCache;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -215,7 +217,7 @@ public class Permanent extends Card {
|
|||
Graphics2D g = (Graphics2D) tappedImage.getGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
g.drawImage(this.createImage(ImageHelper.rotate(small, dimension)), 0, 0, this);
|
||||
g.drawImage(TransformedImageCache.getRotatedResizedImage(small, dimension.frameWidth, dimension.frameHeight, Math.toRadians(90.0)), 0, 0, this);
|
||||
|
||||
g.dispose();
|
||||
}
|
||||
|
|
|
@ -409,8 +409,7 @@ public class TableModel extends AbstractTableModel implements ICardGrid {
|
|||
Image image = Plugins.getInstance().getOriginalImage(card);
|
||||
if (image != null && image instanceof BufferedImage) {
|
||||
// XXX: scaled to fit width
|
||||
image = ImageHelper.getResizedImage((BufferedImage) image, bigCard.getWidth());
|
||||
bigCard.setCard(card.getId(), EnlargeMode.NORMAL, image, new ArrayList<String>());
|
||||
bigCard.setCard(card.getId(), EnlargeMode.NORMAL, image, new ArrayList<String>(), false);
|
||||
} else {
|
||||
drawCardText(card);
|
||||
}
|
||||
|
|
|
@ -228,7 +228,7 @@ public class DraftPanel extends javax.swing.JPanel {
|
|||
int height = left * 18;
|
||||
lblTableImage.setSize(new Dimension(lblTableImage.getWidth(), height));
|
||||
Image tableImage = ImageHelper.getImageFromResources(draftView.getBoosterNum() == 2 ? "/draft/table_left.png" : "/draft/table_right.png");
|
||||
BufferedImage resizedTable = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(tableImage, BufferedImage.TYPE_INT_ARGB), lblTableImage.getWidth());
|
||||
BufferedImage resizedTable = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(tableImage, BufferedImage.TYPE_INT_ARGB), lblTableImage.getWidth(), lblTableImage.getHeight());
|
||||
lblTableImage.setIcon(new ImageIcon(resizedTable));
|
||||
|
||||
int count = 0;
|
||||
|
|
|
@ -598,13 +598,7 @@ public class MageActionCallback implements ActionCallback {
|
|||
private void displayCardInfo(MageCard mageCard, Image image, BigCard bigCard) {
|
||||
if (image != null && image instanceof BufferedImage) {
|
||||
// XXX: scaled to fit width
|
||||
if (mageCard.getOriginal().isToRotate() && bigCard.getWidth() > bigCard.getHeight()) {
|
||||
image = ImageHelper.getResizedImage((BufferedImage) image, bigCard.getHeight());
|
||||
image = ImageHelper.rotate((BufferedImage) image, Math.toRadians(90));
|
||||
} else {
|
||||
image = ImageHelper.getResizedImage((BufferedImage) image, bigCard.getWidth());
|
||||
}
|
||||
bigCard.setCard(mageCard.getOriginal().getId(), enlargeMode, image, mageCard.getOriginal().getRules());
|
||||
bigCard.setCard(mageCard.getOriginal().getId(), enlargeMode, image, mageCard.getOriginal().getRules(), mageCard.getOriginal().isToRotate());
|
||||
// if it's an ability, show only the ability text as overlay
|
||||
if (mageCard.getOriginal().isAbility() && enlargeMode.equals(EnlargeMode.NORMAL)) {
|
||||
bigCard.showTextComponent();
|
||||
|
|
|
@ -52,6 +52,7 @@ import static mage.client.constants.Constants.FRAME_MAX_WIDTH;
|
|||
import static mage.client.constants.Constants.SYMBOL_MAX_SPACE;
|
||||
import mage.view.CardView;
|
||||
import org.mage.card.arcane.UI;
|
||||
import org.mage.plugins.card.images.ImageCache;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -70,21 +71,6 @@ public class ImageHelper {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ref - image name
|
||||
* @param height - height after scaling
|
||||
* @return a scaled image that preserves the original aspect ratio, with a
|
||||
* specified height
|
||||
*/
|
||||
public static BufferedImage loadImage(String ref, int height) {
|
||||
BufferedImage image = loadImage(ref);
|
||||
if (image != null) {
|
||||
return scaleImage(image, height);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static BufferedImage loadImage(String ref) {
|
||||
if (!images.containsKey(ref)) {
|
||||
try {
|
||||
|
@ -107,67 +93,7 @@ public class ImageHelper {
|
|||
}
|
||||
|
||||
public static BufferedImage scaleImage(BufferedImage image, int width, int height) {
|
||||
BufferedImage scaledImage = image;
|
||||
int w = image.getWidth();
|
||||
int h = image.getHeight();
|
||||
do {
|
||||
w /= 2;
|
||||
h /= 2;
|
||||
if (w < width || h < height) {
|
||||
w = width;
|
||||
h = height;
|
||||
}
|
||||
BufferedImage newImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics2D = newImage.createGraphics();
|
||||
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
graphics2D.drawImage(scaledImage, 0, 0, w, h, null);
|
||||
graphics2D.dispose();
|
||||
scaledImage = newImage;
|
||||
} while (w != width || h != height);
|
||||
return scaledImage;
|
||||
}
|
||||
|
||||
public static BufferedImage scaleImage(BufferedImage image, int height) {
|
||||
double ratio = height / (double) image.getHeight();
|
||||
int width = (int) (image.getWidth() * ratio);
|
||||
return scaleImage(image, width, height);
|
||||
}
|
||||
|
||||
public static MemoryImageSource rotate(Image image, CardDimensions dimensions) {
|
||||
int buffer[] = new int[dimensions.frameWidth * dimensions.frameHeight];
|
||||
int rotate[] = new int[dimensions.frameHeight * dimensions.frameWidth];
|
||||
PixelGrabber grabber = new PixelGrabber(image, 0, 0, dimensions.frameWidth, dimensions.frameHeight, buffer, 0, dimensions.frameWidth);
|
||||
try {
|
||||
grabber.grabPixels();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
for (int y = 0; y < dimensions.frameHeight; y++) {
|
||||
for (int x = 0; x < dimensions.frameWidth; x++) {
|
||||
rotate[((dimensions.frameWidth - x - 1) * dimensions.frameHeight) + y] = buffer[(y * dimensions.frameWidth) + x];
|
||||
}
|
||||
}
|
||||
|
||||
return new MemoryImageSource(dimensions.frameHeight, dimensions.frameWidth, rotate, 0, dimensions.frameHeight);
|
||||
|
||||
}
|
||||
|
||||
public static BufferedImage rotate(BufferedImage image, double angle) {
|
||||
double sin = Math.abs(Math.sin(angle)), cos = Math.abs(Math.cos(angle));
|
||||
int w = image.getWidth(), h = image.getHeight();
|
||||
int neww = (int) Math.floor(w * cos + h * sin), newh = (int) Math.floor(h * cos + w * sin);
|
||||
|
||||
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
GraphicsDevice gs = ge.getDefaultScreenDevice();
|
||||
GraphicsConfiguration gc = gs.getDefaultConfiguration();
|
||||
|
||||
BufferedImage result = gc.createCompatibleImage(neww, newh, Transparency.TRANSLUCENT);
|
||||
Graphics2D g = result.createGraphics();
|
||||
g.translate((neww - w) / 2, (newh - h) / 2);
|
||||
g.rotate(angle, w / 2, h / 2);
|
||||
g.drawRenderedImage(image, null);
|
||||
g.dispose();
|
||||
return result;
|
||||
return TransformedImageCache.getResizedImage(image, width, height);
|
||||
}
|
||||
|
||||
public static void drawCosts(List<String> costs, Graphics2D g, int xOffset, int yOffset, ImageObserver o) {
|
||||
|
@ -191,26 +117,7 @@ public class ImageHelper {
|
|||
* @return
|
||||
*/
|
||||
public static BufferedImage getResizedImage(BufferedImage original, int width, int height) {
|
||||
ResampleOp resampleOp = new ResampleOp(width, height);
|
||||
BufferedImage image = resampleOp.filter(original, null);
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an image scaled to fit width panel
|
||||
*
|
||||
* @param original
|
||||
* @param width
|
||||
* @return
|
||||
*/
|
||||
public static BufferedImage getResizedImage(BufferedImage original, int width) {
|
||||
if (width != original.getWidth()) {
|
||||
double ratio = width / (double) original.getWidth();
|
||||
int height = (int) (original.getHeight() * ratio);
|
||||
return getResizedImage(original, width, height);
|
||||
} else {
|
||||
return original;
|
||||
}
|
||||
return TransformedImageCache.getResizedImage(original, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -223,17 +130,7 @@ public class ImageHelper {
|
|||
* @return scaled image
|
||||
*/
|
||||
public static BufferedImage scale(BufferedImage sbi, int imageType, int dWidth, int dHeight) {
|
||||
BufferedImage dbi = null;
|
||||
if (sbi != null) {
|
||||
double fWidth = dWidth / sbi.getWidth();
|
||||
double fHeight = dHeight / sbi.getHeight();
|
||||
dbi = new BufferedImage(dWidth, dHeight, imageType);
|
||||
Graphics2D g = dbi.createGraphics();
|
||||
AffineTransform at = AffineTransform.getScaleInstance(fWidth, fHeight);
|
||||
g.drawRenderedImage(sbi, at);
|
||||
g.dispose();
|
||||
}
|
||||
return dbi;
|
||||
return TransformedImageCache.getResizedImage(sbi, dWidth, dHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -244,9 +141,7 @@ public class ImageHelper {
|
|||
* @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;
|
||||
return TransformedImageCache.getResizedImage(original, sizeNeed.width, sizeNeed.height);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package mage.client.util;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.MapMaker;
|
||||
import com.mortennobel.imagescaling.ResampleOp;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Transparency;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Map;
|
||||
import mage.client.util.ImageHelper;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author user
|
||||
*/
|
||||
public class TransformedImageCache {
|
||||
private final static class Key
|
||||
{
|
||||
final int width;
|
||||
final int height;
|
||||
final double angle;
|
||||
|
||||
public Key(int width, int height, double angle) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.angle = angle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 53 * hash + this.width;
|
||||
hash = 53 * hash + this.height;
|
||||
hash = 53 * hash + (int) (Double.doubleToLongBits(this.angle) ^ (Double.doubleToLongBits(this.angle) >>> 32));
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final Key other = (Key) obj;
|
||||
if (this.width != other.width) {
|
||||
return false;
|
||||
}
|
||||
if (this.height != other.height) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(this.angle) != Double.doubleToLongBits(other.angle)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static Map<Key, Map<BufferedImage, BufferedImage>> IMAGE_CACHE;
|
||||
|
||||
static
|
||||
{
|
||||
// TODO: can we use a single map?
|
||||
IMAGE_CACHE = ImageCaches.register(new MapMaker().softValues().makeComputingMap(new Function<Key, Map<BufferedImage, BufferedImage>>() {
|
||||
@Override
|
||||
public Map<BufferedImage, BufferedImage> apply(final Key key) {
|
||||
return new MapMaker().weakKeys().softValues().makeComputingMap(new Function<BufferedImage, BufferedImage>() {
|
||||
@Override
|
||||
public BufferedImage apply(BufferedImage image) {
|
||||
if(key.width != image.getWidth() || key.height != image.getHeight())
|
||||
image = resizeImage(image, key.width, key.height);
|
||||
if(key.angle != 0.0)
|
||||
image = rotateImage(image, key.angle);
|
||||
return image;
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private static BufferedImage rotateImage(BufferedImage image, double angle) {
|
||||
double sin = Math.abs(Math.sin(angle)), cos = Math.abs(Math.cos(angle));
|
||||
int w = image.getWidth(), h = image.getHeight();
|
||||
int neww = (int) Math.floor(w * cos + h * sin), newh = (int) Math.floor(h * cos + w * sin);
|
||||
|
||||
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
GraphicsDevice gs = ge.getDefaultScreenDevice();
|
||||
GraphicsConfiguration gc = gs.getDefaultConfiguration();
|
||||
|
||||
BufferedImage result = gc.createCompatibleImage(neww, newh, Transparency.TRANSLUCENT);
|
||||
Graphics2D g = result.createGraphics();
|
||||
g.translate((neww - w) / 2, (newh - h) / 2);
|
||||
g.rotate(angle, w / 2, h / 2);
|
||||
g.drawRenderedImage(image, null);
|
||||
g.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static BufferedImage resizeImage(BufferedImage original, int width, int height) {
|
||||
ResampleOp resampleOp = new ResampleOp(width, height);
|
||||
BufferedImage image = resampleOp.filter(original, null);
|
||||
return image;
|
||||
}
|
||||
|
||||
public static BufferedImage getResizedImage(BufferedImage image, int width, int height)
|
||||
{
|
||||
return getRotatedResizedImage(image, width, height, 0.0);
|
||||
}
|
||||
|
||||
public static BufferedImage getRotatedImage(BufferedImage image, double angle)
|
||||
{
|
||||
return getRotatedResizedImage(image, -1, -1, angle);
|
||||
}
|
||||
|
||||
public static BufferedImage getRotatedResizedImage(BufferedImage image, int width, int height, double angle)
|
||||
{
|
||||
int imageWidth = image.getWidth();
|
||||
int imageHeight = image.getHeight();
|
||||
|
||||
if(angle == 0.0 && (width < 0 || imageWidth == width) && (height < 0 || imageHeight == height))
|
||||
return image;
|
||||
|
||||
int resWidth;
|
||||
int resHeight;
|
||||
if(width < 0 && height < 0) {
|
||||
resWidth = imageWidth;
|
||||
resHeight = imageHeight;
|
||||
} else if((height < 0) || (width >= 0 && imageHeight * width <= imageWidth * height)) {
|
||||
resWidth = width;
|
||||
resHeight = imageHeight * width / imageWidth;
|
||||
} else {
|
||||
resWidth = imageWidth * height / imageHeight;
|
||||
resHeight = height;
|
||||
}
|
||||
|
||||
if(angle == 0.0 && imageWidth == resWidth && imageHeight == resHeight)
|
||||
return image;
|
||||
|
||||
return IMAGE_CACHE.get(new Key(resWidth, resHeight, angle)).get(image);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package org.mage.plugins.card.images;
|
||||
|
||||
import mage.client.util.TransformedImageCache;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ComputationException;
|
||||
import com.google.common.collect.MapMaker;
|
||||
|
@ -274,7 +275,7 @@ public class ImageCache {
|
|||
}
|
||||
|
||||
public static BufferedImage makeThumbnail(BufferedImage original, String path) {
|
||||
BufferedImage image = getResizedImage(original, Constants.THUMBNAIL_SIZE_FULL);
|
||||
BufferedImage image = TransformedImageCache.getResizedImage(original, Constants.THUMBNAIL_SIZE_FULL.width, Constants.THUMBNAIL_SIZE_FULL.height);
|
||||
TFile imageFile = getTFile(path);
|
||||
if (imageFile == null) {
|
||||
return null;
|
||||
|
@ -312,36 +313,7 @@ public class ImageCache {
|
|||
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;
|
||||
return TransformedImageCache.getResizedImage(original, tgtWidth, tgtHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -364,11 +336,11 @@ public class ImageCache {
|
|||
}
|
||||
|
||||
double scale = Math.min((double) width / original.getWidth(), (double) height / original.getHeight());
|
||||
if (scale > 1) {
|
||||
scale = 1;
|
||||
if (scale >= 1) {
|
||||
return original;
|
||||
}
|
||||
|
||||
return getFullSizeImage(original, scale);
|
||||
return TransformedImageCache.getResizedImage(original, (int)(original.getWidth() * scale), (int)(original.getHeight() * scale));
|
||||
}
|
||||
|
||||
public static TFile getTFile(String path) {
|
||||
|
|
Loading…
Reference in a new issue