// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.layer.geoimage; import java.awt.Graphics2D; import java.awt.Image; import java.awt.MediaTracker; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import javax.imageio.ImageIO; import org.apache.commons.jcs.access.behavior.ICacheAccess; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; import org.openstreetmap.josm.data.cache.JCSCacheManager; import org.openstreetmap.josm.tools.ExifReader; public class ThumbsLoader implements Runnable { public static final int maxSize = 120; public static final int minSize = 22; public volatile boolean stop; private final Collection<ImageEntry> data; private final GeoImageLayer layer; private MediaTracker tracker; private ICacheAccess<String, BufferedImageCacheEntry> cache; private final boolean cacheOff = Main.pref.getBoolean("geoimage.noThumbnailCache", false); private ThumbsLoader(Collection<ImageEntry> data, GeoImageLayer layer) { this.data = data; this.layer = layer; initCache(); } /** * Constructs a new thumbnail loader that operates on a geoimage layer. * @param layer geoimage layer */ public ThumbsLoader(GeoImageLayer layer) { this(new ArrayList<>(layer.data), layer); } /** * Constructs a new thumbnail loader that operates on the image entries * @param entries image entries */ public ThumbsLoader(Collection<ImageEntry> entries) { this(entries, null); } /** * Initialize the thumbnail cache. */ private void initCache() { if (!cacheOff) { try { cache = JCSCacheManager.getCache("geoimage-thumbnails", 0, 120, Main.pref.getCacheDirectory().getPath() + File.separator + "geoimage-thumbnails"); } catch (IOException e) { Main.warn("Failed to initialize cache for geoimage-thumbnails"); Main.warn(e); } } } @Override public void run() { Main.debug("Load Thumbnails"); tracker = new MediaTracker(Main.map.mapView); for (ImageEntry entry : data) { if (stop) return; // Do not load thumbnails that were loaded before. if (!entry.hasThumbnail()) { entry.setThumbnail(loadThumb(entry)); if (layer != null && Main.isDisplayingMapView()) { layer.updateOffscreenBuffer = true; Main.map.mapView.repaint(); } } } if (layer != null) { layer.thumbsLoaded(); layer.updateOffscreenBuffer = true; Main.map.mapView.repaint(); } } private BufferedImage loadThumb(ImageEntry entry) { final String cacheIdent = entry.getFile().toString()+':'+maxSize; if (!cacheOff && cache != null) { try { BufferedImageCacheEntry cacheEntry = cache.get(cacheIdent); if (cacheEntry != null && cacheEntry.getImage() != null) { Main.debug(" from cache"); return cacheEntry.getImage(); } } catch (IOException e) { Main.warn(e); } } Image img = Toolkit.getDefaultToolkit().createImage(entry.getFile().getPath()); tracker.addImage(img, 0); try { tracker.waitForID(0); } catch (InterruptedException e) { Main.error(" InterruptedException while loading thumb"); Thread.currentThread().interrupt(); return null; } if (tracker.isErrorID(1) || img.getWidth(null) <= 0 || img.getHeight(null) <= 0) { Main.error(" Invalid image"); return null; } final int w = img.getWidth(null); final int h = img.getHeight(null); final int hh, ww; final Integer exifOrientation = entry.getExifOrientation(); if (exifOrientation != null && ExifReader.orientationSwitchesDimensions(exifOrientation)) { ww = h; hh = w; } else { ww = w; hh = h; } Rectangle targetSize = ImageDisplay.calculateDrawImageRectangle( new Rectangle(0, 0, ww, hh), new Rectangle(0, 0, maxSize, maxSize)); BufferedImage scaledBI = new BufferedImage(targetSize.width, targetSize.height, BufferedImage.TYPE_INT_RGB); Graphics2D g = scaledBI.createGraphics(); final AffineTransform scale = AffineTransform.getScaleInstance((double) targetSize.width / ww, (double) targetSize.height / hh); if (exifOrientation != null) { final AffineTransform restoreOrientation = ExifReader.getRestoreOrientationTransform(exifOrientation, w, h); scale.concatenate(restoreOrientation); } while (!g.drawImage(img, scale, null)) { try { Thread.sleep(10); } catch (InterruptedException e) { Main.warn("InterruptedException while drawing thumb"); Thread.currentThread().interrupt(); } } g.dispose(); tracker.removeImage(img); if (scaledBI.getWidth() <= 0 || scaledBI.getHeight() <= 0) { Main.error(" Invalid image"); return null; } if (!cacheOff && cache != null) { try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { ImageIO.write(scaledBI, "png", output); cache.put(cacheIdent, new BufferedImageCacheEntry(output.toByteArray())); } catch (IOException e) { Main.warn("Failed to save geoimage thumb to cache"); Main.warn(e); } } return scaledBI; } }