package games.strategy.triplea.image; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Toolkit; import java.awt.image.BufferedImage; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Optional; import javax.swing.ImageIcon; import games.strategy.engine.data.GameData; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.UnitType; import games.strategy.triplea.Constants; import games.strategy.triplea.ResourceLoader; import games.strategy.triplea.attachments.UnitAttachment; import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.delegate.TechTracker; import games.strategy.ui.Util; public class UnitImageFactory { public static final int DEFAULT_UNIT_ICON_SIZE = 48; /** * Width of all icons. * You probably want getUnitImageWidth(), which takes scale factor into account. */ private static int UNIT_ICON_WIDTH = DEFAULT_UNIT_ICON_SIZE; /** * Height of all icons. * You probably want getUnitImageHeight(), which takes scale factor into account. **/ private static int UNIT_ICON_HEIGHT = DEFAULT_UNIT_ICON_SIZE; private static int UNIT_COUNTER_OFFSET_WIDTH = DEFAULT_UNIT_ICON_SIZE / 4; private static int UNIT_COUNTER_OFFSET_HEIGHT = UNIT_ICON_HEIGHT; private static final String FILE_NAME_BASE = "units/"; // maps Point -> image private final Map<String, Image> m_images = new HashMap<>(); // maps Point -> Icon private final Map<String, ImageIcon> m_icons = new HashMap<>(); // Scaling factor for unit images private double m_scaleFactor; private ResourceLoader m_resourceLoader; /** Creates new UnitImageFactory. */ public UnitImageFactory() {} public void setResourceLoader(final ResourceLoader loader, final double scaleFactor, final int initialUnitWidth, final int initialUnitHeight, final int initialUnitCounterOffsetWidth, final int initialUnitCounterOffsetHeight) { UNIT_ICON_WIDTH = initialUnitWidth; UNIT_ICON_HEIGHT = initialUnitHeight; UNIT_COUNTER_OFFSET_WIDTH = initialUnitCounterOffsetWidth; UNIT_COUNTER_OFFSET_HEIGHT = initialUnitCounterOffsetHeight; m_scaleFactor = scaleFactor; m_resourceLoader = loader; clearImageCache(); } /** * Set the unitScaling factor. */ public void setScaleFactor(final double scaleFactor) { if (m_scaleFactor != scaleFactor) { m_scaleFactor = scaleFactor; clearImageCache(); } } /** * Return the unit scaling factor. */ public double getScaleFactor() { return m_scaleFactor; } /** * Return the width of scaled units. */ public int getUnitImageWidth() { return (int) (m_scaleFactor * UNIT_ICON_WIDTH); } /** * Return the height of scaled units. */ public int getUnitImageHeight() { return (int) (m_scaleFactor * UNIT_ICON_HEIGHT); } public int getUnitCounterOffsetWidth() { return (int) (m_scaleFactor * UNIT_COUNTER_OFFSET_WIDTH); } public int getUnitCounterOffsetHeight() { return (int) (m_scaleFactor * UNIT_COUNTER_OFFSET_HEIGHT); } // Clear the image and icon cache private void clearImageCache() { m_images.clear(); m_icons.clear(); } /** * Return the appropriate unit image. */ public Optional<Image> getImage(final UnitType type, final PlayerID player, final GameData data, final boolean damaged, final boolean disabled) { final String baseName = getBaseImageName(type, player, damaged, disabled); final String fullName = baseName + player.getName(); if (m_images.containsKey(fullName)) { return Optional.of(m_images.get(fullName)); } final Optional<Image> image = getBaseImage(baseName, player); if (!image.isPresent()) { return Optional.empty(); } final Image baseImage = image.get(); // We want to scale units according to the given scale factor. // We use smooth scaling since the images are cached to allow // to take our time in doing the scaling. // Image observer is null, since the image should have been // guaranteed to be loaded. final int width = (int) (baseImage.getWidth(null) * m_scaleFactor); final int height = (int) (baseImage.getHeight(null) * m_scaleFactor); final Image scaledImage = baseImage.getScaledInstance(width, height, Image.SCALE_SMOOTH); // Ensure the scaling is completed. Util.ensureImageLoaded(scaledImage); m_images.put(fullName, scaledImage); return Optional.of(scaledImage); } public Optional<URL> getBaseImageURL(final String baseImageName, final PlayerID id) { return getBaseImageURL(baseImageName, id, m_resourceLoader); } private static Optional<URL> getBaseImageURL(final String baseImageName, final PlayerID id, final ResourceLoader resourceLoader) { // URL uses '/' not '\' final String fileName = FILE_NAME_BASE + id.getName() + "/" + baseImageName + ".png"; final URL url = resourceLoader.getResource(fileName); return Optional.ofNullable(url); } private Optional<Image> getBaseImage(final String baseImageName, final PlayerID id) { final Optional<URL> imageLocation = getBaseImageURL(baseImageName, id); Image image = null; if (imageLocation.isPresent()) { image = Toolkit.getDefaultToolkit().getImage(getBaseImageURL(baseImageName, id).get()); Util.ensureImageLoaded(image); } return Optional.ofNullable(image); } public Optional<Image> getHighlightImage(final UnitType type, final PlayerID player, final GameData data, final boolean damaged, final boolean disabled) { final Optional<Image> baseImage = getImage(type, player, data, damaged, disabled); if (!baseImage.isPresent()) { return Optional.empty(); } final Image base = baseImage.get(); final BufferedImage newImage = Util.createImage(base.getWidth(null), base.getHeight(null), true); // copy the real image final Graphics2D g = newImage.createGraphics(); g.drawImage(base, 0, 0, null); // we want a highlight only over the area // that is not clear g.setComposite(AlphaComposite.SrcIn); g.setColor(new Color(240, 240, 240, 127)); g.fillRect(0, 0, base.getWidth(null), base.getHeight(null)); g.dispose(); return Optional.of(newImage); } /** * Return a icon image for a unit. */ public Optional<ImageIcon> getIcon(final UnitType type, final PlayerID player, final GameData data, final boolean damaged, final boolean disabled) { final String baseName = getBaseImageName(type, player, damaged, disabled); final String fullName = baseName + player.getName(); if (m_icons.containsKey(fullName)) { return Optional.of(m_icons.get(fullName)); } final Optional<Image> image = getBaseImage(baseName, player); if (!image.isPresent()) { return Optional.empty(); } final ImageIcon icon = new ImageIcon(image.get()); m_icons.put(fullName, icon); return Optional.of(icon); } private static String getBaseImageName(final UnitType type, final PlayerID id, final boolean damaged, final boolean disabled) { StringBuilder name = new StringBuilder(32); name.append(type.getName()); if (!type.getName().endsWith("_hit") && !type.getName().endsWith("_disabled")) { if (type.getName().equals(Constants.UNIT_TYPE_AAGUN)) { if (TechTracker.hasRocket(id) && UnitAttachment.get(type).getIsRocket()) { name = new StringBuilder("rockets"); } if (TechTracker.hasAARadar(id) && Matches.UnitTypeIsAAforAnything.match(type)) { name.append("_r"); } } else if (UnitAttachment.get(type).getIsRocket() && Matches.UnitTypeIsAAforAnything.match(type)) { if (TechTracker.hasRocket(id)) { name.append("_rockets"); } if (TechTracker.hasAARadar(id)) { name.append("_r"); } } else if (UnitAttachment.get(type).getIsRocket()) { if (TechTracker.hasRocket(id)) { name.append("_rockets"); } } else if (Matches.UnitTypeIsAAforAnything.match(type)) { if (TechTracker.hasAARadar(id)) { name.append("_r"); } } if (UnitAttachment.get(type).getIsAir() && !UnitAttachment.get(type).getIsStrategicBomber()) { if (TechTracker.hasLongRangeAir(id)) { name.append("_lr"); } if (TechTracker.hasJetFighter(id) && (UnitAttachment.get(type).getAttack(id) > 0 || UnitAttachment.get(type).getDefense(id) > 0)) { name.append("_jp"); } } if (UnitAttachment.get(type).getIsAir() && UnitAttachment.get(type).getIsStrategicBomber()) { if (TechTracker.hasLongRangeAir(id)) { name.append("_lr"); } if (TechTracker.hasHeavyBomber(id)) { name.append("_hb"); } } if (UnitAttachment.get(type).getIsSub() && (UnitAttachment.get(type).getAttack(id) > 0 || UnitAttachment.get(type).getDefense(id) > 0)) { if (TechTracker.hasSuperSubs(id)) { name.append("_ss"); } if (TechTracker.hasRocket(id)) { // do nothing } } if (type.getName().equals(Constants.UNIT_TYPE_FACTORY) || UnitAttachment.get(type).getCanProduceUnits()) { if (TechTracker.hasIndustrialTechnology(id) || TechTracker.hasIncreasedFactoryProduction(id)) { name.append("_it"); } } } if (disabled) { name.append("_disabled"); } else if (damaged) { name.append("_hit"); } return name.toString(); } }