Ticket #20813: 20813-beta.patch

File 20813-beta.patch, 16.9 KB (added by simon04, 5 years ago)
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
    index 0cda83cc8..56631183d 100644
    a b import java.awt.FontMetrics;  
    99import java.awt.Graphics;
    1010import java.awt.Graphics2D;
    1111import java.awt.Image;
    12 import java.awt.MediaTracker;
    1312import java.awt.Point;
    1413import java.awt.Rectangle;
    1514import java.awt.RenderingHints;
    import java.awt.event.MouseWheelListener;  
    2120import java.awt.geom.AffineTransform;
    2221import java.awt.geom.Rectangle2D;
    2322import java.awt.image.BufferedImage;
    24 import java.awt.image.ImageObserver;
    2523import java.io.File;
    2624import java.io.IOException;
    2725import java.util.Objects;
    2826
    2927import javax.imageio.ImageIO;
     28import javax.imageio.ImageReadParam;
     29import javax.imageio.ImageReader;
     30import javax.imageio.stream.ImageInputStream;
    3031import javax.swing.JComponent;
    3132import javax.swing.SwingUtilities;
    3233
    3334import org.openstreetmap.josm.data.preferences.BooleanProperty;
    3435import org.openstreetmap.josm.data.preferences.DoubleProperty;
     36import org.openstreetmap.josm.gui.MainApplication;
    3537import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings;
    3638import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings.FilterChangeListener;
    3739import org.openstreetmap.josm.spi.preferences.Config;
    import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;  
    3941import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
    4042import org.openstreetmap.josm.tools.Destroyable;
    4143import org.openstreetmap.josm.tools.ExifReader;
    42 import org.openstreetmap.josm.tools.HiDPISupport;
    4344import org.openstreetmap.josm.tools.ImageProcessor;
    44 import org.openstreetmap.josm.tools.ImageProvider;
    4545import org.openstreetmap.josm.tools.Logging;
    4646
    4747/**
    public class ImageDisplay extends JComponent implements Destroyable, PreferenceC  
    7878    /** When a selection is done, the rectangle of the selection (in image coordinates) */
    7979    private VisRect selectedRect;
    8080
    81     /** The tracker to load the images */
    82     private final MediaTracker tracker = new MediaTracker(this);
    83 
    8481    private final ImgDisplayMouseListener imgMouseListener = new ImgDisplayMouseListener();
    8582
    8683    private String emptyText;
    public class ImageDisplay extends JComponent implements Destroyable, PreferenceC  
    103100    private static final DoubleProperty MAX_ZOOM =
    104101        new DoubleProperty("geoimage.maximum-zoom-scale", 2.0);
    105102
    106     /** Use bilinear filtering **/
    107     private static final BooleanProperty BILIN_DOWNSAMP =
    108         new BooleanProperty("geoimage.bilinear-downsampling-progressive", true);
    109     private static final BooleanProperty BILIN_UPSAMP =
    110         new BooleanProperty("geoimage.bilinear-upsampling", false);
    111     private static double bilinUpper;
    112     private static double bilinLower;
    113 
    114103    /** Show a background for the error text (may be hard on eyes) */
    115104    private static final BooleanProperty ERROR_MESSAGE_BACKGROUND = new BooleanProperty("geoimage.message.error.background", false);
    116105
    public class ImageDisplay extends JComponent implements Destroyable, PreferenceC  
    121110            dragButton = AGPIFO_STYLE.get() ? 1 : 3;
    122111            zoomButton = dragButton == 1 ? 3 : 1;
    123112        }
    124         if (e == null ||
    125             e.getKey().equals(MAX_ZOOM.getKey()) ||
    126             e.getKey().equals(BILIN_DOWNSAMP.getKey()) ||
    127             e.getKey().equals(BILIN_UPSAMP.getKey())) {
    128             bilinUpper = (BILIN_UPSAMP.get() ? 2*MAX_ZOOM.get() : (BILIN_DOWNSAMP.get() ? 0.5 : 0));
    129             bilinLower = (BILIN_DOWNSAMP.get() ? 0 : 1);
    130         }
    131113    }
    132114
    133115    /**
    public class ImageDisplay extends JComponent implements Destroyable, PreferenceC  
    236218    }
    237219
    238220    /** The thread that reads the images. */
    239     protected class LoadImageRunnable implements Runnable, ImageObserver {
     221    protected class LoadImageRunnable implements Runnable {
    240222
    241223        private final ImageEntry entry;
    242224        private final File file;
    public class ImageDisplay extends JComponent implements Destroyable, PreferenceC  
    246228            this.file = entry.getFile();
    247229        }
    248230
    249         @Override
    250         public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
    251             if (((infoflags & ImageObserver.WIDTH) == ImageObserver.WIDTH) &&
    252                 ((infoflags & ImageObserver.HEIGHT) == ImageObserver.HEIGHT)) {
    253                 synchronized (entry) {
    254                     entry.setWidth(width);
    255                     entry.setHeight(height);
    256                     entry.notifyAll();
    257                     return false;
    258                 }
    259             }
    260             return true;
     231        private BufferedImage read(ImageInputStream stream) throws IOException {
     232            ImageReader reader = ImageIO.getImageReaders(stream).next();
     233            reader.setInput(stream, true, true);
     234            ImageReadParam param = reader.getDefaultReadParam();
     235            Dimension d1 = new Dimension(reader.getWidth(0), reader.getHeight(0));
     236            Dimension d2 = getSize();
     237            int subsampling = getSubsampling(d1, d2);
     238            param.setSourceSubsampling(subsampling, subsampling, 0, 0);
     239            // param.setSourceRegion(visibleRect);
     240            return reader.read(0, param);
    261241        }
    262242
    263         private boolean updateImageEntry(Image img) {
    264             if (img == null) {
    265                 synchronized (ImageDisplay.this) {
    266                     errorLoading = true;
    267                     ImageDisplay.this.repaint();
    268                     return false;
    269                 }
    270             }
    271             if (!(entry.getWidth() > 0 && entry.getHeight() > 0)) {
    272                 synchronized (entry) {
    273                     int width = img.getWidth(this);
    274                     int height = img.getHeight(this);
    275 
    276                     if (!(entry.getWidth() > 0 && entry.getHeight() > 0) && width > 0 && height > 0) {
    277                         // dimensions not in metadata but already present in image, so observer won't be called
    278                         entry.setWidth(width);
    279                         entry.setHeight(height);
    280                         entry.notifyAll();
    281                     }
    282 
    283                     long now = System.currentTimeMillis();
    284                     while (!(entry.getWidth() > 0 && entry.getHeight() > 0)) {
    285                         try {
    286                             entry.wait(1000);
    287                             if (this.entry != ImageDisplay.this.entry)
    288                                 return false;
    289                             if (System.currentTimeMillis() - now > 10000)
    290                                 synchronized (ImageDisplay.this) {
    291                                     errorLoading = true;
    292                                     ImageDisplay.this.repaint();
    293                                     return false;
    294                                 }
    295                         } catch (InterruptedException e) {
    296                             Logging.trace(e);
    297                             Logging.warn("InterruptedException in {0} while getting properties of image {1}",
    298                                     getClass().getSimpleName(), file.getPath());
    299                             Thread.currentThread().interrupt();
    300                         }
    301                     }
    302                 }
     243        private int getSubsampling(Dimension d1, Dimension d2) {
     244            if (d1.getWidth() > d2.getWidth()) {
     245                return (int) Math.round(d1.getWidth() / d2.getWidth());
     246            } else if (d1.getHeight() > d2.getHeight()) {
     247                return (int) Math.round(d1.getHeight() / d2.getHeight());
     248            } else {
     249                return 1;
    303250            }
    304             return true;
    305         }
    306 
    307         private boolean mayFitMemory(long amountWanted) {
    308             return amountWanted < (
    309                    Runtime.getRuntime().maxMemory() -
    310                    Runtime.getRuntime().totalMemory() +
    311                    Runtime.getRuntime().freeMemory());
    312251        }
    313252
    314253        @Override
    315254        public void run() {
    316             BufferedImage img;
    317             try {
    318                 img = ImageIO.read(file);
    319 
    320                 if (!updateImageEntry(img))
    321                     return;
    322 
    323                 int width = entry.getWidth();
    324                 int height = entry.getHeight();
    325 
    326                 if (mayFitMemory(((long) width)*height*4*2)) {
    327                     Logging.info(tr("Loading {0}", file.getPath()));
    328                     tracker.addImage(img, 1);
    329 
    330                     // Wait for the end of loading
    331                     while (!tracker.checkID(1, true)) {
    332                         if (this.entry != ImageDisplay.this.entry) {
    333                             // The file has changed
    334                             tracker.removeImage(img);
    335                             return;
    336                         }
    337                         try {
    338                             Thread.sleep(5);
    339                         } catch (InterruptedException e) {
    340                             Logging.trace(e);
    341                             Logging.warn("InterruptedException in {0} while loading image {1}",
    342                                     getClass().getSimpleName(), file.getPath());
    343                             Thread.currentThread().interrupt();
    344                         }
    345                     }
    346                     if (tracker.isErrorID(1)) {
    347                         Logging.warn("Abort loading of {0} since tracker errored with 1", file);
    348                         // the tracker catches OutOfMemory conditions
    349                         tracker.removeImage(img);
    350                         img = null;
    351                     } else {
    352                         tracker.removeImage(img);
     255            Logging.info(tr("Loading {0}", file.getPath()));
     256            try (ImageInputStream stream = ImageIO.createImageInputStream(file)) {
     257                BufferedImage img = read(stream);
     258                if (img == null) {
     259                    synchronized (ImageDisplay.this) {
     260                        errorLoading = true;
     261                        ImageDisplay.this.repaint();
     262                        return;
    353263                    }
    354                 } else {
    355                     Logging.warn("Abort loading of {0} since it might not fit into memory", file);
    356                     img = null;
    357264                }
    358265
     266                int width = img.getWidth();
     267                int height = img.getHeight();
     268                entry.setWidth(width);
     269                entry.setHeight(height);
     270
    359271                synchronized (ImageDisplay.this) {
    360272                    if (this.entry != ImageDisplay.this.entry) {
    361273                        // The file has changed
    362274                        return;
    363275                    }
    364276
    365                     if (img != null) {
    366                         boolean switchedDim = false;
    367                         if (ExifReader.orientationNeedsCorrection(entry.getExifOrientation())) {
    368                             if (ExifReader.orientationSwitchesDimensions(entry.getExifOrientation())) {
    369                                 width = img.getHeight(null);
    370                                 height = img.getWidth(null);
    371                                 switchedDim = true;
    372                             }
    373                             final BufferedImage rot = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    374                             final AffineTransform xform = ExifReader.getRestoreOrientationTransform(
    375                                     entry.getExifOrientation(),
    376                                     img.getWidth(null),
    377                                     img.getHeight(null));
    378                             final Graphics2D g = rot.createGraphics();
    379                             g.drawImage(img, xform, null);
    380                             g.dispose();
    381                             img = rot;
     277                    boolean switchedDim = false;
     278                    if (ExifReader.orientationNeedsCorrection(entry.getExifOrientation())) {
     279                        if (ExifReader.orientationSwitchesDimensions(entry.getExifOrientation())) {
     280                            width = img.getHeight(null);
     281                            height = img.getWidth(null);
     282                            switchedDim = true;
    382283                        }
     284                        final BufferedImage rot = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
     285                        final AffineTransform xform = ExifReader.getRestoreOrientationTransform(
     286                                entry.getExifOrientation(),
     287                                img.getWidth(null),
     288                                img.getHeight(null));
     289                        final Graphics2D g = rot.createGraphics();
     290                        g.drawImage(img, xform, null);
     291                        g.dispose();
     292                        img = rot;
     293                    }
    383294
    384                         ImageDisplay.this.image = img;
    385                         updateProcessedImage();
    386                         // This will clear the loading info box
    387                         ImageDisplay.this.oldEntry = ImageDisplay.this.entry;
    388                         visibleRect = new VisRect(0, 0, width, height);
     295                    ImageDisplay.this.image = img;
     296                    updateProcessedImage();
     297                    // This will clear the loading info box
     298                    ImageDisplay.this.oldEntry = ImageDisplay.this.entry;
     299                    visibleRect = new VisRect(0, 0, width, height);
    389300
    390                         Logging.debug("Loaded {0} with dimensions {1}x{2} memoryTaken={3}m exifOrientationSwitchedDimension={4}",
    391                                 file.getPath(), width, height, width*height*4/1024/1024, switchedDim);
    392                     }
     301                    Logging.debug("Loaded {0} with dimensions {1}x{2} memoryTaken={3}m exifOrientationSwitchedDimension={4}",
     302                            file.getPath(), width, height, width * height * 4 / 1024 / 1024, switchedDim);
    393303
    394304                    selectedRect = null;
    395                     errorLoading = (img == null);
     305                    errorLoading = false;
    396306                }
    397307                ImageDisplay.this.repaint();
    398308            } catch (IOException ex) {
    public class ImageDisplay extends JComponent implements Destroyable, PreferenceC  
    745655    public void setImage(ImageEntry entry) {
    746656        LoadImageRunnable runnable = setImage0(entry);
    747657        if (runnable != null) {
    748             new Thread(runnable, LoadImageRunnable.class.getName()).start();
     658            MainApplication.worker.execute(runnable);
    749659        }
    750660    }
    751661
    public class ImageDisplay extends JComponent implements Destroyable, PreferenceC  
    820730        if (image != null && (entry != null || oldEntry != null)) {
    821731            Rectangle r = new Rectangle(visibleRect);
    822732            Rectangle target = calculateDrawImageRectangle(visibleRect, size);
    823             double scale = target.width / (double) r.width; // pixel ratio is 1:1
    824 
    825             if (selectedRect == null && !visibleRect.isDragUpdate &&
    826                 bilinLower < scale && scale < bilinUpper) {
    827                 try {
    828                     BufferedImage bi = ImageProvider.toBufferedImage(image, r);
    829                     if (bi != null) {
    830                         r.x = r.y = 0;
    831                         double hiDPIScale = HiDPISupport.getHiDPIScale();
    832                         int width = (int) (target.width * hiDPIScale);
    833                         int height = (int) (target.height * hiDPIScale);
    834                         // See https://community.oracle.com/docs/DOC-983611 - The Perils of Image.getScaledInstance()
    835                         // Pre-scale image when downscaling by more than two times to avoid aliasing from default algorithm
    836                         bi = ImageProvider.createScaledImage(bi, width, height, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    837                         r.width = width;
    838                         r.height = height;
    839                         image = bi;
    840                     }
    841                 } catch (OutOfMemoryError oom) {
    842                     Logging.trace(oom);
    843                     // fall-back to the non-bilinear scaler
    844                     r.x = visibleRect.x;
    845                     r.y = visibleRect.y;
    846                 }
    847             } else {
    848                 // if target and r cause drawImage to scale image region to a tmp buffer exceeding
    849                 // its bounds, it will silently fail; crop with r first in such cases
    850                 // (might be impl. dependent, exhibited by openjdk 1.8.0_151)
    851                 if (scale*(r.x+r.width) > Short.MAX_VALUE || scale*(r.y+r.height) > Short.MAX_VALUE) {
    852                     image = ImageProvider.toBufferedImage(image, r);
    853                     r.x = r.y = 0;
    854                 }
    855             }
    856733
    857734            g.drawImage(image,
    858735                    target.x, target.y, target.x + target.width, target.y + target.height,