How to move a bitmap slowly on the window by Direct2D? - c++

I am making a game by Direct2D and I need some help. In this game, I need to move a bitmap on the window slowly and rotates. However, when I ran the code, the bitmap didn't show on the window, if I wait a moment, it shows. I don't know how to deal with the problem because if it sleeps after it moves, the game suspended animation. And this is the following code:
VOID Rotate()
{
ID2D1BitmapBrush* pBitmapBrush = NULL;
g_pRenderTarget->CreateBitmapBrush(g_pMainRole, &pBitmapBrush);
pBitmapBrush->SetExtendModeX(D2D1_EXTEND_MODE_CLAMP);
pBitmapBrush->SetExtendModeY(D2D1_EXTEND_MODE_CLAMP);
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(g_hGameWnd, &ps);
g_pRenderTarget->BeginDraw();
DOUBLE dCurrentX = 0;
UINT uCurrentAngle = 0;
RECT rc = { 0 };
GetClientRect(g_hGameWnd, &rc);
while (dCurrentX <= rc.right - 128)
{
pBitmapBrush->SetTransform(
D2D1::Matrix3x2F::Rotation(uCurrentAngle % 360, D2D1::Point2F(64, 64)) *
D2D1::Matrix3x2F::Translation(D2D1::SizeF(dCurrentX, 60))
);
g_pRenderTarget->DrawBitmap(g_pBgBitmap, D2D1::RectF(0, 0,
g_pRenderTarget->GetSize().width, g_pRenderTarget->GetSize().height));
D2D1_RECT_F rc = D2D1::RectF(350, 150, 1200, 700);
g_pRenderTarget->DrawRoundedRectangle(D2D1::RoundedRect(rc, 10, 10), g_pBrush, 10.0F);
if (pBitmapBrush != NULL)
{
g_pRenderTarget->FillRectangle(D2D1::RectF(dCurrentX, 60, 1920, 1080), pBitmapBrush);
}
pBitmapBrush->SetTransform(D2D1::Matrix3x2F::Identity());
dCurrentX += 0.1;
uCurrentAngle++;
// Sleep(10);
// This code makes my game suspend animation.
}
g_pRenderTarget->EndDraw();
EndPaint(g_hGameWnd, &ps);
SafeRelease(pBitmapBrush); // This is my inline function
// It calls pBitmapBrush->Release() if pBitmapBrush is not null.
}
Thanks.

Related

Why is drawing to a hidden HDC giving different results than when drawing to the HDC returned by GetDC(HWND)

I have two methods, paintDoubleBuffered and paint. They are both supposed to draw this image on the screen:
The image is made up of approximately 12 smaller images each sized 256x256 tiled together.
My standard painting method works as expected. Here it is:
void MainWindow::paint(HWND hwnd) {
HDC hdc{GetDC(hwnd)};
paint(hwnd, hdc);
ReleaseDC(hwnd, hdc);
}
void MainWindow::paint(HWND hwnd, HDC hdc) {
constexpr INT img_width{ MDNR_Map::pannel_width };
constexpr INT img_height{ MDNR_Map::pannel_height };
const INT width{ GetDeviceCaps(hdc, HORZRES) };
const INT height{ GetDeviceCaps(hdc, VERTRES) };
const INT num_width_pannels{ (width / img_width) + 1 };
const INT num_height_pannels{ (height / img_height) + 1 };
Gdiplus::Graphics g(hdc);
g.SetCompositingMode(CompositingMode::CompositingModeSourceCopy);
g.SetInterpolationMode(InterpolationMode::InterpolationModeNearestNeighbor);
for (INT y = 0; y < num_height_pannels; y++) {
for (INT x = 0; x < num_width_pannels; x++) {
Location_t get_loaction(x + map_location.x, y + map_location.y, map_location.layer);
const IMG_t v{ mdnr_map.get(get_loaction) };
const Point drawPoint((INT)(img_width * x), (INT)(img_height * y));
Status stat{ g.DrawImage(v, drawPoint) };
if (stat != Status::Ok)
{
throw std::runtime_error(":(");
}
}
}
}
The issue with that paint method is that mdnr_map.get is an io bound call and may take several micro seconds. Because I need to call it about 12 times, it can lead to flickering.
To solve this, I attempted to write a double-buffered paint method, which is as follows:
void MainWindow::paintDoubleBuffered(HWND hwnd) {
// Get DC for window
HDC hdc{ GetDC(hwnd) };
const INT win_width{ GetDeviceCaps(hdc, HORZRES) };
const INT win_height{ GetDeviceCaps(hdc, VERTRES) };
// Create an off-screen DC for double-buffering
HDC hdcMem{ CreateCompatibleDC(hdc) };
HBITMAP hbmMem{ CreateCompatibleBitmap(hdc, win_width, win_height) };
HANDLE hOld{ SelectObject(hdcMem, hbmMem) };
// Draw into hdcMem here
paint(hwnd, hdcMem);
// Transfer the off-screen DC to the screen
BitBlt(hdc, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY);
// Free-up the off-screen DC
SelectObject(hdcMem, hOld);
DeleteObject(hbmMem);
DeleteDC(hdcMem);
}
However, this does not work and instead produces this abombination of an image:
With a little poking and proding, I was able to discover that if I changed my double buffered paint method by multiplying the image size by 1.5, the image was no longer so garbled, but was now zoomed in by a factor of 1.5
void MainWindow::paintDoubleBuffered(HWND hwnd) {
// Get DC for window
HDC hdc{ GetDC(hwnd) };
const INT win_width{ GetDeviceCaps(hdc, HORZRES) };
const INT win_height{ GetDeviceCaps(hdc, VERTRES) };
// Create an off-screen DC for double-buffering
HDC hdcMem{ CreateCompatibleDC(hdc) };
HBITMAP hbmMem{ CreateCompatibleBitmap(hdc, win_width, win_height) };
HANDLE hOld{ SelectObject(hdcMem, hbmMem) };
// Draw into hdcMem here
constexpr INT img_width{ MDNR_Map::pannel_width + 128 }; // MDNR_Map::pannel_width is 256
constexpr INT img_height{ MDNR_Map::pannel_height + 128}; // MDNR_Map::pannel_height is 256
const INT num_width_pannels{ (win_width / img_width) + 1 };
const INT num_height_pannels{ (win_height / img_height) + 1 };
Gdiplus::Graphics g(hdcMem);
g.SetCompositingMode(CompositingMode::CompositingModeSourceCopy);
g.SetInterpolationMode(InterpolationMode::InterpolationModeNearestNeighbor);
for (INT y = 0; y < num_height_pannels; y++) {
for (INT x = 0; x < num_width_pannels; x++) {
Location_t get_loaction(x + map_location.x, y + map_location.y, map_location.layer);
Gdiplus::Bitmap* pannel{ mdnr_map.get(get_loaction) };
const Point drawPoint((INT)(img_width * x), (INT)(img_height * y));
Status stat{ g.DrawImage(pannel, drawPoint) };
if (stat != Status::Ok)
{
throw std::runtime_error(":(");
}
}
}
// Transfer the off-screen DC to the screen
BitBlt(hdc, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY);
// Free-up the off-screen DC
SelectObject(hdcMem, hOld);
DeleteObject(hbmMem);
DeleteDC(hdcMem);
}
My question is why does drawing to the HDC returned by CreateCompatibleBitmap produce a different result than drawing to the HDC returned by GetDC?
I have tried:
All raster-operation codes for BltBlt.
I have checked that the temporary HDC is the same size as the window.
I have tried replacing the code snippet
const INT win_width{ GetDeviceCaps(hdc, HORZRES) };
const INT win_height{ GetDeviceCaps(hdc, VERTRES) };
with
RECT rect;
GetWindowRect(hwnd, &rect);
const INT win_width{ rect.right - rect.left };
const INT win_height{ rect.bottom - rect.top };
I have also called SetProcessDPIAware() before drawing.
Upon feedback from #Paul Sanders, I rewrote my paintDoubleBuffered method as follows, NOTE, I have called BufferedPaintInit in the object constructor:
void MainWindow::paintDoubleBuffered(HWND hwnd) {
PAINTSTRUCT ps;
HDC hdc{ BeginPaint(hwnd, &ps)};
RECT sz;
GetWindowRect(hwnd, &sz);
BP_PAINTPARAMS paintParams = { 0 };
paintParams.cbSize = sizeof(paintParams);
paintParams.dwFlags = BPPF_ERASE;
paintParams.pBlendFunction = NULL;
paintParams.prcExclude = NULL;
HDC hdcBuffer;
HPAINTBUFFER hBufferedPaint = BeginBufferedPaint(hdc, &sz, BPBF_COMPATIBLEBITMAP, &paintParams, &hdcBuffer);
if (hBufferedPaint && this->bufferedInitResult == Ok) {
// Application specific painting code
paint(hwnd, hdcBuffer);
EndBufferedPaint(hBufferedPaint, TRUE);
}
else{
paint(hwnd, hdc);
}
ReleaseDC(hwnd, hdc);
}
Unfortunately, this did not work and the resultant screen looks like this:
The issue ended up being not in the approach to double buffering, but in the call to Graphics::DrawImage(Image*, Gdiplus::Point) in my paint method. Changing to DrawImage(Image* image, INT x, INT y, INT width, INT height) fixed the scaling issue.

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.

in direct2d drawing dimensions change after window resizing

I am a beginner, and I have made a program in direct2d using c++ in visual studio 2015, just a very basic program that draws a white square on a black background , and it does show the square in a small window. But the problem is that if the user maximizes that window the vertical dimension of the drawing becomes elongated and the square becomes rectangle.
This is the screenshot of the window before maximizing it:
enter image description here
And this is the screenshot of the window after maximizing it:
enter image description here
What am I doing wrong ?
here is my code and thanks in advance:
ID2D1HwndRenderTarget* pRT = NULL;
ID2D1Factory* pD2DFactory = NULL;
ID2D1SolidColorBrush* pBlackBrush = NULL;
ID2D1SolidColorBrush* pRedBrush = NULL;
HRESULT hr;
RECT rc;
POINT pt;
void draw(){
pRT->BeginDraw();
pRT->FillRectangle( D2D1::RectF((FLOAT)rc.left,
(FLOAT)rc.top,(FLOAT)rc.right, (FLOAT)rc.bottom),pBlackBrush);
pRT->FillRectangle( D2D1::RectF((FLOAT)pt.x,
(FLOAT)pt.y,pt.x + 50.0f ,pt.y + 50.0f),pRedBrush);
hr = pRT->EndDraw();
}
int APIENTRY wWinMain(/* wWinMain parameters */){
/*
...
window making
...
*/
hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,&pD2DFactory );
// Obtain the size of the drawing area.
GetClientRect(hWnd, &rc);
// Create a Direct2D render target
hr =pD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(
hWnd,D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top)
),
&pRT
);
if (SUCCEEDED(hr)){
pRT->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::White),
&pRedBrush
);
pRT->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Black),
&pBlackBrush
);
}
pt.x = 0;
pt.y = 0;
while (1) {
PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE);
if (msg.message == WM_QUIT) break;
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
draw();
}
}
It seems like you don't resize the render target when your window resizes. I expect you run into similar problems not just from maximizing, but when you resize your window too? See https://learn.microsoft.com/en-us/windows/desktop/LearnWin32/dpi-and-device-independent-pixels#resizing-the-render-target.
Code snippet from the linked document:
void Resize()
{
if (pRenderTarget != NULL)
{
RECT rc;
GetClientRect(m_hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(rc.right, rc.bottom);
pRenderTarget->Resize(size);
InvalidateRect(m_hwnd, NULL, FALSE);
}
}
You'll want to call this by catching WM_SIZE.

Draw text on bitmap splash screen (MFC)

I am trying to draw text on a simple splash screen that displays a bitmap and nothing else. Below is my code. The bitmap displays properly but no text. What am I doing wrong?
I am required to add some more "details" before posting because this is mostly code: I am not sure where the author got this example. I can't find an example of drawing text on a bitmap that fits the way this is done, so need some help.
Thanks
Splash.cpp
#include "stdafx.h"
#include "Splash.h"
Splash::Splash()
{
bool b = false; //debugging
}
Splash::~Splash()
{
DestroyWindow(hSplashWnd);
}
void Splash::Init(HWND hWnd,HINSTANCE hInst,int resid)
{
hParentWindow=hWnd;
hSplashWnd=CreateWindowEx(WS_EX_CLIENTEDGE,"STATIC","",
WS_POPUP|WS_DLGFRAME|SS_BITMAP,300,300,300,300,hWnd,NULL,hInst,NULL);
SendMessage(hSplashWnd,STM_SETIMAGE,IMAGE_BITMAP,(LPARAM)LoadBitmap(hInst,MAKEINTRESOURCE(resid)));
this->SHOWING = FALSE;
}
void Splash::Show()
{
//get size of hSplashWnd (width and height)
int x, y;
int pwidth, pheight;
int tx, ty;
HDWP windefer;
RECT rect, prect;
GetClientRect(hSplashWnd,&rect);
x=rect.right;
y=rect.bottom;
//get parent screen coordinates
GetWindowRect(this->hParentWindow,&prect);
//center splash window on parent window
pwidth=prect.right-prect.left;
pheight=prect.bottom - prect.top;
int iScreenWidth, iScreenHeight, iSplashLeft, iSplashTop;
iScreenWidth = ::GetSystemMetrics(SM_CXSCREEN);
iScreenHeight = ::GetSystemMetrics(SM_CYSCREEN);
if(prect.left > iScreenWidth)
{
//On second monitor
iSplashLeft = iScreenWidth + (iScreenWidth / 2) - ((rect.right - rect.left) / 2);
}
else
{
//On first monitor
iSplashLeft = (iScreenWidth / 2) - ((rect.right - rect.left) / 2);
}
iSplashTop = (iScreenHeight / 2) - ((rect.bottom - rect.top) /2);
tx = iSplashLeft;
ty = iSplashTop;
windefer=BeginDeferWindowPos(1);
DeferWindowPos(windefer,hSplashWnd,HWND_NOTOPMOST,tx,ty,50,50,SWP_NOSIZE|SWP_SHOWWINDOW|SWP_NOZORDER);
EndDeferWindowPos(windefer);
BOOL isValidWindow = IsWindow(hSplashWnd);
//##################### Draw text on the bitmap ###############
CBrush brush;
brush.CreateSolidBrush(RGB(255,0,0));
HDC hdc = GetDC(hSplashWnd);
char *text = "HELLO HELLO HELLO HELLO HELLO HELLO";
SelectObject(hdc, brush);
SetTextColor(hdc, RGB(0,255,0));
DrawTextA(hdc, text, strlen(text), &rect, DT_SINGLELINE | DT_LEFT); //DT_CENTER | DT_VCENTER );
ShowWindow(hSplashWnd,SW_SHOWNORMAL);
UpdateWindow(hSplashWnd);
this->SHOWING = TRUE;
}
void Splash::Hide()
{
ShowWindow(hSplashWnd,SW_HIDE);
this->SHOWING = FALSE;
}
Splash.h
#if !defined(AFX_SPLASH_H_INCLUDED)
#define AFX_SPLASH_H_INCLUDED
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class Splash
{
public:
void Hide();
void Show();
void Init(HWND hWnd,HINSTANCE hInst,int resid);
BOOL SHOWING;
Splash();
virtual ~Splash();
private:
UINT TimerID;
HWND hParentWindow;
HWND hSplashWnd;
};
#endif
Draw the splash screen:
Splash Splash1;
Splash1.Init( m_pMainWnd->m_hWnd, this->m_hInstance, IDB_BITMAP_SPLASH );
Splash.Show();
Sleep(3000);
Splash1.Hide;
Move the call to UpdateWindow(hSplashWnd); to after your GetDC(..)
The WM_PAINT message gets your bitmap drawn, and then you want to draw your text after that.
HDC hdc = GetDC(hSplashWnd);
UpdateWindow(hSplashWnd);
wchar_t * text = L"HELLO";
//SelectObject(hdc, brush);
SetTextColor(hdc, RGB( 0, 255, 0 ) );
DrawText(hdc, text, 200, &rect, DT_SINGLELINE | DT_LEFT); //DT_CENTER | DT_VCENTER );
ShowWindow(hSplashWnd,SW_SHOWNORMAL);

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