Prevent Flickering When Drawing - c++

So I have this code to draw a rectangle on my screen:
LOGBRUSH m_LogBrush;
HBRUSH m_hBrush;
HPEN m_hPen;
HDC m_hDC;
void DrawBox(int x, int y, int r, int g, int b, int size, int thickness)
{
// Brush style to hollow
m_LogBrush.lbStyle = BS_NULL;
// Create a logical brush and select into the context
m_hBrush = CreateBrushIndirect(&m_LogBrush);
SelectObject(m_hDC, m_hBrush);
// Create a logical pen and select into the context
m_hPen = CreatePen(PS_SOLID, thickness, RGB(r, g, b));
SelectObject(m_hDC, m_hPen);
// Draw the rectangle
Rectangle(m_hDC, (x - size / 2), (y - size / 2), (x + size / 2), (y + size / 2));
// Remove the object
DeleteObject(m_hBrush);
DeleteObject(m_hPen);
}
However, when being called repeatedly inside a loop it flickers on the screen. I was wondering if there was a way to prevent this flicker?
Any help would be appreciated.
Thanks

This should not be an answer, but I can not post code in comments:
You have many GDI leaks in your code.
Copy/paste the following code and report us if flickering diminishes:
void DrawBox(int x, int y, int r, int g, int b, int size, int thickness)
{
// Brush style to hollow
m_LogBrush.lbStyle = BS_NULL;
// Create a logical brush and select into the context
m_hBrush = CreateBrushIndirect(&m_LogBrush);
HBRUSH hbrOldBrush = SelectObject(m_hDC, m_hBrush);
// Create a logical pen and select into the context
m_hPen = CreatePen(PS_SOLID, thickness, RGB(r, g, b));
HPEN hpOldPen = SelectObject(m_hDC, m_hPen);
// Draw the rectangle
Rectangle(m_hDC, (x - size / 2), (y - size / 2), (x + size / 2), (y + size / 2));
// Remove the object
SelectObject(m_hDC, hbrOldBrush); // first you must restore DC to original state
SelectObject(m_hDC, hpOldPen); // same here
DeleteObject(m_hBrush);
DeleteObject(m_hPen);
}
Read on MSDN about GDI leaks.
This should diminish flickering, but to completely remove flickering you should do the following:
Remove the CS_VREDRAW | CS_HREDRAW from your window class definition;
return 1L in your window procedure ( or TRUE in your dialog box procedure ) in response to WM_ERASEBKGND;
draw everything on a memory bitmap and then BitBlt it into your m_hDC -> this is called double buffering ( you can find many examples online );

/*Hi You May Change Your Code To This*/
LOGBRUSH m_LogBrush;
//HBRUSH m_hBrush;
//HPEN m_hPen;
//HDC m_hDC;
HWND m_hWND; //Your WindowHandle instead of your DC
//
void DrawBox(int x, int y, int r, int g, int b, int size, int thickness)
{
// Lock & Get Forground DC From m_hWND
HDC m_hDC = GetDC(m_hWND);
if (m_hDC != 0) //Make Sure It's ok
{
// Double Buffering Begins Here
// Create Background DC From m_hDC
HDC mem_m_hDC = CreateCompatibleDC(m_hDC);
// Calculate Window Bounds
RECT ClientRect = { 0 };
GetClientRect(m_hWND, &ClientRect);
// Create Background Buffer Frame
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = ClientRect.right - ClientRect.left;
bmi.bmiHeader.biHeight = ClientRect.bottom - ClientRect.top;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biPlanes = 1;
HBITMAP memBMP = CreateDIBSection(mem_m_hDC, &bmi, DIB_RGB_COLORS, 0, 0, 0);
// Select Background Buffer Frame
SelectObject(mem_m_hDC, memBMP);
// Brush style to hollow
m_LogBrush.lbStyle = BS_NULL;
// Create a logical brush and select into the context
HBRUSH m_hBrush = CreateBrushIndirect(&m_LogBrush);
HGDIOBJ oldHGDIOBJ1 = SelectObject(m_hDC, m_hBrush); //Save Old Seleteed GDI Object To oldHGDIOBJ1
// Create a logical pen and select into the context
HPEN m_hPen = CreatePen(PS_SOLID, thickness, RGB(r, g, b));
HGDIOBJ oldHGDIOBJ2 = SelectObject(m_hDC, m_hPen); //Save Old Seleteed GDI Object To oldHGDIOBJ2
// Draw the rectangle in Background Memory DC
Rectangle(mem_m_hDC, (x - size / 2), (y - size / 2), (x + size / 2), (y + size / 2));
// Copy Background DC To Forground DC
BitBlt(m_hDC, 0, 0, bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight, mem_m_hDC, 0, 0, SRCCOPY);
// Delete Background Buffer Frame
DeleteObject(memBMP);
// Delete Background DC
DeleteDC(mem_m_hDC);
// Double Buffering Ends Here
// Unlock Forground DC
ReleaseDC(m_hWND, m_hDC);
// Remove the objects
DeleteObject(m_hBrush);
DeleteObject(m_hPen);
}
}

Related

Capture pixel data from only a specified window

I want the code below to take a screenshot of a specified window only, becuse this way BitBlt is faster.
The code below takes a screenshot of a window, specified by the name of window, loads the pixel data in a buffer and then re-draws the picture on your screen just to prove the copying worked.
I'd like to take screenshots of browser windows like Google Chrome, but it doesn't seem to work. It draws a black rectangle on my screen with the correct dimensions of the window.
Seems to be working all the time with the window of Minecraft.
Also worked with the Tor Browser.
I noticed that after querying window info, minecraft's window had no child-windows, but when it didn't work with the others, all of them had multiple child-windows.
#include<iostream>
#include<Windows.h>
#include<vector>
using namespace std;
int main() {
HWND wnd = FindWindow(NULL, "Minecraft 1.15.1");//Name of window to be screenshoted
if (!wnd) {
std::cout << "e1\n";
std::cin.get();
return 0;
}
WINDOWINFO wi = { 0 };
wi.cbSize = sizeof(WINDOWINFO);
GetWindowInfo(wnd, &wi);
RECT rect;
GetWindowRect(wnd, &rect);
int width = wi.rcClient.right - wi.rcClient.left;
int height = wi.rcClient.bottom - wi.rcClient.top;
cout << width << height;
/////Fetch window info^^^^^^^^
BYTE* ScreenData = new BYTE[4 * width*height];
// copy screen to bitmap
HDC hScreen = GetWindowDC(wnd);
HDC hDC = CreateCompatibleDC(hScreen);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, width, height);
HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
BitBlt(hDC, 0, 0, width, height, hScreen, 0, 0, SRCCOPY);
SelectObject(hDC, old_obj);
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
std::cout << GetDIBits(hDC, hBitmap, 0, 0, NULL, &bmi, DIB_RGB_COLORS)<<endl;
//std::cout << bmi.bmiHeader.biHeight<< " "<< bmi.bmiHeader.biWidth<<endl;
BYTE*buffer = new BYTE[4* bmi.bmiHeader.biHeight*bmi.bmiHeader.biWidth];
bmi.bmiHeader.biHeight *= -1;
std::cout<<GetDIBits(hDC, hBitmap, 0, bmi.bmiHeader.biHeight, buffer, &bmi, DIB_RGB_COLORS)<<endl;//DIB_PAL_COLORS
/////Load window pixels into a buffer^^^^^^^^
POINT p;
int r = 0;
int g = 0;
int b = 0;
int x = 0;
int y = 0;
HDC sc = GetDC(NULL);
COLORREF color;
while (true) {
if ((y*-1) == bmi.bmiHeader.biHeight+1 && x == bmi.bmiHeader.biWidth-1) { break; }
r = (int)buffer[4 * ((y*bmi.bmiHeader.biWidth) + x)+2];
g = (int)buffer[4 * ((y*bmi.bmiHeader.biWidth) + x)+1];
b = (int)buffer[4 * ((y*bmi.bmiHeader.biWidth) + x) ];
color = RGB(r, g, b);
SetPixel(sc, x, y, color);
x++;
if (x == bmi.bmiHeader.biWidth) {
y++;
x = 0;
}
}
/////Prove that the copying was successful and buffer is full^^^^^^^^
Sleep(5000);
std::cout << "fin\n";
std::cin.get();
return 0;
}
I think the problem is the order you're doing things.
Before you put the bitmap on the clipboard, you should select it out of your memory device context.
Once you put the bitmaps on the clipboard, you no longer own it, so you shouldn't try to delete it.
The best way to do that is probably to move your // clean up section before the // save bitmap to clipboard section and to eliminate your DelectObject(hBitmap) statement. So the tail end of your code should probably be:
BitBlt(hDC, 0, 0, width, height, hScreen, 0, 0, SRCCOPY);
// clean up
SelectObject(hDC, old_obj); // selects hBitmap out of hDC
DeleteDC(hDC);
ReleaseDC(NULL, hScreen);
// save bitmap to clipboard
OpenClipboard(NULL);
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap); // clipboard now owns the bitmap
CloseClipboard();
If you still have a problem after those changes, I would check the return value of the SetClipboardData call. If that's failing, GetLastError may give a clue.

Win32 GDI: AlphaBlend() not using constant alpha value correctly

The code provided at the end draws a grid of red 3x3px rectangles with a random constant alpha value using AlphaBlend(). The output however, turns out not "quite" random:
Notice runs of constant alpha along x-axis.
What might be causing this?
P.S. Stepping though the debugger produces the expected output.
Code to produce output:
void draw_mark(HDC hdc, int x, int y,
COLORREF mark_clr, int mark_w, int mark_h, BYTE alpha);
void produce_output(HWND hWnd) {
InvalidateRect(hWnd, NULL, TRUE);
UpdateWindow(hWnd);
const int grid_w = 64, grid_h = 64;
const int mark_sz = 3;
HDC hdc = GetDC(hWnd);
for(int y = 0; y < grid_h; ++y) {
for(int x = 0; x < grid_w; ++x) {
BYTE rnd_alpha = rand(); // use random alpha for each mark
draw_mark(hdc, x * mark_sz, y * mark_sz,
RGB(255,0,0), mark_sz, mark_sz, rnd_alpha);
}
}
// clean-up
ReleaseDC(hWnd, hdc);
}
// draws a [mark_w x mark_h] rectangle at (x,y) with alpha
void draw_mark(HDC hdc, int x, int y,
COLORREF mark_clr, int mark_w, int mark_h, BYTE alpha)
{
HDC hdcMem = CreateCompatibleDC(NULL);
HBITMAP hbm = CreateCompatibleBitmap(hdc, mark_w, mark_h);
HGDIOBJ hOldBmp = SelectObject(hdcMem, hbm);
for(int x = 0; x < mark_w; ++x) {
for(int y = 0; y < mark_h; ++y) {
SetPixel(hdcMem, x, y, mark_clr);
}
}
POINT marker_center{mark_w / 2, mark_h / 2};
SetPixel(hdcMem, marker_center.x, marker_center.y, RGB(255, 255, 255));
BLENDFUNCTION bf{};
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.AlphaFormat = 0; // ignore source per-pixel alpha and...
bf.SourceConstantAlpha = alpha; // ...use constant alpha provided instead
AlphaBlend(hdc,
x - marker_center.x, y - marker_center.y,
mark_w, mark_h,
hdcMem, 0, 0, mark_w, mark_h, bf);
// clean-up
SelectObject(hdcMem, hOldBmp);
DeleteObject(hbm);
DeleteDC(hdcMem);
};
EDIT - As I look more into it, here are the additional issues I have noticed:
1- Output is normal when AlphaBlend() destination is a memory DC, but not when a window DC. So the issue has to do with bliting directly to screen.
2- Corrupt output is unrelated to use of rand() function. Replacing BYTE rnd_alpha = rand(); with ++alpha also produces somewhat similar corrupt outputs.
3- More interestingly, suspending the thread in the inner loop such as Sleep(some_duration) seems to reduce the corruption. Higher the some_duration, less the corruption. Here is a sample output:
First output is generated by first blitting to a memory DC, then to window. The rest is directly to the window. Notice how corruption increases(i.e. output becomes less random) as thread suspension time decreases.

Screenshot code works but goes on taking RAM and crashes

I'm trying to make a program which makes screenshot of the entire desktop or a specific window, and draw it in a SFML window. Initially it goes fine but after 10 seconds it stop capturing. I also noticed that it go on consuming RAM even though we talk about only 20kb per second. I tried to join code found around the web. When it stops working it prints "2" and then infinite "1", from the code you quickly realize where these errors are.
#include <SFML/Graphics.hpp>
#include <Windows.h>
HBITMAP ScreenShot(HWND hParent, int x, int y, int nWidth, int nHeight)
{
//Get a DC from the parent window
HDC hDC = GetDC(hParent);
//Create a memory DC to store the picture to
HDC hMemDC = CreateCompatibleDC(hDC);
//Create the actual picture
HBITMAP hBackground = CreateCompatibleBitmap(hDC, nWidth, nHeight);
//Select the object and store what we got back
HBITMAP hOld = (HBITMAP)SelectObject(hMemDC, hBackground);
//Now do the actually painting into the MemDC (result will be in the selected object)
//Note: We ask to return on 0,0,Width,Height and take a blit from x,y
BitBlt(hMemDC, 0, 0, nWidth, nHeight, hDC, x, y, SRCCOPY);
//Restore the old bitmap (if any)
SelectObject(hMemDC, hOld);
//Release the DCs we created
ReleaseDC(hParent, hMemDC);
ReleaseDC(hParent, hDC);
//Return the picture (not a clean method, but you get the drill)
return hBackground;
}
bool SFMLLoadHBitmapAsImage(HBITMAP hBitmap, sf::Image *pPicture)
{
HWND hParent = FindWindow(NULL, TEXT("Calculator"));
//if (hParent == NULL)printf("%s", "w");
//Create a DC to get hBitmap information
HDC hDC = GetDC(hParent);
//Create BITMAPINFO variable, set size
BITMAPINFO MyBMInfo = { 0 };
MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
//Get the BITMAPINFO structure from the bitmap
if (0 == GetDIBits(hDC, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS))
{
printf("%s", "1");
return false;
}
//Create the bitmap pixel array each element is [b,g,r]
BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];
//Setting up the structure of the buffer to be received
MyBMInfo.bmiHeader.biCompression = BI_RGB; // No-compression
//Now get the actual data from the picture
if (0 == GetDIBits(hDC, hBitmap, 0, MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS))
{
printf("%s", "2");
return false;
}
//Now create an array of SFML pixels we want to fill
sf::Uint8 *lpPixelWithAlpha = new sf::Uint8[MyBMInfo.bmiHeader.biSizeImage +
(MyBMInfo.bmiHeader.biSizeImage / 3) / 3]; //Add room for alpha
//Loop through each pixel, with steps of four RGBA!
for (int x = 0; x < MyBMInfo.bmiHeader.biSizeImage; x += 4)
{
lpPixelWithAlpha[x] = lpPixels[x + 2]; //lpPixels = r
lpPixelWithAlpha[x + 1] = lpPixels[x + 1]; //lpPixels = g
lpPixelWithAlpha[x + 2] = lpPixels[x]; //lpPixels = b
lpPixelWithAlpha[x + 3] = 255; //Nada alpha (just to adjust if you like)
}
//Remove old DIBsection
delete[] lpPixels;
//Load picture, now with correct pixels and alpha channel
pPicture->create(MyBMInfo.bmiHeader.biWidth,
MyBMInfo.bmiHeader.biHeight, lpPixelWithAlpha);
//Remove the pixels with alphachannel
delete[] lpPixelWithAlpha;
//Release the DC
ReleaseDC(hParent, hDC);//::GDW()
//Notify ok!
return true;
}
int main()
{
.....
hBitmap = ScreenShot(FindWindow(NULL, TEXT("Calculator")), 0, 0, 640, 400);
SFMLLoadHBitmapAsImage(hBitmap, &picture);
.....
}

How to extract bitmap from spritesheet in Win32 C++?

I'm trying to load individual cards from a spritesheet of cards based on suit and rank but I'm unsure of how to construct a new Bitmap object from cutting out Rectangle coordinates in the source image. I'm using <windows.h> currently and trying to find a simple way to accomplish this. I'm looking for something like this:
HBITMAP* twoOfHearts = CutOutFromImage(sourceImage, new Rectangle(0, 0, 76, 116));
From source: http://i.stack.imgur.com/WZ9Od.gif
Here's a function I played with the other week for more-or-less this same task. In my case, I wanted to return a HBRUSH that could be used with FillRect. In that instance, we still need to create a bitmap of the area of interest, before then going on to create a brush from it.
In your case, just return the dstBmp instead. spriteSheet is a global that has had a 256x256 spritesheet loaded. I've hardcoded the size of my sprites to 16x16, you'd need to change that to something like 81x117.
Here's some code that grabs a copy of the required area and some more that uses these 'stamps' to draw a level map. That said - there are all kinds of problems with this approach. Speed is one, excessive work is another one that impacts on the first. Finally, scrolling a window drawn like this produces artefacts.
// grabs a 16x16px section from the spriteSheet HBITMAP
HBRUSH getSpriteBrush(int col, int row)
{
HDC memDC, dstDC, curDC;
HBITMAP oldMemBmp, oldDstBmp, dstBmp;
curDC = GetDC(HWND_DESKTOP);
memDC = CreateCompatibleDC(NULL);
dstDC = CreateCompatibleDC(NULL);
dstBmp = CreateCompatibleBitmap(curDC, 16, 16);
int xOfs, yOfs;
xOfs = 16 * col;
yOfs = 16 * row;
oldMemBmp = (HBITMAP)SelectObject(memDC, spriteSheet);
oldDstBmp = (HBITMAP)SelectObject(dstDC, dstBmp);
BitBlt(dstDC,0,0,16,16, memDC, xOfs,yOfs, SRCCOPY);
SelectObject(memDC, oldMemBmp);
SelectObject(dstDC, oldDstBmp);
ReleaseDC(HWND_DESKTOP, curDC);
DeleteDC(memDC);
DeleteDC(dstDC);
HBRUSH result;
result = CreatePatternBrush(dstBmp);
DeleteObject(dstBmp);
return result;
}
void drawCompoundSprite(int x, int y, HDC paintDC, char *tileIndexes, int numCols, int numRows)
{
int mapCol, mapRow;
HBRUSH curSprite;
RECT curDstRect;
for (mapRow=0; mapRow<numRows; mapRow++)
{
for (mapCol=0; mapCol<numCols; mapCol++)
{
int curSpriteIndex = tileIndexes[mapRow*numCols + mapCol];
int spriteX, spriteY;
spriteX = curSpriteIndex % 16;
spriteY = curSpriteIndex / 16;
curDstRect.left = x + 16*mapCol;
curDstRect.top = y + 16 * mapRow;
curDstRect.right = curDstRect.left + 16;
curDstRect.bottom = curDstRect.top + 16;
curSprite = getSpriteBrush(spriteX, spriteY);
FillRect(paintDC, &curDstRect, curSprite);
DeleteObject(curSprite);
}
}
}
The latter function has since been replaced with the following:
void drawCompoundSpriteFast(int x, int y, HDC paintDC, unsigned char *tileIndexes, int numCols, int numRows, pMapInternalData mData)
{
int mapCol, mapRow;
HBRUSH curSprite;
RECT curDstRect;
HDC memDC;
HBITMAP oldBmp;
memDC = CreateCompatibleDC(NULL);
oldBmp = (HBITMAP)SelectObject(memDC, mData->spriteSheet);
for (mapRow=0; mapRow<numRows; mapRow++)
{
for (mapCol=0; mapCol<numCols; mapCol++)
{
int curSpriteIndex = tileIndexes[mapRow*numCols + mapCol];
int spriteX, spriteY;
spriteX = curSpriteIndex % mData->tileWidth;
spriteY = curSpriteIndex / mData->tileHeight
// Draw sprite as-is
// BitBlt(paintDC, x+16*mapCol, y+16*mapRow,
// mData->tileWidth, mData->tileHeight,
// memDC,
// spriteX * 16, spriteY*16,
// SRCCOPY);
// Draw sprite with magenta rgb(255,0,255) areas treated as transparent (empty)
TransparentBlt(
paintDC,
x+mData->tileWidth*mapCol, y+mData->tileHeight*mapRow,
mData->tileWidth, mData->tileHeight,
memDC,
spriteX * mData->tileWidth, spriteY*mData->tileHeight,
mData->tileWidth, mData->tileHeight,
RGB(255,0,255)
);
}
}
SelectObject(memDC, oldBmp);
DeleteObject(memDC);
}

EMF quality diminishes when window is shrinked, but is good when window dimensions are high

I am creating desktop application using C++ and pure WinApi. I need to display image that was given to me as SVG.
Since WinAPI supports only EMF files as vector format, I have used Inkscape to convert the file into EMF. My graphics design skills are at beginner level, but I have managed to convert SVG file into EMF successfully. However, the result is not looking as the original one, it is less "precise" so to say.
If I export the SVG as PNG and display it with GDI+, the result is the same as the original file. Unfortunately I need vector format.
To see exactly what I mean, download SVG, and EMF and PNG that I made here. Just click on Download:test.rar above 5 yellow stars ( see image below ).
Here are the instructions for creating minimal application that reproduces the problem:
1) Create default Win32 project in Visual Studio ( I use VS 2008, but this shouldn't be the problem );
2) Rewrite WM_PAINT like this:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
RECT rcClient;
GetClientRect( hWnd, &rcClient );
FillRect( hdc, &rcClient, (HBRUSH)GetStockObject( LTGRAY_BRUSH) );
// put metafile in the same place your app is
HENHMETAFILE hemf = GetEnhMetaFile( L".\\test.emf" );
ENHMETAHEADER emh;
GetEnhMetaFileHeader( hemf, sizeof(emh), &emh );
// rescale metafile, and keep proportion
UINT o_height = emh.rclFrame.bottom - emh.rclFrame.top,
o_width = emh.rclFrame.right - emh.rclFrame.left;
float scale = 0.5;
scale = (float)( rcClient.right - rcClient.left ) / o_width;
if( (float)( rcClient.bottom - rcClient.top ) / o_height < scale )
scale = (float)( rcClient.bottom - rcClient.top ) / o_height;
int marginX = ( rcClient.right - rcClient.left ) - (int)( o_width * scale );
int marginY = ( rcClient.bottom - rcClient.top ) - (int)( o_height * scale );
marginX /= 2;
marginY /= 2;
rcClient.left = rcClient.left + marginX;
rcClient.right = rcClient.right - marginX;
rcClient.top = rcClient.top + marginY;
rcClient.bottom = rcClient.bottom - marginY;
// Draw the picture.
PlayEnhMetaFile( hdc, hemf, &rcClient );
// Release the metafile handle.
DeleteEnhMetaFile(hemf);
EndPaint(hWnd, &ps);
}
break;
3) Add following handlers for WM_SIZE and WM_ERASEBKGND just below WM_PAINT :
case WM_SIZE:
InvalidateRect( hWnd, NULL, FALSE );
return 0L;
case WM_ERASEBKGND:
return 1L;
4) Resize the window to the smallest possible size, and then maximize it.
Notice that the bigger the window gets, the better the image quality is, but the smaller it gets the "less precise" the image gets. I tested this on Windows XP.
I am asking your help to get the same graphic quality of the EMF file as the original SVG.
Thank you for your time and efforts. Best regards.
Solved it!
The solution makes much, if not all of the solution I've submitted redundant. I've therefore decided to replace it with this one.
There's a number of things to take into account and a number of concepts that are employed to get the desired result. These include (in no particular order)
The need to set a maskColour that closely matches the background, while also not being present in the final computed image. Pixels that straddle the border between transparent/opaque areas are blended values of the mask and the EMF's colour at that point.
The need to choose a scaling rate that's appropriate for the source image - in the case of this image and the code I've used, I chose 8. This means that we're drawing this particular EMF at about a megapixel, even though the destination is likely to be in the vicinity of about 85k pixels.
The need to manually set the alpha channel of the generated 32bit HBITMAP, since the GDI stretching/drawing functions disregard this channel, yet the AlphaBlend function requires them to be accurate.
I also note that I've used old code to draw the background manually each time the screen is refreshed. A much better approach would be to create a patternBrush once which is then simply copied using the FillRect function. This is much faster than filling the rect with a solid colour and then drawing the lines over the top. I can't be bothered to re-write that part of the code, though I'll include a snippet for reference that I've used in other projects in the past.
Here's a couple of shots of the result I get from the code below:
Here's the code I used to achieve it:
#define WINVER 0x0500 // for alphablend stuff
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <stdint.h>
#include "resource.h"
HINSTANCE hInst;
HBITMAP mCreateDibSection(HDC hdc, int width, int height, int bitCount)
{
BITMAPINFO bi;
ZeroMemory(&bi, sizeof(bi));
bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
bi.bmiHeader.biWidth = width;
bi.bmiHeader.biHeight = height;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = bitCount;
bi.bmiHeader.biCompression = BI_RGB;
return CreateDIBSection(hdc, &bi, DIB_RGB_COLORS, 0,0,0);
}
void makePixelsTransparent(HBITMAP bmp, byte r, byte g, byte b)
{
BITMAP bm;
GetObject(bmp, sizeof(bm), &bm);
int x, y;
for (y=0; y<bm.bmHeight; y++)
{
uint8_t *curRow = (uint8_t *)bm.bmBits;
curRow += y * bm.bmWidthBytes;
for (x=0; x<bm.bmWidth; x++)
{
if ((curRow[x*4 + 0] == b) && (curRow[x*4 + 1] == g) && (curRow[x*4 + 2] == r))
{
curRow[x*4 + 0] = 0; // blue
curRow[x*4 + 1] = 0; // green
curRow[x*4 + 2] = 0; // red
curRow[x*4 + 3] = 0; // alpha
}
else
curRow[x*4 + 3] = 255; // alpha
}
}
}
// Note: maskCol should be as close to the colour of the background as is practical
// this is because the pixels that border transparent/opaque areas will have
// their colours derived from a blending of the image colour and the maskColour
//
// I.e - if drawing to a white background (255,255,255), you should NOT use a mask of magenta (255,0,255)
// this would result in a magenta-ish border
HBITMAP HbitmapFromEmf(HENHMETAFILE hEmf, int width, int height, COLORREF maskCol)
{
ENHMETAHEADER emh;
GetEnhMetaFileHeader(hEmf, sizeof(emh), &emh);
int emfWidth, emfHeight;
emfWidth = emh.rclFrame.right - emh.rclFrame.left;
emfHeight = emh.rclFrame.bottom - emh.rclFrame.top;
// these are arbitrary and selected to give a good mix of speed and accuracy
// it may be worth considering passing this value in as a parameter to allow
// fine-tuning
emfWidth /= 8;
emfHeight /= 8;
// draw at 'native' size
HBITMAP emfSrcBmp = mCreateDibSection(NULL, emfWidth, emfHeight, 32);
HDC srcDC = CreateCompatibleDC(NULL);
HBITMAP oldSrcBmp = (HBITMAP)SelectObject(srcDC, emfSrcBmp);
RECT tmpEmfRect, emfRect;
SetRect(&tmpEmfRect, 0,0,emfWidth,emfHeight);
// fill background with mask colour
HBRUSH bkgBrush = CreateSolidBrush(maskCol);
FillRect(srcDC, &tmpEmfRect, bkgBrush);
DeleteObject(bkgBrush);
// draw emf
PlayEnhMetaFile(srcDC, hEmf, &tmpEmfRect);
HDC dstDC = CreateCompatibleDC(NULL);
HBITMAP oldDstBmp;
HBITMAP result;
result = mCreateDibSection(NULL, width, height, 32);
oldDstBmp = (HBITMAP)SelectObject(dstDC, result);
SetStretchBltMode(dstDC, HALFTONE);
StretchBlt(dstDC, 0,0,width,height, srcDC, 0,0, emfWidth,emfHeight, SRCCOPY);
SelectObject(srcDC, oldSrcBmp);
DeleteDC(srcDC);
DeleteObject(emfSrcBmp);
SelectObject(dstDC, oldDstBmp);
DeleteDC(dstDC);
makePixelsTransparent(result, GetRValue(maskCol),GetGValue(maskCol),GetBValue(maskCol));
return result;
}
int rectWidth(RECT &r)
{
return r.right - r.left;
}
int rectHeight(RECT &r)
{
return r.bottom - r.top;
}
void onPaintEmf(HWND hwnd, HENHMETAFILE srcEmf)
{
PAINTSTRUCT ps;
RECT mRect, drawRect;
HDC hdc;
double scaleWidth, scaleHeight, scale;
int spareWidth, spareHeight;
int emfWidth, emfHeight;
ENHMETAHEADER emh;
GetClientRect( hwnd, &mRect );
hdc = BeginPaint(hwnd, &ps);
// calculate the draw-size - retain aspect-ratio.
GetEnhMetaFileHeader(srcEmf, sizeof(emh), &emh );
emfWidth = emh.rclFrame.right - emh.rclFrame.left;
emfHeight = emh.rclFrame.bottom - emh.rclFrame.top;
scaleWidth = (double)rectWidth(mRect) / emfWidth;
scaleHeight = (double)rectHeight(mRect) / emfHeight;
scale = min(scaleWidth, scaleHeight);
int drawWidth, drawHeight;
drawWidth = emfWidth * scale;
drawHeight = emfHeight * scale;
spareWidth = rectWidth(mRect) - drawWidth;
spareHeight = rectHeight(mRect) - drawHeight;
drawRect = mRect;
InflateRect(&drawRect, -spareWidth/2, -spareHeight/2);
// create a HBITMAP from the emf and draw it
// **** note that the maskCol matches the background drawn by the below function ****
HBITMAP srcImg = HbitmapFromEmf(srcEmf, drawWidth, drawHeight, RGB(230,230,230) );
HDC memDC;
HBITMAP old;
memDC = CreateCompatibleDC(hdc);
old = (HBITMAP)SelectObject(memDC, srcImg);
byte alpha = 255;
BLENDFUNCTION bf = {AC_SRC_OVER,0,alpha,AC_SRC_ALPHA};
AlphaBlend(hdc, drawRect.left,drawRect.top, drawWidth,drawHeight,
memDC, 0,0,drawWidth,drawHeight, bf);
SelectObject(memDC, old);
DeleteDC(memDC);
DeleteObject(srcImg);
EndPaint(hwnd, &ps);
}
void drawHeader(HDC dst, RECT headerRect)
{
HBRUSH b1;
int i,j;//,headerHeight = (headerRect.bottom - headerRect.top)+1;
b1 = CreateSolidBrush(RGB(230,230,230));
FillRect(dst, &headerRect,b1);
DeleteObject(b1);
HPEN oldPen, curPen;
curPen = CreatePen(PS_SOLID, 1, RGB(216,216,216));
oldPen = (HPEN)SelectObject(dst, curPen);
for (j=headerRect.top;j<headerRect.bottom;j+=10)
{
MoveToEx(dst, headerRect.left, j, NULL);
LineTo(dst, headerRect.right, j);
}
for (i=headerRect.left;i<headerRect.right;i+=10)
{
MoveToEx(dst, i, headerRect.top, NULL);
LineTo(dst, i, headerRect.bottom);
}
SelectObject(dst, oldPen);
DeleteObject(curPen);
MoveToEx(dst, headerRect.left,headerRect.bottom,NULL);
LineTo(dst, headerRect.right,headerRect.bottom);
}
BOOL CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HENHMETAFILE hemf;
switch(uMsg)
{
case WM_INITDIALOG:
{
hemf = GetEnhMetaFile( "test.emf" );
}
return TRUE;
case WM_PAINT:
onPaintEmf(hwndDlg, hemf);
return 0;
case WM_ERASEBKGND:
{
RECT mRect;
GetClientRect(hwndDlg, &mRect);
drawHeader( (HDC)wParam, mRect);
}
return true;
case WM_SIZE:
InvalidateRect( hwndDlg, NULL, true );
return 0L;
case WM_CLOSE:
{
EndDialog(hwndDlg, 0);
}
return TRUE;
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
}
}
return TRUE;
}
return FALSE;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
hInst=hInstance;
InitCommonControls();
return DialogBox(hInst, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DlgMain);
}
Finally, here's an example of creating a patternBrush for filling the background using the FillRect function. This approach is suitable for any tileable background.
HBRUSH makeCheckerBrush(int squareSize, COLORREF col1, COLORREF col2)
{
HDC memDC, tmpDC = GetDC(NULL);
HBRUSH result, br1, br2;
HBITMAP old, bmp;
RECT rc, r1, r2;
br1 = CreateSolidBrush(col1);
br2 = CreateSolidBrush(col2);
memDC = CreateCompatibleDC(tmpDC);
bmp = CreateCompatibleBitmap(tmpDC, 2*squareSize, 2*squareSize);
old = (HBITMAP)SelectObject(memDC, bmp);
SetRect(&rc, 0,0, squareSize*2, squareSize*2);
FillRect(memDC, &rc, br1);
// top right
SetRect(&r1, squareSize, 0, 2*squareSize, squareSize);
FillRect(memDC, &r1, br2);
// bot left
SetRect(&r2, 0, squareSize, squareSize, 2*squareSize);
FillRect(memDC, &r2, br2);
SelectObject(memDC, old);
DeleteObject(br1);
DeleteObject(br2);
ReleaseDC(0, tmpDC);
DeleteDC(memDC);
result = CreatePatternBrush(bmp);
DeleteObject(bmp);
return result;
}
Example of result, created with:
HBRUSH bkBrush = makeCheckerBrush(8, RGB(153,153,153), RGB(102,102,102));