Changeset 13127 in josm


Ignore:
Timestamp:
2017-11-19T00:10:41+01:00 (4 weeks ago)
Author:
Don-vip
Message:

see #15476, fix #15511 - fix image scaling regression and makes geoimage feature more configurable through prefs (adjustable max zoom, zoom-step, click zooming with mouse buttons (e.g. if a mouse wheel is not present). Patch by cmuelle8

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java

    r13038 r13127  
    2626
    2727import javax.swing.JComponent;
    28 
     28import javax.swing.SwingUtilities;
     29
     30import org.openstreetmap.josm.data.preferences.BooleanProperty;
     31import org.openstreetmap.josm.data.preferences.DoubleProperty;
    2932import org.openstreetmap.josm.spi.preferences.Config;
     33import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
     34import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
    3035import org.openstreetmap.josm.tools.ExifReader;
    3136import org.openstreetmap.josm.tools.ImageProvider;
     
    3742 * Offers basic mouse interaction (zoom, drag) and on-screen text.
    3843 */
    39 public class ImageDisplay extends JComponent {
     44public class ImageDisplay extends JComponent implements PreferenceChangedListener {
    4045
    4146    /** The file that is currently displayed */
     
    5055    /** The rectangle (in image coordinates) of the image that is visible. This rectangle is calculated
    5156     * each time the zoom is modified */
    52     private Rectangle visibleRect;
     57    private VisRect visibleRect;
    5358
    5459    /** When a selection is done, the rectangle of the selection (in image coordinates) */
    55     private Rectangle selectedRect;
     60    private VisRect selectedRect;
    5661
    5762    /** The tracker to load the images */
     
    6065    private String osdText;
    6166
    62     private static final int DRAG_BUTTON = Config.getPref().getBoolean("geoimage.agpifo-style-drag-and-zoom", false) ? 1 : 3;
    63     private static final int ZOOM_BUTTON = DRAG_BUTTON == 1 ? 3 : 1;
     67    private static final BooleanProperty AGPIFO_STYLE2 =
     68        new BooleanProperty("geoimage.agpifo-style-drag-and-zoom", false);
     69    private static int DRAG_BUTTON;
     70    private static int ZOOM_BUTTON;
     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 BILIN_UPPER;
     90    private static double BILIN_LOWER;
     91
     92    @Override
     93    public void preferenceChanged(PreferenceChangeEvent e) {
     94        if (e == null ||
     95            e.getKey().equals(AGPIFO_STYLE2.getKey()))
     96        {
     97            DRAG_BUTTON = AGPIFO_STYLE2.get() ? 1 : 3;
     98            ZOOM_BUTTON = DRAG_BUTTON == 1 ? 3 : 1;
     99        }
     100        if (e == null ||
     101            e.getKey().equals(MAX_ZOOM.getKey()) ||
     102            e.getKey().equals(BILIN_DOWNSAMP.getKey()) ||
     103            e.getKey().equals(BILIN_UPSAMP.getKey()))
     104        {
     105            BILIN_UPPER = (BILIN_UPSAMP.get() ? 2*MAX_ZOOM.get() : (BILIN_DOWNSAMP.get() ? 0.5 : 0));
     106            BILIN_LOWER = (BILIN_DOWNSAMP.get() ? 0 : 1);
     107        }
     108    }
     109
     110    /** Manage the visible rectangle of an image with full bounds stored in init. **/
     111    public static class VisRect extends Rectangle {
     112        private final Rectangle init;
     113
     114        public VisRect(int x, int y, int width, int height) {
     115            super(x, y, width, height);
     116            init = new Rectangle(this);
     117        }
     118
     119        public VisRect(int x, int y, int width, int height, VisRect peer) {
     120            super(x, y, width, height);
     121            init = peer.init;
     122        }
     123
     124        public VisRect(VisRect v) {
     125            super(v);
     126            init = v.init;
     127        }
     128
     129        public VisRect() {
     130            this(0, 0, 0, 0);
     131        }
     132
     133        public boolean isFullView() {
     134            return init.equals(this);
     135        }
     136
     137        public boolean isFullView1D() {
     138            return (init.x == x && init.width == width)
     139                || (init.y == y && init.height == height);
     140        }
     141
     142        public void reset() {
     143            setBounds(init);
     144        }
     145
     146        public void checkRectPos() {
     147            if (x < 0) {
     148                x = 0;
     149            }
     150            if (y < 0) {
     151                y = 0;
     152            }
     153            if (x + width > init.width) {
     154                x = init.width - width;
     155            }
     156            if (y + height > init.height) {
     157                y = init.height - height;
     158            }
     159        }
     160
     161        public void checkRectSize() {
     162            if (width > init.width) {
     163                width = init.width;
     164            }
     165            if (height > init.height) {
     166                height = init.height;
     167            }
     168        }
     169
     170        public void checkPointInside(Point p) {
     171            if (p.x < x) {
     172                p.x = x;
     173            }
     174            if (p.x > x + width) {
     175                p.x = x + width;
     176            }
     177            if (p.y < y) {
     178                p.y = y;
     179            }
     180            if (p.y > y + height) {
     181                p.y = y + height;
     182            }
     183        }
     184    }
    64185
    65186    /** The thread that reads the images. */
     
    108229                if (!error) {
    109230                    ImageDisplay.this.image = img;
    110                     visibleRect = new Rectangle(0, 0, img.getWidth(null), img.getHeight(null));
     231                    visibleRect = new VisRect(0, 0, img.getWidth(null), img.getHeight(null));
    111232
    112233                    final int w = (int) visibleRect.getWidth();
     
    144265    private class ImgDisplayMouseListener implements MouseListener, MouseWheelListener, MouseMotionListener {
    145266
    146         private boolean mouseIsDragging;
    147         private long lastTimeForMousePoint;
     267        private MouseEvent lastMouseEvent;
    148268        private Point mousePointInImg;
    149269
    150         /** Zoom in and out, trying to preserve the point of the image that was under the mouse cursor
    151          * at the same place */
    152         @Override
    153         public void mouseWheelMoved(MouseWheelEvent e) {
     270        private boolean mouseIsDragging(MouseEvent e) {
     271            return (DRAG_BUTTON == 1 && SwingUtilities.isLeftMouseButton(e)) ||
     272                   (DRAG_BUTTON == 2 && SwingUtilities.isMiddleMouseButton(e)) ||
     273                   (DRAG_BUTTON == 3 && SwingUtilities.isRightMouseButton(e));
     274        }
     275
     276        private boolean mouseIsZoomSelecting(MouseEvent e) {
     277            return (ZOOM_BUTTON == 1 && SwingUtilities.isLeftMouseButton(e)) ||
     278                   (ZOOM_BUTTON == 2 && SwingUtilities.isMiddleMouseButton(e)) ||
     279                   (ZOOM_BUTTON == 3 && SwingUtilities.isRightMouseButton(e));
     280        }
     281
     282        private boolean isAtMaxZoom(Rectangle visibleRect) {
     283            return (visibleRect.width == (int) (getSize().width / MAX_ZOOM.get()) ||
     284                    visibleRect.height == (int) (getSize().height / MAX_ZOOM.get()));
     285        }
     286
     287        private void mouseWheelMovedImpl(int x, int y, int rotation, boolean refreshMousePointInImg) {
    154288            File file;
    155289            Image image;
    156             Rectangle visibleRect;
     290            VisRect visibleRect;
    157291
    158292            synchronized (ImageDisplay.this) {
     
    162296            }
    163297
    164             mouseIsDragging = false;
    165298            selectedRect = null;
    166299
     
    168301                return;
    169302
    170             // Calculate the mouse cursor position in image coordinates, so that we can center the zoom
    171             // on that mouse position.
    172             // To avoid issues when the user tries to zoom in on the image borders, this point is not calculated
    173             // again if there was less than 1.5seconds since the last event.
    174             if (e.getWhen() - lastTimeForMousePoint > 1500 || mousePointInImg == null) {
    175                 lastTimeForMousePoint = e.getWhen();
    176                 mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize());
    177             }
    178 
    179             // Applicate the zoom to the visible rectangle in image coordinates
    180             if (e.getWheelRotation() > 0) {
    181                 visibleRect.width = visibleRect.width * 3 / 2;
    182                 visibleRect.height = visibleRect.height * 3 / 2;
     303            // Calculate the mouse cursor position in image coordinates to center the zoom.
     304            if (refreshMousePointInImg)
     305                mousePointInImg = comp2imgCoord(visibleRect, x, y, getSize());
     306
     307            // Apply the zoom to the visible rectangle in image coordinates
     308            if (rotation > 0) {
     309                visibleRect.width = (int) (visibleRect.width * ZOOM_STEP.get());
     310                visibleRect.height = (int) (visibleRect.height * ZOOM_STEP.get());
    183311            } else {
    184                 visibleRect.width = visibleRect.width * 2 / 3;
    185                 visibleRect.height = visibleRect.height * 2 / 3;
    186             }
    187 
    188             // Check that the zoom doesn't exceed 2:1
    189             if (visibleRect.width < getSize().width / 2) {
    190                 visibleRect.width = getSize().width / 2;
    191             }
    192             if (visibleRect.height < getSize().height / 2) {
    193                 visibleRect.height = getSize().height / 2;
     312                visibleRect.width = (int) (visibleRect.width / ZOOM_STEP.get());
     313                visibleRect.height = (int) (visibleRect.height / ZOOM_STEP.get());
     314            }
     315
     316            // Check that the zoom doesn't exceed MAX_ZOOM:1
     317            if (visibleRect.width < getSize().width / MAX_ZOOM.get()) {
     318                visibleRect.width = (int) (getSize().width / MAX_ZOOM.get());
     319            }
     320            if (visibleRect.height < getSize().height / MAX_ZOOM.get()) {
     321                visibleRect.height = (int) (getSize().height / MAX_ZOOM.get());
    194322            }
    195323
     
    204332
    205333            // The size of the visible rectangle is limited by the image size.
    206             checkVisibleRectSize(image, visibleRect);
     334            visibleRect.checkRectSize();
    207335
    208336            // Set the position of the visible rectangle, so that the mouse cursor doesn't move on the image.
    209337            Rectangle drawRect = calculateDrawImageRectangle(visibleRect, getSize());
    210             visibleRect.x = mousePointInImg.x + ((drawRect.x - e.getX()) * visibleRect.width) / drawRect.width;
    211             visibleRect.y = mousePointInImg.y + ((drawRect.y - e.getY()) * visibleRect.height) / drawRect.height;
     338            visibleRect.x = mousePointInImg.x + ((drawRect.x - x) * visibleRect.width) / drawRect.width;
     339            visibleRect.y = mousePointInImg.y + ((drawRect.y - y) * visibleRect.height) / drawRect.height;
    212340
    213341            // The position is also limited by the image size
    214             checkVisibleRectPos(image, visibleRect);
     342            visibleRect.checkRectPos();
    215343
    216344            synchronized (ImageDisplay.this) {
     
    220348            }
    221349            ImageDisplay.this.repaint();
     350        }
     351
     352        /** Zoom in and out, trying to preserve the point of the image that was under the mouse cursor
     353         * at the same place */
     354        @Override
     355        public void mouseWheelMoved(MouseWheelEvent e) {
     356            boolean refreshMousePointInImg = false;
     357
     358            // To avoid issues when the user tries to zoom in on the image borders, this
     359            // point is not recalculated as long as e occurs at roughly the same position.
     360            if (lastMouseEvent == null || mousePointInImg == null ||
     361                ((lastMouseEvent.getX()-e.getX())*(lastMouseEvent.getX()-e.getX())
     362                +(lastMouseEvent.getY()-e.getY())*(lastMouseEvent.getY()-e.getY()) > 4*4)) {
     363                lastMouseEvent = e;
     364                refreshMousePointInImg = true;
     365            }
     366
     367            mouseWheelMovedImpl(e.getX(), e.getY(), e.getWheelRotation(), refreshMousePointInImg);
    222368        }
    223369
     
    228374            File file;
    229375            Image image;
    230             Rectangle visibleRect;
     376            VisRect visibleRect;
    231377
    232378            synchronized (ImageDisplay.this) {
     
    239385                return;
    240386
    241             if (e.getButton() != DRAG_BUTTON)
    242                 return;
     387            if (ZOOM_ON_CLICK.get()) {
     388                // click notions are less coherent than wheel, refresh mousePointInImg on each click
     389                lastMouseEvent = null;
     390
     391                if (mouseIsZoomSelecting(e) && !isAtMaxZoom(visibleRect)) {
     392                    // zoom in if clicked with the zoom button
     393                    mouseWheelMovedImpl(e.getX(), e.getY(), -1, true);
     394                    return;
     395                }
     396                if (mouseIsDragging(e)) {
     397                    // zoom out if clicked with the drag button
     398                    mouseWheelMovedImpl(e.getX(), e.getY(), 1, true);
     399                    return;
     400                }
     401            }
    243402
    244403            // Calculate the translation to set the clicked point the center of the view.
     
    249408            visibleRect.y += click.y - center.y;
    250409
    251             checkVisibleRectPos(image, visibleRect);
     410            visibleRect.checkRectPos();
    252411
    253412            synchronized (ImageDisplay.this) {
     
    263422        @Override
    264423        public void mousePressed(MouseEvent e) {
    265             if (image == null) {
    266                 mouseIsDragging = false;
    267                 selectedRect = null;
    268                 return;
    269             }
    270 
    271424            Image image;
    272             Rectangle visibleRect;
     425            VisRect visibleRect;
    273426
    274427            synchronized (ImageDisplay.this) {
     
    280433                return;
    281434
    282             if (e.getButton() == DRAG_BUTTON) {
     435            selectedRect = null;
     436
     437            if (mouseIsDragging(e) || mouseIsZoomSelecting(e))
    283438                mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize());
    284                 mouseIsDragging = true;
    285                 selectedRect = null;
    286             } else if (e.getButton() == ZOOM_BUTTON) {
    287                 mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize());
    288                 checkPointInVisibleRect(mousePointInImg, visibleRect);
    289                 mouseIsDragging = false;
    290                 selectedRect = new Rectangle(mousePointInImg.x, mousePointInImg.y, 0, 0);
    291                 ImageDisplay.this.repaint();
    292             } else {
    293                 mouseIsDragging = false;
    294                 selectedRect = null;
    295             }
    296439        }
    297440
    298441        @Override
    299442        public void mouseDragged(MouseEvent e) {
    300             if (!mouseIsDragging && selectedRect == null)
     443            if (!mouseIsDragging(e) && !mouseIsZoomSelecting(e))
    301444                return;
    302445
    303446            File file;
    304447            Image image;
    305             Rectangle visibleRect;
     448            VisRect visibleRect;
    306449
    307450            synchronized (ImageDisplay.this) {
     
    311454            }
    312455
    313             if (image == null) {
    314                 mouseIsDragging = false;
    315                 selectedRect = null;
     456            if (image == null)
    316457                return;
    317             }
    318 
    319             if (mouseIsDragging) {
     458
     459            if (mouseIsDragging(e)) {
    320460                Point p = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize());
    321461                visibleRect.x += mousePointInImg.x - p.x;
    322462                visibleRect.y += mousePointInImg.y - p.y;
    323                 checkVisibleRectPos(image, visibleRect);
     463                visibleRect.checkRectPos();
    324464                synchronized (ImageDisplay.this) {
    325465                    if (ImageDisplay.this.file == file) {
     
    328468                }
    329469                ImageDisplay.this.repaint();
    330 
    331             } else if (selectedRect != null) {
     470            }
     471
     472            if (mouseIsZoomSelecting(e)) {
    332473                Point p = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize());
    333                 checkPointInVisibleRect(p, visibleRect);
    334                 Rectangle rect = new Rectangle(
     474                visibleRect.checkPointInside(p);
     475                VisRect selectedRect = new VisRect(
    335476                        p.x < mousePointInImg.x ? p.x : mousePointInImg.x,
    336477                        p.y < mousePointInImg.y ? p.y : mousePointInImg.y,
    337478                        p.x < mousePointInImg.x ? mousePointInImg.x - p.x : p.x - mousePointInImg.x,
    338                         p.y < mousePointInImg.y ? mousePointInImg.y - p.y : p.y - mousePointInImg.y);
    339                 checkVisibleRectSize(image, rect);
    340                 checkVisibleRectPos(image, rect);
    341                 ImageDisplay.this.selectedRect = rect;
     479                        p.y < mousePointInImg.y ? mousePointInImg.y - p.y : p.y - mousePointInImg.y,
     480                        visibleRect);
     481                selectedRect.checkRectSize();
     482                selectedRect.checkRectPos();
     483                ImageDisplay.this.selectedRect = selectedRect;
    342484                ImageDisplay.this.repaint();
    343485            }
     
    347489        @Override
    348490        public void mouseReleased(MouseEvent e) {
    349             if (!mouseIsDragging && selectedRect == null)
     491            if (!mouseIsZoomSelecting(e) || selectedRect == null)
    350492                return;
    351493
     
    359501
    360502            if (image == null) {
    361                 mouseIsDragging = false;
    362                 selectedRect = null;
    363503                return;
    364504            }
    365505
    366             if (mouseIsDragging) {
    367                 mouseIsDragging = false;
    368 
    369             } else if (selectedRect != null) {
    370                 int oldWidth = selectedRect.width;
    371                 int oldHeight = selectedRect.height;
    372 
    373                 // Check that the zoom doesn't exceed 2:1
    374                 if (selectedRect.width < getSize().width / 2) {
    375                     selectedRect.width = getSize().width / 2;
    376                 }
    377                 if (selectedRect.height < getSize().height / 2) {
    378                     selectedRect.height = getSize().height / 2;
    379                 }
    380 
    381                 // Set the same ratio for the visible rectangle and the display area
    382                 int hFact = selectedRect.height * getSize().width;
    383                 int wFact = selectedRect.width * getSize().height;
    384                 if (hFact > wFact) {
    385                     selectedRect.width = hFact / getSize().height;
    386                 } else {
    387                     selectedRect.height = wFact / getSize().width;
    388                 }
    389 
    390                 // Keep the center of the selection
    391                 if (selectedRect.width != oldWidth) {
    392                     selectedRect.x -= (selectedRect.width - oldWidth) / 2;
    393                 }
    394                 if (selectedRect.height != oldHeight) {
    395                     selectedRect.y -= (selectedRect.height - oldHeight) / 2;
    396                 }
    397 
    398                 checkVisibleRectSize(image, selectedRect);
    399                 checkVisibleRectPos(image, selectedRect);
    400 
    401                 synchronized (ImageDisplay.this) {
    402                     if (file == ImageDisplay.this.file) {
    403                         ImageDisplay.this.visibleRect = selectedRect;
    404                     }
    405                 }
    406                 selectedRect = null;
    407                 ImageDisplay.this.repaint();
    408             }
     506            int oldWidth = selectedRect.width;
     507            int oldHeight = selectedRect.height;
     508
     509            // Check that the zoom doesn't exceed MAX_ZOOM:1
     510            if (selectedRect.width < getSize().width / MAX_ZOOM.get()) {
     511                selectedRect.width = (int) (getSize().width / MAX_ZOOM.get());
     512            }
     513            if (selectedRect.height < getSize().height / MAX_ZOOM.get()) {
     514                selectedRect.height = (int) (getSize().height / MAX_ZOOM.get());
     515            }
     516
     517            // Set the same ratio for the visible rectangle and the display area
     518            int hFact = selectedRect.height * getSize().width;
     519            int wFact = selectedRect.width * getSize().height;
     520            if (hFact > wFact) {
     521                selectedRect.width = hFact / getSize().height;
     522            } else {
     523                selectedRect.height = wFact / getSize().width;
     524            }
     525
     526            // Keep the center of the selection
     527            if (selectedRect.width != oldWidth) {
     528                selectedRect.x -= (selectedRect.width - oldWidth) / 2;
     529            }
     530            if (selectedRect.height != oldHeight) {
     531                selectedRect.y -= (selectedRect.height - oldHeight) / 2;
     532            }
     533
     534            selectedRect.checkRectSize();
     535            selectedRect.checkRectPos();
     536
     537            synchronized (ImageDisplay.this) {
     538                if (file == ImageDisplay.this.file) {
     539                    ImageDisplay.this.visibleRect.setBounds(selectedRect);
     540                }
     541            }
     542            selectedRect = null;
     543            ImageDisplay.this.repaint();
    409544        }
    410545
     
    422557        public void mouseMoved(MouseEvent e) {
    423558            // Do nothing
    424         }
    425 
    426         private void checkPointInVisibleRect(Point p, Rectangle visibleRect) {
    427             if (p.x < visibleRect.x) {
    428                 p.x = visibleRect.x;
    429             }
    430             if (p.x > visibleRect.x + visibleRect.width) {
    431                 p.x = visibleRect.x + visibleRect.width;
    432             }
    433             if (p.y < visibleRect.y) {
    434                 p.y = visibleRect.y;
    435             }
    436             if (p.y > visibleRect.y + visibleRect.height) {
    437                 p.y = visibleRect.y + visibleRect.height;
    438             }
    439559        }
    440560    }
     
    448568        addMouseWheelListener(mouseListener);
    449569        addMouseMotionListener(mouseListener);
     570        Config.getPref().addPreferenceChangeListener(this);
     571        preferenceChanged(null);
    450572    }
    451573
     
    454576            this.file = file;
    455577            image = null;
    456             selectedRect = null;
    457578            errorLoading = false;
    458579        }
     
    476597        Image image;
    477598        File file;
    478         Rectangle visibleRect;
     599        VisRect visibleRect;
    479600        boolean errorLoading;
    480601
     
    511632                    (int) ((size.height - noImageSize.getHeight()) / 2));
    512633        } else {
     634            Rectangle r = new Rectangle(visibleRect);
    513635            Rectangle target = calculateDrawImageRectangle(visibleRect, size);
    514             // See https://community.oracle.com/docs/DOC-983611 - The Perils of Image.getScaledInstance()
    515             // Pre-scale image when downscaling by more than two times to avoid aliasing from default algorithm
    516             if (selectedRect == null && (target.width < visibleRect.width/2 || target.height < visibleRect.height/2)) {
    517                 BufferedImage buffImage = ImageProvider.toBufferedImage(image);
    518                 g.drawImage(ImageProvider.createScaledImage(buffImage, target.width, target.height, RenderingHints.VALUE_INTERPOLATION_BILINEAR),
    519                         target.x, target.y, target.x + target.width, target.y + target.height,
    520                         visibleRect.x, visibleRect.y, visibleRect.x + target.width, visibleRect.y + target.height,
    521                         null);
     636            double scale = target.width / (double) r.width; // pixel ratio is 1:1
     637
     638            if (selectedRect == null && BILIN_LOWER < scale && scale < BILIN_UPPER) {
     639                BufferedImage bi = ImageProvider.toBufferedImage(image, r);
     640                r.x = r.y = 0;
     641
     642                // See https://community.oracle.com/docs/DOC-983611 - The Perils of Image.getScaledInstance()
     643                // Pre-scale image when downscaling by more than two times to avoid aliasing from default algorithm
     644                image = ImageProvider.createScaledImage(bi, target.width, target.height,
     645                            RenderingHints.VALUE_INTERPOLATION_BILINEAR);
     646                r.width = target.width;
     647                r.height = target.height;
    522648            } else {
    523                 g.drawImage(image,
    524                         target.x, target.y, target.x + target.width, target.y + target.height,
    525                         visibleRect.x, visibleRect.y, visibleRect.x + visibleRect.width, visibleRect.y + visibleRect.height,
    526                         null);
    527             }
     649                // if target and r cause drawImage to scale image region to a tmp buffer exceeding
     650                // its bounds, it will silently fail; crop with r first in such cases
     651                // (might be impl. dependent, exhibited by openjdk 1.8.0_151)
     652                if (scale*(r.x+r.width) > Short.MAX_VALUE || scale*(r.y+r.height) > Short.MAX_VALUE) {
     653                    image = ImageProvider.toBufferedImage(image, r);
     654                    r.x = r.y = 0;
     655                }
     656            }
     657
     658            g.drawImage(image,
     659                    target.x, target.y, target.x + target.width, target.y + target.height,
     660                    r.x, r.y, r.x + r.width, r.y + r.height, null);
     661
    528662            if (selectedRect != null) {
    529663                Point topLeft = img2compCoord(visibleRect, selectedRect.x, selectedRect.y, size);
     
    577711    }
    578712
    579     static Point img2compCoord(Rectangle visibleRect, int xImg, int yImg, Dimension compSize) {
     713    static Point img2compCoord(VisRect visibleRect, int xImg, int yImg, Dimension compSize) {
    580714        Rectangle drawRect = calculateDrawImageRectangle(visibleRect, compSize);
    581715        return new Point(drawRect.x + ((xImg - visibleRect.x) * drawRect.width) / visibleRect.width,
     
    583717    }
    584718
    585     static Point comp2imgCoord(Rectangle visibleRect, int xComp, int yComp, Dimension compSize) {
     719    static Point comp2imgCoord(VisRect visibleRect, int xComp, int yComp, Dimension compSize) {
    586720        Rectangle drawRect = calculateDrawImageRectangle(visibleRect, compSize);
    587         return new Point(visibleRect.x + ((xComp - drawRect.x) * visibleRect.width) / drawRect.width,
    588                 visibleRect.y + ((yComp - drawRect.y) * visibleRect.height) / drawRect.height);
     721        Point p = new Point(
     722                        ((xComp - drawRect.x) * visibleRect.width),
     723                        ((yComp - drawRect.y) * visibleRect.height));
     724        p.x += (((p.x % drawRect.width) << 1) >= drawRect.width) ? drawRect.width : 0;
     725        p.y += (((p.y % drawRect.height) << 1) >= drawRect.height) ? drawRect.height : 0;
     726        p.x = visibleRect.x + p.x / drawRect.width;
     727        p.y = visibleRect.y + p.y / drawRect.height;
     728        return p;
    589729    }
    590730
     
    594734    }
    595735
    596     static Rectangle calculateDrawImageRectangle(Rectangle visibleRect, Dimension compSize) {
     736    static VisRect calculateDrawImageRectangle(VisRect visibleRect, Dimension compSize) {
    597737        return calculateDrawImageRectangle(visibleRect, new Rectangle(0, 0, compSize.width, compSize.height));
    598738    }
     
    605745     * @return the part of compRect with the same width/height ratio as the image
    606746     */
    607     static Rectangle calculateDrawImageRectangle(Rectangle imgRect, Rectangle compRect) {
     747    static VisRect calculateDrawImageRectangle(VisRect imgRect, Rectangle compRect) {
    608748        int x = 0;
    609749        int y = 0;
     
    622762            }
    623763        }
    624         return new Rectangle(x + compRect.x, y + compRect.y, w, h);
     764
     765        // overscan to prevent empty edges when zooming in to zoom scales > 2:1
     766        if (w > imgRect.width && h > imgRect.height && !imgRect.isFullView1D()) {
     767            if (wFact != hFact) {
     768                if (wFact > hFact) {
     769                    w = compRect.width;
     770                    x = 0;
     771                    h = wFact / imgRect.width;
     772                    y = (compRect.height - h) / 2;
     773                } else {
     774                    h = compRect.height;
     775                    y = 0;
     776                    w = hFact / imgRect.height;
     777                    x = (compRect.width - w) / 2;
     778                }
     779            }
     780        }
     781
     782        return new VisRect(x + compRect.x, y + compRect.y, w, h, imgRect);
    625783    }
    626784
     
    628786        File file;
    629787        Image image;
    630         Rectangle visibleRect;
     788        VisRect visibleRect;
    631789
    632790        synchronized (this) {
     
    641799        if (visibleRect.width != image.getWidth(null) || visibleRect.height != image.getHeight(null)) {
    642800            // The display is not at best fit. => Zoom to best fit
    643             visibleRect = new Rectangle(0, 0, image.getWidth(null), image.getHeight(null));
    644 
     801            visibleRect.reset();
    645802        } else {
    646803            // The display is at best fit => zoom to 1:1
    647804            Point center = getCenterImgCoord(visibleRect);
    648             visibleRect = new Rectangle(center.x - getWidth() / 2, center.y - getHeight() / 2,
     805            visibleRect.setBounds(center.x - getWidth() / 2, center.y - getHeight() / 2,
    649806                    getWidth(), getHeight());
    650             checkVisibleRectPos(image, visibleRect);
     807            visibleRect.checkRectSize();
     808            visibleRect.checkRectPos();
    651809        }
    652810
     
    658816        repaint();
    659817    }
    660 
    661     static void checkVisibleRectPos(Image image, Rectangle visibleRect) {
    662         if (visibleRect.x < 0) {
    663             visibleRect.x = 0;
    664         }
    665         if (visibleRect.y < 0) {
    666             visibleRect.y = 0;
    667         }
    668         if (visibleRect.x + visibleRect.width > image.getWidth(null)) {
    669             visibleRect.x = image.getWidth(null) - visibleRect.width;
    670         }
    671         if (visibleRect.y + visibleRect.height > image.getHeight(null)) {
    672             visibleRect.y = image.getHeight(null) - visibleRect.height;
    673         }
    674     }
    675 
    676     static void checkVisibleRectSize(Image image, Rectangle visibleRect) {
    677         if (visibleRect.width > image.getWidth(null)) {
    678             visibleRect.width = image.getWidth(null);
    679         }
    680         if (visibleRect.height > image.getHeight(null)) {
    681             visibleRect.height = image.getHeight(null);
    682         }
    683     }
    684818}
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java

    r12856 r13127  
    2121import org.openstreetmap.josm.data.cache.JCSCacheManager;
    2222import org.openstreetmap.josm.gui.MainApplication;
     23import org.openstreetmap.josm.gui.layer.geoimage.ImageDisplay.VisRect;
    2324import org.openstreetmap.josm.spi.preferences.Config;
    2425import org.openstreetmap.josm.tools.ExifReader;
     
    141142
    142143        Rectangle targetSize = ImageDisplay.calculateDrawImageRectangle(
    143                 new Rectangle(0, 0, ww, hh),
     144                new VisRect(0, 0, ww, hh),
    144145                new Rectangle(0, 0, maxSize, maxSize));
    145146        BufferedImage scaledBI = new BufferedImage(targetSize.width, targetSize.height, BufferedImage.TYPE_INT_RGB);
  • trunk/src/org/openstreetmap/josm/tools/ImageProvider.java

    r13038 r13127  
    1212import java.awt.Image;
    1313import java.awt.Point;
     14import java.awt.Rectangle;
    1415import java.awt.RenderingHints;
    1516import java.awt.Toolkit;
     
    14211422            if (w > targetWidth) {
    14221423                w /= 2;
    1423                 if (w < targetWidth) {
    1424                     w = targetWidth;
    1425                 }
     1424            }
     1425            if (w < targetWidth) {
     1426                w = targetWidth;
    14261427            }
    14271428            if (h > targetHeight) {
    14281429                h /= 2;
    1429                 if (h < targetHeight) {
    1430                     h = targetHeight;
    1431                 }
     1430            }
     1431            if (h < targetHeight) {
     1432                h = targetHeight;
    14321433            }
    14331434            BufferedImage tmp = new BufferedImage(w, h, type);
     
    19761977        }
    19771978    }
     1979
     1980    /**
     1981     * Converts an {@link Rectangle} area of {@link Image} to a {@link BufferedImage} instance.
     1982     * @param image image to convert
     1983     * @param crop_area rectangle to crop image with
     1984     * @return a {@code BufferedImage} instance for the cropped area of {@code Image}.
     1985     * @since 13127
     1986     */
     1987    public static BufferedImage toBufferedImage(Image image, Rectangle crop_area) {
     1988        BufferedImage buffImage = null;
     1989
     1990        Rectangle r = new Rectangle(image.getWidth(null), image.getHeight(null));
     1991        if (r.intersection(crop_area).equals(crop_area)) {
     1992            buffImage = new BufferedImage(crop_area.width, crop_area.height, BufferedImage.TYPE_INT_ARGB);
     1993            Graphics2D g2 = buffImage.createGraphics();
     1994            g2.drawImage(image,
     1995                0, 0, crop_area.width, crop_area.height,
     1996                crop_area.x, crop_area.y,
     1997                crop_area.x + crop_area.width, crop_area.y + crop_area.height,
     1998                null);
     1999            g2.dispose();
     2000        }
     2001        return buffImage;
     2002    }
    19782003}
  • trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplayTest.java

    r11539 r13127  
    99import org.junit.Rule;
    1010import org.junit.Test;
     11import org.openstreetmap.josm.gui.layer.geoimage.ImageDisplay.VisRect;
    1112import org.openstreetmap.josm.testutils.JOSMTestRules;
    1213
     
    3031    public void testCalculateDrawImageRectangle() {
    3132        assertEquals(new Rectangle(),
    32                 ImageDisplay.calculateDrawImageRectangle(new Rectangle(), new Dimension()));
     33                ImageDisplay.calculateDrawImageRectangle(new VisRect(), new Dimension()));
    3334        assertEquals(new Rectangle(0, 0, 10, 5),
    34                 ImageDisplay.calculateDrawImageRectangle(new Rectangle(0, 0, 10, 5), new Dimension(10, 5)));
     35                ImageDisplay.calculateDrawImageRectangle(new VisRect(0, 0, 10, 5), new Dimension(10, 5)));
    3536        assertEquals(new Rectangle(0, 0, 10, 5),
    36                 ImageDisplay.calculateDrawImageRectangle(new Rectangle(0, 0, 20, 10), new Dimension(10, 5)));
     37                ImageDisplay.calculateDrawImageRectangle(new VisRect(0, 0, 20, 10), new Dimension(10, 5)));
    3738        assertEquals(new Rectangle(0, 0, 20, 10),
    38                 ImageDisplay.calculateDrawImageRectangle(new Rectangle(0, 0, 10, 5), new Dimension(20, 10)));
     39                ImageDisplay.calculateDrawImageRectangle(new VisRect(0, 0, 10, 5), new Dimension(20, 10)));
    3940        assertEquals(new Rectangle(5, 0, 24, 12),
    40                 ImageDisplay.calculateDrawImageRectangle(new Rectangle(0, 0, 10, 5), new Dimension(35, 12)));
     41                ImageDisplay.calculateDrawImageRectangle(new VisRect(0, 0, 10, 5), new Dimension(35, 12)));
    4142        assertEquals(new Rectangle(0, 1, 8, 4),
    42                 ImageDisplay.calculateDrawImageRectangle(new Rectangle(0, 0, 10, 5), new Dimension(8, 6)));
     43                ImageDisplay.calculateDrawImageRectangle(new VisRect(0, 0, 10, 5), new Dimension(8, 6)));
    4344    }
    4445}
Note: See TracChangeset for help on using the changeset viewer.