[8378] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[2593] | 2 | package org.openstreetmap.josm.gui.layer.geoimage;
|
---|
| 3 |
|
---|
| 4 | import java.awt.Graphics2D;
|
---|
| 5 | import java.awt.Image;
|
---|
[2617] | 6 | import java.awt.MediaTracker;
|
---|
| 7 | import java.awt.Rectangle;
|
---|
[2593] | 8 | import java.awt.Toolkit;
|
---|
[7956] | 9 | import java.awt.geom.AffineTransform;
|
---|
[2593] | 10 | import java.awt.image.BufferedImage;
|
---|
[8762] | 11 | import java.io.ByteArrayOutputStream;
|
---|
| 12 | import java.io.File;
|
---|
| 13 | import java.io.IOException;
|
---|
[2606] | 14 | import java.util.ArrayList;
|
---|
[9277] | 15 | import java.util.Collection;
|
---|
[2593] | 16 |
|
---|
[8762] | 17 | import javax.imageio.ImageIO;
|
---|
| 18 |
|
---|
| 19 | import org.apache.commons.jcs.access.behavior.ICacheAccess;
|
---|
[2986] | 20 | import org.openstreetmap.josm.Main;
|
---|
[8762] | 21 | import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
|
---|
| 22 | import org.openstreetmap.josm.data.cache.JCSCacheManager;
|
---|
[7956] | 23 | import org.openstreetmap.josm.tools.ExifReader;
|
---|
[2593] | 24 |
|
---|
| 25 | public class ThumbsLoader implements Runnable {
|
---|
[2986] | 26 | public static final int maxSize = 120;
|
---|
| 27 | public static final int minSize = 22;
|
---|
[8840] | 28 | public volatile boolean stop;
|
---|
[9277] | 29 | private final Collection<ImageEntry> data;
|
---|
[9078] | 30 | private final GeoImageLayer layer;
|
---|
[8285] | 31 | private MediaTracker tracker;
|
---|
[8836] | 32 | private ICacheAccess<String, BufferedImageCacheEntry> cache;
|
---|
[9078] | 33 | private final boolean cacheOff = Main.pref.getBoolean("geoimage.noThumbnailCache", false);
|
---|
[2617] | 34 |
|
---|
[9277] | 35 | private ThumbsLoader(Collection<ImageEntry> data, GeoImageLayer layer) {
|
---|
| 36 | this.data = data;
|
---|
| 37 | this.layer = layer;
|
---|
| 38 | initCache();
|
---|
| 39 | }
|
---|
| 40 |
|
---|
[9270] | 41 | /**
|
---|
| 42 | * Constructs a new thumbnail loader that operates on a geoimage layer.
|
---|
| 43 | * @param layer geoimage layer
|
---|
| 44 | */
|
---|
[2986] | 45 | public ThumbsLoader(GeoImageLayer layer) {
|
---|
[9277] | 46 | this(new ArrayList<>(layer.data), layer);
|
---|
[9270] | 47 | }
|
---|
| 48 |
|
---|
| 49 | /**
|
---|
[9277] | 50 | * Constructs a new thumbnail loader that operates on the image entries
|
---|
| 51 | * @param entries image entries
|
---|
| 52 | */
|
---|
| 53 | public ThumbsLoader(Collection<ImageEntry> entries) {
|
---|
| 54 | this(entries, null);
|
---|
| 55 | }
|
---|
| 56 |
|
---|
| 57 | /**
|
---|
[9270] | 58 | * Initialize the thumbnail cache.
|
---|
| 59 | */
|
---|
| 60 | private void initCache() {
|
---|
[2986] | 61 | if (!cacheOff) {
|
---|
[8762] | 62 | try {
|
---|
[8764] | 63 | cache = JCSCacheManager.getCache("geoimage-thumbnails", 0, 120,
|
---|
| 64 | Main.pref.getCacheDirectory().getPath() + File.separator + "geoimage-thumbnails");
|
---|
[8762] | 65 | } catch (IOException e) {
|
---|
| 66 | Main.warn("Failed to initialize cache for geoimage-thumbnails");
|
---|
| 67 | Main.warn(e);
|
---|
| 68 | }
|
---|
[2593] | 69 | }
|
---|
[2986] | 70 | }
|
---|
[2593] | 71 |
|
---|
[6084] | 72 | @Override
|
---|
[2986] | 73 | public void run() {
|
---|
[6248] | 74 | Main.debug("Load Thumbnails");
|
---|
[2986] | 75 | tracker = new MediaTracker(Main.map.mapView);
|
---|
[8905] | 76 | for (ImageEntry entry : data) {
|
---|
[2986] | 77 | if (stop) return;
|
---|
[2617] | 78 |
|
---|
[7784] | 79 | // Do not load thumbnails that were loaded before.
|
---|
[9270] | 80 | if (!entry.hasThumbnail()) {
|
---|
| 81 | entry.setThumbnail(loadThumb(entry));
|
---|
[2617] | 82 |
|
---|
[9277] | 83 | if (layer != null && Main.isDisplayingMapView()) {
|
---|
[12340] | 84 | layer.updateBufferAndRepaint();
|
---|
[7784] | 85 | }
|
---|
[2986] | 86 | }
|
---|
| 87 | }
|
---|
[9277] | 88 | if (layer != null) {
|
---|
| 89 | layer.thumbsLoaded();
|
---|
[12340] | 90 | layer.updateBufferAndRepaint();
|
---|
[9277] | 91 | }
|
---|
[2986] | 92 | }
|
---|
[2617] | 93 |
|
---|
[2986] | 94 | private BufferedImage loadThumb(ImageEntry entry) {
|
---|
[10300] | 95 | final String cacheIdent = entry.getFile().toString()+':'+maxSize;
|
---|
[2617] | 96 |
|
---|
[8762] | 97 | if (!cacheOff && cache != null) {
|
---|
| 98 | try {
|
---|
| 99 | BufferedImageCacheEntry cacheEntry = cache.get(cacheIdent);
|
---|
| 100 | if (cacheEntry != null && cacheEntry.getImage() != null) {
|
---|
| 101 | Main.debug(" from cache");
|
---|
| 102 | return cacheEntry.getImage();
|
---|
| 103 | }
|
---|
| 104 | } catch (IOException e) {
|
---|
| 105 | Main.warn(e);
|
---|
[2606] | 106 | }
|
---|
[2986] | 107 | }
|
---|
[2617] | 108 |
|
---|
[2986] | 109 | Image img = Toolkit.getDefaultToolkit().createImage(entry.getFile().getPath());
|
---|
| 110 | tracker.addImage(img, 0);
|
---|
| 111 | try {
|
---|
| 112 | tracker.waitForID(0);
|
---|
| 113 | } catch (InterruptedException e) {
|
---|
[6313] | 114 | Main.error(" InterruptedException while loading thumb");
|
---|
[11535] | 115 | Thread.currentThread().interrupt();
|
---|
[2986] | 116 | return null;
|
---|
| 117 | }
|
---|
| 118 | if (tracker.isErrorID(1) || img.getWidth(null) <= 0 || img.getHeight(null) <= 0) {
|
---|
[6248] | 119 | Main.error(" Invalid image");
|
---|
[2986] | 120 | return null;
|
---|
| 121 | }
|
---|
[7956] | 122 |
|
---|
| 123 | final int w = img.getWidth(null);
|
---|
| 124 | final int h = img.getHeight(null);
|
---|
| 125 | final int hh, ww;
|
---|
[8765] | 126 | final Integer exifOrientation = entry.getExifOrientation();
|
---|
| 127 | if (exifOrientation != null && ExifReader.orientationSwitchesDimensions(exifOrientation)) {
|
---|
[7956] | 128 | ww = h;
|
---|
| 129 | hh = w;
|
---|
| 130 | } else {
|
---|
| 131 | ww = w;
|
---|
| 132 | hh = h;
|
---|
| 133 | }
|
---|
| 134 |
|
---|
[2986] | 135 | Rectangle targetSize = ImageDisplay.calculateDrawImageRectangle(
|
---|
[7956] | 136 | new Rectangle(0, 0, ww, hh),
|
---|
[2617] | 137 | new Rectangle(0, 0, maxSize, maxSize));
|
---|
[2986] | 138 | BufferedImage scaledBI = new BufferedImage(targetSize.width, targetSize.height, BufferedImage.TYPE_INT_RGB);
|
---|
| 139 | Graphics2D g = scaledBI.createGraphics();
|
---|
[7956] | 140 |
|
---|
| 141 | final AffineTransform scale = AffineTransform.getScaleInstance((double) targetSize.width / ww, (double) targetSize.height / hh);
|
---|
[8765] | 142 | if (exifOrientation != null) {
|
---|
| 143 | final AffineTransform restoreOrientation = ExifReader.getRestoreOrientationTransform(exifOrientation, w, h);
|
---|
| 144 | scale.concatenate(restoreOrientation);
|
---|
| 145 | }
|
---|
[7956] | 146 |
|
---|
| 147 | while (!g.drawImage(img, scale, null)) {
|
---|
[2986] | 148 | try {
|
---|
| 149 | Thread.sleep(10);
|
---|
[11620] | 150 | } catch (InterruptedException e) {
|
---|
[6313] | 151 | Main.warn("InterruptedException while drawing thumb");
|
---|
[11535] | 152 | Thread.currentThread().interrupt();
|
---|
[6310] | 153 | }
|
---|
[2986] | 154 | }
|
---|
| 155 | g.dispose();
|
---|
| 156 | tracker.removeImage(img);
|
---|
[2617] | 157 |
|
---|
[2986] | 158 | if (scaledBI.getWidth() <= 0 || scaledBI.getHeight() <= 0) {
|
---|
[6248] | 159 | Main.error(" Invalid image");
|
---|
[2986] | 160 | return null;
|
---|
| 161 | }
|
---|
[2617] | 162 |
|
---|
[8762] | 163 | if (!cacheOff && cache != null) {
|
---|
| 164 | try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
---|
[8764] | 165 | ImageIO.write(scaledBI, "png", output);
|
---|
[8762] | 166 | cache.put(cacheIdent, new BufferedImageCacheEntry(output.toByteArray()));
|
---|
| 167 | } catch (IOException e) {
|
---|
| 168 | Main.warn("Failed to save geoimage thumb to cache");
|
---|
| 169 | Main.warn(e);
|
---|
| 170 | }
|
---|
[2606] | 171 | }
|
---|
[2593] | 172 |
|
---|
[2986] | 173 | return scaledBI;
|
---|
[2593] | 174 | }
|
---|
[2986] | 175 | }
|
---|