Ticket #15574: josm-store-width-and-height-info-in-ImageEntry.patch

File josm-store-width-and-height-info-in-ImageEntry.patch, 22.4 KB (added by cmuelle8, 7 years ago)

patch against r13193; based on the patch in comment:14; additionally refactors ImageDisplay to use ImageEntry instead; stores width and height info while metadata of images are read; might break plugin code

  • src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java

     
    7979import org.openstreetmap.josm.gui.widgets.JosmTextField;
    8080import org.openstreetmap.josm.io.GpxReader;
    8181import org.openstreetmap.josm.spi.preferences.Config;
    82 import org.openstreetmap.josm.tools.ExifReader;
    8382import org.openstreetmap.josm.tools.GBC;
    8483import org.openstreetmap.josm.tools.ImageProvider;
    8584import org.openstreetmap.josm.tools.JosmRuntimeException;
     
    458457            imgList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    459458            imgList.getSelectionModel().addListSelectionListener(evt -> {
    460459                int index = imgList.getSelectedIndex();
    461                 Integer orientation = ExifReader.readOrientation(yLayer.data.get(index).getFile());
    462                 imgDisp.setImage(yLayer.data.get(index).getFile(), orientation);
     460                imgDisp.setImage(yLayer.data.get(index));
    463461                Date date = yLayer.data.get(index).getExifTime();
    464462                if (date != null) {
    465463                    DateFormat df = DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.MEDIUM);
     
    482480                        JpgImporter.FILE_FILTER_WITH_FOLDERS, JFileChooser.FILES_ONLY, "geoimage.lastdirectory");
    483481                if (fc == null)
    484482                    return;
    485                 File sel = fc.getSelectedFile();
     483                ImageEntry entry = new ImageEntry(fc.getSelectedFile());
     484                entry.extractExif();
     485                imgDisp.setImage(entry);
    486486
    487                 Integer orientation = ExifReader.readOrientation(sel);
    488                 imgDisp.setImage(sel, orientation);
    489 
    490                 Date date = ExifReader.readTime(sel);
     487                Date date = entry.getExifTime();
    491488                if (date != null) {
    492489                    lbExifTime.setText(DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.MEDIUM).format(date));
    493490                    tfGpsTime.setText(DateUtils.getDateFormat(DateFormat.SHORT).format(date)+' ');
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java

     
    4545public class ImageDisplay extends JComponent implements PreferenceChangedListener {
    4646
    4747    /** The file that is currently displayed */
    48     private File file;
     48    private ImageEntry entry;
    4949
    5050    /** The image currently displayed */
    5151    private transient Image image;
     
    9090    private static double bilinUpper;
    9191    private static double bilinLower;
    9292
    93     @Override
    94     public void preferenceChanged(PreferenceChangeEvent e) {
     93    /**
     94     * Avoid find-bugs bad style warnings by write accessing static members from
     95     * a static method only.
     96     * @param e {@code PreferenceChangeEvent}
     97     */
     98    public static synchronized void preferenceChangedImpl(PreferenceChangeEvent e) {
    9599        if (e == null ||
    96100            e.getKey().equals(AGPIFO_STYLE.getKey())) {
    97101            dragButton = AGPIFO_STYLE.get() ? 1 : 3;
     
    106110        }
    107111    }
    108112
     113    @Override
     114    public void preferenceChanged(PreferenceChangeEvent e) {
     115        preferenceChangedImpl(e);
     116    }
     117
    109118    /**
    110119     * Manage the visible rectangle of an image with full bounds stored in init.
    111120     * @since 13127
     
    214223    /** The thread that reads the images. */
    215224    private class LoadImageRunnable implements Runnable, ImageObserver {
    216225
     226        private final ImageEntry entry;
    217227        private final File file;
    218         private final int orientation;
    219         private int width;
    220         private int height;
    221228
    222         LoadImageRunnable(File file, Integer orientation) {
    223             this.file = file;
    224             this.orientation = orientation == null ? -1 : orientation;
     229        LoadImageRunnable(ImageEntry entry) {
     230            this.entry = entry;
     231            this.file = entry.getFile();
    225232        }
    226233
    227234        @Override
    228235        public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
    229236            if (((infoflags & ImageObserver.WIDTH) == ImageObserver.WIDTH) &&
    230237                ((infoflags & ImageObserver.HEIGHT) == ImageObserver.HEIGHT)) {
    231                 this.width = width;
    232                 this.height = height;
    233                 synchronized (this) {
    234                     this.notify();
     238                synchronized (entry) {
     239                    entry.setWidth(width);
     240                    entry.setHeight(height);
     241                    entry.notifyAll();
    235242                    return false;
    236243                }
    237244            }
    238245            return true;
    239246        }
    240247
    241         @Override
    242         public void run() {
    243             Image img = Toolkit.getDefaultToolkit().createImage(file.getPath());
     248        private boolean updateImageEntry(Image img) {
     249            if (!(entry.getWidth() > 0 && entry.getHeight() > 0)) {
     250                synchronized (entry) {
     251                    img.getWidth(this);
     252                    img.getHeight(this);
    244253
    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;
     254                    long now = System.currentTimeMillis();
     255                    while (!(entry.getWidth() > 0 && entry.getHeight() > 0)) {
     256                        try {
     257                            entry.wait(1000);
     258                            if (this.entry != ImageDisplay.this.entry)
     259                                return false;
     260                            if (System.currentTimeMillis() - now > 10000)
     261                                synchronized (ImageDisplay.this) {
     262                                    errorLoading = true;
     263                                    ImageDisplay.this.repaint();
     264                                    return false;
     265                                }
     266                        } catch (InterruptedException e) {
     267                            Logging.trace(e);
     268                            Logging.warn("InterruptedException in {0} while getting properties of image {1}",
     269                                    getClass().getSimpleName(), file.getPath());
     270                            Thread.currentThread().interrupt();
    256271                        }
    257                     } catch (InterruptedException e) {
    258                         e.printStackTrace();
    259272                    }
    260273                }
    261274            }
     275            return true;
     276        }
     277
     278        private boolean mayFitMemory(long amountWanted) {
     279            return amountWanted < (
     280                   Runtime.getRuntime().maxMemory() -
     281                   Runtime.getRuntime().totalMemory() +
     282                   Runtime.getRuntime().freeMemory());
     283        }
     284
     285        @Override
     286        public void run() {
     287            Image img = Toolkit.getDefaultToolkit().createImage(file.getPath());
     288            if (!updateImageEntry(img))
     289                return;
    262290
    263             long allocatedMem = Runtime.getRuntime().totalMemory() -
    264                     Runtime.getRuntime().freeMemory();
    265             long mem = Runtime.getRuntime().maxMemory()-allocatedMem;
     291            int width = entry.getWidth();
     292            int height = entry.getHeight();
    266293
    267             if (mem > ((long) width*height*4)*2) {
     294            if (mayFitMemory(((long) width)*height*4*2)) {
    268295                Logging.info("Loading {0} using default toolkit", file.getPath());
    269296                tracker.addImage(img, 1);
    270297
    271298                // Wait for the end of loading
    272299                while (!tracker.checkID(1, true)) {
    273                     if (this.file != ImageDisplay.this.file) {
     300                    if (this.entry != ImageDisplay.this.entry) {
    274301                        // The file has changed
    275302                        tracker.removeImage(img);
    276303                        return;
     
    279306                        Thread.sleep(5);
    280307                    } catch (InterruptedException e) {
    281308                        Logging.trace(e);
    282                         Logging.warn("InterruptedException in "+getClass().getSimpleName()+
    283                                 " while loading image "+file.getPath());
     309                        Logging.warn("InterruptedException in {0} while loading image {1}",
     310                                getClass().getSimpleName(), file.getPath());
    284311                        Thread.currentThread().interrupt();
    285312                    }
    286313                }
    287314                if (tracker.isErrorID(1)) {
     315                    // the tracker catches OutOfMemory conditions
    288316                    img = null;
    289                     System.gc();
    290317                }
    291318            } else {
    292319                img = null;
    293320            }
    294321
    295             if (img == null || width <= 0 || height <= 0) {
    296                 tracker.removeImage(img);
    297                 img = null;
    298             }
    299 
    300322            synchronized (ImageDisplay.this) {
    301                 if (this.file != ImageDisplay.this.file) {
     323                if (this.entry != ImageDisplay.this.entry) {
    302324                    // The file has changed
    303325                    tracker.removeImage(img);
    304326                    return;
     
    306328
    307329                if (img != null) {
    308330                    boolean switchedDim = false;
    309                     if (ExifReader.orientationNeedsCorrection(orientation)) {
    310                         if (ExifReader.orientationSwitchesDimensions(orientation)) {
     331                    if (ExifReader.orientationNeedsCorrection(entry.getExifOrientation())) {
     332                        if (ExifReader.orientationSwitchesDimensions(entry.getExifOrientation())) {
    311333                            width = img.getHeight(null);
    312334                            height = img.getWidth(null);
    313335                            switchedDim = true;
    314336                        }
    315337                        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));
     338                        final AffineTransform xform = ExifReader.getRestoreOrientationTransform(
     339                                entry.getExifOrientation(),
     340                                img.getWidth(null),
     341                                img.getHeight(null));
    318342                        final Graphics2D g = rot.createGraphics();
    319343                        g.drawImage(img, xform, null);
    320344                        g.dispose();
     
    325349                    ImageDisplay.this.image = img;
    326350                    visibleRect = new VisRect(0, 0, width, height);
    327351
    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);
     352                    Logging.info("Loaded {0} with dimensions {1}x{2} memoryTaken={3}m exifOrientationSwitchedDimension={4}",
     353                            file.getPath(), width, height, width*height*4/1024/1024, switchedDim);
    330354                }
    331355
    332356                selectedRect = null;
     
    360384        }
    361385
    362386        private void mouseWheelMovedImpl(int x, int y, int rotation, boolean refreshMousePointInImg) {
    363             File file;
     387            ImageEntry entry;
    364388            Image image;
    365389            VisRect visibleRect;
    366390
    367391            synchronized (ImageDisplay.this) {
    368                 file = ImageDisplay.this.file;
     392                entry = ImageDisplay.this.entry;
    369393                image = ImageDisplay.this.image;
    370394                visibleRect = ImageDisplay.this.visibleRect;
    371395            }
     
    417441            visibleRect.checkRectPos();
    418442
    419443            synchronized (ImageDisplay.this) {
    420                 if (ImageDisplay.this.file == file) {
     444                if (ImageDisplay.this.entry == entry) {
    421445                    ImageDisplay.this.visibleRect = visibleRect;
    422446                }
    423447            }
     
    446470        @Override
    447471        public void mouseClicked(MouseEvent e) {
    448472            // Move the center to the clicked point.
    449             File file;
     473            ImageEntry entry;
    450474            Image image;
    451475            VisRect visibleRect;
    452476
    453477            synchronized (ImageDisplay.this) {
    454                 file = ImageDisplay.this.file;
     478                entry = ImageDisplay.this.entry;
    455479                image = ImageDisplay.this.image;
    456480                visibleRect = ImageDisplay.this.visibleRect;
    457481            }
     
    485509            visibleRect.checkRectPos();
    486510
    487511            synchronized (ImageDisplay.this) {
    488                 if (ImageDisplay.this.file == file) {
     512                if (ImageDisplay.this.entry == entry) {
    489513                    ImageDisplay.this.visibleRect = visibleRect;
    490514                }
    491515            }
     
    518542            if (!mouseIsDragging(e) && !mouseIsZoomSelecting(e))
    519543                return;
    520544
    521             File file;
     545            ImageEntry entry;
    522546            Image image;
    523547            VisRect visibleRect;
    524548
    525549            synchronized (ImageDisplay.this) {
    526                 file = ImageDisplay.this.file;
     550                entry = ImageDisplay.this.entry;
    527551                image = ImageDisplay.this.image;
    528552                visibleRect = ImageDisplay.this.visibleRect;
    529553            }
     
    538562                visibleRect.y += mousePointInImg.y - p.y;
    539563                visibleRect.checkRectPos();
    540564                synchronized (ImageDisplay.this) {
    541                     if (ImageDisplay.this.file == file) {
     565                    if (ImageDisplay.this.entry == entry) {
    542566                        ImageDisplay.this.visibleRect = visibleRect;
    543567                    }
    544568                }
     
    564588
    565589        @Override
    566590        public void mouseReleased(MouseEvent e) {
    567             File file;
     591            ImageEntry entry;
    568592            Image image;
     593            VisRect visibleRect;
    569594
    570595            synchronized (ImageDisplay.this) {
    571                 file = ImageDisplay.this.file;
     596                entry = ImageDisplay.this.entry;
    572597                image = ImageDisplay.this.image;
     598                visibleRect = ImageDisplay.this.visibleRect;
    573599            }
    574600
    575601            if (image == null)
     
    613639            }
    614640
    615641            synchronized (ImageDisplay.this) {
    616                 if (file == ImageDisplay.this.file) {
     642                if (entry == ImageDisplay.this.entry) {
    617643                    if (selectedRect == null) {
    618644                        ImageDisplay.this.visibleRect = visibleRect;
    619645                    } else {
     
    655681
    656682    /**
    657683     * 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.)
     684     * @param entry new source image
    660685     */
    661     public void setImage(File file, Integer orientation) {
     686    public void setImage(ImageEntry entry) {
    662687        synchronized (this) {
    663             this.file = file;
     688            this.entry = entry;
    664689            image = null;
    665690            errorLoading = false;
    666691        }
    667692        repaint();
    668         if (file != null) {
    669             new Thread(new LoadImageRunnable(file, orientation), LoadImageRunnable.class.getName()).start();
     693        if (entry != null) {
     694            new Thread(new LoadImageRunnable(entry), LoadImageRunnable.class.getName()).start();
    670695        }
    671696    }
    672697
     
    681706
    682707    @Override
    683708    public void paintComponent(Graphics g) {
     709        ImageEntry entry;
    684710        Image image;
    685         File file;
    686711        VisRect visibleRect;
    687712        boolean errorLoading;
    688713
    689714        synchronized (this) {
    690715            image = this.image;
    691             file = this.file;
     716            entry = this.entry;
    692717            visibleRect = this.visibleRect;
    693718            errorLoading = this.errorLoading;
    694719        }
     
    698723        }
    699724
    700725        Dimension size = getSize();
    701         if (file == null) {
     726        if (entry == null) {
    702727            g.setColor(Color.black);
    703728            String noImageStr = tr("No image");
    704729            Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(noImageStr, g);
     
    709734            g.setColor(Color.black);
    710735            String loadingStr;
    711736            if (!errorLoading) {
    712                 loadingStr = tr("Loading {0}", file.getName());
     737                loadingStr = tr("Loading {0}", entry.getFile().getName());
    713738            } else {
    714                 loadingStr = tr("Error on file {0}", file.getName());
     739                loadingStr = tr("Error on file {0}", entry.getFile().getName());
    715740            }
    716741            Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
    717742            g.drawString(loadingStr,
     
    771796                g.drawRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y);
    772797            }
    773798            if (errorLoading) {
    774                 String loadingStr = tr("Error on file {0}", file.getName());
     799                String loadingStr = tr("Error on file {0}", entry.getFile().getName());
    775800                Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
    776801                g.drawString(loadingStr,
    777802                        (int) ((size.width - noImageSize.getWidth()) / 2),
     
    884909     * the component size.
    885910     */
    886911    public void zoomBestFitOrOne() {
    887         File file;
     912        ImageEntry entry;
    888913        Image image;
    889914        VisRect visibleRect;
    890915
    891916        synchronized (this) {
    892             file = this.file;
     917            entry = this.entry;
    893918            image = this.image;
    894919            visibleRect = this.visibleRect;
    895920        }
     
    910935        }
    911936
    912937        synchronized (this) {
    913             if (file == this.file) {
     938            if (this.entry == entry) {
    914939                this.visibleRect = visibleRect;
    915940            }
    916941        }
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java

     
    2020import com.drew.metadata.MetadataException;
    2121import com.drew.metadata.exif.ExifIFD0Directory;
    2222import com.drew.metadata.exif.GpsDirectory;
     23import com.drew.metadata.jpeg.JpegDirectory;
    2324
    2425/**
    2526 * Stores info about each image
     
    5253    /** The time after correlation with a gpx track */
    5354    private Date gpsTime;
    5455
     56    private int width;
     57    private int height;
     58
    5559    /**
    5660     * When the correlation dialog is open, we like to show the image position
    5761     * for the current time offset on the map in real time.
     
    7680    }
    7781
    7882    /**
     83     * @return width of the image this ImageEntry represents
     84     */
     85    public int getWidth() {
     86        return width;
     87    }
     88
     89    /**
     90     * @return height of the image this ImageEntry represents
     91     */
     92    public int getHeight() {
     93        return height;
     94    }
     95
     96    /**
    7997     * Returns the position value. The position value from the temporary copy
    8098     * is returned if that copy exists.
    8199     * @return the position value
     
    141159     * @return EXIF orientation
    142160     */
    143161    public Integer getExifOrientation() {
    144         return exifOrientation;
     162        return exifOrientation != null ? exifOrientation : 1;
    145163    }
    146164
    147165    /**
     
    230248    }
    231249
    232250    /**
     251     * @param width set the width of this ImageEntry
     252     */
     253    public void setWidth(int width) {
     254        this.width = width;
     255    }
     256
     257    /**
     258     * @param height set the height of this ImageEntry
     259     */
     260    public void setHeight(int height) {
     261        this.height = height;
     262    }
     263
     264    /**
    233265     * Sets the position.
    234266     * @param pos cached position
    235267     */
     
    456488            setExifTime(null);
    457489        }
    458490
     491        final Directory dir = metadata.getFirstDirectoryOfType(JpegDirectory.class);
    459492        final Directory dirExif = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
    460493        final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
    461494
     
    468501            Logging.debug(ex);
    469502        }
    470503
     504        try {
     505            if (dir != null) {
     506                // there are cases where these do not match width and height stored in dirExif
     507                int width = dir.getInt(JpegDirectory.TAG_IMAGE_WIDTH);
     508                int height = dir.getInt(JpegDirectory.TAG_IMAGE_HEIGHT);
     509                setWidth(width);
     510                setHeight(height);
     511            }
     512        } catch (MetadataException ex) {
     513            Logging.debug(ex);
     514        }
     515
    471516        if (dirGps == null) {
    472517            setExifCoor(null);
    473518            setPos(null);
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java

     
    322322            if (imageChanged) {
    323323                // Set only if the image is new to preserve zoom and position if the same image is redisplayed
    324324                // (e.g. to update the OSD).
    325                 imgDisplay.setImage(entry.getFile(), entry.getExifOrientation());
     325                imgDisplay.setImage(entry);
    326326            }
    327327            setTitle(tr("Geotagged Images") + (entry.getFile() != null ? " - " + entry.getFile().getName() : ""));
    328328            StringBuilder osd = new StringBuilder(entry.getFile() != null ? entry.getFile().getName() : "");
     
    355355            // if this method is called to reinitialize dialog content with a blank image,
    356356            // do not actually show the dialog again with a blank image if currently hidden (fix #10672)
    357357            setTitle(tr("Geotagged Images"));
    358             imgDisplay.setImage(null, null);
     358            imgDisplay.setImage(null);
    359359            imgDisplay.setOsdText("");
    360360            return;
    361361        }