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

Last change on this file since 6084 was 6084, checked in by bastiK, 11 years ago

see #8902 - add missing @Override annotations (patch by shinigami)

  • Property svn:eol-style set to native
File size: 25.0 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.Graphics2D;
13import java.awt.Image;
14import java.awt.MediaTracker;
15import java.awt.Point;
16import java.awt.Rectangle;
17import java.awt.Toolkit;
18import java.awt.event.MouseEvent;
19import java.awt.event.MouseListener;
20import java.awt.event.MouseMotionListener;
21import java.awt.event.MouseWheelEvent;
22import java.awt.event.MouseWheelListener;
23import java.awt.geom.AffineTransform;
24import java.awt.geom.Rectangle2D;
25import java.awt.image.BufferedImage;
26import java.io.File;
27
28import javax.swing.JComponent;
29
30import org.openstreetmap.josm.Main;
31
32public class ImageDisplay extends JComponent {
33
34 /** The file that is currently displayed */
35 private File file = null;
36
37 /** The image currently displayed */
38 private Image image = null;
39
40 /** The image currently displayed */
41 private boolean errorLoading = false;
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 = 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 private File file;
62 private int orientation;
63
64 public 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 }
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 outer: {
107 final int hh, ww, q;
108 final double ax, ay;
109 switch (orientation) {
110 case 8:
111 q = -1;
112 ax = w / 2;
113 ay = w / 2;
114 ww = h;
115 hh = w;
116 break;
117 case 3:
118 q = 2;
119 ax = w / 2;
120 ay = h / 2;
121 ww = w;
122 hh = h;
123 break;
124 case 6:
125 q = 1;
126 ax = h / 2;
127 ay = h / 2;
128 ww = h;
129 hh = w;
130 break;
131 default:
132 break outer;
133 }
134
135 final BufferedImage rot = new BufferedImage(ww, hh, BufferedImage.TYPE_INT_RGB);
136 final AffineTransform xform = AffineTransform.getQuadrantRotateInstance(q, ax, ay);
137 final Graphics2D g = rot.createGraphics();
138 g.drawImage(image, xform, null);
139 g.dispose();
140
141 visibleRect.setSize(ww, hh);
142 image.flush();
143 ImageDisplay.this.image = rot;
144 }
145 }
146
147 selectedRect = null;
148 errorLoading = error;
149 }
150 tracker.removeImage(img);
151 ImageDisplay.this.repaint();
152 }
153 }
154
155 private class ImgDisplayMouseListener implements MouseListener, MouseWheelListener, MouseMotionListener {
156
157 boolean mouseIsDragging = false;
158 long lastTimeForMousePoint = 0l;
159 Point mousePointInImg = null;
160
161 /** Zoom in and out, trying to preserve the point of the image that was under the mouse cursor
162 * at the same place */
163 @Override
164 public void mouseWheelMoved(MouseWheelEvent e) {
165 File file;
166 Image image;
167 Rectangle visibleRect;
168
169 synchronized (ImageDisplay.this) {
170 file = ImageDisplay.this.file;
171 image = ImageDisplay.this.image;
172 visibleRect = ImageDisplay.this.visibleRect;
173 }
174
175 mouseIsDragging = false;
176 selectedRect = null;
177
178 if (image == null)
179 return;
180
181 // Calculate the mouse cursor position in image coordinates, so that we can center the zoom
182 // on that mouse position.
183 // To avoid issues when the user tries to zoom in on the image borders, this point is not calculated
184 // again if there was less than 1.5seconds since the last event.
185 if (e.getWhen() - lastTimeForMousePoint > 1500 || mousePointInImg == null) {
186 lastTimeForMousePoint = e.getWhen();
187 mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
188 }
189
190 // Applicate the zoom to the visible rectangle in image coordinates
191 if (e.getWheelRotation() > 0) {
192 visibleRect.width = visibleRect.width * 3 / 2;
193 visibleRect.height = visibleRect.height * 3 / 2;
194 } else {
195 visibleRect.width = visibleRect.width * 2 / 3;
196 visibleRect.height = visibleRect.height * 2 / 3;
197 }
198
199 // Check that the zoom doesn't exceed 2:1
200 if (visibleRect.width < getSize().width / 2) {
201 visibleRect.width = getSize().width / 2;
202 }
203 if (visibleRect.height < getSize().height / 2) {
204 visibleRect.height = getSize().height / 2;
205 }
206
207 // Set the same ratio for the visible rectangle and the display area
208 int hFact = visibleRect.height * getSize().width;
209 int wFact = visibleRect.width * getSize().height;
210 if (hFact > wFact) {
211 visibleRect.width = hFact / getSize().height;
212 } else {
213 visibleRect.height = wFact / getSize().width;
214 }
215
216 // The size of the visible rectangle is limited by the image size.
217 checkVisibleRectSize(image, visibleRect);
218
219 // Set the position of the visible rectangle, so that the mouse cursor doesn't move on the image.
220 Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
221 visibleRect.x = mousePointInImg.x + ((drawRect.x - e.getX()) * visibleRect.width) / drawRect.width;
222 visibleRect.y = mousePointInImg.y + ((drawRect.y - e.getY()) * visibleRect.height) / drawRect.height;
223
224 // The position is also limited by the image size
225 checkVisibleRectPos(image, visibleRect);
226
227 synchronized(ImageDisplay.this) {
228 if (ImageDisplay.this.file == file) {
229 ImageDisplay.this.visibleRect = visibleRect;
230 }
231 }
232 ImageDisplay.this.repaint();
233 }
234
235 /** Center the display on the point that has been clicked */
236 @Override
237 public void mouseClicked(MouseEvent e) {
238 // Move the center to the clicked point.
239 File file;
240 Image image;
241 Rectangle visibleRect;
242
243 synchronized (ImageDisplay.this) {
244 file = ImageDisplay.this.file;
245 image = ImageDisplay.this.image;
246 visibleRect = ImageDisplay.this.visibleRect;
247 }
248
249 if (image == null)
250 return;
251
252 if (e.getButton() != DRAG_BUTTON)
253 return;
254
255 // Calculate the translation to set the clicked point the center of the view.
256 Point click = comp2imgCoord(visibleRect, e.getX(), e.getY());
257 Point center = getCenterImgCoord(visibleRect);
258
259 visibleRect.x += click.x - center.x;
260 visibleRect.y += click.y - center.y;
261
262 checkVisibleRectPos(image, visibleRect);
263
264 synchronized(ImageDisplay.this) {
265 if (ImageDisplay.this.file == file) {
266 ImageDisplay.this.visibleRect = visibleRect;
267 }
268 }
269 ImageDisplay.this.repaint();
270 }
271
272 /** Initialize the dragging, either with button 1 (simple dragging) or button 3 (selection of
273 * a picture part) */
274 @Override
275 public void mousePressed(MouseEvent e) {
276 if (image == null) {
277 mouseIsDragging = false;
278 selectedRect = null;
279 return;
280 }
281
282 Image image;
283 Rectangle visibleRect;
284
285 synchronized (ImageDisplay.this) {
286 image = ImageDisplay.this.image;
287 visibleRect = ImageDisplay.this.visibleRect;
288 }
289
290 if (image == null)
291 return;
292
293 if (e.getButton() == DRAG_BUTTON) {
294 mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
295 mouseIsDragging = true;
296 selectedRect = null;
297 } else if (e.getButton() == ZOOM_BUTTON) {
298 mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
299 checkPointInVisibleRect(mousePointInImg, visibleRect);
300 mouseIsDragging = false;
301 selectedRect = new Rectangle(mousePointInImg.x, mousePointInImg.y, 0, 0);
302 ImageDisplay.this.repaint();
303 } else {
304 mouseIsDragging = false;
305 selectedRect = null;
306 }
307 }
308
309 @Override
310 public void mouseDragged(MouseEvent e) {
311 if (! mouseIsDragging && selectedRect == null)
312 return;
313
314 File file;
315 Image image;
316 Rectangle visibleRect;
317
318 synchronized (ImageDisplay.this) {
319 file = ImageDisplay.this.file;
320 image = ImageDisplay.this.image;
321 visibleRect = ImageDisplay.this.visibleRect;
322 }
323
324 if (image == null) {
325 mouseIsDragging = false;
326 selectedRect = null;
327 return;
328 }
329
330 if (mouseIsDragging) {
331 Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
332 visibleRect.x += mousePointInImg.x - p.x;
333 visibleRect.y += mousePointInImg.y - p.y;
334 checkVisibleRectPos(image, visibleRect);
335 synchronized(ImageDisplay.this) {
336 if (ImageDisplay.this.file == file) {
337 ImageDisplay.this.visibleRect = visibleRect;
338 }
339 }
340 ImageDisplay.this.repaint();
341
342 } else if (selectedRect != null) {
343 Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
344 checkPointInVisibleRect(p, visibleRect);
345 Rectangle rect = new Rectangle(
346 (p.x < mousePointInImg.x ? p.x : mousePointInImg.x),
347 (p.y < mousePointInImg.y ? p.y : mousePointInImg.y),
348 (p.x < mousePointInImg.x ? mousePointInImg.x - p.x : p.x - mousePointInImg.x),
349 (p.y < mousePointInImg.y ? mousePointInImg.y - p.y : p.y - mousePointInImg.y));
350 checkVisibleRectSize(image, rect);
351 checkVisibleRectPos(image, rect);
352 ImageDisplay.this.selectedRect = rect;
353 ImageDisplay.this.repaint();
354 }
355
356 }
357
358 @Override
359 public void mouseReleased(MouseEvent e) {
360 if (! mouseIsDragging && selectedRect == null)
361 return;
362
363 File file;
364 Image image;
365
366 synchronized (ImageDisplay.this) {
367 file = ImageDisplay.this.file;
368 image = ImageDisplay.this.image;
369 }
370
371 if (image == null) {
372 mouseIsDragging = false;
373 selectedRect = null;
374 return;
375 }
376
377 if (mouseIsDragging) {
378 mouseIsDragging = false;
379
380 } else if (selectedRect != null) {
381 int oldWidth = selectedRect.width;
382 int oldHeight = selectedRect.height;
383
384 // Check that the zoom doesn't exceed 2:1
385 if (selectedRect.width < getSize().width / 2) {
386 selectedRect.width = getSize().width / 2;
387 }
388 if (selectedRect.height < getSize().height / 2) {
389 selectedRect.height = getSize().height / 2;
390 }
391
392 // Set the same ratio for the visible rectangle and the display area
393 int hFact = selectedRect.height * getSize().width;
394 int wFact = selectedRect.width * getSize().height;
395 if (hFact > wFact) {
396 selectedRect.width = hFact / getSize().height;
397 } else {
398 selectedRect.height = wFact / getSize().width;
399 }
400
401 // Keep the center of the selection
402 if (selectedRect.width != oldWidth) {
403 selectedRect.x -= (selectedRect.width - oldWidth) / 2;
404 }
405 if (selectedRect.height != oldHeight) {
406 selectedRect.y -= (selectedRect.height - oldHeight) / 2;
407 }
408
409 checkVisibleRectSize(image, selectedRect);
410 checkVisibleRectPos(image, selectedRect);
411
412 synchronized (ImageDisplay.this) {
413 if (file == ImageDisplay.this.file) {
414 ImageDisplay.this.visibleRect = selectedRect;
415 }
416 }
417 selectedRect = null;
418 ImageDisplay.this.repaint();
419 }
420 }
421
422 @Override
423 public void mouseEntered(MouseEvent e) {
424 }
425
426 @Override
427 public void mouseExited(MouseEvent e) {
428 }
429
430 @Override
431 public void mouseMoved(MouseEvent e) {
432 }
433
434 private void checkPointInVisibleRect(Point p, Rectangle visibleRect) {
435 if (p.x < visibleRect.x) {
436 p.x = visibleRect.x;
437 }
438 if (p.x > visibleRect.x + visibleRect.width) {
439 p.x = visibleRect.x + visibleRect.width;
440 }
441 if (p.y < visibleRect.y) {
442 p.y = visibleRect.y;
443 }
444 if (p.y > visibleRect.y + visibleRect.height) {
445 p.y = visibleRect.y + visibleRect.height;
446 }
447 }
448 }
449
450 public ImageDisplay() {
451 ImgDisplayMouseListener mouseListener = new ImgDisplayMouseListener();
452 addMouseListener(mouseListener);
453 addMouseWheelListener(mouseListener);
454 addMouseMotionListener(mouseListener);
455 }
456
457 public void setImage(File file, Integer orientation) {
458 synchronized(this) {
459 this.file = file;
460 image = null;
461 selectedRect = null;
462 errorLoading = false;
463 }
464 repaint();
465 if (file != null) {
466 new Thread(new LoadImageRunnable(file, orientation)).start();
467 }
468 }
469
470 public void setOsdText(String text) {
471 this.osdText = text;
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.