【问题标题】:Is it possible to give AWT applications sharp taskbar icons in Windows 10是否可以在 Windows 10 中为 AWT 应用程序提供清晰的任务栏图标
【发布时间】:2019-08-06 21:23:35
【问题描述】:

我正在尝试设置 Java AWT 应用程序的图标,以便它在 Windows 10 任务栏上以本机分辨率呈现(包括当桌面缩放设置为 100% 以上时)。似乎默认情况下,如果一个可执行文件嵌入了一个包含多个大小的图标,Windows 似乎会选择一个大于任务栏图标实际大小的大小并缩小它(在 100% 缩放时,它会将 32 像素图标的大小调整为 24,即使提供了一个 24 像素的图标,对于其他比例也是如此。)

我已经为 C++ MFC 应用程序解决了这个问题,方法是仅加载正确大小的图标作为资源并向窗口发送 WM_SETICON 消息,这会在任务栏和 alt-tab 对话框上产生一个漂亮的清晰图标。

smallIcon = (HICON)LoadImage( myInstance, MAKEINTRESOURCE(smallIconRes), IMAGE_ICON, smallIconSize, smallIconSize, LR_DEFAULTCOLOR );
SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)smallIcon);

bigIcon   = (HICON)LoadImage( myInstance, MAKEINTRESOURCE(bigIconRes),   IMAGE_ICON, bigIconSize,   bigIconSize,   LR_DEFAULTCOLOR );
SendMessage(hWnd, WM_SETICON, ICON_BIG,   (LPARAM)bigIcon); 

这种方法似乎不适用于 Java 应用程序 - wParam 设置为 ICON_SMALL 的 WM_SETICON 消息可以正常工作,但忽略 ICON_BIG 的等效消息。

如果我尝试使用 Java 的 API 来设置图标,通过这样做

    List<Image> icons = new ArrayList<Image>();
    icons.add(windowIcons.getIcon(20)); // small icons are 20x20 pixels
    icons.add(windowIcons.getIcon(30)); // large are 30x30 at 125% scale
    setIconImages(icons);

使用了正确的图标,但它看起来很模糊,好像有什么东西把它调整到了“预期”的大小,然后又重新调整了大小。左边是它的外观,右边是图标文件的内容。

所以,我的问题是:我可以在这个 Java 应用程序中做些什么来让 Windows 在任务栏上呈现我给它的图标而不缩放它并模糊细节?

【问题讨论】:

标签: java windows-10 icons awt


【解决方案1】:

这不是您希望的答案,但这看起来像是 JDK 级别的问题。

窗口图标由sun.awt.windows.WWindowPeer 类处理,该类又进行了一些本机方法调用,但在源代码中可以看到足以指出问题所在。请阅读重要的一点here

基本上,无论提供多少图标图像尺寸,它只会挑选出两种尺寸——WWindowPeer.getSysIconWidth()getSysSmIconWidth()——传递给原生的setIconImagesData() 方法。

getSysIconWidth()getSysSmIconWidth() 方法也是原生的,但是可以直接查看它们的返回值:

JFrame frame = new JFrame();
runOnPeer(frame, "getSysIconWidth");
runOnPeer(frame, "getSysIconHeight");
runOnPeer(frame, "getSysSmIconWidth");
runOnPeer(frame, "getSysSmIconHeight");

private void runOnPeer(JFrame frame, String methodName) {

    //JDK8 style
    //ComponentPeer peer = frame.getPeer();

    //JDK11 style
    Field peerField = Component.class.getDeclaredField("peer");
    peerField.setAccessible(true);
    Object peer = peerField.get(frame);

    Method method = Class.forName("sun.awt.windows.WWindowPeer")
            .getDeclaredMethod(methodName);
    method.setAccessible(true);
    System.out.println(methodName + "()=" + method.invoke(peer));
}

... 在 Windows 10 上返回 this ...

getSysIconWidth()=32
getSysIconHeight()=32
getSysSmIconWidth()=16
getSysSmIconHeight()=16

正如您所说,显然这些图像尺寸之一正在为任务栏缩放。

【讨论】:

    【解决方案2】:

    sun.awt.SunToolkit 中确实有一个叫做getScaledIconImage() 的缩放功能,在设置图标时总是使用它。您必须绕过此功能才能获得无锯齿图标。因此,您需要替换 java.awt.Window.setIconImages() 方法。

    提供了几个图标图像 Icon16x16.png、Icon24x24.png 等。这是 customSetIconImages() 的示例,它在 Windows 10 的任务栏中放置了一个清晰的 24x24 像素图标。

    import java.awt.*;
    import java.awt.event.*;
    import java.awt.image.*;
    import javax.swing.ImageIcon;
    import java.awt.peer.WindowPeer;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Iterator;
    
    @SuppressWarnings("serial")
    public class MyFrame extends Frame implements WindowListener {
    
        final Image i16, i24, i32, i48;
    
        MyFrame() throws Exception {
    
            i16 = Toolkit.getDefaultToolkit().getImage("Icon16x16.png");
            i24 = Toolkit.getDefaultToolkit().getImage("Icon24x24.png");
            i32 = Toolkit.getDefaultToolkit().getImage("Icon32x32.png");
            i48 = Toolkit.getDefaultToolkit().getImage("Icon48x48.png");
    
            addWindowListener(this);
            setSize(500,300);
            setTitle("Unaliased icon example");
            setLayout(new FlowLayout());
            setVisible(true);
        }
    
        public synchronized void customSetIconImages(java.util.List<Image> icons) throws Exception {
            Field windowIcons = Class.forName("java.awt.Window").getDeclaredField("icons");
            windowIcons.setAccessible(true);
            windowIcons.set(this, new ArrayList<Image>(icons));
    
            if (getPeer() != null)
                updateIconImages(i24, 24, 24, i24, 24, 24);
    
            firePropertyChange("iconImage", null, null);
        }
    
        public void updateIconImages(Image big, int bw, int bh, Image small, int sw, int sh) throws Exception {
            DataBufferInt iconData = getUnscaledIconData(big, bw, bh);
            DataBufferInt iconSmData = getUnscaledIconData(small, sw, sh);
    
            WindowPeer peer = (WindowPeer) getPeer();
            Method setIconImagesData = Class.forName("sun.awt.windows.WWindowPeer").getDeclaredMethod("setIconImagesData", int[].class, int.class, int.class, int[].class, int.class, int.class);
            setIconImagesData.setAccessible(true);
            setIconImagesData.invoke(peer, iconData.getData(), bw, bh, iconSmData.getData(), sw, sh);
        }
    
        public static DataBufferInt getUnscaledIconData(Image image, int w, int h) {
            Image temporary = new ImageIcon(image).getImage();
            BufferedImage buffImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = buffImage.createGraphics();
            g2d.drawImage(temporary, 0, 0, null);
            g2d.dispose();
            Raster raster = buffImage.getRaster();
            DataBuffer buffer = raster.getDataBuffer();
            return (DataBufferInt) buffer;
        }
    
        @Override
        public void windowOpened(WindowEvent arg0) {
            try {
                customSetIconImages(Arrays.asList(i24));
            } catch (Exception e) {
                System.err.println(e.getClass().getName()+" "+e.getMessage());
            }
        }
    
        @Override
        public void windowActivated(WindowEvent arg0) {
        }
    
        @Override
        public void windowClosed(WindowEvent arg0) {
        }
    
        @Override
        public void windowClosing(WindowEvent arg0) {
            dispose();
        }
    
        @Override
        public void windowDeactivated(WindowEvent arg0) {
        }
    
        @Override
        public void windowDeiconified(WindowEvent arg0) {
        }
    
        @Override
        public void windowIconified(WindowEvent arg0) {
        }
    
        public static void main(String args[]) throws Exception {
            MyFrame fr = new MyFrame();
        }
    }
    

    正如@df778899 所说,在 sun.awt.windows.WWindowPeer 内部有四个私有本机方法,您可以调用它们来确定系统图标的大小。您可以将这些方法返回的信息与您自己的版本getScaledIconImage() 结合起来,根据您的意愿执行或不执行反锯齿。

    最后,请注意,这是一个非常肮脏的 hack,仅用于获取无别名图标。我只在 Java 8 和 Windows 10 中进行了测试。而且很有可能它在 Java 的较新版本中不起作用。

    【讨论】:

    • 感谢 Serg - 这正是我想要的,我已经尝试过了,效果很好!一个问题:customSetIconImages 函数的第一部分似乎是以通用方式设置窗口图标,没有它您的代码也可以正常工作。对于无法使用 updateIconImages 的情况(例如,如果 getPeer 不可用),它是一种后备方式,还是它是代码的重要部分,而我只是没有看到需要它的情况?
    • java.awt.Window 中的标准 setIconImages() 方法设置内部 List 图标。我还没有深入挖掘 Sun 的代码来弄清楚它这样做的原因。因此,在编写替代方法时,我决定也初始化图标列表,以防万一......
    • 请注意,JDK 选择 2 个图像并没有错,Windows 只允许使用“大”和“小”图标(请参阅 WM_SETICON)并且 JDK 选择关闭尺寸,如果出现缩放图像它们的尺寸不合适。问题似乎出在 JDK 确定正确大小的位置,它使用 DPI 对其进行调整。一旦该大小错误,它会为 Windows 提供错误大小的图标,并且 Windows 会缩放它,这看起来很糟糕。例如,在 150% DPI 时会发生这种情况。 github.com/openjdk/jdk/blob/…
    • 另外注意,加载和设置图标的正确方法不涉及应用任何 DPI:blog.barthe.ph/2009/07/17/wmseticon
    • 原始发布者知道他想要一个未缩放的 24x24 图标。但是我怎么能在运行时确定特定用户工具栏的最佳图标大小,无论他们有什么显示设置?
    猜你喜欢
    • 2023-03-17
    • 2017-02-15
    • 1970-01-01
    • 2011-02-12
    • 2012-07-13
    • 2016-04-09
    • 2012-05-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多