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