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

@@ -42,6 +42,7 @@ import forge.game.card.CardView;
import forge.game.player.PlayerView; import forge.game.player.PlayerView;
import forge.gui.FThreads; import forge.gui.FThreads;
import forge.item.InventoryItem; import forge.item.InventoryItem;
import forge.item.PaperCard;
import forge.localinstance.properties.ForgeConstants; import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgePreferences; import forge.localinstance.properties.ForgePreferences;
import forge.localinstance.properties.ForgePreferences.FPref; import forge.localinstance.properties.ForgePreferences.FPref;
@@ -49,6 +50,7 @@ import forge.localinstance.skin.FSkinProp;
import forge.model.FModel; import forge.model.FModel;
import forge.toolbox.FSkin; import forge.toolbox.FSkin;
import forge.toolbox.FSkin.SkinIcon; import forge.toolbox.FSkin.SkinIcon;
import forge.toolbox.imaging.FCardImageRenderer;
import forge.util.ImageUtil; import forge.util.ImageUtil;
/** /**
@@ -147,11 +149,13 @@ public class ImageCache {
return null; return null;
} }
PaperCard pc = null;
boolean altState = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX); boolean altState = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX);
if(altState) if(altState)
imageKey = imageKey.substring(0, imageKey.length() - ImageKeys.BACKFACE_POSTFIX.length()); imageKey = imageKey.substring(0, imageKey.length() - ImageKeys.BACKFACE_POSTFIX.length());
if (imageKey.startsWith(ImageKeys.CARD_PREFIX)) { if (imageKey.startsWith(ImageKeys.CARD_PREFIX)) {
imageKey = ImageUtil.getImageKey(ImageUtil.getPaperCardFromImageKey(imageKey), altState, true); pc = ImageUtil.getPaperCardFromImageKey(imageKey);
imageKey = ImageUtil.getImageKey(pc, altState, true);
if (StringUtils.isBlank(imageKey)) { if (StringUtils.isBlank(imageKey)) {
return _defaultImage; return _defaultImage;
} }
@@ -198,8 +202,14 @@ public class ImageCache {
// a default "not available" image, however do not add it to the cache, // 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. // as otherwise it's problematic to update if the real image gets fetched.
if (original == null && useDefaultIfNotFound) { 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; original = _defaultImage;
} }
}
return original; return original;
} }

View File

@@ -58,7 +58,7 @@ public class ColorSetRenderer extends ItemCellRenderer {
final int dx = Math.min(elemtWidth + elemtGap, offsetIfNoSpace); final int dx = Math.min(elemtWidth + elemtGap, offsetIfNoSpace);
for (final ManaCostShard s : cs.getOrderedShards()) { for (final ManaCostShard s : cs.getOrderedShards()) {
CardFaceSymbols.drawSymbol(s.getImageKey(), g, x, y); CardFaceSymbols.drawManaSymbol(s.getImageKey(), g, x, y);
x += dx; x += dx;
} }
} }

View File

@@ -112,7 +112,7 @@ public class ManaCostRenderer extends ItemCellRenderer {
// Display X Mana before any other type of mana // Display X Mana before any other type of mana
if (xManaCosts > 0) { if (xManaCosts > 0) {
for (int i = 0; i < xManaCosts; i++) { 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; x += dx;
} }
} }
@@ -120,7 +120,7 @@ public class ManaCostRenderer extends ItemCellRenderer {
// Display generic mana before colored mana // Display generic mana before colored mana
if (hasGeneric) { if (hasGeneric) {
final String sGeneric = Integer.toString(genericManaCost); final String sGeneric = Integer.toString(genericManaCost);
CardFaceSymbols.drawSymbol(sGeneric, g, x, y); CardFaceSymbols.drawManaSymbol(sGeneric, g, x, y);
x += dx; x += dx;
} }
@@ -129,7 +129,7 @@ public class ManaCostRenderer extends ItemCellRenderer {
// X costs already drawn up above // X costs already drawn up above
continue; continue;
} }
CardFaceSymbols.drawSymbol(s.getImageKey(), g, x, y); CardFaceSymbols.drawManaSymbol(s.getImageKey(), g, x, y);
x += dx; x += dx;
} }
} }

View File

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