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 | } |