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

Last change on this file since 10173 was 10173, checked in by Don-vip, 8 years ago

sonar - squid:S1186 - Methods should not be empty

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