用Java将HCURSOR保存到BufferedImage

问题描述

我需要将HCURSOR存储在BufferedImage中,并保留其真实大小和颜色。

我发现类似的问题12在标准的32x32光标上也能正常工作,但是如果我更改颜色或大小,则BufferedImage无效,结果如下:

enter image description here

首先,我的问题是获取实际的光标大小。但是后来我找到了从注册表中通过JNA获取它的方法。

然后我需要将其保存到BufferedImage。我尝试使用上面第一个链接中的代码片段getImageByHICON()getIcon(),但是某处存在错误-图像仍然不正确或损坏。也许我不了解如何正确使用它,因为我对BufferedImage的创建不是很熟悉。

如果我有光标的实际大小和HCURSOR,如何将BufferedImage保存到CURSORINFO

这是我的完整代码:

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,height,colorBitsMem,bmi,WinGDI.DIB_RGB_COLORS);
                // g32.GetDIBits(dc,size,// bmi,// GDI32.DIB_RGB_COLORS);
                final int[] colorBits = colorBitsMem.getIntArray(0,width * height);

                final BufferedImage bi = new BufferedImage(width,BufferedImage.TYPE_INT_ARGB);
                bi.setRGB(0,width,colorBits,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);
        }
    }
}

解决方法

我最初回答了这个问题,建议您使用GetSystemMetrics()函数,对像素的光标宽度使用常数SM_CXCURSOR(13),对于常数,使用SM_CYCURSOR(14)高度。链接的文档指出“系统无法创建其他大小的游标。”

但是随后我看到您发布了类似的问题here,并指出这些值从32x32不变。 as noted in this answer在那里发生的是,光标实际上仍然是该大小,但是屏幕上只显示了较小的图像。其余像素只是“不可见”。对于“较大”的图像似乎也是如此,因为在内部与光标关联的“图标”仍然是相同的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
  • colorBitsMem获取了GetDIBits()后,插入了以下内容:
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系统图标的情况下,我会在摆动窗口中看到以下内容:

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,0x00004000);
return getImageByHICON(baseSize,new WinDef.HCURSOR(foo.getPointer()));

为此:

image of icon

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...