diff --git a/forge-gui-desktop/src/main/java/forge/ImageCache.java b/forge-gui-desktop/src/main/java/forge/ImageCache.java index a1587ba2445..4c5c5182e33 100644 --- a/forge-gui-desktop/src/main/java/forge/ImageCache.java +++ b/forge-gui-desktop/src/main/java/forge/ImageCache.java @@ -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 . */ @@ -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; *
  • Keys start with the file name, extension is skipped
  • *
  • The key without suffix belongs to the unmodified image from the file
  • * - * + * * @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. *

    * 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. *

    */ 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; diff --git a/forge-gui-desktop/src/main/java/forge/itemmanager/views/ColorSetRenderer.java b/forge-gui-desktop/src/main/java/forge/itemmanager/views/ColorSetRenderer.java index db9576ebe98..6735e15f1ec 100644 --- a/forge-gui-desktop/src/main/java/forge/itemmanager/views/ColorSetRenderer.java +++ b/forge-gui-desktop/src/main/java/forge/itemmanager/views/ColorSetRenderer.java @@ -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; } } diff --git a/forge-gui-desktop/src/main/java/forge/itemmanager/views/ManaCostRenderer.java b/forge-gui-desktop/src/main/java/forge/itemmanager/views/ManaCostRenderer.java index b18787cebb2..30a3a3133c4 100644 --- a/forge-gui-desktop/src/main/java/forge/itemmanager/views/ManaCostRenderer.java +++ b/forge-gui-desktop/src/main/java/forge/itemmanager/views/ManaCostRenderer.java @@ -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 . */ @@ -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; } } diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/CardFaceSymbols.java b/forge-gui-desktop/src/main/java/forge/toolbox/CardFaceSymbols.java index 68ff467db99..ac7105b0a75 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/CardFaceSymbols.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/CardFaceSymbols.java @@ -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() { diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/imaging/FCardImageRenderer.java b/forge-gui-desktop/src/main/java/forge/toolbox/imaging/FCardImageRenderer.java new file mode 100644 index 00000000000..d1527bee3f5 --- /dev/null +++ b/forge-gui-desktop/src/main/java/forge/toolbox/imaging/FCardImageRenderer.java @@ -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 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 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)", "
    "); + 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 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]; + } + } +}