8151385: [hidpi] JOptionPane-Icons only partially visible when using Windows 10 L&F
authorhschreiber
Tue, 14 Jun 2016 11:33:56 +0300
changeset 39515 5f9e9e8bf57b
parent 39514 353b1c8d1149
child 39516 0e1925d06a4d
8151385: [hidpi] JOptionPane-Icons only partially visible when using Windows 10 L&F Reviewed-by: serb, alexsch
jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolder2.java
jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolderManager2.java
jdk/src/java.desktop/windows/native/libawt/windows/ShellFolder2.cpp
jdk/test/sun/awt/shell/BadHiDPIIcon.java
--- a/jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolder2.java	Fri Jun 10 13:05:49 2016 +0300
+++ b/jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolder2.java	Tue Jun 14 11:33:56 2016 +0300
@@ -27,7 +27,9 @@
 
 import java.awt.Image;
 import java.awt.Toolkit;
+import java.awt.image.AbstractMultiResolutionImage;
 import java.awt.image.BufferedImage;
+import java.awt.image.ImageObserver;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -982,11 +984,12 @@
 
     // Return the bits from an HICON.  This has a side effect of setting
     // the imageHash variable for efficient caching / comparing.
-    private static native int[] getIconBits(long hIcon, int iconSize);
+    private static native int[] getIconBits(long hIcon);
     // Dispose the HICON
     private static native void disposeIcon(long hIcon);
 
-    static native int[] getStandardViewButton0(int iconIndex);
+    // Get buttons from native toolbar implementation.
+    static native int[] getStandardViewButton0(int iconIndex, boolean small);
 
     // Should be called from the COM thread
     private long getIShellIcon() {
@@ -1000,12 +1003,17 @@
     private static Image makeIcon(long hIcon, boolean getLargeIcon) {
         if (hIcon != 0L && hIcon != -1L) {
             // Get the bits.  This has the side effect of setting the imageHash value for this object.
-            int size = getLargeIcon ? 32 : 16;
-            int[] iconBits = getIconBits(hIcon, size);
+            final int[] iconBits = getIconBits(hIcon);
             if (iconBits != null) {
-                BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
+                // icons are always square
+                final int size = (int) Math.sqrt(iconBits.length);
+                final int baseSize = getLargeIcon ? 32 : 16;
+                final BufferedImage img =
+                        new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
                 img.setRGB(0, 0, size, size, iconBits, 0, size);
-                return img;
+                return size == baseSize
+                        ? img
+                        : new MultiResolutionIconImage(baseSize, img);
             }
         }
         return null;
@@ -1298,4 +1306,39 @@
         });
     }
 
+    static class MultiResolutionIconImage extends AbstractMultiResolutionImage {
+
+        final int baseSize;
+        final Image resolutionVariant;
+
+        public MultiResolutionIconImage(int baseSize, Image resolutionVariant) {
+            this.baseSize = baseSize;
+            this.resolutionVariant = resolutionVariant;
+        }
+
+        @Override
+        public int getWidth(ImageObserver observer) {
+            return baseSize;
+        }
+
+        @Override
+        public int getHeight(ImageObserver observer) {
+            return baseSize;
+        }
+
+        @Override
+        protected Image getBaseImage() {
+            return resolutionVariant;
+        }
+
+        @Override
+        public Image getResolutionVariant(double width, double height) {
+            return resolutionVariant;
+        }
+
+        @Override
+        public List<Image> getResolutionVariants() {
+            return Arrays.asList(resolutionVariant);
+        }
+    }
 }
--- a/jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolderManager2.java	Fri Jun 10 13:05:49 2016 +0300
+++ b/jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolderManager2.java	Tue Jun 14 11:33:56 2016 +0300
@@ -27,6 +27,7 @@
 
 import java.awt.*;
 import java.awt.image.BufferedImage;
+import java.awt.image.BaseMultiResolutionImage;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -116,13 +117,21 @@
             return result;
         }
 
-        BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
-
-        img.setRGB(0, 0, 16, 16, Win32ShellFolder2.getStandardViewButton0(iconIndex), 0, 16);
+        final int[] iconBits = Win32ShellFolder2
+                .getStandardViewButton0(iconIndex, true);
+        if (iconBits != null) {
+            // icons are always square
+            final int size = (int) Math.sqrt(iconBits.length);
+            final BufferedImage img =
+                    new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
+            img.setRGB(0, 0, size, size, iconBits, 0, size);
 
-        STANDARD_VIEW_BUTTONS[iconIndex] = img;
+            STANDARD_VIEW_BUTTONS[iconIndex] = (size == 16)
+                    ? img
+                    : new MultiResolutionIconImage(16, img);
+        }
 
-        return img;
+        return STANDARD_VIEW_BUTTONS[iconIndex];
     }
 
     // Special folders
--- a/jdk/src/java.desktop/windows/native/libawt/windows/ShellFolder2.cpp	Fri Jun 10 13:05:49 2016 +0300
+++ b/jdk/src/java.desktop/windows/native/libawt/windows/ShellFolder2.cpp	Tue Jun 14 11:33:56 2016 +0300
@@ -930,19 +930,43 @@
 /*
  * Class:     sun_awt_shell_Win32ShellFolder2
  * Method:    getIconBits
- * Signature: (JI)[I
+ * Signature: (J)[I
  */
 JNIEXPORT jintArray JNICALL Java_sun_awt_shell_Win32ShellFolder2_getIconBits
-    (JNIEnv* env, jclass cls, jlong hicon, jint iconSize)
+    (JNIEnv* env, jclass cls, jlong hicon)
 {
+    const int MAX_ICON_SIZE = 128;
+    int iconSize = 0;
     jintArray iconBits = NULL;
 
+    BITMAP bmp;
+    memset(&bmp, 0, sizeof(BITMAP));
+
     // Get the icon info
     ICONINFO iconInfo;
     if (fn_GetIconInfo((HICON)hicon, &iconInfo)) {
         // Get the screen DC
         HDC dc = GetDC(NULL);
         if (dc != NULL) {
+            // find out the icon size in order to deal with different sizes
+            // delivered depending on HiDPI mode or SD DPI mode.
+            if (iconInfo.hbmColor) {
+                const int nWrittenBytes = GetObject(iconInfo.hbmColor, sizeof(bmp), &bmp);
+                if(nWrittenBytes > 0) {
+                    iconSize = bmp.bmWidth;
+                }
+            } else if (iconInfo.hbmMask) {
+                // Icon has no color plane, image data stored in mask
+                const int nWrittenBytes = GetObject(iconInfo.hbmMask, sizeof(bmp), &bmp);
+                if (nWrittenBytes > 0) {
+                    iconSize = bmp.bmWidth;
+                }
+            }
+            // limit iconSize to MAX_ICON_SIZE, so that the colorBits and maskBits
+            // arrays are big enough.
+            // (logic: rather show bad icons than overrun the array size)
+            iconSize = iconSize > MAX_ICON_SIZE ? MAX_ICON_SIZE : iconSize;
+
             // Set up BITMAPINFO
             BITMAPINFO bmi;
             memset(&bmi, 0, sizeof(BITMAPINFO));
@@ -954,7 +978,7 @@
             bmi.bmiHeader.biCompression = BI_RGB;
             // Extract the color bitmap
             int nBits = iconSize * iconSize;
-            long colorBits[1024];
+            long colorBits[MAX_ICON_SIZE * MAX_ICON_SIZE];
             GetDIBits(dc, iconInfo.hbmColor, 0, iconSize, colorBits, &bmi, DIB_RGB_COLORS);
             // XP supports alpha in some icons, and depending on device.
             // This should take precedence over the icon mask bits.
@@ -969,7 +993,7 @@
             }
             if (!hasAlpha) {
                 // Extract the mask bitmap
-                long maskBits[1024];
+                long maskBits[MAX_ICON_SIZE * MAX_ICON_SIZE];
                 GetDIBits(dc, iconInfo.hbmMask, 0, iconSize, maskBits, &bmi, DIB_RGB_COLORS);
                 // Copy the mask alphas into the color bits
                 for (int i = 0; i < nBits; i++) {
@@ -1001,10 +1025,10 @@
 /*
  * Class:     sun_awt_shell_Win32ShellFolder2
  * Method:    getStandardViewButton0
- * Signature: (I)[I
+ * Signature: (IZ)[I
  */
 JNIEXPORT jintArray JNICALL Java_sun_awt_shell_Win32ShellFolder2_getStandardViewButton0
-    (JNIEnv* env, jclass cls, jint iconIndex)
+    (JNIEnv* env, jclass cls, jint iconIndex, jboolean smallIcon)
 {
     jintArray result = NULL;
 
@@ -1014,7 +1038,8 @@
         NULL, NULL, NULL, NULL);
 
     if (hWndToolbar != NULL) {
-        SendMessage(hWndToolbar, TB_LOADIMAGES, (WPARAM)IDB_VIEW_SMALL_COLOR, (LPARAM)HINST_COMMCTRL);
+        WPARAM size = smallIcon ? (WPARAM)IDB_VIEW_SMALL_COLOR : (WPARAM)IDB_VIEW_LARGE_COLOR;
+        SendMessage(hWndToolbar, TB_LOADIMAGES, size, (LPARAM)HINST_COMMCTRL);
 
         HIMAGELIST hImageList = (HIMAGELIST) SendMessage(hWndToolbar, TB_GETIMAGELIST, 0, 0);
 
@@ -1022,7 +1047,7 @@
             HICON hIcon = ImageList_GetIcon(hImageList, iconIndex, ILD_TRANSPARENT);
 
             if (hIcon != NULL) {
-                result = Java_sun_awt_shell_Win32ShellFolder2_getIconBits(env, cls, ptr_to_jlong(hIcon), 16);
+                result = Java_sun_awt_shell_Win32ShellFolder2_getIconBits(env, cls, ptr_to_jlong(hIcon));
 
                 DestroyIcon(hIcon);
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/awt/shell/BadHiDPIIcon.java	Tue Jun 14 11:33:56 2016 +0300
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8151385
+ * @summary JOptionPane icons are cropped on Windows 10 with HiDPI display
+ * @author Hendrik Schreiber
+ * @requires os.family == "windows"
+ * @modules java.desktop/sun.awt.shell
+ * @run main BadHiDPIIcon
+ */
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.awt.image.MultiResolutionImage;
+import sun.awt.shell.ShellFolder;
+
+public class BadHiDPIIcon {
+
+    public static void main(String[] args) {
+        // the error icon is round and in all four corner transparent
+        // we check that all corners are identical
+        Image icon = (Image) ShellFolder.get("optionPaneIcon Error");
+        final BufferedImage image = getBufferedImage(icon);
+        final int upperLeft = image.getRGB(0, 0);
+        final int upperRight = image.getRGB(image.getWidth() - 1, 0);
+        final int lowerLeft = image.getRGB(0, image.getHeight() - 1);
+        final int lowerRight = image.getRGB(image.getWidth() - 1, image.getHeight() - 1);
+        if (upperLeft != upperRight || upperLeft != lowerLeft || upperLeft != lowerRight) {
+            throw new RuntimeException("optionPaneIcon Error is not a round icon with transparent background.");
+        }
+    }
+
+    private static BufferedImage getBufferedImage(Image image) {
+        if (image instanceof MultiResolutionImage) {
+            MultiResolutionImage mrImage = (MultiResolutionImage) image;
+            return (BufferedImage) mrImage.getResolutionVariant(32, 32);
+        }
+        return (BufferedImage) image;
+    }
+}