Create 32 bit color Icon programmatically - c++

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

Related

Anti aliasing in MFC

I'm trying to implement anti-aliasing in my MFC app, I'm using the technique described in this tutorial.
Create a bitmap (2x, 4x, 8x) the size of the original bitmap.
Draw on the resized bitmap (I'm only using simple figures (lines, circles and etc)).
Set StretchBlt Mode to HalfTone.
And Resize with StretchBlt to the original size.
Using this way, drawing in the resized bitmap it works, but I want to create a more generic function that receives a bitmap with the drawing already made and return with the anti-aliasing, I tried this:
static HBITMAP AntiAliasing(HBITMAP hBitmap)
{
int escala = 4;
HBITMAP bmp = __copia(hBitmap); // Copy the bitmap.
HDC hMemDC = CreateCompatibleDC(NULL);
HBITMAP bmpAntigo1 = (HBITMAP)::SelectObject(hMemDC, bmp);
BITMAP bitmap;
::GetObject(hBitmap, sizeof(BITMAP), &bitmap);
// Create a bitmap (2x, 4x, 8x) the size of the original bitmap.
HDC hDCDimensionado = ::CreateCompatibleDC(hMemDC);
HBITMAP bmpDimensionado = ::CreateCompatibleBitmap(hDCDimensionado,
bitmap.bmWidth * escala,
bitmap.bmHeight * escala);
HBITMAP hBmpVelho = (HBITMAP)::SelectObject(hDCDimensionado, bmpDimensionado);
// I also tried with {BLACKONWHITE, HALFTONE, WHITEONBLACK}
int oldStretchBltMode2 = ::SetStretchBltMode(hDCDimensionado, COLORONCOLOR);
// Resize the bitmap to the new size.
::StretchBlt(hDCDimensionado,
0, 0, bitmap.bmWidth * escala, bitmap.bmHeight * escala,
hMemDC,
0, 0, bitmap.bmWidth, bitmap.bmHeight,
SRCCOPY);
/*
* Here the bitmap has lost his colors and became black and white.
*/
::SetStretchBltMode(hDCDimensionado, oldStretchBltMode2);
// Set StretchBltMode to halfTone so can mimic the anti aliasing effect.
int oldStretchBltMode = ::SetStretchBltMode(hMemDC, HALFTONE);
// resize to the original size.
::StretchBlt(hMemDC,
0, 0, bitmap.bmWidth, bitmap.bmHeight,
hDCDimensionado,
0, 0, escala * bitmap.bmWidth, escala * bitmap.bmHeight,
SRCCOPY);
::SetStretchBltMode(hMemDC, oldStretchBltMode);
::SelectObject(hMemDC, bmpAntigo1);
::DeleteDC(hMemDC);
::SelectObject(hDCDimensionado, hBmpVelho);
DeleteDC(hDCDimensionado);
return bmp;
}
But this function doesn't work, the result loses its colors (all drawings became black) and there isn't anti aliasing.
Any help will be appreciated!
From documentation for CreateCompatibleBitmap:
Note: When a memory device context is created, it initially has a
1-by-1 monochrome bitmap selected into it. If this memory device
context is used in CreateCompatibleBitmap, the bitmap that is created
is a monochrome bitmap. To create a color bitmap, use the HDC that was
used to create the memory device context, as shown in the following
code:
Change the code and supply hdc for the desktop as show below:
HDC hdc = ::GetDC(0);
HBITMAP bmpDimensionado = ::CreateCompatibleBitmap(hdc, ...)
::ReleaseDC(0, hdc);
This will show the image, however this method will not produce the desired effect because it simply magnifies each pixel to larger size and reduces it back to the original pixel. There is no blending with neighboring pixels.
Use other methods such Direct2D with Gaussian blur effect, or use GDI+ instead with interpolation mode:
Gdiplus::GdiplusStartup...
void foo(HDC hdc)
{
Gdiplus::Bitmap bitmap(L"file.bmp");
if(bitmap.GetLastStatus() != 0)
return 0;
auto w = bitmap.GetWidth();
auto h = bitmap.GetHeight();
auto maxw = w * 2;
auto maxh = h * 2;
Gdiplus::Bitmap membmp(maxw, maxh);
Gdiplus::Graphics memgr(&membmp);
memgr.SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBilinear);
memgr.DrawImage(&bitmap, 0, 0, maxw, maxh);
Gdiplus::Graphics gr(hdc);
gr.SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBilinear);
gr.DrawImage(&membmp, 0, 0, w, h);
}
If target window is at least Vista, use GDI+ version 1.1 with blur effect. See also How to turn on GDI+ 1.1 in MFC project
#define GDIPVER 0x0110 //add this to precompiled header file
void blur(HDC hdc)
{
Gdiplus::Graphics graphics(hdc);
Gdiplus::Bitmap bitmap(L"file.bmp");
if(bitmap.GetLastStatus() != 0)
return;
Gdiplus::Blur blur;
Gdiplus::BlurParams blur_param;
blur_param.radius = 3; //change the radius for different result
blur_param.expandEdge = TRUE;
blur.SetParameters(&blur_param);
bitmap.ApplyEffect(&blur, NULL);
graphics.DrawImage(&bitmap, 0, 0);
}

How to draw text with transparency using GDI?

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

Completely transparent image

Does anyone know how I can create a Completely transparent image or given am image (HBITMAP) how can I wipe it Completely so that all pixels in it are 100% transparent?
Thank you.
The bitmap needs to be 32-bit so it has an alpha channel that you can set opacity values with. If your image is not 32-bit, you will have to create a new 32-bit bitmap and copy the original pixels into it.
I also found the solution without the use of GDI+.
BITMAPV5HEADER bi = {sizeof(BITMAPV5HEADER), 320, 240, 1, 32, BI_BITFIELDS, 0, 0, 0, 0, 0, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000};
HDC hDc = ::CreateCompatibleDC(0);
if(NULL != hDc)
{
RGBQUAD* pArgb;
HBITMAP tBmp = ::CreateDIBSection(hDc, (BITMAPINFO*)&bi, DIB_RGB_COLORS, (void**)&pArgb, NULL, 0);
if(NULL != tBmp)
{
int x, y;
DWORD pBits;
pBits = (DWORD*)pArgb;
for(y = 0; y < bi.bV5Height; y++)
{
for(x = 0; x < bi.bV5Width; x++)
{
*pBits = 0;
pBits++;
}
}
}
::DeleteObject(hDc);
}
tBmp will be a 320 x 240 bitmap that is completely transparent
You can use a monochrome bitmap as a mask to create transparent images from colour ones. This is quite complex. See this example (in VB but uses Win32 APIs directly)
Alternatively, the TransparentBlt function might make what you're trying to do unnecessary.
I found the answer in GDI+. The best way to do this is to attach the device context of the image that you are working with with GDI+ graphic object and then call its clear method.

Windows 7 and ScreenShot.cpp GDI+ PNG problemo

was using XP without issue for a long time. switched to 7 and trying to capture screenshots with my previously functioning code no longer works. simple concept and relatively generic code...just find the window that i call and save it as a .png. any ideas what might make this bad boy run again? can't debug with my current setup, but it makes it all the way and spits out the error message after bmp->save(...) ...couldn't save image file. edit: also a file does get created/saved, but it is blank and not written to. perhaps the bitmap encoding or GDI is screwed up?
bool CScreenShot::Snap(CString wintitle, CString file, CString& ermsg)
{
ermsg = ""; // no error message
// create screen shot bitmap
EnumWinProcStruct prm = {0, (LPSTR)(LPCTSTR)wintitle, 0};
// Find the descriptor of the window with the caption wintitle
EnumDesktopWindows(0, EnumWindowsProc, (LPARAM)&prm);
if(!prm.hwnd)
{
ermsg.Format("couldn't find window \"%s\"", wintitle);
return false;
}
// Make the window the topmost window
SetWindowPos(prm.hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
Sleep(300);
// Get device context for the top-level window and client rect
HDC hDC = GetDC(prm.hwnd);
RECT rc;
GetClientRect(prm.hwnd, &rc);
HDC memDC = CreateCompatibleDC(hDC);
// Set the size and color depth for the screen shot image
BITMAPINFO bmpInfo;
memset(&bmpInfo, 0, sizeof(bmpInfo));
bmpInfo.bmiHeader.biSize = sizeof(bmpInfo.bmiHeader);
bmpInfo.bmiHeader.biWidth = rc.right - rc.left;
bmpInfo.bmiHeader.biHeight = rc.bottom - rc.top;
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 24;
bmpInfo.bmiHeader.biCompression = BI_RGB;
bmpInfo.bmiHeader.biSizeImage = bmpInfo.bmiHeader.biWidth * bmpInfo.bmiHeader.biHeight * 3;
// Create memory buffer and perform a bit-block transfer of the color data from the window to the memory
LPVOID addr;
HBITMAP memBM = CreateDIBSection(memDC, &bmpInfo, DIB_RGB_COLORS, &addr, 0, 0);
HGDIOBJ stdBM = SelectObject(memDC, memBM);
BOOL OK = BitBlt(memDC, 0, 0, bmpInfo.bmiHeader.biWidth, bmpInfo.bmiHeader.biHeight, hDC, 0, 0, SRCCOPY);
ReleaseDC(prm.hwnd, hDC);
SetWindowPos(prm.hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
// Initialize GDI+.
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
if(GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) != Ok)
{
ermsg.Format("couldn't start GDI+");
return false;
}
// Create a Bitmap object for work with images defined by pixel data from the GDI HBitmap and the GDI HPalette.
Bitmap* bmp = ::new Bitmap(memBM, DIB_RGB_COLORS);
SelectObject(memDC, stdBM);
DeleteObject(memBM);
DeleteDC(memDC);
// Find the encoder for "image/png" mime type
CLSID encoderClsid;
EncoderParameters encoderParameters;
GetEncoderClsid(L"image/png", &encoderClsid);
encoderParameters.Count = 0;
// Convert file name to Unicode (wide-char) string.
WCHAR fn[_MAX_PATH];
MultiByteToWideChar(CP_THREAD_ACP, MB_PRECOMPOSED, file, file.GetLength() + 1, fn, _MAX_PATH);
// Save the screen shot into the specified file using image encoder with the mime style "image/png"
if(bmp->Save(fn, &encoderClsid, &encoderParameters) != Ok)
{
ermsg.Format("couldn't save image file \"%s\"", file);
return false;
}
::delete bmp;
GdiplusShutdown(gdiplusToken);
return true;
}
The error message implies that you're trying to save the file to a folder that you don't have permission to write to. Many folders such as Program Files are now protected. Since you didn't include the path in your sample code I'm unable to determine if this is the actual problem.
Edit: Another possibility is that the Bitmap is improperly constructed which causes the Save to fail. The second parameter to the constructor is supposed to be a handle to a palette, I think DIB_RGB_COLORS would be invalid here and you should use NULL. Also there are a couple of caveats noted in the Microsoft documentation and perhaps the different OS versions react differently when you break the rules:
You are responsible for deleting the GDI bitmap and the GDI palette. However, you should not delete the GDI bitmap or the GDI palette until after the GDI+ Bitmap::Bitmap object is deleted or goes out of scope.
Do not pass to the GDI+ Bitmap::Bitmap constructor a GDI bitmap or a GDI palette that is currently (or was previously) selected into a device context.
win7 won't accept encoderParameters.Count == 0 for some reason. Set that == 1 and you should be all set.
you probably could also just remove that parameter from Save() (overloaded)

How to draw 32-bit alpha channel bitmaps?

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.