8151385: [hidpi] JOptionPane-Icons only partially visible when using Windows 10 L&F
Reviewed-by: serb, alexsch
--- 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;
+ }
+}