This is an automated email from the ASF dual-hosted git repository.

ebakke pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git

commit 0f61cd9928841a065a4ec18b0bb808e154a9632b
Author: Eirik Bakke <eba...@ultorg.com>
AuthorDate: Thu May 30 00:20:20 2019 -0400

    Make ImageUtilities handle scalable Icon implementations.
    
    This commit prepares ImageUtilities to work with scalable Icon 
implementations,
    such as a future SVGIcon class. Conversions such 
image2icon(icon2Image(icon))
    are now lossless with respect to scalable Icon implementations. This allows
    HiDPI support to be implemented via custom Icon classes while keeping 
existing
    Image-based APIs (such as org.openide.nodes.Node.getIcon) unchanged.
    
    The internal ImageUtilities.ToolTipImage class, which also implements the 
Icon
    interface, is now used in most cases for ImageUtilities methods that return 
an
    Image. Modifying ToolTipImage's paint() method to paint an extra debugging
    stroke yields a quick confirmation that most of the icons visible in the IDE
    now successfully have their painting delegated through this method. This 
will
    come in handy in a subsequent commit, when we try to do some centralized 
scaling
    improvements for HiDPI displays.
    
    It is no longer safe to assume that ImageUtilities.image2Icon returns an
    ImageIcon. Grepped the entire codebase for such casts (searched for cast to
    ImageIcon within 3 lines of 'image2Icon'), and found only one, which was 
fixed.
    
    Manually tested, including for the case where
    ImageUtilities.getImageIconFilter() returns a non-null filter. Fixed a 
deadlock
    bug that was unearthed in the latter case.
---
 .../jshell/editor/CommandCompletionProvider.java   |   2 +-
 .../src/org/openide/util/ImageUtilities.java       | 166 +++++++++++++++------
 2 files changed, 125 insertions(+), 43 deletions(-)

diff --git 
a/java/jshell.support/src/org/netbeans/modules/jshell/editor/CommandCompletionProvider.java
 
b/java/jshell.support/src/org/netbeans/modules/jshell/editor/CommandCompletionProvider.java
index fbb8fd9..697970d 100644
--- 
a/java/jshell.support/src/org/netbeans/modules/jshell/editor/CommandCompletionProvider.java
+++ 
b/java/jshell.support/src/org/netbeans/modules/jshell/editor/CommandCompletionProvider.java
@@ -337,7 +337,7 @@ public class CommandCompletionProvider implements 
CompletionProvider{
                             n = f.getLookup().lookup(Node.class);
                         }
                         if (n != null) {
-                            return 
(ImageIcon)ImageUtilities.image2Icon(n.getIcon(BeanInfo.ICON_COLOR_16x16));
+                            return new 
ImageIcon(n.getIcon(BeanInfo.ICON_COLOR_16x16));
                         }
                     }
                 }
diff --git a/platform/openide.util.ui/src/org/openide/util/ImageUtilities.java 
b/platform/openide.util.ui/src/org/openide/util/ImageUtilities.java
index 7a3c73a..8783424 100644
--- a/platform/openide.util.ui/src/org/openide/util/ImageUtilities.java
+++ b/platform/openide.util.ui/src/org/openide/util/ImageUtilities.java
@@ -28,7 +28,6 @@ import java.awt.Toolkit;
 import java.awt.Transparency;
 import java.awt.image.BufferedImage;
 import java.awt.image.ColorModel;
-import java.awt.image.FilteredImageSource;
 import java.awt.image.ImageObserver;
 import java.awt.image.RGBImageFilter;
 import java.awt.image.WritableRaster;
@@ -54,6 +53,13 @@ import javax.swing.UIManager;
 
 /** 
  * Useful static methods for manipulation with images/icons, results are 
cached.
+ *
+ * <p>Images can be represented as instances of either {@link Image} or {@link 
Icon}. For best
+ * results on HiDPI displays, clients should use the {@link 
#image2Icon(Image)} method provided by
+ * this class when converting an {@code Image} to an {@code Icon}, rather than 
constructing
+ * {@link ImageIcon} instances themselves. When doing manual painting, clients 
should use
+ * {@link Icon#paintIcon(Component, Graphics, int, int)} rather than
+ * {@link Graphics#drawImage(Image, int, int, ImageObserver)}.
  * 
  * @author Jaroslav Tulach, Tomas Holy
  * @since 7.15
@@ -93,6 +99,22 @@ public final class ImageUtilities {
     private static final Logger ERR = 
Logger.getLogger(ImageUtilities.class.getName());
     
     private static final String DARK_LAF_SUFFIX = "_dark"; //NOI18N
+
+    /**
+     * Dummy component to be passed to the first parameter  of
+     * {@link Icon#paintIcon(Component, Graphics, int, int)} when converting 
an {@code Icon} to an
+     * {@code Image}. See comment in {@link #icon2ToolTipImage(Icon)}.
+     */
+    private static volatile Component dummyIconComponent;
+
+    static {
+        Mutex.EVENT.writeAccess(new Runnable() {
+            @Override
+            public void run() {
+                dummyIconComponent = new JLabel();
+            }
+        });
+    }
     
     private ImageUtilities() {
     }
@@ -135,11 +157,16 @@ public final class ImageUtilities {
      * @return icon's Image or null if the icon cannot be loaded
      */
     public static final Image loadImage(String resource, boolean localized) {
+        return loadImageInternal(resource, localized);
+    }
+
+    // Private version with more specific return type.
+    private static ToolTipImage loadImageInternal(String resource, boolean 
localized) {
         // Avoid a NPE that could previously occur in the isDarkLaF case only. 
See NETBEANS-2401.
         if (resource == null) {
             return null;
         }
-        Image image = null;
+        ToolTipImage image = null;
         if( isDarkLaF() ) {
             image = getIcon(addDarkSuffix(resource), localized);
             // found an image with _dark-suffix, so there no need to apply an
@@ -150,7 +177,10 @@ public final class ImageUtilities {
             // only non _dark images need filtering
             RGBImageFilter imageFilter = getImageIconFilter();
             if (null != image && null != imageFilter) {
-                image = createFilteredImage(imageFilter, image);
+                /* Make sure that every Image loaded by this class is a 
ToolTipImage, whether or not
+                a filter is applied. That way we're less likely to introduce 
bugs that only occur on
+                certain LAFs. */
+                image = icon2ToolTipImage(FilteredIcon.create(imageFilter, 
image));
             }
         }
         return image;
@@ -171,11 +201,11 @@ public final class ImageUtilities {
      * @since 7.22
      */
     public static final ImageIcon loadImageIcon( String resource, boolean 
localized ) {
-        Image image = loadImage(resource, localized);
+        ToolTipImage image = loadImageInternal(resource, localized);
         if( image == null ) {
             return null;
         }
-        return ( ImageIcon ) image2Icon( image );
+        return IconImageIcon.create(image);
     }
     
     private static boolean isDarkLaF() {
@@ -246,16 +276,16 @@ public final class ImageUtilities {
      * @return icon corresponding icon
      */    
     public static final Icon image2Icon(Image image) {
-        if (image instanceof ToolTipImage) {
-            return ((ToolTipImage) image).getIcon();
-        } else {
-            return new ImageIcon(image);
-        }
+        return (image instanceof ToolTipImage)
+                ? (ToolTipImage) image : assignToolTipToImageInternal(image, 
"");
     }
     
     /**
      * Converts given icon to a {@link java.awt.Image}.
      *
+     * <p>A scalable {@link Icon} instance can always be recovered by passing 
the returned
+     * {@code Image} to {@link #image2Icon(Image)} again, i.e. for painting on 
HiDPI screens.
+     *
      * @param icon {@link javax.swing.Icon} to be converted.
      */
     public static final Image icon2Image(Icon icon) {
@@ -263,16 +293,34 @@ public final class ImageUtilities {
             LOGGER.log(Level.WARNING, null, new NullPointerException());
             return loadImage("org/openide/nodes/defaultNode.png", true);
         }
-        if (icon instanceof ImageIcon) {
+        if (icon instanceof ToolTipImage) {
+            return (ToolTipImage) icon;
+        } else if (icon instanceof IconImageIcon) {
+            return icon2Image(((IconImageIcon) icon).getDelegateIcon());
+        } else if (icon instanceof ImageIcon) {
             return ((ImageIcon) icon).getImage();
         } else {
-            ToolTipImage image = new ToolTipImage("", icon.getIconWidth(), 
icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
-            Graphics g = image.getGraphics();
-            icon.paintIcon(new JLabel(), g, 0, 0);
-            g.dispose();
-            return image;
+            return icon2ToolTipImage(icon);
         }
     }
+
+    private static ToolTipImage icon2ToolTipImage(Icon icon) {
+        Parameters.notNull("icon", icon);
+        if (icon instanceof ToolTipImage) {
+            return (ToolTipImage) icon;
+        }
+        ToolTipImage image = new ToolTipImage(icon, "", 
BufferedImage.TYPE_INT_ARGB);
+        Graphics g = image.getGraphics();
+        /* Previously, we'd create a new JLabel here every time; this once led 
to a deadlock on
+        startup when the nb.imageicon.filter setting was enabled. The 
underlying problem is that
+        methods in this class may be called from any thread, while JLabel's 
methods and constructors
+        should really only be called on the Event Dispatch Thread. 
Constructing the component once
+        on the EDT fixed the problem. Read-only operations from non-EDT 
threads shouldn't really be
+        a problem; most Icon implementations won't ever access the component 
parameter anyway. */
+        icon.paintIcon(dummyIconComponent, g, 0, 0);
+        g.dispose();
+        return image;
+    }
     
     /**
      * Assign tool tip text to given image (creates new or returns cached, 
original remains unmodified)
@@ -361,14 +409,8 @@ public final class ImageUtilities {
      */
     public static Image createDisabledImage(Image image)  {
         Parameters.notNull("image", image);
-        return createFilteredImage(DisabledButtonFilter.INSTANCE, image);
-    }
-
-    private static Image createFilteredImage(RGBImageFilter filter, Image 
image) {
-        Parameters.notNull("filter", filter);
-        Parameters.notNull("image", image);
-        return Toolkit.getDefaultToolkit().createImage(
-                new FilteredImageSource(image.getSource(), filter));
+        // Go through FilteredIcon to preserve scalable icons.
+        return icon2Image(createDisabledIcon(image2Icon(image)));
     }
 
     /**
@@ -676,7 +718,7 @@ public final class ImageUtilities {
         Object firstUrl = image1.getProperty("url", null);
         
         ColorModel model = colorModel(bitmask? Transparency.BITMASK: 
Transparency.TRANSLUCENT);
-        ToolTipImage buffImage = new ToolTipImage(str.toString(), 
+        ToolTipImage buffImage = new ToolTipImage(str.toString(), null,
                 model, model.createCompatibleWritableRaster(w, h), 
model.isAlphaPremultiplied(), null, firstUrl instanceof URL ? (URL)firstUrl : 
null
             );
 
@@ -812,11 +854,40 @@ public final class ImageUtilities {
      // end of ActiveRef
 
     /**
+     * Wraps an arbitrary {@link Icon} inside an {@link ImageIcon}. This 
allows us to provide
+     * scalable icons from {@link #loadImageIcon(String,boolean)} without 
changing the API.
+     */
+    private static final class IconImageIcon extends ImageIcon {
+        private final Icon delegate;
+
+        private IconImageIcon(Icon delegate) {
+            super(icon2Image(delegate));
+            Parameters.notNull("delegate", delegate);
+            this.delegate = delegate;
+        }
+
+        private static ImageIcon create(Icon delegate) {
+            return (delegate instanceof ImageIcon)
+                    ? (ImageIcon) delegate : new IconImageIcon(delegate);
+        }
+
+        @Override
+        public synchronized void paintIcon(Component c, Graphics g, int x, int 
y) {
+            delegate.paintIcon(c, g, x, y);
+        }
+
+        public Icon getDelegateIcon() {
+            return delegate;
+        }
+    }
+
+    /**
      * Image with tool tip text (for icons with badges)
      */
     private static class ToolTipImage extends BufferedImage implements Icon {
         final String toolTipText;
-        ImageIcon imageIcon;
+        // May be null.
+        final Icon delegateIcon;
         final URL url;
 
         public static ToolTipImage createNew(String toolTipText, Image image, 
URL url) {
@@ -829,8 +900,11 @@ public final class ImageUtilities {
                 Object value = image.getProperty("url", null);
                 url = (value instanceof URL) ? (URL) value : null;
             }            
+            Icon icon = (image instanceof ToolTipImage)
+                    ? ((ToolTipImage) image).getDelegateIcon() : null;
             ToolTipImage newImage = new ToolTipImage(
                 toolTipText,
+                icon,
                 model,
                 model.createCompatibleWritableRaster(w, h),
                 model.isAlphaPremultiplied(), null, url
@@ -841,27 +915,31 @@ public final class ImageUtilities {
             g.dispose();
             return newImage;
         }
-        
+
         public ToolTipImage(
-            String toolTipText, ColorModel cm, WritableRaster raster,
+            String toolTipText, Icon delegateIcon, ColorModel cm, 
WritableRaster raster,
             boolean isRasterPremultiplied, Hashtable<?, ?> properties, URL url
         ) {
             super(cm, raster, isRasterPremultiplied, properties);
             this.toolTipText = toolTipText;
+            this.delegateIcon = delegateIcon;
             this.url = url;
         }
 
-        public ToolTipImage(String toolTipText, int width, int height, int 
imageType) {
-            super(width, height, imageType);
+        public ToolTipImage(Icon delegateIcon, String toolTipText, int 
imageType) {
+            super(delegateIcon.getIconWidth(), delegateIcon.getIconHeight(), 
imageType);
+            this.delegateIcon = delegateIcon;
             this.toolTipText = toolTipText;
             this.url = null;
         }
-        
-        synchronized ImageIcon getIcon() {
-            if (imageIcon == null) {
-                imageIcon = new ImageIcon(this);
-            }
-            return imageIcon;
+
+        /**
+         * Get an {@link Icon} instance representing a scalable version of 
this {@code Image}.
+         *
+         * @return may be null
+         */
+        public Icon getDelegateIcon() {
+            return delegateIcon;
         }
 
         public int getIconHeight() {
@@ -873,7 +951,11 @@ public final class ImageUtilities {
         }
 
         public void paintIcon(Component c, Graphics g, int x, int y) {
-            g.drawImage(this, x, y, null);
+            if (delegateIcon != null) {
+                delegateIcon.paintIcon(c, g, x, y);
+            } else {
+                g.drawImage(this, x, y, null);
+            }
         }
 
         @Override
@@ -881,14 +963,14 @@ public final class ImageUtilities {
             if ("url".equals(name)) { // NOI18N
                 if (url != null) {
                     return url;
+                } else if (!(delegateIcon instanceof ImageIcon)) {
+                    return null;
                 } else {
-                    if (imageIcon == null) {
-                        return null;
-                    }
-                    if (imageIcon.getImage() == this) {
+                    Image image = ((ImageIcon) delegateIcon).getImage();
+                    if (image == this || image == null) {
                         return null;
                     }
-                    return imageIcon.getImage().getProperty("url", observer);
+                    return image.getProperty("url", observer);
                 }
             }
             return super.getProperty(name, observer);


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org
For additional commands, e-mail: commits-h...@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists

Reply via email to