Ticket #15574: josm-image-loading_check-ram-constraints-to-prevent-oom-exceptions_do-not-use-expensive-scaling-while-dragging.patch
File josm-image-loading_check-ram-constraints-to-prevent-oom-exceptions_do-not-use-expensive-scaling-while-dragging.patch, 16.2 KB (added by , 6 years ago) |
---|
-
src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
22 22 import java.awt.geom.AffineTransform; 23 23 import java.awt.geom.Rectangle2D; 24 24 import java.awt.image.BufferedImage; 25 import java.awt.image.ImageObserver; 25 26 import java.io.File; 26 27 27 28 import javax.swing.JComponent; … … 112 113 public static class VisRect extends Rectangle { 113 114 private final Rectangle init; 114 115 116 /** set when this {@code VisRect} is updated by a mouse drag operation and 117 * unset on mouse release **/ 118 public boolean isDragUpdate; 119 115 120 /** 116 121 * Constructs a new {@code VisRect}. 117 122 * @param x the specified X coordinate … … 124 129 init = new Rectangle(this); 125 130 } 126 131 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 */ 127 140 public VisRect(int x, int y, int width, int height, VisRect peer) { 128 141 super(x, y, width, height); 129 142 init = peer.init; … … 145 158 this(0, 0, 0, 0); 146 159 } 147 160 161 @SuppressWarnings("javadoc") 148 162 public boolean isFullView() { 149 163 return init.equals(this); 150 164 } 151 165 166 @SuppressWarnings("javadoc") 152 167 public boolean isFullView1D() { 153 168 return (init.x == x && init.width == width) 154 169 || (init.y == y && init.height == height); 155 170 } 156 171 172 @SuppressWarnings("javadoc") 157 173 public void reset() { 158 174 setBounds(init); 159 175 } 160 176 177 @SuppressWarnings("javadoc") 161 178 public void checkRectPos() { 162 179 if (x < 0) { 163 180 x = 0; … … 173 190 } 174 191 } 175 192 193 @SuppressWarnings("javadoc") 176 194 public void checkRectSize() { 177 195 if (width > init.width) { 178 196 width = init.width; … … 182 200 } 183 201 } 184 202 203 @SuppressWarnings("javadoc") 185 204 public void checkPointInside(Point p) { 186 205 if (p.x < x) { 187 206 p.x = x; … … 199 218 } 200 219 201 220 /** The thread that reads the images. */ 202 private class LoadImageRunnable implements Runnable {221 private class LoadImageRunnable implements Runnable, ImageObserver { 203 222 204 223 private final File file; 205 224 private final int orientation; 225 private int width; 226 private int height; 206 227 207 228 LoadImageRunnable(File file, Integer orientation) { 208 229 this.file = file; … … 210 231 } 211 232 212 233 @Override 234 public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { 235 if (((infoflags & ImageObserver.WIDTH) == ImageObserver.WIDTH) && 236 ((infoflags & ImageObserver.HEIGHT) == ImageObserver.HEIGHT)) { 237 this.width = width; 238 this.height = height; 239 synchronized (this) { 240 this.notify(); 241 return false; 242 } 243 } 244 return true; 245 } 246 247 @Override 213 248 public void run() { 214 249 Image img = Toolkit.getDefaultToolkit().createImage(file.getPath()); 215 tracker.addImage(img, 1);216 250 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; 251 synchronized (this) { 252 width = -1; 253 img.getWidth(this); 254 img.getHeight(this); 255 256 while (width < 0) { 257 try { 258 this.wait(); 259 if (width < 0) { 260 errorLoading = true; 261 return; 262 } 263 } catch (InterruptedException e) { 264 e.printStackTrace(); 265 } 223 266 } 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(); 267 } 268 269 long allocatedMem = Runtime.getRuntime().totalMemory() - 270 Runtime.getRuntime().freeMemory(); 271 long mem = Runtime.getRuntime().maxMemory()-allocatedMem; 272 273 if (mem > ((long)width*height*4)*2) { 274 Logging.info("Loading "+file.getPath()+" using default toolkit"); 275 tracker.addImage(img, 1); 276 277 // Wait for the end of loading 278 while (!tracker.checkID(1, true)) { 279 if (this.file != ImageDisplay.this.file) { 280 // The file has changed 281 tracker.removeImage(img); 282 return; 283 } 284 try { 285 Thread.sleep(5); 286 } catch (InterruptedException e) { 287 Logging.warn("InterruptedException in "+getClass().getSimpleName()+ 288 " while loading image "+file.getPath()); 289 Thread.currentThread().interrupt(); 290 } 229 291 } 292 if (tracker.isErrorID(1)) { 293 img = null; 294 System.gc(); 295 } 296 } else { 297 img = null; 230 298 } 231 299 232 boolean error = tracker.isErrorID(1);233 if (img.getWidth(null) < 0 || img.getHeight(null) < 0) {234 error = true;300 if (img == null || width <= 0 || height <= 0) { 301 tracker.removeImage(img); 302 img = null; 235 303 } 236 304 237 305 synchronized (ImageDisplay.this) { … … 241 309 return; 242 310 } 243 311 244 if (!error) { 245 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 312 if (img != null) { 313 boolean switchedDim = false; 251 314 if (ExifReader.orientationNeedsCorrection(orientation)) { 252 final int hh, ww;253 315 if (ExifReader.orientationSwitchesDimensions(orientation)) { 254 ww = h; 255 hh = w; 256 } else { 257 ww = w; 258 hh = h; 316 width = img.getHeight(null); 317 height = img.getWidth(null); 318 switchedDim = true; 259 319 } 260 final BufferedImage rot = new BufferedImage(ww, hh, BufferedImage.TYPE_INT_RGB); 261 final AffineTransform xform = ExifReader.getRestoreOrientationTransform(orientation, w, h); 320 final BufferedImage rot = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 321 final AffineTransform xform = ExifReader.getRestoreOrientationTransform(orientation, 322 img.getWidth(null), img.getHeight(null)); 262 323 final Graphics2D g = rot.createGraphics(); 263 g.drawImage(im age, xform, null);324 g.drawImage(img, xform, null); 264 325 g.dispose(); 265 266 visibleRect.setSize(ww, hh); 267 image.flush(); 268 ImageDisplay.this.image = rot; 326 img.flush(); 327 img = rot; 269 328 } 329 330 ImageDisplay.this.image = img; 331 visibleRect = new VisRect(0, 0, width, height); 332 333 Logging.info("Loaded "+file.getPath()+ 334 " with dimensions "+width+"x"+height+ 335 " mem(prev-avail="+mem/1024/1024+"m,taken="+ 336 width*height*4/1024/1024+"m)"+ 337 " exifOrientationSwitchedDimension="+switchedDim); 270 338 } 271 339 272 340 selectedRect = null; 273 errorLoading = error;341 errorLoading = (img == null); 274 342 } 275 343 tracker.removeImage(img); 276 344 ImageDisplay.this.repaint(); … … 473 541 474 542 if (mouseIsDragging(e)) { 475 543 Point p = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize()); 544 visibleRect.isDragUpdate = true; 476 545 visibleRect.x += mousePointInImg.x - p.x; 477 546 visibleRect.y += mousePointInImg.y - p.y; 478 547 visibleRect.checkRectPos(); … … 503 572 504 573 @Override 505 574 public void mouseReleased(MouseEvent e) { 506 if (!mouseIsZoomSelecting(e) || selectedRect == null)507 return;508 509 575 File file; 510 576 Image image; 511 577 … … 514 580 image = ImageDisplay.this.image; 515 581 } 516 582 517 if (image == null) {583 if (image == null) 518 584 return; 585 586 if (mouseIsDragging(e)) { 587 visibleRect.isDragUpdate = false; 519 588 } 520 589 521 int oldWidth = selectedRect.width; 522 int oldHeight = selectedRect.height; 590 if (mouseIsZoomSelecting(e) && selectedRect != null) { 591 int oldWidth = selectedRect.width; 592 int oldHeight = selectedRect.height; 523 593 524 // Check that the zoom doesn't exceed MAX_ZOOM:1525 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 }594 // Check that the zoom doesn't exceed MAX_ZOOM:1 595 if (selectedRect.width < getSize().width / MAX_ZOOM.get()) { 596 selectedRect.width = (int) (getSize().width / MAX_ZOOM.get()); 597 } 598 if (selectedRect.height < getSize().height / MAX_ZOOM.get()) { 599 selectedRect.height = (int) (getSize().height / MAX_ZOOM.get()); 600 } 531 601 532 // Set the same ratio for the visible rectangle and the display area533 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 }602 // Set the same ratio for the visible rectangle and the display area 603 int hFact = selectedRect.height * getSize().width; 604 int wFact = selectedRect.width * getSize().height; 605 if (hFact > wFact) { 606 selectedRect.width = hFact / getSize().height; 607 } else { 608 selectedRect.height = wFact / getSize().width; 609 } 540 610 541 // Keep the center of the selection542 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 }611 // Keep the center of the selection 612 if (selectedRect.width != oldWidth) { 613 selectedRect.x -= (selectedRect.width - oldWidth) / 2; 614 } 615 if (selectedRect.height != oldHeight) { 616 selectedRect.y -= (selectedRect.height - oldHeight) / 2; 617 } 548 618 549 selectedRect.checkRectSize(); 550 selectedRect.checkRectPos(); 619 selectedRect.checkRectSize(); 620 selectedRect.checkRectPos(); 621 } 551 622 552 623 synchronized (ImageDisplay.this) { 553 624 if (file == ImageDisplay.this.file) { 554 ImageDisplay.this.visibleRect.setBounds(selectedRect); 625 if (selectedRect == null) { 626 ImageDisplay.this.visibleRect = visibleRect; 627 } else { 628 ImageDisplay.this.visibleRect.setBounds(selectedRect); 629 selectedRect = null; 630 } 555 631 } 556 632 } 557 selectedRect = null;558 633 ImageDisplay.this.repaint(); 559 634 } 560 635 … … 586 661 preferenceChanged(null); 587 662 } 588 663 664 /** 665 * Sets a new source image to be displayed by this {@code ImageDisplay}. 666 * @param file new source image 667 * @param orientation orientation of new source (landscape, portrait, upside-down, etc.) 668 */ 589 669 public void setImage(File file, Integer orientation) { 590 670 synchronized (this) { 591 671 this.file = file; … … 650 730 Rectangle target = calculateDrawImageRectangle(visibleRect, size); 651 731 double scale = target.width / (double) r.width; // pixel ratio is 1:1 652 732 653 if (selectedRect == null && bilinLower < scale && scale < bilinUpper) { 654 BufferedImage bi = ImageProvider.toBufferedImage(image, r); 655 r.x = r.y = 0; 733 if (selectedRect == null && !visibleRect.isDragUpdate && 734 bilinLower < scale && scale < bilinUpper) { 735 try { 736 BufferedImage bi = ImageProvider.toBufferedImage(image, r); 737 if (bi != null) { 738 r.x = r.y = 0; 656 739 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; 740 // See https://community.oracle.com/docs/DOC-983611 - The Perils of Image.getScaledInstance() 741 // Pre-scale image when downscaling by more than two times to avoid aliasing from default algorithm 742 bi = ImageProvider.createScaledImage(bi, target.width, target.height, 743 RenderingHints.VALUE_INTERPOLATION_BILINEAR); 744 r.width = target.width; 745 r.height = target.height; 746 image = bi; 747 } 748 } catch (java.lang.OutOfMemoryError oom) { 749 // fall-back to the non-bilinear scaler 750 r.x = visibleRect.x; 751 r.y = visibleRect.y; 752 System.gc(); 753 } 663 754 } else { 664 755 // if target and r cause drawImage to scale image region to a tmp buffer exceeding 665 756 // its bounds, it will silently fail; crop with r first in such cases … … 797 888 return new VisRect(x + compRect.x, y + compRect.y, w, h, imgRect); 798 889 } 799 890 891 /** 892 * Make the current image either scale to fit inside this component, 893 * or show a portion of image (1:1), if the image size is larger than 894 * the component size. 895 */ 800 896 public void zoomBestFitOrOne() { 801 897 File file; 802 898 Image image;