HCURSOR를 Java의 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하고 커서 너비 SM_CYCURSOR( 픽셀)에 상수 (13 )를 사용하고 높이에 (14)를 사용하도록 제안했습니다 . 링크 된 문서에는 "시스템이 다른 크기의 커서를 작성할 수 없습니다."라고 설명되어 있습니다.

그러나 여기에 비슷한 질문을 게시하고 해당 값이 32x32에서 변경되지 않는다고 말했습니다. 뭐,이 일 이 답변에서 언급 한 바와 같이하면 , 커서가 실제로 아직도 그 크기,하지만 작은 이미지가 화면에 표시되는 것입니다; 나머지 픽셀은 단순히 "보이지 않습니다". 내부적으로 커서와 연관된 "아이콘"은 여전히 ​​동일한 32x32 크기이지만 화면에는 다른 것이 표시된다는 점에서 "큰"이미지에 대해서도 마찬가지입니다.

흥미롭게도 스윙 창 위로 마우스를 가져갈 때의 아이콘은 항상 32x32입니다. 사용할 선택 Cursor c = toolkit.createCustomCursor(image, pointerPos, "cursorname");은 비트 맵 이미지를 창에서 새 (더 작은) 커서로 축소하는 것입니다. 간단한 방법으로 기본 커서를 유지할 수 있습니다.

Cursor c = Cursor.getDefaultCursor();

올바른 크기로보기 흉한 픽셀 화 버전을 얻기 위해 코드를 다음과 같이 변경했습니다.

  • 변경된 메서드 인수 widthheightto wand h:getImageByHICON(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 시스템 아이콘을 사용하면 스윙 창에 다음이 표시됩니다.

그 크기는 내 마우스 커서와 일치합니다. 픽셀이 아닙니다.

이 답변 에서 영감을 얻은 또 다른 옵션 은 픽셀이있는 간단한 정수 수학보다 더 나은 비트 맵 스케일링을 사용하는 것입니다. 귀하의 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()));

다음을 제공합니다.