Guardar HCURSOR en BufferedImage en Java
Necesito almacenar HCURSOR
en BufferedImage
su tamaño y color verdadero .
Encontré preguntas similares 1 y 2 que funcionan bien con el cursor estándar de 32x32, pero si cambio el color o el tamaño, BufferedImage se vuelve inválido y me da un resultado como este:

En primer lugar, mi problema era obtener un tamaño de cursor real. Pero luego encontré la manera de obtenerlo a través de JNA desde el registro.
Entonces necesito guardarlo en BufferedImage
. Traté de usar fragmentos de código getImageByHICON()
y getIcon()
del primer enlace anterior, pero hay un error en alguna parte: la imagen aún es incorrecta o está rota. Quizás no entiendo cómo usarlo correctamente porque no estoy muy familiarizado con la BufferedImage
creación.
¿Cómo puedo guardar HCURSOR
para BufferedImage
si tengo cursores de tamaño real y CURSORINFO
?
Aquí está mi código completo:
import com.sun.jna.Memory;
import com.sun.jna.platform.win32.*;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
class CursorExtractor {
public static void main(String[] args) {
BufferedImage image = getCursor();
JLabel icon = new JLabel();
icon.setIcon(new ImageIcon(image));
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(icon);
frame.pack();
frame.setVisible(true);
Toolkit toolkit = Toolkit.getDefaultToolkit();
Point pointerPos = new Point(1, 1);
Cursor c = toolkit.createCustomCursor(image, pointerPos, "cursorname");
frame.setCursor(c);
}
public static BufferedImage getCursor() {
// Read an int (& 0xFFFFFFFFL for large unsigned int)
int baseSize = Advapi32Util.registryGetIntValue(
WinReg.HKEY_CURRENT_USER, "Control Panel\\Cursors", "CursorBaseSize");
final User32.CURSORINFO cursorinfo = new User32.CURSORINFO();
User32.INSTANCE.GetCursorInfo(cursorinfo);
WinDef.HCURSOR hCursor = cursorinfo.hCursor;
return getImageByHICON(baseSize, baseSize, hCursor);
}
public static BufferedImage getImageByHICON(final int width, final int height, final WinDef.HICON hicon) {
final WinGDI.ICONINFO iconinfo = new WinGDI.ICONINFO();
try {
// get icon information
if (!User32.INSTANCE.GetIconInfo(hicon, iconinfo)) {
return null;
}
final WinDef.HWND hwdn = new WinDef.HWND();
final WinDef.HDC dc = User32.INSTANCE.GetDC(hwdn);
if (dc == null) {
return null;
}
try {
final int nBits = width * height * 4;
// final BitmapInfo bmi = new BitmapInfo(1);
final Memory colorBitsMem = new Memory(nBits);
// // Extract the color bitmap
final WinGDI.BITMAPINFO bmi = new WinGDI.BITMAPINFO();
bmi.bmiHeader.biWidth = width;
bmi.bmiHeader.biHeight = -height;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = WinGDI.BI_RGB;
GDI32.INSTANCE.GetDIBits(dc, iconinfo.hbmColor, 0, height, colorBitsMem, bmi, WinGDI.DIB_RGB_COLORS);
// g32.GetDIBits(dc, iconinfo.hbmColor, 0, size, colorBitsMem,
// bmi,
// GDI32.DIB_RGB_COLORS);
final int[] colorBits = colorBitsMem.getIntArray(0, width * height);
final BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
bi.setRGB(0, 0, width, height, colorBits, 0, height);
return bi;
} finally {
com.sun.jna.platform.win32.User32.INSTANCE.ReleaseDC(hwdn, dc);
}
} finally {
User32.INSTANCE.DestroyIcon(new WinDef.HICON(hicon.getPointer()));
GDI32.INSTANCE.DeleteObject(iconinfo.hbmColor);
GDI32.INSTANCE.DeleteObject(iconinfo.hbmMask);
}
}
}
Respuestas
Originalmente respondí esta pregunta sugiriendo que use la función GetSystemMetrics () , usando la constante SM_CXCURSOR
(13) para el ancho del cursor en píxeles y SM_CYCURSOR
(14) para la altura. La documentación vinculada dice "El sistema no puede crear cursores de otros tamaños".
Pero luego veo que publicó una pregunta similar aquí y declaró que esos valores no cambian de 32x32. Lo que sucede allí, como se indica en esta respuesta , es que el cursor todavía tiene ese tamaño, pero solo la imagen más pequeña se muestra en la pantalla; el resto de los píxeles son simplemente "invisibles". Lo mismo parece ser cierto para las imágenes "más grandes", ya que internamente el "icono" asociado con el cursor sigue teniendo el mismo tamaño de 32x32, pero la pantalla muestra algo más.
Curiosamente, el icono al pasar el cursor sobre la ventana Swing es siempre de 32x32. Su opción de uso Cursor c = toolkit.createCustomCursor(image, pointerPos, "cursorname");
es reducir la imagen de mapa de bits a un cursor nuevo (más pequeño) en la ventana. Puedo mantener el cursor predeterminado con un simple:
Cursor c = Cursor.getDefaultCursor();
Hice los siguientes cambios en su código para obtener una versión pixelada fea en el tamaño correcto:
- cambiado los argumentos del método
width
yheight
dew
yh
:getImageByHICON(final int w, final int h, final WinDef.HICON hicon)
- al comienzo del bloque try, configure
int width = 32
yint height = 32
. - después de obtener el
colorBitsMem
deGetDIBits()
insertó lo siguiente:
final int[] colorBitsBase = colorBitsMem.getIntArray(0, width * height);
final int[] colorBits = new int[w * h];
for (int row = 0; row < h; row++) {
for (int col = 0; col < w; col++) {
int r = row * 32 / h;
int c = col * 32 / w;
colorBits[row * w + col] = colorBitsBase[r * 32 + c];
}
}
Entonces, con un ícono de sistema de 64x64, veo esto en la ventana giratoria:

Ese tamaño coincide con el cursor de mi mouse. Los píxeles, no tanto.
Otra opción, inspirada en esta respuesta, es usar una mejor escala de mapa de bits que mi simple matemática entera con píxeles. En su getCursor()
método, haga:
BufferedImage before = getImageByHICON(32, 32, hCursor);
int w = before.getWidth();
int h = before.getHeight();
BufferedImage after = new BufferedImage(baseSize, baseSize, BufferedImage.TYPE_INT_ARGB);
AffineTransform at = new AffineTransform();
at.scale(baseSize / 32d, baseSize / 32d);
AffineTransformOp scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
after = scaleOp.filter(before, after);
return after;
Lo que me está dando esto:

Otra opción más, en su getCursor()
clase es CopyImage () .
WinDef.HCURSOR hCursor = cursorinfo.hCursor;
HANDLE foo = User32.INSTANCE.CopyImage(hCursor, 2, baseSize, baseSize, 0x00004000);
return getImageByHICON(baseSize, baseSize, new WinDef.HCURSOR(foo.getPointer()));
Da esto:
