package games.strategy.triplea.image;
import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.imageio.ImageIO;
import games.strategy.debug.ClientLogger;
import games.strategy.triplea.ResourceLoader;
import games.strategy.triplea.image.BlendComposite.BlendingMode;
import games.strategy.triplea.util.Stopwatch;
import games.strategy.ui.Util;
public final class TileImageFactory {
private final Object m_mutex = new Object();
// one instance in the application
private static final String SHOW_RELIEF_IMAGES_PREFERENCE = "ShowRelief2";
private static boolean s_showReliefImages = true;
private static final String SHOW_MAP_BLENDS_PREFERENCE = "ShowBlends";
private static boolean s_showMapBlends = false;
private static final String SHOW_MAP_BLEND_MODE = "BlendMode";
private static String s_showMapBlendMode = "normal";
private static final String SHOW_MAP_BLEND_ALPHA = "BlendAlpha";
private static float s_showMapBlendAlpha = 1.0f;
private final Composite composite = AlphaComposite.Src;
private static GraphicsConfiguration configuration =
GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
private static final Logger s_logger = Logger.getLogger(TileImageFactory.class.getName());
private double m_scale = 1;
// maps image name to ImageRef
private HashMap<String, ImageRef> m_imageCache = new HashMap<>();
static {
final Preferences prefs = Preferences.userNodeForPackage(TileImageFactory.class);
s_showReliefImages = prefs.getBoolean(SHOW_RELIEF_IMAGES_PREFERENCE, true);
s_showMapBlends = prefs.getBoolean(SHOW_MAP_BLENDS_PREFERENCE, false);
s_showMapBlendMode = prefs.get(SHOW_MAP_BLEND_MODE, "normal");
s_showMapBlendAlpha = prefs.getFloat(SHOW_MAP_BLEND_ALPHA, 1.0f);
}
public static boolean getShowReliefImages() {
return s_showReliefImages;
}
public static boolean getShowMapBlends() {
return s_showMapBlends;
}
private static String getShowMapBlendMode() {
return s_showMapBlendMode.toUpperCase();
}
private static float getShowMapBlendAlpha() {
return s_showMapBlendAlpha;
}
public void setScale(final double newScale) {
if (newScale > 1) {
throw new IllegalArgumentException("Wrong scale");
}
synchronized (m_mutex) {
m_scale = newScale;
getM_imageCache().clear();
}
}
public static void setShowReliefImages(final boolean aBool) {
s_showReliefImages = aBool;
final Preferences prefs = Preferences.userNodeForPackage(TileImageFactory.class);
prefs.putBoolean(SHOW_RELIEF_IMAGES_PREFERENCE, s_showReliefImages);
try {
prefs.flush();
} catch (final BackingStoreException ex) {
ClientLogger.logQuietly("Failed to save value: " + aBool, ex);
}
}
public static void setShowMapBlends(final boolean aBool) {
s_showMapBlends = aBool;
final Preferences prefs = Preferences.userNodeForPackage(TileImageFactory.class);
prefs.putBoolean(SHOW_MAP_BLENDS_PREFERENCE, s_showMapBlends);
try {
prefs.flush();
} catch (final BackingStoreException ex) {
ClientLogger.logQuietly("faild to save value: " + aBool, ex);
}
}
public static void setShowMapBlendMode(final String aString) {
s_showMapBlendMode = aString;
final Preferences prefs = Preferences.userNodeForPackage(TileImageFactory.class);
prefs.put(SHOW_MAP_BLEND_MODE, s_showMapBlendMode);
try {
prefs.flush();
} catch (final BackingStoreException ex) {
ClientLogger.logQuietly("faild to save value: " + aString, ex);
}
}
public static void setShowMapBlendAlpha(final float aFloat) {
s_showMapBlendAlpha = aFloat;
final Preferences prefs = Preferences.userNodeForPackage(TileImageFactory.class);
prefs.putFloat(SHOW_MAP_BLEND_ALPHA, s_showMapBlendAlpha);
try {
prefs.flush();
} catch (final BackingStoreException ex) {
ClientLogger.logQuietly("faild to save value: " + aFloat, ex);
}
}
private ResourceLoader m_resourceLoader;
public void setMapDir(final ResourceLoader loader) {
m_resourceLoader = loader;
synchronized (m_mutex) {
// we manually want to clear each ref to allow the soft reference to
// be removed
final Iterator<ImageRef> values = getM_imageCache().values().iterator();
while (values.hasNext()) {
final ImageRef imageRef = values.next();
imageRef.clear();
}
getM_imageCache().clear();
}
}
public TileImageFactory() {}
private Image isImageLoaded(final String fileName) {
if (getM_imageCache().get(fileName) == null) {
return null;
}
return getM_imageCache().get(fileName).getImage();
}
public Image getBaseTile(final int x, final int y) {
final String fileName = getBaseTileImageName(x, y);
if (m_resourceLoader.getResource(fileName) == null) {
return null;
}
return getImage(fileName, false);
}
public Image getUnscaledUncachedBaseTile(final int x, final int y) {
final String fileName = getBaseTileImageName(x, y);
final URL url = m_resourceLoader.getResource(fileName);
if (url == null) {
return null;
}
return loadImage(url, fileName, false, false, false);
}
private static String getBaseTileImageName(final int x, final int y) {
// we are loading with a class loader now, use /
final String fileName = "baseTiles" + "/" + x + "_" + y + ".png";
return fileName;
}
private Image getImage(final String fileName, final boolean transparent) {
synchronized (m_mutex) {
final Image rVal = isImageLoaded(fileName);
if (rVal != null) {
return rVal;
}
// This is null if there is no image
final URL url = m_resourceLoader.getResource(fileName);
if ((!s_showMapBlends || !s_showReliefImages || !transparent) && url == null) {
return null;
}
loadImage(url, fileName, transparent, true, true);
}
return getImage(fileName, transparent);
}
public Image getReliefTile(final int a, final int b) {
final String fileName = getReliefTileImageName(a, b);
return getImage(fileName, true);
}
public Image getUnscaledUncachedReliefTile(final int x, final int y) {
final String fileName = getReliefTileImageName(x, y);
final URL url = m_resourceLoader.getResource(fileName);
if (url == null) {
return null;
}
return loadImage(url, fileName, true, false, false);
}
private static String getReliefTileImageName(final int x, final int y) {
// we are loading with a class loader now, use /
final String fileName = "reliefTiles" + "/" + x + "_" + y + ".png";
return fileName;
}
/**
* @return compatibleImage This method produces a blank white tile for use in blending.
*/
private static BufferedImage makeMissingBaseTile(final BufferedImage input) {
final BufferedImage compatibleImage =
configuration.createCompatibleImage(input.getWidth(null), input.getHeight(null), Transparency.TRANSLUCENT);
final Graphics2D g2 = compatibleImage.createGraphics();
g2.fillRect(0, 0, input.getWidth(null), input.getHeight(null));
g2.drawImage(compatibleImage, 0, 0, null);
g2.dispose();
return compatibleImage;
}
private Image loadImage(final URL imageLocation, final String fileName, final boolean transparent,
final boolean cache, final boolean scale) {
if (s_showMapBlends && s_showReliefImages && transparent) {
return loadBlendedImage(fileName, cache, scale);
} else {
return loadUnblendedImage(imageLocation, fileName, transparent, cache, scale);
}
}
private Image loadBlendedImage(final String fileName, final boolean cache, final boolean scale) {
BufferedImage reliefFile = null;
BufferedImage baseFile = null;
// The relief tile
final String reliefFileName = fileName.replace("baseTiles", "reliefTiles");
final URL urlrelief = m_resourceLoader.getResource(reliefFileName);
// The base tile
final String baseFileName = fileName.replace("reliefTiles", "baseTiles");
final URL urlBase = m_resourceLoader.getResource(baseFileName);
// blank relief tile
final String blankReliefFileName = "reliefTiles/blank_relief.png";
final URL urlBlankRelief = m_resourceLoader.getResource(blankReliefFileName);
// Get buffered images
try {
final Stopwatch loadingImages =
new Stopwatch(s_logger, Level.FINE, "Loading images:" + urlrelief + " and " + urlBase);
if (urlrelief != null) {
reliefFile = loadCompatibleImage(urlrelief);
}
if (urlBase != null) {
baseFile = loadCompatibleImage(urlBase);
}
loadingImages.done();
} catch (final IOException e) {
ClientLogger.logQuietly(e);
}
// This does the blend
final float alpha = getShowMapBlendAlpha();
final int overX = 0;
final int overY = 0;
if (reliefFile == null) {
try {
reliefFile = loadCompatibleImage(urlBlankRelief);
} catch (final IOException e) {
ClientLogger.logQuietly(e);
}
}
// This fixes the blank land territories
if (baseFile == null) {
baseFile = makeMissingBaseTile(reliefFile);
}
/* reversing the to/from files leaves white underlays visible */
if (reliefFile != null) {
final Graphics2D g2 = reliefFile.createGraphics();
if (scale && m_scale != 1.0) {
final AffineTransform transform = new AffineTransform();
transform.scale(m_scale, m_scale);
g2.setTransform(transform);
}
g2.drawImage(reliefFile, overX, overY, null);
// gets the blending mode from the map.properties file (sometimes)
final BlendingMode blendMode = BlendComposite.BlendingMode.valueOf(getShowMapBlendMode());
final BlendComposite blendComposite = BlendComposite.getInstance(blendMode).derive(alpha);
// g2.setComposite(BlendComposite.Overlay.derive(alpha));
g2.setComposite(blendComposite);
g2.drawImage(baseFile, overX, overY, null);
final ImageRef ref = new ImageRef(reliefFile);
if (cache) {
getM_imageCache().put(fileName, ref);
}
return reliefFile;
} else {
final ImageRef ref = new ImageRef(baseFile);
if (cache) {
getM_imageCache().put(fileName, ref);
}
return baseFile;
}
}
private Image loadUnblendedImage(final URL imageLocation, final String fileName, final boolean transparent,
final boolean cache, final boolean scale) {
Image image;
try {
final Stopwatch loadingImages = new Stopwatch(s_logger, Level.FINE, "Loading image:" + imageLocation);
final BufferedImage fromFile = ImageIO.read(imageLocation);
loadingImages.done();
final Stopwatch copyingImage = new Stopwatch(s_logger, Level.FINE, "Copying image:" + imageLocation);
// if we dont copy, drawing the tile to the screen takes significantly longer
// has something to do with the colour model and type of the images
// some images can be copeid quickly to the screen
// this step is a significant bottle neck in the image drawing process
// we should try to find a way to avoid it, and load the
// png directly as the right type
image = Util.createImage(fromFile.getWidth(null), fromFile.getHeight(null), transparent);
final Graphics2D g = (Graphics2D) image.getGraphics();
if (scale && m_scale != 1.0) {
final AffineTransform transform = new AffineTransform();
transform.scale(m_scale, m_scale);
g.setTransform(transform);
}
g.drawImage(fromFile, 0, 0, null);
g.dispose();
fromFile.flush();
copyingImage.done();
} catch (final IOException e) {
ClientLogger.logError("Could not load image, url: " + imageLocation.toString(), e);
image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
}
final ImageRef ref = new ImageRef(image);
if (cache) {
getM_imageCache().put(fileName, ref);
}
return image;
}
public Composite getComposite() {
return this.composite;
}
private static BufferedImage loadCompatibleImage(final URL resource) throws IOException {
final BufferedImage image = ImageIO.read(resource);
return toCompatibleImage(image);
}
private static BufferedImage toCompatibleImage(final BufferedImage image) {
final BufferedImage compatibleImage =
configuration.createCompatibleImage(image.getWidth(), image.getHeight(), Transparency.TRANSLUCENT);
final Graphics g = compatibleImage.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return compatibleImage;
}
public static BufferedImage createCompatibleImage(final int width, final int height) {
return configuration.createCompatibleImage(width, height);
}
public void setM_imageCache(final HashMap<String, ImageRef> m_imageCache) {
this.m_imageCache = m_imageCache;
}
public HashMap<String, ImageRef> getM_imageCache() {
return m_imageCache;
}
}