Changeset 17871 in josm
- Timestamp:
- 2021-05-06T21:47:00+02:00 (4 years ago)
- Location:
- trunk/src/org/openstreetmap/josm
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
r17834 r17871 10 10 import java.awt.Graphics2D; 11 11 import java.awt.Image; 12 import java.awt.MediaTracker;13 12 import java.awt.Point; 14 13 import java.awt.Rectangle; … … 22 21 import java.awt.geom.Rectangle2D; 23 22 import java.awt.image.BufferedImage; 24 import java.awt.image.ImageObserver;25 import java.io.File;26 23 import java.io.IOException; 27 24 import java.util.Objects; 28 25 29 import javax.imageio.ImageIO;30 26 import javax.swing.JComponent; 31 27 import javax.swing.SwingUtilities; … … 33 29 import org.openstreetmap.josm.data.preferences.BooleanProperty; 34 30 import org.openstreetmap.josm.data.preferences.DoubleProperty; 31 import org.openstreetmap.josm.data.preferences.IntegerProperty; 32 import org.openstreetmap.josm.gui.MainApplication; 35 33 import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings; 36 34 import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings.FilterChangeListener; … … 40 38 import org.openstreetmap.josm.tools.Destroyable; 41 39 import org.openstreetmap.josm.tools.ExifReader; 42 import org.openstreetmap.josm.tools.HiDPISupport;43 40 import org.openstreetmap.josm.tools.ImageProcessor; 44 import org.openstreetmap.josm.tools.ImageProvider;45 41 import org.openstreetmap.josm.tools.Logging; 46 42 … … 79 75 private VisRect selectedRect; 80 76 81 /** The tracker to load the images */82 private final MediaTracker tracker = new MediaTracker(this);83 84 77 private final ImgDisplayMouseListener imgMouseListener = new ImgDisplayMouseListener(); 85 78 … … 104 97 new DoubleProperty("geoimage.maximum-zoom-scale", 2.0); 105 98 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; 99 /** Maximum width (in pixels) for loading images **/ 100 private static final IntegerProperty MAX_WIDTH = 101 new IntegerProperty("geoimage.maximum-width", 6000); 113 102 114 103 /** Show a background for the error text (may be hard on eyes) */ … … 121 110 dragButton = AGPIFO_STYLE.get() ? 1 : 3; 122 111 zoomButton = dragButton == 1 ? 3 : 1; 123 }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 112 } 131 113 } … … 237 219 238 220 /** The thread that reads the images. */ 239 protected class LoadImageRunnable implements Runnable , ImageObserver{221 protected class LoadImageRunnable implements Runnable { 240 222 241 223 private final ImageEntry entry; 242 private final File file;243 224 244 225 LoadImageRunnable(ImageEntry entry) { 245 226 this.entry = entry; 246 this.file = entry.getFile();247 }248 249 @Override250 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;261 }262 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 called278 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 }303 }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());312 227 } 313 228 314 229 @Override 315 230 public void run() { 316 BufferedImage img;317 231 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 } 232 Dimension target = new Dimension(MAX_WIDTH.get(), MAX_WIDTH.get()); 233 BufferedImage img = entry.read(target); 234 if (img == null) { 235 synchronized (ImageDisplay.this) { 236 errorLoading = true; 237 ImageDisplay.this.repaint(); 238 return; 345 239 } 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); 353 } 354 } else { 355 Logging.warn("Abort loading of {0} since it might not fit into memory", file); 356 img = null; 357 } 240 } 241 242 int width = img.getWidth(); 243 int height = img.getHeight(); 244 entry.setWidth(width); 245 entry.setHeight(height); 358 246 359 247 synchronized (ImageDisplay.this) { … … 363 251 } 364 252 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; 253 boolean switchedDim = false; 254 if (ExifReader.orientationNeedsCorrection(entry.getExifOrientation())) { 255 if (ExifReader.orientationSwitchesDimensions(entry.getExifOrientation())) { 256 width = img.getHeight(null); 257 height = img.getWidth(null); 258 switchedDim = true; 382 259 } 383 384 ImageDisplay.this.image = img;385 updateProcessedImage();386 // This will clear the loading info box387 ImageDisplay.this.oldEntry = ImageDisplay.this.entry;388 visibleRect = new VisRect(0, 0, width, height);389 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);260 final BufferedImage rot = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 261 final AffineTransform xform = ExifReader.getRestoreOrientationTransform( 262 entry.getExifOrientation(), 263 img.getWidth(null), 264 img.getHeight(null)); 265 final Graphics2D g = rot.createGraphics(); 266 g.drawImage(img, xform, null); 267 g.dispose(); 268 img = rot; 392 269 } 393 270 271 ImageDisplay.this.image = img; 272 updateProcessedImage(); 273 // This will clear the loading info box 274 ImageDisplay.this.oldEntry = ImageDisplay.this.entry; 275 visibleRect = new VisRect(0, 0, width, height); 276 277 Logging.debug("Loaded {0} with dimensions {1}x{2} memoryTaken={3}m exifOrientationSwitchedDimension={4}", 278 entry.getFile().getPath(), width, height, width * height * 4 / 1024 / 1024, switchedDim); 279 394 280 selectedRect = null; 395 errorLoading = (img == null);281 errorLoading = false; 396 282 } 397 283 ImageDisplay.this.repaint(); … … 746 632 LoadImageRunnable runnable = setImage0(entry); 747 633 if (runnable != null) { 748 new Thread(runnable, LoadImageRunnable.class.getName()).start();634 MainApplication.worker.execute(runnable); 749 635 } 750 636 } … … 821 707 Rectangle r = new Rectangle(visibleRect); 822 708 Rectangle target = calculateDrawImageRectangle(visibleRect, size); 823 double scale = target.width / (double) r.width; // pixel ratio is 1:1824 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 algorithm836 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 scaler844 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 exceeding849 // its bounds, it will silently fail; crop with r first in such cases850 // (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 }856 709 857 710 g.drawImage(image, -
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java
r17580 r17871 2 2 package org.openstreetmap.josm.gui.layer.geoimage; 3 3 4 import java.awt.Dimension; 4 5 import java.awt.Image; 6 import java.awt.image.BufferedImage; 5 7 import java.io.File; 8 import java.io.IOException; 9 import java.io.UncheckedIOException; 6 10 import java.util.Collections; 7 11 import java.util.Objects; … … 9 13 import org.openstreetmap.josm.data.ImageData; 10 14 import org.openstreetmap.josm.data.gpx.GpxImageEntry; 15 import org.openstreetmap.josm.tools.ImageProvider; 16 import org.openstreetmap.josm.tools.Logging; 17 18 import javax.imageio.IIOParam; 19 import javax.imageio.ImageReadParam; 20 import javax.imageio.ImageReader; 21 22 import static org.openstreetmap.josm.tools.I18n.tr; 11 23 12 24 /** … … 118 130 return Objects.equals(thumbnail, other.thumbnail) && Objects.equals(dataSet, other.dataSet); 119 131 } 132 133 /** 134 * Reads the image represented by this entry in the given target dimension. 135 * @param target the desired dimension used for {@linkplain IIOParam#setSourceSubsampling subsampling} or {@code null} 136 * @return the read image 137 * @throws IOException if any I/O error occurs 138 */ 139 public BufferedImage read(Dimension target) throws IOException { 140 Logging.info(tr("Loading {0}", getFile().getPath())); 141 return ImageProvider.read(getFile(), false, false, 142 r -> target == null ? r.getDefaultReadParam() : withSubsampling(r, target)); 143 } 144 145 private ImageReadParam withSubsampling(ImageReader reader, Dimension target) { 146 try { 147 ImageReadParam param = reader.getDefaultReadParam(); 148 Dimension source = new Dimension(reader.getWidth(0), reader.getHeight(0)); 149 if (source.getWidth() > target.getWidth() || source.getHeight() > target.getHeight()) { 150 int subsampling = (int) Math.floor(Math.max( 151 source.getWidth() / target.getWidth(), 152 source.getHeight() / target.getHeight())); 153 param.setSourceSubsampling(subsampling, subsampling, 0, 0); 154 } 155 return param; 156 } catch (IOException e) { 157 throw new UncheckedIOException(e); 158 } 159 } 120 160 } -
trunk/src/org/openstreetmap/josm/tools/ImageProvider.java
r17369 r17871 48 48 import java.util.concurrent.Executors; 49 49 import java.util.function.Consumer; 50 import java.util.function.Function; 50 51 import java.util.function.UnaryOperator; 51 52 import java.util.regex.Matcher; … … 1548 1549 */ 1549 1550 public static BufferedImage read(File input, boolean readMetadata, boolean enforceTransparency) throws IOException { 1551 return read(input, readMetadata, enforceTransparency, ImageReader::getDefaultReadParam); 1552 } 1553 1554 /** 1555 * Returns a <code>BufferedImage</code> as the result of decoding 1556 * a supplied <code>File</code> with an <code>ImageReader</code> 1557 * chosen automatically from among those currently registered. 1558 * The <code>File</code> is wrapped in an 1559 * <code>ImageInputStream</code>. If no registered 1560 * <code>ImageReader</code> claims to be able to read the 1561 * resulting stream, <code>null</code> is returned. 1562 * 1563 * <p> The current cache settings from <code>getUseCache</code>and 1564 * <code>getCacheDirectory</code> will be used to control caching in the 1565 * <code>ImageInputStream</code> that is created. 1566 * 1567 * <p> Note that there is no <code>read</code> method that takes a 1568 * filename as a <code>String</code>; use this method instead after 1569 * creating a <code>File</code> from the filename. 1570 * 1571 * <p> This method does not attempt to locate 1572 * <code>ImageReader</code>s that can read directly from a 1573 * <code>File</code>; that may be accomplished using 1574 * <code>IIORegistry</code> and <code>ImageReaderSpi</code>. 1575 * 1576 * @param input a <code>File</code> to read from. 1577 * @param readMetadata if {@code true}, makes sure to read image metadata to detect transparency color, if any. 1578 * In that case the color can be retrieved later through {@link #PROP_TRANSPARENCY_COLOR}. 1579 * Always considered {@code true} if {@code enforceTransparency} is also {@code true} 1580 * @param enforceTransparency if {@code true}, makes sure to read image metadata and, if the image does not 1581 * provide an alpha channel but defines a {@code TransparentColor} metadata node, that the resulting image 1582 * has a transparency set to {@code TRANSLUCENT} and uses the correct transparent color. 1583 * @param readParamFunction a function to compute the read parameters from the image reader 1584 * 1585 * @return a <code>BufferedImage</code> containing the decoded contents of the input, or <code>null</code>. 1586 * 1587 * @throws IllegalArgumentException if <code>input</code> is <code>null</code>. 1588 * @throws IOException if an error occurs during reading. 1589 * @see BufferedImage#getProperty 1590 * @since xxx 1591 */ 1592 public static BufferedImage read(File input, boolean readMetadata, boolean enforceTransparency, 1593 Function<ImageReader, ImageReadParam> readParamFunction) throws IOException { 1550 1594 CheckParameterUtil.ensureParameterNotNull(input, "input"); 1551 1595 if (!input.canRead()) { … … 1557 1601 throw new IIOException("Can't create an ImageInputStream!"); 1558 1602 } 1559 BufferedImage bi = read(stream, readMetadata, enforceTransparency); 1603 BufferedImage bi = read(stream, readMetadata, enforceTransparency, readParamFunction); 1560 1604 if (bi == null) { 1561 1605 stream.close(); … … 1687 1731 */ 1688 1732 public static BufferedImage read(ImageInputStream stream, boolean readMetadata, boolean enforceTransparency) throws IOException { 1733 return read(stream, readMetadata, enforceTransparency, ImageReader::getDefaultReadParam); 1734 } 1735 1736 private static BufferedImage read(ImageInputStream stream, boolean readMetadata, boolean enforceTransparency, 1737 Function<ImageReader, ImageReadParam> readParamFunction) throws IOException { 1689 1738 CheckParameterUtil.ensureParameterNotNull(stream, "stream"); 1690 1739 … … 1695 1744 1696 1745 ImageReader reader = iter.next(); 1697 ImageReadParam param = reader.getDefaultReadParam();1698 1746 reader.setInput(stream, true, !readMetadata && !enforceTransparency); 1747 ImageReadParam param = readParamFunction.apply(reader); 1699 1748 BufferedImage bi = null; 1700 1749 try { // NOPMD
Note:
See TracChangeset
for help on using the changeset viewer.