001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.data.imagery; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.Color; 007 import java.awt.Font; 008 import java.awt.Graphics; 009 import java.awt.Image; 010 import java.awt.Transparency; 011 import java.awt.image.BufferedImage; 012 import java.io.IOException; 013 import java.io.ObjectInputStream; 014 import java.io.ObjectOutputStream; 015 import java.io.Serializable; 016 import java.lang.ref.SoftReference; 017 018 import javax.imageio.ImageIO; 019 020 import org.openstreetmap.josm.data.coor.EastNorth; 021 import org.openstreetmap.josm.gui.NavigatableComponent; 022 import org.openstreetmap.josm.gui.layer.ImageryLayer; 023 import org.openstreetmap.josm.gui.layer.WMSLayer; 024 025 public 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 //System.out.println("Free Memory: "+ (freeMem/1024/1024) +" MB"); 151 // Notice that this value can get negative due to integer overflows 152 //System.out.println("Img Size: "+ (width*height*3/1024/1024) +" MB"); 153 154 int multipl = alphaChannel ? 4 : 3; 155 // This happens when requesting images while zoomed out and then zooming in 156 // Storing images this large in memory will certainly hang up JOSM. Luckily 157 // traditional rendering is as fast at these zoom levels, so it's no loss. 158 // Also prevent caching if we're out of memory soon 159 if(width > 2000 || height > 2000 || width*height*multipl > freeMem) { 160 fallbackDraw(g, getImage(), x, y, width, height, alphaChannel); 161 } else { 162 // We haven't got a saved resized copy, so resize and cache it 163 img = new BufferedImage(width, height, alphaChannel?BufferedImage.TYPE_INT_ARGB:BufferedImage.TYPE_3BYTE_BGR); 164 img.getGraphics().drawImage(getImage(), 165 0, 0, width, height, // dest 166 0, 0, getImage().getWidth(null), getImage().getHeight(null), // src 167 null); 168 if (!alphaChannel) { 169 drawFadeRect(img.getGraphics(), 0, 0, width, height); 170 } 171 img.getGraphics().dispose(); 172 g.drawImage(img, x, y, null); 173 reImg = new SoftReference<BufferedImage>(img); 174 } 175 } catch(Exception e) { 176 fallbackDraw(g, getImage(), x, y, width, height, alphaChannel); 177 } 178 return true; 179 } 180 181 private void fallbackDraw(Graphics g, Image img, int x, int y, int width, int height, boolean alphaChannel) { 182 flushedResizedCachedInstance(); 183 g.drawImage( 184 img, x, y, x + width, y + height, 185 0, 0, img.getWidth(null), img.getHeight(null), 186 null); 187 if (!alphaChannel) { //FIXME: fading for layers with alpha channel currently is not supported 188 drawFadeRect(g, x, y, width, height); 189 } 190 } 191 192 private void drawFadeRect(Graphics g, int x, int y, int width, int height) { 193 if (fadeColor != transparentColor) { 194 g.setColor(fadeColor); 195 g.fillRect(x, y, width, height); 196 } 197 } 198 199 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 200 state = (State) in.readObject(); 201 boolean hasImage = in.readBoolean(); 202 if (hasImage) { 203 image = (ImageIO.read(ImageIO.createImageInputStream(in))); 204 } else { 205 in.readObject(); // read null from input stream 206 image = null; 207 } 208 } 209 210 private void writeObject(ObjectOutputStream out) throws IOException { 211 out.writeObject(state); 212 if(getImage() == null) { 213 out.writeBoolean(false); 214 out.writeObject(null); 215 } else { 216 out.writeBoolean(true); 217 ImageIO.write(getImage(), "png", ImageIO.createImageOutputStream(out)); 218 } 219 } 220 221 public void flushedResizedCachedInstance() { 222 if (reImg != null) { 223 BufferedImage img = reImg.get(); 224 if (img != null) { 225 img.flush(); 226 } 227 } 228 reImg = null; 229 } 230 231 232 public BufferedImage getImage() { 233 return image; 234 } 235 236 public State getState() { 237 return state; 238 } 239 240 public int getXIndex() { 241 return xIndex; 242 } 243 244 public int getYIndex() { 245 return yIndex; 246 } 247 248 public void setLayer(WMSLayer layer) { 249 this.layer = layer; 250 } 251 }