Merge branch 'patch-carddb-performance' into 'master'

Card Hover FIX in Catalog + extra optimisation in performance and refactoring

See merge request core-developers/forge!5263
This commit is contained in:
Michael Kamensky
2021-08-28 05:05:21 +00:00
20 changed files with 97 additions and 76 deletions

View File

@@ -236,7 +236,9 @@ public interface IPaperCard extends InventoryItem, Serializable {
CardRules getRules(); CardRules getRules();
CardRarity getRarity(); CardRarity getRarity();
String getArtist(); String getArtist();
String getItemType(); String getItemType();
boolean hasBackFace();
String getCardImageKey();
String getCardAltImageKey();
} }

View File

@@ -20,10 +20,7 @@ package forge.item;
import com.google.common.base.Function; import com.google.common.base.Function;
import forge.ImageKeys; import forge.ImageKeys;
import forge.StaticData; import forge.StaticData;
import forge.card.CardDb; import forge.card.*;
import forge.card.CardEdition;
import forge.card.CardRarity;
import forge.card.CardRules;
import forge.util.CardTranslation; import forge.util.CardTranslation;
import forge.util.ImageUtil; import forge.util.ImageUtil;
import forge.util.Localizer; import forge.util.Localizer;
@@ -141,13 +138,6 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
return hasImage; return hasImage;
} }
private String imageKeyFromSet = null;
public String getImageKeyFromSet() {
if (this.imageKeyFromSet == null)
this.imageKeyFromSet = ImageUtil.getImageKey(this, false, true);
return imageKeyFromSet;
}
/** /**
* Lambda to get rules for selects from list of printed cards. * Lambda to get rules for selects from list of printed cards.
*/ */
@@ -308,6 +298,32 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
return imageKey; return imageKey;
} }
private String cardImageKey = null;
@Override
public String getCardImageKey() {
if (this.cardImageKey == null)
this.cardImageKey = ImageUtil.getImageKey(this, false, true);
return cardImageKey;
}
private String cardAltImageKey = null;
@Override
public String getCardAltImageKey() {
if (this.cardAltImageKey == null){
if (this.hasBackFace())
this.cardAltImageKey = ImageUtil.getImageKey(this, true, true);
else // altImageKey will be the same as cardImageKey
this.cardAltImageKey = ImageUtil.getImageKey(this, false, true);
}
return cardAltImageKey;
}
@Override
public boolean hasBackFace(){
CardSplitType cst = this.rules.getSplitType();
return cst == CardSplitType.Transform || cst == CardSplitType.Flip || cst == CardSplitType.Meld || cst == CardSplitType.Modal;
}
// Return true if card is one of the five basic lands that can be added for free // Return true if card is one of the five basic lands that can be added for free
public boolean isVeryBasicLand() { public boolean isVeryBasicLand() {
return (this.getName().equals("Swamp")) return (this.getName().equals("Swamp"))

View File

@@ -151,8 +151,25 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
@Override public String getItemType() { return "Token"; } @Override public String getItemType() { return "Token"; }
@Override
public boolean hasBackFace() {
return false;
}
@Override public boolean isToken() { return true; } @Override public boolean isToken() { return true; }
// IPaperCard
@Override
public String getCardImageKey() {
return this.getImageKey(false);
}
@Override
public String getCardAltImageKey() {
return getImageKey(true);
}
// InventoryItem
@Override @Override
public String getImageKey(boolean altState) { public String getImageKey(boolean altState) {
int idx = MyRandom.getRandom().nextInt(artIndex); int idx = MyRandom.getRandom().nextInt(artIndex);

View File

@@ -83,15 +83,10 @@ public class ImageUtil {
} }
} }
public static boolean hasBackFacePicture(PaperCard cp) {
CardSplitType cst = cp.getRules().getSplitType();
return cst == CardSplitType.Transform || cst == CardSplitType.Flip || cst == CardSplitType.Meld || cst == CardSplitType.Modal;
}
public static String getNameToUse(PaperCard cp, boolean backFace) { public static String getNameToUse(PaperCard cp, boolean backFace) {
final CardRules card = cp.getRules(); final CardRules card = cp.getRules();
if (backFace) { if (backFace) {
if (hasBackFacePicture(cp)) if (cp.hasBackFace())
if (card.getOtherPart() != null) { if (card.getOtherPart() != null) {
return card.getOtherPart().getName(); return card.getOtherPart().getName();
} else if (!card.getMeldWith().isEmpty()) { } else if (!card.getMeldWith().isEmpty()) {

View File

@@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentHashMap;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.item.InventoryItem; import forge.item.InventoryItem;
/** /**
@@ -149,12 +150,14 @@ public class ItemPool<T extends InventoryItem> implements Iterable<Entry<T, Inte
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public final <U extends InventoryItem> int countAll(Predicate<U> condition, Class<U> cls) { public final <U extends InventoryItem> int countAll(Predicate<U> condition, Class<U> cls) {
int count = 0; int count = 0;
for (Entry<T, Integer> e : this) { Iterable<T> matchingKeys = Iterables.filter(this.items.keySet(), new Predicate<T>() {
T item = e.getKey(); @Override
if (cls.isInstance(item) && condition.apply((U)item)) { public boolean apply(T item) {
count += e.getValue(); return cls.isInstance(item) && condition.apply((U)item);
} }
} });
for (T key : matchingKeys)
count += this.items.get(key);
return count; return count;
} }

View File

@@ -168,11 +168,11 @@ public class ImageCache {
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)) {
PaperCard pc = ImageUtil.getPaperCardFromImageKey(imageKey); ipc = ImageUtil.getPaperCardFromImageKey(imageKey);
ipc = pc; if (ipc != null) {
imageKey = ImageUtil.getImageKey(pc, altState, true); imageKey = altState ? ipc.getCardAltImageKey() : ipc.getCardImageKey();
if (StringUtils.isBlank(imageKey)) { if (StringUtils.isBlank(imageKey))
return Pair.of(_defaultImage, true); return Pair.of(_defaultImage, true);
} }
} }
@@ -184,7 +184,7 @@ public class ImageCache {
if (useArtCrop) { if (useArtCrop) {
if (ipc != null && ipc.getRules().getSplitType() == CardSplitType.Flip) { if (ipc != null && ipc.getRules().getSplitType() == CardSplitType.Flip) {
// Art crop will always use front face as image key for flip cards // Art crop will always use front face as image key for flip cards
imageKey = ((PaperCard) ipc).getImageKeyFromSet(); // ImageUtil.getImageKey((PaperCard) ipc, false, true); imageKey = ((PaperCard) ipc).getCardImageKey();
} }
imageKey = TextUtil.fastReplace(imageKey, ".full", ".artcrop"); imageKey = TextUtil.fastReplace(imageKey, ".full", ".artcrop");
} }

View File

@@ -35,7 +35,6 @@ import forge.item.PaperCard;
import forge.localinstance.properties.ForgeConstants; import forge.localinstance.properties.ForgeConstants;
import forge.model.FModel; import forge.model.FModel;
import forge.util.FileUtil; import forge.util.FileUtil;
import forge.util.ImageUtil;
public class ImportSourceAnalyzer { public class ImportSourceAnalyzer {
@@ -315,7 +314,7 @@ public class ImportSourceAnalyzer {
for (final PaperCard c : FModel.getMagicDb().getCommonCards().getAllCards()) { for (final PaperCard c : FModel.getMagicDb().getCommonCards().getAllCards()) {
addDefaultPicNames(c, false); addDefaultPicNames(c, false);
if (ImageUtil.hasBackFacePicture(c)) { if (c.hasBackFace()) {
addDefaultPicNames(c, true); addDefaultPicNames(c, true);
} }
} }
@@ -360,10 +359,10 @@ public class ImportSourceAnalyzer {
private static void addSetCards(final Map<String, String> cardFileNames, final Iterable<PaperCard> library, final Predicate<PaperCard> filter) { private static void addSetCards(final Map<String, String> cardFileNames, final Iterable<PaperCard> library, final Predicate<PaperCard> filter) {
for (final PaperCard c : Iterables.filter(library, filter)) { for (final PaperCard c : Iterables.filter(library, filter)) {
String filename = ImageUtil.getImageKey(c, false, true) + ".jpg"; String filename = c.getCardImageKey() + ".jpg";
cardFileNames.put(filename, filename); cardFileNames.put(filename, filename);
if (ImageUtil.hasBackFacePicture(c)) { if (c.hasBackFace()) {
filename = ImageUtil.getImageKey(c, true, true) + ".jpg"; filename = c.getCardAltImageKey() + ".jpg";
cardFileNames.put(filename, filename); cardFileNames.put(filename, filename);
} }
} }
@@ -392,10 +391,10 @@ public class ImportSourceAnalyzer {
}; };
for (final PaperCard c : Iterables.filter(FModel.getMagicDb().getVariantCards().getAllCards(), predPlanes)) { for (final PaperCard c : Iterables.filter(FModel.getMagicDb().getVariantCards().getAllCards(), predPlanes)) {
String baseName = ImageUtil.getImageKey(c,false, true); String baseName = c.getCardImageKey();
nameUpdates.put(baseName + ".full.jpg", baseName + ".jpg"); nameUpdates.put(baseName + ".full.jpg", baseName + ".jpg");
if (ImageUtil.hasBackFacePicture(c)) { if (c.hasBackFace()) {
baseName = ImageUtil.getImageKey(c, true, true); baseName = c.getCardAltImageKey();
nameUpdates.put(baseName + ".full.jpg", baseName + ".jpg"); nameUpdates.put(baseName + ".full.jpg", baseName + ".jpg");
} }
} }

View File

@@ -1,6 +1,7 @@
package forge.itemmanager.filters; package forge.itemmanager.filters;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import javax.swing.JPanel; import javax.swing.JPanel;
@@ -76,10 +77,12 @@ public abstract class StatTypeFilter<T extends InventoryItem> extends ToggleButt
btnPackOrDeck.setText(String.valueOf(count)); btnPackOrDeck.setText(String.valueOf(count));
} }
for (Map.Entry<StatTypes, FLabel> btn : buttonMap.entrySet()) { Iterator<StatTypes> buttonMapStatsIterator = buttonMap.keySet().iterator();
if (btn.getKey().predicate != null) { while (buttonMapStatsIterator.hasNext()){
int count = items.countAll(Predicates.compose(btn.getKey().predicate, PaperCard.FN_GET_RULES), PaperCard.class); StatTypes statTypes = buttonMapStatsIterator.next();
btn.getValue().setText(String.valueOf(count)); if (statTypes.predicate != null){
int count = items.countAll(Predicates.compose(statTypes.predicate, PaperCard.FN_GET_RULES), PaperCard.class);
buttonMap.get(statTypes).setText(String.valueOf(count));
} }
} }
getWidget().revalidate(); getWidget().revalidate();

View File

@@ -23,7 +23,6 @@ import forge.toolbox.FSkin.SkinColor;
import forge.toolbox.FSkin.SkinFont; import forge.toolbox.FSkin.SkinFont;
import forge.toolbox.FSkin.SkinImage; import forge.toolbox.FSkin.SkinImage;
import forge.toolbox.special.CardZoomer; import forge.toolbox.special.CardZoomer;
import forge.util.ImageUtil;
import forge.util.Localizer; import forge.util.Localizer;
import forge.view.arcane.CardPanel; import forge.view.arcane.CardPanel;
@@ -61,8 +60,6 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
private ItemInfo hoveredItem; private ItemInfo hoveredItem;
private ItemInfo focalItem; private ItemInfo focalItem;
private boolean panelOptionsCreated = false; private boolean panelOptionsCreated = false;
// cards with alternate states are added twice for displaying
private InventoryItem lastAltCard = null;
private final List<ItemInfo> orderedItems = new ArrayList<>(); private final List<ItemInfo> orderedItems = new ArrayList<>();
private final List<Group> groups = new ArrayList<>(); private final List<Group> groups = new ArrayList<>();
@@ -1103,30 +1100,16 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
InventoryItem item = itemInfo.item; InventoryItem item = itemInfo.item;
itemInfo.alt = false; itemInfo.alt = false;
if (!FModel.getPreferences().getPref(FPref.UI_SWITCH_STATES_DECKVIEW).equals(ForgeConstants.SWITCH_CARDSTATES_DECK_NEVER)) { if (!FModel.getPreferences().getPref(FPref.UI_SWITCH_STATES_DECKVIEW).equals(ForgeConstants.SWITCH_CARDSTATES_DECK_NEVER)) {
if ((hoveredItem == null || !hoveredItem.item.equals(item)) || (FModel.getPreferences().getPref(FPref.UI_SWITCH_STATES_DECKVIEW).equals(ForgeConstants.SWITCH_CARDSTATES_DECK_ALWAYS))) { if (hoveredItem != null && hoveredItem.item.equals(item)) {
if (item instanceof PaperCard) { if (item instanceof PaperCard && ((PaperCard)item).hasBackFace())
if (ImageUtil.hasBackFacePicture(((PaperCard)item))) { itemInfo.alt = true;
if (item.equals(lastAltCard)) {
itemInfo.alt = true;
lastAltCard = null;
}
else {
lastAltCard = item;
}
}
else {
lastAltCard = null;
}
}
} }
} }
if (itemInfo != hoveredItem) { //save hovered item for last if (itemInfo != hoveredItem) //save hovered item for last
drawItemImage(g2d, itemInfo); drawItemImage(g2d, itemInfo);
} else
else {
skippedItem = itemInfo; skippedItem = itemInfo;
}
} }
} }
if (skippedItem != null) { //draw hovered item on top if (skippedItem != null) { //draw hovered item on top

View File

@@ -538,7 +538,7 @@ public enum CSubmenuPreferences implements ICDoc {
} }
private void initializeSwitchStatesCombobox() { private void initializeSwitchStatesCombobox() {
final String[] elems = {ForgeConstants.SWITCH_CARDSTATES_DECK_NEVER, ForgeConstants.SWITCH_CARDSTATES_DECK_HOVER, ForgeConstants.SWITCH_CARDSTATES_DECK_ALWAYS}; final String[] elems = {ForgeConstants.SWITCH_CARDSTATES_DECK_NEVER, ForgeConstants.SWITCH_CARDSTATES_DECK_HOVER};
final FPref userSetting = FPref.UI_SWITCH_STATES_DECKVIEW; final FPref userSetting = FPref.UI_SWITCH_STATES_DECKVIEW;
final FComboBoxPanel<String> panel = this.view.getSwitchStates(); final FComboBoxPanel<String> panel = this.view.getSwitchStates();
final FComboBox<String> comboBox = createComboBox(elems, userSetting); final FComboBox<String> comboBox = createComboBox(elems, userSetting);

View File

@@ -306,7 +306,7 @@ public enum VSubmenuDownloaders implements IVSubmenu<CSubmenuDownloaders> {
// //
// check the back face // check the back face
// //
if (ImageUtil.hasBackFacePicture(cp)) { if (cp.hasBackFace()) {
imagePath = ImageUtil.getImageRelativePath(cp, true, true, false); imagePath = ImageUtil.getImageRelativePath(cp, true, true, false);
if (imagePath != null) { if (imagePath != null) {
File file = ImageKeys.getImageFile(imagePath); File file = ImageKeys.getImageFile(imagePath);

View File

@@ -30,6 +30,7 @@ import javax.imageio.ImageIO;
import forge.ImageCache; import forge.ImageCache;
import forge.ImageKeys; import forge.ImageKeys;
import forge.game.card.CardView.CardStateView; import forge.game.card.CardView.CardStateView;
import forge.item.PaperCard;
import forge.localinstance.properties.ForgePreferences; import forge.localinstance.properties.ForgePreferences;
import forge.model.FModel; import forge.model.FModel;
import forge.toolbox.CardFaceSymbols; import forge.toolbox.CardFaceSymbols;
@@ -78,7 +79,9 @@ public final class FImageUtil {
boolean altState = key.endsWith(ImageKeys.BACKFACE_POSTFIX); boolean altState = key.endsWith(ImageKeys.BACKFACE_POSTFIX);
String imageKey = key; String imageKey = key;
if (prefix.equals(ImageKeys.CARD_PREFIX)) { if (prefix.equals(ImageKeys.CARD_PREFIX)) {
imageKey = ImageUtil.getImageKey(ImageUtil.getPaperCardFromImageKey(key), altState, true); PaperCard card = ImageUtil.getPaperCardFromImageKey(key);
if (card != null)
imageKey = altState ? card.getCardAltImageKey() : card.getCardImageKey();
} }
if(altState) { if(altState) {
imageKey = imageKey.substring(0, imageKey.length() - ImageKeys.BACKFACE_POSTFIX.length()); imageKey = imageKey.substring(0, imageKey.length() - ImageKeys.BACKFACE_POSTFIX.length());

View File

@@ -164,7 +164,7 @@ public class ImageCache {
return false; return false;
final boolean backFace = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX); final boolean backFace = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX);
final String cardfilename = ImageUtil.getImageKey(paperCard, backFace, true); final String cardfilename = backFace ? paperCard.getCardAltImageKey() : paperCard.getCardImageKey();
if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".jpg").exists()) if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".jpg").exists())
if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".png").exists()) if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".png").exists())
if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + TextUtil.fastReplace(cardfilename,".full", ".fullborder") + ".jpg").exists()) if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + TextUtil.fastReplace(cardfilename,".full", ".fullborder") + ".jpg").exists())
@@ -200,7 +200,9 @@ public class ImageCache {
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); PaperCard card = ImageUtil.getPaperCardFromImageKey(imageKey);
if (card != null)
imageKey = altState ? card.getCardAltImageKey() : card.getCardImageKey();
if (StringUtils.isBlank(imageKey)) { if (StringUtils.isBlank(imageKey)) {
return defaultImage; return defaultImage;
} }

View File

@@ -47,7 +47,7 @@ public class GuiDownloadPicturesHQ extends GuiDownloadService {
for (final PaperCard c : FModel.getMagicDb().getCommonCards().getAllCards()) { for (final PaperCard c : FModel.getMagicDb().getCommonCards().getAllCards()) {
addDLObject(c, false); addDLObject(c, false);
if (ImageUtil.hasBackFacePicture(c)) { if (c.hasBackFace()) {
addDLObject(c, true); addDLObject(c, true);
} }
} }

View File

@@ -49,7 +49,7 @@ public class GuiDownloadPicturesLQ extends GuiDownloadService {
for (final PaperCard c : FModel.getMagicDb().getCommonCards().getAllCards()) { for (final PaperCard c : FModel.getMagicDb().getCommonCards().getAllCards()) {
addDLObject(c, false); addDLObject(c, false);
if (ImageUtil.hasBackFacePicture(c)) { if (c.hasBackFace()) {
addDLObject(c, true); addDLObject(c, true);
} }
} }

View File

@@ -74,7 +74,7 @@ public class GuiDownloadSetPicturesLQ extends GuiDownloadService {
addDLObject(ImageUtil.getDownloadUrl(c, false), ImageUtil.getImageKey(c, false, true), downloads); addDLObject(ImageUtil.getDownloadUrl(c, false), ImageUtil.getImageKey(c, false, true), downloads);
if (ImageUtil.hasBackFacePicture(c)) { if (c.hasBackFace()) {
addDLObject(ImageUtil.getDownloadUrl(c, true), ImageUtil.getImageKey(c, true, true), downloads); addDLObject(ImageUtil.getDownloadUrl(c, true), ImageUtil.getImageKey(c, true, true), downloads);
} }
} }

View File

@@ -1,7 +1,6 @@
package forge.itemmanager; package forge.itemmanager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -151,7 +150,7 @@ public final class SItemManagerUtil {
for (final Entry<InventoryItem, Integer> itemEntry : items) { for (final Entry<InventoryItem, Integer> itemEntry : items) {
sorted.add(itemEntry); sorted.add(itemEntry);
} }
Collections.sort(sorted, new Comparator<Entry<InventoryItem, Integer>>() { sorted.sort(new Comparator<Entry<InventoryItem, Integer>>() {
@Override @Override
public int compare(final Entry<InventoryItem, Integer> x, final Entry<InventoryItem, Integer> y) { public int compare(final Entry<InventoryItem, Integer> x, final Entry<InventoryItem, Integer> y) {
return x.getKey().toString().compareTo(y.getKey().toString()); return x.getKey().toString().compareTo(y.getKey().toString());

View File

@@ -353,7 +353,6 @@ public final class ForgeConstants {
// Constants for Land played notification policy // Constants for Land played notification policy
public static final String SWITCH_CARDSTATES_DECK_NEVER = "Never"; public static final String SWITCH_CARDSTATES_DECK_NEVER = "Never";
public static final String SWITCH_CARDSTATES_DECK_ALWAYS = "Always";
public static final String SWITCH_CARDSTATES_DECK_HOVER = "Switch back on hover"; public static final String SWITCH_CARDSTATES_DECK_HOVER = "Switch back on hover";
// Set boolean constant for landscape mode for gdx port // Set boolean constant for landscape mode for gdx port

View File

@@ -163,7 +163,7 @@ public class ForgePreferences extends PreferencesStore<ForgePreferences.FPref> {
UI_CARD_ART_FORMAT("Full"), UI_CARD_ART_FORMAT("Full"),
UI_SELECT_FROM_CARD_DISPLAYS("true"), UI_SELECT_FROM_CARD_DISPLAYS("true"),
UI_FOR_TOUCHSCREN("false"), UI_FOR_TOUCHSCREN("false"),
UI_SWITCH_STATES_DECKVIEW("Always"), UI_SWITCH_STATES_DECKVIEW("Switch back on hover"),
UI_VIBRATE_ON_LIFE_LOSS("true"), UI_VIBRATE_ON_LIFE_LOSS("true"),
UI_VIBRATE_ON_LONG_PRESS("true"), UI_VIBRATE_ON_LONG_PRESS("true"),

View File

@@ -79,7 +79,7 @@ public abstract class ImageFetcher {
return; return;
final boolean backFace = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX); final boolean backFace = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX);
String filename = ImageUtil.getImageKey(paperCard, backFace, true); String filename = backFace ? paperCard.getCardAltImageKey() : paperCard.getCardImageKey();
if (useArtCrop) { if (useArtCrop) {
filename = TextUtil.fastReplace(filename, ".full", ".artcrop"); filename = TextUtil.fastReplace(filename, ".full", ".artcrop");
} }