在Java中将HCURSOR保存到BufferedImage

3
我需要在BufferedImage中存储真实大小和颜色的HCURSOR

我已经找到了类似的问题12,可以处理标准32x32的光标,但如果我改变颜色或大小,则BufferedImage将无效,给出如下结果:

enter image description here

首先,我的问题是获取真实的光标大小。但是我通过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 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();

我对您的代码进行了以下更改,以获得正确大小的丑陋像素化版本:

  • 将方法参数widthheight更改为whgetImageByHICON(final int w, final int h, final WinDef.HICON hicon)
  • 在try块的开头设置int width = 32int 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窗口中会看到如下内容:

image of icon

这个尺寸与我的鼠标光标匹配,但像素不太匹配。

另一个选项是受this answer启发,使用更好的位图缩放而非简单的整数像素计算。在你的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;

这给了我这个:

enter image description here

在您的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()));

给出这个:

image of icon


但是我的问题出现在当我的光标比32x32大时(例如256x256或128x128),我该如何处理这种情况? - BaLiK
我已经通过JNA从注册表中获取了真实的光标大小。它存储在baseSize变量中。 - BaLiK
添加了另一个选项,对位图进行缩放。我认为这就是我能做的全部了。很抱歉我不能提供更多帮助。 - Daniel Widdis
找到另一个选项了!User32.INSTANCE.CopyImage(hCursor, 2, baseSize, baseSize, 0x00000040);...可能有些用处。 - Daniel Widdis
其实这可能会很有用。我已经通过cpp创建了一个类似的沙盒。当我绘制一个大光标时,它就像真的一样。https://gist.github.com/BaLiKfromUA/669c4e6ba3fffdf7db6dfad535976d97 - BaLiK
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接