| 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); |
| 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; |
| 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; |
| 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; |
| 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); |
| 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); |
| 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 | | } |