Changeset 17364 in josm for trunk


Ignore:
Timestamp:
2020-11-26T22:24:27+01:00 (3 years ago)
Author:
simon04
Message:

see #20141 - ImageProvider: cache rendered SVG images using JCS

This experimental feature is disabled by default. Set the advanced preference jcs.cache.use_image_resource_cache=true to enable. No cache eviction for altered images is implemented at the moment.

Location:
trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/com/kitfox/svg/SVGUniverse.java

    r14328 r17364  
    693693        this.imageDataInlineOnly = imageDataInlineOnly;
    694694    }
     695
     696    public String statistics() {
     697        return String.format("%s has loaded %d SVG images", this, loadedDocs.size());
     698    }
     699
    695700}
  • trunk/src/org/openstreetmap/josm/data/cache/JCSCacheManager.java

    r16401 r17364  
    3131import org.openstreetmap.josm.data.preferences.IntegerProperty;
    3232import org.openstreetmap.josm.spi.preferences.Config;
     33import org.openstreetmap.josm.tools.ImageResource;
    3334import org.openstreetmap.josm.tools.Logging;
    3435import org.openstreetmap.josm.tools.Utils;
     
    4546    private static final String PREFERENCE_PREFIX = "jcs.cache";
    4647    public static final BooleanProperty USE_BLOCK_CACHE = new BooleanProperty(PREFERENCE_PREFIX + ".use_block_cache", true);
     48
     49    /**
     50     * The preference key {@code jcs.cache.use_image_resource_cache} controls the caching mechanism used for {@link ImageResource}.
     51     * If set to {@code true}, a combined memory/disk is used. Otherwise, an in-memory-cache is used.
     52     */
     53    public static final BooleanProperty USE_IMAGE_RESOURCE_CACHE = new BooleanProperty(PREFERENCE_PREFIX + ".use_image_resource_cache", false);
    4754
    4855    private static final AuxiliaryCacheFactory DISK_CACHE_FACTORY =
     
    171178     * @return cache access object
    172179     */
     180    public static <K, V> CacheAccess<K, V> getCache(String cacheName, int maxMemoryObjects, int maxDiskObjects, String cachePath) {
     181        return getCache(cacheName, maxMemoryObjects, maxDiskObjects, cachePath, USE_BLOCK_CACHE.get() ? 4096 : 0);
     182    }
     183
    173184    @SuppressWarnings("unchecked")
    174     public static <K, V> CacheAccess<K, V> getCache(String cacheName, int maxMemoryObjects, int maxDiskObjects, String cachePath) {
     185    private static <K, V> CacheAccess<K, V> getCache(String cacheName, int maxMemoryObjects, int maxDiskObjects,
     186                                                     String cachePath, int blockSizeBytes) {
    175187        CacheAccess<K, V> cacheAccess = JCS.getInstance(cacheName, getCacheAttributes(maxMemoryObjects));
    176188        CompositeCache<K, V> cc = cacheAccess.getCacheControl();
    177189
    178190        if (cachePath != null && cacheDirLock != null) {
    179             IDiskCacheAttributes diskAttributes = getDiskCacheAttributes(maxDiskObjects, cachePath, cacheName);
     191            IDiskCacheAttributes diskAttributes = getDiskCacheAttributes(maxDiskObjects, cachePath, cacheName, blockSizeBytes);
     192            Logging.debug("Setting up cache: {0}", diskAttributes);
    180193            try {
    181194                if (cc.getAuxCaches().length == 0) {
     
    193206
    194207    /**
     208     * Returns a cache for {@link ImageResource}
     209     * @param <K> key type
     210     * @param <V> value type
     211     * @return cache access object
     212     */
     213    public static <K, V> CacheAccess<K, V> getImageResourceCache() {
     214        if (!USE_IMAGE_RESOURCE_CACHE.get()) {
     215            return getCache("images", 16 * 1024, 0, null);
     216        }
     217        String cachePath = new File(Config.getDirs().getCacheDirectory(true), "images").getAbsolutePath();
     218        Logging.warn("Using experimental disk cache {0} for ImageResource", cachePath);
     219        return getCache("images", 16 * 1024, 512 * 1024, cachePath, 1024);
     220    }
     221
     222    /**
    195223     * Close all files to ensure, that all indexes and data are properly written
    196224     */
     
    199227    }
    200228
    201     private static IDiskCacheAttributes getDiskCacheAttributes(int maxDiskObjects, String cachePath, String cacheName) {
     229    private static IDiskCacheAttributes getDiskCacheAttributes(int maxDiskObjects, String cachePath, String cacheName, int blockSizeBytes) {
    202230        IDiskCacheAttributes ret;
    203         removeStaleFiles(cachePath + File.separator + cacheName, USE_BLOCK_CACHE.get() ? "_INDEX_v2" : "_BLOCK_v2");
    204         String newCacheName = cacheName + (USE_BLOCK_CACHE.get() ? "_BLOCK_v2" : "_INDEX_v2");
    205 
    206         if (USE_BLOCK_CACHE.get()) {
     231        boolean isBlockDiskCache = blockSizeBytes > 0;
     232        removeStaleFiles(cachePath + File.separator + cacheName, isBlockDiskCache ? "_INDEX_v2" : "_BLOCK_v2");
     233        String newCacheName = cacheName + (isBlockDiskCache ? "_BLOCK_v2" : "_INDEX_v2");
     234
     235        if (isBlockDiskCache) {
    207236            BlockDiskCacheAttributes blockAttr = new BlockDiskCacheAttributes();
    208237            /*
     
    218247                blockAttr.setMaxKeySize(maxDiskObjects);
    219248            }
    220             blockAttr.setBlockSizeBytes(4096); // use 4k blocks
     249            blockAttr.setBlockSizeBytes(blockSizeBytes);
    221250            ret = blockAttr;
    222251        } else {
  • trunk/src/org/openstreetmap/josm/gui/MainInitialization.java

    r17188 r17364  
    153153                MainApplication.toolbar.control.updateUI();
    154154                MainApplication.contentPanePrivate.updateUI();
     155                // image provider statistics
     156                ImageProvider.printStatistics();
    155157            }))
    156158        );
  • trunk/src/org/openstreetmap/josm/tools/ImageProvider.java

    r17144 r17364  
    954954                        continue;
    955955                    }
    956                     ir = getIfAvailableLocalURL(path, type);
     956                    ir = getIfAvailableLocalURL(subdir + name, path, type);
    957957                    if (ir != null) {
    958958                        cache.put(cacheName, ir);
     
    984984                    svg = getSvgUniverse().getDiagram(uri);
    985985                }
    986                 return svg == null ? null : new ImageResource(svg);
     986                return svg == null ? null : new ImageResource(url, svg);
    987987            case OTHER:
    988988                BufferedImage img = null;
     
    992992                    Logging.log(Logging.LEVEL_WARN, "Exception while reading HTTP image:", e);
    993993                }
    994                 return img == null ? null : new ImageResource(img);
     994                return img == null ? null : new ImageResource(url, img);
    995995            default:
    996996                throw new AssertionError("Unsupported type: " + type);
     
    10411041                    return null;
    10421042                }
    1043                 return new ImageResource(svg);
     1043                return new ImageResource(url, svg);
    10441044            } else {
    10451045                try {
     
    10501050                    // CHECKSTYLE.ON: LineLength
    10511051                    Image img = read(new ByteArrayInputStream(bytes), false, true);
    1052                     return img == null ? null : new ImageResource(img);
     1052                    return img == null ? null : new ImageResource(url, img);
    10531053                } catch (IOException | UnsatisfiedLinkError e) {
    10541054                    Logging.log(Logging.LEVEL_WARN, "Exception while reading image:", e);
     
    11251125                            svg = getSvgUniverse().getDiagram(uri);
    11261126                        }
    1127                         return svg == null ? null : new ImageResource(svg);
     1127                        return svg == null ? null : new ImageResource(fullName, svg);
    11281128                    case OTHER:
    11291129                        while (size > 0) {
     
    11381138                            Logging.warn(e);
    11391139                        }
    1140                         return img == null ? null : new ImageResource(img);
     1140                        return img == null ? null : new ImageResource(fullName, img);
    11411141                    default:
    11421142                        throw new AssertionError("Unknown ImageType: "+type);
     
    11571157     * @return the requested image or null if the request failed
    11581158     */
    1159     private static ImageResource getIfAvailableLocalURL(URL path, ImageType type) {
     1159    private static ImageResource getIfAvailableLocalURL(String cacheKey, URL path, ImageType type) {
    11601160        switch (type) {
    11611161        case SVG:
    1162             SVGDiagram svg = null;
    1163             synchronized (getSvgUniverse()) {
    1164                 try {
    1165                     URI uri = null;
     1162            return new ImageResource(cacheKey, () -> {
     1163                synchronized (getSvgUniverse()) {
    11661164                    try {
    1167                         uri = getSvgUniverse().loadSVG(path);
    1168                     } catch (InvalidPathException e) {
    1169                         Logging.error("Cannot open {0}: {1}", path, e.getMessage());
    1170                         Logging.trace(e);
     1165                        URI uri = null;
     1166                        try {
     1167                            uri = getSvgUniverse().loadSVG(path);
     1168                        } catch (InvalidPathException e) {
     1169                            Logging.error("Cannot open {0}: {1}", path, e.getMessage());
     1170                            Logging.trace(e);
     1171                        }
     1172                        if (uri == null && "jar".equals(path.getProtocol())) {
     1173                            URL betterPath = Utils.betterJarUrl(path);
     1174                            if (betterPath != null) {
     1175                                uri = getSvgUniverse().loadSVG(betterPath);
     1176                            }
     1177                        }
     1178                        return getSvgUniverse().getDiagram(uri);
     1179                    } catch (SecurityException | IOException e) {
     1180                        Logging.log(Logging.LEVEL_WARN, "Unable to read SVG", e);
    11711181                    }
    1172                     if (uri == null && "jar".equals(path.getProtocol())) {
    1173                         URL betterPath = Utils.betterJarUrl(path);
    1174                         if (betterPath != null) {
    1175                             uri = getSvgUniverse().loadSVG(betterPath);
    1176                         }
    1177                     }
    1178                     svg = getSvgUniverse().getDiagram(uri);
    1179                 } catch (SecurityException | IOException e) {
    1180                     Logging.log(Logging.LEVEL_WARN, "Unable to read SVG", e);
    1181                 }
    1182             }
    1183             return svg == null ? null : new ImageResource(svg);
     1182                }
     1183                return null;
     1184            });
    11841185        case OTHER:
    11851186            BufferedImage img = null;
     
    11961197                Logging.debug(e);
    11971198            }
    1198             return img == null ? null : new ImageResource(img);
     1199            return img == null ? null : new ImageResource(path.toString(), img);
    11991200        default:
    12001201            throw new AssertionError();
     
    13941395     */
    13951396    public static Image createBoundedImage(Image img, int maxSize) {
    1396         return new ImageResource(img).getImageIconBounded(new Dimension(maxSize, maxSize)).getImage();
     1397        return new ImageResource(img.toString(), img).getImageIconBounded(new Dimension(maxSize, maxSize)).getImage();
    13971398    }
    13981399
     
    19331934    }
    19341935
     1936    /**
     1937     * Prints statistics concerning the image loading caches.
     1938     */
     1939    public static void printStatistics() {
     1940        Logging.info(getSvgUniverse().statistics());
     1941        Logging.info(ImageResource.statistics());
     1942    }
     1943
    19351944    @Override
    19361945    public String toString() {
  • trunk/src/org/openstreetmap/josm/tools/ImageResource.java

    r17144 r17364  
    55import java.awt.Image;
    66import java.awt.image.BufferedImage;
     7import java.io.IOException;
     8import java.io.UncheckedIOException;
    79import java.util.List;
    8 import java.util.Map;
    9 import java.util.concurrent.ConcurrentHashMap;
     10import java.util.Locale;
     11import java.util.Objects;
     12import java.util.function.Supplier;
    1013
    1114import javax.swing.AbstractAction;
     
    1518import javax.swing.JPanel;
    1619import javax.swing.UIManager;
     20
     21import org.apache.commons.jcs3.access.behavior.ICacheAccess;
     22import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
     23import org.openstreetmap.josm.data.cache.JCSCacheManager;
    1724
    1825import com.kitfox.svg.SVGDiagram;
     
    2936
    3037    /**
    31      * Caches the image data for resized versions of the same image. The key is obtained using {@link ImageResizeMode#cacheKey(Dimension)}.
    32      */
    33     private final Map<Integer, BufferedImage> imgCache = new ConcurrentHashMap<>(4);
     38     * Caches the image data for resized versions of the same image.
     39     * Depending on {@link JCSCacheManager#USE_IMAGE_RESOURCE_CACHE}, a combined memory/disk, or an in-memory-cache is used.
     40     */
     41    private static final ICacheAccess<String, BufferedImageCacheEntry> imgCache = JCSCacheManager.getImageResourceCache();
     42
     43    private final String cacheKey;
    3444    /**
    3545     * SVG diagram information in case of SVG vector image.
     
    3747    private SVGDiagram svg;
    3848    /**
     49     * Supplier for SVG diagram information in case of possibly cached SVG vector image.
     50     */
     51    private Supplier<SVGDiagram> svgSupplier;
     52    /**
    3953     * Use this dimension to request original file dimension.
    4054     */
     
    5569    /**
    5670     * Constructs a new {@code ImageResource} from an image.
     71     * @param cacheKey the caching identifier of the image
    5772     * @param img the image
    5873     */
    59     public ImageResource(Image img) {
    60         CheckParameterUtil.ensureParameterNotNull(img);
    61         baseImage = img;
     74    public ImageResource(String cacheKey, Image img) {
     75        this.cacheKey = Objects.requireNonNull(cacheKey);
     76        this.baseImage = Objects.requireNonNull(img);
    6277    }
    6378
    6479    /**
    6580     * Constructs a new {@code ImageResource} from SVG data.
     81     * @param cacheKey the caching identifier of the image
    6682     * @param svg SVG data
    6783     */
    68     public ImageResource(SVGDiagram svg) {
    69         CheckParameterUtil.ensureParameterNotNull(svg);
    70         this.svg = svg;
     84    public ImageResource(String cacheKey, SVGDiagram svg) {
     85        this.cacheKey = Objects.requireNonNull(cacheKey);
     86        this.svg = Objects.requireNonNull(svg);
     87    }
     88
     89    public ImageResource(String cacheKey, Supplier<SVGDiagram> svgSupplier) {
     90        this.cacheKey = Objects.requireNonNull(cacheKey);
     91        this.svgSupplier = Objects.requireNonNull(svgSupplier);
    7192    }
    7293
     
    7899     */
    79100    public ImageResource(ImageResource res, List<ImageOverlay> overlayInfo) {
     101        this.cacheKey = res.cacheKey;
    80102        this.svg = res.svg;
     103        this.svgSupplier = res.svgSupplier;
    81104        this.baseImage = res.baseImage;
    82105        this.overlayInfo = overlayInfo;
     
    157180    ImageIcon getImageIcon(Dimension dim, boolean multiResolution, ImageResizeMode resizeMode) {
    158181        return getImageIconAlreadyScaled(GuiSizesHelper.getDimensionDpiAdjusted(dim), multiResolution, false, resizeMode);
     182    }
     183
     184    private BufferedImage getImageFromCache(String cacheKey) {
     185        try {
     186            BufferedImageCacheEntry image = imgCache.get(cacheKey);
     187            if (image == null || image.getImage() == null) {
     188                return null;
     189            }
     190            Logging.trace("{0} is in cache :-)", cacheKey);
     191            return image.getImage();
     192        } catch (IOException e) {
     193            throw new UncheckedIOException(e);
     194        }
    159195    }
    160196
     
    181217            resizeMode = ImageResizeMode.BOUNDED;
    182218        }
    183         final int cacheKey = resizeMode.cacheKey(dim);
    184         BufferedImage img = imgCache.get(cacheKey);
     219        final String cacheKey = String.format(Locale.ROOT, "%s--%s--%d--%d",
     220                this.cacheKey, resizeMode.name(), dim.width, dim.height);
     221        BufferedImage img = getImageFromCache(cacheKey);
    185222        if (img == null) {
     223            if (svgSupplier != null) {
     224                svg = svgSupplier.get();
     225                Logging.trace("{0} is not in cache :-(", cacheKey);
     226                svgSupplier = null;
     227            }
    186228            if (svg != null) {
    187229                img = ImageProvider.createImageFromSvg(svg, dim, resizeMode);
     
    211253                disabledIcon.paintIcon(new JPanel(), img.getGraphics(), 0, 0);
    212254            }
    213             imgCache.put(cacheKey, img);
     255            if (img == null) {
     256                return null;
     257            }
     258            BufferedImageCacheEntry cacheEntry = BufferedImageCacheEntry.pngEncoded(img);
     259            Logging.trace("Storing {0} ({1} bytes) in cache...", cacheKey, cacheEntry.getContent().length);
     260            imgCache.put(cacheKey, cacheEntry);
    214261        }
    215262
     
    251298    }
    252299
     300    static String statistics() {
     301        return String.format("ImageResource cache: [%s]", imgCache.getStatistics());
     302    }
     303
    253304    @Override
    254305    public String toString() {
  • trunk/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java

    r17275 r17364  
    9090     */
    9191    @Test
    92     void testReadDefaulPresets() throws SAXException, IOException {
     92    void testReadDefaultPresets() 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}
Note: See TracChangeset for help on using the changeset viewer.