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 , 7 years ago) |
---|
-
src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
79 79 import org.openstreetmap.josm.gui.widgets.JosmTextField; 80 80 import org.openstreetmap.josm.io.GpxReader; 81 81 import org.openstreetmap.josm.spi.preferences.Config; 82 import org.openstreetmap.josm.tools.ExifReader;83 82 import org.openstreetmap.josm.tools.GBC; 84 83 import org.openstreetmap.josm.tools.ImageProvider; 85 84 import org.openstreetmap.josm.tools.JosmRuntimeException; … … 458 457 imgList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 459 458 imgList.getSelectionModel().addListSelectionListener(evt -> { 460 459 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)); 463 461 Date date = yLayer.data.get(index).getExifTime(); 464 462 if (date != null) { 465 463 DateFormat df = DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.MEDIUM); … … 482 480 JpgImporter.FILE_FILTER_WITH_FOLDERS, JFileChooser.FILES_ONLY, "geoimage.lastdirectory"); 483 481 if (fc == null) 484 482 return; 485 File sel = fc.getSelectedFile(); 483 ImageEntry entry = new ImageEntry(fc.getSelectedFile()); 484 entry.extractExif(); 485 imgDisp.setImage(entry); 486 486 487 Integer orientation = ExifReader.readOrientation(sel); 488 imgDisp.setImage(sel, orientation); 489 490 Date date = ExifReader.readTime(sel); 487 Date date = entry.getExifTime(); 491 488 if (date != null) { 492 489 lbExifTime.setText(DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.MEDIUM).format(date)); 493 490 tfGpsTime.setText(DateUtils.getDateFormat(DateFormat.SHORT).format(date)+' '); -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
45 45 public class ImageDisplay extends JComponent implements PreferenceChangedListener { 46 46 47 47 /** The file that is currently displayed */ 48 private File file;48 private ImageEntry entry; 49 49 50 50 /** The image currently displayed */ 51 51 private transient Image image; … … 90 90 private static double bilinUpper; 91 91 private static double bilinLower; 92 92 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) { 95 99 if (e == null || 96 100 e.getKey().equals(AGPIFO_STYLE.getKey())) { 97 101 dragButton = AGPIFO_STYLE.get() ? 1 : 3; … … 106 110 } 107 111 } 108 112 113 @Override 114 public void preferenceChanged(PreferenceChangeEvent e) { 115 preferenceChangedImpl(e); 116 } 117 109 118 /** 110 119 * Manage the visible rectangle of an image with full bounds stored in init. 111 120 * @since 13127 … … 214 223 /** The thread that reads the images. */ 215 224 private class LoadImageRunnable implements Runnable, ImageObserver { 216 225 226 private final ImageEntry entry; 217 227 private final File file; 218 private final int orientation;219 private int width;220 private int height;221 228 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(); 225 232 } 226 233 227 234 @Override 228 235 public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { 229 236 if (((infoflags & ImageObserver.WIDTH) == ImageObserver.WIDTH) && 230 237 ((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(); 235 242 return false; 236 243 } 237 244 } 238 245 return true; 239 246 } 240 247 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); 244 253 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(); 256 271 } 257 } catch (InterruptedException e) {258 e.printStackTrace();259 272 } 260 273 } 261 274 } 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; 262 290 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(); 266 293 267 if (m em > ((long) width*height*4)*2) {294 if (mayFitMemory(((long) width)*height*4*2)) { 268 295 Logging.info("Loading {0} using default toolkit", file.getPath()); 269 296 tracker.addImage(img, 1); 270 297 271 298 // Wait for the end of loading 272 299 while (!tracker.checkID(1, true)) { 273 if (this. file != ImageDisplay.this.file) {300 if (this.entry != ImageDisplay.this.entry) { 274 301 // The file has changed 275 302 tracker.removeImage(img); 276 303 return; … … 279 306 Thread.sleep(5); 280 307 } catch (InterruptedException e) { 281 308 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()); 284 311 Thread.currentThread().interrupt(); 285 312 } 286 313 } 287 314 if (tracker.isErrorID(1)) { 315 // the tracker catches OutOfMemory conditions 288 316 img = null; 289 System.gc();290 317 } 291 318 } else { 292 319 img = null; 293 320 } 294 321 295 if (img == null || width <= 0 || height <= 0) {296 tracker.removeImage(img);297 img = null;298 }299 300 322 synchronized (ImageDisplay.this) { 301 if (this. file != ImageDisplay.this.file) {323 if (this.entry != ImageDisplay.this.entry) { 302 324 // The file has changed 303 325 tracker.removeImage(img); 304 326 return; … … 306 328 307 329 if (img != null) { 308 330 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())) { 311 333 width = img.getHeight(null); 312 334 height = img.getWidth(null); 313 335 switchedDim = true; 314 336 } 315 337 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)); 318 342 final Graphics2D g = rot.createGraphics(); 319 343 g.drawImage(img, xform, null); 320 344 g.dispose(); … … 325 349 ImageDisplay.this.image = img; 326 350 visibleRect = new VisRect(0, 0, width, height); 327 351 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); 330 354 } 331 355 332 356 selectedRect = null; … … 360 384 } 361 385 362 386 private void mouseWheelMovedImpl(int x, int y, int rotation, boolean refreshMousePointInImg) { 363 File file;387 ImageEntry entry; 364 388 Image image; 365 389 VisRect visibleRect; 366 390 367 391 synchronized (ImageDisplay.this) { 368 file = ImageDisplay.this.file;392 entry = ImageDisplay.this.entry; 369 393 image = ImageDisplay.this.image; 370 394 visibleRect = ImageDisplay.this.visibleRect; 371 395 } … … 417 441 visibleRect.checkRectPos(); 418 442 419 443 synchronized (ImageDisplay.this) { 420 if (ImageDisplay.this. file == file) {444 if (ImageDisplay.this.entry == entry) { 421 445 ImageDisplay.this.visibleRect = visibleRect; 422 446 } 423 447 } … … 446 470 @Override 447 471 public void mouseClicked(MouseEvent e) { 448 472 // Move the center to the clicked point. 449 File file;473 ImageEntry entry; 450 474 Image image; 451 475 VisRect visibleRect; 452 476 453 477 synchronized (ImageDisplay.this) { 454 file = ImageDisplay.this.file;478 entry = ImageDisplay.this.entry; 455 479 image = ImageDisplay.this.image; 456 480 visibleRect = ImageDisplay.this.visibleRect; 457 481 } … … 485 509 visibleRect.checkRectPos(); 486 510 487 511 synchronized (ImageDisplay.this) { 488 if (ImageDisplay.this. file == file) {512 if (ImageDisplay.this.entry == entry) { 489 513 ImageDisplay.this.visibleRect = visibleRect; 490 514 } 491 515 } … … 518 542 if (!mouseIsDragging(e) && !mouseIsZoomSelecting(e)) 519 543 return; 520 544 521 File file;545 ImageEntry entry; 522 546 Image image; 523 547 VisRect visibleRect; 524 548 525 549 synchronized (ImageDisplay.this) { 526 file = ImageDisplay.this.file;550 entry = ImageDisplay.this.entry; 527 551 image = ImageDisplay.this.image; 528 552 visibleRect = ImageDisplay.this.visibleRect; 529 553 } … … 538 562 visibleRect.y += mousePointInImg.y - p.y; 539 563 visibleRect.checkRectPos(); 540 564 synchronized (ImageDisplay.this) { 541 if (ImageDisplay.this. file == file) {565 if (ImageDisplay.this.entry == entry) { 542 566 ImageDisplay.this.visibleRect = visibleRect; 543 567 } 544 568 } … … 564 588 565 589 @Override 566 590 public void mouseReleased(MouseEvent e) { 567 File file;591 ImageEntry entry; 568 592 Image image; 593 VisRect visibleRect; 569 594 570 595 synchronized (ImageDisplay.this) { 571 file = ImageDisplay.this.file;596 entry = ImageDisplay.this.entry; 572 597 image = ImageDisplay.this.image; 598 visibleRect = ImageDisplay.this.visibleRect; 573 599 } 574 600 575 601 if (image == null) … … 613 639 } 614 640 615 641 synchronized (ImageDisplay.this) { 616 if ( file == ImageDisplay.this.file) {642 if (entry == ImageDisplay.this.entry) { 617 643 if (selectedRect == null) { 618 644 ImageDisplay.this.visibleRect = visibleRect; 619 645 } else { … … 655 681 656 682 /** 657 683 * 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 660 685 */ 661 public void setImage( File file, Integer orientation) {686 public void setImage(ImageEntry entry) { 662 687 synchronized (this) { 663 this. file = file;688 this.entry = entry; 664 689 image = null; 665 690 errorLoading = false; 666 691 } 667 692 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(); 670 695 } 671 696 } 672 697 … … 681 706 682 707 @Override 683 708 public void paintComponent(Graphics g) { 709 ImageEntry entry; 684 710 Image image; 685 File file;686 711 VisRect visibleRect; 687 712 boolean errorLoading; 688 713 689 714 synchronized (this) { 690 715 image = this.image; 691 file = this.file;716 entry = this.entry; 692 717 visibleRect = this.visibleRect; 693 718 errorLoading = this.errorLoading; 694 719 } … … 698 723 } 699 724 700 725 Dimension size = getSize(); 701 if ( file== null) {726 if (entry == null) { 702 727 g.setColor(Color.black); 703 728 String noImageStr = tr("No image"); 704 729 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(noImageStr, g); … … 709 734 g.setColor(Color.black); 710 735 String loadingStr; 711 736 if (!errorLoading) { 712 loadingStr = tr("Loading {0}", file.getName());737 loadingStr = tr("Loading {0}", entry.getFile().getName()); 713 738 } else { 714 loadingStr = tr("Error on file {0}", file.getName());739 loadingStr = tr("Error on file {0}", entry.getFile().getName()); 715 740 } 716 741 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g); 717 742 g.drawString(loadingStr, … … 771 796 g.drawRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y); 772 797 } 773 798 if (errorLoading) { 774 String loadingStr = tr("Error on file {0}", file.getName());799 String loadingStr = tr("Error on file {0}", entry.getFile().getName()); 775 800 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g); 776 801 g.drawString(loadingStr, 777 802 (int) ((size.width - noImageSize.getWidth()) / 2), … … 884 909 * the component size. 885 910 */ 886 911 public void zoomBestFitOrOne() { 887 File file;912 ImageEntry entry; 888 913 Image image; 889 914 VisRect visibleRect; 890 915 891 916 synchronized (this) { 892 file = this.file;917 entry = this.entry; 893 918 image = this.image; 894 919 visibleRect = this.visibleRect; 895 920 } … … 910 935 } 911 936 912 937 synchronized (this) { 913 if ( file == this.file) {938 if (this.entry == entry) { 914 939 this.visibleRect = visibleRect; 915 940 } 916 941 } -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java
20 20 import com.drew.metadata.MetadataException; 21 21 import com.drew.metadata.exif.ExifIFD0Directory; 22 22 import com.drew.metadata.exif.GpsDirectory; 23 import com.drew.metadata.jpeg.JpegDirectory; 23 24 24 25 /** 25 26 * Stores info about each image … … 52 53 /** The time after correlation with a gpx track */ 53 54 private Date gpsTime; 54 55 56 private int width; 57 private int height; 58 55 59 /** 56 60 * When the correlation dialog is open, we like to show the image position 57 61 * for the current time offset on the map in real time. … … 76 80 } 77 81 78 82 /** 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 /** 79 97 * Returns the position value. The position value from the temporary copy 80 98 * is returned if that copy exists. 81 99 * @return the position value … … 141 159 * @return EXIF orientation 142 160 */ 143 161 public Integer getExifOrientation() { 144 return exifOrientation ;162 return exifOrientation != null ? exifOrientation : 1; 145 163 } 146 164 147 165 /** … … 230 248 } 231 249 232 250 /** 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 /** 233 265 * Sets the position. 234 266 * @param pos cached position 235 267 */ … … 456 488 setExifTime(null); 457 489 } 458 490 491 final Directory dir = metadata.getFirstDirectoryOfType(JpegDirectory.class); 459 492 final Directory dirExif = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); 460 493 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class); 461 494 … … 468 501 Logging.debug(ex); 469 502 } 470 503 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 471 516 if (dirGps == null) { 472 517 setExifCoor(null); 473 518 setPos(null); -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
322 322 if (imageChanged) { 323 323 // Set only if the image is new to preserve zoom and position if the same image is redisplayed 324 324 // (e.g. to update the OSD). 325 imgDisplay.setImage(entry .getFile(), entry.getExifOrientation());325 imgDisplay.setImage(entry); 326 326 } 327 327 setTitle(tr("Geotagged Images") + (entry.getFile() != null ? " - " + entry.getFile().getName() : "")); 328 328 StringBuilder osd = new StringBuilder(entry.getFile() != null ? entry.getFile().getName() : ""); … … 355 355 // if this method is called to reinitialize dialog content with a blank image, 356 356 // do not actually show the dialog again with a blank image if currently hidden (fix #10672) 357 357 setTitle(tr("Geotagged Images")); 358 imgDisplay.setImage(null , null);358 imgDisplay.setImage(null); 359 359 imgDisplay.setOsdText(""); 360 360 return; 361 361 }