Related
I find it difficult to understand the excerpt below from MSDN site on GetDIBits() function:
If lpvBits is NULL and the bit count member of BITMAPINFO is
initialized to zero, GetDIBits fills in a BITMAPINFOHEADER
structure or BITMAPCOREHEADER without the color table. This
technique can be used to query bitmap attributes.
Question-1: What is meant by "the bit count member of BITMAPINFO"? Does it mean some_bmi.bmiHeader.biBitCount?
Question-2: What is meant by "GetDIBits fills in a BITMAPINFOHEADER structure or BITMAPCOREHEADER without the color table"? What color table is there to fill in those structures? None of them seems to have a member related to the color table. Is this about the array some_bmi.bmiColors?
Question-3: Is there a way to use GetDIBits() to get the color table(i.e. the array mapping indexes to colors) for a bitmap?
EDIT:
From the comments so far, it looks like breaking the question down into smaller parts was not effective. I will try it another way.
This is what I understand from the part I quoted from MSDN at the beginning:
Assuming the function call is GetDIBits(hdc, hbmp, uStartScan, cScanLines, lpvBits, lpbi, uUsage); if lpvBits is NULL and lpvBits->bmiHeader.biBitCount is initialized to zero, GetDIBits() fills in lpbi->bmiHeader only and lpbi->bmiColors is not modified.
Is this the correct way to understand it? And if so, is there a way to get GetDIBits() to fill in lpbi->bmiColors, such as initializing lpvBits->bmiHeader.biBitCount to bit-depth of the bitmap?
I tried testing Question-1's assumption as follows but GetDIBits() fails in that case:
void test(HWND hWnd) {
// get a memory DC
HDC hdc = GetDC(hWnd);
HDC hdcmem = CreateCompatibleDC(hdc); // has 1x1 mono bitmap selected
// into it initially by default
// select a 16x16 mono bmp into it
const int bmp_h = 16, bmp_w = 16;
const int bits_per_px = 1;
HBITMAP hbmp = CreateCompatibleBitmap(hdcmem, bmp_h, bmp_w); // 16x16 mono bitmap
HGDIOBJ hOldBmp = SelectObject(hdcmem, hbmp);
// initialize BITMAPINFO ptr
// (make sure to allocate a buffer large enough for 2 RGBQUADs
// in case color table is retured by GetDIBits() call)
const int bmi_buf_sz =
sizeof(BITMAPINFO) + sizeof(RGBQUAD) * (1 << bits_per_px); // 2 + 1(extra) RGBQUADs allocated for pbmi->bimColors
BYTE* p_bmi_buf = new BYTE[bmi_buf_sz];
BITMAPINFO* pbmi = reinterpret_cast<BITMAPINFO*>(p_bmi_buf);
ZeroMemory(pbmi, bmi_buf_sz);
// populate BITMAPINFO
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biBitCount = 1; // set to 1 just to see if GetDIBits()
// fills in pbmi->bmiColors too
// (when set to 0, only pbmi->bmiHeader is filled)
if(!GetDIBits(hdcmem, hbmp,
0, (UINT)bmp_h,
NULL, pbmi, DIB_PAL_COLORS)) {
MessageBox(hWnd, L"GetDIBits() failed!", NULL, MB_OK);
}
// clean-up
delete[] p_bmi_buf;
SelectObject(hdcmem, hOldBmp); // push hbmp out
DeleteObject(hbmp);
DeleteDC(hdcmem);
ReleaseDC(hWnd, hdc);
}
The easiest way to get the color table is with GetDibColorTable:
HDC memdc = CreateCompatibleDC(NULL);
HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);
int ncolors = 1 << bm.bmBitsPixel;
std::vector<RGBQUAD> rgb(ncolors);
if(ncolors == GetDIBColorTable(memdc, 0, ncolors, &rgb[0]))
{
//success!
}
SelectObject(memdc, oldbmp);
DeleteDC(memdc);
Back to your question: GetDIBits expects pbmi to contain all zeros (except for bmiHeader.biSize member). So pbmi->bmiHeader.biBitCount should be zero.
//pbmi->bmiHeader.biBitCount = 1; <<= comment out this line
This should work; however, as it is stated in documentation, this will only fill the info header, not the color table. To get the color table you have to make another call to GetDIBits with enough allocation for the dib bits.
Moreover DIB_PAL_COLORS will return palette index array (I am not sure what you can do with that). You may want to use DIB_RGB_COLORS flag which will return actual colors when a color table is present.
Try this with a bitmap loaded from file:
HBITMAP hbitmap = (HBITMAP)LoadImage(0, L"8bit.bmp",
IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE);
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
//don't continue for hi color bitmaps
if(bm.bmBitsPixel > 8) return;
int ncolors = 1 << bm.bmBitsPixel;
HDC memdc = CreateCompatibleDC(NULL);
int bmpinfo_size = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * ncolors;
std::vector<BYTE> buf(bmpinfo_size);
BITMAPINFO* bmpinfo = (BITMAPINFO*)buf.data();
bmpinfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
if(!GetDIBits(memdc, hbitmap, 0, bm.bmHeight, NULL, bmpinfo, DIB_RGB_COLORS))
{
DWORD err = GetLastError();
//...
}
int dibsize = ((bm.bmWidth * bm.bmBitsPixel + 31) / 32) * 4 * bm.bmHeight;
std::vector<BYTE> dib(dibsize);
if(!GetDIBits(memdc, hbitmap, 0, (UINT)bm.bmHeight, &dib[0], bmpinfo, DIB_RGB_COLORS))
{
DWORD err = GetLastError();
//...
}
Now bmpinfo->bmiColors should contain the same values as rgb array shown earlier.
Possible confusion between BITMAPINFO and BITMAPINFOHEADER:
The above structures are declared as follows:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO, FAR *LPBITMAPINFO, *PBITMAPINFO;
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
So BITMAPINFO does not have biBitCount member. But it does have bmiHeader.biBitCount member.
When you declare a BITMAPINFOHEADER variable, you have to set biSize member (that's Windows' idea of version control). When you declare a BITMAPINFO variable, you have to make sure it's BITMAPINFOHEADER is taken care of.
Note that most of the time you don't have to worry about palette. For example LoadImage will return a compatible bitmap (if you don't specify LR_CREATEDIBSECTION) and you can use that right away.
Although the accepted answer covers the details, this is more of a direct answer to the questions in the OP.
Assuming the function call is GetDIBits(hdc, hbmp, uStartScan, cScanLines, lpvBits, lpbi, uUsage);
Question-1:
That is correct. "The bit count member of BITMAPINFO" refers to lpbi->bmiHeader.biBitCount.
Question-2:
When GetDIBits() is called to get DIB bits(i.e. with a non-null lpvBits and an appropriately initialized lpbi->bmiHeader), lpbi->bmiColors also gets filled by the function with the color table(if bit depth is less then 24 bpp).
Unfortunately, this is not clear in the documentation of the function. With that in mind, what the quoted part means is that, when lpvBits is NULL and lpbi->bmiHeader.biBitCount is zero, the function fills lpbi->bmiHeader only, and does not modify lpbi->bimColor(as opposed to when caling the function to get DIB bits).
Question-3:
You can get the function to return color table(for bitmaps with 8-bbp or less) in lpbi->bmiColors by calling it with a non-null lpvBits and an appropriately initialized lpbi->bmiHeader. IOW, when you call the function to get DIB bits as usual, it fills lpbi->bmiColors as well.
Question in EDIT section:
If lpvBits is NULL and lpvBits->bmiHeader.biBitCount is initialized to
zero, GetDIBits() fills in lpbi->bmiHeader only and lpbi->bmiColors is
not modified.
Is this the correct way to understand it?
Yes, that is correct.
And if so, is there a way to get GetDIBits() to fill in
lpbi->bmiColors, such as initializing lpvBits->bmiHeader.biBitCount to
bit-depth of the bitmap?
Yes, there is a way to get the function to return the color table, but as explained in answer to Q2, initializing lpvBits->bmiHeader.biBitCount to bit-depth of the bitmap alone is not enough. All members of lpvBits->bmiHeader must be appropriately initialized and lpvBits must be non-null. This is basically the same as calling the function to get the DIB bits.
My goal is to dynamically put some arbitrary text into an HICON image (at runtime.) I'm using the following code:
//Error checks are omitted for brevity
//First create font
LOGFONT lf = {0};
lf.lfHeight = -58;
lf.lfWeight = FW_NORMAL;
lf.lfOutPrecision = OUT_TT_PRECIS; //Use TrueType fonts for anti-alliasing
lf.lfQuality = CLEARTYPE_QUALITY;
lstrcpy(lf.lfFaceName, L"Segoe UI");
HFONT hFont = ::CreateFontIndirect(&lf);
//HICON hIcon = original icon to use as a source
//I'm using a large 256x256 pixel icon
hIcon = (HICON)::LoadImage(theApp.m_hInstance, MAKEINTRESOURCE(IDI_ICON_GREEN_DIAMOND), IMAGE_ICON, 256, 256, LR_DEFAULTCOLOR);
ICONINFO ii = {0};
::GetIconInfo(hIcon, &ii);
BITMAP bm = {0};
::GetObject(ii.hbmColor, sizeof(bm), &bm);
SIZE szBmp = {bm.bmWidth, bm.bmHeight};
HDC hDc = ::GetDC(hWnd);
HDC hMemDC = ::CreateCompatibleDC(hDc);
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);
::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 0, 0)); //Red text
//Draw text
//NOTE that DrawText API behaves in a similar way
::TextOut(hMemDC, 0, 0, L"Hello", 5);
::SelectObject(hMemDC, hOldFont);
::SelectObject(hMemDC, hOldBmp);
//We need a simple mask bitmap for the icon
HBITMAP hBmpMsk = ::CreateBitmap(szBmp.cx, szBmp.cy, 1, 1, NULL);
ICONINFO ii2 = {0};
ii2.fIcon = TRUE;
ii2.hbmColor = ii.hbmColor;
ii2.hbmMask = hBmpMsk;
//Create updated icon
HICON hIcon2 = ::CreateIconIndirect(&ii2);
//Cleanup
::DeleteObject(hBmpMsk);
::DeleteDC(hMemDC);
::ReleaseDC(hWnd, hDc);
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);
::DeleteObject(hFont);
and then I can display the icon in my window from OnPaint() handler (so that I can see how it turns out) as such:
::DrawIconEx(dc.GetSafeHdc(), 0, 0,
hIcon2,
256, 256, NULL,
::GetSysColorBrush(COLOR_BTNFACE),
DI_NORMAL);
So here's what I get:
To see what's going on pixel-wise in my hIcon2 I called GetDIBits on its ii.hbmColor from the code above. The resulting pixel array where my word "Hello" was supposed to be shown looked like this:
The pixels are encoded as BGRA in that memory dump, so the 4th byte in each DWORD stands for transparency: 0=transparent, FF=opaque. But in this case TextOut doesn't fill out transparency, or leaves it as 0, which is interpreted as "fully transparent." Instead it seems to pre-multiply it into the RGB colors themselves.
Note that if I keep looking further down the same bitmap, where the green diamond begins, the image pixels seem to have transparency bytes set correctly:
Any idea how to draw text so that the API could set those transparency bytes?
EDIT: As was suggested below I tried the following GDI+ method:
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
Graphics grpx(hMemDC);
RectF rcfTxt(0.0f, 0.0f, (REAL)szBmp.cx, (REAL)szBmp.cy);
Font gdiFont(L"Segoe UI", 58.0f, FontStyleRegular, UnitPixel);
SolidBrush gdiBrush(Color(255, 0, 0));
StringFormat gdiSF;
gdiSF.SetAlignment(StringAlignmentNear);
gdiSF.SetFormatFlags(StringFormatFlagsNoWrap);
gdiSF.SetHotkeyPrefix(HotkeyPrefixNone);
//The reason I was using GDI was because I was setting
//spacing between letters using SetTextCharacterExtra()
//Unfortunately with GDI+ this does not work!
HDC hTmpDC = grpx.GetHDC();
::SetTextCharacterExtra(hTmpDC, -4); //This doesn't do anything!
grpx.ReleaseHDC(hTmpDC);
grpx.DrawString(L"Hello", 5, &gdiFont, rcfTxt, &gdiSF, &gdiBrush);
::SelectObject(hMemDC, hOldBmp);
and besides not being able to set character spacing (which I could with GDI using SetTextCharacterExtra) here's what I got (slightly enlarged for visibility):
So clearly still an issue with transparency.
Taken from an old post by Microsoft MVP Mike D Sutton here.
When you create a DC it initially has default 'stock' objects selected
into it, including the stock 1*1*1 Bitmap. Since there is a Bitmap
already selected into the DC when you call DrawText() it will still
try and render to it even though pretty much everything (apart from
one pixel) will be clipped.
What you need to do is to create a Bitmap,
either DDB or DIBSection, and select that into your DC before drawing
to it.
First though you need to find the size of your Bitmap since you
want it large enough to display your text in, so for that you use the
DrawText() call again on the initial DC but include the DT_CALCRECT
flag. What this does is rather than drawing anything it simply
measures how large the text is and dumps that into the RECT you pass
the call. From here you can go ahead and create your DIBSection using
those dimensions and select it into your DC. Finally perform your
existing DrawText ()call (you may also want to use SetBkMode/Color())
which will render the text to the DIBSection from which you can get at
the data.
This seems to work pretty well here:
HBITMAP CreateAlphaTextBitmap(LPCSTR inText, HFONT inFont, COLORREF inColour) {
int TextLength = (int)strlen(inText);
if (TextLength <= 0) return NULL;
// Create DC and select font into it
HDC hTextDC = CreateCompatibleDC(NULL);
HFONT hOldFont = (HFONT)SelectObject(hTextDC, inFont);
HBITMAP hMyDIB = NULL;
// Get text area
RECT TextArea = {0, 0, 0, 0};
DrawText(hTextDC, inText, TextLength, &TextArea, DT_CALCRECT);
if ((TextArea.right > TextArea.left) && (TextArea.bottom > TextArea.top)) {
BITMAPINFOHEADER BMIH;
memset(&BMIH, 0x0, sizeof(BITMAPINFOHEADER));
void *pvBits = NULL;
// Specify DIB setup
BMIH.biSize = sizeof(BMIH);
BMIH.biWidth = TextArea.right - TextArea.left;
BMIH.biHeight = TextArea.bottom - TextArea.top;
BMIH.biPlanes = 1;
BMIH.biBitCount = 32;
BMIH.biCompression = BI_RGB;
// Create and select DIB into DC
hMyDIB = CreateDIBSection(hTextDC, (LPBITMAPINFO)&BMIH, 0, (LPVOID*)&pvBits, NULL, 0);
HBITMAP hOldBMP = (HBITMAP)SelectObject(hTextDC, hMyDIB);
if (hOldBMP != NULL) {
// Set up DC properties
SetTextColor(hTextDC, 0x00FFFFFF);
SetBkColor(hTextDC, 0x00000000);
SetBkMode(hTextDC, OPAQUE);
// Draw text to buffer
DrawText(hTextDC, inText, TextLength, &TextArea, DT_NOCLIP);
BYTE* DataPtr = (BYTE*)pvBits;
BYTE FillR = GetRValue(inColour);
BYTE FillG = GetGValue(inColour);
BYTE FillB = GetBValue(inColour);
BYTE ThisA;
for (int LoopY = 0; LoopY < BMIH.biHeight; LoopY++) {
for (int LoopX = 0; LoopX < BMIH.biWidth; LoopX++) {
ThisA = *DataPtr; // Move alpha and pre-multiply with RGB
*DataPtr++ = (FillB * ThisA) >> 8;
*DataPtr++ = (FillG * ThisA) >> 8;
*DataPtr++ = (FillR * ThisA) >> 8;
*DataPtr++ = ThisA; // Set Alpha
}
}
// De-select bitmap
SelectObject(hTextDC, hOldBMP);
}
}
// De-select font and destroy temp DC
SelectObject(hTextDC, hOldFont);
DeleteDC(hTextDC);
// Return DIBSection
return hMyDIB;
}
If you need an example of how to call it then try something like this
(inDC is the DC to render to):
void TestAlphaText(HDC inDC, int inX, int inY) {
const char *DemoText = "Hello World!\0";
RECT TextArea = {0, 0, 0, 0};
HFONT TempFont = CreateFont(50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Arial\0");
HBITMAP MyBMP = CreateAlphaTextBitmap(DemoText, TempFont, 0xFF);
DeleteObject(TempFont);
if (MyBMP) { // Create temporary DC and select new Bitmap into it
HDC hTempDC = CreateCompatibleDC(inDC);
HBITMAP hOldBMP = (HBITMAP)SelectObject(hTempDC, MyBMP);
if (hOldBMP) {
BITMAP BMInf; // Get Bitmap image size
GetObject(MyBMP, sizeof(BITMAP), &BMInf);
// Fill blend function and blend new text to window
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0x80;
bf.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(inDC, inX, inY, BMInf.bmWidth, BMInf.bmHeight,
hTempDC, 0, 0, BMInf.bmWidth, BMInf.bmHeight, bf);
// Clean up
SelectObject(hTempDC, hOldBMP);
DeleteObject(MyBMP);
DeleteDC(hTempDC);
}
}
}
All credit to answer and code go to original posters on that forum, I've simply reposted it so that this answer will be valid if the links die.
This reply is coming almost 3 years after the question was posted, but people still consult these things long into the future. So I'll explain what's happening.
DrawText (and other GDI text functions) will work on a transparent bitmap. The text is not coming out black even though it displays that way. The alpha channel is set to 0 on all pixels the text draws to, overriding whatever alpha you had set previously. If you set an alpha value in SetTextColor the text will render all black. If you're feeling ambitious you can run through pixel by pixel and target anything not your fill color (which requires a single fill color) but the problem then becomes one of the nature of ClearType being overridden and all alphas are set to whatever you set them to. The text ends up looking very funky. If you use a constant alpha for your background fill you can simply do a blanket run across the entire bitmap's bits after the text is drawn and reset all the alpha values. Since you have to read a byte to determine if it's background or not, you might as well just set every pixel's alpha to whatever the standard alpha is for that image and bypass the slow compares. This works reasonably well and I've found it to be very acceptable. In this day and age, MS should have taken care of this long ago but it's not to be.
https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-antialiasing-with-text-use
Gdiplus::Bitmap bmp( your_Width, your_Height, PixelFormat64bppARGB);
//PixelFormat64bppARGB ARGB needed
FontFamily fontFamily(L"Arial");
Font font(&fontFamily, 29, FontStyleRegular, UnitPoint);
Gdiplus::RectF rectF(00.0f, 10.0f, your_Width, your_Height);
StringFormat stringFormat;
SolidBrush solidBrush(Color(63, 0, 0, 255));
stringFormat.SetAlignment(StringAlignmentCenter);
//solidBrush Color(63, 0, 0, 255) ARGB neede
graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
graphics.DrawString("your_text", -1, &font, rectF, &stringFormat, &solidBrush);
//TextRenderingHintAntiAlias this needed
I would like to create 32 bit color icons programmatically using C++ and Win API. For this purpose I use the following code which I found here.
HICON CreateSolidColorIcon(COLORREF iconColor, int width, int height)
{
// Obtain a handle to the screen device context.
HDC hdcScreen = GetDC(NULL);
// Create a memory device context, which we will draw into.
HDC hdcMem = CreateCompatibleDC(hdcScreen);
// Create the bitmap, and select it into the device context for drawing.
HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen, width, height);
HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcMem, hbmp);
// Draw your icon.
//
// For this simple example, we're just drawing a solid color rectangle
// in the specified color with the specified dimensions.
HPEN hpen = CreatePen(PS_SOLID, 1, iconColor);
HPEN hpenOld = (HPEN)SelectObject(hdcMem, hpen);
HBRUSH hbrush = CreateSolidBrush(iconColor);
HBRUSH hbrushOld = (HBRUSH)SelectObject(hdcMem, hbrush);
Rectangle(hdcMem, 0, 0, width, height);
SelectObject(hdcMem, hbrushOld);
SelectObject(hdcMem, hpenOld);
DeleteObject(hbrush);
DeleteObject(hpen);
// Create an icon from the bitmap.
//
// Icons require masks to indicate transparent and opaque areas. Since this
// simple example has no transparent areas, we use a fully opaque mask.
HBITMAP hbmpMask = CreateCompatibleBitmap(hdcScreen, width, height);
ICONINFO ii;
ii.fIcon = TRUE;
ii.hbmMask = hbmpMask;
ii.hbmColor = hbmp;
HICON hIcon = CreateIconIndirect(&ii);
DeleteObject(hbmpMask);
// Clean-up.
SelectObject(hdcMem, hbmpOld);
DeleteObject(hbmp);
DeleteDC(hdcMem);
ReleaseDC(NULL, hdcScreen);
// Return the icon.
return hIcon;
}
In principle the code works and I can use it to create colored icons at runtime using the Win API. However, I have some problems and questions about that code (and creating icons in general) which I would like to discuss.
The icons created with this function don't seem to be of 32 bit color depth. If I use a color like RGB(218, 112, 214) I would expect it to be some light purple. However, the actual displayed color is gray. How can I change the code such that the color is really 32 bit RGB?
The icon created is completly filled with the color, I would like to have a thin black boarder around it... how can this be achieved?
In the MSDN documentation (a bit downwards) it is mentioned that "Before closing, your application must use DestroyIcon to destroy any icon it created by using CreateIconIndirect. It is not necessary to destroy icons created by other functions. " However, in the documentation for e.g. CreateIcon in MSDN it is said that "When you are finished using the icon, destroy it using the DestroyIcon function." which is pretty much a contradiction. When do I actually have to destroy the icon?
Do these rules then also apply when I add the icon to an image list and this list to a combobox? I.e. do I have to clean up the image list and each associated icon?
Any help is highly appreciated.
When do I actually have to destroy the icon?
read about DestroyIcon
It is only necessary to call DestroyIcon for icons and cursors
created with the following functions: CreateIconFromResourceEx (if
called without the LR_SHARED flag), CreateIconIndirect, and
CopyIcon. Do not use this function to destroy a shared icon. A
shared icon is valid as long as the module from which it was loaded
remains in memory. The following functions obtain a shared icon.
LoadIcon
LoadImage (if you use the LR_SHARED flag)
CopyImage (if you use the LR_COPYRETURNORG flag and the hImage parameter is a shared icon)
CreateIconFromResource
CreateIconFromResourceEx (if you use the LR_SHARED flag)
so you need call DestroyIcon for not shared icon, when you are finished using it
ComboBoxEx not destroy image list which you assign to it with CBEM_SETIMAGELIST - so this image list must be valid until ComboBoxEx valid and you must destroy it yourself later.
ImageList_AddIcon
Because the system does not save hicon, you can destroy it after the
macro returns
in other words ImageList_AddIcon make copy of your icon, and you can destroy your original icon, after macro return
for create 32 bit color icon try code like this:
HICON CreateGradientColorIcon(COLORREF iconColor, int width, int height)
{
HICON hIcon = 0;
ICONINFO ii = { TRUE };
ULONG n = width * height;
if (PULONG lpBits = new ULONG[n])
{
PULONG p = lpBits;
ULONG x, y = height, t;
do
{
x = width, t = --y << 8;
do
{
*p++ = iconColor | ((t * --x) / n << 24);
} while (x);
} while (y);
if (ii.hbmColor = CreateBitmap(width, height, 1, 32, lpBits))
{
if (ii.hbmMask = CreateBitmap(width, height, 1, 1, 0))
{
hIcon = CreateIconIndirect(&ii);
DeleteObject(ii.hbmMask);
}
DeleteObject(ii.hbmColor);
}
delete [] lpBits;
}
return hIcon;
}
when I draw (DrawIconEx(, DI_IMAGE|DI_MASK)) this icon over green mesh I view next:
To everyone who has stumbled upon this solution, I am simply posting a little bit more of a documented solution to RbMm's answer. This is basically the same as his solution (maybe not as performant, I'm not sure):
static HICON CreateIconFromBytes(HDC DC, int width, int height, uint32* bytes) {
HICON hIcon = NULL;
ICONINFO iconInfo = {
TRUE, // fIcon, set to true if this is an icon, set to false if this is a cursor
NULL, // xHotspot, set to null for icons
NULL, // yHotspot, set to null for icons
NULL, // Monochrome bitmap mask, set to null initially
NULL // Color bitmap mask, set to null initially
};
uint32* rawBitmap = new uint32[width * height];
ULONG uWidth = (ULONG)width;
ULONG uHeight = (ULONG)height;
uint32* bitmapPtr = rawBitmap;
for (ULONG y = 0; y < uHeight; y++) {
for (ULONG x = 0; x < uWidth; x++) {
// Bytes are expected to be in RGB order (8 bits each)
// Swap G and B bytes, so that it is in BGR order for windows
uint32 byte = bytes[x + y * width];
uint8 A = (byte & 0xff000000) >> 24;
uint8 R = (byte & 0xff0000) >> 16;
uint8 G = (byte & 0xff00) >> 8;
uint8 B = (byte & 0xff);
*bitmapPtr = (A << 24) | (R << 16) | (G << 8) | B;
bitmapPtr++;
}
}
iconInfo.hbmColor = CreateBitmap(width, height, 1, 32, rawBitmap);
if (iconInfo.hbmColor) {
iconInfo.hbmMask = CreateCompatibleBitmap(DC, width, height);
if (iconInfo.hbmMask) {
hIcon = CreateIconIndirect(&iconInfo);
if (hIcon == NULL) {
Log::Warning("Failed to create icon.");
}
DeleteObject(iconInfo.hbmMask);
} else {
Log::Warning("Failed to create color mask.");
}
DeleteObject(iconInfo.hbmColor);
} else {
Log::Warning("Failed to create bitmap mask.");
}
delete[] rawBitmap;
return hIcon;
}
This solution will work with STB image library for loading images. So you can literally just load an image with stb, then pass the byte data to this function, and you will get an icon as a result. I had a little bit of trouble setting the icon as well, and eventually did this to get that to work:
HICON icon = CreateIconFromBytes(DC, image.m_Width, image.m_Height, image.m_Pixels);
SendMessage(WND, WM_SETICON, ICON_SMALL, (LPARAM)icon);
SendMessage(WND, WM_SETICON, ICON_BIG, (LPARAM)icon);
SendMessage(WND, WM_SETICON, ICON_SMALL2, (LPARAM)icon);
The only thing you should note about this is that you should probably use 3 different sized icons for the SendMessage() functions, but other than that this worked good for me :)
Edit:
Here's the links to official MSDN documentation as well.
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createiconindirect
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createbitmap
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createcompatiblebitmap
https://learn.microsoft.com/en-us/windows/win32/menurc/using-icons
I am writing an extremely small C++ program to help me animate sprites. I'd like it to take data I copy to the clipboard from photoshop, manipulate it in my program, then overwrite the clipboard with the transform.
The problem though is that I'm not sure how to read the initial clipboard from photoshop.
I can load the clipboard with GetClipboardData(CF_DIB), and get a valid handle, but I've no idea how to use that handle. I've tried using SFML's Image::LoadFromMemory(handle, GlobalSize(handle)) which is able to load bitmap files from memory, but that doesn't seem to work.
Will I need to actually parse the entire format? What format structure would I be looking at in that case? Would there perhaps be any way I could quickly mangle the data so it might look like a bitmap file? Could it be easier/possible to simply save it to file using the windows API? (I could then load that file with SFML to edit, that way)
It's just a quick and dirty tool for myself to save a lot of grunt work in photoshop, so efficiency or robustness aren't important at all.
Learn the bitmap structure from Wikipedia and then write it out to a file and then write out the pixels..
I've tested the below with Paint on Windows 8.1. I opened an image with paint and then pressed Ctrl + C to copy to the clipboard.. then I ran the following code and it copied the clipboard image to the desktop:
#include <iostream>
#include <fstream>
#include <windows.h>
int main()
{
std::cout<<"Format Bitmap: "<<IsClipboardFormatAvailable(CF_BITMAP)<<"\n";
std::cout<<"Format DIB: "<<IsClipboardFormatAvailable(CF_DIB)<<"\n";
std::cout<<"Format DIBv5: "<<IsClipboardFormatAvailable(CF_DIBV5)<<"\n";
if (IsClipboardFormatAvailable(CF_DIB))
{
if (OpenClipboard(NULL))
{
HANDLE hClipboard = GetClipboardData(CF_DIB);
if (hClipboard != NULL && hClipboard != INVALID_HANDLE_VALUE)
{
void* dib = GlobalLock(hClipboard);
if (dib)
{
BITMAPINFOHEADER* info = reinterpret_cast<BITMAPINFOHEADER*>(dib);
BITMAPFILEHEADER fileHeader = {0};
fileHeader.bfType = 0x4D42;
fileHeader.bfOffBits = 54;
fileHeader.bfSize = (((info->bmiHeader.biWidth * info->bmiHeader.biBitCount + 31) & ~31) / 8
* info->bmiHeader.biHeight) + fileHeader.bfOffBits;
std::cout<<"Type: "<<std::hex<<fileHeader.bfType<<std::dec<<"\n";
std::cout<<"bfSize: "<<fileHeader.bfSize<<"\n";
std::cout<<"Reserved: "<<fileHeader.bfReserved1<<"\n";
std::cout<<"Reserved2: "<<fileHeader.bfReserved2<<"\n";
std::cout<<"Offset: "<<fileHeader.bfOffBits<<"\n";
std::cout<<"biSize: "<<info->bmiHeader.biSize<<"\n";
std::cout<<"Width: "<<info->bmiHeader.biWidth<<"\n";
std::cout<<"Height: "<<info->bmiHeader.biHeight<<"\n";
std::cout<<"Planes: "<<info->bmiHeader.biPlanes<<"\n";
std::cout<<"Bits: "<<info->bmiHeader.biBitCount<<"\n";
std::cout<<"Compression: "<<info->bmiHeader.biCompression<<"\n";
std::cout<<"Size: "<<info->bmiHeader.biSizeImage<<"\n";
std::cout<<"X-res: "<<info->bmiHeader.biXPelsPerMeter<<"\n";
std::cout<<"Y-res: "<<info->bmiHeader.biYPelsPerMeter<<"\n";
std::cout<<"ClrUsed: "<<info->bmiHeader.biClrUsed<<"\n";
std::cout<<"ClrImportant: "<<info->bmiHeader.biClrImportant<<"\n";
std::ofstream file("C:/Users/Brandon/Desktop/Test.bmp", std::ios::out | std::ios::binary);
if (file)
{
file.write(reinterpret_cast<char*>(&fileHeader), sizeof(BITMAPFILEHEADER));
file.write(reinterpret_cast<char*>(info), sizeof(BITMAPINFOHEADER));
file.write(reinterpret_cast<char*>(++info), bmp.dib.biSizeImage);
}
GlobalUnlock(dib);
}
}
CloseClipboard();
}
}
return 0;
}
I wasn't going to post an answer initially, after all you already have a good enough answer. But I guess I was coerced into doing so anyway, besides, this is the primary question that pops when you search for GetClipboardData CF_DIB, so might as well try to present a more complete solution.
Unfortunately, clipboard formats are a minefield. And GDI bitmaps are an even bigger minefield. CF_DIB gives you a "packed DIB", you do need to parse it to some extent if you actually want to do anything meaningful with it. This is the layout (pseudo code):
struct PACKED_DIB {
struct BITMAPINFO {
BITMAPINFOHEADER bih; // 40 bytes
DWORD optional_RGB_bitmaks[]; // (variable size)
DWORD optional_color_table[]; // (variable size)
}
BYTE pixel_data_array[]; // (variable size)
}
The total size of the structure is given by GlobalSize(). The crucial bit of information that is required for any further processing is the offset, in bytes, from the start of the BITMAPINFO structure to the start of the pixel data array. If the optional bitmasks and the color table are absent, this offset is constant 40 (sizeof(BITMAPINFOHEADER)). Whether this is the case depends entirely on how an application has put the bitmap into the clipboard. Most applications do this because it's the simplest way.
This code calculates that offset:
// Returns the offset, in bytes, from the start of the BITMAPINFO, to the start of the pixel data array, for a packed DIB.
static INT GetPixelDataOffsetForPackedDIB(const BITMAPINFOHEADER *BitmapInfoHeader)
{
INT OffsetExtra = 0;
if (BitmapInfoHeader->biSize == sizeof(BITMAPINFOHEADER) /* 40 */)
{
// This is the common BITMAPINFOHEADER type. In this case, there may be bit masks following the BITMAPINFOHEADER
// and before the actual pixel bits (does not apply if bitmap has <= 8 bpp)
if (BitmapInfoHeader->biBitCount > 8)
{
if (BitmapInfoHeader->biCompression == BI_BITFIELDS)
{
OffsetExtra += 3 * sizeof(RGBQUAD);
}
else if (BitmapInfoHeader->biCompression == 6 /* BI_ALPHABITFIELDS */)
{
// Not widely supported, but technically a valid DIB format.
// You *can* get this in the clipboard, although neither GDI nor stb_image will like it.
OffsetExtra += 4 * sizeof(RGBQUAD);
}
}
}
if (BitmapInfoHeader->biClrUsed > 0)
{
// We have no choice but to trust this value.
OffsetExtra += BitmapInfoHeader->biClrUsed * sizeof(RGBQUAD);
}
else
{
// In this case, the color table contains the maximum number for the current bit count (0 if > 8bpp)
if (BitmapInfoHeader->biBitCount <= 8)
{
// 1bpp: 2
// 4bpp: 16
// 8bpp: 256
OffsetExtra += sizeof(RGBQUAD) << BitmapInfoHeader->biBitCount;
}
}
return BitmapInfoHeader->biSize + OffsetExtra;
}
Below is a program that demonstrates several things you can do with this offset:
Write the clipboard image to a .bmp file
Load it from memory using SFML (sf::Image::loadFromMemory)
Put it back in the clipboard from an SFML image
Convert it to a HBITMAP (so it can be used in GDI)
Put it back in the clipboard from a GDI HBITMAP
#include <sdkddkver.h>
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#if DEMO_SFML
#include <SFML/Graphics.hpp>
#endif
static BOOL OpenClipboard_ButTryABitHarder(HWND ClipboardOwner);
static INT GetPixelDataOffsetForPackedDIB(const BITMAPINFOHEADER *BitmapInfoHeader);
static void PutBitmapInClipboard_AsDIB(HBITMAP hBitmap);
static void PutBitmapInClipboard_From32bppTopDownRGBAData(INT Width, INT Height, const void *Data32bppRGBA);
int wmain(int argc, wchar_t *argv[])
{
if (!OpenClipboard_ButTryABitHarder(NULL))
{
// Could not open clipboard. This usually indicates that another application is permanently blocking it.
return 1;
}
HGLOBAL ClipboardDataHandle = (HGLOBAL)GetClipboardData(CF_DIB);
if (!ClipboardDataHandle)
{
// Clipboard object is not a DIB, and is not auto-convertible to DIB
CloseClipboard();
return 0;
}
BITMAPINFOHEADER *BitmapInfoHeader = (BITMAPINFOHEADER *)GlobalLock(ClipboardDataHandle);
assert(BitmapInfoHeader); // This can theoretically fail if mapping the HGLOBAL into local address space fails. Very pathological, just act as if it wasn't a bitmap in the clipboard.
SIZE_T ClipboardDataSize = GlobalSize(ClipboardDataHandle);
assert(ClipboardDataSize >= sizeof(BITMAPINFOHEADER)); // Malformed data. While older DIB formats exist (e.g. BITMAPCOREHEADER), they are not valid data for CF_DIB; it mandates a BITMAPINFO struct. If this fails, just act as if it wasn't a bitmap in the clipboard.
INT PixelDataOffset = GetPixelDataOffsetForPackedDIB(BitmapInfoHeader);
// ============================================================================================================
// ============================================================================================================
//
// Example 1: Write it to a .bmp file
//
// The clipboard contains a packed DIB, whose start address coincides with BitmapInfoHeader, and whose total size is ClipboardDataSize.
// By definition, we can jam the whole DIB memory into a BMP file as-is, except that we need to prepend a BITMAPFILEHEADER struct.
// The tricky part is that for BITMAPFILEHEADER.bfOffBits, which must be calculated using the information in BITMAPINFOHEADER.
// The BMP file layout:
// #offset 0: BITMAPFILEHEADER
// #offset 14 (sizeof(BITMAPFILEHEADER)): BITMAPINFOHEADER
// #offset 14 + BitmapInfoHeader->biSize: Optional bit masks and color table
// #offset 14 + DIBPixelDataOffset: pixel bits
// #offset 14 + ClipboardDataSize: EOF
size_t TotalBitmapFileSize = sizeof(BITMAPFILEHEADER) + ClipboardDataSize;
wprintf(L"BITMAPINFOHEADER size: %u\r\n", BitmapInfoHeader->biSize);
wprintf(L"Format: %hubpp, Compression %u\r\n", BitmapInfoHeader->biBitCount, BitmapInfoHeader->biCompression);
wprintf(L"Pixel data offset within DIB: %u\r\n", PixelDataOffset);
wprintf(L"Total DIB size: %zu\r\n", ClipboardDataSize);
wprintf(L"Total bitmap file size: %zu\r\n", TotalBitmapFileSize);
BITMAPFILEHEADER BitmapFileHeader = {};
BitmapFileHeader.bfType = 0x4D42;
BitmapFileHeader.bfSize = (DWORD)TotalBitmapFileSize; // Will fail if bitmap size is nonstandard >4GB
BitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + PixelDataOffset;
HANDLE FileHandle = CreateFileW(L"test.bmp", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (FileHandle != INVALID_HANDLE_VALUE)
{
DWORD dummy = 0;
BOOL Success = true;
Success &= WriteFile(FileHandle, &BitmapFileHeader, sizeof(BITMAPFILEHEADER), &dummy, NULL);
Success &= WriteFile(FileHandle, BitmapInfoHeader, (DWORD)ClipboardDataSize, &dummy, NULL);
Success &= CloseHandle(FileHandle);
if (Success)
{
wprintf(L"File saved.\r\n");
}
}
#if DEMO_SFML
// ============================================================================================================
// ============================================================================================================
//
// Example 2: Load it from memory in SFML
//
// SFML expects a whole bitmap file, including its BITMAPFILEHEADER, in memory.
// So this is similar to Example 1, except in memory.
BYTE *BitmapFileContents = (BYTE *)malloc(TotalBitmapFileSize);
assert(BitmapFileContents);
memcpy(BitmapFileContents, &BitmapFileHeader, sizeof(BITMAPFILEHEADER));
// Append DIB
memcpy(BitmapFileContents + sizeof(BITMAPFILEHEADER), BitmapInfoHeader, ClipboardDataSize);
sf::Image image;
image.loadFromMemory(BitmapFileContents, TotalBitmapFileSize);
// The memory can be freed once the image has been loaded in SFML.
free(BitmapFileContents);
// Manipulate it:
image.flipHorizontally();
// Put it back in the clipboard:
PutBitmapInClipboard_From32bppTopDownRGBAData(image.getSize().x, image.getSize().y, image.getPixelsPtr());
#else
// ============================================================================================================
// ============================================================================================================
//
// Example 3: Convert to HBITMAP for GDI
//
BYTE *PixelDataFromClipboard = (BYTE *)BitmapInfoHeader + PixelDataOffset;
// This will only work if the DIB format is supported by GDI. Not all formats are supported.
BYTE *PixelDataNew;
HBITMAP hBitmap = CreateDIBSection(NULL, (BITMAPINFO *)BitmapInfoHeader, DIB_RGB_COLORS, (void **)&PixelDataNew, NULL, 0);
assert(hBitmap);
// Need to copy the data from the clipboard to the new DIBSection.
BITMAP BitmapDesc = {};
GetObjectW(hBitmap, sizeof(BitmapDesc), &BitmapDesc);
SIZE_T PixelDataBytesToCopy = (SIZE_T)BitmapDesc.bmHeight * BitmapDesc.bmWidthBytes;
SIZE_T PixelDataBytesAvailable = ClipboardDataSize - PixelDataOffset;
if (PixelDataBytesAvailable < PixelDataBytesToCopy)
{
// Malformed data; doesn't contain enough pixels.
PixelDataBytesToCopy = PixelDataBytesAvailable;
}
memcpy(PixelDataNew, PixelDataFromClipboard, PixelDataBytesToCopy);
// NOTE: While it is possible to create a DIB section without copying the pixel data, in general you'd want to
// copy it anyway because the clipboard needs to be closed asap.
// Draw something on it.
PixelDataNew[7] = 0;
PixelDataNew[11] = 100;
HDC hdc = CreateCompatibleDC(NULL);
assert(hdc);
SelectObject(hdc, hBitmap);
RECT rc = { 0, 0, BitmapDesc.bmWidth / 2, BitmapDesc.bmHeight / 2 };
HBRUSH brush = CreateSolidBrush(RGB(250, 100, 0));
FillRect(hdc, &rc, brush);
DeleteObject(brush);
DeleteDC(hdc);
// ============================================================================================================
// ============================================================================================================
//
// Copy it back to the clipboard.
//
PutBitmapInClipboard_AsDIB(hBitmap);
#endif // DEMO_SFML
GlobalUnlock(ClipboardDataHandle);
CloseClipboard();
return 0;
}
static BOOL OpenClipboard_ButTryABitHarder(HWND hWnd)
{
for (int i = 0; i < 20; ++i)
{
// This can fail if the clipboard is currently being accessed by another application.
if (OpenClipboard(hWnd)) return true;
Sleep(10);
}
return false;
}
// Returns the offset, in bytes, from the start of the BITMAPINFO, to the start of the pixel data array, for a packed DIB.
static INT GetPixelDataOffsetForPackedDIB(const BITMAPINFOHEADER *BitmapInfoHeader)
{
INT OffsetExtra = 0;
if (BitmapInfoHeader->biSize == sizeof(BITMAPINFOHEADER) /* 40 */)
{
// This is the common BITMAPINFOHEADER type. In this case, there may be bit masks following the BITMAPINFOHEADER
// and before the actual pixel bits (does not apply if bitmap has <= 8 bpp)
if (BitmapInfoHeader->biBitCount > 8)
{
if (BitmapInfoHeader->biCompression == BI_BITFIELDS)
{
OffsetExtra += 3 * sizeof(RGBQUAD);
}
else if (BitmapInfoHeader->biCompression == 6 /* BI_ALPHABITFIELDS */)
{
// Not widely supported, but valid.
OffsetExtra += 4 * sizeof(RGBQUAD);
}
}
}
if (BitmapInfoHeader->biClrUsed > 0)
{
// We have no choice but to trust this value.
OffsetExtra += BitmapInfoHeader->biClrUsed * sizeof(RGBQUAD);
}
else
{
// In this case, the color table contains the maximum number for the current bit count (0 if > 8bpp)
if (BitmapInfoHeader->biBitCount <= 8)
{
// 1bpp: 2
// 4bpp: 16
// 8bpp: 256
OffsetExtra += sizeof(RGBQUAD) << BitmapInfoHeader->biBitCount;
}
}
return BitmapInfoHeader->biSize + OffsetExtra;
}
// Helper function for interaction with libraries like stb_image.
// Data will be copied, so you can do what you want with it after this function returns.
static void PutBitmapInClipboard_From32bppTopDownRGBAData(INT Width, INT Height, const void *Data32bppRGBA)
{
// Nomenclature: Data at offset 0 is R top left corner, offset 1 is G top left corner, etc.
// This is pretty much the opposite of what a HBITMAP normally does.
assert(Width > 0);
assert(Height > 0);
assert(Data32bppRGBA);
// GDI won't help us here if we want to preserve the alpha channel. It doesn't support BI_ALPHABITFIELDS, and
// we can't use BI_RGB directly because BI_RGB actually means BGRA in reality.
// That means, unfortunately it's not going to be a simple memcpy :(
DWORD PixelDataSize = 4/*32bpp*/ * Width * Height;
// We need BI_BITFIELDS for RGB color masks here.
size_t TotalSize = sizeof(BITMAPINFOHEADER) + PixelDataSize;
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, TotalSize);
assert(hGlobal);
void *mem = GlobalLock(hGlobal);
assert(mem);
BITMAPINFOHEADER *bih = (BITMAPINFOHEADER *)mem;
bih->biSize = sizeof(BITMAPINFOHEADER);
bih->biWidth = Width;
bih->biHeight = -Height; // Negative height means top-down bitmap
bih->biPlanes = 1;
bih->biBitCount = 32;
bih->biCompression = BI_RGB;
bih->biSizeImage = PixelDataSize;
BYTE *PixelData = (BYTE *)mem + sizeof(BITMAPINFOHEADER);
DWORD NumPixels = Width * Height;
for (DWORD i = 0; i < NumPixels; ++i)
{
// Convert RGBA to BGRA
DWORD tmp = ((DWORD *)Data32bppRGBA)[i];
DWORD tmp2 = tmp & 0xff00ff00; // assumes LE
tmp2 |= (tmp >> 16) & 0xff;
tmp2 |= (tmp & 0xff) << 16;
((DWORD *)PixelData)[i] = tmp2;
}
GlobalUnlock(hGlobal);
EmptyClipboard();
SetClipboardData(CF_DIB, hGlobal);
// The hGlobal now belongs to the clipboard. Do not free it.
}
// Bitmap will be copied, so you can do what you want with it after this function returns.
static void PutBitmapInClipboard_AsDIB(HBITMAP hBitmap)
{
// Need this to get the bitmap dimensions.
BITMAP desc = {};
int tmp = GetObjectW(hBitmap, sizeof(desc), &desc);
assert(tmp != 0);
// We need to build this structure in a GMEM_MOVEABLE global memory block:
// BITMAPINFOHEADER (40 bytes)
// PixelData (4 * Width * Height bytes)
// We're enforcing 32bpp BI_RGB, so no bitmasks and no color table.
// NOTE: SetClipboardData(CF_DIB) insists on the size 40 version of BITMAPINFOHEADER, otherwise it will misinterpret the data.
DWORD PixelDataSize = 4/*32bpp*/ * desc.bmWidth * desc.bmHeight; // Correct alignment happens implicitly.
assert(desc.bmWidth > 0);
assert(desc.bmHeight > 0);
size_t TotalSize = sizeof(BITMAPINFOHEADER) + PixelDataSize;
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, TotalSize);
assert(hGlobal);
void *mem = GlobalLock(hGlobal);
assert(mem);
BITMAPINFOHEADER *bih = (BITMAPINFOHEADER *)mem;
bih->biSize = sizeof(BITMAPINFOHEADER);
bih->biWidth = desc.bmWidth;
bih->biHeight = desc.bmHeight;
bih->biPlanes = 1;
bih->biBitCount = 32;
bih->biCompression = BI_RGB;
bih->biSizeImage = PixelDataSize;
HDC hdc = CreateCompatibleDC(NULL);
assert(hdc);
HGDIOBJ old = SelectObject(hdc, hBitmap);
assert(old != nullptr); // This can fail if the hBitmap is still selected into a different DC.
void *PixelData = (BYTE *)mem + sizeof(BITMAPINFOHEADER);
// Pathologial "bug": If the bitmap is a DDB that originally belonged to a device with a different palette, that palette is lost. The caller would need to give us the correct HDC, but this is already insane enough as it is.
tmp = GetDIBits(hdc, hBitmap, 0, desc.bmHeight, PixelData, (BITMAPINFO *)bih, DIB_RGB_COLORS);
assert(tmp != 0);
// NOTE: This will correctly preserve the alpha channel if possible, but it's up to the receiving application to handle it.
DeleteDC(hdc);
GlobalUnlock(hGlobal);
EmptyClipboard();
SetClipboardData(CF_DIB, hGlobal);
// The hGlobal now belongs to the clipboard. Do not free it.
}
I intend this code to be mostly production-ready because I need it for myself, if anyone finds a problem I'd be happy to hear about it.
Some additional notes for reference:
Tested on Win10
Tested on WinXP (except SFML), although the %zu doesn't work in older CRTs, who knew
Error handling is not production-ready.
What the explanation of CF_DIB really wanted to say is "it's a packed DIB". There is no official guarantee that it will be a plain BITMAPINFOHEADER, i.e. biSize == 40 though, although it is likely that this is the case.
The BITMAPFINO documentation explains that the structure is really variable in length, and that BITMAPINFOHEADER.biClrUsed needs to be taken into account, but it fails to mention BI_BITFIELDS.
BITMAPINFOHEADER has more details on this, but fails to mention BI_ALPHABITFIELDS or the fact that the bitmasks are only present if the polymorphic BITMAPINFOHEADER struct is actually a plain BITMAPINFOHEADER (i.e. biSize == 40). Later versions, like BITMAPV5HEADER, include the bitmasks unconditionally.
All in all, the wikipedia article on the BMP file format contains a much more concise and coherent explanation of the DIB memory layout.
Older versions of Paint handled the clipboard, including the offset calculation in a very similar fashion to what I did above in GetDIBPixelDataOffset (obviously I can't post that verbatim here). It does not assume that biSize == 40. Newer versions of Paint use COleServerItem for clipboard handling.
As a final reference, the source code of GTK, which is used by GIMP and other cross-platform software, implements CF_DIB very similarly. That code even handles web browser specific formats, so a bit harder to follow. It's the transmute_cf_dib_to_image_bmp function. The function's length parameter comes from GlobalSize. Note that it also does not assume that biSize == 40.
If SetClipboardData is called with CF_BITMAP, it requires a DDB (it will silently fail if you pass it a HBITMAP that is really a DIB). A CF_BITMAP that is implicitly converted to a DIB uses BI_BITFIELDS, this also applies to screenshots (at least if the original DDB was compatible with the screen DC).
Putting bitmaps into the clipboard is a whole new can of worms. If a DDB is put in the clipboard with CF_BITMAP, that bitmap is not copied (at least not initially). If any program manipulates it, all programs accessing the clipboard will see the manipulated bitmap. However, as soon as any one application ever requests it as a CF_DIB, Windows applies a bunch of magic, and that is no longer true, the bitmap is now a copy. This does not apply to bitmaps that were put in the clipboard as CF_DIB, those immediately become immune to manipulations by other programs. CF_DIB seems to have fewer unpleasant implications and surprises, and also seems to be used by most applications. While you could try to preserve the original bitmap's format when putting it in the clipboard, I chose to use a fixed catch-all format for outgoing data because it's already crazy enough.
SetClipboardData implies that CF_DIB doesn't work with Windows Store apps, but I was unable to confirm that claim. Then again, the paragraph about a NULL owner is also incorrect.
Both PutBitmapInClipboard_AsDIB and PutBitmapInClipboard_From32bppTopDownRGBAData do preserve the alpha channel if possible, although GDI drawing functions as demonstrated don't support alpha and will destroy it (SFML will handle it just fine). Putting the alpha channel in the MSB and using BI_RGB seems to be the de-facto standard for storing alpha in DIBs.
I need to create a custom control to display bmp images with alpha channel. The background can be painted in different colors and the images have shadows so I need to truly "paint" the alpha channel.
Does anybody know how to do it?
I also want if possible to create a mask using the alpha channel information to know whether the mouse has been click on the image or on the transparent area.
Any kind of help will be appreciated!
Thanks.
Edited(JDePedro): As some of you have suggested I've been trying to use alpha blend to paint the bitmap with alpha channel. This just a test I've implemented where I load a 32-bit bitmap from resources and I try to paint it using AlphaBlend function:
void CAlphaDlg::OnPaint()
{
CClientDC dc(this);
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP);
BITMAP BitMap;
bitmap.GetBitmap(&BitMap);
int nWidth = BitMap.bmWidth;
int nHeight = BitMap.bmHeight;
CBitmap *pOldBitmap = dcMem.SelectObject(&bitmap);
BLENDFUNCTION m_bf;
m_bf.BlendOp = AC_SRC_OVER;
m_bf.BlendFlags = 0;
m_bf.SourceConstantAlpha = 255;
m_bf.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(dc.GetSafeHdc(), 100, 100, nWidth, nHeight, dcMem.GetSafeHdc(), 0, 0,nWidth, nHeight,m_bf);
dcMem.SelectObject(pOldBitmap);
CDialog::OnPaint();
}
This is just a test so I put the code in the OnPaint of the dialog (I also tried the AlphaBlend function of the CDC object).
The non-transparent areas are being painted correctly but I get white where the bitmap should be transparent.
Any help???
This is a screenshot..it's not easy to see but there is a white rectangle around the blue circle:
alt text http://img385.imageshack.us/img385/7965/alphamh8.png
Ok. I got it! I have to pre-multiply every pixel for the alpha value. Someone can suggest the optimized way to do that?
For future google users, here is a working pre-multiply function. Note that this was taken from http://www.viksoe.dk/code/alphatut1.htm .
inline void PremultiplyBitmapAlpha(HDC hDC, HBITMAP hBmp)
{
BITMAP bm = { 0 };
GetObject(hBmp, sizeof(bm), &bm);
BITMAPINFO* bmi = (BITMAPINFO*) _alloca(sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
::ZeroMemory(bmi, sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
BOOL bRes = ::GetDIBits(hDC, hBmp, 0, bm.bmHeight, NULL, bmi, DIB_RGB_COLORS);
if( !bRes || bmi->bmiHeader.biBitCount != 32 ) return;
LPBYTE pBitData = (LPBYTE) ::LocalAlloc(LPTR, bm.bmWidth * bm.bmHeight * sizeof(DWORD));
if( pBitData == NULL ) return;
LPBYTE pData = pBitData;
::GetDIBits(hDC, hBmp, 0, bm.bmHeight, pData, bmi, DIB_RGB_COLORS);
for( int y = 0; y < bm.bmHeight; y++ ) {
for( int x = 0; x < bm.bmWidth; x++ ) {
pData[0] = (BYTE)((DWORD)pData[0] * pData[3] / 255);
pData[1] = (BYTE)((DWORD)pData[1] * pData[3] / 255);
pData[2] = (BYTE)((DWORD)pData[2] * pData[3] / 255);
pData += 4;
}
}
::SetDIBits(hDC, hBmp, 0, bm.bmHeight, pBitData, bmi, DIB_RGB_COLORS);
::LocalFree(pBitData);
}
So then your OnPaint becomes:
void MyButton::OnPaint()
{
CPaintDC dc(this);
CRect rect(0, 0, 16, 16);
static bool pmdone = false;
if (!pmdone) {
PremultiplyBitmapAlpha(dc, m_Image);
pmdone = true;
}
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 255;
bf.AlphaFormat = AC_SRC_ALPHA;
HDC src_dc = m_Image.GetDC();
::AlphaBlend(dc, rect.left, rect.top, 16, 16, src_dc, 0, 0, 16, 16, bf);
m_Image.ReleaseDC();
}
And the loading of the image (in the constructor of your control):
if ((HBITMAP)m_Image == NULL) {
m_Image.LoadFromResource(::AfxGetResourceHandle(), IDB_RESOURCE_OF_32_BPP_BITMAP);
}
The way I usually do this is via a DIBSection - a device independent bitmap that you can modify the pixels of directly. Unfortunately there isn't any MFC support for DIBSections: you have to use the Win32 function CreateDIBSection() to use it.
Start by loading the bitmap as 32-bit RGBA (that is, four bytes per pixel: one red, one green, one blue and one for the alpha channel). In the control, create a suitably sized DIBSection. Then, in the paint routine
Copy the bitmap data into the DIBSection's bitmap data, using the alpha channel byte to blend the bitmap image with the background colour.
Create a device context and select the DIBSection into it.
Use BitBlt() to copy from the new device context to the paint device context.
You can create a mask given the raw bitmap data simply by looking at the alpha channel values - I'm not sure what you're asking here.
You need to do an alpha blend with your background color, then take out the alpha channel to paint it to the control.
The alpha channel should just be every 4th byte of your image. You can use that directly for your mask, or you can just copy every 4th byte to a new mask image.
Painting it is very easy with the AlphaBlend function.
As for you mask, you'll need to get the bits of the bitmap and examine the alpha channel byte for each pixel you're interested in.
An optimised way to pre-multiply the RGB channels with the alpha channel is to set up a [256][256] array containing the calculated multiplication results. The first dimension is the alpha value, the second is the R/G/B value, the values in the array are the pre-multiplied values you need.
With this array set up correctly, you can calculate the value you need like this:
R = multiplicationLookup[alpha][R];
G = multiplicationLookup[alpha][G];
B = multiplicationLookup[alpha][B];
You are on the right track, but need to fix two things.
First use ::LoadImage( .. LR_CREATEDIBSECTION ..) instead of CBitmap::LoadBitmap. Two, you have to "pre-multiply" RGB values of every pixel in a bitmap to their respective A value. This is a requirement of AlphaBlend function, see AlphaFormat description on this MSDN page. T
The lpng has a working code that does the premultiplication of the DIB data.