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

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

fix recent Sonar issues

  • Property svn:eol-style set to native
File size: 31.1 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.RenderingHints;
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.AffineTransform;
23import java.awt.geom.Rectangle2D;
24import java.awt.image.BufferedImage;
25import java.io.File;
26
27import javax.swing.JComponent;
28import javax.swing.SwingUtilities;
29
30import org.openstreetmap.josm.data.preferences.BooleanProperty;
31import org.openstreetmap.josm.data.preferences.DoubleProperty;
32import org.openstreetmap.josm.spi.preferences.Config;
33import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
34import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
35import org.openstreetmap.josm.tools.ExifReader;
36import org.openstreetmap.josm.tools.ImageProvider;
37import org.openstreetmap.josm.tools.Logging;
38
39/**
40 * GUI component to display an image (photograph).
41 *
42 * Offers basic mouse interaction (zoom, drag) and on-screen text.
43 */
44public class ImageDisplay extends JComponent implements PreferenceChangedListener {
45
46 /** The file that is currently displayed */
47 private File file;
48
49 /** The image currently displayed */
50 private transient Image image;
51
52 /** The image currently displayed */
53 private boolean errorLoading;
54
55 /** The rectangle (in image coordinates) of the image that is visible. This rectangle is calculated
56 * each time the zoom is modified */
57 private VisRect visibleRect;
58
59 /** When a selection is done, the rectangle of the selection (in image coordinates) */
60 private VisRect selectedRect;
61
62 /** The tracker to load the images */
63 private final MediaTracker tracker = new MediaTracker(this);
64
65 private String osdText;
66
67 private static final BooleanProperty AGPIFO_STYLE =
68 new BooleanProperty("geoimage.agpifo-style-drag-and-zoom", false);
69 private static int dragButton;
70 private static int zoomButton;
71
72 /** Alternative to mouse wheel zoom; esp. handy if no mouse wheel is present **/
73 private static final BooleanProperty ZOOM_ON_CLICK =
74 new BooleanProperty("geoimage.use-mouse-clicks-to-zoom", true);
75
76 /** Zoom factor when click or wheel zooming **/
77 private static final DoubleProperty ZOOM_STEP =
78 new DoubleProperty("geoimage.zoom-step-factor", 3 / 2.0);
79
80 /** Maximum zoom allowed **/
81 private static final DoubleProperty MAX_ZOOM =
82 new DoubleProperty("geoimage.maximum-zoom-scale", 2.0);
83
84 /** Use bilinear filtering **/
85 private static final BooleanProperty BILIN_DOWNSAMP =
86 new BooleanProperty("geoimage.bilinear-downsampling-progressive", true);
87 private static final BooleanProperty BILIN_UPSAMP =
88 new BooleanProperty("geoimage.bilinear-upsampling", false);
89 private static double bilinUpper;
90 private static double bilinLower;
91
92 @Override
93 public void preferenceChanged(PreferenceChangeEvent e) {
94 if (e == null ||
95 e.getKey().equals(AGPIFO_STYLE.getKey())) {
96 dragButton = AGPIFO_STYLE.get() ? 1 : 3;
97 zoomButton = dragButton == 1 ? 3 : 1;
98 }
99 if (e == null ||
100 e.getKey().equals(MAX_ZOOM.getKey()) ||
101 e.getKey().equals(BILIN_DOWNSAMP.getKey()) ||
102 e.getKey().equals(BILIN_UPSAMP.getKey())) {
103 bilinUpper = (BILIN_UPSAMP.get() ? 2*MAX_ZOOM.get() : (BILIN_DOWNSAMP.get() ? 0.5 : 0));
104 bilinLower = (BILIN_DOWNSAMP.get() ? 0 : 1);
105 }
106 }
107
108 /**
109 * Manage the visible rectangle of an image with full bounds stored in init.
110 * @since 13127
111 */
112 public static class VisRect extends Rectangle {
113 private final Rectangle init;
114
115 /**
116 * Constructs a new {@code VisRect}.
117 * @param x the specified X coordinate
118 * @param y the specified Y coordinate
119 * @param width the width of the rectangle
120 * @param height the height of the rectangle
121 */
122 public VisRect(int x, int y, int width, int height) {
123 super(x, y, width, height);
124 init = new Rectangle(this);
125 }
126
127 public VisRect(int x, int y, int width, int height, VisRect peer) {
128 super(x, y, width, height);
129 init = peer.init;
130 }
131
132 /**
133 * Constructs a new {@code VisRect} from another one.
134 * @param v rectangle to copy
135 */
136 public VisRect(VisRect v) {
137 super(v);
138 init = v.init;
139 }
140
141 /**
142 * Constructs a new empty {@code VisRect}.
143 */
144 public VisRect() {
145 this(0, 0, 0, 0);
146 }
147
148 public boolean isFullView() {
149 return init.equals(this);
150 }
151
152 public boolean isFullView1D() {
153 return (init.x == x && init.width == width)
154 || (init.y == y && init.height == height);
155 }
156
157 public void reset() {
158 setBounds(init);
159 }
160
161 public void checkRectPos() {
162 if (x < 0) {
163 x = 0;
164 }
165 if (y < 0) {
166 y = 0;
167 }
168 if (x + width > init.width) {
169 x = init.width - width;
170 }
171 if (y + height > init.height) {
172 y = init.height - height;
173 }
174 }
175
176 public void checkRectSize() {
177 if (width > init.width) {
178 width = init.width;
179 }
180 if (height > init.height) {
181 height = init.height;
182 }
183 }
184
185 public void checkPointInside(Point p) {
186 if (p.x < x) {
187 p.x = x;
188 }
189 if (p.x > x + width) {
190 p.x = x + width;
191 }
192 if (p.y < y) {
193 p.y = y;
194 }
195 if (p.y > y + height) {
196 p.y = y + height;
197 }
198 }
199 }
200
201 /** The thread that reads the images. */
202 private class LoadImageRunnable implements Runnable {
203
204 private final File file;
205 private final int orientation;
206
207 LoadImageRunnable(File file, Integer orientation) {
208 this.file = file;
209 this.orientation = orientation == null ? -1 : orientation;
210 }
211
212 @Override
213 public void run() {
214 Image img = Toolkit.getDefaultToolkit().createImage(file.getPath());
215 tracker.addImage(img, 1);
216
217 // Wait for the end of loading
218 while (!tracker.checkID(1, true)) {
219 if (this.file != ImageDisplay.this.file) {
220 // The file has changed
221 tracker.removeImage(img);
222 return;
223 }
224 try {
225 Thread.sleep(5);
226 } catch (InterruptedException e) {
227 Logging.warn("InterruptedException in "+getClass().getSimpleName()+" while loading image "+file.getPath());
228 Thread.currentThread().interrupt();
229 }
230 }
231
232 boolean error = tracker.isErrorID(1);
233 if (img.getWidth(null) < 0 || img.getHeight(null) < 0) {
234 error = true;
235 }
236
237 synchronized (ImageDisplay.this) {
238 if (this.file != ImageDisplay.this.file) {
239 // The file has changed
240 tracker.removeImage(img);
241 return;
242 }
243
244 if (!error) {
245 ImageDisplay.this.image = img;
246 visibleRect = new VisRect(0, 0, img.getWidth(null), img.getHeight(null));
247
248 final int w = (int) visibleRect.getWidth();
249 final int h = (int) visibleRect.getHeight();
250
251 if (ExifReader.orientationNeedsCorrection(orientation)) {
252 final int hh, ww;
253 if (ExifReader.orientationSwitchesDimensions(orientation)) {
254 ww = h;
255 hh = w;
256 } else {
257 ww = w;
258 hh = h;
259 }
260 final BufferedImage rot = new BufferedImage(ww, hh, BufferedImage.TYPE_INT_RGB);
261 final AffineTransform xform = ExifReader.getRestoreOrientationTransform(orientation, w, h);
262 final Graphics2D g = rot.createGraphics();
263 g.drawImage(image, xform, null);
264 g.dispose();
265
266 visibleRect.setSize(ww, hh);
267 image.flush();
268 ImageDisplay.this.image = rot;
269 }
270 }
271
272 selectedRect = null;
273 errorLoading = error;
274 }
275 tracker.removeImage(img);
276 ImageDisplay.this.repaint();
277 }
278 }
279
280 private class ImgDisplayMouseListener implements MouseListener, MouseWheelListener, MouseMotionListener {
281
282 private MouseEvent lastMouseEvent;
283 private Point mousePointInImg;
284
285 private boolean mouseIsDragging(MouseEvent e) {
286 return (dragButton == 1 && SwingUtilities.isLeftMouseButton(e)) ||
287 (dragButton == 2 && SwingUtilities.isMiddleMouseButton(e)) ||
288 (dragButton == 3 && SwingUtilities.isRightMouseButton(e));
289 }
290
291 private boolean mouseIsZoomSelecting(MouseEvent e) {
292 return (zoomButton == 1 && SwingUtilities.isLeftMouseButton(e)) ||
293 (zoomButton == 2 && SwingUtilities.isMiddleMouseButton(e)) ||
294 (zoomButton == 3 && SwingUtilities.isRightMouseButton(e));
295 }
296
297 private boolean isAtMaxZoom(Rectangle visibleRect) {
298 return (visibleRect.width == (int) (getSize().width / MAX_ZOOM.get()) ||
299 visibleRect.height == (int) (getSize().height / MAX_ZOOM.get()));
300 }
301
302 private void mouseWheelMovedImpl(int x, int y, int rotation, boolean refreshMousePointInImg) {
303 File file;
304 Image image;
305 VisRect visibleRect;
306
307 synchronized (ImageDisplay.this) {
308 file = ImageDisplay.this.file;
309 image = ImageDisplay.this.image;
310 visibleRect = ImageDisplay.this.visibleRect;
311 }
312
313 selectedRect = null;
314
315 if (image == null)
316 return;
317
318 // Calculate the mouse cursor position in image coordinates to center the zoom.
319 if (refreshMousePointInImg)
320 mousePointInImg = comp2imgCoord(visibleRect, x, y, getSize());
321
322 // Apply the zoom to the visible rectangle in image coordinates
323 if (rotation > 0) {
324 visibleRect.width = (int) (visibleRect.width * ZOOM_STEP.get());
325 visibleRect.height = (int) (visibleRect.height * ZOOM_STEP.get());
326 } else {
327 visibleRect.width = (int) (visibleRect.width / ZOOM_STEP.get());
328 visibleRect.height = (int) (visibleRect.height / ZOOM_STEP.get());
329 }
330
331 // Check that the zoom doesn't exceed MAX_ZOOM:1
332 if (visibleRect.width < getSize().width / MAX_ZOOM.get()) {
333 visibleRect.width = (int) (getSize().width / MAX_ZOOM.get());
334 }
335 if (visibleRect.height < getSize().height / MAX_ZOOM.get()) {
336 visibleRect.height = (int) (getSize().height / MAX_ZOOM.get());
337 }
338
339 // Set the same ratio for the visible rectangle and the display area
340 int hFact = visibleRect.height * getSize().width;
341 int wFact = visibleRect.width * getSize().height;
342 if (hFact > wFact) {
343 visibleRect.width = hFact / getSize().height;
344 } else {
345 visibleRect.height = wFact / getSize().width;
346 }
347
348 // The size of the visible rectangle is limited by the image size.
349 visibleRect.checkRectSize();
350
351 // Set the position of the visible rectangle, so that the mouse cursor doesn't move on the image.
352 Rectangle drawRect = calculateDrawImageRectangle(visibleRect, getSize());
353 visibleRect.x = mousePointInImg.x + ((drawRect.x - x) * visibleRect.width) / drawRect.width;
354 visibleRect.y = mousePointInImg.y + ((drawRect.y - y) * visibleRect.height) / drawRect.height;
355
356 // The position is also limited by the image size
357 visibleRect.checkRectPos();
358
359 synchronized (ImageDisplay.this) {
360 if (ImageDisplay.this.file == file) {
361 ImageDisplay.this.visibleRect = visibleRect;
362 }
363 }
364 ImageDisplay.this.repaint();
365 }
366
367 /** Zoom in and out, trying to preserve the point of the image that was under the mouse cursor
368 * at the same place */
369 @Override
370 public void mouseWheelMoved(MouseWheelEvent e) {
371 boolean refreshMousePointInImg = false;
372
373 // To avoid issues when the user tries to zoom in on the image borders, this
374 // point is not recalculated as long as e occurs at roughly the same position.
375 if (lastMouseEvent == null || mousePointInImg == null ||
376 ((lastMouseEvent.getX()-e.getX())*(lastMouseEvent.getX()-e.getX())
377 +(lastMouseEvent.getY()-e.getY())*(lastMouseEvent.getY()-e.getY()) > 4*4)) {
378 lastMouseEvent = e;
379 refreshMousePointInImg = true;
380 }
381
382 mouseWheelMovedImpl(e.getX(), e.getY(), e.getWheelRotation(), refreshMousePointInImg);
383 }
384
385 /** Center the display on the point that has been clicked */
386 @Override
387 public void mouseClicked(MouseEvent e) {
388 // Move the center to the clicked point.
389 File file;
390 Image image;
391 VisRect visibleRect;
392
393 synchronized (ImageDisplay.this) {
394 file = ImageDisplay.this.file;
395 image = ImageDisplay.this.image;
396 visibleRect = ImageDisplay.this.visibleRect;
397 }
398
399 if (image == null)
400 return;
401
402 if (ZOOM_ON_CLICK.get()) {
403 // click notions are less coherent than wheel, refresh mousePointInImg on each click
404 lastMouseEvent = null;
405
406 if (mouseIsZoomSelecting(e) && !isAtMaxZoom(visibleRect)) {
407 // zoom in if clicked with the zoom button
408 mouseWheelMovedImpl(e.getX(), e.getY(), -1, true);
409 return;
410 }
411 if (mouseIsDragging(e)) {
412 // zoom out if clicked with the drag button
413 mouseWheelMovedImpl(e.getX(), e.getY(), 1, true);
414 return;
415 }
416 }
417
418 // Calculate the translation to set the clicked point the center of the view.
419 Point click = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize());
420 Point center = getCenterImgCoord(visibleRect);
421
422 visibleRect.x += click.x - center.x;
423 visibleRect.y += click.y - center.y;
424
425 visibleRect.checkRectPos();
426
427 synchronized (ImageDisplay.this) {
428 if (ImageDisplay.this.file == file) {
429 ImageDisplay.this.visibleRect = visibleRect;
430 }
431 }
432 ImageDisplay.this.repaint();
433 }
434
435 /** Initialize the dragging, either with button 1 (simple dragging) or button 3 (selection of
436 * a picture part) */
437 @Override
438 public void mousePressed(MouseEvent e) {
439 Image image;
440 VisRect visibleRect;
441
442 synchronized (ImageDisplay.this) {
443 image = ImageDisplay.this.image;
444 visibleRect = ImageDisplay.this.visibleRect;
445 }
446
447 if (image == null)
448 return;
449
450 selectedRect = null;
451
452 if (mouseIsDragging(e) || mouseIsZoomSelecting(e))
453 mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize());
454 }
455
456 @Override
457 public void mouseDragged(MouseEvent e) {
458 if (!mouseIsDragging(e) && !mouseIsZoomSelecting(e))
459 return;
460
461 File file;
462 Image image;
463 VisRect visibleRect;
464
465 synchronized (ImageDisplay.this) {
466 file = ImageDisplay.this.file;
467 image = ImageDisplay.this.image;
468 visibleRect = ImageDisplay.this.visibleRect;
469 }
470
471 if (image == null)
472 return;
473
474 if (mouseIsDragging(e)) {
475 Point p = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize());
476 visibleRect.x += mousePointInImg.x - p.x;
477 visibleRect.y += mousePointInImg.y - p.y;
478 visibleRect.checkRectPos();
479 synchronized (ImageDisplay.this) {
480 if (ImageDisplay.this.file == file) {
481 ImageDisplay.this.visibleRect = visibleRect;
482 }
483 }
484 ImageDisplay.this.repaint();
485 }
486
487 if (mouseIsZoomSelecting(e)) {
488 Point p = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize());
489 visibleRect.checkPointInside(p);
490 VisRect selectedRect = new VisRect(
491 p.x < mousePointInImg.x ? p.x : mousePointInImg.x,
492 p.y < mousePointInImg.y ? p.y : mousePointInImg.y,
493 p.x < mousePointInImg.x ? mousePointInImg.x - p.x : p.x - mousePointInImg.x,
494 p.y < mousePointInImg.y ? mousePointInImg.y - p.y : p.y - mousePointInImg.y,
495 visibleRect);
496 selectedRect.checkRectSize();
497 selectedRect.checkRectPos();
498 ImageDisplay.this.selectedRect = selectedRect;
499 ImageDisplay.this.repaint();
500 }
501
502 }
503
504 @Override
505 public void mouseReleased(MouseEvent e) {
506 if (!mouseIsZoomSelecting(e) || selectedRect == null)
507 return;
508
509 File file;
510 Image image;
511
512 synchronized (ImageDisplay.this) {
513 file = ImageDisplay.this.file;
514 image = ImageDisplay.this.image;
515 }
516
517 if (image == null) {
518 return;
519 }
520
521 int oldWidth = selectedRect.width;
522 int oldHeight = selectedRect.height;
523
524 // Check that the zoom doesn't exceed MAX_ZOOM:1
525 if (selectedRect.width < getSize().width / MAX_ZOOM.get()) {
526 selectedRect.width = (int) (getSize().width / MAX_ZOOM.get());
527 }
528 if (selectedRect.height < getSize().height / MAX_ZOOM.get()) {
529 selectedRect.height = (int) (getSize().height / MAX_ZOOM.get());
530 }
531
532 // Set the same ratio for the visible rectangle and the display area
533 int hFact = selectedRect.height * getSize().width;
534 int wFact = selectedRect.width * getSize().height;
535 if (hFact > wFact) {
536 selectedRect.width = hFact / getSize().height;
537 } else {
538 selectedRect.height = wFact / getSize().width;
539 }
540
541 // Keep the center of the selection
542 if (selectedRect.width != oldWidth) {
543 selectedRect.x -= (selectedRect.width - oldWidth) / 2;
544 }
545 if (selectedRect.height != oldHeight) {
546 selectedRect.y -= (selectedRect.height - oldHeight) / 2;
547 }
548
549 selectedRect.checkRectSize();
550 selectedRect.checkRectPos();
551
552 synchronized (ImageDisplay.this) {
553 if (file == ImageDisplay.this.file) {
554 ImageDisplay.this.visibleRect.setBounds(selectedRect);
555 }
556 }
557 selectedRect = null;
558 ImageDisplay.this.repaint();
559 }
560
561 @Override
562 public void mouseEntered(MouseEvent e) {
563 // Do nothing
564 }
565
566 @Override
567 public void mouseExited(MouseEvent e) {
568 // Do nothing
569 }
570
571 @Override
572 public void mouseMoved(MouseEvent e) {
573 // Do nothing
574 }
575 }
576
577 /**
578 * Constructs a new {@code ImageDisplay}.
579 */
580 public ImageDisplay() {
581 ImgDisplayMouseListener mouseListener = new ImgDisplayMouseListener();
582 addMouseListener(mouseListener);
583 addMouseWheelListener(mouseListener);
584 addMouseMotionListener(mouseListener);
585 Config.getPref().addPreferenceChangeListener(this);
586 preferenceChanged(null);
587 }
588
589 public void setImage(File file, Integer orientation) {
590 synchronized (this) {
591 this.file = file;
592 image = null;
593 errorLoading = false;
594 }
595 repaint();
596 if (file != null) {
597 new Thread(new LoadImageRunnable(file, orientation), LoadImageRunnable.class.getName()).start();
598 }
599 }
600
601 /**
602 * Sets the On-Screen-Display text.
603 * @param text text to display on top of the image
604 */
605 public void setOsdText(String text) {
606 this.osdText = text;
607 repaint();
608 }
609
610 @Override
611 public void paintComponent(Graphics g) {
612 Image image;
613 File file;
614 VisRect visibleRect;
615 boolean errorLoading;
616
617 synchronized (this) {
618 image = this.image;
619 file = this.file;
620 visibleRect = this.visibleRect;
621 errorLoading = this.errorLoading;
622 }
623
624 if (g instanceof Graphics2D) {
625 ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
626 }
627
628 Dimension size = getSize();
629 if (file == null) {
630 g.setColor(Color.black);
631 String noImageStr = tr("No image");
632 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(noImageStr, g);
633 g.drawString(noImageStr,
634 (int) ((size.width - noImageSize.getWidth()) / 2),
635 (int) ((size.height - noImageSize.getHeight()) / 2));
636 } else if (image == null) {
637 g.setColor(Color.black);
638 String loadingStr;
639 if (!errorLoading) {
640 loadingStr = tr("Loading {0}", file.getName());
641 } else {
642 loadingStr = tr("Error on file {0}", file.getName());
643 }
644 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
645 g.drawString(loadingStr,
646 (int) ((size.width - noImageSize.getWidth()) / 2),
647 (int) ((size.height - noImageSize.getHeight()) / 2));
648 } else {
649 Rectangle r = new Rectangle(visibleRect);
650 Rectangle target = calculateDrawImageRectangle(visibleRect, size);
651 double scale = target.width / (double) r.width; // pixel ratio is 1:1
652
653 if (selectedRect == null && bilinLower < scale && scale < bilinUpper) {
654 BufferedImage bi = ImageProvider.toBufferedImage(image, r);
655 r.x = r.y = 0;
656
657 // See https://community.oracle.com/docs/DOC-983611 - The Perils of Image.getScaledInstance()
658 // Pre-scale image when downscaling by more than two times to avoid aliasing from default algorithm
659 image = ImageProvider.createScaledImage(bi, target.width, target.height,
660 RenderingHints.VALUE_INTERPOLATION_BILINEAR);
661 r.width = target.width;
662 r.height = target.height;
663 } else {
664 // if target and r cause drawImage to scale image region to a tmp buffer exceeding
665 // its bounds, it will silently fail; crop with r first in such cases
666 // (might be impl. dependent, exhibited by openjdk 1.8.0_151)
667 if (scale*(r.x+r.width) > Short.MAX_VALUE || scale*(r.y+r.height) > Short.MAX_VALUE) {
668 image = ImageProvider.toBufferedImage(image, r);
669 r.x = r.y = 0;
670 }
671 }
672
673 g.drawImage(image,
674 target.x, target.y, target.x + target.width, target.y + target.height,
675 r.x, r.y, r.x + r.width, r.y + r.height, null);
676
677 if (selectedRect != null) {
678 Point topLeft = img2compCoord(visibleRect, selectedRect.x, selectedRect.y, size);
679 Point bottomRight = img2compCoord(visibleRect,
680 selectedRect.x + selectedRect.width,
681 selectedRect.y + selectedRect.height, size);
682 g.setColor(new Color(128, 128, 128, 180));
683 g.fillRect(target.x, target.y, target.width, topLeft.y - target.y);
684 g.fillRect(target.x, target.y, topLeft.x - target.x, target.height);
685 g.fillRect(bottomRight.x, target.y, target.x + target.width - bottomRight.x, target.height);
686 g.fillRect(target.x, bottomRight.y, target.width, target.y + target.height - bottomRight.y);
687 g.setColor(Color.black);
688 g.drawRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y);
689 }
690 if (errorLoading) {
691 String loadingStr = tr("Error on file {0}", file.getName());
692 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
693 g.drawString(loadingStr,
694 (int) ((size.width - noImageSize.getWidth()) / 2),
695 (int) ((size.height - noImageSize.getHeight()) / 2));
696 }
697 if (osdText != null) {
698 FontMetrics metrics = g.getFontMetrics(g.getFont());
699 int ascent = metrics.getAscent();
700 Color bkground = new Color(255, 255, 255, 128);
701 int lastPos = 0;
702 int pos = osdText.indexOf('\n');
703 int x = 3;
704 int y = 3;
705 String line;
706 while (pos > 0) {
707 line = osdText.substring(lastPos, pos);
708 Rectangle2D lineSize = metrics.getStringBounds(line, g);
709 g.setColor(bkground);
710 g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight());
711 g.setColor(Color.black);
712 g.drawString(line, x, y + ascent);
713 y += (int) lineSize.getHeight();
714 lastPos = pos + 1;
715 pos = osdText.indexOf('\n', lastPos);
716 }
717
718 line = osdText.substring(lastPos);
719 Rectangle2D lineSize = g.getFontMetrics(g.getFont()).getStringBounds(line, g);
720 g.setColor(bkground);
721 g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight());
722 g.setColor(Color.black);
723 g.drawString(line, x, y + ascent);
724 }
725 }
726 }
727
728 static Point img2compCoord(VisRect visibleRect, int xImg, int yImg, Dimension compSize) {
729 Rectangle drawRect = calculateDrawImageRectangle(visibleRect, compSize);
730 return new Point(drawRect.x + ((xImg - visibleRect.x) * drawRect.width) / visibleRect.width,
731 drawRect.y + ((yImg - visibleRect.y) * drawRect.height) / visibleRect.height);
732 }
733
734 static Point comp2imgCoord(VisRect visibleRect, int xComp, int yComp, Dimension compSize) {
735 Rectangle drawRect = calculateDrawImageRectangle(visibleRect, compSize);
736 Point p = new Point(
737 ((xComp - drawRect.x) * visibleRect.width),
738 ((yComp - drawRect.y) * visibleRect.height));
739 p.x += (((p.x % drawRect.width) << 1) >= drawRect.width) ? drawRect.width : 0;
740 p.y += (((p.y % drawRect.height) << 1) >= drawRect.height) ? drawRect.height : 0;
741 p.x = visibleRect.x + p.x / drawRect.width;
742 p.y = visibleRect.y + p.y / drawRect.height;
743 return p;
744 }
745
746 static Point getCenterImgCoord(Rectangle visibleRect) {
747 return new Point(visibleRect.x + visibleRect.width / 2,
748 visibleRect.y + visibleRect.height / 2);
749 }
750
751 static VisRect calculateDrawImageRectangle(VisRect visibleRect, Dimension compSize) {
752 return calculateDrawImageRectangle(visibleRect, new Rectangle(0, 0, compSize.width, compSize.height));
753 }
754
755 /**
756 * calculateDrawImageRectangle
757 *
758 * @param imgRect the part of the image that should be drawn (in image coordinates)
759 * @param compRect the part of the component where the image should be drawn (in component coordinates)
760 * @return the part of compRect with the same width/height ratio as the image
761 */
762 static VisRect calculateDrawImageRectangle(VisRect imgRect, Rectangle compRect) {
763 int x = 0;
764 int y = 0;
765 int w = compRect.width;
766 int h = compRect.height;
767
768 int wFact = w * imgRect.height;
769 int hFact = h * imgRect.width;
770 if (wFact != hFact) {
771 if (wFact > hFact) {
772 w = hFact / imgRect.height;
773 x = (compRect.width - w) / 2;
774 } else {
775 h = wFact / imgRect.width;
776 y = (compRect.height - h) / 2;
777 }
778 }
779
780 // overscan to prevent empty edges when zooming in to zoom scales > 2:1
781 if (w > imgRect.width && h > imgRect.height && !imgRect.isFullView1D() && wFact != hFact) {
782 if (wFact > hFact) {
783 w = compRect.width;
784 x = 0;
785 h = wFact / imgRect.width;
786 y = (compRect.height - h) / 2;
787 } else {
788 h = compRect.height;
789 y = 0;
790 w = hFact / imgRect.height;
791 x = (compRect.width - w) / 2;
792 }
793 }
794
795 return new VisRect(x + compRect.x, y + compRect.y, w, h, imgRect);
796 }
797
798 public void zoomBestFitOrOne() {
799 File file;
800 Image image;
801 VisRect visibleRect;
802
803 synchronized (this) {
804 file = this.file;
805 image = this.image;
806 visibleRect = this.visibleRect;
807 }
808
809 if (image == null)
810 return;
811
812 if (visibleRect.width != image.getWidth(null) || visibleRect.height != image.getHeight(null)) {
813 // The display is not at best fit. => Zoom to best fit
814 visibleRect.reset();
815 } else {
816 // The display is at best fit => zoom to 1:1
817 Point center = getCenterImgCoord(visibleRect);
818 visibleRect.setBounds(center.x - getWidth() / 2, center.y - getHeight() / 2,
819 getWidth(), getHeight());
820 visibleRect.checkRectSize();
821 visibleRect.checkRectPos();
822 }
823
824 synchronized (this) {
825 if (file == this.file) {
826 this.visibleRect = visibleRect;
827 }
828 }
829 repaint();
830 }
831}
Note: See TracBrowser for help on using the repository browser.