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

Last change on this file since 3775 was 3512, checked in by bastiK, 14 years ago

fixed #5373 - JOSM can't delete old and replace by new plugins (JOSM loaded icons from plugin jar file, but did not release file handles.)

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