// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.mappaint.styleelement; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Graphics; import java.awt.Image; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.util.Objects; import javax.swing.ImageIcon; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; import org.openstreetmap.josm.gui.mappaint.StyleSource; import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.BoxProvider; import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.BoxProviderResult; import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Utils; /** * An image that will be displayed on the map. */ public class MapImage { private static final int MAX_SIZE = 48; /** * ImageIcon can change while the image is loading. */ private BufferedImage img; public int alpha = 255; public String name; public StyleSource source; public boolean autoRescale; public int width = -1; public int height = -1; public int offsetX; public int offsetY; private boolean temporary; private BufferedImage disabledImgCache; public MapImage(String name, StyleSource source) { this(name, source, true); } public MapImage(String name, StyleSource source, boolean autoRescale) { this.name = name; this.source = source; this.autoRescale = autoRescale; } /** * Get the image associated with this MapImage object. * * @param disabled {@code} true to request disabled version, {@code false} for the standard version * @return the image */ public BufferedImage getImage(boolean disabled) { if (disabled) { return getDisabled(); } else { return getImage(); } } private BufferedImage getDisabled() { if (disabledImgCache != null) return disabledImgCache; if (img == null) getImage(); // fix #7498 ? Image disImg = GuiHelper.getDisabledImage(img); if (disImg instanceof BufferedImage) { disabledImgCache = (BufferedImage) disImg; } else { disabledImgCache = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics g = disabledImgCache.getGraphics(); g.drawImage(disImg, 0, 0, null); g.dispose(); } return disabledImgCache; } private BufferedImage getImage() { if (img != null) return img; temporary = false; new ImageProvider(name) .setDirs(MapPaintStyles.getIconSourceDirs(source)) .setId("mappaint."+source.getPrefName()) .setArchive(source.zipIcons) .setInArchiveDir(source.getZipEntryDirName()) .setWidth(width) .setHeight(height) .setOptional(true) .getAsync().thenAccept(result -> { synchronized (this) { if (result == null) { source.logWarning(tr("Failed to locate image ''{0}''", name)); ImageIcon noIcon = MapPaintStyles.getNoIconIcon(source); img = noIcon == null ? null : (BufferedImage) noIcon.getImage(); } else { img = (BufferedImage) rescale(result.getImage()); } if (temporary) { disabledImgCache = null; Main.map.mapView.preferenceChanged(null); // otherwise repaint is ignored, because layer hasn't changed Main.map.mapView.repaint(); } temporary = false; } } ); synchronized (this) { if (img == null) { img = (BufferedImage) ImageProvider.get("clock").getImage(); temporary = true; } } return img; } public int getWidth() { return getImage().getWidth(null); } public int getHeight() { return getImage().getHeight(null); } public float getAlphaFloat() { return Utils.colorInt2float(alpha); } /** * Determines if image is not completely loaded and {@code getImage()} returns a temporary image. * @return {@code true} if image is not completely loaded and getImage() returns a temporary image */ public boolean isTemporary() { return temporary; } protected class MapImageBoxProvider implements BoxProvider { @Override public BoxProviderResult get() { return new BoxProviderResult(box(), temporary); } private Rectangle box() { int w = getWidth(), h = getHeight(); if (mustRescale(getImage())) { w = 16; h = 16; } return new Rectangle(-w/2, -h/2, w, h); } private MapImage getParent() { return MapImage.this; } @Override public int hashCode() { return MapImage.this.hashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof BoxProvider)) return false; if (obj instanceof MapImageBoxProvider) { MapImageBoxProvider other = (MapImageBoxProvider) obj; return MapImage.this.equals(other.getParent()); } else if (temporary) { return false; } else { final BoxProvider other = (BoxProvider) obj; BoxProviderResult resultOther = other.get(); if (resultOther.isTemporary()) return false; return box().equals(resultOther.getBox()); } } } public BoxProvider getBoxProvider() { return new MapImageBoxProvider(); } /** * Rescale excessively large images. * @param image the unscaled image * @return The scaled down version to 16x16 pixels if the image height and width exceeds 48 pixels and no size has been explicitely specified */ private Image rescale(Image image) { if (image == null) return null; // Scale down large (.svg) images to 16x16 pixels if no size is explicitely specified if (mustRescale(image)) { return ImageProvider.createBoundedImage(image, 16); } else { return image; } } private boolean mustRescale(Image image) { return autoRescale && width == -1 && image.getWidth(null) > MAX_SIZE && height == -1 && image.getHeight(null) > MAX_SIZE; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; MapImage mapImage = (MapImage) obj; return alpha == mapImage.alpha && autoRescale == mapImage.autoRescale && width == mapImage.width && height == mapImage.height && Objects.equals(name, mapImage.name) && Objects.equals(source, mapImage.source); } @Override public int hashCode() { return Objects.hash(alpha, name, source, autoRescale, width, height); } @Override public String toString() { return name; } }