Changeset 13191 in josm


Ignore:
Timestamp:
2017-12-03T20:16:52+01:00 (13 days ago)
Author:
Don-vip
Message:

fix #15625, see #15574 - geo image loading: do ram constraint checking; add some javadoc; do not use bilinear scaling when image is dragged (patch by cmuelle8, modified)

File:
1 edited

Legend:

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

    r13186 r13191  
    2323import java.awt.geom.Rectangle2D;
    2424import java.awt.image.BufferedImage;
     25import java.awt.image.ImageObserver;
    2526import java.io.File;
    2627
     
    112113    public static class VisRect extends Rectangle {
    113114        private final Rectangle init;
     115
     116        /** set when this {@code VisRect} is updated by a mouse drag operation and
     117         * unset on mouse release **/
     118        public boolean isDragUpdate;
    114119
    115120        /**
     
    125130        }
    126131
     132        /**
     133         * Constructs a new {@code VisRect}.
     134         * @param     x the specified X coordinate
     135         * @param     y the specified Y coordinate
     136         * @param     width  the width of the rectangle
     137         * @param     height the height of the rectangle
     138         * @param     peer share full bounds with this peer {@code VisRect}
     139         */
    127140        public VisRect(int x, int y, int width, int height, VisRect peer) {
    128141            super(x, y, width, height);
     
    200213
    201214    /** The thread that reads the images. */
    202     private class LoadImageRunnable implements Runnable {
     215    private class LoadImageRunnable implements Runnable, ImageObserver {
    203216
    204217        private final File file;
    205218        private final int orientation;
     219        private int width;
     220        private int height;
    206221
    207222        LoadImageRunnable(File file, Integer orientation) {
     
    211226
    212227        @Override
     228        public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
     229            if (((infoflags & ImageObserver.WIDTH) == ImageObserver.WIDTH) &&
     230                ((infoflags & ImageObserver.HEIGHT) == ImageObserver.HEIGHT)) {
     231                this.width = width;
     232                this.height = height;
     233                synchronized (this) {
     234                    this.notify();
     235                    return false;
     236                }
     237            }
     238            return true;
     239        }
     240
     241        @Override
    213242        public void run() {
    214243            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;
     244
     245            synchronized (this) {
     246                width = -1;
     247                img.getWidth(this);
     248                img.getHeight(this);
     249
     250                while (width < 0) {
     251                    try {
     252                        this.wait();
     253                        if (width < 0) {
     254                            errorLoading = true;
     255                            return;
     256                        }
     257                    } catch (InterruptedException e) {
     258                        e.printStackTrace();
     259                    }
     260                }
     261            }
     262
     263            long allocatedMem = Runtime.getRuntime().totalMemory() -
     264                    Runtime.getRuntime().freeMemory();
     265            long mem = Runtime.getRuntime().maxMemory()-allocatedMem;
     266
     267            if (mem > ((long) width*height*4)*2) {
     268                Logging.info("Loading {0} using default toolkit", file.getPath());
     269                tracker.addImage(img, 1);
     270
     271                // Wait for the end of loading
     272                while (!tracker.checkID(1, true)) {
     273                    if (this.file != ImageDisplay.this.file) {
     274                        // The file has changed
     275                        tracker.removeImage(img);
     276                        return;
     277                    }
     278                    try {
     279                        Thread.sleep(5);
     280                    } catch (InterruptedException e) {
     281                        Logging.trace(e);
     282                        Logging.warn("InterruptedException in "+getClass().getSimpleName()+
     283                                " while loading image "+file.getPath());
     284                        Thread.currentThread().interrupt();
     285                    }
     286                }
     287                if (tracker.isErrorID(1)) {
     288                    img = null;
     289                    System.gc();
     290                }
     291            } else {
     292                img = null;
     293            }
     294
     295            if (img == null || width <= 0 || height <= 0) {
     296                tracker.removeImage(img);
     297                img = null;
    235298            }
    236299
     
    242305                }
    243306
    244                 if (!error) {
     307                if (img != null) {
     308                    boolean switchedDim = false;
     309                    if (ExifReader.orientationNeedsCorrection(orientation)) {
     310                        if (ExifReader.orientationSwitchesDimensions(orientation)) {
     311                            width = img.getHeight(null);
     312                            height = img.getWidth(null);
     313                            switchedDim = true;
     314                        }
     315                        final BufferedImage rot = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
     316                        final AffineTransform xform = ExifReader.getRestoreOrientationTransform(orientation,
     317                                img.getWidth(null), img.getHeight(null));
     318                        final Graphics2D g = rot.createGraphics();
     319                        g.drawImage(img, xform, null);
     320                        g.dispose();
     321                        img.flush();
     322                        img = rot;
     323                    }
     324
    245325                    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                     }
     326                    visibleRect = new VisRect(0, 0, width, height);
     327
     328                    Logging.info("Loaded {0} with dimensions {1}x{2} mem(prev-avail={3}m,taken={4}m) exifOrientationSwitchedDimension={5}",
     329                            file.getPath(), width, height, mem/1024/1024, width*height*4/1024/1024, switchedDim);
    270330                }
    271331
    272332                selectedRect = null;
    273                 errorLoading = error;
     333                errorLoading = (img == null);
    274334            }
    275335            tracker.removeImage(img);
     
    474534            if (mouseIsDragging(e)) {
    475535                Point p = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize());
     536                visibleRect.isDragUpdate = true;
    476537                visibleRect.x += mousePointInImg.x - p.x;
    477538                visibleRect.y += mousePointInImg.y - p.y;
     
    504565        @Override
    505566        public void mouseReleased(MouseEvent e) {
    506             if (!mouseIsZoomSelecting(e) || selectedRect == null)
    507                 return;
    508 
    509567            File file;
    510568            Image image;
     
    515573            }
    516574
    517             if (image == null) {
     575            if (image == null)
    518576                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();
     577
     578            if (mouseIsDragging(e)) {
     579                visibleRect.isDragUpdate = false;
     580            }
     581
     582            if (mouseIsZoomSelecting(e) && selectedRect != null) {
     583                int oldWidth = selectedRect.width;
     584                int oldHeight = selectedRect.height;
     585
     586                // Check that the zoom doesn't exceed MAX_ZOOM:1
     587                if (selectedRect.width < getSize().width / MAX_ZOOM.get()) {
     588                    selectedRect.width = (int) (getSize().width / MAX_ZOOM.get());
     589                }
     590                if (selectedRect.height < getSize().height / MAX_ZOOM.get()) {
     591                    selectedRect.height = (int) (getSize().height / MAX_ZOOM.get());
     592                }
     593
     594                // Set the same ratio for the visible rectangle and the display area
     595                int hFact = selectedRect.height * getSize().width;
     596                int wFact = selectedRect.width * getSize().height;
     597                if (hFact > wFact) {
     598                    selectedRect.width = hFact / getSize().height;
     599                } else {
     600                    selectedRect.height = wFact / getSize().width;
     601                }
     602
     603                // Keep the center of the selection
     604                if (selectedRect.width != oldWidth) {
     605                    selectedRect.x -= (selectedRect.width - oldWidth) / 2;
     606                }
     607                if (selectedRect.height != oldHeight) {
     608                    selectedRect.y -= (selectedRect.height - oldHeight) / 2;
     609                }
     610
     611                selectedRect.checkRectSize();
     612                selectedRect.checkRectPos();
     613            }
    551614
    552615            synchronized (ImageDisplay.this) {
    553616                if (file == ImageDisplay.this.file) {
    554                     ImageDisplay.this.visibleRect.setBounds(selectedRect);
    555                 }
    556             }
    557             selectedRect = null;
     617                    if (selectedRect == null) {
     618                        ImageDisplay.this.visibleRect = visibleRect;
     619                    } else {
     620                        ImageDisplay.this.visibleRect.setBounds(selectedRect);
     621                        selectedRect = null;
     622                    }
     623                }
     624            }
    558625            ImageDisplay.this.repaint();
    559626        }
     
    587654    }
    588655
     656    /**
     657     * Sets a new source image to be displayed by this {@code ImageDisplay}.
     658     * @param file new source image
     659     * @param orientation orientation of new source (landscape, portrait, upside-down, etc.)
     660     */
    589661    public void setImage(File file, Integer orientation) {
    590662        synchronized (this) {
     
    651723            double scale = target.width / (double) r.width; // pixel ratio is 1:1
    652724
    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;
     725            if (selectedRect == null && !visibleRect.isDragUpdate &&
     726                bilinLower < scale && scale < bilinUpper) {
     727                try {
     728                    BufferedImage bi = ImageProvider.toBufferedImage(image, r);
     729                    if (bi != null) {
     730                        r.x = r.y = 0;
     731
     732                        // See https://community.oracle.com/docs/DOC-983611 - The Perils of Image.getScaledInstance()
     733                        // Pre-scale image when downscaling by more than two times to avoid aliasing from default algorithm
     734                        bi = ImageProvider.createScaledImage(bi, target.width, target.height,
     735                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
     736                        r.width = target.width;
     737                        r.height = target.height;
     738                        image = bi;
     739                    }
     740                } catch (OutOfMemoryError oom) {
     741                    // fall-back to the non-bilinear scaler
     742                    r.x = visibleRect.x;
     743                    r.y = visibleRect.y;
     744                    System.gc();
     745                }
    663746            } else {
    664747                // if target and r cause drawImage to scale image region to a tmp buffer exceeding
     
    796879    }
    797880
     881    /**
     882     * Make the current image either scale to fit inside this component,
     883     * or show a portion of image (1:1), if the image size is larger than
     884     * the component size.
     885     */
    798886    public void zoomBestFitOrOne() {
    799887        File file;
Note: See TracChangeset for help on using the changeset viewer.