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 }