Speichern Sie HCURSOR in BufferedImage in Java

Aug 17 2020

Ich brauche zu speichern HCURSORin BufferedImage mit seiner wirklichen Größe und Farbe .

Ich habe ähnliche Fragen 1 und 2 gefunden, die mit dem Standard-32x32-Cursor gut funktionieren. Wenn ich jedoch Farbe oder Größe ändere, wird BufferedImage ungültig, was zu einem Ergebnis wie dem folgenden führt:

Erstens bestand mein Problem darin, eine echte Cursorgröße zu erhalten. Aber dann habe ich den Weg gefunden, es über JNA aus der Registrierung zu bekommen.

Dann muss ich es speichern BufferedImage. Ich habe versucht, Codefragmente getImageByHICON()und getIcon()den ersten Link oben zu verwenden, aber irgendwo ist ein Fehler aufgetreten - das Bild ist immer noch falsch oder fehlerhaft. Vielleicht verstehe ich nicht, wie man es richtig benutzt, weil ich mit der BufferedImageSchöpfung nicht sehr vertraut bin .

Wie kann ich speichern HCURSOR, BufferedImagewenn ich Cursor in Originalgröße habe und CURSORINFO?

Hier ist mein vollständiger 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);
        }
    }
}

Antworten

1 DanielWiddis Aug 17 2020 at 09:46

Ich habe diese Frage ursprünglich beantwortet und vorgeschlagen, dass Sie die Funktion GetSystemMetrics () verwenden, wobei Sie die Konstante SM_CXCURSOR(13) für die Breite des Cursors in Pixel und SM_CYCURSOR(14) für die Höhe verwenden. In der verknüpften Dokumentation heißt es: "Das System kann keine Cursor anderer Größe erstellen."

Aber dann sehe ich Sie eine ähnliche Frage gestellt hier , und stellte fest , dass diese Werte nicht ändern , von 32x32. Wie in dieser Antwort erwähnt , passiert dort, dass der Cursor immer noch diese Größe hat, aber nur das kleinere Bild auf dem Bildschirm angezeigt wird. Der Rest der Pixel ist einfach "unsichtbar". Dasselbe scheint für "größere" Bilder zu gelten, da das dem Cursor zugeordnete "Symbol" intern immer noch dieselbe Größe von 32 x 32 hat, der Bildschirm jedoch etwas anderes anzeigt.

Interessanterweise ist das Symbol beim Bewegen des Mauszeigers über das Swing-Fenster immer 32 x 32. Sie können Cursor c = toolkit.createCustomCursor(image, pointerPos, "cursorname");das Bitmap-Bild auf einen neuen (kleineren) Cursor im Fenster verkleinern. Ich kann den Standardcursor mit einem einfachen behalten:

Cursor c = Cursor.getDefaultCursor();

Ich habe die folgenden Änderungen an Ihrem Code vorgenommen, um eine hässliche Pixelversion mit der richtigen Größe zu erhalten:

  • geänderte Methodenargumente widthund heightzu wund h:getImageByHICON(final int w, final int h, final WinDef.HICON hicon)
  • Setzen Sie zu Beginn des try-Blocks int width = 32und int height = 32.
  • nach dem abrufen der colorBitsMemvon GetDIBits()eingefügt folgendes:
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];
    }
}

Mit einem 64x64-Systemsymbol sehe ich dies im Swing-Fenster:

Diese Größe entspricht meinem Mauszeiger. Die Pixel, nicht viel.

Eine andere Option, die von dieser Antwort inspiriert ist, ist die Verwendung einer besseren Bitmap-Skalierung als meine einfache Ganzzahl-Mathematik mit Pixeln. Führen Sie in Ihrer getCursor()Methode Folgendes aus:

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;

Welches gibt mir das:

Eine weitere Option in Ihrer getCursor()Klasse ist 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()));

Gibt dies: