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

Last change on this file since 7912 was 7912, checked in by bastiK, 9 years ago

applied #10894 - Show photo direction arrow for thumbnail (patch by holgermappt)

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