Можно ли дать приложениям AWT четкие значки на панели задач в Windows 10

Я пытаюсь установить значок приложения 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 - сообщение WM_SETICON с wParam, установленным в ICON_SMALL, работает нормально, но эквивалент с 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 application's icon vs. how it should look

Итак, мой вопрос: что я могу сделать в этом Java-приложении, чтобы Windows отображала значок, который я даю, на панели задач, не масштабируя и не размывая детали?

Ответ 1

Существует действительно функция масштабирования называется getScaledIconImage() в sun.awt.SunToolkit, который всегда используется при установке иконок. Вы должны обойти эту функцию, чтобы получить значок без печати. Так что вам нужна замена для метода java.awt.Window.setIconImages().

Предоставлено несколько изображений значков Icon16x16.png, Icon24x24.png и т.д. Это пример customSetIconImages() который помещает четкий значок 24x24 пикселей на панель задач Windows 10.

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() которая выполняет сглаживание или нет по вашему желанию.

И последнее, обратите внимание, что это очень грязный хак только для того, чтобы получить непроверенную иконку. Я тестировал только в Java 8 и Windows 10. И есть большие шансы, что он не будет работать в более новых версиях Java.

Ответ 2

Это не тот ответ, на который вы надеетесь, но это похоже на проблему на уровне JDK.

Значки окна обрабатываются классом sun.awt.windows.WWindowPeer, который, в свою очередь, выполняет несколько вызовов нативных методов, но в исходном коде этого достаточно, чтобы это sun.awt.windows.WWindowPeer на проблему. Пожалуйста, прочитайте важный бит здесь.

По сути, независимо от того, сколько размеров изображений предусмотрено, он выберет только два размера - для 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...

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

Как вы говорите, очевидно, что один из этих размеров изображения затем масштабируется для панели задач.