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