001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.imagery; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Color; 007import java.awt.Font; 008import java.awt.Graphics; 009import java.awt.Image; 010import java.awt.Transparency; 011import java.awt.image.BufferedImage; 012import java.io.IOException; 013import java.io.ObjectInputStream; 014import java.io.ObjectOutputStream; 015import java.io.Serializable; 016import java.lang.ref.SoftReference; 017 018import javax.imageio.ImageIO; 019 020import org.openstreetmap.josm.data.coor.EastNorth; 021import org.openstreetmap.josm.gui.NavigatableComponent; 022import org.openstreetmap.josm.gui.layer.ImageryLayer; 023import org.openstreetmap.josm.gui.layer.WMSLayer; 024 025public class GeorefImage implements Serializable { 026 private static final long serialVersionUID = 1L; 027 028 public enum State { IMAGE, NOT_IN_CACHE, FAILED, PARTLY_IN_CACHE} 029 030 private WMSLayer layer; 031 private State state; 032 033 private BufferedImage image; 034 private SoftReference<BufferedImage> reImg; 035 private int xIndex; 036 private int yIndex; 037 038 private static final Color transparentColor = new Color(0,0,0,0); 039 private Color fadeColor = transparentColor; 040 041 public EastNorth getMin() { 042 return layer.getEastNorth(xIndex, yIndex); 043 } 044 045 public EastNorth getMax() { 046 return layer.getEastNorth(xIndex+1, yIndex+1); 047 } 048 049 050 public GeorefImage(WMSLayer layer) { 051 this.layer = layer; 052 } 053 054 public void changePosition(int xIndex, int yIndex) { 055 if (!equalPosition(xIndex, yIndex)) { 056 this.xIndex = xIndex; 057 this.yIndex = yIndex; 058 this.image = null; 059 flushedResizedCachedInstance(); 060 } 061 } 062 063 public boolean equalPosition(int xIndex, int yIndex) { 064 return this.xIndex == xIndex && this.yIndex == yIndex; 065 } 066 067 public void changeImage(State state, BufferedImage image) { 068 flushedResizedCachedInstance(); 069 this.image = image; 070 this.state = state; 071 072 switch (state) { 073 case FAILED: 074 { 075 BufferedImage img = createImage(); 076 layer.drawErrorTile(img); 077 this.image = img; 078 break; 079 } 080 case NOT_IN_CACHE: 081 { 082 BufferedImage img = createImage(); 083 Graphics g = img.getGraphics(); 084 g.setColor(Color.GRAY); 085 g.fillRect(0, 0, img.getWidth(), img.getHeight()); 086 Font font = g.getFont(); 087 Font tempFont = font.deriveFont(Font.PLAIN).deriveFont(36.0f); 088 g.setFont(tempFont); 089 g.setColor(Color.BLACK); 090 String text = tr("Not in cache"); 091 g.drawString(text, (img.getWidth() - g.getFontMetrics().stringWidth(text)) / 2, img.getHeight()/2); 092 g.setFont(font); 093 this.image = img; 094 break; 095 } 096 default: 097 if (this.image != null) { 098 this.image = layer.sharpenImage(this.image); 099 } 100 break; 101 } 102 } 103 104 private BufferedImage createImage() { 105 return new BufferedImage(layer.getImageSize(), layer.getImageSize(), BufferedImage.TYPE_INT_RGB); 106 } 107 108 public boolean paint(Graphics g, NavigatableComponent nc, int xIndex, int yIndex, int leftEdge, int bottomEdge) { 109 if (image == null) 110 return false; 111 112 if(!(this.xIndex == xIndex && this.yIndex == yIndex)) 113 return false; 114 115 int left = layer.getImageX(xIndex); 116 int bottom = layer.getImageY(yIndex); 117 int width = layer.getImageWidth(xIndex); 118 int height = layer.getImageHeight(yIndex); 119 120 int x = left - leftEdge; 121 int y = nc.getHeight() - (bottom - bottomEdge) - height; 122 123 // This happens if you zoom outside the world 124 if(width == 0 || height == 0) 125 return false; 126 127 // TODO: implement per-layer fade color 128 Color newFadeColor; 129 if (ImageryLayer.PROP_FADE_AMOUNT.get() == 0) { 130 newFadeColor = transparentColor; 131 } else { 132 newFadeColor = ImageryLayer.getFadeColorWithAlpha(); 133 } 134 135 BufferedImage img = reImg == null?null:reImg.get(); 136 if(img != null && img.getWidth() == width && img.getHeight() == height && fadeColor.equals(newFadeColor)) { 137 g.drawImage(img, x, y, null); 138 return true; 139 } 140 141 fadeColor = newFadeColor; 142 143 boolean alphaChannel = WMSLayer.PROP_ALPHA_CHANNEL.get() && getImage().getTransparency() != Transparency.OPAQUE; 144 145 try { 146 if(img != null) { 147 img.flush(); 148 } 149 long freeMem = Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory(); 150 // Notice that this value can get negative due to integer overflows 151 152 int multipl = alphaChannel ? 4 : 3; 153 // This happens when requesting images while zoomed out and then zooming in 154 // Storing images this large in memory will certainly hang up JOSM. Luckily 155 // traditional rendering is as fast at these zoom levels, so it's no loss. 156 // Also prevent caching if we're out of memory soon 157 if(width > 2000 || height > 2000 || width*height*multipl > freeMem) { 158 fallbackDraw(g, getImage(), x, y, width, height, alphaChannel); 159 } else { 160 // We haven't got a saved resized copy, so resize and cache it 161 img = new BufferedImage(width, height, alphaChannel?BufferedImage.TYPE_INT_ARGB:BufferedImage.TYPE_3BYTE_BGR); 162 img.getGraphics().drawImage(getImage(), 163 0, 0, width, height, // dest 164 0, 0, getImage().getWidth(null), getImage().getHeight(null), // src 165 null); 166 if (!alphaChannel) { 167 drawFadeRect(img.getGraphics(), 0, 0, width, height); 168 } 169 img.getGraphics().dispose(); 170 g.drawImage(img, x, y, null); 171 reImg = new SoftReference<BufferedImage>(img); 172 } 173 } catch(Exception e) { 174 fallbackDraw(g, getImage(), x, y, width, height, alphaChannel); 175 } 176 return true; 177 } 178 179 private void fallbackDraw(Graphics g, Image img, int x, int y, int width, int height, boolean alphaChannel) { 180 flushedResizedCachedInstance(); 181 g.drawImage( 182 img, x, y, x + width, y + height, 183 0, 0, img.getWidth(null), img.getHeight(null), 184 null); 185 if (!alphaChannel) { //FIXME: fading for layers with alpha channel currently is not supported 186 drawFadeRect(g, x, y, width, height); 187 } 188 } 189 190 private void drawFadeRect(Graphics g, int x, int y, int width, int height) { 191 if (fadeColor != transparentColor) { 192 g.setColor(fadeColor); 193 g.fillRect(x, y, width, height); 194 } 195 } 196 197 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 198 state = (State) in.readObject(); 199 boolean hasImage = in.readBoolean(); 200 if (hasImage) { 201 image = (ImageIO.read(ImageIO.createImageInputStream(in))); 202 } else { 203 in.readObject(); // read null from input stream 204 image = null; 205 } 206 } 207 208 private void writeObject(ObjectOutputStream out) throws IOException { 209 out.writeObject(state); 210 if(getImage() == null) { 211 out.writeBoolean(false); 212 out.writeObject(null); 213 } else { 214 out.writeBoolean(true); 215 ImageIO.write(getImage(), "png", ImageIO.createImageOutputStream(out)); 216 } 217 } 218 219 public void flushedResizedCachedInstance() { 220 if (reImg != null) { 221 BufferedImage img = reImg.get(); 222 if (img != null) { 223 img.flush(); 224 } 225 } 226 reImg = null; 227 } 228 229 230 public BufferedImage getImage() { 231 return image; 232 } 233 234 public State getState() { 235 return state; 236 } 237 238 public int getXIndex() { 239 return xIndex; 240 } 241 242 public int getYIndex() { 243 return yIndex; 244 } 245 246 public void setLayer(WMSLayer layer) { 247 this.layer = layer; 248 } 249}