From 233ac79ccce2b581a59b1a1d7a7addb329e5eb3e Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Wed, 3 Jun 2020 18:52:32 +0300 Subject: [PATCH] IDEA-242075 "Coordinate out of bounds" when rendering a disabled button with JBHiDPIScaledImage icon GitOrigin-RevId: 15c786dc1a70304c376ef05a8fb5273a15e0c542 --- .../ide/ui/laf/darcula/DarculaLaf.java | 13 ++-- .../src/com/intellij/ui/AppIcon.java | 10 +-- .../com/intellij/openapi/util/IconLoader.java | 10 ++- .../util/ui/MultiResolutionImageProvider.java | 69 ++++++++++++++++--- 4 files changed, 79 insertions(+), 23 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaLaf.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaLaf.java index 7882e919c53a..45dff2142490 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaLaf.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaLaf.java @@ -14,10 +14,9 @@ import com.intellij.openapi.util.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.TableActions; import com.intellij.ui.scale.JBUIScale; +import com.intellij.ui.scale.ScaleContext; import com.intellij.util.Alarm; -import com.intellij.util.ui.JBUI; -import com.intellij.util.ui.StartupUiUtil; -import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.*; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,6 +37,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.util.*; +import java.util.List; /** * @author Konstantin Bulenkov @@ -463,12 +463,15 @@ public class DarculaLaf extends BasicLookAndFeel implements UserDataHolder { public Icon getDisabledIcon(JComponent component, Icon icon) { if (icon == null) return null; + ScaleContext ctx = ScaleContext.create(component); + icon = MultiResolutionImageProvider.convertFromJBIcon(icon, ctx); Icon disabledIcon = super.getDisabledIcon(component, icon); + disabledIcon = MultiResolutionImageProvider.convertFromMRIcon(disabledIcon, ctx); + if (disabledIcon != null) { return disabledIcon; } - - return IconLoader.getDisabledIcon(icon); + return IconLoader.getDisabledIcon(icon, component); } @Override diff --git a/platform/platform-impl/src/com/intellij/ui/AppIcon.java b/platform/platform-impl/src/com/intellij/ui/AppIcon.java index f4a26d88371b..055b200ab84c 100644 --- a/platform/platform-impl/src/com/intellij/ui/AppIcon.java +++ b/platform/platform-impl/src/com/intellij/ui/AppIcon.java @@ -183,15 +183,7 @@ public abstract class AppIcon { if (appImage == null) return null; // [tav] expecting two resolution variants for the dock icon: 128x128, 256x256 - if (MultiResolutionImageProvider.isMultiResolutionImage(appImage)) { - List variants = MultiResolutionImageProvider.getAccessor(appImage).getResolutionVariants(); - int width = appImage.getWidth(null); - for (Image img : variants) { - if (img.getWidth(null) > width) { - appImage = img; - } - } - } + appImage = MultiResolutionImageProvider.getMaxSizeResolutionVariant(appImage); myAppImage = ImageUtil.toBufferedImage(appImage); } catch (NoSuchMethodException e) { diff --git a/platform/util/ui/src/com/intellij/openapi/util/IconLoader.java b/platform/util/ui/src/com/intellij/openapi/util/IconLoader.java index d0abcb58ba0c..04c256df5c73 100644 --- a/platform/util/ui/src/com/intellij/openapi/util/IconLoader.java +++ b/platform/util/ui/src/com/intellij/openapi/util/IconLoader.java @@ -433,6 +433,14 @@ public final class IconLoader { */ @NotNull public static Icon getDisabledIcon(@NotNull Icon icon) { + return getDisabledIcon(icon, null); + } + + /** + * Same as {@link #getDisabledIcon(Icon)} with an ancestor component for HiDPI-awareness. + */ + @NotNull + public static Icon getDisabledIcon(@NotNull Icon icon, @Nullable Component ancestor) { if (!ourIsActivated) { return icon; } @@ -442,7 +450,7 @@ public final class IconLoader { Icon disabledIcon = ourIcon2DisabledIcon.get(icon); if (disabledIcon == null) { disabledIcon = ConcurrencyUtil.cacheOrGet(ourIcon2DisabledIcon, icon, - filterIcon(icon, UIUtil::getGrayFilter/* returns laf-aware instance */, null)); // [tav] todo: lack ancestor + filterIcon(icon, UIUtil::getGrayFilter/* returns laf-aware instance */, ancestor)); } return disabledIcon; } diff --git a/platform/util/ui/src/com/intellij/util/ui/MultiResolutionImageProvider.java b/platform/util/ui/src/com/intellij/util/ui/MultiResolutionImageProvider.java index 9b9c7fb0aede..310597b57e2c 100644 --- a/platform/util/ui/src/com/intellij/util/ui/MultiResolutionImageProvider.java +++ b/platform/util/ui/src/com/intellij/util/ui/MultiResolutionImageProvider.java @@ -1,40 +1,42 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.util.ui; +import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.util.SystemInfoRt; +import com.intellij.ui.scale.ScaleContext; import com.intellij.util.JBHiDPIScaledImage; import com.intellij.util.MethodInvocator; import org.imgscalr.Scalr; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; +import javax.swing.*; import java.awt.*; import java.awt.image.BufferedImageOp; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * Provides access (or converter) to {@code java.awt.image.MultiResolutionImage} available since JDK 9. * * @author tav */ -@ApiStatus.Experimental public final class MultiResolutionImageProvider { /** * An accessor to the {@code MultiResolutionImage}'s resolution variants methods. */ public static class Accessor { - private static final Class MRI_CLASS; + private static final Class MRI_CLASS; private static final MethodInvocator GET_RESOLUTION_VARIANTS_METHOD; private static final MethodInvocator GET_RESOLUTION_VARIANT_METHOD; private final Image myMRImage; static { - Class cls = null; + Class cls = null; MethodInvocator m1 = null; MethodInvocator m2 = null; if (SystemInfoRt.IS_AT_LEAST_JAVA9) { @@ -71,6 +73,7 @@ public final class MultiResolutionImageProvider { /** * @see {@code java.awt.image.MultiResolutionImage.getResolutionVariant} */ + @SuppressWarnings("unused") public Image getResolutionVariant(double width, double height) { if (!isMultiResolutionImage(myMRImage)) { if (!checkSize(myMRImage)) { @@ -87,11 +90,11 @@ public final class MultiResolutionImageProvider { * A converter from {@link JBHiDPIScaledImage} to {@code MultiResolutionImage}. */ private static class Converter { - private static final Constructor BMRI_CLASS_CTOR; + private static final Constructor BMRI_CLASS_CTOR; static { - Class cls = null; - Constructor ctor = null; + Class cls = null; + Constructor ctor = null; if (SystemInfoRt.IS_AT_LEAST_JAVA9) { try { cls = Class.forName("java.awt.image.BaseMultiResolutionImage"); @@ -153,7 +156,7 @@ public final class MultiResolutionImageProvider { * If the provided image is not {@code JBHiDPIScaledImage} the returned {@code MultiResolutionImage} will * default to the provided image's single resolution variant. */ - @Nullable + @Contract("null -> null; !null -> !null") public static Image convertFromJBImage(@Nullable Image jbImage) { if (jbImage == null) return null; @@ -164,6 +167,56 @@ public final class MultiResolutionImageProvider { return Converter.convert(jbImage); } + /** + * Converts the provided icon with {@link JBHiDPIScaledImage} to an {@link ImageIcon} with {@code MultiResolutionImage}. + * If the provided icon's image is not {@code JBHiDPIScaledImage} the returned icon's {@code MultiResolutionImage} will + * default to the provided image's single resolution variant. + */ + @Contract("null, _ -> null; !null, _ -> !null") + public static Icon convertFromJBIcon(@Nullable Icon jbIcon, @Nullable ScaleContext ctx) { + if (jbIcon == null) return null; + + Image image = Objects.requireNonNull(IconLoader.toImage(jbIcon, ctx)); + image = convertFromJBImage(image); + return new ImageIcon(image); + } + + /** + * Returns the max-size resolution variant image of the provided {@code MultiResolutionImage}. + * If the provided image is not {@code MultiResolutionImage} then returns same image. + */ + @Contract("null -> null; !null -> !null") + public static Image getMaxSizeResolutionVariant(@Nullable Image mrImage) { + if (isMultiResolutionImage(mrImage)) { + List variants = getAccessor(mrImage).getResolutionVariants(); + int width = mrImage.getWidth(null); + for (Image img : variants) { + if (img.getWidth(null) >= width) { + mrImage = img; + } + } + } + return mrImage; + } + + /** + * Converts from the provided icon with {@code MultiResolutionImage} to an icon with {@link JBHiDPIScaledImage}. + * If the provided icon's image is not {@code MultiResolutionImage} then returns same icon. + */ + @Contract("null, _ -> null; !null, _ -> !null") + public static Icon convertFromMRIcon(@Nullable Icon mrIcon, @Nullable ScaleContext ctx) { + if (mrIcon == null) return null; + + if (ctx == null) ctx = ScaleContext.create(); + Image image = Objects.requireNonNull(IconLoader.toImage(mrIcon, ctx)); + if (isMultiResolutionImage(image)) { + return mrIcon; + } + image = getMaxSizeResolutionVariant(image); + image = ImageUtil.ensureHiDPI(image, ctx); + return new JBImageIcon(image); + } + /** * Returns an accessor to the provided {@code MultiResolutionImage}. * If the provided image is not {@code MultiResolutionImage} the resolution variants methods will default to the provided image.