diff --git a/forge-gui-android/src/forge/app/Main.java b/forge-gui-android/src/forge/app/Main.java index c235fb01ef2..bf4c73e0e4f 100644 --- a/forge-gui-android/src/forge/app/Main.java +++ b/forge-gui-android/src/forge/app/Main.java @@ -9,6 +9,8 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; @@ -28,6 +30,8 @@ import forge.util.FileUtil; import forge.util.ThreadUtil; import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; import java.util.concurrent.Callable; public class Main extends AndroidApplication { @@ -243,5 +247,11 @@ public class Main extends AndroidApplication { } }); } + + @Override + public void convertToJPEG(InputStream input, OutputStream output) { + Bitmap bmp = BitmapFactory.decodeStream(input); + bmp.compress(Bitmap.CompressFormat.JPEG, 100, output); + } } } diff --git a/forge-gui-desktop/src/main/java/forge/CachedCardImage.java b/forge-gui-desktop/src/main/java/forge/CachedCardImage.java index 8a34907f3d2..67c57a1d1bd 100644 --- a/forge-gui-desktop/src/main/java/forge/CachedCardImage.java +++ b/forge-gui-desktop/src/main/java/forge/CachedCardImage.java @@ -4,6 +4,9 @@ import java.awt.image.BufferedImage; import forge.game.card.CardView; import forge.game.player.PlayerView; +import forge.util.ImageFetcher; +import forge.util.SwingImageFetcher; + public abstract class CachedCardImage implements ImageFetcher.Callback { final CardView card; @@ -11,6 +14,8 @@ public abstract class CachedCardImage implements ImageFetcher.Callback { final int width; final int height; + static final SwingImageFetcher fetcher = new SwingImageFetcher(); + public CachedCardImage(final CardView card, final Iterable viewers, final int width, final int height) { this.card = card; this.viewers = viewers; @@ -19,7 +24,7 @@ public abstract class CachedCardImage implements ImageFetcher.Callback { BufferedImage image = ImageCache.getImageNoDefault(card, viewers, width, height); if (image == null) { String key = card.getCurrentState().getImageKey(viewers); - ImageFetcher.fetchImage(card, key, this); + fetcher.fetchImage(key, this); } } diff --git a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java index 6e4e7f7385a..ae3bc960403 100644 --- a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java +++ b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java @@ -23,10 +23,7 @@ import forge.sound.*; import forge.toolbox.FOptionPane; import forge.toolbox.FSkin; import forge.toolbox.FSkin.SkinImage; -import forge.util.BuildInfo; -import forge.util.Callback; -import forge.util.FileUtil; -import forge.util.OperatingSystem; +import forge.util.*; import org.apache.commons.lang3.StringUtils; import javax.swing.*; @@ -42,6 +39,8 @@ import java.util.Collection; import java.util.List; public class GuiDesktop implements IGuiBase { + private ImageFetcher imageFetcher = new SwingImageFetcher(); + @Override public boolean isRunningOnDesktop() { return true; @@ -63,6 +62,11 @@ public class GuiDesktop implements IGuiBase { "../forge-gui/" : ""; } + @Override + public ImageFetcher getImageFetcher() { + return imageFetcher; + } + @Override public void invokeInEdtNow(final Runnable proc) { proc.run(); diff --git a/forge-gui-desktop/src/main/java/forge/gui/CardPicturePanel.java b/forge-gui-desktop/src/main/java/forge/gui/CardPicturePanel.java index 17b1d5ae1d7..17cf49e6e0a 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/CardPicturePanel.java +++ b/forge-gui-desktop/src/main/java/forge/gui/CardPicturePanel.java @@ -24,8 +24,9 @@ import java.awt.image.WritableRaster; import javax.swing.JPanel; +import forge.GuiBase; import forge.ImageCache; -import forge.ImageFetcher; +import forge.util.ImageFetcher; import forge.ImageKeys; import forge.game.card.CardView.CardStateView; import forge.item.InventoryItem; @@ -115,7 +116,7 @@ public final class CardPicturePanel extends JPanel implements ImageFetcher.Callb CardStateView card = (CardStateView) displayed; BufferedImage image = ImageCache.getOriginalImage(card.getImageKey(), false); if (image == null) { - ImageFetcher.fetchImage(card.getCard(), card.getImageKey(), this); + GuiBase.getInterface().getImageFetcher().fetchImage(card.getImageKey(), this); } return FImageUtil.getImage((CardStateView) displayed); } diff --git a/forge-gui-desktop/src/main/java/forge/util/SwingImageFetcher.java b/forge-gui-desktop/src/main/java/forge/util/SwingImageFetcher.java new file mode 100644 index 00000000000..7949a3e1f10 --- /dev/null +++ b/forge-gui-desktop/src/main/java/forge/util/SwingImageFetcher.java @@ -0,0 +1,57 @@ +package forge.util; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.net.URL; + +public class SwingImageFetcher extends ImageFetcher { + + @Override + protected Runnable getDownloadTask(String[] downloadUrls, String destPath, Runnable notifyObservers) { + return new SwingDownloadTask(downloadUrls, destPath, notifyObservers); + } + + private static class SwingDownloadTask implements Runnable { + private final String[] downloadUrls; + private final String destPath; + private final Runnable notifyObservers; + + public SwingDownloadTask(String[] downloadUrls, String destPath, Runnable notifyObservers) { + this.downloadUrls = downloadUrls; + this.destPath = destPath; + this.notifyObservers = notifyObservers; + } + + private void doFetch(String urlToDownload) throws IOException { + URL url = new URL(urlToDownload); + System.out.println("Attempting to fetch: " + url); + java.net.URLConnection c = url.openConnection(); + c.setRequestProperty("User-Agent", ""); + BufferedImage image = ImageIO.read(c.getInputStream()); + // First, save to a temporary file so that nothing tries to read + // a partial download. + File destFile = new File(destPath + ".tmp"); + destFile.mkdirs(); + ImageIO.write(image, "jpg", destFile); + // Now, rename it to the correct name. + destFile.renameTo(new File(destPath)); + System.out.println("Saved image to " + destPath); + SwingUtilities.invokeLater(notifyObservers); + } + + public void run() { + for (String urlToDownload : downloadUrls) { + try { + doFetch(urlToDownload); + break; + } catch (IOException e) { + System.out.println("Failed to download card [" + destPath + "] image: " + e.getMessage()); + } + } + } + } + +} diff --git a/forge-gui-ios/src/forge/ios/Main.java b/forge-gui-ios/src/forge/ios/Main.java index cd1f569eca0..bad9865e574 100644 --- a/forge-gui-ios/src/forge/ios/Main.java +++ b/forge-gui-ios/src/forge/ios/Main.java @@ -13,6 +13,10 @@ import org.robovm.apple.foundation.NSAutoreleasePool; import org.robovm.apple.uikit.UIApplication; import org.robovm.apple.uikit.UIPasteboard; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + public class Main extends IOSApplication.Delegate { @Override @@ -94,5 +98,10 @@ public class Main extends IOSApplication.Delegate { public void exit() { // Not possible on iOS } + + @Override + public void convertToJPEG(InputStream input, OutputStream output) throws IOException { + + } } } \ No newline at end of file diff --git a/forge-gui-mobile-dev/src/forge/app/Main.java b/forge-gui-mobile-dev/src/forge/app/Main.java index 85c0ed08db0..d5a932cefda 100644 --- a/forge-gui-mobile-dev/src/forge/app/Main.java +++ b/forge-gui-mobile-dev/src/forge/app/Main.java @@ -13,9 +13,10 @@ import forge.util.RestartUtil; import forge.util.Utils; import org.apache.commons.cli.*; +import javax.imageio.ImageIO; import java.awt.*; -import java.io.File; -import java.io.IOException; +import java.awt.image.BufferedImage; +import java.io.*; public class Main { public static void main(String[] args) { @@ -162,5 +163,11 @@ public class Main { public void preventSystemSleep(boolean preventSleep) { OperatingSystem.preventSystemSleep(preventSleep); } + + @Override + public void convertToJPEG(InputStream input, OutputStream output) throws IOException { + BufferedImage image = ImageIO.read(input); + ImageIO.write(image, "jpg", output); + } } } diff --git a/forge-gui-mobile/src/forge/CachedCardImage.java b/forge-gui-mobile/src/forge/CachedCardImage.java new file mode 100644 index 00000000000..bb468f27e44 --- /dev/null +++ b/forge-gui-mobile/src/forge/CachedCardImage.java @@ -0,0 +1,40 @@ +package forge; + +import com.badlogic.gdx.graphics.Texture; +import forge.assets.ImageCache; +import forge.game.card.CardView; +import forge.item.InventoryItem; +import forge.util.ImageFetcher; + +public abstract class CachedCardImage implements ImageFetcher.Callback { + protected final String key; + static final ImageFetcher fetcher = GuiBase.getInterface().getImageFetcher(); + + public CachedCardImage(final CardView card) { + key = card.getCurrentState().getImageKey(); + fetch(); + } + + public CachedCardImage(final InventoryItem ii) { + key = ii.getImageKey(false); + fetch(); + } + + public CachedCardImage(String key) { + this.key = key; + fetch(); + } + + public void fetch() { + Texture image = ImageCache.getImage(key, false); + if (image == null) { + fetcher.fetchImage(key, this); + } + } + + public Texture getImage() { + return ImageCache.getImage(key, true); + } + + public abstract void onImageFetched(); +} diff --git a/forge-gui-mobile/src/forge/GuiMobile.java b/forge-gui-mobile/src/forge/GuiMobile.java index 0d5bb9e1552..8cea9560f78 100644 --- a/forge-gui-mobile/src/forge/GuiMobile.java +++ b/forge-gui-mobile/src/forge/GuiMobile.java @@ -37,14 +37,11 @@ import forge.sound.IAudioClip; import forge.sound.IAudioMusic; import forge.toolbox.FOptionPane; import forge.toolbox.GuiChoose; -import forge.util.Callback; -import forge.util.FileUtil; -import forge.util.ThreadUtil; -import forge.util.WaitCallback; -import forge.util.WaitRunnable; +import forge.util.*; public class GuiMobile implements IGuiBase { private final String assetsDir; + private ImageFetcher imageFetcher = new LibGDXImageFetcher(); public GuiMobile(final String assetsDir0) { assetsDir = assetsDir0; @@ -70,6 +67,11 @@ public class GuiMobile implements IGuiBase { return assetsDir; } + @Override + public ImageFetcher getImageFetcher() { + return imageFetcher; + } + @Override public void invokeInEdtNow(final Runnable proc) { proc.run(); diff --git a/forge-gui-mobile/src/forge/card/CardRenderer.java b/forge-gui-mobile/src/forge/card/CardRenderer.java index cc82751f248..c39781e0503 100644 --- a/forge-gui-mobile/src/forge/card/CardRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardRenderer.java @@ -15,6 +15,7 @@ import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; import com.badlogic.gdx.graphics.glutils.PixmapTextureData; import com.badlogic.gdx.utils.Array; +import forge.CachedCardImage; import forge.FThreads; import forge.Graphics; import forge.StaticData; @@ -28,6 +29,7 @@ import forge.game.card.CardView.CardStateView; import forge.game.keyword.Keyword; import forge.game.card.CounterType; import forge.item.IPaperCard; +import forge.item.InventoryItem; import forge.item.PaperCard; import forge.model.FModel; import forge.properties.ForgeConstants; @@ -51,6 +53,34 @@ public class CardRenderer { BehindVert } + // class that simplifies the callback logic of CachedCardImage + static class RendererCachedCardImage extends CachedCardImage { + boolean clearCardArtCache = false; + + public RendererCachedCardImage(CardView card, boolean clearArtCache) { + super(card); + this.clearCardArtCache = clearArtCache; + } + + public RendererCachedCardImage(InventoryItem ii, boolean clearArtCache) { + super(ii); + this.clearCardArtCache = clearArtCache; + } + + public RendererCachedCardImage(String key, boolean clearArtCache) { + super(key); + this.clearCardArtCache = clearArtCache; + } + + @Override + public void onImageFetched() { + ImageCache.clear(); + if (clearCardArtCache) { + cardArtCache.remove(key); + } + } + } + private static final FSkinFont NAME_FONT = FSkinFont.get(16); public static final float NAME_BOX_TINT = 0.2f; public static final float TEXT_BOX_TINT = 0.1f; @@ -124,7 +154,7 @@ public class CardRenderer { public static FImageComplex getCardArt(String imageKey, boolean isSplitCard, boolean isHorizontalCard, boolean isAftermathCard) { FImageComplex cardArt = cardArtCache.get(imageKey); if (cardArt == null) { - Texture image = ImageCache.getImage(imageKey, true); + Texture image = new RendererCachedCardImage(imageKey, true).getImage(); if (image != null) { if (image == ImageCache.defaultImage) { cardArt = CardImageRenderer.forgeArt; @@ -186,7 +216,13 @@ public class CardRenderer { public static FImageComplex getAftermathSecondCardArt(String imageKey) { FImageComplex cardArt = cardArtCache.get("Aftermath_second_"+imageKey); if (cardArt == null) { - Texture image = ImageCache.getImage(imageKey, true); + Texture image = new CachedCardImage(imageKey) { + @Override + public void onImageFetched() { + ImageCache.clear(); + cardArtCache.remove("Aftermath_second_" + imageKey); + } + }.getImage(); if (image != null) { if (image == ImageCache.defaultImage) { cardArt = CardImageRenderer.forgeArt; @@ -347,7 +383,8 @@ public class CardRenderer { } public static void drawCard(Graphics g, IPaperCard pc, float x, float y, float w, float h, CardStackPosition pos) { - Texture image = ImageCache.getImage(pc); + Texture image = new RendererCachedCardImage(pc, false).getImage(); + if (image != null) { if (image == ImageCache.defaultImage) { CardImageRenderer.drawCardImage(g, CardView.getCardForUi(pc), false, x, y, w, h, pos); @@ -369,7 +406,8 @@ public class CardRenderer { } public static void drawCard(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean rotate) { - Texture image = ImageCache.getImage(card); + Texture image = new RendererCachedCardImage(card, false).getImage();; + if (image != null) { if (image == ImageCache.defaultImage) { CardImageRenderer.drawCardImage(g, card, false, x, y, w, h, pos); diff --git a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java index 375e7d94b84..36c95917843 100644 --- a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java +++ b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java @@ -204,6 +204,10 @@ public class SettingsPage extends TabPage { "If turned on, Forge will load all historic format definitions, this may take slightly longer to load at startup."), 3); //Graphic Options + lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER, + "Download missing card art", + "Automatically download missing card art"), + 4); lstSettings.addItem(new BooleanSetting(FPref.UI_OVERLAY_FOIL_EFFECT, "Display Foil Overlay", "Displays foil cards with the visual foil overlay effect."), diff --git a/forge-gui-mobile/src/forge/util/LibGDXImageFetcher.java b/forge-gui-mobile/src/forge/util/LibGDXImageFetcher.java new file mode 100644 index 00000000000..6e8bc3f7ca5 --- /dev/null +++ b/forge-gui-mobile/src/forge/util/LibGDXImageFetcher.java @@ -0,0 +1,62 @@ +package forge.util; + +import com.badlogic.gdx.files.FileHandle; +import forge.Forge; +import forge.GuiBase; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +public class LibGDXImageFetcher extends ImageFetcher { + @Override + protected Runnable getDownloadTask(String[] downloadUrls, String destPath, Runnable notifyObservers) { + return new LibGDXDownloadTask(downloadUrls, destPath, notifyObservers); + } + + private static class LibGDXDownloadTask implements Runnable { + private final String[] downloadUrls; + private final String destPath; + private final Runnable notifyObservers; + + LibGDXDownloadTask(String[] downloadUrls, String destPath, Runnable notifyObservers) { + this.downloadUrls = downloadUrls; + this.destPath = destPath; + this.notifyObservers = notifyObservers; + } + + private void doFetch(String urlToDownload) throws IOException { + URL url = new URL(urlToDownload); + System.out.println("Attempting to fetch: " + url); + java.net.URLConnection c = url.openConnection(); + c.setRequestProperty("User-Agent", ""); + + InputStream is = c.getInputStream(); + // First, save to a temporary file so that nothing tries to read + // a partial download. + FileHandle destFile = new FileHandle(destPath + ".tmp"); + System.out.println(destPath); + destFile.parent().mkdirs(); + + // Conversion to JPEG will be handled differently depending on the platform + Forge.getDeviceAdapter().convertToJPEG(is, new FileOutputStream(destFile.file())); + destFile.moveTo(new FileHandle(destPath)); + + System.out.println("Saved image to " + destPath); + GuiBase.getInterface().invokeInEdtLater(notifyObservers); + } + + public void run() { + for (String urlToDownload : downloadUrls) { + try { + doFetch(urlToDownload); + break; + } catch (IOException e) { + System.out.println("Failed to download card [" + destPath + "] image: " + e.getMessage()); + } + } + } + } + +} diff --git a/forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java b/forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java index 41e00408465..06361f28bc7 100644 --- a/forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java +++ b/forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java @@ -1,5 +1,9 @@ package forge.interfaces; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + public interface IDeviceAdapter { boolean isConnectedToInternet(); boolean isConnectedToWifi(); @@ -10,4 +14,5 @@ public interface IDeviceAdapter { void preventSystemSleep(boolean preventSleep); void restart(); void exit(); + void convertToJPEG(InputStream input, OutputStream output) throws IOException; } diff --git a/forge-gui/src/main/java/forge/interfaces/IGuiBase.java b/forge-gui/src/main/java/forge/interfaces/IGuiBase.java index 15f23f41fe2..21802b2717d 100644 --- a/forge-gui/src/main/java/forge/interfaces/IGuiBase.java +++ b/forge-gui/src/main/java/forge/interfaces/IGuiBase.java @@ -16,12 +16,14 @@ import forge.match.HostedMatch; import forge.sound.IAudioClip; import forge.sound.IAudioMusic; import forge.util.Callback; +import forge.util.ImageFetcher; public interface IGuiBase { boolean isRunningOnDesktop(); boolean isLibgdxPort(); String getCurrentVersion(); String getAssetsDir(); + ImageFetcher getImageFetcher(); void invokeInEdtNow(Runnable runnable); void invokeInEdtLater(Runnable runnable); void invokeInEdtAndWait(Runnable proc); diff --git a/forge-gui-desktop/src/main/java/forge/ImageFetcher.java b/forge-gui/src/main/java/forge/util/ImageFetcher.java similarity index 67% rename from forge-gui-desktop/src/main/java/forge/ImageFetcher.java rename to forge-gui/src/main/java/forge/util/ImageFetcher.java index 6b0d878b721..9ab7a134387 100644 --- a/forge-gui-desktop/src/main/java/forge/ImageFetcher.java +++ b/forge-gui/src/main/java/forge/util/ImageFetcher.java @@ -1,34 +1,28 @@ -package forge; +package forge.util; -import java.awt.image.BufferedImage; import java.io.File; -import java.io.IOException; -import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import javax.imageio.ImageIO; -import javax.swing.SwingUtilities; - +import forge.FThreads; +import forge.ImageKeys; +import forge.StaticData; import org.apache.commons.lang3.tuple.Pair; -import forge.game.card.CardView; import forge.item.PaperCard; import forge.model.FModel; import forge.properties.ForgeConstants; import forge.properties.ForgePreferences; -import forge.util.FileUtil; -import forge.util.ImageUtil; -public class ImageFetcher { +public abstract class ImageFetcher { private static final ExecutorService threadPool = Executors.newCachedThreadPool(); - private static HashMap> currentFetches = new HashMap<>(); - private static HashMap tokenImages; + private HashMap> currentFetches = new HashMap<>(); + private HashMap tokenImages; - public static void fetchImage(final CardView card, final String imageKey, final Callback callback) { + public void fetchImage(final String imageKey, final Callback callback) { FThreads.assertExecutedByEdt(true); if (!FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER)) @@ -119,50 +113,12 @@ public class ImageFetcher { currentFetches.remove(destPath); } }; - threadPool.submit(new DownloadTask(downloadUrls.toArray(new String[0]), destPath, notifyObservers)); + threadPool.submit(getDownloadTask(downloadUrls.toArray(new String[0]), destPath, notifyObservers)); } - + + protected abstract Runnable getDownloadTask(String[] toArray, String destPath, Runnable notifyObservers); + public static interface Callback { public void onImageFetched(); } - - private static class DownloadTask implements Runnable { - private final String[] downloadUrls; - private final String destPath; - private final Runnable notifyObservers; - - public DownloadTask(String[] downloadUrls, String destPath, Runnable notifyObservers) { - this.downloadUrls = downloadUrls; - this.destPath = destPath; - this.notifyObservers = notifyObservers; - } - - private void doFetch(String urlToDownload) throws IOException { - URL url = new URL(urlToDownload); - System.out.println("Attempting to fetch: " + url); - java.net.URLConnection c = url.openConnection(); - c.setRequestProperty("User-Agent", ""); - BufferedImage image = ImageIO.read(c.getInputStream()); - // First, save to a temporary file so that nothing tries to read - // a partial download. - File destFile = new File(destPath + ".tmp"); - destFile.mkdirs(); - ImageIO.write(image, "jpg", destFile); - // Now, rename it to the correct name. - destFile.renameTo(new File(destPath)); - System.out.println("Saved image to " + destPath); - SwingUtilities.invokeLater(notifyObservers); - } - - public void run() { - for (String urlToDownload : downloadUrls) { - try { - doFetch(urlToDownload); - break; - } catch (IOException e) { - System.out.println("Failed to download card [" + destPath + "] image: " + e.getMessage()); - } - } - } - } - } \ No newline at end of file +} \ No newline at end of file