Ticket #20141: 20141.patch

File 20141.patch, 15.9 KB (added by simon04, 3 months ago)
  • src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java

    commit 641cea8f4f7e5fc8a65a212a1d0ad30c8f8b38d7
    Author: Simon Legner <Simon.Legner@gmail.com>
    Date:   2020-11-23 23:26:51 +0100
    
        see #20141 - ImageProvider: cache rendered SVG images using JCS
    
    diff --git a/src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java b/src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java
    index 3b908d5a9..2e637a12d 100644
    a b  
    33
    44import java.awt.image.BufferedImage;
    55import java.io.ByteArrayInputStream;
     6import java.io.ByteArrayOutputStream;
    67import java.io.IOException;
     8import java.io.UncheckedIOException;
    79
    810import javax.imageio.ImageIO;
    911
    public BufferedImageCacheEntry(byte[] content) { 
    2931        super(content);
    3032    }
    3133
     34    /**
     35     * Encodes the given image as PNG and returns a cache entry
     36     * @param img the image
     37     * @return a cache entry for the PNG encoded image
     38     * @throws UncheckedIOException if an I/O error occurs
     39     */
     40    public static BufferedImageCacheEntry pngEncoded(BufferedImage img) {
     41        try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
     42            ImageIO.write(img, "png", output);
     43            return new BufferedImageCacheEntry(output.toByteArray());
     44        } catch (IOException e) {
     45            throw new UncheckedIOException(e);
     46        }
     47    }
     48
    3249    /**
    3350     * Returns BufferedImage from for the content. Subsequent calls will return the same instance,
    3451     * to reduce overhead of ImageIO
  • src/org/openstreetmap/josm/data/cache/JCSCacheManager.java

    diff --git a/src/org/openstreetmap/josm/data/cache/JCSCacheManager.java b/src/org/openstreetmap/josm/data/cache/JCSCacheManager.java
    index a32238ad7..d3be3a6e3 100644
    a b private JCSCacheManager() { 
    177177
    178178        if (cachePath != null && cacheDirLock != null) {
    179179            IDiskCacheAttributes diskAttributes = getDiskCacheAttributes(maxDiskObjects, cachePath, cacheName);
     180            Logging.debug("Setting up cache: {0}", diskAttributes);
    180181            try {
    181182                if (cc.getAuxCaches().length == 0) {
    182183                    cc.setAuxCaches(new AuxiliaryCache[]{DISK_CACHE_FACTORY.createCache(
  • src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java
    index bd24e6bf8..9579ed839 100644
    a b  
    88import java.awt.Toolkit;
    99import java.awt.geom.AffineTransform;
    1010import java.awt.image.BufferedImage;
    11 import java.io.ByteArrayOutputStream;
    1211import java.io.File;
    1312import java.io.IOException;
     13import java.io.UncheckedIOException;
    1414import java.util.ArrayList;
    1515import java.util.Collection;
    1616
    17 import javax.imageio.ImageIO;
    18 
    1917import org.apache.commons.jcs3.access.behavior.ICacheAccess;
    2018import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
    2119import org.openstreetmap.josm.data.cache.JCSCacheManager;
    private BufferedImage loadThumb(ImageEntry entry) { 
    164162        }
    165163
    166164        if (!cacheOff && cache != null) {
    167             try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
    168                 ImageIO.write(scaledBI, "png", output);
    169                 cache.put(cacheIdent, new BufferedImageCacheEntry(output.toByteArray()));
    170             } catch (IOException e) {
     165            try {
     166                cache.put(cacheIdent, BufferedImageCacheEntry.pngEncoded(scaledBI));
     167            } catch (UncheckedIOException e) {
    171168                Logging.warn("Failed to save geoimage thumb to cache");
    172169                Logging.warn(e);
    173170            }
  • src/org/openstreetmap/josm/tools/ImageProvider.java

    diff --git a/src/org/openstreetmap/josm/tools/ImageProvider.java b/src/org/openstreetmap/josm/tools/ImageProvider.java
    index 67d424bcd..75a429f87 100644
    a b  
    6363import javax.swing.ImageIcon;
    6464import javax.xml.parsers.ParserConfigurationException;
    6565
     66import org.apache.commons.jcs3.access.behavior.ICacheAccess;
    6667import org.openstreetmap.josm.data.Preferences;
     68import org.openstreetmap.josm.data.cache.JCSCacheManager;
    6769import org.openstreetmap.josm.data.osm.OsmPrimitive;
    6870import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    6971import org.openstreetmap.josm.io.CachedFile;
    private static ImageResource getIfAvailableHttp(String url, ImageType type) { 
    983985                    URI uri = getSvgUniverse().loadSVG(is, Utils.fileToURL(cf.getFile()).toString());
    984986                    svg = getSvgUniverse().getDiagram(uri);
    985987                }
    986                 return svg == null ? null : new ImageResource(svg);
     988                return svg == null ? null : new ImageResource(url, svg);
    987989            case OTHER:
    988990                BufferedImage img = null;
    989991                try {
    private static ImageResource getIfAvailableHttp(String url, ImageType type) { 
    991993                } catch (IOException | UnsatisfiedLinkError e) {
    992994                    Logging.log(Logging.LEVEL_WARN, "Exception while reading HTTP image:", e);
    993995                }
    994                 return img == null ? null : new ImageResource(img);
     996                return img == null ? null : new ImageResource(url, img);
    995997            default:
    996998                throw new AssertionError("Unsupported type: " + type);
    997999            }
    private static ImageResource getIfAvailableDataUrl(String url) { 
    10401042                    Logging.warn("Unable to process svg: "+s);
    10411043                    return null;
    10421044                }
    1043                 return new ImageResource(svg);
     1045                return new ImageResource(url, svg);
    10441046            } else {
    10451047                try {
    10461048                    // See #10479: for PNG files, always enforce transparency to be sure tNRS chunk is used even not in paletted mode
    private static ImageResource getIfAvailableDataUrl(String url) { 
    10491051                    // hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/dc4322602480/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java#l656
    10501052                    // CHECKSTYLE.ON: LineLength
    10511053                    Image img = read(new ByteArrayInputStream(bytes), false, true);
    1052                     return img == null ? null : new ImageResource(img);
     1054                    return img == null ? null : new ImageResource(url, img);
    10531055                } catch (IOException | UnsatisfiedLinkError e) {
    10541056                    Logging.log(Logging.LEVEL_WARN, "Exception while reading image:", e);
    10551057                }
    private static ImageResource getIfAvailableZip(String fullName, File archive, St 
    11241126                            URI uri = getSvgUniverse().loadSVG(is, entryName);
    11251127                            svg = getSvgUniverse().getDiagram(uri);
    11261128                        }
    1127                         return svg == null ? null : new ImageResource(svg);
     1129                        return svg == null ? null : new ImageResource(fullName, svg);
    11281130                    case OTHER:
    11291131                        while (size > 0) {
    11301132                            int l = is.read(buf, offs, size);
    private static ImageResource getIfAvailableZip(String fullName, File archive, St 
    11371139                        } catch (IOException | UnsatisfiedLinkError e) {
    11381140                            Logging.warn(e);
    11391141                        }
    1140                         return img == null ? null : new ImageResource(img);
     1142                        return img == null ? null : new ImageResource(fullName, img);
    11411143                    default:
    11421144                        throw new AssertionError("Unknown ImageType: "+type);
    11431145                    }
    private static ImageResource getIfAvailableZip(String fullName, File archive, St 
    11591161    private static ImageResource getIfAvailableLocalURL(URL path, ImageType type) {
    11601162        switch (type) {
    11611163        case SVG:
    1162             SVGDiagram svg = null;
     1164            return new ImageResource(path.toString(), () -> {
    11631165                synchronized (getSvgUniverse()) {
    11641166                    try {
    11651167                        URI uri = null;
    private static ImageResource getIfAvailableLocalURL(URL path, ImageType type) { 
    11751177                                uri = getSvgUniverse().loadSVG(betterPath);
    11761178                            }
    11771179                        }
    1178                     svg = getSvgUniverse().getDiagram(uri);
     1180                        return getSvgUniverse().getDiagram(uri);
    11791181                    } catch (SecurityException | IOException e) {
    11801182                        Logging.log(Logging.LEVEL_WARN, "Unable to read SVG", e);
    11811183                    }
    11821184                }
    1183             return svg == null ? null : new ImageResource(svg);
     1185                return null;
     1186            });
    11841187        case OTHER:
    11851188            BufferedImage img = null;
    11861189            try {
    private static ImageResource getIfAvailableLocalURL(URL path, ImageType type) { 
    11951198                Logging.log(Logging.LEVEL_WARN, "Unable to read image", e);
    11961199                Logging.debug(e);
    11971200            }
    1198             return img == null ? null : new ImageResource(img);
     1201            return img == null ? null : new ImageResource(path.toString(), img);
    11991202        default:
    12001203            throw new AssertionError();
    12011204        }
    static Image getCursorImage(String name, String overlay, UnaryOperator<Dimension 
    13931396     * @since 6172
    13941397     */
    13951398    public static Image createBoundedImage(Image img, int maxSize) {
    1396         return new ImageResource(img).getImageIconBounded(new Dimension(maxSize, maxSize)).getImage();
     1399        return new ImageResource(img.toString(), img).getImageIconBounded(new Dimension(maxSize, maxSize)).getImage();
    13971400    }
    13981401
    13991402    /**
  • src/org/openstreetmap/josm/tools/ImageResource.java

    diff --git a/src/org/openstreetmap/josm/tools/ImageResource.java b/src/org/openstreetmap/josm/tools/ImageResource.java
    index f710b7f30..fdc18ae44 100644
    a b  
    44import java.awt.Dimension;
    55import java.awt.Image;
    66import java.awt.image.BufferedImage;
     7import java.io.File;
     8import java.io.IOException;
     9import java.io.UncheckedIOException;
    710import java.util.List;
    8 import java.util.Map;
    9 import java.util.concurrent.ConcurrentHashMap;
     11import java.util.Objects;
     12import java.util.function.Supplier;
    1013
    1114import javax.swing.AbstractAction;
    1215import javax.swing.Action;
     
    1518import javax.swing.JPanel;
    1619import javax.swing.UIManager;
    1720
     21import org.apache.commons.jcs3.access.behavior.ICacheAccess;
     22import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
     23import org.openstreetmap.josm.data.cache.JCSCacheManager;
     24import org.openstreetmap.josm.spi.preferences.Config;
     25
    1826import com.kitfox.svg.SVGDiagram;
    1927
    2028/**
     
    3038    /**
    3139     * Caches the image data for resized versions of the same image. The key is obtained using {@link ImageResizeMode#cacheKey(Dimension)}.
    3240     */
    33     private final Map<Integer, BufferedImage> imgCache = new ConcurrentHashMap<>(4);
     41    private static final ICacheAccess<String, BufferedImageCacheEntry> imgCache = JCSCacheManager.getCache(
     42            "images", 10, 10000,
     43            new File(Config.getDirs().getCacheDirectory(true), "images").getAbsolutePath());
     44    private final String cacheKey;
    3445    /**
    3546     * SVG diagram information in case of SVG vector image.
    3647     */
    3748    private SVGDiagram svg;
     49    private Supplier<SVGDiagram> svgSupplier;
    3850    /**
    3951     * Use this dimension to request original file dimension.
    4052     */
     
    5466
    5567    /**
    5668     * Constructs a new {@code ImageResource} from an image.
     69     * @param cacheKey the caching identifier of the image
    5770     * @param img the image
    5871     */
    59     public ImageResource(Image img) {
    60         CheckParameterUtil.ensureParameterNotNull(img);
    61         baseImage = img;
     72    public ImageResource(String cacheKey, Image img) {
     73        this.cacheKey = Objects.requireNonNull(cacheKey);
     74        this.baseImage = Objects.requireNonNull(img);
    6275    }
    6376
    6477    /**
    6578     * Constructs a new {@code ImageResource} from SVG data.
     79     * @param cacheKey the caching identifier of the image
    6680     * @param svg SVG data
    6781     */
    68     public ImageResource(SVGDiagram svg) {
    69         CheckParameterUtil.ensureParameterNotNull(svg);
    70         this.svg = svg;
     82    public ImageResource(String cacheKey, SVGDiagram svg) {
     83        this.cacheKey = Objects.requireNonNull(cacheKey);
     84        this.svg = Objects.requireNonNull(svg);
     85    }
     86
     87    public ImageResource(String cacheKey, Supplier<SVGDiagram> svgSupplier) {
     88        this.cacheKey = Objects.requireNonNull(cacheKey);
     89        this.svgSupplier = Objects.requireNonNull(svgSupplier);
    7190    }
    7291
    7392    /**
    public ImageResource(SVGDiagram svg) { 
    7796     * @since 8095
    7897     */
    7998    public ImageResource(ImageResource res, List<ImageOverlay> overlayInfo) {
     99        this.cacheKey = res.cacheKey;
    80100        this.svg = res.svg;
     101        this.svgSupplier = res.svgSupplier;
    81102        this.baseImage = res.baseImage;
    82103        this.overlayInfo = overlayInfo;
    83104    }
    ImageIcon getImageIcon(Dimension dim, boolean multiResolution, ImageResizeMode r 
    158179        return getImageIconAlreadyScaled(GuiSizesHelper.getDimensionDpiAdjusted(dim), multiResolution, false, resizeMode);
    159180    }
    160181
     182    private BufferedImage getImageFromCache(String cacheKey) {
     183        try {
     184            BufferedImageCacheEntry cacheEntry = imgCache.get(cacheKey);
     185            return cacheEntry == null ? null : cacheEntry.getImage();
     186        } catch (IOException e) {
     187            throw new UncheckedIOException(e);
     188        }
     189    }
     190
    161191    /**
    162192     * Get an ImageIcon object for the image of this resource. A potential UI scaling is assumed
    163193     * to be already taken care of, so dim is already scaled accordingly.
    ImageIcon getImageIconAlreadyScaled(Dimension dim, boolean multiResolution, bool 
    180210        } else if (resizeMode == null) {
    181211            resizeMode = ImageResizeMode.BOUNDED;
    182212        }
    183         final int cacheKey = resizeMode.cacheKey(dim);
    184         BufferedImage img = imgCache.get(cacheKey);
     213        final String cacheKey = this.cacheKey + "--" + Integer.toHexString(resizeMode.cacheKey(dim));
     214        BufferedImage img = getImageFromCache(cacheKey);
    185215        if (img == null) {
     216            if (svgSupplier != null) {
     217                svg = svgSupplier.get();
     218                svgSupplier = null;
     219            }
    186220            if (svg != null) {
    187221                img = ImageProvider.createImageFromSvg(svg, dim, resizeMode);
    188222                if (img == null) {
    ImageIcon getImageIconAlreadyScaled(Dimension dim, boolean multiResolution, bool 
    210244                img = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
    211245                disabledIcon.paintIcon(new JPanel(), img.getGraphics(), 0, 0);
    212246            }
    213             imgCache.put(cacheKey, img);
     247            if (img == null) {
     248                return null;
     249            }
     250            imgCache.put(cacheKey, BufferedImageCacheEntry.pngEncoded(img));
    214251        }
    215252
    216253        if (!multiResolution)
  • test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java

    diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java
    index c4cf52276..bec7fc418 100644
    a b void testReadDefaulPresets() throws SAXException, IOException { 
    9393        String presetfile = "resource://data/defaultpresets.xml";
    9494        final Collection<TaggingPreset> presets = TaggingPresetReader.readAll(presetfile, true);
    9595        Assert.assertTrue("Default presets are empty", presets.size() > 0);
     96        TaggingPresetsTest.waitForIconLoading(presets);
    9697    }
    9798}