Getting windows mousepointer icon = black square Topic is solved

Do you have a typical platform dependent issue you're battling with ? Ask it here. Make sure you mention your platform, compiler, and wxWidgets version.
User avatar
Parduz
Earned some good credits
Earned some good credits
Posts: 118
Joined: Fri Jan 30, 2015 1:48 pm
Location: Bologna, Italy

Getting windows mousepointer icon = black square

Post by Parduz » Tue Apr 13, 2021 10:55 am

This is the function that should capture the system mouse pointer icon and store it in the given wxImage:

Code: Select all

void CopyMousePointerImage(wxImage &im)
{
	// Get information about the global cursor.
	ICONINFO ii = {0};
	CURSORINFO ci = {0};
	ci.cbSize = sizeof(ci);
	if (!GetCursorInfo(&ci)) {
		// error
		return;
	}
	if (!GetIconInfo(ci.hCursor, &ii) ) {
		// error
		return;
	}

	SIZE CursSize = {0};
	BITMAP bm;
	if(
	  GetObject(ii.hbmMask, sizeof(bm), &bm) != sizeof(bm)
	) {
		// error
		return;
	}
	CursSize.cx = bm.bmWidth;
	CursSize.cy = ii.hbmColor ? bm.bmHeight : bm.bmHeight / 2;

	// Get your device contexts.
	MemoryHDC hdcMem;
	// Create the bitmap to use as a canvas.
	CompatibleBitmap hbmCanvas(hdcMem,CursSize.cx, CursSize.cy);
	// Select the bitmap into the device context.
	HGDIOBJ hbmOld = SelectObject(hdcMem, hbmCanvas);

	// Draw the cursor into the canvas.
	if (
	  DrawIconEx(hdcMem, 0, 0, ci.hCursor, 0, 0, 0, NULL, DI_NORMAL)
	) {
		wxDIB dib(hbmCanvas);
		im = dib.ConvertToImage(wxDIB::Convert_AlphaAlwaysIf32bpp);
	};
	// Clean up
	SelectObject(hdcMem, hbmOld);
}
The function is called elsewhere, and the resulting image drawn over another bitmap (a copy of a portion of the screen):

Code: Select all

wxImage		m_iCursor;
CopyMousePointerImage(m_iCursor);
...
dcMem.Blit(wxpZero, m_rCapture.GetSize(), &dcScreen, m_rCapture.GetLeftTop() );
dcMem.DrawBitmap(m_iCursor, m_pPointer.x, m_pPointer.y);
All i get is a black rectangle where there's the mouse pointer, and i don't get what i'm doing wrong.
Could someone please help me?

PB
Part Of The Furniture
Part Of The Furniture
Posts: 3041
Joined: Sun Jan 03, 2010 5:45 pm

Re: Getting windows mousepointer icon = black square

Post by PB » Tue Apr 13, 2021 11:11 am

I would unselect hbmCanvas from the DC before accessing it.

I would also check if DrawIconEx() returns TRUE and dib.IsOk() (no idea about CompatibleBitmap type, nor I am sure if one can even create wxDIB from a DDB) returns true.

But firstly, I would check if wxWidgets does not offer a better way to convert Windows cursor to bitmap. wxCursor has SetHCURSOR() method, wxBitmap has a ctor taking wxCursor...

User avatar
Parduz
Earned some good credits
Earned some good credits
Posts: 118
Joined: Fri Jan 30, 2015 1:48 pm
Location: Bologna, Italy

Re: Getting windows mousepointer icon = black square

Post by Parduz » Tue Apr 13, 2021 12:17 pm

PB wrote:
Tue Apr 13, 2021 11:11 am
I would unselect hbmCanvas from the DC before accessing it.
Nothing changed, anyway.
PB wrote:
Tue Apr 13, 2021 11:11 am
I would also check if DrawIconEx() returns TRUE and dib.IsOk()
Both alredy there: DrawIconEx "If the function succeeds, the return value is nonzero" and the wxDIB constructor throws an error if it fails.
PB wrote:
Tue Apr 13, 2021 11:11 am
(no idea about CompatibleBitmap type
Handy class found in "wx\msw\private.h"

Code: Select all

// classes for temporary bitmaps
class AutoHBITMAP : private AutoGDIObject
{
public:
    AutoHBITMAP()
        : AutoGDIObject() { }
    AutoHBITMAP(HBITMAP hbmp) : AutoGDIObject(hbmp) { }
    void Init(HBITMAP hbmp) { InitGdiobj(hbmp); }
    operator HBITMAP() const { return (HBITMAP)GetObject(); }
};
class CompatibleBitmap : public AutoHBITMAP
{
public:
    CompatibleBitmap(HDC hdc, int w, int h)
        : AutoHBITMAP(::CreateCompatibleBitmap(hdc, w, h)) { }
};
PB wrote:
Tue Apr 13, 2021 11:11 am
nor I am sure if one can even create wxDIB from a DDB)
uh ... well, there's a constructor taking an HBITMAP, in the dib.h... is'nt this enough?
PB wrote:
Tue Apr 13, 2021 11:11 am
But firstly, I would check if wxWidgets does not offer a better way to convert Windows cursor to bitmap. wxCursor has SetHCURSOR() method, wxBitmap has a ctor taking wxCursor...
i'd be happy to use it.. i can't find any explanation about it.
What it does is calling SetHandle(WXHANDLE handle), then i'm lost in gdiimage.h

Have you a link, or some sample code about it?

PB
Part Of The Furniture
Part Of The Furniture
Posts: 3041
Joined: Sun Jan 03, 2010 5:45 pm

Re: Getting windows mousepointer icon = black square

Post by PB » Tue Apr 13, 2021 2:45 pm

Sorry, I do not have time to address your comments ATM.

But, FWIW, I tried to write simple code to get the system cursor. It seems to work mostly fine, except when the cursor is the text cursor over an edit control. In this case, the cursor has black instead of transparent background and it itself is white instead of black. Not sure where the issue is, I did not have time to investigate further.

Code: Select all

#include <wx/wx.h>
#include <wx/msw/wrapwin.h>

class MyDialog : public wxDialog
{
public:
    MyDialog() : wxDialog(nullptr, wxID_ANY, "Test")
    {
        m_button = new wxButton(this, wxID_ANY, "Update Cursor Bitmap (<F5>)");
        Bind(wxEVT_BUTTON, [this](wxCommandEvent&){ UpdateCursorBitmap(); } );

        wxAcceleratorEntry entries[1];
        entries[0].Set(wxACCEL_NORMAL, WXK_F5, m_button->GetId());
        wxAcceleratorTable accel(1, entries);
        SetAcceleratorTable(accel);

        UpdateCursorBitmap();
    }
private:
    wxButton* m_button;

    void UpdateCursorBitmap()
    {
        CURSORINFO ci{0};

        ci.cbSize = sizeof(ci);
        if ( !::GetCursorInfo(&ci) || (ci.flags & CURSOR_SHOWING) == 0 )
        {
            wxLogError("Could not get cursor info.");
            return;
        }

        wxIcon icon;
        if ( !icon.CreateFromHICON(WXHICON(ci.hCursor)) )
        {
            wxLogError("Could not create wxIcon from HCURSOR.");
            return;
        }

        m_button->SetBitmap(wxNullBitmap, wxTOP); // work around a bitmap not being updated properly
        m_button->SetBitmap(icon, wxTOP);

        wxBitmap(icon).SaveFile("captured_cursor.png", wxBITMAP_TYPE_PNG);
    }
};


class MyApp : public wxApp
{
public:
    bool OnInit() override
    {
        wxInitAllImageHandlers();
        MyDialog().ShowModal();
        return false;
    }
}; wxIMPLEMENT_APP(MyApp);
EDIT
Regarding the issue with the text cursor... When I look at the saved PNG, the text cursor looks like this
textCursor.png
textCursor.png (206 Bytes) Viewed 1212 times
while other cursors like this
arrowCursor.png
arrowCursor.png (450 Bytes) Viewed 1212 times
where the checkered area is what is actually transparent (the bitmaps are sloppily cropped by me).

User avatar
Parduz
Earned some good credits
Earned some good credits
Posts: 118
Joined: Fri Jan 30, 2015 1:48 pm
Location: Bologna, Italy

Re: Getting windows mousepointer icon = black square

Post by Parduz » Wed Apr 14, 2021 12:51 pm

Thanks a lot!
PB wrote:
Tue Apr 13, 2021 2:45 pm
Not sure where the issue is, I did not have time to investigate further.
Not an issue at all: Monochrome icons are managed in this way
The icon bitmask bitmap. If this structure defines a black and white icon, this bitmask is formatted so that the upper half is the icon AND bitmask and the lower half is the icon XOR bitmask. Under this condition, the height should be an even multiple of two. If this structure defines a color icon, this mask only defines the AND bitmask of the icon.
I'll try your code ASAP!

User avatar
Parduz
Earned some good credits
Earned some good credits
Posts: 118
Joined: Fri Jan 30, 2015 1:48 pm
Location: Bologna, Italy

Re: Getting windows mousepointer icon = black square

Post by Parduz » Wed Apr 14, 2021 2:13 pm

Your code works flawlessy.... and i spent so many hours trying to get this result! :roll:
PB wrote:
Tue Apr 13, 2021 2:45 pm
Regarding the issue with the text cursor... When I look at the saved PNG, the text cursor looks like this
textCursor.png
while other cursors like this
arrowCursor.png
I think that this should be considered a bug... i don't know what CreateFromHICON do but it should take care of this default and well documented behaviour of b/w icons in Windows. Don't you?

Now i need to find a way to manage this case myself... which i fear will lead to the same problems i had before. I'll see.

Last question:
This part of the docs worries me:
This wxMSW-specific method allows assigning a native Windows HICON (which must be castes to WXHICON opaque handle type) to wxIcon. Notice that this means that the HICON will be destroyed by wxIcon when it is destroyed.
because i'll update the icon 10 times per second.

Do the HICON will be destroyed also with a new CreateFromHICON call, or have i to keep track of the current HICON and destroy it myself?

PB
Part Of The Furniture
Part Of The Furniture
Posts: 3041
Joined: Sun Jan 03, 2010 5:45 pm

Re: Getting windows mousepointer icon = black square

Post by PB » Wed Apr 14, 2021 4:01 pm

I have updated the code to include some more information

Code: Select all

#include <wx/wx.h>
#include <wx/msw/wrapwin.h>

bool CreatewxBitmapFromCurrentCursor(wxBitmap& bitmap)
{
    CURSORINFO ci{0};
    ICONINFO   ii{0};

    ci.cbSize = sizeof(ci);
    if ( !::GetCursorInfo(&ci) || (ci.flags & CURSOR_SHOWING) == 0 )
    {
        wxLogError("Could not get the cursor info.");
        return false;
    }

    // class AutoIconInfo from include\msw\private.h can be used instead
    if ( !::GetIconInfo(ci.hCursor, &ii) )
    {
        wxLogError("Could not get icon info about the cursor.");
        return false;
    }
    if ( !ii.hbmColor )
        wxLogMessage("The cursor is monochrome.");
    else
        wxLogMessage("The cursor is in color.");
    ::DeleteObject(ii.hbmColor);
    ::DeleteObject(ii.hbmMask);

    wxIcon   icon;
    wxBitmap bmp;

    if ( !icon.CreateFromHICON(WXHICON(ci.hCursor)) )
    {
        wxLogError("Could not create wxIcon from HCURSOR.");
        return false;
    }

    if ( !bmp.CopyFromIcon(icon) )
    {
        wxLogError("Could not create wxBitmap from wxIcon.");
        return false;
    }

    wxLogMessage("wxBitmap created from the cursor: size = %dx%d pixels, depth = %d, mask = %s.",
        bmp.GetWidth(), bmp.GetHeight(), bmp.GetDepth(), bmp.GetMask() ? "Yes" : "No");

    bitmap = bmp;
    return true;
}

class MyDialog : public wxDialog
{
public:
    MyDialog() : wxDialog(nullptr, wxID_ANY, "Test")
    {
        wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);

        m_button = new wxButton(this, wxID_ANY, "Update Cursor Bitmap (<F5>)");
        Bind(wxEVT_BUTTON, [this](wxCommandEvent&){ UpdateCursorBitmap(); } );
        mainSizer->Add(m_button, wxSizerFlags().Expand().Border());

        wxTextCtrl* logCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
            wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2);
        wxLog::SetActiveTarget(new wxLogTextCtrl(logCtrl));
        mainSizer->Add(logCtrl, wxSizerFlags().Proportion(1).Expand().Border());

        SetSizer(mainSizer);
        SetClientSize(FromDIP(wxSize(800, 800)));

        wxAcceleratorEntry entries[1];
        entries[0].Set(wxACCEL_NORMAL, WXK_F5, m_button->GetId());
        wxAcceleratorTable accel(1, entries);
        SetAcceleratorTable(accel);

        UpdateCursorBitmap();
    }
private:
    wxButton* m_button;

    void UpdateCursorBitmap()
    {
        wxBitmap bitmap;

        if ( !CreatewxBitmapFromCurrentCursor(bitmap) )
            return;

        m_button->SetBitmap(wxNullBitmap, wxTOP); // work around a bitmap not being updated properly
        m_button->SetBitmap(bitmap, wxTOP);

        bitmap.SaveFile("captured_cursor.png", wxBITMAP_TYPE_PNG);
    }
};


class MyApp : public wxApp
{
public:
    bool OnInit() override
    {
        wxInitAllImageHandlers();
        MyDialog().ShowModal();
        return false;
    }
}; wxIMPLEMENT_APP(MyApp);
Parduz wrote:
Wed Apr 14, 2021 2:13 pm
I think that this should be considered a bug... i don't know what CreateFromHICON do but it should take care of this default and well documented
behaviour of b/w icons in Windows. Don't you?
The mask is recognized but the depth and size are wrong, this is output from text cursor first and arrow cursor second:

Code: Select all

17:43:33: The cursor is monochrome.
17:43:33: wxBitmap created from cursor: size = 32x64 pixels, depth = 32, mask = Yes.
17:43:37: The cursor is in color.
17:43:37: wxBitmap created from cursor: size = 32x32 pixels, depth = 32, mask = No.
Parduz wrote:
Wed Apr 14, 2021 2:13 pm
Now i need to find a way to manage this case myself... which i fear will lead to the same problems i had before. I'll see.
I would ask in the wx-users mailing first, to see what can be done and if it is indeed a bug. I think it could be worked around in the user code, but I did not have time to try yet.

IIRC, there were some recentish changes regarding handling monochrome bitmaps on Windows, I have tested this on the master but also with 3.05 to see if there was any regression but the result was same for both. But perhaps my whole idea is incorrect and one is not supposed to create a wxIcon from HCURSOR even when HCURSOR is typedef for HICON. OTOH, creating a wxCursor from HCURSOR and then trying to create a wxBitmap from it led to a crash...
Parduz wrote:
Wed Apr 14, 2021 2:13 pm
Do the HICON will be destroyed also with a new CreateFromHICON call?
I would hope so but I would also rather ask about this to make sure.

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 5152
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: Getting windows mousepointer icon = black square

Post by ONEEYEMAN » Wed Apr 14, 2021 9:35 pm

Hi,
OTOH, creating a wxCursor from HCURSOR and then trying to create a wxBitmap from it led to a crash...
Was the crash on the 3.0.5 or HEAD?

Thank you.

P.S.: I'm going to get latest sources over the weekend and try it on all 3 platforms after OSX update..

Thank you.

User avatar
Parduz
Earned some good credits
Earned some good credits
Posts: 118
Joined: Fri Jan 30, 2015 1:48 pm
Location: Bologna, Italy

Re: Getting windows mousepointer icon = black square

Post by Parduz » Thu Apr 15, 2021 1:53 pm

PB wrote:
Wed Apr 14, 2021 4:01 pm
I would ask in the wx-users mailing first, to see what can be done and if it is indeed a bug. I think it could be worked around in the user code, but I did not have time to try yet.
I'm not sure about the mailing list, i'm still waiting for a reply about the questions i wrote here about the wxStopWatch class members.
I think i'll try to sort out the monochrome icons on my own.
PB wrote:
Wed Apr 14, 2021 4:01 pm
Parduz wrote:
Wed Apr 14, 2021 2:13 pm
Do the HICON will be destroyed also with a new CreateFromHICON call?
I would hope so but I would also rather ask about this to make sure.
uh.
If the bmp.CopyFromIcon in your code just copy the data without any reference to the original handle, i'll let the wxIcon destroy and take the HICON with it for sure.

User avatar
doublemax
Moderator
Moderator
Posts: 16012
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Getting windows mousepointer icon = black square

Post by doublemax » Thu Apr 15, 2021 2:53 pm

Off-topic:
i'm still waiting for a reply about the questions i wrote here about the wxStopWatch class members.
I think Vadim gave you a pretty clear answer. And in case you don't know him, he's the main wxWidgets maintainer. His word has weight.
Use the source, Luke!

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 5152
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: Getting windows mousepointer icon = black square

Post by ONEEYEMAN » Thu Apr 15, 2021 2:58 pm

@OP:
In fsct in 99.99% of cases iot is 100% accurate and should be followed.

Thank you.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 3041
Joined: Sun Jan 03, 2010 5:45 pm

Re: Getting windows mousepointer icon = black square

Post by PB » Thu Apr 15, 2021 3:58 pm

Parduz wrote:
Thu Apr 15, 2021 1:53 pm
PB wrote:
Wed Apr 14, 2021 4:01 pm
I would ask in the wx-users mailing first, to see what can be done and if it is indeed a bug.
I think i'll try to sort out the monochrome icons on my own.
I am going to ask there, wxWidgets has code to convert monochrome icons to a wxBitmap:
https://github.com/wxWidgets/wxWidgets/ ... p.cpp#L486

However, I would expect this to be handled differently and the resulting wxBitmap be different. I will ask why is this done as it is.

User avatar
Parduz
Earned some good credits
Earned some good credits
Posts: 118
Joined: Fri Jan 30, 2015 1:48 pm
Location: Bologna, Italy

Re: Getting windows mousepointer icon = black square

Post by Parduz » Fri Apr 16, 2021 8:06 am

doublemax wrote:
Thu Apr 15, 2021 2:53 pm
Off-topic:
i'm still waiting for a reply about the questions i wrote here about the wxStopWatch class members.
I think Vadim gave you a pretty clear answer. And in case you don't know him, he's the main wxWidgets maintainer. His word has weight.
Yes and i'm totally dumb and missed his answer, waiting for a notification in the mail :roll:
I did'nt meant to be mean, anyway :)

PB
Part Of The Furniture
Part Of The Furniture
Posts: 3041
Joined: Sun Jan 03, 2010 5:45 pm

Re: Getting windows mousepointer icon = black square

Post by PB » Fri Apr 16, 2021 3:13 pm

In the end, I decided to file a ticket, considering this a bug: https://trac.wxwidgets.org/ticket/19146

User avatar
Parduz
Earned some good credits
Earned some good credits
Posts: 118
Joined: Fri Jan 30, 2015 1:48 pm
Location: Bologna, Italy

Re: Getting windows mousepointer icon = black square

Post by Parduz » Fri Apr 16, 2021 3:16 pm

PB wrote:
Thu Apr 15, 2021 3:58 pm
I am going to ask there, wxWidgets has code to convert monochrome icons to a wxBitmap:
https://github.com/wxWidgets/wxWidgets/ ... p.cpp#L486
Problem is that there's the right comments, but the code does nothing to handle the double-height. Seems something like "i forgot to finish the code there".

Anyway, i still wonder WHY we should use the wxIcon, and then the wxBitmap, while the IconInfo.hbmColor and hbmMask already are the HBITMAP we need. I see that wxBitmap have a deprecated SetHBITMAP, and i can't find a way to have a transparent wxBitmap from an HBITMAP.
I tried modifying your code but if i don't get errors all i get is ... nothing at all (no visible cursor, transparent or not).

-EDIT-
I was writing in the same time of your last comment... i'd still like to find a way out of the wxIcon route.

Post Reply