Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to give AWT applications sharp taskbar icons in Windows 10

I'm trying to set the icon of a Java AWT application so it renders in native resolution on the Windows 10 taskbar (including when desktop scaling is set above 100%). It seems that by default, if an executable embeds an icon containing multiple sizes, Windows seems to pick a size larger than the actual size of taskbar icons and downsize it (at 100% scale it resizes the 32 pixel icon to 24, even if a 24 pixel icon is supplied, and similarly for other scales.)

I've solved this problem for C++ MFC applications by loading just the correctly sized icon as a resource and sending a WM_SETICON message to the window, which results in a nice sharp icon on the taskbar and alt-tab dialog.

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); 

That approach doesn't seem to work for Java applications - a WM_SETICON message with wParam set to ICON_SMALL works fine, but the equivalent with ICON_BIG is ignored.

If I try to use Java's API to set the icon, by doing this

    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);

the correct icon is used but it appears blurry, as if something has resized it to the "expected" size and then resized it back. Left here is how it appears, right is the contents of the icon file.

Java application's icon vs. how it should look

So, my question is: what can I do in this Java application to make Windows render the icon I give it on the taskbar without scaling it and blurring the details?

like image 414
MLdeS Avatar asked Mar 15 '19 18:03

MLdeS


People also ask

Can you change icons on taskbar Windows 10?

To change how icons and notifications appearPress and hold or right-click any empty space on the taskbar and select Taskbar settings. Under Taskbar corner icons: Select On for any icons you want to see on the taskbar. Select Off for any icons you don't want to see on the taskbar.


2 Answers

There is indeed a scaling function called getScaledIconImage() in sun.awt.SunToolkit which is is always used when setting the icons. You must bypass this function in order to get an unaliased icon. So what you need is a replacement for java.awt.Window.setIconImages() method.

Provided several icon images Icon16x16.png, Icon24x24.png, etc. This is an example of a customSetIconImages() which puts a crisp 24x24 pixels icon in the taskbar of 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();
    }
}

As @df778899 said, inside sun.awt.windows.WWindowPeer there are four private native methods which you can call t determine system icons size. You can combine the information returned by these methods with your own version getScaledIconImage() that performs unaliasing or not as yoou wish.

Last, note that this is a very dirty hack just for getting an unaliased icon. I've only tested in in Java 8 and Windows 10. And there are high chances that it doesn't work in newer versions of Java.

like image 143
Serg M Ten Avatar answered Sep 27 '22 21:09

Serg M Ten


This won't be the answer you're hoping for, but this looks like a problem at the JDK level.

The window icons are handled by the sun.awt.windows.WWindowPeer class, which in turn makes a few native method calls, but there is enough to see in the source for this to point to the problem. Please read the important bit here.

Essentially, regardless of how many icon image sizes are provided, it will only pick out two sizes - for the WWindowPeer.getSysIconWidth() and getSysSmIconWidth() - to pass into the native setIconImagesData() method.

The getSysIconWidth() and getSysSmIconWidth() methods are also native, but it is possible to directly check their return values:

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));
}

... which returns this on Windows 10 ...

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

As you say, clearly one of these image sizes is then being scaled for the taskbar.

like image 24
df778899 Avatar answered Sep 27 '22 22:09

df778899