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}