ADDITIONAL SYSTEM INFORMATION :
Windows 10, Java 15 EA Build 6
A DESCRIPTION OF THE PROBLEM :
The described problems occur in the following multi-screen setup (and probably on similar ones):
The primary screen is a HiDPI screen and has Windows scaling set to 200%. (In principle, also a screen with regular physical resolution will reproduce the problem, just imagine the results scaled reciprocally.)
The secondary screen has Windows scaling set to 100%.
This bug was found while developing JOSM, the Java OpenStreetMap editor.
Please add the "josm-found" label.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the Java application provided as source code below. Drag the window that it opens to either screen and hover the mouse over it.
The application passes a BaseMultiResolutionImage to Toolkit.createCustomCursor, with the first image having 32x32 pixels (low-res), and the second image having 64x64 pixels (hi-res). Passing a MultiResolutionImage is suggested in bug JDK-8139152, and ordering the images by size increasing is suggested in https://docs.oracle.com/javase/9/docs/api/java/awt/image/BaseMultiResolutionImage.html
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
On both screens, the cursor has about the same (real-world) size, consisting of 64x64 pixels on the HiDPI screen, and 32x32 pixels on the regular screen.
The alpha blending of the background and the lines, in the transparent right half of the cursor, is smooth.
ACTUAL -
On the secondary screen, the cursor is about twice as large as on the primary screen, consisting of 64x64 pixels on both.
The lines in the transparent right half of the cursor are jaggy, black pixels surround the blue lines. So alpha blending is not smooth.
The erroneous alpha blending might be caused by this source code:
https://github.com/openjdk/jdk/blob/6bab0f539fba8fb441697846347597b4a0ade428/src/java.desktop/share/classes/sun/awt/CustomCursor.java
It constructs the alpha mask from variable "cursor", for which getScaledInstance will default to the first, low-res image. However, it passes on variable "image" to createNativeCursor, which reference the original "cursor", i.e. the BaseMultiResolutionImage in this case. createNativeCursor then seems to choose the HiDPI image (which is good at least for the primary screen).
Generally, the code mentioned above is hard to read. Initially, "image" and "cursor" reference the same object, but depending on conditions, they reference different objects later. One is scaled, the other is not. The hotspot is never scaled accordingly, causing bug JDK-8238734.
---------- BEGIN SOURCE ----------
import java.awt.*;
import java.awt.image.BaseMultiResolutionImage;
import java.awt.image.BufferedImage;
public class CustomCursorResolution {
public static void main(String[] args)
{
Dimension bestCursorSize = Toolkit.getDefaultToolkit().getBestCursorSize(32, 32);
assert bestCursorSize.width == bestCursorSize.height; // effectively 64 for systems with (also) a HiDPI screen
final int cursorSize = bestCursorSize.width;
// construct blue diagonal cross with a frame, in two resolutions
BufferedImage bufferedImageLowRes = createCursorImage(cursorSize / 2);
BufferedImage bufferedImageHighRes = createCursorImage(cursorSize);
Image image;
image = new BaseMultiResolutionImage(bufferedImageLowRes, bufferedImageHighRes); // triggers bug
//image = bufferedImageHighRes; // workaround avoiding low res alpha mask
Frame frame = new Frame();
Cursor cursor;
cursor = Toolkit.getDefaultToolkit().createCustomCursor(
image, new Point(cursorSize / 2, cursorSize / 2), null);
frame.setCursor(cursor);
frame.setSize(new Dimension(500, 500));
frame.setBackground(Color.LIGHT_GRAY);
frame.setVisible(true);
}
private static BufferedImage createCursorImage(int cursorSize) {
BufferedImage bufferedImage = new BufferedImage(cursorSize, cursorSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D bg = bufferedImage.createGraphics();
bg.setBackground(new Color(1.0f, 1.0f, 1.0f, 0.0f));
bg.clearRect(0, 0, cursorSize, cursorSize);
bg.setColor(Color.WHITE);
bg.fillRect(0, 0, cursorSize / 2, cursorSize); // white background in the left half, transparent background in right half
bg.setColor(Color.BLUE);
bg.drawRect(0, 0, cursorSize - 1, cursorSize - 1);
bg.setStroke(new BasicStroke(2));
bg.drawLine(0, 0, cursorSize, cursorSize);
bg.drawLine(0, cursorSize, cursorSize, 0);
return bufferedImage;
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Passing only a 64x64 pixel simple Image (uncomment respective line in the sample source code), or making the HiDPI Image the first one of the BaseMultiResolutionImage, fixes the alpha blending problem.
However, the problem remains that the cursor is double size on the regular screen.
Predefined cursors as return by e.g. Cursor.getPredefinedCursor(Cursor.HAND_CURSOR) work perfectly.
FREQUENCY : always