JavaでHCURSORをBufferedImageに保存します

Aug 17 2020

私は保存する必要があるHCURSOR中でBufferedImage 、その実際のサイズと色で

標準の32x32カーソルで正常に機能する同様の質問1と2を見つけましたが、色やサイズを変更すると、BufferedImageが無効になり、次のような結果になります。

まず、私の問題は実際のカーソルサイズを取得することでした。しかし、それから私はレジストリからJNAを介してそれを取得する方法を見つけました。

次に、それをに保存する必要がありBufferedImageます。私は、コードスニペットを使用しようgetImageByHICON()getIcon()イメージがまだ正しくないか壊れている-上記の最初のリンクから、エラーのどこかがあります。BufferedImage創作にあまり詳しくないので、正しく使う方法がわからないのかもしれません。

私はどのように保存することができますHCURSORBufferedImage、私はカーソル本当の大きさとを持っている場合は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, 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 DanielWiddis Aug 17 2020 at 09:46

私は当初、GetSystemMetrics()関数を使用することを提案してこの質問に回答しました。定数SM_CXCURSOR(13)をピクセル単位のカーソルの幅に使用し、SM_CYCURSOR(14)を高さに使用します。リンクされたドキュメントには、「システムは他のサイズのカーソルを作成できません」と記載されています。

しかし、あなたがここに同様の質問を投稿し、それらの値は32x32から変更されないと述べたことがわかります。この回答に記載されているように、そこで何が起こるかというと、カーソルは実際にはそのサイズのままですが、画面には小さい画像のみが表示されます。残りのピクセルは単に「見えない」だけです。同じことが「より大きな」画像にも当てはまるように見えます。内部的には、カーソルに関連付けられた「アイコン」は同じ32x32サイズですが、画面には別のものが表示されます。

興味深いことに、Swingウィンドウにカーソルを合わせたときのアイコンは常に32x32です。使用する選択Cursor c = toolkit.createCustomCursor(image, pointerPos, "cursorname");は、ビットマップイメージをウィンドウ内の新しい(小さい)カーソルに縮小することです。簡単な方法でデフォルトのカーソルを維持できます。

Cursor c = Cursor.getDefaultCursor();

醜いピクセル化されたバージョンを適切なサイズで取得するために、コードに次の変更を加えました。

  • メソッド引数widthheighttowおよびh:を変更しましたgetImageByHICON(final int w, final int h, final WinDef.HICON hicon)
  • tryブロックの開始時に、とを設定int width = 32int height = 32ます。
  • colorBitsMemfromをフェッチした後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のシステムアイコンを使用すると、スイングウィンドウに次のように表示されます。

そのサイズは私のマウスカーソルと一致します。ピクセル、それほど多くはありません。

この回答に触発された別のオプションは、ピクセルを使用した単純な整数計算よりも優れたビットマップスケーリングを使用することです。あなたの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()));

これを与える: