So I've found a lot of code samples, guides, and answers on SO about drawing an image to a layered window. I've tried using pure HBITMAPS and the WIC libs to draw, and now I'm on to GDI+ to draw (which is much simpler and is seemingly easier to use, and thus far it has solved a lot of errors that were caused by faulty WIC code).
I'm currently stuck on UpdateLayeredWindow. No matter what I try I can't get it to work. Right now, it's returning 87, or ERROR_INVALID_PARAMETER. The question is, which one is incorrect? I'm stumped! The below code seems to be the solution other than the fact that UpdateLayeredWindow is refusing to work.
What am I doing wrong?
Here is the code that sets up the HDC/bitmap information/graphics object.
// Create DC
_oGrphInf.canvasHDC = GetDC(_hwndWindow);
// Create drawing 'canvas'
_oGrphInf.lpBits = NULL;
_oGrphInf.bmpCanvas = CreateDIBSection(_oGrphInf.canvasHDC,
&_oGrphInf.bmpWinInformation, DIB_RGB_COLORS,
&_oGrphInf.lpBits, NULL, 0);
// Create graphics object
_oGrphInf.graphics = new Gdiplus::Graphics(_oGrphInf.canvasHDC);
The above works fine - I step through it and all of the pointers work.
And here is the method that draws the PNG.
void Splash::DrawPNG(PNG* lpPNG, int x, int y)
{
LOGD("Drawing bitmap!");
HDC hdcMem = CreateCompatibleDC(_oGrphInf.canvasHDC);
// Select
HBITMAP bmpOld = (HBITMAP)SelectObject(hdcMem, _oGrphInf.bmpCanvas);
Gdiplus::Color trans(0, 0, 0, 0);
_oGrphInf.graphics->Clear(trans);
_oGrphInf.graphics->DrawImage(lpPNG->GetImage(), x, y);
_oGrphInf.graphics->Flush();
SIZE szSize = {_oGrphInf.bmpWinInformation.bmiHeader.biWidth,
_oGrphInf.bmpWinInformation.bmiHeader.biHeight};
// Setup drawing location
POINT ptLoc = {0, 0};
POINT ptSrc = {0, 0};
// Set up alpha blending
BLENDFUNCTION blend = {0};
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
blend.BlendFlags = 0;
// Update
if(UpdateLayeredWindow(_hwndWindow, _oGrphInf.canvasHDC, &ptLoc,
&szSize, hdcMem, &ptSrc,
(COLORREF)RGB(0, 0, 0),
&blend, ULW_ALPHA) == FALSE)
LOGE("Could not update layered window: %u", GetLastError());
// Delete temp objects
SelectObject(hdcMem, bmpOld);
DeleteObject(hdcMem);
DeleteDC(hdcMem);
}
Pulling my hair out! Help?
EDIT: I just decided to re-write the call to UpdateLayeredWindow function, which solved the incorrect parameter issue. Here is what I came up with. However, it still does not work. What am I doing wrong?
UpdateLayeredWindow(_hwndWindow, _oGrphInf.canvasHDC,
NULL, NULL, hdcMem, &ptLoc,
RGB(0, 0, 0), &blend, ULW_ALPHA)
For alpha information to be preserved in drawing operations, you have to make your Graphics object based on a memory-backed Bitmap object, not an HDC, and of course your Bitmap needs to be in a format with an alpha channel.
You'll need to use this Bitmap constructor: http://msdn.microsoft.com/en-us/library/ms536315%28v=vs.85%29.aspx
Just give that a stride of 0, a pointer to your DIB's bits, and PixelFormat32bppPARGB.
Then use Graphics::FromImage to create your Graphics object.
I've never used UpdateLayeredWindow, so I can't verify that that side of it is correct.
Related
I don't use c++ or MFC/ATL etc so assume I know nothing.
The whole picture is that I need to take accept a PNG with a transparency layer and write it out as a JPEG with specified compression to pass to another system.
What I would like to know here, is how can I initialize a target CImage structure with solid white?
I've done this so far (yes I know it has other stylistic issues)
void ClipBoardFuncs::savePNGASJPEG(char filePath[256], char errorBuff[256]) {
int sizeOfErrorBuff = 256;
CImage imagePNG;
CImage imageJPG;
int xPNG, yPNG = 0;
imagePNG.Load(filePath);
xPNG = imagePNG.GetWidth();
yPNG = imagePNG.GetHeight();
//Create my target JPG same size and bit depth specifiying that there is no alpha channel (dwflag last param)
imageJPG.Create(xPNG, yPNG, imagePNG.GetBPP(), 0);
//Let there be white....
for (int x = 0; x <= xPNG; x++)
{
for (int y = 0; y <= yPNG; y++)
{
imageJPG.SetPixelRGB(x, y, 255, 255, 255);
}
}
//Copy the image over onto on the white
//BitBlt copies everything....
//imagePNG.BitBlt(imageJPG.GetDC(), 0, 0);
//Draw is more like the C# draw examples I've seen
imagePNG.Draw(imageJPG.GetDC(), 0, 0);
imageJPG.ReleaseDC();
HRESULT hr = NULL;
hr = imageJPG.Save(filePath, Gdiplus::ImageFormatJPEG);
imagePNG.Destroy();
imagePNG.ReleaseGDIPlus();
imageJPG.Destroy();
imageJPG.ReleaseGDIPlus();
LPCTSTR error = _com_error(hr).ErrorMessage();
strncpy_s(errorBuff, sizeOfErrorBuff, _com_error(hr).ErrorMessage(), _TRUNCATE);
}
The lovely C# people have this answer:
Convert Transparent PNG to JPG with Non-Black Background Color
But I need the c++ MFC solution to use as an exported function in a DLL.
By exported Function I mean the same architecture as you would find in kernel32.dll - sorry I do not know the terminology to differentiate that kind of DLL from one stuffed with COM objects.
Can anyone suggest a faster way to initialize the imageJPEG structure to solid white than the nested x/y for loops I have here?
Cheers
4GLGuy
The initialization can be done with Rectangle or FillRect (for this, FillRect is probably preferred--Rectangle draws an outline of the rectangle in the current pen color, which we probably don't want).
So, the sequence looks something like this:
CImage png;
png.Load(pDoc->filename);
CRect rect{ 0, 0, png.GetWidth(), png.GetHeight() };
CImage jpeg;
jpeg.Create(rect.Width(), rect.Height(), png.GetBPP());
auto dc = jpeg.GetDC();
HBRUSH white = CreateSolidBrush(RGB(255, 255, 255));
FillRect(dc, &rect, white);
png.Draw(dc, 0, 0);
jpeg.ReleaseDC();
jpeg.Save(L"Insert File name here", Gdiplus::ImageFormatJPEG);
jpeg.Destroy();
jpeg.ReleaseGDIPlus();
png.ReleaseGDIPlus();
GDI+ is "smart" enough that when you do a .Draw with an image that has an alpha channel, it takes that channel into account, without your having to use TransparentBlt (or anything similar).
SetTransparentColor will not work for what you're trying to do here. SetTransparentColor is for an image that does not have an alpha channel ("transparency layer"). You then use it to choose a color that will be treated as if it were transparent--which can certainly be useful, but isn't what you want here.
You can use memset instead, but only for colors where the red, green, and blue channels all have the same values (i.e., black, white, or some shade of grey). Otherwise, you can do the fill on your own with a nested loop, but in most cases you probably want to use FillRect instead (it may be able to use graphics hardware for acceleration, where the loop will pretty dependably just run on the CPU--worst case, they're both about the same speed, but in some cases FillRect will be faster).
imagePNG.Draw(imageJPG.GetDC(), 0, 0);
Each call to GetDC must have a subsequent call to ReleaseDC. See also CImage::GetDC documentation.
CImage::GetDC provides a handle to memory device context. This handle can be used to draw using standard GDI functions. The handle should later be cleaned up with CImage::ReleaseDC.
CImage::Draw may not know what the transparent color is. You have to use TransparentBlt to tell it what the transparent color is. For example, to replace red color with white color:
HDC hdc = imageJPG.GetDC();
CDC dc;
dc.Attach(hdc);
CRect rc(0, 0, xPNG, yPNG);
dc.FillSolidRect(&rc, RGB(255, 255, 255));
dc.Detach();
imagePNG.TransparentBlt(hdc, rc, RGB(255, 0, 0));//replace red with white
imageJPG.ReleaseDC();
...
imageJPG.Save(...);
Or just use CImage::SetTransparentColor:
imagePNG.SetTransparentColor(RGB(255, 0, 0));
imagePNG.Draw(hdc, rc);
for (int x = 0; x <= xPNG; x++){...}
To do this using a loop, change the condition in the loop to x < xPNG and x < yPNG.
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 have inherited an old-school MFC Windows CE program, and am having to make some modifications to it. As part of this I have to create a monochrome image with text on it, and both display it on a screen as well as send each row of the image to a printer one at a time.
I originally used a bitmap, and had success using DrawText() and getting a test string (Hello World) to display on the screen (this code is in Figure 1). However, I hit a wall at the stage where I am looking to extract the wrap data from the bitmap. What I am trying to get is an array with 1s or 0s representing black or white. I had first thought I would use GetBitmapBits() but unfortunately the code I am working with is so old that function is not supported yet. I thought I could get around this issue by using GetBitmap() and then accessing the bmBits parameter. However this appears to always be null which was confirmed when I found the following link: Why does GetObject return an BITMAP with null bmBits?.
My next attempt was to follow the advice in the link and use CreateDIBSection() instead of CreateCompatibleBitmap(). This seems like the right path, and I should have access to the data I want, but unfortunately I cannot get the DIB to display (code is in Figure 2). I suspect I am doing something wrong in creating the header of the DIB, but I cannot figure out what my mistake is.
If anyone has suggestions for a way to access the data in the bitmap, or can see what I am doing wrong with the DIB, I would greatly appreciate the help!
*** FIGURE 1: Code to create and display a bitmap
void CRunPage::OnPaint()
{
CPaintDC dc(this); // property page device context for painting
CBitmap mBmp; // CBitmap object for displaying built-in bitmaps
CDC mDCMem; // CDC object to handle built-in bitmap
int iWidth, iHeight; // dimension to draw on the screen
int icurLabel, // current label index of open print file
iLabelNum; // number of labels in open print file
LPBITMAPINFOHEADER pBMIH; // bitmap header object for current label
LPBYTE pImage; // bitmap data for current label
CSize size; // size of label
int PreviewLeft,PreviewTop,PreviewWidth,PreviewHeight;
CRect Rect;
BITMAP bm;
LPVOID bmBits=NULL;
// Calculate the preview area
PreviewLeft=5;
PreviewTop=5;
GetDlgItem(IDC_RUN_NEXT)->GetWindowRect(&Rect);
ScreenToClient(&Rect);
PreviewWidth=Rect.left-PreviewLeft*2;
GetDlgItem(IDC_RUN_WRAPTEXT)->GetWindowRect(&Rect);
ScreenToClient(&Rect);
PreviewHeight=Rect.top-PreviewTop*2;
CRect textRect;
CString testText(_T("Hello World"));
CBitmap * pOldBitmap;
CBrush whiteBrush, *pOldBrush;
CPen blackPen, *pOldPen;
mDCMem.CreateCompatibleDC(&dc);
mBmp.CreateCompatibleBitmap(&dc, PreviewWidth+PreviewLeft*2, PreviewHeight+PreviewTop*2);
//mBmp.CreateCompatibleBitmap(&dc, PreviewWidth, PreviewHeight);
pOldBitmap = mDCMem.SelectObject(&mBmp);
blackPen.CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
whiteBrush.CreateSolidBrush(RGB(255,255,255));
textRect.SetRect(0,0,PreviewWidth, PreviewHeight);
// this means behind the text will be a white box w/ a black boarder
pOldBrush = mDCMem.SelectObject(&whiteBrush);
pOldPen = mDCMem.SelectObject(&blackPen);
//these commands draw on the memory-only context (mDCMem)
mDCMem.Rectangle(&textRect);
mDCMem.DrawText((LPCTSTR)testText, 11, &textRect, DT_CENTER|DT_VCENTER);
mDCMem.SelectObject(pOldBrush);
mDCMem.SelectObject(pOldPen);
dc.StretchBlt(PreviewLeft,PreviewTop, PreviewWidth, PreviewHeight, & mDCMem, 0, 0, PreviewWidth, PreviewHeight, SRCCOPY);
mDCMem.SelectObject(pOldBitmap);
}
*** FIGURE 2: Trying to use a DIB instead of a bitmap
void CRunPage::OnPaint()
{
CPaintDC dc(this); // property page device context for painting
CBitmap mBmp; // CBitmap object for displaying built-in bitmaps
CDC mDCMem; // CDC object to handle built-in bitmap
int iWidth, iHeight; // dimension to draw on the screen
int icurLabel, // current label index of open print file
iLabelNum; // number of labels in open print file
LPBITMAPINFOHEADER pBMIH; // bitmap header object for current label
LPBYTE pImage; // bitmap data for current label
CSize size; // size of label
int PreviewLeft,PreviewTop,PreviewWidth,PreviewHeight;
CRect Rect;
BITMAP bm;
// Calculate the preview area
PreviewLeft=5;
PreviewTop=5;
GetDlgItem(IDC_RUN_NEXT)->GetWindowRect(&Rect);
ScreenToClient(&Rect);
PreviewWidth=Rect.left-PreviewLeft*2;
GetDlgItem(IDC_RUN_WRAPTEXT)->GetWindowRect(&Rect);
ScreenToClient(&Rect);
PreviewHeight=Rect.top-PreviewTop*2;
CRect textRect;
CString testText(_T("Hello World"));
CBitmap * pOldBitmap;
CBrush whiteBrush, *pOldBrush;
CPen blackPen, *pOldPen;
LPBYTE pFWandImageMem=NULL, pImageMem=NULL, pTemp=NULL;
int i=0,j=0, buffSize=0, numBytesPerRow=0, bitmapWidthPix,bitmapHeightPix;
char *numBytesPerRowString;
char temp;
void ** ppvBits;
BITMAPINFOHEADER bmif;
BITMAPINFO bmi;
HBITMAP myDIB, myOldDIB;
mDCMem.CreateCompatibleDC(&dc);
//this rect is the area in which I can draw (its x,y location is set by BitBlt or StretchBlt
//mBmp.CreateCompatibleBitmap(&dc, PreviewWidth+PreviewLeft*2, PreviewHeight+PreviewTop*2);
bmif.biSize = sizeof(BITMAPINFOHEADER);
bmif.biWidth = PreviewWidth+PreviewLeft*2;
bmif.biHeight = -(PreviewHeight+PreviewTop*2);//- means top down (I think? I tried both ways and neither worked)
bmif.biPlanes = 1;
bmif.biBitCount = 1;
bmif.biCompression = BI_RGB; // no compression
bmif.biSizeImage = 0; // Size (bytes) if image - this can be set to 0 for uncompressed images
bmif.biXPelsPerMeter = 0;
bmif.biYPelsPerMeter = 0;
bmif.biClrUsed =0;
bmif.biClrImportant = 0;
bmi.bmiColors[0].rgbBlue=0;
bmi.bmiColors[0].rgbGreen=0;
bmi.bmiColors[0].rgbRed=0;
bmi.bmiColors[0].rgbReserved=0;
bmi.bmiColors[1].rgbBlue=255;
bmi.bmiColors[1].rgbGreen=255;
bmi.bmiColors[1].rgbRed=255;
bmi.bmiColors[1].rgbReserved=0;
bmi.bmiHeader=bmif;
myDIB = CreateDIBSection(dc.GetSafeHdc(), &bmi, DIB_RGB_COLORS, ppvBits, NULL, 0);
myOldDIB = (HBITMAP)mDCMem.SelectObject(myDIB);//SelectObject(mDCMem, myDIB);
blackPen.CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
whiteBrush.CreateSolidBrush(RGB(255,255,255));
textRect.SetRect(0,0,PreviewWidth, PreviewHeight);
// this means behind the text will be a white box w/ a black boarder
pOldBrush = mDCMem.SelectObject(&whiteBrush);
pOldPen = mDCMem.SelectObject(&blackPen);
//these commands draw on the memory-only context (mDCMem)
mDCMem.Rectangle(&textRect);
mDCMem.DrawText((LPCTSTR)testText, 11, &textRect, DT_CENTER|DT_VCENTER);
mDCMem.SelectObject(pOldBrush);
mDCMem.SelectObject(pOldPen);
dc.StretchBlt(PreviewLeft,PreviewTop, PreviewWidth, PreviewHeight, & mDCMem, 0, 0, PreviewWidth, PreviewHeight, SRCCOPY);
mDCMem.SelectObject(myOldDIB);
}
So I made two minor changes to the DIB code, and it is displaying the image correctly now.
First, I changed the way I passed in my pointer to the CreateDIBSection():
void ** ppvBits;
to
LPBYTE pBits;
And then I had to change how I passed that into CreateDIBSection. I also explicitly casted the return of CreateDIBSection() to an HBITMAP:
myDIB = CreateDIBSection(dc.GetSafeHdc(), &bmi, DIB_RGB_COLORS, (void**)&pBits, NULL, 0);
to
myDIB = (HBITMAP) CreateDIBSection(dc.GetSafeHdc(), &bmi, DIB_RGB_COLORS, ppvBits, NULL, 0);
I have not had a chance to see if I can access the image data, but I am past the initial issues now. Thanks to anyone who looked at this, and if people know how to do the first (device dependent bitmap) method I would be interested to know.
I recently asked a question about this, and understood the answer, but couldn't translate it into code. After another day of messing around with stuff, and fixing the leaks. I literally cannot for the life of me figure this out, though.
This is slightly different, all I need to do is get the background bitmap, under the map bitmap.
HDC hdc = GetDC(hWnd);
HDC hdcMem = CreateCompatibleDC(hdc);
HDC hdcMem2 = CreateCompatibleDC(hdc);
ReleaseDC(hWnd, hdc);
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, bitmap.hbmBackground);
BitBlt(buffer.getBufferDC(), 1, 1, WINDOW_WIDTH, WINDOW_HEIGHT, hdcMem, 0, 0, SRCCOPY);
HBITMAP hbmOld2 = (HBITMAP)SelectObject(hdcMem2, bitmap.hbmMap);
BitBlt(buffer.getBufferDC(), 1, 1, WINDOW_WIDTH, WINDOW_HEIGHT, hdcMem2, 0, 0, SRCPAINT);
SelectObject(hdcMem2, hbmOld2);
My problem is combining... creating memory dcs to keep results of raster operations. I can not at all wrap my head around this, any help would be great.
Thanks.
Probably the most difficult part of this is the rectangle in the middle. The other white areas in the mask are the places the background should show through, but inside that rectangle you have white where the background should not show through.
For the moment, I'm going to assume we can start with those sections being different colors. It doesn't really matter what color we use for the transparent part (the part where the background should show) as long as it's unique. For now I'll assume it's an RGB value for 254, 254, 254--code won't mistake it for pure white (255, 255, 255), but usually not visually distinguishable.
With that, the job becomes quite simple: we can use TransparentBlt to handle the transparency, so you blit your background to a destination with BitBlt, then blit your mask to that destination using TransparentBlt. Simplifying a bit (leaving out parameters we don't care much about for the moment) the code looks something like this:
HDC mask, background, combined;
mask = CreateCompatibleDC(screen);
background = CreateCompatibleDC(screen);
combined = CreateCompatibleDC(screen);
HBITMAP mask_bmp = LoadBitmap(MAKEINTRESOURCE(IDB_MASK));
HBITMAP back_bmp = LoadBitmap(MAKEINTRESOURCE(IDB_BKGND));
HBITMAP result = CreateCompatibleBitmap(screen);
mask_original = Selectobject(mask, mask_bmp);
back_original = SelectObject(background, back_bmp);
combined_original = SelectObject(combined, result);
BitBlt(background, result, SRCCOPY);
TransparentBlt(mask, result, RGB(254, 254, 254));
BitBlt(result, screen, SRCCOPY);
SelectObject(mask, mask_original);
SelectObject(background, back_original);
SelectObject(combined, combined_original);
DeleteDC(mask);
DeleteDC(background);
DeleteDC(combined):
DeleteObject(result);
(unmanaged C++)
I already succeeded drawing PNG files to a transparent layered window that I can drag around the desktop, but now my problem is drawing text on a transparent layered window
Here's my code and my attempt at drawing text in the middle, it's important to note that i'm using the screenDC instead of using the one in WM_PAINT messages
[edit]
updated code after the comments, now i'm just trying to write text on the bitmap before getting the HBITMAP version which i need to use
this time I'm using DrawString because textout() isn't GDI+, I hope DrawString really is GDI+ lol
still doesn't work though, wonder what i'm doing wrong
void Draw() // draws a frame on the layered window AND moves it based on x and y
{
HDC screenDC( NULL ); // grab screen
HDC sourceDC( CreateCompatibleDC(screenDC) );
POINT pos = {x,y}; // drawing location
POINT sourcePos = {0,0}; // top left of image
SIZE size = {100,100}; // 100x100 image
BLENDFUNCTION blendFunction = {0};
HBITMAP bufferBitmap = {0};
Bitmap* TheBitmap = crnimage; // crnimage was already loaded earlier
// ------------important part goes here, my attempt at drawing text ------------//
Gdiplus::Graphics Gx(TheBitmap);
// Font* myFont = new Font(sourceDC);
Font myFont(L"Arial", 16);
RectF therect;
therect.Height = 20;
therect.Width = 180;
therect.X = 0;
therect.Y = 0;
StringFormat format;
format.SetAlignment(StringAlignmentCenter);
format.GenericDefault();
Gdiplus::SolidBrush GxTextBrush(Gdiplus::Color(255, 255, 0,255));
WCHAR thetext[] = L"Sample Text";
int stats = Gx.DrawString(thetext, -1, &myFont, therect, &format, &GxTextBrush);
if(stats) // DrawString returns nonzero if there is an error
msgbox(stats);
stats = Gx.DrawRectangle(&Pen(Color::Red, 3), therect);
// the rectangle and text both draw fine now
// ------------important part goes here, my attempt at drawing text ------------//
TheBitmap->GetHBITMAP(0, &bufferBitmap);
HBITMAP oldBmpSelInDC;
oldBmpSelInDC = (HBITMAP)SelectObject(sourceDC, bufferBitmap);
// some alpha blending
blendFunction.BlendOp = AC_SRC_OVER;
blendFunction.SourceConstantAlpha = wndalpha;
blendFunction.AlphaFormat = AC_SRC_ALPHA;
COLORREF colorKey( RGB(255,0,255) );
DWORD flags( ULW_ALPHA);
UpdateLayeredWindow(hWnd, screenDC, &pos, & size, sourceDC, &sourcePos,
colorKey, &blendFunction, flags);
// release buffered image from memory
SelectObject(sourceDC, oldBmpSelInDC);
DeleteDC(sourceDC);
DeleteObject(bufferBitmap);
// finally release the screen
ReleaseDC(0, screenDC);
}
I've been trying to write text on my layered window for two days now, but from those attempts I know there are several ways I can go about doing this
(unfortunately I have no idea how exactly)
The usual option I see is drawing text on a bitmap, then rendering the bitmap itself
Use Gdi+ to load a bitmap
Create a Graphics object from the bitmap
Use DrawString to write text to the bitmap
Dispose of the Graphics object
Use the bitmap Save method to save the result to a file
Apparently one can also make a graphics object from a DC, then draw text on the DC, but again i have no clue as to how to do this
The overall approach looks right, but I think you've got some problems with the DrawString call. Check out the documentation (especially the sample) on MSDN.
Gx.DrawString(thetext, 4, NULL, therect, NULL, NULL)
The third, fifth, and sixth parameters (font, format, and brush) probably need to be specified. The documentation doesn't say that they are optional. Passing NULL for these is probably causing GDI+ to treat the call as a no-op.
The second parameter should not include the terminating L'\0' in the string. It's probably safest to use -1 if your string is always terminated.