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