I have an MFC application which is support multi-language. To support multi-language, I have developed an API that can calculate drawing width of a String(CString). It works perfectly for English language only. For other unicode language like Russian,Hindi,Arabic(RTL) etc. it cannot calculate exact width of a String. Here below is the API code:
CRect MyUtil::GetTextRect(LPCTSTR str, CRect* rect, UINT format, MyFontClass *textFont /*, BOOL getActualRect*/)
{
if (str == NULL || _tcslen(str) == 0 || rect == NULL || rect->Width() <= 0 || rect->Height() <= 0 || textFont == NULL)
return CRect(0, 0, 0, 0);
CFont *font = textFont->GetCFont();
HDC textHDC = ::GetDC(NULL);
if (textHDC == NULL)
return CRect(0, 0, 0, 0);
HFONT hfont = (HFONT)(font->GetSafeHandle());
HFONT hOldFont = (HFONT)::SelectObject(textHDC, hfont);
CRect textRect(rect->left, rect->top, rect->Width(), rect->Height());
int result = ::DrawText(textHDC, str, -1, &textRect, format | DT_CALCRECT);
::SelectObject(textHDC, hOldFont);
::ReleaseDC(NULL, textHDC);
CRect retRect(textRect.left, textRect.top, textRect.Width() + 1, textRect.Height() + 1);
//if(getActualRect == FALSE)
//{
// retRect.SetRect(retRect.left, retRect.top
// , retRect.Width() / textFont->GetDPIEnlargeRate(), retRect.Height() / textFont->GetDPIEnlargeRate());
//}
return result == 0 ? CRect(0, 0, 0, 0) : retRect;
}
Here below is the calling method:
Suppose I have a string ID named: "SOFTWARE_LICENSE" in *.resx file having text below:
Text for English:
<data name="SOFTWARE_LICENSE" xml:space="preserve">
<value>Software Licence</value>
</data>
Text for Russian:
<data name="SOFTWARE_LICENSE" xml:space="preserve">
<value>Лицензия программного обеспечения</value>
</data>
Calling Method:
CString strSL = AfxGetStrRes(_T("SOFTWARE_LICENSE"));
MyFontClass txtFont14Regular = MyFontTemplate::CreateFont(_T("Segoe UI"), -14, FW_NORMAL);
int textWidth = MyUtil::GetTextRect(strSL, &CRect(0, 0, 1000, 1000), DT_LEFT | DT_VCENTER | DT_SINGLELINE,txtFont14Regular).Width();
I need this text width to set the Size of the UI controls like button, Checkbox etc for multi-language (All UI controls are customized).
MyUtil::GetTextRect can calculate the width for English language only. For other language calculated width is not perfect, either large or too small.
Is there any way to calculate the accurate text width for Unicode String?
This used to work for me:
CDC *pDC = CDC::FromHandle(textHDC);
CSize size(pDC->GetTextExtent(text));
// use size.cx, size.cy
Related
I'm trying to get the data (byte[]) of the current mouse cursor, but one type of cursor (known as MaskedColor) needs special treatment.
http://elektronotdienst-nuernberg.de/bugs/cursor.html
It's the grab and grabbing cursors from this page.
I wonder if there's any way to combine somehow the mask with the color data.
The color data comes with alpha = 0, so there's no transparency (which is available in the mask).
The mask looks like this (32x32, 128 bytes):
And the color info is this (32x32, 4096 bytes, but with alpha=0):
Getting the cursor references:
var cursorInfo = new CursorInfo(false); //Create struct.
if (!User32.GetCursorInfo(out cursorInfo))
return;
if (cursorInfo.Flags != ScreenToGif.Native.Constants.CursorShowing)
{
Gdi32.DeleteObject(cursorInfo.CursorHandle);
return;
}
var iconHandle = User32.CopyIcon(cursorInfo.CursorHandle);
if (iconHandle == IntPtr.Zero)
{
Gdi32.DeleteObject(cursorInfo.CursorHandle);
return;
}
if (!User32.GetIconInfo(iconHandle, out var iconInfo))
{
User32.DestroyIcon(iconHandle);
Gdi32.DeleteObject(cursorInfo.CursorHandle);
return;
}
Getting the cursor color and mask buffers:
try
{
//Color.
var colorHeader = new BitmapInfoHeader(false);
Gdi32.GetDIBits(_windowDeviceContext, iconInfo.Color, 0, 0, null, ref colorHeader, DibColorModes.RgbColors);
//Mask.
var maskHeader = new BitmapInfoHeader(false);
Gdi32.GetDIBits(_windowDeviceContext, iconInfo.Mask, 0, 0, null, ref maskHeader, DibColorModes.RgbColors);
Whenever there's a color buffer, draw to a bitmap to get the cursor animation steps.
if (colorHeader.Height != 0)
{
//Create bitmap.
var compatibleBitmap = Gdi32.CreateCompatibleBitmap(_windowDeviceContext, colorHeader.Width, colorHeader.Height);
var oldBitmap = Gdi32.SelectObject(_compatibleDeviceContext, compatibleBitmap);
//Draw image.
var ok = User32.DrawIconEx(_compatibleDeviceContext, 0, 0, cursorInfo.CursorHandle, 0, 0, _cursorStep, IntPtr.Zero, DrawIconFlags.Image);
if (!ok)
{
_cursorStep = 0;
User32.DrawIconEx(_compatibleDeviceContext, 0, 0, cursorInfo.CursorHandle, 0, 0, _cursorStep, IntPtr.Zero, DrawIconFlags.Image);
}
else
_cursorStep++;
Here comes the tricky part, if there's a mask, I need to apply to that cursor color buffer to know which pixels are transparent or not.
I tried to call DrawIconEx() passing Normal which Mask + Color in the last parameter, but without any difference.
if (maskHeader.SizeImage > 0)
{
//User32.DrawIconEx(_compatibleDeviceContext, 0, 0, cursorInfo.CursorHandle, 0, 0, _cursorStep, IntPtr.Zero, DrawIconFlags.Mask);
var ok2 = Gdi32.MaskBlt(_compatibleDeviceContext, 0, 0, colorHeader.Width, colorHeader.Height, compatibleBitmap, 0, 0, iconInfo.Mask, 0, 0, (int)CopyPixelOperations.SourceErase);
}
The last part is to actually get the final cursor as a byte[].
colorHeader.Height *= -1;
var colorBuffer = new byte[colorHeader.SizeImage];
Gdi32.GetDIBits(_windowDeviceContext, compatibleBitmap, 0, (uint)(colorHeader.Height * -1), colorBuffer, ref colorHeader, DibColorModes.RgbColors);
//Send colorBuffer
//Erase bitmaps.
Gdi32.SelectObject(_compatibleDeviceContext, oldBitmap);
Gdi32.DeleteObject(compatibleBitmap);
return;
}
For the mask monochrome cursor, just get the mask itself as byte[].
var maskBuffer = new byte[maskHeader.SizeImage];
maskHeader.Height *= -1;
Gdi32.GetDIBits(_windowDeviceContext, iconInfo.Mask, 0, (uint)(maskHeader.Height * -1), maskBuffer, ref maskHeader, DibColorModes.RgbColors);
//Send maskBuffer
}
finally
{
Gdi32.DeleteObject(iconInfo.Color);
Gdi32.DeleteObject(iconInfo.Mask);
User32.DestroyIcon(iconHandle);
Gdi32.DeleteObject(cursorInfo.CursorHandle);
}
Edit
Well, I can merge the mask buffer with the color buffer manually, but the issue is that I don't have a straightforward way to detect if the cursor needs the mask or not.
It works for the masked-color cursors:
But it butchers normal cursors (semi-transparent pixels turn opaque):
Both have a mask and color data, but the only difference is that the masked cursor will have its color buffer with all alpha = 0.
var colorHeight = colorHeader.Height * -1;
var colorWidth = colorHeader.Width; //Bug: For some reason, after calling GetDIBits() for the mask, the width of the color struct shifts.
var maskBuffer2 = new byte[maskHeader.SizeImage];
maskHeader.Height *= -1;
Gdi32.GetDIBits(_windowDeviceContext, iconInfo.Mask, 0, (uint)(maskHeader.Height * -1), maskBuffer2, ref maskHeader, DibColorModes.RgbColors);
var targetPitch = colorBuffer.Length / colorHeight;
var cursorPitch = maskBuffer2.Length / maskHeader.Height * -1;
//Merge mask with color.
for (var row = 0; row < colorWidth; row++)
{
//128 in binary.
byte mask = 0x80;
for (var col = 0; col < colorHeight; col++)
{
//Reads current pixel and merge with mask.
//Each mask byte holds information for 8 pixels.
var targetIndex = row * targetPitch + col * 4;
var xor = (maskBuffer2[row * cursorPitch + col / 8] & mask) == mask;
colorBuffer[targetIndex + 3] = (byte) (xor ? 255 : 0);
//Shifts the mask around until it reaches 1, then resets it back to 128.
if (mask == 0x01)
mask = 0x80;
else
mask = (byte)(mask >> 1);
}
}
The non-elegant way to know for sure that I need the mask is to check all alphas in the cursor buffer, if all zero, then I need the mask.
var needsMask = true;
for (var index = 0; index < colorBuffer.Length; index += 4)
{
if (colorBuffer[index] == 0) continue;
//If there's any non-zero alpha value, it means that the mask is not necessary.
needsMask = false;
break;
}
if (!needsMask)
{
//Send colorBuffer.
return;
}
//Send colorBuffer + maskBuffer.
At least it gives me the correct result:
I have the following code in an otherwise default VisualStudio project. It passes DT_CALCRECT to DrawTextW to calculate the rectangle to draw some text, then it uses that rectangle to draw the text. To test it yourself just paste this code into a default VisualStudio project:
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
{
wchar_t txt[] = L"abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef\r\nabc\r\n123456";
BOOL useDT_RIGHT = TRUE; // <<< ** SWITCH THIS BETWEEN TRUE AND FALSE **
wchar_t buf1[100] = {0};
wchar_t buf2[100] = {0};
RECT r1 = {0, 0, 192, 1000};
RECT r2 = {r1.right + 10, r1.top, r1.right + 400, r1.top + 100};
int ret1, ret2;
FillRect(hdc, &r1, (HBRUSH)GetStockObject(GRAY_BRUSH));
ret1 = DrawTextW(hdc, txt, -1, &r1,
DT_CALCRECT |
DT_WORDBREAK |
(useDT_RIGHT == FALSE ? DT_LEFT : DT_RIGHT)
);
if(ret1 == 0) MessageBoxW(NULL, L"ret1 == 0", NULL, MB_OK);
wsprintfW(buf1, L"useDT_RIGHT = %i\r\nDT_CALCRECT returned %i %i %i %i\r\nret1 = %i\r\n", useDT_RIGHT, r1.left, r1.top, r1.right, r1.bottom, ret1);
ret2 = DrawTextW(hdc, txt, -1, &r1,
DT_WORDBREAK |
(useDT_RIGHT == FALSE ? DT_LEFT : DT_RIGHT)
);
if(ret2 == 0) MessageBoxW(NULL, L"ret2 == 0", NULL, MB_OK);
wsprintfW(buf2, L"%sret2 = %i", buf1, ret2);
DrawTextW(hdc, buf2, -1, &r2, DT_LEFT);
}
EndPaint(hWnd, &ps);
break;
In the code, if useDT_RIGHT is set to FALSE, the text is left aligned and DT_CALCRECT returns the correct rectangle, as shown below:
http://i64.tinypic.com/2ptw2dk.png
If useDT_RIGHT is set to TRUE, the text is right aligned but DT_CALCRECT returns an incorrect rectangle, as shown below:
http://i68.tinypic.com/nwx9co.png
Or rather, it may be returning a correct rectangle, and the subsequent call to actually draw the text is drawing it incorrectly, it's impossible to tell.
The docs for DT_CALCRECT say "If there are multiple lines of text, DrawText uses the width of the rectangle pointed to by the lpRect parameter and extends the base of the rectangle to bound the last line of text. If the largest word is wider than the rectangle, the width is expanded. If the text is less than the width of the rectangle, the width is reduced."
What I would expect is that the rectangle returned by DrawTextW would be the right size to draw the text (in the actual code, the rectangle is also used for positioning the surrounding controls, so just expanding it willy nilly won't really help). I would also expect the text to be properly right aligned (ie. the opposite of the left aligned text), and not the mess it is as shown in the second screenshot above. By properly right aligned I mean as shown in this screenshot of wordpad:
http:
//i63.tinypic.com/qqya1u.png
(Please remove the space from this link to make it work.)
What is wrong with this code? Why does DT_CALCRECT with DT_RIGHT not produce the expected results? Or, if it is, why is the second call to DrawTextW not drawing it correctly?
It seems that this behavior is either a bug or by design. Maybe DT_WORDBREAK removes spaces and that's why it produces a narrower rectangle when using DT_RIGHT. Anyway, here is a way to make DrawText behave the same way when using DT_CALCRECT with either DT_LEFT or DT_RIGHT, you can test this code (check the comment that starts with FIX):
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
{
wchar_t txt[] = L"abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef\r\nabc\r\n123456";
BOOL useDT_RIGHT = FALSE; // TRUE; // <<< ** SWITCH THIS BETWEEN TRUE AND FALSE **
wchar_t buf1[100] = { 0 };
wchar_t buf2[100] = { 0 };
RECT r1 = { 0, 0, 192, 1000 };
RECT r2 = { r1.right + 10, r1.top, r1.right + 400, r1.top + 100 };
int ret1, ret2;
FillRect(hdc, &r1, (HBRUSH)GetStockObject(GRAY_BRUSH));
ret1 = DrawTextW(hdc, txt, -1, &r1,
DT_CALCRECT |
DT_WORDBREAK |
(useDT_RIGHT == FALSE ? DT_LEFT : DT_RIGHT)
);
if (ret1 == 0) MessageBoxW(NULL, L"ret1 == 0", NULL, MB_OK);
// FIX: The following two lines make DrawText with DT_CALCRECT behave the same way for DT_LEFT and DT_RIGHT
r1.right = 192;
r1.bottom = ret1;
wsprintfW(buf1, L"useDT_RIGHT = %i\r\nDT_CALCRECT returned %i %i %i %i\r\nret1 = %i\r\n", useDT_RIGHT, r1.left, r1.top, r1.right, r1.bottom, ret1);
ret2 = DrawTextW(hdc, txt, -1, &r1,
DT_WORDBREAK |
(useDT_RIGHT == FALSE ? DT_LEFT : DT_RIGHT)
);
if (ret2 == 0) MessageBoxW(NULL, L"ret2 == 0", NULL, MB_OK);
wsprintfW(buf2, L"%sret2 = %i", buf1, ret2);
DrawTextW(hdc, buf2, -1, &r2, DT_LEFT);
}
EndPaint(hWnd, &ps);
break;
I have a list control in report mode.
I fill this list control with data and then I auto size all columns with LVM_SETCOLUMNWIDTH. Depending on the data the list control may end up with a horizontal scrollbar or not.
So far so good. But now I'd like to get the minimum width the list control should have so no horizontal scrollbar is needed. Knowing that size I could resize the list control in order to get rid of the horizontal scrollbar.
Any ideas ?
Since you already know the required width, you can use that information and have the system calculate the corresponding window width for you. Either of the following APIs can be used: AdjustWindowRect or AdjustWindowRectEx. The height can be ignored.
int requiredWidth = 0;
for ( int index = 0; index < itemCount; ++index ) {
// calculate item width
requiredWidth += itemWidth;
}
RECT r = { 0, 0, requiredWidth, 1 };
DWORD style = (DWORD)::GetWindowLongPtr( hList, GWL_STYLE );
DWORD styleEx = (DWORD)::GetWindowLongPtr( hList, GWL_EXSTYLE );
::AdjustWindowRectEx( &r, style, FALSE, styleEx );
int windowWidth = r.right - r.left;
A lazy solution is to increase the width until the scrollbar disappears.
RECT r;
::GetWindowRect(hlist, &r);
RECT rc;
::GetClientRect(hparent, &rc);
POINT p { rc.right, 0 };
::ClientToScreen(hparent, &p);
int limit = p.x - r.right;
for (int i = 0; i < limit; i++)
{
if (!(::GetWindowLong(hlist, GWL_STYLE) & WS_HSCROLL))
break;
r.right++;
::SetWindowPos(hlist, 0, 0, 0, r.right - r.left, r.bottom - r.top,
SWP_NOREDRAW | SWP_NOMOVE | SWP_NOZORDER);
}
I am capturing particular portion of desktop window in a bitmap and trying to print
BGR pixel color value. This captured portion of desktop window is completely filled with 16, 0, 16 color.
When I capture and print the data when my window color depth is 32 everything is right,
but if my window is in 24/16 bit color mode then I am getting different pixel values instead of 16, 0, 16.
I am capturing screen left = 150, top = 150, right = 200, bottom = 200.
*********CAPTURING AN IMAGE FROM DESKTOP*********
iLeft = 150;
iTop = 150;
iRight = iLeft + 50;
iBottom = iTop + 50;
/*
HDC hdcScreen;
HDC hdcWindow;
HDC hdcMemDC = NULL;
HBITMAP hbmScreen = NULL;
BITMAP bmpScreen;
// Retrieve the handle to a display device context for the client
// area of the window.
hdcScreen = GetDC(NULL);
hdcWindow = GetDC(hWnd);
// Create a compatible DC which is used in a BitBlt from the window DC
hdcMemDC = CreateCompatibleDC(hdcWindow);
if(!hdcMemDC)
{
MessageBox(hWnd, L"CreateCompatibleDC has failed",L"Failed", MB_OK);
goto done;
}
// Get the client area for size calculation
RECT rcClient;
GetClientRect(hWnd, &rcClient);
//This is the best stretch mode
SetStretchBltMode(hdcWindow,HALFTONE);
//The source DC is the entire screen and the destination DC is the current window (HWND)
if(!StretchBlt(hdcWindow,
0,0,
rcClient.right, rcClient.bottom,
hdcScreen,
0,0,
GetSystemMetrics (SM_CXSCREEN),
GetSystemMetrics (SM_CYSCREEN),
SRCCOPY))
{
MessageBox(hWnd, L"StretchBlt has failed",L"Failed", MB_OK);
goto done;
}
// Create a compatible bitmap from the Window DC
hbmScreen = CreateCompatibleBitmap(hdcWindow, rcClient.right-rcClient.left, rcClient.bottom-rcClient.top);
if(!hbmScreen)
{
MessageBox(hWnd, L"CreateCompatibleBitmap Failed",L"Failed", MB_OK);
goto done;
}
// Select the compatible bitmap into the compatible memory DC.
SelectObject(hdcMemDC,hbmScreen);
// Bit block transfer into our compatible memory DC.
if(!BitBlt(hdcMemDC,
0,0,
rcClient.right-rcClient.left, rcClient.bottom-rcClient.top,
hdcWindow,
0,0,
SRCCOPY))
{
MessageBox(hWnd, L"BitBlt has failed", L"Failed", MB_OK);
goto done;
}
// Get the BITMAP from the HBITMAP
GetObject(hbmScreen,sizeof(BITMAP),&bmpScreen);
BITMAPFILEHEADER bmfHeader;
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = bmpScreen.bmWidth;
bi.biHeight = bmpScreen.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
DWORD dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;
// Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that
// call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc
// have greater overhead than HeapAlloc.
HANDLE hDIB = GlobalAlloc(GHND,dwBmpSize);
char *lpbitmap = (char *)GlobalLock(hDIB);
// Gets the "bits" from the bitmap and copies them into a buffer
// which is pointed to by lpbitmap.
GetDIBits(hdcWindow, hbmScreen, 0,
(UINT)bmpScreen.bmHeight,
lpbitmap,
(BITMAPINFO *)&bi, DIB_RGB_COLORS);
// A file is created, this is where we will save the screen capture.
HANDLE hFile = CreateFile(L"captureqwsx.bmp",
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
// Add the size of the headers to the size of the bitmap to get the total file size
DWORD dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
//Offset to where the actual bitmap bits start.
bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);
//Size of the file
bmfHeader.bfSize = dwSizeofDIB;
//bfType must always be BM for Bitmaps
bmfHeader.bfType = 0x4D42; //BM
DWORD dwBytesWritten = 0;
WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
WriteFile(hFile, (LPSTR)lpbitmap, dwBmpSize, &dwBytesWritten, NULL);
//Unlock and Free the DIB from the heap
GlobalUnlock(hDIB);
GlobalFree(hDIB);
//Close the handle for the file that was created
CloseHandle(hFile);
//Clean up
done:
DeleteObject(hbmScreen);
DeleteObject(hdcMemDC);
ReleaseDC(NULL,hdcScreen);
ReleaseDC(hWnd,hdcWindow);
return 0;
*/
//#if 0
HDC hdcScreen;
// HDC hdcWindow;
HDC hdcMemDC = NULL;
HBITMAP hbmScreen = NULL;
HRGN rgn = NULL;
BITMAP bmpScreen;
// Retrieve the handle to a display device context for the client
// area of the window.
hdcScreen = GetDC(NULL);
//hdcWindow = GetDC(hWnd);
//if(g_hdcMemDC == NULL)
{
// Create a compatible DC which is used in a BitBlt from the window DC
hdcMemDC = CreateCompatibleDC(hdcScreen);
if(!hdcMemDC)
{
//MessageBox(hWnd, L"CreateCompatibleDC has failed",L"Failed", MB_OK);
goto done;
}
// Get the client area for size calculation
//RECT rcClient;
//GetClientRect(hWnd, &rcClient);
//This is the best stretch mode
SetStretchBltMode(hdcMemDC,HALFTONE);
// Create a compatible bitmap from the Window DC
hbmScreen = CreateCompatibleBitmap(hdcScreen, iRight - iLeft, iBottom - iTop);
if(!hbmScreen)
{
//MessageBox(hWnd, L"CreateCompatibleBitmap Failed",L"Failed", MB_OK);
goto done;
}
// Select the compatible bitmap into the compatible memory DC.
SelectObject(hdcMemDC,hbmScreen);
}
//The source DC is the entire screen and the destination DC is the current window (HWND)
/* if(!StretchBlt(hdcWindow,
0,0,
rcClient.right, rcClient.bottom,
hdcScreen,
0,0,
GetSystemMetrics (SM_CXSCREEN),
GetSystemMetrics (SM_CYSCREEN),
SRCCOPY))
{
MessageBox(hWnd, L"StretchBlt has failed",L"Failed", MB_OK);
goto done;
}
*/
// Bit block transfer into our compatible memory DC.
if(!StretchBlt(hdcMemDC,
0,0,
iRight - iLeft,
iBottom - iTop,
hdcScreen,
iLeft,iTop,
iRight - iLeft,
iBottom - iTop,
SRCCOPY))
{
// MessageBox(hWnd, L"StretchBlt has failed",L"Failed", MB_OK);
goto done;
}
}
*******************FUNCTION FOR GETTING THE BITMAP RAW DATA POINTER***********
BYTE* Get24BitPixels(HDC dcDesktop, HBITMAP pBitmap, WORD *pwWidth, WORD *pwHeight, WORD * pReminderWidth)
{
// a bitmap object just to get bitmap width and height
BITMAP bmpBmp;
// pointer to original bitmap info
LPBITMAPINFO pbmiInfo;
// bitmap info will hold the new 24bit bitmap info
BITMAPINFO bmiInfo;
// width and height of the bitmap
WORD wBmpWidth ; WORD wBmpHeight;
// ---------------------------------------------------------
// get some info from the bitmap
// ---------------------------------------------------------
GetObject(pBitmap, sizeof(bmpBmp),&bmpBmp);
pbmiInfo = (LPBITMAPINFO)&bmpBmp;
//get width and height
wBmpWidth = (WORD)pbmiInfo->bmiHeader.biWidth;
int iReminderWidth = (wBmpWidth%4);
//wBmpWidth -= (wBmpWidth%4); // width is 4 byte boundary aligned.
wBmpHeight = (WORD)pbmiInfo->bmiHeader.biHeight;
// copy to caller width and height parms
*pwWidth = wBmpWidth;
*pwHeight = wBmpHeight;
wBmpWidth += (4 - iReminderWidth); // width is 4 byte boundary aligned, thereby increasing the width
//so that it will be fully divible by four , it will cause some extra bytes to be filled in with garbage value
//beyond the actual width of the bitmap, we will be discrading this extra padding pixels data while processign each pixel.
*pReminderWidth = 4 - iReminderWidth;
// ---------------------------------------------------------
// allocate width * height * 24bits pixels
BYTE * pPixels = new BYTE[wBmpWidth*wBmpHeight*3];
if (!pPixels) return NULL;
// get user desktop device context to get pixels from
//HDC hDC = GetWindowDC(NULL);
// fill desired structure
bmiInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmiInfo.bmiHeader.biWidth = wBmpWidth;
bmiInfo.bmiHeader.biHeight = -wBmpHeight;
bmiInfo.bmiHeader.biPlanes = 1;
bmiInfo.bmiHeader.biBitCount = 24;
bmiInfo.bmiHeader.biCompression = BI_RGB;
bmiInfo.bmiHeader.biSizeImage = wBmpWidth*wBmpHeight*3;
bmiInfo.bmiHeader.biXPelsPerMeter = 0;
bmiInfo.bmiHeader.biYPelsPerMeter = 0;
bmiInfo.bmiHeader.biClrUsed = 0;
bmiInfo.bmiHeader.biClrImportant = 0;
// get pixels from the original bitmap converted to 24bits
int iRes = GetDIBits(dcDesktop,pBitmap,0,wBmpHeight,(LPVOID)pPixels,&bmiInfo,DIB_RGB_COLORS);
// release the device context
//ReleaseDC(NULL,hDC);
// if failed, cancel the operation.
if (!iRes)
{
delete [] pPixels;
pPixels = NULL;
return NULL;
};
// return the pixel array
return pPixels;
}
Fortunately I got the below mention post on StackOverflow.
Many thanks to Vodemki who posted this answer.
Get Pixel color fastest way?
I'm using UpdateLayeredWindow to display an application window. I have created my own custom buttons and i would like to create my own static text. The problem is that when i try to draw the text on the hdc, the DrawText or TextOut functions overwrite the alpha channel of my picture and the text will become transparent. I tried to find a solution to this but i could not find any. My custom controls are designed in such way that they will do all the drawing in a member function called Draw(HDC hDc), so they can only access the hdc. I would like to keep this design. Can anyone help me? I am using MFC and i would want to achieve the desired result without the use of GDI+.
I know this is an old post ... but I just had this very same problem ... and it was driving me CRAZY.
Eventually, I stumbled upon this post by Mike Sutton to the microsoft.public.win32.programmer.gdi newsgroup ... from almost 7 years ago!
Basically, the DrawText (and TextOut) do not play nicely with the alpha channel and UpdateLayeredWindow ... and you need to premultiply the R, G, and B channels with the alpha channel.
In Mike's post, he shows how he creates another DIB (device independent bitmap) upon which he draws the text ... and alpha blends that into the other bitmap.
After doing this, my text looked perfect!
Just in case, the link to the newsgroup post dies ... I am going to include the code here. All credit goes to Mike Sutton (#mikedsutton).
Here is the code that creates the alpha blended bitmap with the text on it:
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;
}
Here is the code that drives the CreateAlphaTextBitmap method:
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)
{
// Get Bitmap image size
BITMAP BMInf;
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);
}
}
}