001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.cache;
003
004import java.awt.image.BufferedImage;
005import java.io.ByteArrayInputStream;
006import java.io.ByteArrayOutputStream;
007import java.io.IOException;
008import java.io.UncheckedIOException;
009
010import javax.imageio.ImageIO;
011
012/**
013 * Cache Entry that has methods to get the BufferedImage, that will be cached along in memory
014 * but will be not serialized when saved to the disk (to avoid duplication of data)
015 *
016 * @author Wiktor Niesiobędzki
017 */
018public class BufferedImageCacheEntry extends CacheEntry {
019    private static final long serialVersionUID = 1L; //version
020    // transient to avoid serialization, volatile to avoid synchronization of whole getImage() method
021    private transient volatile BufferedImage img;
022    // we need to have separate control variable, to know, if we already tried to load the image, as img might be null
023    // after we loaded image, as for example, when image file is malformed (eg. HTML file)
024    private transient volatile boolean imageLoaded;
025
026    /**
027     *
028     * @param content byte array containing image
029     */
030    public BufferedImageCacheEntry(byte[] content) {
031        super(content);
032    }
033
034    /**
035     * Encodes the given image as PNG and returns a cache entry
036     * @param img the image
037     * @return a cache entry for the PNG encoded image
038     * @throws UncheckedIOException if an I/O error occurs
039     */
040    public static BufferedImageCacheEntry pngEncoded(BufferedImage img) {
041        try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
042            ImageIO.write(img, "png", output);
043            return new BufferedImageCacheEntry(output.toByteArray());
044        } catch (IOException e) {
045            throw new UncheckedIOException(e);
046        }
047    }
048
049    /**
050     * Returns BufferedImage from for the content. Subsequent calls will return the same instance,
051     * to reduce overhead of ImageIO
052     *
053     * @return BufferedImage of cache entry content
054     * @throws IOException if an error occurs during reading.
055     */
056    public BufferedImage getImage() throws IOException {
057        if (imageLoaded)
058            return img;
059        synchronized (this) {
060            if (imageLoaded)
061                return img;
062            byte[] content = getContent();
063            if (content.length > 0) {
064                img = ImageIO.read(new ByteArrayInputStream(content));
065                imageLoaded = true;
066            }
067        }
068        return img;
069    }
070
071    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
072        /*
073         * This method below will be needed, if Apache Commons JCS (or any other caching system), will update
074         * disk representation of object from memory, once it is put into the cache (for example - at closing the cache)
075         *
076         * For now it is not the case, as we use DiskUsagePattern.UPDATE, which on JCS shutdown doesn't write again memory
077         * contents to file, so the fact, that we've cleared never gets saved to the disk
078         *
079         * This method is commented out, as it will convert all cache entries to PNG files regardless of what was returned.
080         * It might cause recompression/change of format which may result in decreased quality of imagery
081         */
082        /* synchronized (this) {
083            if (content == null && img != null) {
084                ByteArrayOutputStream restoredData = new ByteArrayOutputStream();
085                ImageIO.write(img, "png", restoredData);
086                content = restoredData.toByteArray();
087            }
088            out.writeObject(this);
089        }
090         */
091        synchronized (this) {
092            if (content == null && img != null) {
093                throw new AssertionError("Trying to serialize (save to disk?) an BufferedImageCacheEntry " +
094                        "that was converted to BufferedImage and no raw data is present anymore");
095            }
096            out.writeObject(this);
097
098            if (img != null) {
099                content = null;
100            }
101        }
102    }
103}