Looks like Oracle has finally taken notice of this and will fix it in JDK 9, but for the time being here's a qiuck, hacky and Windows dependent solution for those not willing to provide custom icons.
Insert this code after your call to UIManager.setLookAndFeel()
:
try
{
String[][] icons =
{
{"OptionPane.errorIcon", "65581"},
{"OptionPane.warningIcon", "65577"},
{"OptionPane.questionIcon", "65579"},
{"OptionPane.informationIcon", "65583"}
};
//obtain a method for creating proper icons
Method getIconBits = Class.forName("sun.awt.shell.Win32ShellFolder2").getDeclaredMethod("getIconBits", new Class[]{long.class, int.class});
getIconBits.setAccessible(true);
//calculate scaling factor
double dpiScalingFactor = Toolkit.getDefaultToolkit().getScreenResolution() / 96.0;
int icon32Size = (dpiScalingFactor == 1)?(32):((dpiScalingFactor == 1.25)?(40):((dpiScalingFactor == 1.5)?(45):((int) (32 * dpiScalingFactor))));
Object[] arguments = {null, icon32Size};
for (String[] s:icons)
{
if (UIManager.get(s[0]) instanceof ImageIcon)
{
arguments[0] = Long.valueOf(s[1]);
//this method is static, so the first argument can be null
int[] iconBits = (int[]) getIconBits.invoke(null, arguments);
if (iconBits != null)
{
//create an image from the obtained array
BufferedImage img = new BufferedImage(icon32Size, icon32Size, BufferedImage.TYPE_INT_ARGB);
img.setRGB(0, 0, icon32Size, icon32Size, iconBits, 0, icon32Size);
ImageIcon newIcon = new ImageIcon(img);
//override previous icon with the new one
UIManager.put(s[0], newIcon);
}
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
Why this works
On Windows, icons provided to applications are already scaled by the current DPI setting (96 = no scaling, 120 = +25%, 144 = +50%).
Unfortunatelly, Java always assumes that icons are of size either 16x16 or 32x32, which is not the case.
The code above utilizes a native method sun.awt.shell.Win32ShellFolder2.getIconBits()
used by Java to obtain OS icons, but provides appropriate sizes.
This approach may become unusable in the future, as Win32ShellFolder2
is not part of the API and may be modified or removed entirely, but so far this is the only approach preserving the native icons.