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
|
|
| 3 | 3 | |
| 4 | 4 | import java.awt.image.BufferedImage; |
| 5 | 5 | import java.io.ByteArrayInputStream; |
| | 6 | import java.io.ByteArrayOutputStream; |
| 6 | 7 | import java.io.IOException; |
| | 8 | import java.io.UncheckedIOException; |
| 7 | 9 | |
| 8 | 10 | import javax.imageio.ImageIO; |
| 9 | 11 | |
| … |
… |
public BufferedImageCacheEntry(byte[] content) {
|
| 29 | 31 | super(content); |
| 30 | 32 | } |
| 31 | 33 | |
| | 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 | |
| 32 | 49 | /** |
| 33 | 50 | * Returns BufferedImage from for the content. Subsequent calls will return the same instance, |
| 34 | 51 | * to reduce overhead of ImageIO |
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() {
|
| 177 | 177 | |
| 178 | 178 | if (cachePath != null && cacheDirLock != null) { |
| 179 | 179 | IDiskCacheAttributes diskAttributes = getDiskCacheAttributes(maxDiskObjects, cachePath, cacheName); |
| | 180 | Logging.debug("Setting up cache: {0}", diskAttributes); |
| 180 | 181 | try { |
| 181 | 182 | if (cc.getAuxCaches().length == 0) { |
| 182 | 183 | cc.setAuxCaches(new AuxiliaryCache[]{DISK_CACHE_FACTORY.createCache( |
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
|
|
| 8 | 8 | import java.awt.Toolkit; |
| 9 | 9 | import java.awt.geom.AffineTransform; |
| 10 | 10 | import java.awt.image.BufferedImage; |
| 11 | | import java.io.ByteArrayOutputStream; |
| 12 | 11 | import java.io.File; |
| 13 | 12 | import java.io.IOException; |
| | 13 | import java.io.UncheckedIOException; |
| 14 | 14 | import java.util.ArrayList; |
| 15 | 15 | import java.util.Collection; |
| 16 | 16 | |
| 17 | | import javax.imageio.ImageIO; |
| 18 | | |
| 19 | 17 | import org.apache.commons.jcs3.access.behavior.ICacheAccess; |
| 20 | 18 | import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; |
| 21 | 19 | import org.openstreetmap.josm.data.cache.JCSCacheManager; |
| … |
… |
private BufferedImage loadThumb(ImageEntry entry) {
|
| 164 | 162 | } |
| 165 | 163 | |
| 166 | 164 | 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) { |
| 171 | 168 | Logging.warn("Failed to save geoimage thumb to cache"); |
| 172 | 169 | Logging.warn(e); |
| 173 | 170 | } |
diff --git a/src/org/openstreetmap/josm/tools/ImageProvider.java b/src/org/openstreetmap/josm/tools/ImageProvider.java
index 67d424bcd..75a429f87 100644
|
a
|
b
|
|
| 63 | 63 | import javax.swing.ImageIcon; |
| 64 | 64 | import javax.xml.parsers.ParserConfigurationException; |
| 65 | 65 | |
| | 66 | import org.apache.commons.jcs3.access.behavior.ICacheAccess; |
| 66 | 67 | import org.openstreetmap.josm.data.Preferences; |
| | 68 | import org.openstreetmap.josm.data.cache.JCSCacheManager; |
| 67 | 69 | import org.openstreetmap.josm.data.osm.OsmPrimitive; |
| 68 | 70 | import org.openstreetmap.josm.data.osm.OsmPrimitiveType; |
| 69 | 71 | import org.openstreetmap.josm.io.CachedFile; |
| … |
… |
private static ImageResource getIfAvailableHttp(String url, ImageType type) {
|
| 983 | 985 | URI uri = getSvgUniverse().loadSVG(is, Utils.fileToURL(cf.getFile()).toString()); |
| 984 | 986 | svg = getSvgUniverse().getDiagram(uri); |
| 985 | 987 | } |
| 986 | | return svg == null ? null : new ImageResource(svg); |
| | 988 | return svg == null ? null : new ImageResource(url, svg); |
| 987 | 989 | case OTHER: |
| 988 | 990 | BufferedImage img = null; |
| 989 | 991 | try { |
| … |
… |
private static ImageResource getIfAvailableHttp(String url, ImageType type) {
|
| 991 | 993 | } catch (IOException | UnsatisfiedLinkError e) { |
| 992 | 994 | Logging.log(Logging.LEVEL_WARN, "Exception while reading HTTP image:", e); |
| 993 | 995 | } |
| 994 | | return img == null ? null : new ImageResource(img); |
| | 996 | return img == null ? null : new ImageResource(url, img); |
| 995 | 997 | default: |
| 996 | 998 | throw new AssertionError("Unsupported type: " + type); |
| 997 | 999 | } |
| … |
… |
private static ImageResource getIfAvailableDataUrl(String url) {
|
| 1040 | 1042 | Logging.warn("Unable to process svg: "+s); |
| 1041 | 1043 | return null; |
| 1042 | 1044 | } |
| 1043 | | return new ImageResource(svg); |
| | 1045 | return new ImageResource(url, svg); |
| 1044 | 1046 | } else { |
| 1045 | 1047 | try { |
| 1046 | 1048 | // 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) {
|
| 1049 | 1051 | // hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/dc4322602480/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java#l656 |
| 1050 | 1052 | // CHECKSTYLE.ON: LineLength |
| 1051 | 1053 | 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); |
| 1053 | 1055 | } catch (IOException | UnsatisfiedLinkError e) { |
| 1054 | 1056 | Logging.log(Logging.LEVEL_WARN, "Exception while reading image:", e); |
| 1055 | 1057 | } |
| … |
… |
private static ImageResource getIfAvailableZip(String fullName, File archive, St
|
| 1124 | 1126 | URI uri = getSvgUniverse().loadSVG(is, entryName); |
| 1125 | 1127 | svg = getSvgUniverse().getDiagram(uri); |
| 1126 | 1128 | } |
| 1127 | | return svg == null ? null : new ImageResource(svg); |
| | 1129 | return svg == null ? null : new ImageResource(fullName, svg); |
| 1128 | 1130 | case OTHER: |
| 1129 | 1131 | while (size > 0) { |
| 1130 | 1132 | int l = is.read(buf, offs, size); |
| … |
… |
private static ImageResource getIfAvailableZip(String fullName, File archive, St
|
| 1137 | 1139 | } catch (IOException | UnsatisfiedLinkError e) { |
| 1138 | 1140 | Logging.warn(e); |
| 1139 | 1141 | } |
| 1140 | | return img == null ? null : new ImageResource(img); |
| | 1142 | return img == null ? null : new ImageResource(fullName, img); |
| 1141 | 1143 | default: |
| 1142 | 1144 | throw new AssertionError("Unknown ImageType: "+type); |
| 1143 | 1145 | } |
| … |
… |
private static ImageResource getIfAvailableZip(String fullName, File archive, St
|
| 1159 | 1161 | private static ImageResource getIfAvailableLocalURL(URL path, ImageType type) { |
| 1160 | 1162 | switch (type) { |
| 1161 | 1163 | case SVG: |
| 1162 | | SVGDiagram svg = null; |
| | 1164 | return new ImageResource(path.toString(), () -> { |
| 1163 | 1165 | synchronized (getSvgUniverse()) { |
| 1164 | 1166 | try { |
| 1165 | 1167 | URI uri = null; |
| … |
… |
private static ImageResource getIfAvailableLocalURL(URL path, ImageType type) {
|
| 1175 | 1177 | uri = getSvgUniverse().loadSVG(betterPath); |
| 1176 | 1178 | } |
| 1177 | 1179 | } |
| 1178 | | svg = getSvgUniverse().getDiagram(uri); |
| | 1180 | return getSvgUniverse().getDiagram(uri); |
| 1179 | 1181 | } catch (SecurityException | IOException e) { |
| 1180 | 1182 | Logging.log(Logging.LEVEL_WARN, "Unable to read SVG", e); |
| 1181 | 1183 | } |
| 1182 | 1184 | } |
| 1183 | | return svg == null ? null : new ImageResource(svg); |
| | 1185 | return null; |
| | 1186 | }); |
| 1184 | 1187 | case OTHER: |
| 1185 | 1188 | BufferedImage img = null; |
| 1186 | 1189 | try { |
| … |
… |
private static ImageResource getIfAvailableLocalURL(URL path, ImageType type) {
|
| 1195 | 1198 | Logging.log(Logging.LEVEL_WARN, "Unable to read image", e); |
| 1196 | 1199 | Logging.debug(e); |
| 1197 | 1200 | } |
| 1198 | | return img == null ? null : new ImageResource(img); |
| | 1201 | return img == null ? null : new ImageResource(path.toString(), img); |
| 1199 | 1202 | default: |
| 1200 | 1203 | throw new AssertionError(); |
| 1201 | 1204 | } |
| … |
… |
static Image getCursorImage(String name, String overlay, UnaryOperator<Dimension
|
| 1393 | 1396 | * @since 6172 |
| 1394 | 1397 | */ |
| 1395 | 1398 | 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(); |
| 1397 | 1400 | } |
| 1398 | 1401 | |
| 1399 | 1402 | /** |
diff --git a/src/org/openstreetmap/josm/tools/ImageResource.java b/src/org/openstreetmap/josm/tools/ImageResource.java
index f710b7f30..fdc18ae44 100644
|
a
|
b
|
|
| 4 | 4 | import java.awt.Dimension; |
| 5 | 5 | import java.awt.Image; |
| 6 | 6 | import java.awt.image.BufferedImage; |
| | 7 | import java.io.File; |
| | 8 | import java.io.IOException; |
| | 9 | import java.io.UncheckedIOException; |
| 7 | 10 | import java.util.List; |
| 8 | | import java.util.Map; |
| 9 | | import java.util.concurrent.ConcurrentHashMap; |
| | 11 | import java.util.Objects; |
| | 12 | import java.util.function.Supplier; |
| 10 | 13 | |
| 11 | 14 | import javax.swing.AbstractAction; |
| 12 | 15 | import javax.swing.Action; |
| … |
… |
|
| 15 | 18 | import javax.swing.JPanel; |
| 16 | 19 | import javax.swing.UIManager; |
| 17 | 20 | |
| | 21 | import org.apache.commons.jcs3.access.behavior.ICacheAccess; |
| | 22 | import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; |
| | 23 | import org.openstreetmap.josm.data.cache.JCSCacheManager; |
| | 24 | import org.openstreetmap.josm.spi.preferences.Config; |
| | 25 | |
| 18 | 26 | import com.kitfox.svg.SVGDiagram; |
| 19 | 27 | |
| 20 | 28 | /** |
| … |
… |
|
| 30 | 38 | /** |
| 31 | 39 | * Caches the image data for resized versions of the same image. The key is obtained using {@link ImageResizeMode#cacheKey(Dimension)}. |
| 32 | 40 | */ |
| 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; |
| 34 | 45 | /** |
| 35 | 46 | * SVG diagram information in case of SVG vector image. |
| 36 | 47 | */ |
| 37 | 48 | private SVGDiagram svg; |
| | 49 | private Supplier<SVGDiagram> svgSupplier; |
| 38 | 50 | /** |
| 39 | 51 | * Use this dimension to request original file dimension. |
| 40 | 52 | */ |
| … |
… |
|
| 54 | 66 | |
| 55 | 67 | /** |
| 56 | 68 | * Constructs a new {@code ImageResource} from an image. |
| | 69 | * @param cacheKey the caching identifier of the image |
| 57 | 70 | * @param img the image |
| 58 | 71 | */ |
| 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); |
| 62 | 75 | } |
| 63 | 76 | |
| 64 | 77 | /** |
| 65 | 78 | * Constructs a new {@code ImageResource} from SVG data. |
| | 79 | * @param cacheKey the caching identifier of the image |
| 66 | 80 | * @param svg SVG data |
| 67 | 81 | */ |
| 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); |
| 71 | 90 | } |
| 72 | 91 | |
| 73 | 92 | /** |
| … |
… |
public ImageResource(SVGDiagram svg) {
|
| 77 | 96 | * @since 8095 |
| 78 | 97 | */ |
| 79 | 98 | public ImageResource(ImageResource res, List<ImageOverlay> overlayInfo) { |
| | 99 | this.cacheKey = res.cacheKey; |
| 80 | 100 | this.svg = res.svg; |
| | 101 | this.svgSupplier = res.svgSupplier; |
| 81 | 102 | this.baseImage = res.baseImage; |
| 82 | 103 | this.overlayInfo = overlayInfo; |
| 83 | 104 | } |
| … |
… |
ImageIcon getImageIcon(Dimension dim, boolean multiResolution, ImageResizeMode r
|
| 158 | 179 | return getImageIconAlreadyScaled(GuiSizesHelper.getDimensionDpiAdjusted(dim), multiResolution, false, resizeMode); |
| 159 | 180 | } |
| 160 | 181 | |
| | 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 | |
| 161 | 191 | /** |
| 162 | 192 | * Get an ImageIcon object for the image of this resource. A potential UI scaling is assumed |
| 163 | 193 | * to be already taken care of, so dim is already scaled accordingly. |
| … |
… |
ImageIcon getImageIconAlreadyScaled(Dimension dim, boolean multiResolution, bool
|
| 180 | 210 | } else if (resizeMode == null) { |
| 181 | 211 | resizeMode = ImageResizeMode.BOUNDED; |
| 182 | 212 | } |
| 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); |
| 185 | 215 | if (img == null) { |
| | 216 | if (svgSupplier != null) { |
| | 217 | svg = svgSupplier.get(); |
| | 218 | svgSupplier = null; |
| | 219 | } |
| 186 | 220 | if (svg != null) { |
| 187 | 221 | img = ImageProvider.createImageFromSvg(svg, dim, resizeMode); |
| 188 | 222 | if (img == null) { |
| … |
… |
ImageIcon getImageIconAlreadyScaled(Dimension dim, boolean multiResolution, bool
|
| 210 | 244 | img = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); |
| 211 | 245 | disabledIcon.paintIcon(new JPanel(), img.getGraphics(), 0, 0); |
| 212 | 246 | } |
| 213 | | imgCache.put(cacheKey, img); |
| | 247 | if (img == null) { |
| | 248 | return null; |
| | 249 | } |
| | 250 | imgCache.put(cacheKey, BufferedImageCacheEntry.pngEncoded(img)); |
| 214 | 251 | } |
| 215 | 252 | |
| 216 | 253 | if (!multiResolution) |
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 {
|
| 93 | 93 | String presetfile = "resource://data/defaultpresets.xml"; |
| 94 | 94 | final Collection<TaggingPreset> presets = TaggingPresetReader.readAll(presetfile, true); |
| 95 | 95 | Assert.assertTrue("Default presets are empty", presets.size() > 0); |
| | 96 | TaggingPresetsTest.waitForIconLoading(presets); |
| 96 | 97 | } |
| 97 | 98 | } |