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

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

fix sonar squid:S2039 - Member variable visibility should be specified

  • Property svn:eol-style set to native
File size: 24.6 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;
29import org.openstreetmap.josm.tools.ExifReader;
30
31public class ImageDisplay extends JComponent {
32
33 /** The file that is currently displayed */
34 private File file = null;
35
36 /** The image currently displayed */
37 private Image image = null;
38
39 /** The image currently displayed */
40 private boolean errorLoading = 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
46 /** When a selection is done, the rectangle of the selection (in image coordinates) */
47 private Rectangle selectedRect = null;
48
49 /** The tracker to load the images */
50 private MediaTracker tracker = new MediaTracker(this);
51
52 private String osdText = null;
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 File file;
61 private int orientation;
62
63 public 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 = false;
138 private long lastTimeForMousePoint = 0L;
139 private Point mousePointInImg = null;
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 }
405
406 @Override
407 public void mouseExited(MouseEvent e) {
408 }
409
410 @Override
411 public void mouseMoved(MouseEvent e) {
412 }
413
414 private void checkPointInVisibleRect(Point p, Rectangle visibleRect) {
415 if (p.x < visibleRect.x) {
416 p.x = visibleRect.x;
417 }
418 if (p.x > visibleRect.x + visibleRect.width) {
419 p.x = visibleRect.x + visibleRect.width;
420 }
421 if (p.y < visibleRect.y) {
422 p.y = visibleRect.y;
423 }
424 if (p.y > visibleRect.y + visibleRect.height) {
425 p.y = visibleRect.y + visibleRect.height;
426 }
427 }
428 }
429
430 public ImageDisplay() {
431 ImgDisplayMouseListener mouseListener = new ImgDisplayMouseListener();
432 addMouseListener(mouseListener);
433 addMouseWheelListener(mouseListener);
434 addMouseMotionListener(mouseListener);
435 }
436
437 public void setImage(File file, Integer orientation) {
438 synchronized(this) {
439 this.file = file;
440 image = null;
441 selectedRect = null;
442 errorLoading = false;
443 }
444 repaint();
445 if (file != null) {
446 new Thread(new LoadImageRunnable(file, orientation)).start();
447 }
448 }
449
450 public void setOsdText(String text) {
451 this.osdText = text;
452 repaint();
453 }
454
455 @Override
456 public void paintComponent(Graphics g) {
457 Image image;
458 File file;
459 Rectangle visibleRect;
460 boolean errorLoading;
461
462 synchronized(this) {
463 image = this.image;
464 file = this.file;
465 visibleRect = this.visibleRect;
466 errorLoading = this.errorLoading;
467 }
468
469 if (file == null) {
470 g.setColor(Color.black);
471 String noImageStr = tr("No image");
472 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(noImageStr, g);
473 Dimension size = getSize();
474 g.drawString(noImageStr,
475 (int) ((size.width - noImageSize.getWidth()) / 2),
476 (int) ((size.height - noImageSize.getHeight()) / 2));
477 } else if (image == null) {
478 g.setColor(Color.black);
479 String loadingStr;
480 if (! errorLoading) {
481 loadingStr = tr("Loading {0}", file.getName());
482 } else {
483 loadingStr = tr("Error on file {0}", file.getName());
484 }
485 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
486 Dimension size = getSize();
487 g.drawString(loadingStr,
488 (int) ((size.width - noImageSize.getWidth()) / 2),
489 (int) ((size.height - noImageSize.getHeight()) / 2));
490 } else {
491 Rectangle target = calculateDrawImageRectangle(visibleRect);
492 g.drawImage(image,
493 target.x, target.y, target.x + target.width, target.y + target.height,
494 visibleRect.x, visibleRect.y, visibleRect.x + visibleRect.width, visibleRect.y + visibleRect.height,
495 null);
496 if (selectedRect != null) {
497 Point topLeft = img2compCoord(visibleRect, selectedRect.x, selectedRect.y);
498 Point bottomRight = img2compCoord(visibleRect,
499 selectedRect.x + selectedRect.width,
500 selectedRect.y + selectedRect.height);
501 g.setColor(new Color(128, 128, 128, 180));
502 g.fillRect(target.x, target.y, target.width, topLeft.y - target.y);
503 g.fillRect(target.x, target.y, topLeft.x - target.x, target.height);
504 g.fillRect(bottomRight.x, target.y, target.x + target.width - bottomRight.x, target.height);
505 g.fillRect(target.x, bottomRight.y, target.width, target.y + target.height - bottomRight.y);
506 g.setColor(Color.black);
507 g.drawRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y);
508 }
509 if (errorLoading) {
510 String loadingStr = tr("Error on file {0}", file.getName());
511 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
512 Dimension size = getSize();
513 g.drawString(loadingStr,
514 (int) ((size.width - noImageSize.getWidth()) / 2),
515 (int) ((size.height - noImageSize.getHeight()) / 2));
516 }
517 if (osdText != null) {
518 FontMetrics metrics = g.getFontMetrics(g.getFont());
519 int ascent = metrics.getAscent();
520 Color bkground = new Color(255, 255, 255, 128);
521 int lastPos = 0;
522 int pos = osdText.indexOf('\n');
523 int x = 3;
524 int y = 3;
525 String line;
526 while (pos > 0) {
527 line = osdText.substring(lastPos, pos);
528 Rectangle2D lineSize = metrics.getStringBounds(line, g);
529 g.setColor(bkground);
530 g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight());
531 g.setColor(Color.black);
532 g.drawString(line, x, y + ascent);
533 y += (int) lineSize.getHeight();
534 lastPos = pos + 1;
535 pos = osdText.indexOf('\n', lastPos);
536 }
537
538 line = osdText.substring(lastPos);
539 Rectangle2D lineSize = g.getFontMetrics(g.getFont()).getStringBounds(line, g);
540 g.setColor(bkground);
541 g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight());
542 g.setColor(Color.black);
543 g.drawString(line, x, y + ascent);
544 }
545 }
546 }
547
548 private final Point img2compCoord(Rectangle visibleRect, int xImg, int yImg) {
549 Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
550 return new Point(drawRect.x + ((xImg - visibleRect.x) * drawRect.width) / visibleRect.width,
551 drawRect.y + ((yImg - visibleRect.y) * drawRect.height) / visibleRect.height);
552 }
553
554 private final Point comp2imgCoord(Rectangle visibleRect, int xComp, int yComp) {
555 Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
556 return new Point(visibleRect.x + ((xComp - drawRect.x) * visibleRect.width) / drawRect.width,
557 visibleRect.y + ((yComp - drawRect.y) * visibleRect.height) / drawRect.height);
558 }
559
560 private final Point getCenterImgCoord(Rectangle visibleRect) {
561 return new Point(visibleRect.x + visibleRect.width / 2,
562 visibleRect.y + visibleRect.height / 2);
563 }
564
565 private Rectangle calculateDrawImageRectangle(Rectangle visibleRect) {
566 return calculateDrawImageRectangle(visibleRect, new Rectangle(0, 0, getSize().width, getSize().height));
567 }
568
569 /**
570 * calculateDrawImageRectangle
571 *
572 * @param imgRect the part of the image that should be drawn (in image coordinates)
573 * @param compRect the part of the component where the image should be drawn (in component coordinates)
574 * @return the part of compRect with the same width/height ratio as the image
575 */
576 static Rectangle calculateDrawImageRectangle(Rectangle imgRect, Rectangle compRect) {
577 int x, y, w, h;
578 x = 0;
579 y = 0;
580 w = compRect.width;
581 h = compRect.height;
582
583 int wFact = w * imgRect.height;
584 int hFact = h * imgRect.width;
585 if (wFact != hFact) {
586 if (wFact > hFact) {
587 w = hFact / imgRect.height;
588 x = (compRect.width - w) / 2;
589 } else {
590 h = wFact / imgRect.width;
591 y = (compRect.height - h) / 2;
592 }
593 }
594 return new Rectangle(x + compRect.x, y + compRect.y, w, h);
595 }
596
597 public void zoomBestFitOrOne() {
598 File file;
599 Image image;
600 Rectangle visibleRect;
601
602 synchronized (this) {
603 file = ImageDisplay.this.file;
604 image = ImageDisplay.this.image;
605 visibleRect = ImageDisplay.this.visibleRect;
606 }
607
608 if (image == null)
609 return;
610
611 if (visibleRect.width != image.getWidth(null) || visibleRect.height != image.getHeight(null)) {
612 // The display is not at best fit. => Zoom to best fit
613 visibleRect = new Rectangle(0, 0, image.getWidth(null), image.getHeight(null));
614
615 } else {
616 // The display is at best fit => zoom to 1:1
617 Point center = getCenterImgCoord(visibleRect);
618 visibleRect = new Rectangle(center.x - getWidth() / 2, center.y - getHeight() / 2,
619 getWidth(), getHeight());
620 checkVisibleRectPos(image, visibleRect);
621 }
622
623 synchronized(this) {
624 if (file == this.file) {
625 this.visibleRect = visibleRect;
626 }
627 }
628 repaint();
629 }
630
631 private final void checkVisibleRectPos(Image image, Rectangle visibleRect) {
632 if (visibleRect.x < 0) {
633 visibleRect.x = 0;
634 }
635 if (visibleRect.y < 0) {
636 visibleRect.y = 0;
637 }
638 if (visibleRect.x + visibleRect.width > image.getWidth(null)) {
639 visibleRect.x = image.getWidth(null) - visibleRect.width;
640 }
641 if (visibleRect.y + visibleRect.height > image.getHeight(null)) {
642 visibleRect.y = image.getHeight(null) - visibleRect.height;
643 }
644 }
645
646 private void checkVisibleRectSize(Image image, Rectangle visibleRect) {
647 if (visibleRect.width > image.getWidth(null)) {
648 visibleRect.width = image.getWidth(null);
649 }
650 if (visibleRect.height > image.getHeight(null)) {
651 visibleRect.height = image.getHeight(null);
652 }
653 }
654}
Note: See TracBrowser for help on using the repository browser.