001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.layer.geoimage; 003 004import java.awt.Graphics2D; 005import java.awt.Image; 006import java.awt.MediaTracker; 007import java.awt.Rectangle; 008import java.awt.Toolkit; 009import java.awt.geom.AffineTransform; 010import java.awt.image.BufferedImage; 011import java.io.ByteArrayOutputStream; 012import java.io.File; 013import java.io.IOException; 014import java.util.ArrayList; 015import java.util.List; 016 017import javax.imageio.ImageIO; 018 019import org.apache.commons.jcs.access.behavior.ICacheAccess; 020import org.openstreetmap.josm.Main; 021import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 022import org.openstreetmap.josm.data.cache.JCSCacheManager; 023import org.openstreetmap.josm.tools.ExifReader; 024 025public class ThumbsLoader implements Runnable { 026 public static final int maxSize = 120; 027 public static final int minSize = 22; 028 public volatile boolean stop; 029 private List<ImageEntry> data; 030 private GeoImageLayer layer; 031 private MediaTracker tracker; 032 private ICacheAccess<String, BufferedImageCacheEntry> cache; 033 private boolean cacheOff = Main.pref.getBoolean("geoimage.noThumbnailCache", false); 034 035 public ThumbsLoader(GeoImageLayer layer) { 036 this.layer = layer; 037 this.data = new ArrayList<>(layer.data); 038 if (!cacheOff) { 039 try { 040 cache = JCSCacheManager.getCache("geoimage-thumbnails", 0, 120, 041 Main.pref.getCacheDirectory().getPath() + File.separator + "geoimage-thumbnails"); 042 } catch (IOException e) { 043 Main.warn("Failed to initialize cache for geoimage-thumbnails"); 044 Main.warn(e); 045 } 046 } 047 } 048 049 @Override 050 public void run() { 051 Main.debug("Load Thumbnails"); 052 tracker = new MediaTracker(Main.map.mapView); 053 for (ImageEntry entry : data) { 054 if (stop) return; 055 056 // Do not load thumbnails that were loaded before. 057 if (entry.thumbnail == null) { 058 entry.thumbnail = loadThumb(entry); 059 060 if (Main.isDisplayingMapView()) { 061 layer.updateOffscreenBuffer = true; 062 Main.map.mapView.repaint(); 063 } 064 } 065 } 066 layer.thumbsLoaded(); 067 layer.updateOffscreenBuffer = true; 068 Main.map.mapView.repaint(); 069 } 070 071 private BufferedImage loadThumb(ImageEntry entry) { 072 final String cacheIdent = entry.getFile()+":"+maxSize; 073 074 if (!cacheOff && cache != null) { 075 try { 076 BufferedImageCacheEntry cacheEntry = cache.get(cacheIdent); 077 if (cacheEntry != null && cacheEntry.getImage() != null) { 078 Main.debug(" from cache"); 079 return cacheEntry.getImage(); 080 } 081 } catch (IOException e) { 082 Main.warn(e); 083 } 084 } 085 086 Image img = Toolkit.getDefaultToolkit().createImage(entry.getFile().getPath()); 087 tracker.addImage(img, 0); 088 try { 089 tracker.waitForID(0); 090 } catch (InterruptedException e) { 091 Main.error(" InterruptedException while loading thumb"); 092 return null; 093 } 094 if (tracker.isErrorID(1) || img.getWidth(null) <= 0 || img.getHeight(null) <= 0) { 095 Main.error(" Invalid image"); 096 return null; 097 } 098 099 final int w = img.getWidth(null); 100 final int h = img.getHeight(null); 101 final int hh, ww; 102 final Integer exifOrientation = entry.getExifOrientation(); 103 if (exifOrientation != null && ExifReader.orientationSwitchesDimensions(exifOrientation)) { 104 ww = h; 105 hh = w; 106 } else { 107 ww = w; 108 hh = h; 109 } 110 111 Rectangle targetSize = ImageDisplay.calculateDrawImageRectangle( 112 new Rectangle(0, 0, ww, hh), 113 new Rectangle(0, 0, maxSize, maxSize)); 114 BufferedImage scaledBI = new BufferedImage(targetSize.width, targetSize.height, BufferedImage.TYPE_INT_RGB); 115 Graphics2D g = scaledBI.createGraphics(); 116 117 final AffineTransform scale = AffineTransform.getScaleInstance((double) targetSize.width / ww, (double) targetSize.height / hh); 118 if (exifOrientation != null) { 119 final AffineTransform restoreOrientation = ExifReader.getRestoreOrientationTransform(exifOrientation, w, h); 120 scale.concatenate(restoreOrientation); 121 } 122 123 while (!g.drawImage(img, scale, null)) { 124 try { 125 Thread.sleep(10); 126 } catch (InterruptedException ie) { 127 Main.warn("InterruptedException while drawing thumb"); 128 } 129 } 130 g.dispose(); 131 tracker.removeImage(img); 132 133 if (scaledBI.getWidth() <= 0 || scaledBI.getHeight() <= 0) { 134 Main.error(" Invalid image"); 135 return null; 136 } 137 138 if (!cacheOff && cache != null) { 139 try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { 140 ImageIO.write(scaledBI, "png", output); 141 cache.put(cacheIdent, new BufferedImageCacheEntry(output.toByteArray())); 142 } catch (IOException e) { 143 Main.warn("Failed to save geoimage thumb to cache"); 144 Main.warn(e); 145 } 146 } 147 148 return scaledBI; 149 } 150}