英文:
Save HCURSOR to BufferedImage in Java
问题
我需要将HCURSOR
以其实际大小和颜色存储在BufferedImage
中。
我已经找到了类似的问题1和2,它们可以在标准32x32光标上正常工作,但如果我改变颜色或大小,那么BufferedImage
将变为无效,给我一个如下的结果:
首先,我的问题是获取真实的光标大小。但后来我找到了通过JNA从注册表中获取的方法。
然后我需要将其保存到BufferedImage
中。我尝试使用上面第一个链接中的getImageByHICON()
和getIcon()
代码片段,但是某处出现了错误 - 图像仍然不正确或损坏。也许是因为我不太熟悉BufferedImage
的创建方式,所以我可能没有正确地使用它。
如果我有光标的真实大小和CURSORINFO
,我如何将HCURSOR
保存到BufferedImage
中?
这是我的完整代码:
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 Memory colorBitsMem = new Memory(nBits);
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);
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);
}
}
}
英文:
I need to store HCURSOR
in BufferedImage
with its real size and color.
I have found similar questions 1 and 2 which work fine with standard 32x32 cursor, but if I change color or size then BufferedImage becomes invalid, giving me a result like this:
Firstly, my problem was to get a real cursor size. But then I found the way to get it via JNA from the registry.
Then I need to save it to BufferedImage
. I tried to use code snippets getImageByHICON()
and getIcon()
from the first link above, but there's an error somewhere -- the image is still incorrect or broken. Maybe I don't understand how to use it correctly because I am not much familiar with BufferedImage
creation.
How can I save HCURSOR
to BufferedImage
if I have cursors real size and CURSORINFO
?
Here is my full code:
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);
}
}
}
答案1
得分: 1
以下是翻译好的内容:
原本我回答了这个问题,建议您使用 GetSystemMetrics() 函数,使用常量 SM_CXCURSOR
(13)表示光标的宽度(以像素为单位),以及 SM_CYCURSOR
(14)表示高度。链接的文档说明了:“系统无法创建其他尺寸的光标。”
但是后来我看到您在这里发表了类似的问题,并且表示这些值从32x32不会改变。正如这个答案中所指出的,在那种情况下,光标实际上仍然是那个尺寸,但屏幕上只显示较小的图像;其余的像素实际上是“不可见的”。对于“较大”的图像也是如此,内部上与光标相关联的“图标”仍然是相同的32x32大小,但屏幕显示的是其他内容。
有趣的是,在悬停在Swing窗口上时,图标始终为32x32。您选择使用 Cursor c = toolkit.createCustomCursor(image, pointerPos, "cursorname");
是将位图图像缩小为窗口中的新(较小)光标。我可以通过以下方式保持默认光标:
Cursor c = Cursor.getDefaultCursor();
我对您的代码进行了以下更改,以获取正确尺寸的丑陋像素化版本:
- 将方法参数
width
和height
改为w
和h
:getImageByHICON(final int w, final int h, final WinDef.HICON hicon)
- 在try块的开头,设置
int width = 32
和int height = 32
。 - 在从
GetDIBits()
获取colorBitsMem
后插入以下内容:
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];
}
}
因此,对于一个64x64的系统图标,在Swing窗口中看到的是这样的:
这个尺寸与我的鼠标光标相匹配。但像素则不太匹配。
另一个选项,受到这个答案的启发,是使用比我的简单整数像素数学更好的位图缩放。在您的 getCursor()
方法中,进行以下操作:
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;
这给了我这个效果:
还有另一个选项,在您的 getCursor()
类中是 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()));
这给了我这个效果:
英文:
I originally answered this question suggesting that you use the GetSystemMetrics() function, using the constant SM_CXCURSOR
(13) for the width of the cursor in pixels, and SM_CYCURSOR
(14) for the height. The linked documentation states "The system cannot create cursors of other sizes."
But then I see you posted a similar question here, and stated that those values don't change from 32x32. What happens there, as noted in this answer, is that the cursor is still actually that size, but only the smaller image is displayed on the screen; the rest of the pixels are simply "invisible". The same appears to be true for "larger" images, in that internally the "icon" associated with the cursor is still the same 32x32 size, but the screen displays something else.
Interestingly, the icon when hovering over the Swing window is always 32x32. Your choice to use Cursor c = toolkit.createCustomCursor(image, pointerPos, "cursorname");
is scaling down the bitmap image to a new (smaller) cursor in the window. I can keep the default cursor with a simple:
Cursor c = Cursor.getDefaultCursor();
I made the following changes to your code to get an ugly pixellated version at the right size:
- changed method arguments
width
andheight
tow
andh
:getImageByHICON(final int w, final int h, final WinDef.HICON hicon)
- at the start of the try block, set
int width = 32
andint height = 32
. - after fetching the
colorBitsMem
fromGetDIBits()
inserted the following:
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];
}
}
So with a 64x64 system icon, I see this in the swing window:
That size matches my mouse cursor. The pixels, notsomuch.
Another option, inspired by this answer is to use a better bitmap scaling than my simple integer math with pixels. In your getCursor()
method, do:
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;
Which is giving me this:
Yet another option, in your getCursor()
class is 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()));
Gives this:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论