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

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

geoimage: make thumbnails optional + cosmetics (see #4101)

  • 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.