Pages

Showing posts with label ATL. Show all posts
Showing posts with label ATL. Show all posts

Sunday, November 8, 2009

ATL, GDI+. PNG,...

After a half of a day of a dramatic struggle with CImage, I failed to make it working with the png images - the images that contain the alpha chanel. I found the solution, but with GDI+ and I will show it later.

Firstly, I have to say, that the CImage converts any image into 32 bit per pixel format, but it is just RGB (pre-muliplay alpha?). When CImage loads an image it copies the image data into an internal buffer. You can see everything yourself, if you will debug CImage methods such as Load from the stream, for example.

Secondly, I just included atlimage.h file header into my source code and got a bunch of dlls from which my application depends now. Including the dynamic run-time libraries, and GDI+, and something from the Windows Installer, and...

I very like ATL, I just love the style, but, probably, I will try to avoid using CImage in my projects.
For a fast and dirty job that does not require to load any data with alpha..., maybe, it is possible. CImage even has AlphaBlend function - it's just a wrapper for the Win API function. From my point view, it is just one more evidence of a bad design made for this class - from one side the class uses GDI+, from other side it uses GDI function to draw the image.

The original task was to load the PNG-file from the resources in an ATL-based application. I thought I solved it. It looked great - I made a template - CImageResource, that contain only one function -  LoadFromResource. The objects I created in my application were like

CImageResource<CImage> m_Image;

I added a window background image to the resources and saw it's drawn in the application window. AlphaBlend method was used and everything was just fine.

The problems begun when I added a button image with the real visible alpha.

So finally the class CImageResource is not a template :). It looks so:
#pragma once

class CImageResource
{
Bitmap* m_pBitmap;
HGLOBAL m_hBlock;

public:
CImageResource() : m_pBitmap(NULL), m_hBlock(NULL) {}
~CImageResource() { Clear(); }

inline BOOL IsNull() const { return m_pBitmap == NULL; }

UINT GetWidth()
{
if (IsNull())
return 0;
return m_pBitmap->GetWidth();
}

UINT GetHeight()
{
if (IsNull())
return 0;
return m_pBitmap->GetHeight();
}

BOOL Draw(HDC hDC, int x, int y)
{
if (IsNull())
return FALSE;
Graphics graphics(hDC);
return graphics.DrawImage(m_pBitmap, x, y,
m_pBitmap->GetWidth(), m_pBitmap->GetHeight()) == Ok;
}

BOOL Draw(HDC hDC, RECT& rect)
{
if (IsNull())
return FALSE;
Graphics graphics(hDC);
return graphics.DrawImage(m_pBitmap, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top) == Ok;
}

BOOL Load(LPCWSTR lpszFile)
{
Clear();
m_pBitmap = Bitmap::FromFile(lpszFile);
return m_pBitmap->GetLastStatus() == Ok;
}

BOOL LoadFromResource(UINT nResID)
{
Clear();
HMODULE hModule = GetModuleHandle(NULL);
HRSRC hResource = FindResource(hModule,
MAKEINTRESOURCE(nResID), L"IMAGES");
if (hResource == NULL)
return FALSE;

HGLOBAL hImage = LoadResource(hModule, hResource);
if (hImage == NULL)
return FALSE;
LPVOID pImage = LockResource(hImage);
if (pImage == NULL)
return FALSE;

HRESULT hr = E_FAIL;
int size = SizeofResource(hModule, hResource);
m_hBlock = GlobalAlloc(GMEM_MOVEABLE, size);
if (m_hBlock == NULL)
return FALSE;

LPVOID pBlock = GlobalLock(m_hBlock);
if (pBlock != NULL)
{
memmove(pBlock, pImage, size);
IStream* pStream = NULL;
if (CreateStreamOnHGlobal(m_hBlock, FALSE, &pStream) == S_OK)
{
m_pBitmap = Bitmap::FromStream(pStream);
pStream->Release();
if (m_pBitmap != NULL)
{
if (m_pBitmap->GetLastStatus() == Ok)
return TRUE;
}
delete m_pBitmap;
m_pBitmap = NULL;
}
GlobalUnlock(m_hBlock);
}
GlobalFree(m_hBlock);
m_hBlock = NULL;
return FALSE;
}

void Clear()
{
delete m_pBitmap;
m_pBitmap = NULL;
if (m_hBlock != NULL)
{
GlobalUnlock(m_hBlock);
GlobalFree(m_hBlock);
}
}
};

Now all images are loaded and drawn with the alpha. For drawing I use the GDI+ as well.
Here are few helpful links from CodeProject about this topic:
Joe Woodbury. Loading JPG & PNG resources using GDI+: http://www.codeproject.com/KB/GDI-plus/cgdiplusbitmap.aspx
Christian Graus. Starting with GDI+: http://www.codeproject.com/KB/GDI-plus/startinggdiplus.aspx
 Darren Sessions. A user draw button that supports PNG files with transparency, for Visual C++ 6.0 and VS2005: http://www.codeproject.com/KB/buttons/GdipButton.aspx

Sunday, November 1, 2009

ATL Window

From my point of view, MFC is just a wrapper for Windows API. I think, it is a heavy and slow wrapper. I cannot say that if I need to test something simple, I always create an MFC dialog-based application as it used to be few years ago. The Visual Studio wizards create a lot of code, many files, add stdafx.h that connects my code with a lot of headers and libraries, and I still need to edit something in the resources. So, whenever possible, I use just the following simple shablon:


#define WIN32_LEAN_AND_MEAN

#include <windows.h>


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
return 0;
}
It is not the Win32 console application, I can test many new for me things from the Platform SDK and do not see the annoying console window.


I have shown how to add a window to this code - Самая простая Win32 программа-шаблон для тестов. - it is more C-style, than C++. I'd say the ATL can bring a very modern C++ style into this simple test application.


For the beginning let's modify the first example from this article and add the ATL support:


#define WIN32_LEAN_AND_MEAN
#include <atlbase.h>

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
return 0;
}
It's compiled successfully and works, but does nothing.
Let's add an ATL Window:
#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <atlbase.h>
#include <atlwin.h>

typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> CMyWindowTraits;

class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>
{
public:
DECLARE_WND_CLASS(L"My Window")

BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
END_MSG_MAP()

LRESULT OnClose(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DestroyWindow();
return 0;
}

LRESULT OnDestroy(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(0);
return 0;
}
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
CMyWindow wnd;
MSG msg;

HWND hWnd = wnd.Create(NULL, CWindow::rcDefault, L"ATL Window");
if (hWnd == NULL)
return 0;

wnd.ShowWindow(nShowCmd);
wnd.UpdateWindow();
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
This time the application window is an object of CMyWindow class. WinMain function has common things: creates the window, show it, launch the message loop.
CMyWindow class is simple too. It contains:
  1. Window class definition (DECLARE_WND_CLASS).
  2. Message map. (BEGIN_MSG_MAP.. END_MSG_MAP with two message handlers for WM_CLOSE and WM_DESTROY)
  3. Window styles (CMyWindowTraits).

That'all:


Why I like this style?

Mainly, because I see and control each message my window receives. Nothing is hidden from me, I have to write with my hands everything I want to happen in my application. This style allows me to learn more about Win32, understand better the Windows.

Let's add one more message handler into the CMyWindow class - for example, let's handle WM_ERASEBKGND. We need to update the message map:
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
and add the method that will be called on the message arriving:

LRESULT OnEraseBkgnd(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
return 1;
}

We got a transparent window:


The window is transparent, because the background eraser is empty, but return 1 - meaning we have painted the background.

Why I like ATL?

Because I can use the templates and real Object-Oriented programming for my window classes. For example, ATL allows me to split the message map in the CMyWindow class between many classes and do not have a long and heavy window class. Let's move the backround painter to a seperate class like the following:

template <class T, COLORREF color>
class CBackground
{
HBRUSH m_hBrush;

public:
CBackground()
{
m_hBrush = CreateSolidBrush(color);
}

~CBackground()
{
DeleteObject(m_hBrush);
}

BEGIN_MSG_MAP(CBackground)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
END_MSG_MAP()

LRESULT OnEraseBkgnd(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
T* pT = static_cast<T*>(this);
HDC hDC = (HDC)wParam;
RECT rect = { 0 };
pT->GetClientRect(&rect);
FillRect(hDC, &rect, m_hBrush);
return 1;
}
};

This time I created a brush with the predefined color in the class contstructor and delete it in the destructor. Please pay attention that we do not need to check any pointer in OnEraseBkgnd method - no one of them can be NULL.

We need to modify the CMyWindow:
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>,
public CBackground<CMyWindow, RGB(128, 128, 128)>
{
public:

typedef CBackground<CMyWindow, RGB(128, 128, 128)> CBackgroundEraser;
DECLARE_WND_CLASS(L"My Window")

BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
CHAIN_MSG_MAP(CBackgroundEraser)
END_MSG_MAP()

LRESULT OnClose(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DestroyWindow();
return 0;
}

LRESULT OnDestroy(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(0);
return 0;
}
};
CBackground class was added into the inheritance list. and CHAIN_MSG_MAP was added into the map (I used typedef to use a simple name in the message map macro).
And here is the application:



Now we have ATL included and so can use other cool stuff. Let's put an image onto the background of our window:
#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <atlbase.h>
#include <atlwin.h>
#include <atlimage.h>

template <class T, WCHAR* lpszFileName>
class CImageBackground
{
CImage m_Image;

public:
CImageBackground()
{
m_Image.Load(lpszFileName);
}

BEGIN_MSG_MAP(CBackground)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
END_MSG_MAP()

LRESULT OnEraseBkgnd(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
T* pT = static_cast<T*>(this);
HDC hDC = (HDC)wParam;
RECT rect = { 0 };
pT->GetClientRect(&rect);
m_Image.AlphaBlend(hDC, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
0, 0, m_Image.GetWidth(), m_Image.GetHeight());
return 1;
}
};

typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> CMyWindowTraits;

WCHAR s_szFile[] = L"image.jpg";

class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>,
public CImageBackground<CMyWindow, s_szFile>
{
public:

typedef CImageBackground<CMyWindow, s_szFile> CBackgroundEraser;
DECLARE_WND_CLASS(L"My Window")

BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
CHAIN_MSG_MAP(CBackgroundEraser)
END_MSG_MAP()

LRESULT OnClose(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DestroyWindow();
return 0;
}

LRESULT OnDestroy(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(0);
return 0;
}
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
CMyWindow wnd;
MSG msg;

HWND hWnd = wnd.Create(NULL, CWindow::rcDefault, L"ATL Window");
if (hWnd == NULL)
return 0;

wnd.ShowWindow(nShowCmd);
wnd.UpdateWindow();
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}


This image has the alpha and it is drawn with the AlphaBlend.