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

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

use classloader to find projections from plugins

  • 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 ImageWrapper iw = cache.get(name);
137 if (iw == null) {
138 try {
139 MirroredInputStream is = new MirroredInputStream(name, new File(Main.pref.getPreferencesDir(),
140 "images").toString());
141 Image img = Toolkit.getDefaultToolkit().createImage(is.getFile().toURI().toURL());
142 iw = new ImageWrapper(img, false);
143 cache.put(name, iw);
144 } catch (IOException e) {
145 }
146 }
147 return iw;
148 }
149 if (subdir == null) {
150 subdir = "";
151 } else if (!subdir.equals("")) {
152 subdir += "/";
153 }
154 String ext = name.indexOf('.') != -1 ? "" : ".png";
155 String full_name = subdir + name + ext;
156 String cache_name = full_name;
157 /* cache separately */
158 if (dirs != null && dirs.size() > 0) {
159 cache_name = "id:" + id + ":" + full_name;
160 if(archive != null) {
161 cache_name += ":" + archive.getName();
162 }
163 }
164
165 ImageWrapper iw = cache.get(cache_name);
166 if (iw == null) {
167 if(archive != null)
168 {
169 ZipFile zipFile = null;
170 try
171 {
172 zipFile = new ZipFile(archive);
173 ZipEntry entry = zipFile.getEntry(full_name);
174 if(entry != null)
175 {
176 int size = (int)entry.getSize();
177 int offs = 0;
178 byte[] buf = new byte[size];
179 InputStream is = null;
180 try {
181 is = zipFile.getInputStream(entry);
182 while(size > 0)
183 {
184 int l = is.read(buf, offs, size);
185 offs += l;
186 size -= l;
187 }
188 Image img = Toolkit.getDefaultToolkit().createImage(buf);
189 iw = new ImageWrapper(img, false);
190 } finally {
191 if (is != null) {
192 is.close();
193 }
194 }
195 }
196 } catch (Exception e) {
197 System.err.println(tr("Warning: failed to handle zip file ''{0}''. Exception was: {1}", archive.getName(), e.toString()));
198 } finally {
199 if (zipFile != null) {
200 try {
201 zipFile.close();
202 } catch (IOException ex) {
203 }
204 }
205 }
206 }
207 // getImageUrl() does a ton of "stat()" calls and gets expensive
208 // and redundant when you have a whole ton of objects. So,
209 // index the cache by the name of the icon we're looking for
210 // and don't bother to create a URL unless we're actually
211 // creating the image.
212 if(iw == null)
213 {
214 URL path = getImageUrl(full_name, dirs);
215 if (path == null)
216 return null;
217 Image img = Toolkit.getDefaultToolkit().createImage(path);
218 iw = new ImageWrapper(img, false);
219 }
220 cache.put(cache_name, iw);
221 }
222
223 return iw;
224 }
225
226 private static URL getImageUrl(String path, String name) {
227 if (path.startsWith("resource://")) {
228 String p = path.substring("resource://".length());
229 for (ClassLoader source : PluginHandler.getResourceClassLoaders()) {
230 URL res;
231 if ((res = source.getResource(p + name)) != null)
232 return res;
233 }
234 } else {
235 try {
236 File f = new File(path, name);
237 if (f.exists())
238 return f.toURI().toURL();
239 } catch (MalformedURLException e) {
240 }
241 }
242 return null;
243 }
244
245 private static URL getImageUrl(String imageName, Collection<String> dirs) {
246 URL u = null;
247
248 // Try passed directories first
249 if (dirs != null) {
250 for (String name : dirs) {
251 try {
252 u = getImageUrl(name, imageName);
253 if (u != null)
254 return u;
255 } catch (SecurityException e) {
256 System.out.println(tr(
257 "Warning: failed to access directory ''{0}'' for security reasons. Exception was: {1}",
258 name, e.toString()));
259 }
260
261 }
262 }
263 // Try user-preference directory
264 String dir = Main.pref.getPreferencesDir() + "images";
265 try {
266 u = getImageUrl(dir, imageName);
267 if (u != null)
268 return u;
269 } catch (SecurityException e) {
270 System.out.println(tr(
271 "Warning: failed to access directory ''{0}'' for security reasons. Exception was: {1}", dir, e
272 .toString()));
273 }
274
275 // Try plugins and josm classloader
276 u = getImageUrl("resource://images/", imageName);
277 if (u != null)
278 return u;
279
280 // Try all other resource directories
281 for (String location : Main.pref.getAllPossiblePreferenceDirs()) {
282 u = getImageUrl(location + "images", imageName);
283 if (u != null)
284 return u;
285 u = getImageUrl(location, imageName);
286 if (u != null)
287 return u;
288 }
289 return null;
290 }
291
292 /**
293 * Shortcut for get("", name);
294 */
295 public static ImageIcon get(String name) {
296 return get("", name);
297 }
298
299 public static Cursor getCursor(String name, String overlay) {
300 ImageIcon img = get("cursor", name);
301 if (overlay != null) {
302 img = overlay(img, "cursor/modifier/" + overlay, OverlayPosition.SOUTHEAST);
303 }
304 Cursor c = Toolkit.getDefaultToolkit().createCustomCursor(img.getImage(),
305 name.equals("crosshair") ? new Point(10, 10) : new Point(3, 2), "Cursor");
306 return c;
307 }
308
309 /**
310 * @return an icon that represent the overlay of the two given icons. The second icon is layed
311 * on the first relative to the given position.
312 */
313 public static ImageIcon overlay(Icon ground, String overlayImage, OverlayPosition pos) {
314 return overlay(ground, ImageProvider.get(overlayImage), pos);
315 }
316
317 public static ImageIcon overlay(Icon ground, Icon overlay, OverlayPosition pos) {
318 GraphicsConfiguration conf = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()
319 .getDefaultConfiguration();
320 int w = ground.getIconWidth();
321 int h = ground.getIconHeight();
322 int wo = overlay.getIconWidth();
323 int ho = overlay.getIconHeight();
324 BufferedImage img = conf.createCompatibleImage(w, h, Transparency.TRANSLUCENT);
325 Graphics g = img.createGraphics();
326 ground.paintIcon(null, g, 0, 0);
327 int x = 0, y = 0;
328 switch (pos) {
329 case NORTHWEST:
330 x = 0;
331 y = 0;
332 break;
333 case NORTHEAST:
334 x = w - wo;
335 y = 0;
336 break;
337 case SOUTHWEST:
338 x = 0;
339 y = h - ho;
340 break;
341 case SOUTHEAST:
342 x = w - wo;
343 y = h - ho;
344 break;
345 }
346 overlay.paintIcon(null, g, x, y);
347 return new ImageIcon(img);
348 }
349
350 /*
351 * from: http://www.jidesoft.com/blog/2008/02/29/rotate-an-icon-in-java/ License:
352 * "feel free to use"
353 */
354 final static double DEGREE_90 = 90.0 * Math.PI / 180.0;
355
356 /**
357 * Creates a rotated version of the input image.
358 *
359 * @param c The component to get properties useful for painting, e.g. the foreground or
360 * background color.
361 * @param icon the image to be rotated.
362 * @param rotatedAngle the rotated angle, in degree, clockwise. It could be any double but we
363 * will mod it with 360 before using it.
364 *
365 * @return the image after rotating.
366 */
367 public static ImageIcon createRotatedImage(Component c, Icon icon, double rotatedAngle) {
368 // convert rotatedAngle to a value from 0 to 360
369 double originalAngle = rotatedAngle % 360;
370 if (rotatedAngle != 0 && originalAngle == 0) {
371 originalAngle = 360.0;
372 }
373
374 // convert originalAngle to a value from 0 to 90
375 double angle = originalAngle % 90;
376 if (originalAngle != 0.0 && angle == 0.0) {
377 angle = 90.0;
378 }
379
380 double radian = Math.toRadians(angle);
381
382 int iw = icon.getIconWidth();
383 int ih = icon.getIconHeight();
384 int w;
385 int h;
386
387 if ((originalAngle >= 0 && originalAngle <= 90) || (originalAngle > 180 && originalAngle <= 270)) {
388 w = (int) (iw * Math.sin(DEGREE_90 - radian) + ih * Math.sin(radian));
389 h = (int) (iw * Math.sin(radian) + ih * Math.sin(DEGREE_90 - radian));
390 } else {
391 w = (int) (ih * Math.sin(DEGREE_90 - radian) + iw * Math.sin(radian));
392 h = (int) (ih * Math.sin(radian) + iw * Math.sin(DEGREE_90 - radian));
393 }
394 BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
395 Graphics g = image.getGraphics();
396 Graphics2D g2d = (Graphics2D) g.create();
397
398 // calculate the center of the icon.
399 int cx = iw / 2;
400 int cy = ih / 2;
401
402 // move the graphics center point to the center of the icon.
403 g2d.translate(w / 2, h / 2);
404
405 // rotate the graphics about the center point of the icon
406 g2d.rotate(Math.toRadians(originalAngle));
407
408 g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
409 icon.paintIcon(c, g2d, -cx, -cy);
410
411 g2d.dispose();
412 return new ImageIcon(image);
413 }
414
415 /**
416 * Replies the icon for an OSM primitive type
417 * @param type the type
418 * @return the icon
419 */
420 public static ImageIcon get(OsmPrimitiveType type) throws IllegalArgumentException {
421 CheckParameterUtil.ensureParameterNotNull(type, "type");
422 return get("data", type.getAPIName());
423 }
424
425 public static BufferedImage sanitize(Image img) {
426 (new ImageIcon(img)).getImage(); // load competely
427 int width = img.getWidth(null);
428 int height = img.getHeight(null);
429 BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
430 result.getGraphics().drawImage(img, 0, 0, null);
431 return result;
432 }
433}
Note: See TracBrowser for help on using the repository browser.