From 6d82cbce2afaa67be62e6c4d50204fe0eaa88699 Mon Sep 17 00:00:00 2001 From: Hendrik Schreiber Date: Tue, 14 Jun 2016 11:33:56 +0300 Subject: [PATCH] 8151385: [hidpi] JOptionPane-Icons only partially visible when using Windows 10 L&F Reviewed-by: serb, alexsch --- .../sun/awt/shell/Win32ShellFolder2.java | 55 +++++++++++++++-- .../awt/shell/Win32ShellFolderManager2.java | 19 ++++-- .../native/libawt/windows/ShellFolder2.cpp | 41 ++++++++++--- jdk/test/sun/awt/shell/BadHiDPIIcon.java | 61 +++++++++++++++++++ 4 files changed, 157 insertions(+), 19 deletions(-) create mode 100644 jdk/test/sun/awt/shell/BadHiDPIIcon.java diff --git a/jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolder2.java b/jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolder2.java index dab4129ade5..b2e62b41d27 100644 --- a/jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolder2.java +++ b/jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolder2.java @@ -27,7 +27,9 @@ package sun.awt.shell; 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 @@ final class Win32ShellFolder2 extends ShellFolder { // 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 @@ final class Win32ShellFolder2 extends ShellFolder { 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 @@ final class Win32ShellFolder2 extends ShellFolder { }); } + 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 getResolutionVariants() { + return Arrays.asList(resolutionVariant); + } + } } diff --git a/jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolderManager2.java b/jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolderManager2.java index 1e20c3582f7..ffee0ac7ae6 100644 --- a/jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolderManager2.java +++ b/jdk/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolderManager2.java @@ -27,6 +27,7 @@ package sun.awt.shell; 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 @@ final class Win32ShellFolderManager2 extends ShellFolderManager { return result; } - BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + 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); - img.setRGB(0, 0, 16, 16, Win32ShellFolder2.getStandardViewButton0(iconIndex), 0, 16); + STANDARD_VIEW_BUTTONS[iconIndex] = (size == 16) + ? img + : new MultiResolutionIconImage(16, img); + } - STANDARD_VIEW_BUTTONS[iconIndex] = img; - - return img; + return STANDARD_VIEW_BUTTONS[iconIndex]; } // Special folders diff --git a/jdk/src/java.desktop/windows/native/libawt/windows/ShellFolder2.cpp b/jdk/src/java.desktop/windows/native/libawt/windows/ShellFolder2.cpp index 05273d41bad..7ffdb5d7e52 100644 --- a/jdk/src/java.desktop/windows/native/libawt/windows/ShellFolder2.cpp +++ b/jdk/src/java.desktop/windows/native/libawt/windows/ShellFolder2.cpp @@ -930,19 +930,43 @@ JNIEXPORT void JNICALL Java_sun_awt_shell_Win32ShellFolder2_disposeIcon /* * 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 @@ JNIEXPORT jintArray JNICALL Java_sun_awt_shell_Win32ShellFolder2_getIconBits 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 @@ JNIEXPORT jintArray JNICALL Java_sun_awt_shell_Win32ShellFolder2_getIconBits } 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 @@ JNIEXPORT jintArray JNICALL Java_sun_awt_shell_Win32ShellFolder2_getIconBits /* * 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 @@ JNIEXPORT jintArray JNICALL Java_sun_awt_shell_Win32ShellFolder2_getStandardView 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 @@ JNIEXPORT jintArray JNICALL Java_sun_awt_shell_Win32ShellFolder2_getStandardView 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); } diff --git a/jdk/test/sun/awt/shell/BadHiDPIIcon.java b/jdk/test/sun/awt/shell/BadHiDPIIcon.java new file mode 100644 index 00000000000..b0a276b3aa8 --- /dev/null +++ b/jdk/test/sun/awt/shell/BadHiDPIIcon.java @@ -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; + } +}