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

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

sonar - squid:S3052 - Fields should not be initialized to default values

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