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