From c0e5c4f4ee103ecf14cde5e660f42480457a89ac Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Thu, 10 Dec 2015 14:08:31 +0300 Subject: [PATCH] IDEA-148739 [follow-up] 1. Refactor the cache code. Add 1x images to the cache. 2. Clear the cache on state change. 3. Mind concurrency where applicable. --- .../com/intellij/openapi/util/IconLoader.java | 110 +++++++++++------- .../src/com/intellij/util/ImageLoader.java | 9 +- 2 files changed, 76 insertions(+), 43 deletions(-) diff --git a/platform/util/src/com/intellij/openapi/util/IconLoader.java b/platform/util/src/com/intellij/openapi/util/IconLoader.java index 72539fc6452f..3b683da4e0a0 100644 --- a/platform/util/src/com/intellij/openapi/util/IconLoader.java +++ b/platform/util/src/com/intellij/openapi/util/IconLoader.java @@ -41,10 +41,10 @@ import java.awt.image.ImageFilter; import java.lang.ref.Reference; import java.lang.reflect.Field; import java.net.URL; +import java.util.*; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.ConcurrentMap; public final class IconLoader { @@ -355,16 +355,14 @@ public final class IconLoader { } private static final class CachedImageIcon implements ScalableIcon { - private Object myRealIcon; + private volatile Object myRealIcon; @NotNull private final URL myUrl; - private boolean dark; - private float scale; - private ImageFilter filter; + private volatile boolean dark; + private volatile float scale; + private volatile ImageFilter filter; - private Image retinaImageCache; - private HashMap scaledIconsCache; - private static final int SCALED_ICONS_CACHE_LIMIT = 5; + private final MyScaledIconsCache myScaledIconsCache = new MyScaledIconsCache(); public CachedImageIcon(@NotNull URL url) { myUrl = url; @@ -377,11 +375,12 @@ public final class IconLoader { private synchronized Icon getRealIcon() { if (isLoaderDisabled() && (myRealIcon == null || dark != USE_DARK_ICONS || scale != SCALE || filter != IMAGE_FILTER)) return EMPTY_ICON; - if (dark != USE_DARK_ICONS || scale != SCALE || filter != IMAGE_FILTER) { + if (!isValid()) { myRealIcon = null; dark = USE_DARK_ICONS; scale = SCALE; filter = IMAGE_FILTER; + myScaledIconsCache.clear(); } Object realIcon = myRealIcon; if (realIcon instanceof Icon) return (Icon)realIcon; @@ -408,6 +407,10 @@ public final class IconLoader { return icon == null ? EMPTY_ICON : icon; } + private boolean isValid() { + return dark == USE_DARK_ICONS && scale == SCALE && filter == IMAGE_FILTER; + } + @Override public void paintIcon(Component c, Graphics g, int x, int y) { getRealIcon().paintIcon(c, g, x, y); @@ -433,41 +436,66 @@ public final class IconLoader { if (scaleFactor == 1f) { return this; } - if (scaledIconsCache == null) { - scaledIconsCache = new LinkedHashMap(1) { - @Override - public boolean removeEldestEntry(Map.Entry entry) { - return size() > SCALED_ICONS_CACHE_LIMIT; - } - }; + + if (!isValid()) getRealIcon(); // force state update & cache reset + + Icon icon = myScaledIconsCache.getScaledIcon(scaleFactor); + if (icon != null) { + return icon; } - - float effectiveScale = scaleFactor * JBUI.scale(1f); - - Icon result = scaledIconsCache.get(effectiveScale); - if (result != null) { - return result; - } - - boolean needRetinaImage = effectiveScale >= 1.5f || UIUtil.isRetina(); - Image image = needRetinaImage && retinaImageCache != null ? - retinaImageCache : - ImageLoader.loadFromUrl(myUrl, UIUtil.isUnderDarcula(), needRetinaImage, filter); - - if (image != null) { - if (needRetinaImage && retinaImageCache == null) { - retinaImageCache = image; - } - int width = (int)(getIconWidth() * scaleFactor); - int height = (int)(getIconHeight() * scaleFactor); - final BufferedImage resizedImage = Scalr.resize(ImageUtil.toBufferedImage(image), Scalr.Method.ULTRA_QUALITY, width, height); - result = getIcon(resizedImage); - scaledIconsCache.put(effectiveScale, result); - return result; - } - return this; } + + private class MyScaledIconsCache { + // Map {false -> image}, {true -> image@2x} + private Map> origImagesCache = Collections.synchronizedMap(new HashMap>(2)); + + private static final int SCALED_ICONS_CACHE_LIMIT = 5; + + // Map {effective scale -> icon} + private Map> scaledIconsCache = Collections.synchronizedMap(new LinkedHashMap>(SCALED_ICONS_CACHE_LIMIT) { + @Override + public boolean removeEldestEntry(Map.Entry> entry) { + return size() > SCALED_ICONS_CACHE_LIMIT; + } + }); + + public Image getOrigImage(boolean retina) { + SoftReference val = origImagesCache.get(retina); + Image img = val != null ? val.get() : null; + + if (img == null) { + img = ImageLoader.loadFromUrl(myUrl, UIUtil.isUnderDarcula(), retina, filter); + origImagesCache.put(retina, new SoftReference(img)); + } + return img; + } + + public Icon getScaledIcon(float scale) { + float effectiveScale = scale * JBUI.scale(1f); + SoftReference val = scaledIconsCache.get(effectiveScale); + Icon icon = val != null ? val.get() : null; + + if (icon == null) { + boolean needRetinaImage = (effectiveScale >= 1.5f || UIUtil.isRetina()); + Image image = getOrigImage(needRetinaImage); + + if (image != null) { + Icon realIcon = getRealIcon(); + int width = (int)(realIcon.getIconWidth() * scale); + int height = (int)(realIcon.getIconHeight() * scale); + icon = getIcon(Scalr.resize(ImageUtil.toBufferedImage(image), Scalr.Method.ULTRA_QUALITY, width, height)); + scaledIconsCache.put(effectiveScale, new SoftReference(icon)); + } + } + return icon; + } + + public void clear() { + scaledIconsCache.clear(); + origImagesCache.clear(); + } + } } public abstract static class LazyIcon implements Icon { diff --git a/platform/util/src/com/intellij/util/ImageLoader.java b/platform/util/src/com/intellij/util/ImageLoader.java index 39742663a430..e8bf9d1dcea2 100644 --- a/platform/util/src/com/intellij/util/ImageLoader.java +++ b/platform/util/src/com/intellij/util/ImageLoader.java @@ -42,6 +42,7 @@ import java.util.List; @Deprecated public class ImageLoader implements Serializable { + private static final Logger LOG = Logger.getInstance("#com.intellij.util.ImageLoader"); private static class ImageDesc { public enum Type { @@ -87,6 +88,11 @@ public class ImageLoader implements Serializable { } return type.load(url, stream, scale); } + + @Override + public String toString() { + return path + ", scale: " + scale + ", type: " + type; + } } private static class ImageDescList extends ArrayList { @@ -103,6 +109,7 @@ public class ImageLoader implements Serializable { try { Image image = desc.load(); if (image == null) continue; + LOG.info("Loaded image: " + desc); return converters.convert(image, desc); } catch (IOException ignore) { @@ -199,8 +206,6 @@ public class ImageLoader implements Serializable { public static final Component ourComponent = new Component() { }; - private static final Logger LOG = Logger.getInstance("#com.intellij.util.ImageLoader"); - private static boolean waitForImage(Image image) { if (image == null) return false; if (image.getWidth(null) > 0) return true;