Is it necessary to use ScrollWindowEx/ScrollWindow/ScrollDC for scrolling? - c++

I am in the process of implementing scrolling in a custom edit control that I have been working on. What I was wondering was, is it essential to use ScrollWindowEx/ScrollWindow/ScrollDC to implement scrolling? I see that ScrollWindowEx just scrolls the paint area. All that is fine, but since my edit control implements double buffering, I'd have to update my BitBlt as well. That is a trivial thing, but I was wondering if it is essential. If I use only SetScrollInfo, that would also have the same effect. The only advantage that I see here is that when a user scrolls up or down, there would already be some text over there(because ScrollWindowEx shifts the target client area) and I wouldn't have to bother aobut repainting. Is there any other advantage, or reason why ScrollWindowEx is used? I am new to scrolling in win32, and this is actually the first time I've had to do all the processing on my own instead of the api doing it for me, so I really don't know how to go about this.
P.S.
Just to be clear, I'm not using MFC. Only Win32 api.
Programming Language : unmanaged C++

You can implement scrolling any way you want.
ScrollWindow etc will scroll the relevant part of the client area and invalidate the part that then needs repainting.
In general this is an efficient and simple way to handle scrolling, so obviously it's popular. But if you can show that in your case you can achieve the same result more efficiently, go for it.

An example for comparison can be found in ScrollCall.
Also, there's an interesting example in C of using ScrollDC to scroll a screen here
In that example, lprcScroll and lprcClip refer to the same RECT, which delineates the painted output rectangle from the scrolling.
The painted output from ScrollDC can be handled by a routine in WM_PAINT, so long as the call is followed by InvalidateRect. However, unlike ScrollWindow(Ex), ScrollDC doesn't care about any owned/child/parent/sibling windows which may be overlaid in the DC, it is more suited to images or text in single controls. For scrolling a large DC, be sure to use lprcScroll and lprcClip to avoid scrolling areas of the DC which are not visible.
As mentioned, ScrollWindoW(Ex) is the preferred choice, especially for windows with mixed control content.
Included for further illustration is an amazingly venerable implementation of all the three functions copied from here:
* Scroll windows and DCs
*
* Copyright David W. Metcalfe, 1993
*
*/
static char Copyright[] = "Copyright David W. Metcalfe, 1993";
#include <stdlib.h>
#include "windows.h"
#include "gdi.h"
#include "stddebug.h"
/* #define DEBUG_SCROLL /* */
/* #undef DEBUG_SCROLL /* */
#include "debug.h"
static int RgnType;
/*************************************************************************
* ScrollWindow (USER.61)
*/
void ScrollWindow(HWND hwnd, short dx, short dy, LPRECT rect, LPRECT clipRect)
{
HDC hdc;
HRGN hrgnUpdate;
RECT rc, cliprc;
dprintf_scroll(stddeb,"ScrollWindow: dx=%d, dy=%d, rect=%d,%d,%d,%d\n",
dx, dy, rect->left, rect->top, rect->right, rect->bottom);
hdc = GetDC(hwnd);
if (rect == NULL)
GetClientRect(hwnd, &rc);
else
CopyRect(&rc, rect);
if (clipRect == NULL)
GetClientRect(hwnd, &cliprc);
else
CopyRect(&cliprc, clipRect);
hrgnUpdate = CreateRectRgn(0, 0, 0, 0);
ScrollDC(hdc, dx, dy, &rc, &cliprc, hrgnUpdate, NULL);
InvalidateRgn(hwnd, hrgnUpdate, TRUE);
ReleaseDC(hwnd, hdc);
}
/*************************************************************************
* ScrollDC (USER.221)
*/
BOOL ScrollDC(HDC hdc, short dx, short dy, LPRECT rc, LPRECT cliprc,
HRGN hrgnUpdate, LPRECT rcUpdate)
{
HRGN hrgnClip, hrgn1, hrgn2;
POINT src, dest;
short width, height;
DC *dc = (DC *)GDI_GetObjPtr(hdc, DC_MAGIC);
dprintf_scroll(stddeb, "ScrollDC: dx=%d, dy=%d, rc=%d,%d,%d,%d\n", dx, dy,
rc->left, rc->top, rc->right, rc->bottom);
if (rc == NULL)
return FALSE;
if (cliprc)
{
hrgnClip = CreateRectRgnIndirect(cliprc);
SelectClipRgn(hdc, hrgnClip);
}
if (dx > 0)
{
src.x = XDPTOLP(dc, rc->left);
dest.x = XDPTOLP(dc, rc->left + abs(dx));
}
else
{
src.x = XDPTOLP(dc, rc->left + abs(dx));
dest.x = XDPTOLP(dc, rc->left);
}
if (dy > 0)
{
src.y = YDPTOLP(dc, rc->top);
dest.y = YDPTOLP(dc, rc->top + abs(dy));
}
else
{
src.y = YDPTOLP(dc, rc->top + abs(dy));
dest.y = YDPTOLP(dc, rc->top);
}
width = rc->right - rc->left - abs(dx);
height = rc->bottom - rc->top - abs(dy);
if (!BitBlt(hdc, dest.x, dest.y, width, height, hdc, src.x, src.y,
SRCCOPY))
return FALSE;
if (hrgnUpdate)
{
if (dx > 0)
hrgn1 = CreateRectRgn(rc->left, rc->top, rc->left+dx, rc->bottom);
else if (dx < 0)
hrgn1 = CreateRectRgn(rc->right+dx, rc->top, rc->right,
rc->bottom);
else
hrgn1 = CreateRectRgn(0, 0, 0, 0);
if (dy > 0)
hrgn2 = CreateRectRgn(rc->left, rc->top, rc->right, rc->top+dy);
else if (dy < 0)
hrgn2 = CreateRectRgn(rc->left, rc->bottom+dy, rc->right,
rc->bottom);
else
hrgn2 = CreateRectRgn(0, 0, 0, 0);
RgnType = CombineRgn(hrgnUpdate, hrgn1, hrgn2, RGN_OR);
}
if (rcUpdate) GetRgnBox( hrgnUpdate, rcUpdate );
return TRUE;
}
/*************************************************************************
* ScrollWindowEx (USER.319)
*/
int ScrollWindowEx(HWND hwnd, short dx, short dy, LPRECT rect, LPRECT clipRect,
HRGN hrgnUpdate, LPRECT rcUpdate, WORD flags)
{
HDC hdc;
RECT rc, cliprc;
dprintf_scroll(stddeb,"ScrollWindowEx: dx=%d, dy=%d, rect=%d,%d,%d,%d\n",
dx, dy, rect->left, rect->top, rect->right, rect->bottom);
hdc = GetDC(hwnd);
if (rect == NULL)
GetClientRect(hwnd, &rc);
else
CopyRect(&rc, rect);
if (clipRect == NULL)
GetClientRect(hwnd, &cliprc);
else
CopyRect(&cliprc, clipRect);
ScrollDC(hdc, dx, dy, &rc, &cliprc, hrgnUpdate, rcUpdate);
if (flags | SW_INVALIDATE)
{
RedrawWindow(hwnd, NULL, hrgnUpdate,
RDW_INVALIDATE | ((flags & SW_ERASE) ? RDW_ERASENOW : 0));
}
ReleaseDC(hwnd, hdc);
return RgnType;
}

Related

WIN32 how to cache drawing results to a bitmap?

Because there are hover and leave events of the mouse in the interface, if re drawing in each leave event leads to a large amount of resource occupation, how to draw and cache it in memory only when the program is started or the interface is notified of changes, and send InvalidateRect() to directly use BitBlt mapping instead of re drawing in the rest of the time?
int dystat = 0;
int anistat = 0;
void CreatePanelDynamic(HWND h, HDC hdc, DRAWPANEL DrawFun, int Flag = 0)
{
//On hMemDC.
if (PanelID == PrevPanelID)
{
//
if (dystat == 0)
{
RECT rc;
GetClientRect(h, &rc);
BitBlt(hdc, 0, 0, rc.right - rc.left, rc.bottom - rc.top, in_ghdc, 0, 0, SRCCOPY); //by bitmap
}
else
{
_CreatePanel(h, hdc, DrawFun); //directly
dystat = 0;
}
anistat = 0;
}
if (PanelID != PrevPanelID) //animation
{
if (Flag == 0)
{
CreatePanelAnimation(h, hdc, DrawFun);
dystat = 1;
}
if (Flag == 1)
{
_CreatePanel(h, hdc, DrawFun);
dystat = 1;
}
PrevPanelID = PanelID;
anistat = 0;
}
}
My program is already using MemDC.

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.

Capture game window using wm_paint

I'm trying to capture a game window using SendMessage with wm_paint and wm_printclient.
I already did it successfully using PrintWindow but the game can change between graphic engines and for some of them I get a white rectangle. I was hoping using SendMessage would not have this problem.
The problem is I'm getting a black rectangle as result of SendMessage, for any graphic engine and even for any program/window.
void capture::captureProgramScreen(HWND hwnd, tImage* res)
{
RECT rc;
GetWindowRect(hwnd, &rc);
//create
HDC hdcScreen = GetDC(NULL);
HDC hdc = CreateCompatibleDC(hdcScreen);
HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen, rc.right - rc.left, rc.bottom - rc.top);
res->width = rc.right - rc.left - 17;
res->height = rc.bottom - rc.top - 39;
res->absoluteTop = rc.top;
res->absoluteLeft = rc.left;
SelectObject(hdc, hbmp);
SendMessage(hwnd, WM_PRINTCLIENT, (int)hdc, PRF_CHILDREN | PRF_CLIENT | PRF_ERASEBKGND | PRF_NONCLIENT | PRF_OWNED);
BITMAPINFO MyBMInfo = { 0 };
MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
if (0 == GetDIBits(hdc, hbmp, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS))
{
res->error = true;
res->errorcode = 2;
return;
}
res->v = std::vector<BYTE>(MyBMInfo.bmiHeader.biSizeImage);
MyBMInfo.bmiHeader.biBitCount = 32;
MyBMInfo.bmiHeader.biCompression = BI_RGB;
MyBMInfo.bmiHeader.biHeight = abs(MyBMInfo.bmiHeader.biHeight);
if (0 == GetDIBits(hdc, hbmp, 0, MyBMInfo.bmiHeader.biHeight, &(res->v[0]), &MyBMInfo, DIB_RGB_COLORS))
{
res->error = true;
res->errorcode = 3;
res->width = 0;
res->height = 0;
res->v.clear();
return;
}
//4 Bytes per pixel order (B G R A) from [left to right] [bottom to top]
return;
}
Thank you!
There are at least a few possible issues:
Not all programs/windows implement WM_PRINTCLIENT. Many games don't even implement WM_PAINT, as they draw continuously at their desired frame rate rather than in response to a need to update themselves. Many games use newer graphics APIs that don't really draw to a Device Context.
I'm not sure why you have two calls to GetDIBits. The first one happens before you initialize all the fields of the BITMAPINFO, so that one will fail. It's still not completely filled out by the time you make the second call.

How to change background color of SysDateTimePick32 or CDateTimeCtrl?

I seem not to be able to change the background color of a SysDateTimePick32 control (white in this case):
in my Win32/MFC application.
I first tried overriding OnCtlColor notification message in the parent window, which wasn't even called.
I then tried a subclassing approach described here, which was called alright but the control did not change visually. (I did my tests on Windows 8.1 machine.)
So does anyone have idea how to do it?
PS. I need this to work on Windows XP and up.
I'm not sure how much of a hack the following solution is, but it seems to work for a quick fix until someone suggests a better one. Again, it is based on this code, and also requires Windows Vista or later OS:
//Called from a subclassed WndProc
case WM_PAINT:
{
PAINTSTRUCT ps;
::BeginPaint(hWnd, &ps);
//Render control
RenderWithBkgndColor(hWnd, ps.hdc, RGB(255, 0, 0));
::EndPaint(hWnd, &ps);
return 0;
}
void RenderWithBkgndColor(HWND hWnd, HDC hDC, COLORREF clrBkgnd)
{
//Render control with the background color
//'clrBkgnd' = color for the background (if control is enabled)
//SOURCE:
// http://comp.os.ms-windows.programmer.win32.narkive.com/0H8cHhw1/setting-color-for-sysdatetimepick32-control
RECT rect;
::GetWindowRect(hWnd, &rect);
::MapWindowPoints(NULL, hWnd, (LPPOINT)&rect, 2);
long nWidth = rect.right - rect.left, nHeight = rect.bottom - rect.top;
HDC hDCMem = ::CreateCompatibleDC(hDC);
HBITMAP hBitmap = ::CreateBitmap(nWidth, nHeight, ::GetDeviceCaps(hDC, PLANES), ::GetDeviceCaps(hDC, BITSPIXEL), (const void *) NULL);
if (hBitmap)
{
HBITMAP hBitmapOld = (HBITMAP)::SelectObject(hDCMem, hBitmap);
//Render control itself
::SendMessage(hWnd, WM_PRINT, (WPARAM)hDCMem, PRF_CLIENT | PRF_CHILDREN | PRF_NONCLIENT);
//Only if we have the color
if(clrBkgnd != NULL)
{
//Only if control is enabled
if((::GetWindowLongPtr(hWnd, GWL_STYLE) & (WS_VISIBLE | WS_DISABLED)) == (WS_VISIBLE | 0))
{
#define ALLOWED_DIFF 20
DWORD dwBkgClr = ::GetSysColor(COLOR_WINDOW); //0xFFFFFF;
DWORD br0 = dwBkgClr & 0xFF;
DWORD br1 = (dwBkgClr & 0xFF00) >> 8;
DWORD br2 = (dwBkgClr & 0xFF0000) >> (8 * 2);
for(int y = 0; y < nHeight; y++)
{
for(int x = 0; x < nWidth; x++)
{
COLORREF clrPxl = ::GetPixel(hDCMem, x, y);
DWORD r0 = clrPxl & 0xFF;
DWORD r1 = (clrPxl & 0xFF00) >> 8;
DWORD r2 = (clrPxl & 0xFF0000) >> (8 * 2);
int nDiff_r0 = r0 - br0;
int nDiff_r1 = r1 - br1;
int nDiff_r2 = r2 - br2;
if(abs(nDiff_r0) < ALLOWED_DIFF &&
abs(nDiff_r1) < ALLOWED_DIFF &&
abs(nDiff_r2) < ALLOWED_DIFF)
{
::SetPixel(hDCMem, x, y, clrBkgnd);
}
}
}
}
}
::BitBlt(hDC, rect.left, rect.top, nWidth, nHeight, hDCMem, 0, 0, SRCCOPY);
::SelectObject(hDCMem, hBitmapOld);
::DeleteObject(hBitmap);
}
::DeleteDC(hDCMem);
}

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));