diff --git a/images/src/org/intellij/images/editor/ImageDocument.java b/images/src/org/intellij/images/editor/ImageDocument.java index c252dc532a0b..10d201db26cf 100644 --- a/images/src/org/intellij/images/editor/ImageDocument.java +++ b/images/src/org/intellij/images/editor/ImageDocument.java @@ -18,7 +18,7 @@ package org.intellij.images.editor; import javax.swing.event.ChangeListener; import java.awt.*; import java.awt.image.BufferedImage; -import java.util.function.Function; +import java.util.function.BiFunction; /** * Image document to show or edit in {@link ImageEditor}. @@ -30,7 +30,7 @@ public interface ImageDocument { /** * A scaled image provider. */ - interface ScaledImageProvider extends Function {} + interface ScaledImageProvider extends BiFunction {} /** * Return image for rendering diff --git a/images/src/org/intellij/images/editor/impl/ImageEditorManagerImpl.java b/images/src/org/intellij/images/editor/impl/ImageEditorManagerImpl.java index 5473d895cef7..5ed8eb937f4e 100644 --- a/images/src/org/intellij/images/editor/impl/ImageEditorManagerImpl.java +++ b/images/src/org/intellij/images/editor/impl/ImageEditorManagerImpl.java @@ -39,7 +39,7 @@ public final class ImageEditorManagerImpl { ui.getImageComponent().setGridVisible(gridOptions.isShowDefault()); ui.getImageComponent().setTransparencyChessboardVisible(transparencyChessboardOptions.isShowDefault()); - ui.setImageProvider(ignore -> image, null); + ui.setImageProvider((scale, ancestor) -> image, null); return ui; } } diff --git a/images/src/org/intellij/images/thumbnail/impl/ThumbnailViewUI.java b/images/src/org/intellij/images/thumbnail/impl/ThumbnailViewUI.java index cae719f0371e..0d3f64f74ced 100644 --- a/images/src/org/intellij/images/thumbnail/impl/ThumbnailViewUI.java +++ b/images/src/org/intellij/images/thumbnail/impl/ThumbnailViewUI.java @@ -382,8 +382,8 @@ final class ThumbnailViewUI extends JPanel implements DataProvider, Disposable { // File rendering setFileSize(file.getLength()); try { - BufferedImage image = IfsUtil.getImage(file); ImageComponent imageComponent = getImageComponent(); + BufferedImage image = IfsUtil.getImage(file, imageComponent); imageComponent.getDocument().setValue(image); setFormat(IfsUtil.getFormat(file)); } catch (Exception e) { diff --git a/images/src/org/intellij/images/ui/ImageComponent.java b/images/src/org/intellij/images/ui/ImageComponent.java index f2307a5ba4c6..02349489c76b 100644 --- a/images/src/org/intellij/images/ui/ImageComponent.java +++ b/images/src/org/intellij/images/ui/ImageComponent.java @@ -73,7 +73,7 @@ public class ImageComponent extends JComponent { UIManager.getDefaults().put(uiClassID, ImageComponentUI.class.getName()); } - private final ImageDocument document = new ImageDocumentImpl(); + private final ImageDocument document = new ImageDocumentImpl(this); private final Grid grid = new Grid(); private final Chessboard chessboard = new Chessboard(); private boolean myFileSizeVisible = true; @@ -247,6 +247,11 @@ public class ImageComponent extends JComponent { private ScaledImageProvider imageProvider; private String format; private Image renderer; + private Component myComponent; + + public ImageDocumentImpl(Component component) { + this.myComponent = component; + } public Image getRenderer() { return renderer; @@ -263,12 +268,12 @@ public class ImageComponent extends JComponent { @Override public BufferedImage getValue(double scale) { - return imageProvider != null ? imageProvider.apply(scale) : null; + return imageProvider != null ? imageProvider.apply(scale, myComponent) : null; } public void setValue(BufferedImage image) { this.renderer = image != null ? Toolkit.getDefaultToolkit().createImage(image.getSource()) : null; - setValue(image != null ? ignore -> image : null); + setValue(image != null ? (scale, anchor) -> image : null); } @Override diff --git a/images/src/org/intellij/images/vfs/IfsUtil.java b/images/src/org/intellij/images/vfs/IfsUtil.java index 578aebd6b27f..659eca57a9d1 100644 --- a/images/src/org/intellij/images/vfs/IfsUtil.java +++ b/images/src/org/intellij/images/vfs/IfsUtil.java @@ -18,14 +18,15 @@ package org.intellij.images.vfs; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.reference.SoftReference; -import com.intellij.util.ImageLoader; import com.intellij.util.LogicalRoot; import com.intellij.util.LogicalRootsManager; import com.intellij.util.SVGLoader; @@ -41,6 +42,7 @@ import javax.imageio.ImageIO; import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; +import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; @@ -50,12 +52,16 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Iterator; +import static com.intellij.util.ui.JBUI.ScaleType.OBJ_SCALE; + /** * Image loader utility. * * @author Alexey Efimov */ public final class IfsUtil { + private static final Logger LOG = Logger.getInstance("#org.intellij.images.vfs.IfsUtil"); + public static final String ICO_FORMAT = "ico"; public static final String SVG_FORMAT = "svg"; @@ -82,27 +88,43 @@ public final class IfsUtil { try { final BufferedImage image = ICO_IMAGE_PARSER.getBufferedImage(new ByteSourceArray(content), null); file.putUserData(FORMAT_KEY, ICO_FORMAT); - file.putUserData(IMAGE_PROVIDER_REF_KEY, new SoftReference<>(zoom -> image)); + file.putUserData(IMAGE_PROVIDER_REF_KEY, new SoftReference<>((scale, ancestor) -> image)); return true; } catch (ImageReadException ignore) { } } if (isScalableImage(file)) { + final Ref url = Ref.create(); try { - URL url = new File(file.getPath()).toURI().toURL(); - - // ensure that image can be displayed (or throw exception otherwise) - SVGLoader.load(url, new ByteArrayInputStream(content), 1.0f); - - file.putUserData(FORMAT_KEY, SVG_FORMAT); - file.putUserData(IMAGE_PROVIDER_REF_KEY, new SoftReference<>(zoom -> { - ScaleContext ctx = ScaleContext.create(); // todo: lack component, defaults to main monitor - return (BufferedImage)ImageLoader.loadFromUrl(url, true, false, null, ctx); - })); - return true; + url.set(new File(file.getPath()).toURI().toURL()); } - catch (MalformedURLException ignored) {} + catch (MalformedURLException ex) { + LOG.warn(ex.getMessage()); + } + + try { + // ensure svg can be displayed + SVGLoader.load(url.get(), new ByteArrayInputStream(content), 1.0f); + } + catch (Throwable t) { + LOG.warn(url.get() + " " + t.getMessage()); + return false; + } + + file.putUserData(FORMAT_KEY, SVG_FORMAT); + file.putUserData(IMAGE_PROVIDER_REF_KEY, new SoftReference<>((zoom, ancestor) -> { + try { + final ScaleContext ctx = ScaleContext.create(ancestor); + ctx.update(OBJ_SCALE.of(zoom)); + return SVGLoader.loadHiDPI(url.get(), new ByteArrayInputStream(content), ctx); + } + catch (Throwable t) { + LOG.warn(url.get() + " " + t.getMessage()); + return null; + } + })); + return true; } InputStream inputStream = new ByteArrayInputStream(content, 0, content.length); @@ -117,7 +139,7 @@ public final class IfsUtil { imageReader.setInput(imageInputStream, true, true); int minIndex = imageReader.getMinIndex(); BufferedImage image = imageReader.read(minIndex, param); - file.putUserData(IMAGE_PROVIDER_REF_KEY, new SoftReference<>(zoom -> image)); + file.putUserData(IMAGE_PROVIDER_REF_KEY, new SoftReference<>((zoom, ancestor) -> image)); return true; } finally { imageReader.dispose(); @@ -136,9 +158,14 @@ public final class IfsUtil { @Nullable public static BufferedImage getImage(@NotNull VirtualFile file) throws IOException { + return getImage(file, null); + } + + @Nullable + public static BufferedImage getImage(@NotNull VirtualFile file, @Nullable Component ancestor) throws IOException { ScaledImageProvider imageProvider = getImageProvider(file); if (imageProvider == null) return null; - return imageProvider.apply(1d); + return imageProvider.apply(1d, ancestor); } @Nullable diff --git a/platform/util/src/com/intellij/util/ImageLoader.java b/platform/util/src/com/intellij/util/ImageLoader.java index 6726527f88ab..a761fdc3362d 100644 --- a/platform/util/src/com/intellij/util/ImageLoader.java +++ b/platform/util/src/com/intellij/util/ImageLoader.java @@ -46,7 +46,6 @@ import java.util.concurrent.ConcurrentMap; import static com.intellij.util.ImageLoader.ImageDesc.Type.IMG; import static com.intellij.util.ImageLoader.ImageDesc.Type.SVG; import static com.intellij.util.ui.JBUI.ScaleType.PIX_SCALE; -import static com.intellij.util.ui.JBUI.ScaleType.SYS_SCALE; public class ImageLoader implements Serializable { private static final Logger LOG = Logger.getInstance("#com.intellij.util.ImageLoader"); @@ -294,10 +293,7 @@ public class ImageLoader implements Serializable { return with(new ImageConverter() { @Override public Image convert(Image source, ImageDesc desc) { - if (source != null && UIUtil.isJreHiDPI(ctx)) { - return RetinaImage.createFrom(source, ctx.getScale(SYS_SCALE), ourComponent); - } - return source; + return ImageUtil.ensureHiDPI(source, ctx); } }); } @@ -318,6 +314,7 @@ public class ImageLoader implements Serializable { public static final Component ourComponent = new Component() { }; + @SuppressWarnings("UnusedReturnValue") private static boolean waitForImage(Image image) { if (image == null) return false; if (image.getWidth(null) > 0) return true; diff --git a/platform/util/src/com/intellij/util/SVGLoader.java b/platform/util/src/com/intellij/util/SVGLoader.java index 73842192888f..ce1c4af03d21 100644 --- a/platform/util/src/com/intellij/util/SVGLoader.java +++ b/platform/util/src/com/intellij/util/SVGLoader.java @@ -18,6 +18,8 @@ package com.intellij.util; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ui.ImageUtil; +import com.intellij.util.ui.JBUI.ScaleContext; import org.apache.batik.anim.dom.*; import org.apache.batik.dom.AbstractDocument; import org.apache.batik.transcoder.SVGAbstractTranscoder; @@ -33,6 +35,7 @@ import org.w3c.dom.Element; import java.awt.*; import java.awt.image.BufferedImage; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.net.URISyntaxException; @@ -40,6 +43,8 @@ import java.net.URL; import java.util.ArrayList; import java.util.List; +import static com.intellij.util.ui.JBUI.ScaleType.PIX_SCALE; + /** * @author tav */ @@ -147,6 +152,12 @@ public class SVGLoader { } } + public static T loadHiDPI(@Nullable URL url, @NotNull InputStream stream , ScaleContext ctx) throws IOException { + BufferedImage image = (BufferedImage)load(url, stream, ctx.getScale(PIX_SCALE)); + //noinspection unchecked + return (T)ImageUtil.ensureHiDPI(image, ctx); + } + public static Couple loadInfo(@Nullable URL url, @NotNull InputStream stream , double scale) throws IOException { SVGLoader loader = new SVGLoader(url, stream, scale); return Couple.of((int)loader.size.width, (int)loader.size.height); diff --git a/platform/util/src/com/intellij/util/ui/ImageUtil.java b/platform/util/src/com/intellij/util/ui/ImageUtil.java index 50874390c11c..29db5601ba9c 100644 --- a/platform/util/src/com/intellij/util/ui/ImageUtil.java +++ b/platform/util/src/com/intellij/util/ui/ImageUtil.java @@ -17,11 +17,17 @@ package com.intellij.util.ui; import com.intellij.util.ImageLoader; import com.intellij.util.JBHiDPIScaledImage; +import com.intellij.util.RetinaImage; +import com.intellij.util.ui.JBUI.ScaleContext; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.awt.*; import java.awt.image.*; +import static com.intellij.util.ui.JBUI.ScaleType.SYS_SCALE; + /** * @author Konstantin Bulenkov */ @@ -127,4 +133,16 @@ public class ImageUtil { public static Image scaleImage(Image image, double scale) { return ImageLoader.scaleImage(image, scale); } + + /** + * Wraps the {@code image} with {@link JBHiDPIScaledImage} according to {@code ctx} when applicable. + */ + @Contract("null, _ -> null; !null, _ -> !null") + public static Image ensureHiDPI(@Nullable Image image, @NotNull ScaleContext ctx) { + if (image == null) return null; + if (UIUtil.isJreHiDPI(ctx)) { + return RetinaImage.createFrom(image, ctx.getScale(SYS_SCALE), null); + } + return image; + } } diff --git a/platform/util/src/com/intellij/util/ui/JBUI.java b/platform/util/src/com/intellij/util/ui/JBUI.java index fcec94a0bc50..69189cd0e96b 100644 --- a/platform/util/src/com/intellij/util/ui/JBUI.java +++ b/platform/util/src/com/intellij/util/ui/JBUI.java @@ -998,9 +998,9 @@ public class JBUI { * Creates a context based on the comp's system scale and sticks to it via the {@link #update()} method. */ @NotNull - public static ScaleContext create(@NotNull Component comp) { + public static ScaleContext create(@Nullable Component comp) { final ScaleContext ctx = new ScaleContext(SYS_SCALE.of(sysScale(comp))); - ctx.compRef = new WeakReference(comp); + if (comp != null) ctx.compRef = new WeakReference(comp); return ctx; }