source: josm/trunk/src/org/openstreetmap/josm/tools/ImageProvider.java@ 4255

Last change on this file since 4255 was 4255, checked in by bastiK, 13 years ago

minor refactoring

  • Property svn:eol-style set to native
File size: 15.3 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.Cursor;
8import java.awt.Graphics;
9import java.awt.Graphics2D;
10import java.awt.GraphicsConfiguration;
11import java.awt.GraphicsEnvironment;
12import java.awt.Image;
13import java.awt.Point;
14import java.awt.RenderingHints;
15import java.awt.Toolkit;
16import java.awt.Transparency;
17import java.awt.image.BufferedImage;
18import java.io.File;
19import java.io.IOException;
20import java.io.InputStream;
21import java.net.MalformedURLException;
22import java.net.URL;
23import java.util.Arrays;
24import java.util.Collection;
25import java.util.HashMap;
26import java.util.Map;
27import java.util.zip.ZipEntry;
28import java.util.zip.ZipFile;
29
30import javax.swing.Icon;
31import javax.swing.ImageIcon;
32
33import org.openstreetmap.josm.Main;
34import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
35import org.openstreetmap.josm.io.MirroredInputStream;
36import org.openstreetmap.josm.plugins.PluginHandler;
37
38/**
39 * Helperclass to support the application with images.
40 * @author imi
41 */
42public class ImageProvider {
43
44 /**
45 * Position of an overlay icon
46 * @author imi
47 */
48 public static enum OverlayPosition {
49 NORTHWEST, NORTHEAST, SOUTHWEST, SOUTHEAST
50 }
51
52 /**
53 * remember whether the image has been sanitized
54 */
55 private static class ImageWrapper {
56 Image img;
57 boolean sanitized;
58
59 public ImageWrapper(Image img, boolean sanitized) {
60 this.img = img;
61 this.sanitized = sanitized;
62 }
63 }
64
65 /**
66 * The icon cache
67 */
68 private static Map<String, ImageWrapper> cache = new HashMap<String, ImageWrapper>();
69
70 /**
71 * Return an image from the specified location.
72 *
73 * @param subdir The position of the directory, e.g. "layer"
74 * @param name The icons name (without the ending of ".png")
75 * @return The requested Image.
76 */
77 public static ImageIcon get(String subdir, String name) {
78 ImageIcon icon = getIfAvailable(subdir, name);
79 if (icon == null) {
80 String ext = name.indexOf('.') != -1 ? "" : ".png";
81 throw new NullPointerException(tr(
82 "Fatal: failed to locate image ''{0}''. This is a serious configuration problem. JOSM will stop working.",
83 name+ext));
84 }
85 return icon;
86 }
87
88 public static ImageIcon getIfAvailable(String subdir, String name) {
89 return getIfAvailable((Collection<String>) null, null, subdir, name);
90 }
91
92 public static ImageIcon getIfAvailable(String[] dirs, String id, String subdir, String name) {
93 return getIfAvailable(Arrays.asList(dirs), id, subdir, name);
94 }
95
96 /**
97 * Like {@link #get(String)}, but does not throw and return <code>null</code> in case of nothing
98 * is found. Use this, if the image to retrieve is optional. Nevertheless a warning will
99 * be printed on the console if the image could not be found.
100 */
101 public static ImageIcon getIfAvailable(Collection<String> dirs, String id, String subdir, String name) {
102 return getIfAvailable(dirs, id, subdir, name, null);
103 }
104
105 public static ImageIcon getIfAvailable(Collection<String> dirs, String id, String subdir, String name, File archive) {
106 return getIfAvailable(dirs, id, subdir, name, archive, false);
107 }
108
109 /**
110 * The full path of the image is either a url (starting with http://)
111 * or something like
112 * dirs.get(i)+"/"+subdir+"/"+name+".png".
113 * @param dirs Directories to look.
114 * @param id An id used for caching. Id is not used for cache if name starts with http://. (URL is unique anyway.)
115 * @param subdir Subdirectory the image lies in.
116 * @param name The name of the image. If it contains no '.', a png extension is added.
117 * @param archive A zip file where the image is located.
118 * @param sanitize If the image should be repainted to a new BufferedImage to work
119 * around certain issues.
120 */
121 public static ImageIcon getIfAvailable(Collection<String> dirs, String id, String subdir, String name, File archive, boolean sanitize) {
122 ImageWrapper iw = getIfAvailableImpl(dirs, id, subdir, name, archive);
123 if (iw == null)
124 return null;
125 if (sanitize && !iw.sanitized) {
126 iw.img = sanitize(iw.img);
127 iw.sanitized = true;
128 }
129 return new ImageIcon(iw.img);
130 }
131
132 private static ImageWrapper getIfAvailableImpl(Collection<String> dirs, String id, String subdir, String name, File archive) {
133 if (name == null)
134 return null;
135 if (name.startsWith("http://")) {
136 return getIfAvailableHttp(name);
137 }
138 if (subdir == null) {
139 subdir = "";
140 } else if (!subdir.equals("")) {
141 subdir += "/";
142 }
143 String ext = name.indexOf('.') != -1 ? "" : ".png";
144 String full_name = subdir + name + ext;
145 String cache_name = full_name;
146 /* cache separately */
147 if (dirs != null && dirs.size() > 0) {
148 cache_name = "id:" + id + ":" + full_name;
149 if(archive != null) {
150 cache_name += ":" + archive.getName();
151 }
152 }
153
154 ImageWrapper iw = cache.get(cache_name);
155 if (iw == null) {
156 if (archive != null) {
157 iw = getIfAvailableZip(full_name, archive);
158 }
159 // getImageUrl() does a ton of "stat()" calls and gets expensive
160 // and redundant when you have a whole ton of objects. So,
161 // index the cache by the name of the icon we're looking for
162 // and don't bother to create a URL unless we're actually
163 // creating the image.
164 if (iw == null) {
165 URL path = getImageUrl(full_name, dirs);
166 if (path == null)
167 return null;
168 Image img = Toolkit.getDefaultToolkit().createImage(path);
169 iw = new ImageWrapper(img, false);
170 }
171 cache.put(cache_name, iw);
172 }
173
174 return iw;
175 }
176
177 private static ImageWrapper getIfAvailableHttp(String url) {
178 ImageWrapper iw = cache.get(url);
179 if (iw == null) {
180 try {
181 MirroredInputStream is = new MirroredInputStream(url, new File(Main.pref.getPreferencesDir(),
182 "images").toString());
183 Image img = Toolkit.getDefaultToolkit().createImage(is.getFile().toURI().toURL());
184 iw = new ImageWrapper(img, false);
185 cache.put(url, iw);
186 } catch (IOException e) {
187 }
188 }
189 return iw;
190 }
191
192 private static ImageWrapper getIfAvailableZip(String full_name, File archive) {
193 ZipFile zipFile = null;
194 Image img = null;
195 try
196 {
197 zipFile = new ZipFile(archive);
198 ZipEntry entry = zipFile.getEntry(full_name);
199 if(entry != null)
200 {
201 int size = (int)entry.getSize();
202 int offs = 0;
203 byte[] buf = new byte[size];
204 InputStream is = null;
205 try {
206 is = zipFile.getInputStream(entry);
207 while(size > 0)
208 {
209 int l = is.read(buf, offs, size);
210 offs += l;
211 size -= l;
212 }
213 img = Toolkit.getDefaultToolkit().createImage(buf);
214 } finally {
215 if (is != null) {
216 is.close();
217 }
218 }
219 }
220 } catch (Exception e) {
221 System.err.println(tr("Warning: failed to handle zip file ''{0}''. Exception was: {1}", archive.getName(), e.toString()));
222 } finally {
223 if (zipFile != null) {
224 try {
225 zipFile.close();
226 } catch (IOException ex) {
227 }
228 }
229 }
230 return img == null ? null : new ImageWrapper(img, false);
231 }
232
233 private static URL getImageUrl(String path, String name) {
234 if (path.startsWith("resource://")) {
235 String p = path.substring("resource://".length());
236 for (ClassLoader source : PluginHandler.getResourceClassLoaders()) {
237 URL res;
238 if ((res = source.getResource(p + name)) != null)
239 return res;
240 }
241 } else {
242 try {
243 File f = new File(path, name);
244 if (f.exists())
245 return f.toURI().toURL();
246 } catch (MalformedURLException e) {
247 }
248 }
249 return null;
250 }
251
252 private static URL getImageUrl(String imageName, Collection<String> dirs) {
253 URL u = null;
254
255 // Try passed directories first
256 if (dirs != null) {
257 for (String name : dirs) {
258 try {
259 u = getImageUrl(name, imageName);
260 if (u != null)
261 return u;
262 } catch (SecurityException e) {
263 System.out.println(tr(
264 "Warning: failed to access directory ''{0}'' for security reasons. Exception was: {1}",
265 name, e.toString()));
266 }
267
268 }
269 }
270 // Try user-preference directory
271 String dir = Main.pref.getPreferencesDir() + "images";
272 try {
273 u = getImageUrl(dir, imageName);
274 if (u != null)
275 return u;
276 } catch (SecurityException e) {
277 System.out.println(tr(
278 "Warning: failed to access directory ''{0}'' for security reasons. Exception was: {1}", dir, e
279 .toString()));
280 }
281
282 // Try plugins and josm classloader
283 u = getImageUrl("resource://images/", imageName);
284 if (u != null)
285 return u;
286
287 // Try all other resource directories
288 for (String location : Main.pref.getAllPossiblePreferenceDirs()) {
289 u = getImageUrl(location + "images", imageName);
290 if (u != null)
291 return u;
292 u = getImageUrl(location, imageName);
293 if (u != null)
294 return u;
295 }
296 return null;
297 }
298
299 /**
300 * Shortcut for get("", name);
301 */
302 public static ImageIcon get(String name) {
303 return get("", name);
304 }
305
306 public static Cursor getCursor(String name, String overlay) {
307 ImageIcon img = get("cursor", name);
308 if (overlay != null) {
309 img = overlay(img, "cursor/modifier/" + overlay, OverlayPosition.SOUTHEAST);
310 }
311 Cursor c = Toolkit.getDefaultToolkit().createCustomCursor(img.getImage(),
312 name.equals("crosshair") ? new Point(10, 10) : new Point(3, 2), "Cursor");
313 return c;
314 }
315
316 /**
317 * @return an icon that represent the overlay of the two given icons. The second icon is layed
318 * on the first relative to the given position.
319 */
320 public static ImageIcon overlay(Icon ground, String overlayImage, OverlayPosition pos) {
321 return overlay(ground, ImageProvider.get(overlayImage), pos);
322 }
323
324 public static ImageIcon overlay(Icon ground, Icon overlay, OverlayPosition pos) {
325 GraphicsConfiguration conf = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()
326 .getDefaultConfiguration();
327 int w = ground.getIconWidth();
328 int h = ground.getIconHeight();
329 int wo = overlay.getIconWidth();
330 int ho = overlay.getIconHeight();
331 BufferedImage img = conf.createCompatibleImage(w, h, Transparency.TRANSLUCENT);
332 Graphics g = img.createGraphics();
333 ground.paintIcon(null, g, 0, 0);
334 int x = 0, y = 0;
335 switch (pos) {
336 case NORTHWEST:
337 x = 0;
338 y = 0;
339 break;
340 case NORTHEAST:
341 x = w - wo;
342 y = 0;
343 break;
344 case SOUTHWEST:
345 x = 0;
346 y = h - ho;
347 break;
348 case SOUTHEAST:
349 x = w - wo;
350 y = h - ho;
351 break;
352 }
353 overlay.paintIcon(null, g, x, y);
354 return new ImageIcon(img);
355 }
356
357 /*
358 * from: http://www.jidesoft.com/blog/2008/02/29/rotate-an-icon-in-java/ License:
359 * "feel free to use"
360 */
361 final static double DEGREE_90 = 90.0 * Math.PI / 180.0;
362
363 /**
364 * Creates a rotated version of the input image.
365 *
366 * @param c The component to get properties useful for painting, e.g. the foreground or
367 * background color.
368 * @param icon the image to be rotated.
369 * @param rotatedAngle the rotated angle, in degree, clockwise. It could be any double but we
370 * will mod it with 360 before using it.
371 *
372 * @return the image after rotating.
373 */
374 public static ImageIcon createRotatedImage(Component c, Icon icon, double rotatedAngle) {
375 // convert rotatedAngle to a value from 0 to 360
376 double originalAngle = rotatedAngle % 360;
377 if (rotatedAngle != 0 && originalAngle == 0) {
378 originalAngle = 360.0;
379 }
380
381 // convert originalAngle to a value from 0 to 90
382 double angle = originalAngle % 90;
383 if (originalAngle != 0.0 && angle == 0.0) {
384 angle = 90.0;
385 }
386
387 double radian = Math.toRadians(angle);
388
389 int iw = icon.getIconWidth();
390 int ih = icon.getIconHeight();
391 int w;
392 int h;
393
394 if ((originalAngle >= 0 && originalAngle <= 90) || (originalAngle > 180 && originalAngle <= 270)) {
395 w = (int) (iw * Math.sin(DEGREE_90 - radian) + ih * Math.sin(radian));
396 h = (int) (iw * Math.sin(radian) + ih * Math.sin(DEGREE_90 - radian));
397 } else {
398 w = (int) (ih * Math.sin(DEGREE_90 - radian) + iw * Math.sin(radian));
399 h = (int) (ih * Math.sin(radian) + iw * Math.sin(DEGREE_90 - radian));
400 }
401 BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
402 Graphics g = image.getGraphics();
403 Graphics2D g2d = (Graphics2D) g.create();
404
405 // calculate the center of the icon.
406 int cx = iw / 2;
407 int cy = ih / 2;
408
409 // move the graphics center point to the center of the icon.
410 g2d.translate(w / 2, h / 2);
411
412 // rotate the graphics about the center point of the icon
413 g2d.rotate(Math.toRadians(originalAngle));
414
415 g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
416 icon.paintIcon(c, g2d, -cx, -cy);
417
418 g2d.dispose();
419 return new ImageIcon(image);
420 }
421
422 /**
423 * Replies the icon for an OSM primitive type
424 * @param type the type
425 * @return the icon
426 */
427 public static ImageIcon get(OsmPrimitiveType type) throws IllegalArgumentException {
428 CheckParameterUtil.ensureParameterNotNull(type, "type");
429 return get("data", type.getAPIName());
430 }
431
432 public static BufferedImage sanitize(Image img) {
433 (new ImageIcon(img)).getImage(); // load competely
434 int width = img.getWidth(null);
435 int height = img.getHeight(null);
436 BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
437 result.getGraphics().drawImage(img, 0, 0, null);
438 return result;
439 }
440}
Note: See TracBrowser for help on using the repository browser.