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