source: josm/trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java@ 2566

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

Moved the code from agpifoj plugin to JOSM core. Thanks to Christian Gallioz for this great (ex-)plugin.
In the current state it might be a little unstable.

  • Did a view modification so it fits in better.
  • Added the Thumbnail feature, but still work to be done.

New in JOSM core: Possibility to add toggle dialogs not only on startup, but also later.

  • Property svn:eol-style set to native
File size: 22.9 KB
Line 
1// License: GPL. See LICENSE file for details.
2// Copyright 2007 by Christian Gallioz (aka khris78)
3
4package org.openstreetmap.josm.gui.layer.geoimage;
5
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.awt.Color;
9import java.awt.Dimension;
10import java.awt.FontMetrics;
11import java.awt.Graphics;
12import java.awt.Image;
13import java.awt.MediaTracker;
14import java.awt.Point;
15import java.awt.Rectangle;
16import java.awt.Toolkit;
17import java.awt.event.MouseEvent;
18import java.awt.event.MouseListener;
19import java.awt.event.MouseMotionListener;
20import java.awt.event.MouseWheelEvent;
21import java.awt.event.MouseWheelListener;
22import java.awt.geom.Rectangle2D;
23import java.io.File;
24
25import javax.swing.JComponent;
26
27import org.openstreetmap.josm.Main;
28
29public class ImageDisplay extends JComponent {
30
31 /** The file that is currently displayed */
32 private File file = null;
33
34 /** The image currently displayed */
35 private Image image = null;
36 private Image image_c = null;
37
38 /** The image currently displayed */
39 private boolean errorLoading = false;
40 private boolean errorLoading_c = false;
41
42 /** The rectangle (in image coordinates) of the image that is visible. This rectangle is calculated
43 * each time the zoom is modified */
44 private Rectangle visibleRect = null;
45 private Rectangle visibleRect_c = null;
46
47 /** When a selection is done, the rectangle of the selection (in image coordinates) */
48 private Rectangle selectedRect = null;
49
50 /** The tracker to load the images */
51 private MediaTracker tracker = new MediaTracker(this);
52
53 private String osdText = null;
54
55 private static int DRAG_BUTTON = Main.pref.getBoolean("geoimage.agpifo-style-drag-and-zoom", false) ? 1 : 3;
56 private static int ZOOM_BUTTON = DRAG_BUTTON == 1 ? 3 : 1;
57
58 /** The thread that reads the images. */
59 private class LoadImageRunnable implements Runnable {
60
61 File file = null;
62
63 public LoadImageRunnable(File file) {
64 this.file = file;
65 }
66
67 public void run() {
68 Image img = Toolkit.getDefaultToolkit().createImage(file.getPath());
69 tracker.addImage(img, 1);
70
71 // Wait for the end of loading
72 while (! tracker.checkID(1, true)) {
73 if (this.file != ImageDisplay.this.file) {
74 // The file has changed
75 tracker.removeImage(img);
76 return;
77 }
78 try {
79 Thread.sleep(5);
80 } catch (InterruptedException e) {
81 }
82 }
83
84 boolean error = tracker.isErrorID(1);
85 if (img != null && (img.getWidth(null) == 0 || img.getHeight(null) == 0)) {
86 error = true;
87 }
88
89 synchronized(ImageDisplay.this) {
90 if (this.file != ImageDisplay.this.file) {
91 // The file has changed
92 tracker.removeImage(img);
93 return;
94 }
95 ImageDisplay.this.image = img;
96 visibleRect = new Rectangle(0, 0, img.getWidth(null), img.getHeight(null));
97 selectedRect = null;
98 errorLoading = error;
99 }
100 tracker.removeImage(img);
101 ImageDisplay.this.repaint();
102 }
103 }
104
105 private class ImgDisplayMouseListener implements MouseListener, MouseWheelListener, MouseMotionListener {
106
107 boolean mouseIsDragging = false;
108 long lastTimeForMousePoint = 0l;
109 Point mousePointInImg = null;
110
111 /** Zoom in and out, trying to preserve the point of the image that was under the mouse cursor
112 * at the same place */
113 public void mouseWheelMoved(MouseWheelEvent e) {
114 File file;
115 Image image;
116 Rectangle visibleRect;
117
118 synchronized (ImageDisplay.this) {
119 file = ImageDisplay.this.file;
120 image = ImageDisplay.this.image;
121 visibleRect = ImageDisplay.this.visibleRect;
122 }
123
124 mouseIsDragging = false;
125 selectedRect = null;
126
127 if (image == null) {
128 return;
129 }
130
131 // Calculate the mouse cursor position in image coordinates, so that we can center the zoom
132 // on that mouse position.
133 // To avoid issues when the user tries to zoom in on the image borders, this point is not calculated
134 // again if there was less than 1.5seconds since the last event.
135 System.out.println(e);
136 if (e.getWhen() - lastTimeForMousePoint > 1500 || mousePointInImg == null) {
137 lastTimeForMousePoint = e.getWhen();
138 mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
139 }
140
141 // Applicate the zoom to the visible rectangle in image coordinates
142 if (e.getWheelRotation() > 0) {
143 visibleRect.width = visibleRect.width * 3 / 2;
144 visibleRect.height = visibleRect.height * 3 / 2;
145 } else {
146 visibleRect.width = visibleRect.width * 2 / 3;
147 visibleRect.height = visibleRect.height * 2 / 3;
148 }
149
150 // Check that the zoom doesn't exceed 2:1
151 if (visibleRect.width < getSize().width / 2) {
152 visibleRect.width = getSize().width / 2;
153 }
154 if (visibleRect.height < getSize().height / 2) {
155 visibleRect.height = getSize().height / 2;
156 }
157
158 // Set the same ratio for the visible rectangle and the display area
159 int hFact = visibleRect.height * getSize().width;
160 int wFact = visibleRect.width * getSize().height;
161 if (hFact > wFact) {
162 visibleRect.width = hFact / getSize().height;
163 } else {
164 visibleRect.height = wFact / getSize().width;
165 }
166
167 // The size of the visible rectangle is limited by the image size.
168 checkVisibleRectSize(image, visibleRect);
169
170 // Set the position of the visible rectangle, so that the mouse cursor doesn't move on the image.
171 Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
172 visibleRect.x = mousePointInImg.x + ((drawRect.x - e.getX()) * visibleRect.width) / drawRect.width;
173 visibleRect.y = mousePointInImg.y + ((drawRect.y - e.getY()) * visibleRect.height) / drawRect.height;
174
175 // The position is also limited by the image size
176 checkVisibleRectPos(image, visibleRect);
177
178 synchronized(ImageDisplay.this) {
179 if (ImageDisplay.this.file == file) {
180 ImageDisplay.this.visibleRect = visibleRect;
181 }
182 }
183 ImageDisplay.this.repaint();
184 }
185
186 /** Center the display on the point that has been clicked */
187 public void mouseClicked(MouseEvent e) {
188 // Move the center to the clicked point.
189 File file;
190 Image image;
191 Rectangle visibleRect;
192
193 synchronized (ImageDisplay.this) {
194 file = ImageDisplay.this.file;
195 image = ImageDisplay.this.image;
196 visibleRect = ImageDisplay.this.visibleRect;
197 }
198
199 if (image == null) {
200 return;
201 }
202
203 if (e.getButton() != DRAG_BUTTON) {
204 return;
205 }
206
207 // Calculate the translation to set the clicked point the center of the view.
208 Point click = comp2imgCoord(visibleRect, e.getX(), e.getY());
209 Point center = getCenterImgCoord(visibleRect);
210
211 visibleRect.x += click.x - center.x;
212 visibleRect.y += click.y - center.y;
213
214 checkVisibleRectPos(image, visibleRect);
215
216 synchronized(ImageDisplay.this) {
217 if (ImageDisplay.this.file == file) {
218 ImageDisplay.this.visibleRect = visibleRect;
219 }
220 }
221 ImageDisplay.this.repaint();
222 }
223
224 /** Initialize the dragging, either with button 1 (simple dragging) or button 3 (selection of
225 * a picture part) */
226 public void mousePressed(MouseEvent e) {
227 if (image == null) {
228 mouseIsDragging = false;
229 selectedRect = null;
230 return;
231 }
232
233 File file;
234 Image image;
235 Rectangle visibleRect;
236
237 synchronized (ImageDisplay.this) {
238 file = ImageDisplay.this.file;
239 image = ImageDisplay.this.image;
240 visibleRect = ImageDisplay.this.visibleRect;
241 }
242
243 if (image == null) {
244 return;
245 }
246
247 if (e.getButton() == DRAG_BUTTON) {
248 mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
249 mouseIsDragging = true;
250 selectedRect = null;
251 } else if (e.getButton() == ZOOM_BUTTON) {
252 mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
253 checkPointInVisibleRect(mousePointInImg, visibleRect);
254 mouseIsDragging = false;
255 selectedRect = new Rectangle(mousePointInImg.x, mousePointInImg.y, 0, 0);
256 ImageDisplay.this.repaint();
257 } else {
258 mouseIsDragging = false;
259 selectedRect = null;
260 }
261 }
262
263 public void mouseDragged(MouseEvent e) {
264 if (! mouseIsDragging && selectedRect == null) {
265 return;
266 }
267
268 File file;
269 Image image;
270 Rectangle visibleRect;
271
272 synchronized (ImageDisplay.this) {
273 file = ImageDisplay.this.file;
274 image = ImageDisplay.this.image;
275 visibleRect = ImageDisplay.this.visibleRect;
276 }
277
278 if (image == null) {
279 mouseIsDragging = false;
280 selectedRect = null;
281 return;
282 }
283
284 if (mouseIsDragging) {
285 Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
286 visibleRect.x += mousePointInImg.x - p.x;
287 visibleRect.y += mousePointInImg.y - p.y;
288 checkVisibleRectPos(image, visibleRect);
289 synchronized(ImageDisplay.this) {
290 if (ImageDisplay.this.file == file) {
291 ImageDisplay.this.visibleRect = visibleRect;
292 }
293 }
294 ImageDisplay.this.repaint();
295
296 } else if (selectedRect != null) {
297 Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
298 checkPointInVisibleRect(p, visibleRect);
299 Rectangle rect = new Rectangle(
300 (p.x < mousePointInImg.x ? p.x : mousePointInImg.x),
301 (p.y < mousePointInImg.y ? p.y : mousePointInImg.y),
302 (p.x < mousePointInImg.x ? mousePointInImg.x - p.x : p.x - mousePointInImg.x),
303 (p.y < mousePointInImg.y ? mousePointInImg.y - p.y : p.y - mousePointInImg.y));
304 checkVisibleRectSize(image, rect);
305 checkVisibleRectPos(image, rect);
306 ImageDisplay.this.selectedRect = rect;
307 ImageDisplay.this.repaint();
308 }
309
310 }
311
312 public void mouseReleased(MouseEvent e) {
313 if (! mouseIsDragging && selectedRect == null) {
314 return;
315 }
316
317 File file;
318 Image image;
319 Rectangle visibleRect;
320
321 synchronized (ImageDisplay.this) {
322 file = ImageDisplay.this.file;
323 image = ImageDisplay.this.image;
324 visibleRect = ImageDisplay.this.visibleRect;
325 }
326
327 if (image == null) {
328 mouseIsDragging = false;
329 selectedRect = null;
330 return;
331 }
332
333 if (mouseIsDragging) {
334 mouseIsDragging = false;
335
336 } else if (selectedRect != null) {
337 int oldWidth = selectedRect.width;
338 int oldHeight = selectedRect.height;
339
340 // Check that the zoom doesn't exceed 2:1
341 if (selectedRect.width < getSize().width / 2) {
342 selectedRect.width = getSize().width / 2;
343 }
344 if (selectedRect.height < getSize().height / 2) {
345 selectedRect.height = getSize().height / 2;
346 }
347
348 // Set the same ratio for the visible rectangle and the display area
349 int hFact = selectedRect.height * getSize().width;
350 int wFact = selectedRect.width * getSize().height;
351 if (hFact > wFact) {
352 selectedRect.width = hFact / getSize().height;
353 } else {
354 selectedRect.height = wFact / getSize().width;
355 }
356
357 // Keep the center of the selection
358 if (selectedRect.width != oldWidth) {
359 selectedRect.x -= (selectedRect.width - oldWidth) / 2;
360 }
361 if (selectedRect.height != oldHeight) {
362 selectedRect.y -= (selectedRect.height - oldHeight) / 2;
363 }
364
365 checkVisibleRectSize(image, selectedRect);
366 checkVisibleRectPos(image, selectedRect);
367
368 synchronized (ImageDisplay.this) {
369 if (file == ImageDisplay.this.file) {
370 ImageDisplay.this.visibleRect = selectedRect;
371 }
372 }
373 selectedRect = null;
374 ImageDisplay.this.repaint();
375 }
376 }
377
378 public void mouseEntered(MouseEvent e) {
379 }
380
381 public void mouseExited(MouseEvent e) {
382 }
383
384 public void mouseMoved(MouseEvent e) {
385 }
386
387 private void checkPointInVisibleRect(Point p, Rectangle visibleRect) {
388 if (p.x < visibleRect.x) {
389 p.x = visibleRect.x;
390 }
391 if (p.x > visibleRect.x + visibleRect.width) {
392 p.x = visibleRect.x + visibleRect.width;
393 }
394 if (p.y < visibleRect.y) {
395 p.y = visibleRect.y;
396 }
397 if (p.y > visibleRect.y + visibleRect.height) {
398 p.y = visibleRect.y + visibleRect.height;
399 }
400 }
401 }
402
403 public ImageDisplay() {
404 ImgDisplayMouseListener mouseListener = new ImgDisplayMouseListener();
405 addMouseListener(mouseListener);
406 addMouseWheelListener(mouseListener);
407 addMouseMotionListener(mouseListener);
408 }
409
410 public void setImage(File file) {
411 synchronized(this) {
412 this.file = file;
413 image = null;
414 selectedRect = null;
415 errorLoading = false;
416 }
417 repaint();
418 if (file != null) {
419 new Thread(new LoadImageRunnable(file)).start();
420 }
421 }
422
423 public void setOsdText(String text) {
424 this.osdText = text;
425 }
426
427 public void paintComponent(Graphics g) {
428 Image image;
429 File file;
430 Rectangle visibleRect;
431 boolean errorLoading;
432
433 synchronized(this) {
434 image = this.image;
435 file = this.file;
436 visibleRect = this.visibleRect;
437 errorLoading = this.errorLoading;
438 }
439
440 if (file == null) {
441 g.setColor(Color.black);
442 String noImageStr = tr("No image");
443 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(noImageStr, g);
444 Dimension size = getSize();
445 g.drawString(noImageStr,
446 (int) ((size.width - noImageSize.getWidth()) / 2),
447 (int) ((size.height - noImageSize.getHeight()) / 2));
448 } else if (image == null) {
449 g.setColor(Color.black);
450 String loadingStr;
451 if (! errorLoading) {;
452 loadingStr = tr("Loading {0}", file.getName());
453 } else {
454 loadingStr = tr("Error on file {0}", file.getName());
455 }
456 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
457 Dimension size = getSize();
458 g.drawString(loadingStr,
459 (int) ((size.width - noImageSize.getWidth()) / 2),
460 (int) ((size.height - noImageSize.getHeight()) / 2));
461 } else {
462 Rectangle target = calculateDrawImageRectangle(visibleRect);
463 g.drawImage(image,
464 target.x, target.y, target.x + target.width, target.y + target.height,
465 visibleRect.x, visibleRect.y, visibleRect.x + visibleRect.width, visibleRect.y + visibleRect.height,
466 null);
467 if (selectedRect != null) {
468 Point topLeft = img2compCoord(visibleRect, selectedRect.x, selectedRect.y);
469 Point bottomRight = img2compCoord(visibleRect,
470 selectedRect.x + selectedRect.width,
471 selectedRect.y + selectedRect.height);
472 g.setColor(new Color(128, 128, 128, 180));
473 g.fillRect(target.x, target.y, target.width, topLeft.y - target.y);
474 g.fillRect(target.x, target.y, topLeft.x - target.x, target.height);
475 g.fillRect(bottomRight.x, target.y, target.x + target.width - bottomRight.x, target.height);
476 g.fillRect(target.x, bottomRight.y, target.width, target.y + target.height - bottomRight.y);
477 g.setColor(Color.black);
478 g.drawRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y);
479 }
480 if (errorLoading) {
481 String loadingStr = tr("Error on file {0}", file.getName());
482 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
483 Dimension size = getSize();
484 g.drawString(loadingStr,
485 (int) ((size.width - noImageSize.getWidth()) / 2),
486 (int) ((size.height - noImageSize.getHeight()) / 2));
487 }
488 if (osdText != null) {
489 FontMetrics metrics = g.getFontMetrics(g.getFont());
490 int ascent = metrics.getAscent();
491 Color bkground = new Color(255, 255, 255, 128);
492 int lastPos = 0;
493 int pos = osdText.indexOf("\n");
494 int x = 3;
495 int y = 3;
496 String line;
497 while (pos > 0) {
498 line = osdText.substring(lastPos, pos);
499 Rectangle2D lineSize = metrics.getStringBounds(line, g);
500 g.setColor(bkground);
501 g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight());
502 g.setColor(Color.black);
503 g.drawString(line, x, y + ascent);
504 y += (int) lineSize.getHeight();
505 lastPos = pos + 1;
506 pos = osdText.indexOf("\n", lastPos);
507 }
508
509 line = osdText.substring(lastPos);
510 Rectangle2D lineSize = g.getFontMetrics(g.getFont()).getStringBounds(line, g);
511 g.setColor(bkground);
512 g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight());
513 g.setColor(Color.black);
514 g.drawString(line, x, y + ascent);
515 }
516 }
517 }
518
519 private final Point img2compCoord(Rectangle visibleRect, int xImg, int yImg) {
520 Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
521 return new Point(drawRect.x + ((xImg - visibleRect.x) * drawRect.width) / visibleRect.width,
522 drawRect.y + ((yImg - visibleRect.y) * drawRect.height) / visibleRect.height);
523 }
524
525 private final Point comp2imgCoord(Rectangle visibleRect, int xComp, int yComp) {
526 Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
527 return new Point(visibleRect.x + ((xComp - drawRect.x) * visibleRect.width) / drawRect.width,
528 visibleRect.y + ((yComp - drawRect.y) * visibleRect.height) / drawRect.height);
529 }
530
531 private final Point getCenterImgCoord(Rectangle visibleRect) {
532 return new Point(visibleRect.x + visibleRect.width / 2,
533 visibleRect.y + visibleRect.height / 2);
534 }
535
536 private Rectangle calculateDrawImageRectangle(Rectangle visibleRect) {
537 Dimension size = getSize();
538 int x, y, w, h;
539 x = 0;
540 y = 0;
541 w = size.width;
542 h = size.height;
543
544 int wFact = w * visibleRect.height;
545 int hFact = h * visibleRect.width;
546 if (wFact != hFact) {
547 if (wFact > hFact) {
548 w = hFact / visibleRect.height;
549 x = (size.width - w) / 2;
550 } else {
551 h = wFact / visibleRect.width;
552 y = (size.height - h) / 2;
553 }
554 }
555 return new Rectangle(x, y, w, h);
556 }
557
558 public void zoomBestFitOrOne() {
559 File file;
560 Image image;
561 Rectangle visibleRect;
562
563 synchronized (this) {
564 file = ImageDisplay.this.file;
565 image = ImageDisplay.this.image;
566 visibleRect = ImageDisplay.this.visibleRect;
567 }
568
569 if (image == null) {
570 return;
571 }
572
573 if (visibleRect.width != image.getWidth(null) || visibleRect.height != image.getHeight(null)) {
574 // The display is not at best fit. => Zoom to best fit
575 visibleRect = new Rectangle(0, 0, image.getWidth(null), image.getHeight(null));
576
577 } else {
578 // The display is at best fit => zoom to 1:1
579 Point center = getCenterImgCoord(visibleRect);
580 visibleRect = new Rectangle(center.x - getWidth() / 2, center.y - getHeight() / 2,
581 getWidth(), getHeight());
582 checkVisibleRectPos(image, visibleRect);
583 }
584
585 synchronized(this) {
586 if (file == this.file) {
587 this.visibleRect = visibleRect;
588 }
589 }
590 repaint();
591 }
592
593 private final void checkVisibleRectPos(Image image, Rectangle visibleRect) {
594 if (visibleRect.x < 0) {
595 visibleRect.x = 0;
596 }
597 if (visibleRect.y < 0) {
598 visibleRect.y = 0;
599 }
600 if (visibleRect.x + visibleRect.width > image.getWidth(null)) {
601 visibleRect.x = image.getWidth(null) - visibleRect.width;
602 }
603 if (visibleRect.y + visibleRect.height > image.getHeight(null)) {
604 visibleRect.y = image.getHeight(null) - visibleRect.height;
605 }
606 }
607
608 private void checkVisibleRectSize(Image image, Rectangle visibleRect) {
609 if (visibleRect.width > image.getWidth(null)) {
610 visibleRect.width = image.getWidth(null);
611 }
612 if (visibleRect.height > image.getHeight(null)) {
613 visibleRect.height = image.getHeight(null);
614 }
615 }
616}
Note: See TracBrowser for help on using the repository browser.