Salva HCURSOR in BufferedImage in Java

Aug 17 2020

Ho bisogno di memorizzare HCURSORin BufferedImage con la sua vera dimensione e colore .

Ho trovato domande simili 1 e 2 che funzionano bene con il cursore standard 32x32, ma se cambio colore o dimensione, BufferedImage diventa non valida, dandomi un risultato come questo:

In primo luogo, il mio problema era ottenere una dimensione reale del cursore. Ma poi ho trovato il modo per ottenerlo tramite JNA dal registro.

Quindi devo salvarlo in BufferedImage. Ho provato a utilizzare frammenti di codice getImageByHICON()e getIcon()dal primo collegamento sopra, ma c'è un errore da qualche parte: l'immagine è ancora errata o rotta. Forse non capisco come usarlo correttamente perché non ho molta familiarità con la BufferedImagecreazione.

Come posso risparmiare HCURSORa BufferedImagese ho cursori dimensione reale e CURSORINFO?

Ecco il mio codice completo:

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);
        }
    }
}

Risposte

1 DanielWiddis Aug 17 2020 at 09:46

Inizialmente ho risposto a questa domanda suggerendo di utilizzare la funzione GetSystemMetrics () , utilizzando la costante SM_CXCURSOR(13) per la larghezza del cursore in pixel e SM_CYCURSOR(14) per l'altezza. La documentazione collegata afferma "Il sistema non può creare cursori di altre dimensioni".

Ma poi vedo che hai pubblicato una domanda simile qui e hai affermato che quei valori non cambiano da 32x32. Quello che succede lì, come notato in questa risposta , è che il cursore è ancora di quella dimensione, ma sullo schermo viene visualizzata solo l'immagine più piccola; il resto dei pixel è semplicemente "invisibile". Lo stesso sembra valere per le immagini "più grandi", in quanto internamente l '"icona" associata al cursore ha sempre la stessa dimensione 32x32, ma lo schermo mostra qualcos'altro.

È interessante notare che l'icona quando si passa con il mouse sopra la finestra Swing è sempre 32x32. La tua scelta da usare Cursor c = toolkit.createCustomCursor(image, pointerPos, "cursorname");è ridimensionare l'immagine bitmap su un nuovo cursore (più piccolo) nella finestra. Posso mantenere il cursore predefinito con un semplice:

Cursor c = Cursor.getDefaultCursor();

Ho apportato le seguenti modifiche al tuo codice per ottenere una brutta versione pixellata della giusta dimensione:

  • argomenti del metodo modificati widthe heightin we h:getImageByHICON(final int w, final int h, final WinDef.HICON hicon)
  • all'inizio del blocco try, impostare int width = 32e int height = 32.
  • dopo aver recuperato il colorBitsMemda GetDIBits()inserito il seguente:
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];
    }
}

Quindi con un'icona di sistema 64x64, vedo questo nella finestra oscillante:

Quella dimensione corrisponde al mio cursore del mouse. I pixel, non molto.

Un'altra opzione, ispirata da questa risposta, è quella di utilizzare un ridimensionamento bitmap migliore rispetto alla mia semplice matematica intera con pixel. Nel tuo getCursor()metodo, fai:

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;

Che mi sta dando questo:

Ancora un'altra opzione, nella tua getCursor()classe è 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()));

Dà questo: