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

Last change on this file since 2990 was 2990, checked in by jttt, 14 years ago

Fix some eclipse warnings

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