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

Last change on this file since 13129 was 13129, checked in by Don-vip, 11 months ago

see #15476 - fix checkstyle/pmd warnings, add some javadoc

  • Property svn:eol-style set to native
File size: 31.2 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_STYLE2 =
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_STYLE2.getKey())) {
96            dragButton = AGPIFO_STYLE2.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()) {
782            if (wFact != hFact) {
783                if (wFact > hFact) {
784                    w = compRect.width;
785                    x = 0;
786                    h = wFact / imgRect.width;
787                    y = (compRect.height - h) / 2;
788                } else {
789                    h = compRect.height;
790                    y = 0;
791                    w = hFact / imgRect.height;
792                    x = (compRect.width - w) / 2;
793                }
794            }
795        }
796
797        return new VisRect(x + compRect.x, y + compRect.y, w, h, imgRect);
798    }
799
800    public void zoomBestFitOrOne() {
801        File file;
802        Image image;
803        VisRect visibleRect;
804
805        synchronized (this) {
806            file = this.file;
807            image = this.image;
808            visibleRect = this.visibleRect;
809        }
810
811        if (image == null)
812            return;
813
814        if (visibleRect.width != image.getWidth(null) || visibleRect.height != image.getHeight(null)) {
815            // The display is not at best fit. => Zoom to best fit
816            visibleRect.reset();
817        } else {
818            // The display is at best fit => zoom to 1:1
819            Point center = getCenterImgCoord(visibleRect);
820            visibleRect.setBounds(center.x - getWidth() / 2, center.y - getHeight() / 2,
821                    getWidth(), getHeight());
822            visibleRect.checkRectSize();
823            visibleRect.checkRectPos();
824        }
825
826        synchronized (this) {
827            if (file == this.file) {
828                this.visibleRect = visibleRect;
829            }
830        }
831        repaint();
832    }
833}
Note: See TracBrowser for help on using the repository browser.