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.
This commit is contained in:
Anton Tarasov
2015-12-10 14:40:56 +03:00
parent b25cf7afbe
commit c0e5c4f4ee
2 changed files with 76 additions and 43 deletions
@@ -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<Float, Icon> 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<Float, Icon>(1) {
@Override
public boolean removeEldestEntry(Map.Entry<Float, Icon> 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<Boolean, SoftReference<Image>> origImagesCache = Collections.synchronizedMap(new HashMap<Boolean, SoftReference<Image>>(2));
private static final int SCALED_ICONS_CACHE_LIMIT = 5;
// Map {effective scale -> icon}
private Map<Float, SoftReference<Icon>> scaledIconsCache = Collections.synchronizedMap(new LinkedHashMap<Float, SoftReference<Icon>>(SCALED_ICONS_CACHE_LIMIT) {
@Override
public boolean removeEldestEntry(Map.Entry<Float, SoftReference<Icon>> entry) {
return size() > SCALED_ICONS_CACHE_LIMIT;
}
});
public Image getOrigImage(boolean retina) {
SoftReference<Image> 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<Image>(img));
}
return img;
}
public Icon getScaledIcon(float scale) {
float effectiveScale = scale * JBUI.scale(1f);
SoftReference<Icon> 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>(icon));
}
}
return icon;
}
public void clear() {
scaledIconsCache.clear();
origImagesCache.clear();
}
}
}
public abstract static class LazyIcon implements Icon {
@@ -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<ImageDesc> {
@@ -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;