Port Card Image Renderer to Desktop

This commit is contained in:
Lyu Zong-Hong
2021-07-26 11:33:50 +09:00
parent da86b24155
commit 27856e65a0
5 changed files with 488 additions and 68 deletions

View File

@@ -6,12 +6,12 @@
* 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/>.
*/
@@ -42,6 +42,7 @@ import forge.game.card.CardView;
import forge.game.player.PlayerView;
import forge.gui.FThreads;
import forge.item.InventoryItem;
import forge.item.PaperCard;
import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgePreferences;
import forge.localinstance.properties.ForgePreferences.FPref;
@@ -49,6 +50,7 @@ import forge.localinstance.skin.FSkinProp;
import forge.model.FModel;
import forge.toolbox.FSkin;
import forge.toolbox.FSkin.SkinIcon;
import forge.toolbox.imaging.FCardImageRenderer;
import forge.util.ImageUtil;
/**
@@ -61,7 +63,7 @@ import forge.util.ImageUtil;
* <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>
* </ul>
*
*
* @author Forge
* @version $Id: ImageCache.java 25093 2014-03-08 05:36:37Z drdev $
*/
@@ -81,7 +83,7 @@ public class ImageCache {
} catch (Exception ex) {
System.err.println("could not load default card image");
} finally {
_defaultImage = (null == defImage) ? new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB) : defImage;
_defaultImage = (null == defImage) ? new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB) : defImage;
}
}
@@ -133,31 +135,33 @@ public class ImageCache {
}
return new FSkin.UnskinnedIcon(i);
}
/**
* This requests the original unscaled image from the cache for the given key.
* If the image does not exist then it can return a default image if desired.
* <p>
* If the requested image is not present in the cache then it attempts to load
* the image from file (slower) and then add it to the cache for fast future access.
* the image from file (slower) and then add it to the cache for fast future access.
* </p>
*/
public static BufferedImage getOriginalImage(String imageKey, boolean useDefaultIfNotFound) {
if (null == imageKey) {
if (null == imageKey) {
return null;
}
PaperCard pc = null;
boolean altState = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX);
if(altState)
imageKey = imageKey.substring(0, imageKey.length() - ImageKeys.BACKFACE_POSTFIX.length());
if (imageKey.startsWith(ImageKeys.CARD_PREFIX)) {
imageKey = ImageUtil.getImageKey(ImageUtil.getPaperCardFromImageKey(imageKey), altState, true);
if (StringUtils.isBlank(imageKey)) {
pc = ImageUtil.getPaperCardFromImageKey(imageKey);
imageKey = ImageUtil.getImageKey(pc, altState, true);
if (StringUtils.isBlank(imageKey)) {
return _defaultImage;
}
}
// Load from file and add to cache if not found in cache initially.
// Load from file and add to cache if not found in cache initially.
BufferedImage original = getImage(imageKey);
// If the user has indicated that they prefer Forge NOT render a black border, round the image corners
@@ -197,8 +201,14 @@ public class ImageCache {
// No image file exists for the given key so optionally associate with
// a default "not available" image, however do not add it to the cache,
// as otherwise it's problematic to update if the real image gets fetched.
if (original == null && useDefaultIfNotFound) {
original = _defaultImage;
if (original == null && useDefaultIfNotFound) {
if (pc != null) {
original = new BufferedImage(480, 680, BufferedImage.TYPE_INT_ARGB);
FCardImageRenderer.drawCardImage(original.createGraphics(), pc, altState, 480, 680);
_CACHE.put(imageKey, original);
} else {
original = _defaultImage;
}
}
return original;
@@ -217,7 +227,7 @@ public class ImageCache {
//System.out.println("found cached image: " + resizedKey);
return cached;
}
BufferedImage original = getOriginalImage(key, useDefaultImage);
if (original == null) { return null; }
@@ -232,7 +242,7 @@ public class ImageCache {
return cachedDefault;
}
}
// Calculate the scale required to best fit the image into the requested
// (width x height) dimensions whilst retaining aspect ratio.
double scaleX = (-1 == width ? 1 : (double)width / original.getWidth());
@@ -243,17 +253,17 @@ public class ImageCache {
}
BufferedImage result;
if (1 == bestFitScale) {
if (1 == bestFitScale) {
result = original;
} else {
int destWidth = (int)(original.getWidth() * bestFitScale);
int destHeight = (int)(original.getHeight() * bestFitScale);
ResampleOp resampler = new ResampleOp(destWidth, destHeight);
result = resampler.filter(original, null);
}
//System.out.println("caching image: " + resizedKey);
_CACHE.put(resizedKey, result);
return result;

View File

@@ -20,7 +20,7 @@ public class ColorSetRenderer extends ItemCellRenderer {
/*
* (non-Javadoc)
*
*
* @see
* javax.swing.table.DefaultTableCellRenderer#getTableCellRendererComponent
* (javax.swing.JTable, java.lang.Object, boolean, boolean, int, int)
@@ -41,7 +41,7 @@ public class ColorSetRenderer extends ItemCellRenderer {
/*
* (non-Javadoc)
*
*
* @see javax.swing.JComponent#paint(java.awt.Graphics)
*/
@Override
@@ -58,7 +58,7 @@ public class ColorSetRenderer extends ItemCellRenderer {
final int dx = Math.min(elemtWidth + elemtGap, offsetIfNoSpace);
for (final ManaCostShard s : cs.getOrderedShards()) {
CardFaceSymbols.drawSymbol(s.getImageKey(), g, x, y);
CardFaceSymbols.drawManaSymbol(s.getImageKey(), g, x, y);
x += dx;
}
}

View File

@@ -6,12 +6,12 @@
* 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/>.
*/
@@ -44,7 +44,7 @@ public class ManaCostRenderer extends ItemCellRenderer {
/*
* (non-Javadoc)
*
*
* @see
* javax.swing.table.DefaultTableCellRenderer#getTableCellRendererComponent
* (javax.swing.JTable, java.lang.Object, boolean, boolean, int, int)
@@ -68,7 +68,7 @@ public class ManaCostRenderer extends ItemCellRenderer {
/*
* (non-Javadoc)
*
*
* @see javax.swing.JComponent#paint(java.awt.Graphics)
*/
@Override
@@ -112,7 +112,7 @@ public class ManaCostRenderer extends ItemCellRenderer {
// Display X Mana before any other type of mana
if (xManaCosts > 0) {
for (int i = 0; i < xManaCosts; i++) {
CardFaceSymbols.drawSymbol(ManaCostShard.X.getImageKey(), g, x, y);
CardFaceSymbols.drawManaSymbol(ManaCostShard.X.getImageKey(), g, x, y);
x += dx;
}
}
@@ -120,7 +120,7 @@ public class ManaCostRenderer extends ItemCellRenderer {
// Display generic mana before colored mana
if (hasGeneric) {
final String sGeneric = Integer.toString(genericManaCost);
CardFaceSymbols.drawSymbol(sGeneric, g, x, y);
CardFaceSymbols.drawManaSymbol(sGeneric, g, x, y);
x += dx;
}
@@ -129,7 +129,7 @@ public class ManaCostRenderer extends ItemCellRenderer {
// X costs already drawn up above
continue;
}
CardFaceSymbols.drawSymbol(s.getImageKey(), g, x, y);
CardFaceSymbols.drawManaSymbol(s.getImageKey(), g, x, y);
x += dx;
}
}

View File

@@ -42,44 +42,44 @@ public class CardFaceSymbols {
DECK_COLORSET.put("W", FSkin.getImage(FSkinProp.IMG_MANA_W));
for (int i = 0; i <= 20; i++) {
MANA_IMAGES.put(String.valueOf(i), FSkin.getImage(FSkinProp.valueOf("IMG_MANA_" + i), manaImageSize, manaImageSize));
MANA_IMAGES.put(String.valueOf(i), FSkin.getImage(FSkinProp.valueOf("IMG_MANA_" + i)));
}
MANA_IMAGES.put("X", FSkin.getImage(FSkinProp.IMG_MANA_X, manaImageSize, manaImageSize));
MANA_IMAGES.put("Y", FSkin.getImage(FSkinProp.IMG_MANA_Y, manaImageSize, manaImageSize));
MANA_IMAGES.put("Z", FSkin.getImage(FSkinProp.IMG_MANA_Z, manaImageSize, manaImageSize));
MANA_IMAGES.put("C", FSkin.getImage(FSkinProp.IMG_MANA_COLORLESS, manaImageSize, manaImageSize));
MANA_IMAGES.put("X", FSkin.getImage(FSkinProp.IMG_MANA_X));
MANA_IMAGES.put("Y", FSkin.getImage(FSkinProp.IMG_MANA_Y));
MANA_IMAGES.put("Z", FSkin.getImage(FSkinProp.IMG_MANA_Z));
MANA_IMAGES.put("C", FSkin.getImage(FSkinProp.IMG_MANA_COLORLESS));
MANA_IMAGES.put("B", FSkin.getImage(FSkinProp.IMG_MANA_B, manaImageSize, manaImageSize));
MANA_IMAGES.put("BG", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_BG, manaImageSize, manaImageSize));
MANA_IMAGES.put("BR", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_BR, manaImageSize, manaImageSize));
MANA_IMAGES.put("G", FSkin.getImage(FSkinProp.IMG_MANA_G, manaImageSize, manaImageSize));
MANA_IMAGES.put("GU", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_GU, manaImageSize, manaImageSize));
MANA_IMAGES.put("GW", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_GW, manaImageSize, manaImageSize));
MANA_IMAGES.put("R", FSkin.getImage(FSkinProp.IMG_MANA_R, manaImageSize, manaImageSize));
MANA_IMAGES.put("RG", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_RG, manaImageSize, manaImageSize));
MANA_IMAGES.put("RW", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_RW, manaImageSize, manaImageSize));
MANA_IMAGES.put("U", FSkin.getImage(FSkinProp.IMG_MANA_U, manaImageSize, manaImageSize));
MANA_IMAGES.put("UB", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_UB, manaImageSize, manaImageSize));
MANA_IMAGES.put("UR", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_UR, manaImageSize, manaImageSize));
MANA_IMAGES.put("W", FSkin.getImage(FSkinProp.IMG_MANA_W, manaImageSize, manaImageSize));
MANA_IMAGES.put("WB", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_WB, manaImageSize, manaImageSize));
MANA_IMAGES.put("WU", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_WU, manaImageSize, manaImageSize));
MANA_IMAGES.put("PW", FSkin.getImage(FSkinProp.IMG_MANA_PHRYX_W, manaImageSize, manaImageSize));
MANA_IMAGES.put("PR", FSkin.getImage(FSkinProp.IMG_MANA_PHRYX_R, manaImageSize, manaImageSize));
MANA_IMAGES.put("PU", FSkin.getImage(FSkinProp.IMG_MANA_PHRYX_U, manaImageSize, manaImageSize));
MANA_IMAGES.put("PB", FSkin.getImage(FSkinProp.IMG_MANA_PHRYX_B, manaImageSize, manaImageSize));
MANA_IMAGES.put("PG", FSkin.getImage(FSkinProp.IMG_MANA_PHRYX_G, manaImageSize, manaImageSize));
MANA_IMAGES.put("2W", FSkin.getImage(FSkinProp.IMG_MANA_2W, manaImageSize, manaImageSize));
MANA_IMAGES.put("2U", FSkin.getImage(FSkinProp.IMG_MANA_2U, manaImageSize, manaImageSize));
MANA_IMAGES.put("2R", FSkin.getImage(FSkinProp.IMG_MANA_2R, manaImageSize, manaImageSize));
MANA_IMAGES.put("2G", FSkin.getImage(FSkinProp.IMG_MANA_2G, manaImageSize, manaImageSize));
MANA_IMAGES.put("2B", FSkin.getImage(FSkinProp.IMG_MANA_2B, manaImageSize, manaImageSize));
MANA_IMAGES.put("B", FSkin.getImage(FSkinProp.IMG_MANA_B));
MANA_IMAGES.put("BG", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_BG));
MANA_IMAGES.put("BR", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_BR));
MANA_IMAGES.put("G", FSkin.getImage(FSkinProp.IMG_MANA_G));
MANA_IMAGES.put("GU", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_GU));
MANA_IMAGES.put("GW", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_GW));
MANA_IMAGES.put("R", FSkin.getImage(FSkinProp.IMG_MANA_R));
MANA_IMAGES.put("RG", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_RG));
MANA_IMAGES.put("RW", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_RW));
MANA_IMAGES.put("U", FSkin.getImage(FSkinProp.IMG_MANA_U));
MANA_IMAGES.put("UB", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_UB));
MANA_IMAGES.put("UR", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_UR));
MANA_IMAGES.put("W", FSkin.getImage(FSkinProp.IMG_MANA_W));
MANA_IMAGES.put("WB", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_WB));
MANA_IMAGES.put("WU", FSkin.getImage(FSkinProp.IMG_MANA_HYBRID_WU));
MANA_IMAGES.put("PW", FSkin.getImage(FSkinProp.IMG_MANA_PHRYX_W));
MANA_IMAGES.put("PR", FSkin.getImage(FSkinProp.IMG_MANA_PHRYX_R));
MANA_IMAGES.put("PU", FSkin.getImage(FSkinProp.IMG_MANA_PHRYX_U));
MANA_IMAGES.put("PB", FSkin.getImage(FSkinProp.IMG_MANA_PHRYX_B));
MANA_IMAGES.put("PG", FSkin.getImage(FSkinProp.IMG_MANA_PHRYX_G));
MANA_IMAGES.put("2W", FSkin.getImage(FSkinProp.IMG_MANA_2W));
MANA_IMAGES.put("2U", FSkin.getImage(FSkinProp.IMG_MANA_2U));
MANA_IMAGES.put("2R", FSkin.getImage(FSkinProp.IMG_MANA_2R));
MANA_IMAGES.put("2G", FSkin.getImage(FSkinProp.IMG_MANA_2G));
MANA_IMAGES.put("2B", FSkin.getImage(FSkinProp.IMG_MANA_2B));
MANA_IMAGES.put("S", FSkin.getImage(FSkinProp.IMG_MANA_SNOW, manaImageSize, manaImageSize));
MANA_IMAGES.put("T", FSkin.getImage(FSkinProp.IMG_TAP, manaImageSize, manaImageSize));
MANA_IMAGES.put("S", FSkin.getImage(FSkinProp.IMG_MANA_SNOW));
MANA_IMAGES.put("T", FSkin.getImage(FSkinProp.IMG_TAP));
MANA_IMAGES.put("E", FSkin.getImage(FSkinProp.IMG_ENERGY, 40, 40));
MANA_IMAGES.put("EXPERIENCE", FSkin.getImage(FSkinProp.IMG_EXPERIENCE, 40, 30));
MANA_IMAGES.put("slash", FSkin.getImage(FSkinProp.IMG_SLASH, manaImageSize, manaImageSize));
MANA_IMAGES.put("slash", FSkin.getImage(FSkinProp.IMG_SLASH));
MANA_IMAGES.put("attack", FSkin.getImage(FSkinProp.IMG_ATTACK, 32, 32));
MANA_IMAGES.put("defend", FSkin.getImage(FSkinProp.IMG_DEFEND, 32, 32));
MANA_IMAGES.put("summonsick", FSkin.getImage(FSkinProp.IMG_SUMMONSICK, 32, 32));
@@ -179,37 +179,40 @@ public class CardFaceSymbols {
* a int.
*/
public static void draw(final Graphics g, final ManaCost manaCost, final int x, final int y) {
draw(g, manaCost, x, y, manaImageSize);
}
public static void draw(final Graphics g, final ManaCost manaCost, final int x, final int y, final int size) {
if (manaCost.isNoCost()) {
return;
}
int xpos = x;
final int offset = 14;
final int offset = size + 1;
final int genericManaCost = manaCost.getGenericCost();
final boolean hasGeneric = (genericManaCost > 0) || manaCost.isPureGeneric();
if (hasGeneric) {
for (final ManaCostShard s : manaCost) { //render X shards before generic
if (s == ManaCostShard.X) {
CardFaceSymbols.drawSymbol(s.getImageKey(), g, xpos, y);
CardFaceSymbols.drawSymbol(s.getImageKey(), g, xpos, y, size);
xpos += offset;
}
}
final String sGeneric = Integer.toString(genericManaCost);
CardFaceSymbols.drawSymbol(sGeneric, g, xpos, y);
CardFaceSymbols.drawSymbol(sGeneric, g, xpos, y, size);
xpos += offset;
for (final ManaCostShard s : manaCost) { //render non-X shards after generic
if (s != ManaCostShard.X) {
CardFaceSymbols.drawSymbol(s.getImageKey(), g, xpos, y);
CardFaceSymbols.drawSymbol(s.getImageKey(), g, xpos, y, size);
xpos += offset;
}
}
}
else { //if no generic, just render shards in order
for (final ManaCostShard s : manaCost) {
CardFaceSymbols.drawSymbol(s.getImageKey(), g, xpos, y);
CardFaceSymbols.drawSymbol(s.getImageKey(), g, xpos, y, size);
xpos += offset;
}
}
@@ -270,6 +273,12 @@ public class CardFaceSymbols {
public static void drawSymbol(final String imageName, final Graphics g, final int x, final int y) {
FSkin.drawImage(g, MANA_IMAGES.get(imageName), x, y);
}
public static void drawManaSymbol(final String imageName, final Graphics g, final int x, final int y) {
FSkin.drawImage(g, MANA_IMAGES.get(imageName).resize(manaImageSize, manaImageSize), x, y);
}
public static void drawSymbol(final String imageName, final Graphics g, final int x, final int y, final int size) {
FSkin.drawImage(g, MANA_IMAGES.get(imageName).resize(size, size), x, y);
}
public static void drawAbilitySymbol(final String imageName, final Graphics g, final int x, final int y, final int w, final int h) {
FSkin.drawImage(g, MANA_IMAGES.get(imageName), x, y, w, h);
}
@@ -284,7 +293,7 @@ public class CardFaceSymbols {
* @return a int.
*/
public static int getWidth(final ManaCost manaCost) {
return manaCost.getGlyphCount() * 14;
return manaCost.getGlyphCount() * (manaImageSize + 1);
}
public static int getHeight() {

View File

@@ -0,0 +1,401 @@
package forge.toolbox.imaging;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JEditorPane;
import org.apache.commons.lang3.StringUtils;
import forge.card.CardRarity;
import forge.card.mana.ManaCost;
import forge.game.card.Card;
import forge.game.card.CardView;
import forge.game.card.CardView.CardStateView;
import forge.gui.card.CardDetailUtil;
import forge.gui.card.CardDetailUtil.DetailColors;
import forge.item.PaperCard;
import forge.localinstance.skin.FSkinProp;
import forge.toolbox.CardFaceSymbols;
import forge.toolbox.FSkin;
import forge.toolbox.FSkin.SkinIcon;
import forge.util.CardTranslation;
public class FCardImageRenderer {
private static final float BASE_IMAGE_WIDTH = 480;
private static final float BASE_IMAGE_HEIGHT = 680;
private static float MANA_SYMBOL_SIZE, PT_BOX_WIDTH, HEADER_PADDING, BORDER_THICKNESS;
private static Font NAME_FONT, TYPE_FONT, TEXT_FONT, PT_FONT;
private static FontMetrics NAME_METRICS, TYPE_METRICS, TEXT_METRICS, PT_METRICS;
private static float prevImageWidth, prevImageHeight;
private static final float BLACK_BORDER_THICKNESS_RATIO = 0.021f;
private static final float NAME_BOX_TINT = 0.2f;
private static final float TEXT_BOX_TINT = 0.1f;
private static final float PT_BOX_TINT = 0.2f;
private static final float CARD_ART_RATIO = 1.32f;
private static Color tintColor(Color source, Color tint, float alpha) {
float r = (tint.getRed() - source.getRed()) * alpha + source.getRed();
float g = (tint.getGreen() - source.getGreen()) * alpha + source.getGreen();
float b = (tint.getBlue() - source.getBlue()) * alpha + source.getBlue();
return new Color(r / 255f, g / 255f, b / 255f, 1f);
}
private static Color[] tintColors(Color source, Color[] tints, float alpha) {
Color[] tintedColors = new Color[tints.length];
for (int i = 0; i < tints.length; i++) {
tintedColors[i] = tintColor(source, tints[i], alpha);
}
return tintedColors;
}
private static Color fromDetailColor(DetailColors detailColor) {
return new Color(detailColor.r, detailColor.g, detailColor.b);
}
private static Color C_COMMON = fromDetailColor(DetailColors.COMMON);
private static Color C_UNCOMMON = fromDetailColor(DetailColors.UNCOMMON);
private static Color C_RARE = fromDetailColor(DetailColors.RARE);
private static Color C_MYTHIC = fromDetailColor(DetailColors.MYTHIC);
private static Color C_SPECIAL = fromDetailColor(DetailColors.SPECIAL);
private static Color getRarityColor(CardRarity rarity) {
if (rarity == null)// NPE from Rarity weird...
return Color.MAGENTA;
switch(rarity) {
case Uncommon:
return C_UNCOMMON;
case Rare:
return C_RARE;
case MythicRare:
return C_MYTHIC;
case Special: //"Timeshifted" or other Special Rarity Cards
return C_SPECIAL;
default: //case BasicLand: + case Common:
return C_COMMON;
}
}
private static void updateStaticFields(Graphics2D g, float w, float h) {
if (w == prevImageWidth && h == prevImageHeight) {
//for performance sake, only update static fields if card image size is different than previous rendered card
return;
}
float ratio = Math.min(w / BASE_IMAGE_WIDTH, h / BASE_IMAGE_HEIGHT);
MANA_SYMBOL_SIZE = 26 * ratio;
PT_BOX_WIDTH = 75 * ratio;
HEADER_PADDING = 7 * ratio;
NAME_FONT = new Font(Font.SERIF, Font.BOLD, Math.round(MANA_SYMBOL_SIZE));
TYPE_FONT = new Font(Font.SERIF, Font.BOLD, Math.round(MANA_SYMBOL_SIZE * 0.8f));
TEXT_FONT = new Font(Font.SERIF, Font.PLAIN, Math.round(MANA_SYMBOL_SIZE * 0.7f));
PT_FONT = NAME_FONT;
NAME_METRICS = g.getFontMetrics(NAME_FONT);
TYPE_METRICS = g.getFontMetrics(TYPE_FONT);
TEXT_METRICS = g.getFontMetrics(TEXT_FONT);
PT_METRICS = NAME_METRICS;
BORDER_THICKNESS = Math.max(2 * ratio, 1f); //don't let border go below 1
prevImageWidth = w;
prevImageHeight = h;
}
public static void drawCardImage(Graphics2D g, PaperCard pc, boolean altState, int width, int height) {
final CardView card = Card.getCardForUi(pc).getView();
float x = 0, y = 0, w = width, h = height;
updateStaticFields(g, w, h);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
float blackBorderThickness = w * BLACK_BORDER_THICKNESS_RATIO;
g.setColor(Color.BLACK);
g.fillRect((int)x, (int)y, (int)w, (int)h);
x += blackBorderThickness;
y += blackBorderThickness;
w -= 2 * blackBorderThickness;
h -= 2 * blackBorderThickness;
final CardStateView state = card.getState(altState);
//determine colors for borders
final List<DetailColors> borderColors = CardDetailUtil.getBorderColors(state, true);
Color[] colors = fillColorBackground(g, borderColors, x, y, w, h);
float artInset = blackBorderThickness * 0.8f;
float outerBorderThickness = 2 * blackBorderThickness - artInset;
x += outerBorderThickness;
y += outerBorderThickness;
w -= 2 * outerBorderThickness;
float headerHeight = MANA_SYMBOL_SIZE + 2 * HEADER_PADDING + 2;
float artWidth = w - 2 * artInset;
float artHeight = artWidth / CARD_ART_RATIO;
float typeBoxHeight = TYPE_METRICS.getHeight() + HEADER_PADDING + 2;
float ptBoxHeight = 0;
float textBoxHeight = h - headerHeight - artHeight - typeBoxHeight - outerBorderThickness - artInset - PT_METRICS.getHeight() / 2f;
if (state.isCreature() || state.isPlaneswalker() || state.getType().hasSubtype("Vehicle")) {
//if P/T box needed, make room for it
ptBoxHeight = MANA_SYMBOL_SIZE + HEADER_PADDING;
}
float artY = y + headerHeight;
float typeY = artY + artHeight;
float textY = typeY + typeBoxHeight;
float ptY = textY + textBoxHeight;
//draw art box with Forge icon
Color[] artBoxColors = tintColors(Color.DARK_GRAY, colors, NAME_BOX_TINT);
drawArt(g, artBoxColors, x + artInset, artY, artWidth, artHeight);
//draw text box
Color[] textBoxColors = tintColors(Color.WHITE, colors, TEXT_BOX_TINT);
drawTextBox(g, card, state, textBoxColors, x + artInset, textY, w - 2 * artInset, textBoxHeight);
//draw header containing name and mana cost
Color[] headerColors = tintColors(Color.WHITE, colors, NAME_BOX_TINT);
drawHeader(g, card, state, headerColors, x, y, w, headerHeight);
//draw type line
drawTypeLine(g, card, state, headerColors, x, typeY, w, typeBoxHeight);
//draw P/T box
if (ptBoxHeight > 0) {
//only needed if on top since otherwise P/T will be hidden
Color[] ptColors = tintColors(Color.WHITE, colors, PT_BOX_TINT);
drawPtBox(g, card, state, ptColors, x, ptY - ptBoxHeight / 2f, w, ptBoxHeight);
}
}
private static Color[] fillColorBackground(Graphics2D g, List<DetailColors> backColors, float x, float y, float w, float h) {
Color[] colors = new Color[backColors.size()];
for (int i = 0; i < colors.length; i++) {
DetailColors dc = backColors.get(i);
colors[i] = new Color(dc.r, dc.g, dc.b);
}
fillColorBackground(g, colors, x, y, w, h);
return colors;
}
private static void fillColorBackground(Graphics2D g, Color[] colors, float x, float y, float w, float h) {
Paint oldPaint = g.getPaint();
switch (colors.length) {
case 1:
g.setColor(colors[0]);
g.fillRect(Math.round(x), Math.round(y), Math.round(w), Math.round(h));
break;
case 2:
GradientPaint gradient = new GradientPaint(x, y, colors[0], x + w, y, colors[1]);
g.setPaint(gradient);
g.fillRect(Math.round(x), Math.round(y), Math.round(w), Math.round(h));
break;
case 3:
float halfWidth = w / 2;
GradientPaint gradient1 = new GradientPaint(x, y, colors[0], x + halfWidth, y, colors[1]);
g.setPaint(gradient1);
g.fillRect(Math.round(x), Math.round(y), Math.round(halfWidth), Math.round(h));
GradientPaint gradient2 = new GradientPaint(x + halfWidth, y, colors[1], x + w, y, colors[2]);
g.setPaint(gradient2);
g.fillRect(Math.round(x + halfWidth), Math.round(y), Math.round(halfWidth), Math.round(h));
break;
}
g.setPaint(oldPaint);
}
private static void drawVerticallyCenteredString(Graphics2D g, String text, Rectangle area, Font font, final FontMetrics fontMetrics) {
Font oldFont = g.getFont();
int x = area.x;
int y = area.y + (area.height - fontMetrics.getHeight()) / 2 + fontMetrics.getAscent();
g.setFont(font);
g.drawString(text, x, y);
g.setFont(oldFont);
}
private static void drawHeader(Graphics2D g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h) {
fillColorBackground(g, colors, x, y, w, h);
g.setStroke(new BasicStroke(BORDER_THICKNESS));
g.setColor(Color.BLACK);
g.drawRect(Math.round(x), Math.round(y), Math.round(w), Math.round(h));
float padding = h / 8;
//draw mana cost for card
float manaCostWidth = 0;
ManaCost mainManaCost = state.getManaCost();
if (card.isSplitCard() && card.getAlternateState() != null) {
//handle rendering both parts of split card
mainManaCost = card.getLeftSplitState().getManaCost();
ManaCost otherManaCost = card.getAlternateState().getManaCost();
manaCostWidth = otherManaCost.getGlyphCount() * MANA_SYMBOL_SIZE + HEADER_PADDING;
CardFaceSymbols.draw(g, otherManaCost, Math.round(x + w - manaCostWidth), Math.round(y + (h - MANA_SYMBOL_SIZE) / 2), Math.round(MANA_SYMBOL_SIZE));
//draw "//" between two parts of mana cost
manaCostWidth += NAME_METRICS.stringWidth("//") + HEADER_PADDING;
drawVerticallyCenteredString(g, "//",
new Rectangle(Math.round(x + w - manaCostWidth), Math.round(y), Math.round(w), Math.round(h)),
NAME_FONT, NAME_METRICS);
}
manaCostWidth += mainManaCost.getGlyphCount() * MANA_SYMBOL_SIZE + HEADER_PADDING;
CardFaceSymbols.draw(g, mainManaCost, Math.round(x + w - manaCostWidth), Math.round(y + (h - MANA_SYMBOL_SIZE) / 2), Math.round(MANA_SYMBOL_SIZE));
//draw name for card
x += padding;
w -= 2 * padding;
drawVerticallyCenteredString(g, CardTranslation.getTranslatedName(state.getName()),
new Rectangle(Math.round(x), Math.round(y), Math.round(w - manaCostWidth - padding), Math.round(h)),
NAME_FONT, NAME_METRICS);
}
private static void drawArt(Graphics2D g, Color[] colors, float x, float y, float w, float h) {
fillColorBackground(g, colors, x, y, w, h);
SkinIcon art = FSkin.getIcon(FSkinProp.ICO_LOGO);
float artWidth = (float)art.getSizeForPaint(g).getWidth();
float artHeight = (float)art.getSizeForPaint(g).getHeight();
if (artWidth / artHeight >= w / h) {
float newH = w * (artHeight / artWidth);
FSkin.drawImage(g, art, Math.round(x), Math.round(y + (h - newH) / 2), Math.round(w), Math.round(newH));
} else {
float newW = h * (artWidth / artHeight);
FSkin.drawImage(g, art, Math.round(x + (w - newW) / 2), Math.round(y), Math.round(newW), Math.round(h));
}
g.setStroke(new BasicStroke(BORDER_THICKNESS));
g.setColor(Color.BLACK);
g.drawRect(Math.round(x), Math.round(y), Math.round(w), Math.round(h));
}
private static void drawTypeLine(Graphics2D g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h) {
fillColorBackground(g, colors, x, y, w, h);
g.setStroke(new BasicStroke(BORDER_THICKNESS));
g.setColor(Color.BLACK);
g.drawRect(Math.round(x), Math.round(y), Math.round(w), Math.round(h));
float padding = h / 8;
//draw square icon for rarity
float iconSize = h * 0.55f;
float iconPadding = (h - iconSize) / 2;
w -= iconSize + iconPadding * 2;
g.setColor(getRarityColor(state.getRarity()));
g.fillRect(Math.round(x + w + iconPadding), Math.round(y + (h - iconSize) / 2), Math.round(iconSize), Math.round(iconSize));
//draw type
x += padding;
g.setColor(Color.BLACK);
drawVerticallyCenteredString(g, CardDetailUtil.formatCardType(state, true),
new Rectangle(Math.round(x), Math.round(y), Math.round(w), Math.round(h)),
TYPE_FONT, TYPE_METRICS);
}
private static void drawTextBox(Graphics2D g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h) {
fillColorBackground(g, colors, x, y, w, h);
g.setStroke(new BasicStroke(BORDER_THICKNESS));
g.setColor(Color.BLACK);
g.drawRect(Math.round(x), Math.round(y), Math.round(w), Math.round(h));
if (state.isBasicLand()) {
//draw icons for basic lands
String imageKey;
switch (state.getName().replaceFirst("^Snow-Covered ", "")) {
case "Plains":
imageKey = "W";
break;
case "Island":
imageKey = "U";
break;
case "Swamp":
imageKey = "B";
break;
case "Mountain":
imageKey = "R";
break;
case "Forest":
imageKey = "G";
break;
default:
imageKey = "C";
break;
}
int iconSize = Math.round(h * 0.75f);
CardFaceSymbols.drawSymbol(imageKey, g, Math.round(x + (w - iconSize) / 2), Math.round(y + (h - iconSize) / 2), iconSize);
} else {
boolean needTranslation = true;
if (card.isToken()) {
if (card.getCloneOrigin() == null)
needTranslation = false;
}
final String text = !card.isSplitCard() ?
card.getText(state, needTranslation ? CardTranslation.getTranslationTexts(state.getName(), "") : null) :
card.getText(state, needTranslation ? CardTranslation.getTranslationTexts(card.getLeftSplitState().getName(), card.getRightSplitState().getName()) : null );
if (StringUtils.isEmpty(text))
return;
float padding = TEXT_METRICS.getAscent() * 0.25f;
x += padding;
w -= 2 * padding;
String textShow = text.replaceAll("(\r\n\r\n)|(\n)", "<br>");
textShow = FSkin.encodeSymbols(textShow, true);
JEditorPane textArea = new JEditorPane("text/html", textShow);
textArea.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
textArea.setOpaque(false);
textArea.setFont(TEXT_FONT);
textArea.setSize(Math.round(w), Math.round(h));
textArea.print(g.create(Math.round(x), Math.round(y), Math.round(w), Math.round(h)));
}
}
private static void drawPtBox(Graphics2D g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h) {
List<String> pieces = new ArrayList<>();
if (state.isCreature()) {
pieces.add(String.valueOf(state.getPower()));
pieces.add("/");
pieces.add(String.valueOf(state.getToughness()));
}
else if (state.isPlaneswalker()) {
pieces.add(String.valueOf(state.getLoyalty()));
}
else if (state.getType().hasSubtype("Vehicle")) {
pieces.add("[");
pieces.add(String.valueOf(state.getPower()));
pieces.add("/");
pieces.add(String.valueOf(state.getToughness()));
pieces.add("]");
}
else { return; }
float padding = PT_METRICS.getAscent() / 4f;
float totalPieceWidth = -padding;
float[] pieceWidths = new float[pieces.size()];
for (int i = 0; i < pieces.size(); i++) {
float pieceWidth = PT_METRICS.stringWidth(pieces.get(i)) + padding;
pieceWidths[i] = pieceWidth;
totalPieceWidth += pieceWidth;
}
float boxHeight = PT_METRICS.getMaxAscent() + padding;
float boxWidth = Math.max(PT_BOX_WIDTH, totalPieceWidth + 2 * padding);
x += w - boxWidth;
y += h - boxHeight;
w = boxWidth;
h = boxHeight;
fillColorBackground(g, colors, x, y, w, h);
g.setStroke(new BasicStroke(BORDER_THICKNESS));
g.setColor(Color.BLACK);
g.drawRect(Math.round(x), Math.round(y), Math.round(w), Math.round(h));
x += (boxWidth - totalPieceWidth) / 2;
for (int i = 0; i < pieces.size(); i++) {
drawVerticallyCenteredString(g, pieces.get(i),
new Rectangle(Math.round(x), Math.round(y), Math.round(w), Math.round(h)),
PT_FONT, PT_METRICS);
x += pieceWidths[i];
}
}
}