001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.mappaint.styleelement; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Graphics; 007import java.awt.Image; 008import java.awt.Rectangle; 009import java.awt.image.BufferedImage; 010import java.util.Objects; 011 012import javax.swing.ImageIcon; 013 014import org.openstreetmap.josm.Main; 015import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; 016import org.openstreetmap.josm.gui.mappaint.StyleSource; 017import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.BoxProvider; 018import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.BoxProviderResult; 019import org.openstreetmap.josm.gui.util.GuiHelper; 020import org.openstreetmap.josm.tools.ImageProvider; 021import org.openstreetmap.josm.tools.Utils; 022 023/** 024 * An image that will be displayed on the map. 025 */ 026public class MapImage { 027 028 private static final int MAX_SIZE = 48; 029 030 /** 031 * ImageIcon can change while the image is loading. 032 */ 033 private BufferedImage img; 034 035 public int alpha = 255; 036 public String name; 037 public StyleSource source; 038 public boolean autoRescale; 039 public int width = -1; 040 public int height = -1; 041 public int offsetX; 042 public int offsetY; 043 044 private boolean temporary; 045 private BufferedImage disabledImgCache; 046 047 public MapImage(String name, StyleSource source) { 048 this(name, source, true); 049 } 050 051 public MapImage(String name, StyleSource source, boolean autoRescale) { 052 this.name = name; 053 this.source = source; 054 this.autoRescale = autoRescale; 055 } 056 057 /** 058 * Get the image associated with this MapImage object. 059 * 060 * @param disabled {@code} true to request disabled version, {@code false} for the standard version 061 * @return the image 062 */ 063 public BufferedImage getImage(boolean disabled) { 064 if (disabled) { 065 return getDisabled(); 066 } else { 067 return getImage(); 068 } 069 } 070 071 private BufferedImage getDisabled() { 072 if (disabledImgCache != null) 073 return disabledImgCache; 074 if (img == null) 075 getImage(); // fix #7498 ? 076 Image disImg = GuiHelper.getDisabledImage(img); 077 if (disImg instanceof BufferedImage) { 078 disabledImgCache = (BufferedImage) disImg; 079 } else { 080 disabledImgCache = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); 081 Graphics g = disabledImgCache.getGraphics(); 082 g.drawImage(disImg, 0, 0, null); 083 g.dispose(); 084 } 085 return disabledImgCache; 086 } 087 088 private BufferedImage getImage() { 089 if (img != null) 090 return img; 091 temporary = false; 092 new ImageProvider(name) 093 .setDirs(MapPaintStyles.getIconSourceDirs(source)) 094 .setId("mappaint."+source.getPrefName()) 095 .setArchive(source.zipIcons) 096 .setInArchiveDir(source.getZipEntryDirName()) 097 .setWidth(width) 098 .setHeight(height) 099 .setOptional(true) 100 .getAsync().thenAccept(result -> { 101 synchronized (this) { 102 if (result == null) { 103 source.logWarning(tr("Failed to locate image ''{0}''", name)); 104 ImageIcon noIcon = MapPaintStyles.getNoIconIcon(source); 105 img = noIcon == null ? null : (BufferedImage) noIcon.getImage(); 106 } else { 107 img = (BufferedImage) rescale(result.getImage()); 108 } 109 if (temporary) { 110 disabledImgCache = null; 111 Main.map.mapView.preferenceChanged(null); // otherwise repaint is ignored, because layer hasn't changed 112 Main.map.mapView.repaint(); 113 } 114 temporary = false; 115 } 116 } 117 ); 118 synchronized (this) { 119 if (img == null) { 120 img = (BufferedImage) ImageProvider.get("clock").getImage(); 121 temporary = true; 122 } 123 } 124 return img; 125 } 126 127 public int getWidth() { 128 return getImage().getWidth(null); 129 } 130 131 public int getHeight() { 132 return getImage().getHeight(null); 133 } 134 135 public float getAlphaFloat() { 136 return Utils.colorInt2float(alpha); 137 } 138 139 /** 140 * Determines if image is not completely loaded and {@code getImage()} returns a temporary image. 141 * @return {@code true} if image is not completely loaded and getImage() returns a temporary image 142 */ 143 public boolean isTemporary() { 144 return temporary; 145 } 146 147 protected class MapImageBoxProvider implements BoxProvider { 148 @Override 149 public BoxProviderResult get() { 150 return new BoxProviderResult(box(), temporary); 151 } 152 153 private Rectangle box() { 154 int w = getWidth(), h = getHeight(); 155 if (mustRescale(getImage())) { 156 w = 16; 157 h = 16; 158 } 159 return new Rectangle(-w/2, -h/2, w, h); 160 } 161 162 private MapImage getParent() { 163 return MapImage.this; 164 } 165 166 @Override 167 public int hashCode() { 168 return MapImage.this.hashCode(); 169 } 170 171 @Override 172 public boolean equals(Object obj) { 173 if (!(obj instanceof BoxProvider)) 174 return false; 175 if (obj instanceof MapImageBoxProvider) { 176 MapImageBoxProvider other = (MapImageBoxProvider) obj; 177 return MapImage.this.equals(other.getParent()); 178 } else if (temporary) { 179 return false; 180 } else { 181 final BoxProvider other = (BoxProvider) obj; 182 BoxProviderResult resultOther = other.get(); 183 if (resultOther.isTemporary()) return false; 184 return box().equals(resultOther.getBox()); 185 } 186 } 187 } 188 189 public BoxProvider getBoxProvider() { 190 return new MapImageBoxProvider(); 191 } 192 193 /** 194 * Rescale excessively large images. 195 * @param image the unscaled image 196 * @return The scaled down version to 16x16 pixels if the image height and width exceeds 48 pixels and no size has been explicitely specified 197 */ 198 private Image rescale(Image image) { 199 if (image == null) return null; 200 // Scale down large (.svg) images to 16x16 pixels if no size is explicitely specified 201 if (mustRescale(image)) { 202 return ImageProvider.createBoundedImage(image, 16); 203 } else { 204 return image; 205 } 206 } 207 208 private boolean mustRescale(Image image) { 209 return autoRescale && width == -1 && image.getWidth(null) > MAX_SIZE 210 && height == -1 && image.getHeight(null) > MAX_SIZE; 211 } 212 213 @Override 214 public boolean equals(Object obj) { 215 if (this == obj) return true; 216 if (obj == null || getClass() != obj.getClass()) return false; 217 MapImage mapImage = (MapImage) obj; 218 return alpha == mapImage.alpha && 219 autoRescale == mapImage.autoRescale && 220 width == mapImage.width && 221 height == mapImage.height && 222 Objects.equals(name, mapImage.name) && 223 Objects.equals(source, mapImage.source); 224 } 225 226 @Override 227 public int hashCode() { 228 return Objects.hash(alpha, name, source, autoRescale, width, height); 229 } 230 231 @Override 232 public String toString() { 233 return name; 234 } 235}