I was looking for a way to show PNG images in an MFC Picture control, while showing their transparency, and ended up writing my own custom control.
Github repo: https://github.com/securedglobe/SG_PNG
Introduction
While working on an ongoing project for Sony, we needed to develop a modern and good looking application. In these cases you would normally get some graphics designed and integrated. I mostly use PNG images since they support transparent background. See my answer in Stackoverflow.
PNG images allow elements to blend seamlessly with the background which creates a more polished and professional appearance for UI.
However, displaying these PNG images with transparency in MFC, can be challenging. An MFC button with PNG images was introduced in this article but I couldn't find a Picture or Static control that is capable of displaying a PNG whilst keeping its transparency.
For that reason I developed SG_PNGView, which is a custom control which offer a solution to this problem by enabling developers to easily display PNG images with transparency on MFC dialogs.
Let's begin with some basic concepts...
GDI+
GDI+ (Graphics Device Interface Plus) is a powerful graphics library provided by Microsoft for rendering 2D graphics. It offers a comprehensive set of functions and classes for creating, manipulating, and displaying graphical images and text in Windows applications. GDI+ provides a high-level abstraction over the underlying graphics hardware and operating system, enabling developers to create visually appealing and interactive user interfaces with ease.
GDI+ is ideal for working with graphics objects such as pens, brushes, fonts, images, and paths via the corresponding classes allowing developers to perform various operations such as drawing lines, shapes, and text, filling regions with colors or gradients, transforming graphics objects, and working with images in different formats.
SG_PNGView uses GDI+ to load and display PNG images within the control. The Bitmap class from GDI+ is utilized to represent the PNG image, and its methods are used to perform operations such as loading the image from a resource, drawing the image onto the device context during painting, and handling errors during the image loading process. GDI+ is also being used for functionalities for manipulating and rendering images efficiently, ensuring smooth and high-quality graphics output in the SG_PNGView control. During the development, I defined the Static controls with Sizing type set to Vertical, so see how images are stretched in real time.
Overall, GDI+ serves as a versatile and essential tool for developers to create rich and visually appealing graphical user interfaces in Windows applications, offering a wide range of functionalities for working with 2D graphics with ease and efficiency.
Where the magic happens
SetPNGImage()
First, you call SetPNGImage()
which associates a PNG resource and a Static control.
When called, the function loads PNG image from the specified resource ID and initializes the control with the loaded image.
Status SG_PNGView::SetPNGImage(UINT nIDResource)
{
HINSTANCE hInstance = AfxGetInstanceHandle();
HRSRC hResource = ::FindResource(hInstance, MAKEINTRESOURCE(nIDResource), _T("PNG"));
if (!hResource)
{
return GenericError; }
DWORD imageSize = ::SizeofResource(hInstance, hResource);
const void* pResourceData = ::LockResource(::LoadResource(hInstance, hResource));
if (!pResourceData)
{
return OutOfMemory; }
HGLOBAL hBuffer = ::GlobalAlloc(GMEM_MOVEABLE, imageSize);
if (!hBuffer)
{
return OutOfMemory; }
void* pBuffer = ::GlobalLock(hBuffer);
if (!pBuffer)
{
::GlobalFree(hBuffer); return OutOfMemory;
}
CopyMemory(pBuffer, pResourceData, imageSize);
IStream* pStream = NULL;
if (::CreateStreamOnHGlobal(hBuffer, FALSE, &pStream) != S_OK)
{
::GlobalUnlock(hBuffer);
::GlobalFree(hBuffer);
return GenericError; }
delete m_pBitmap;
m_pBitmap = Bitmap::FromStream(pStream);
pStream->Release();
::GlobalUnlock(hBuffer);
::GlobalFree(hBuffer);
if (m_pBitmap == NULL)
{
return OutOfMemory; }
Status status = m_pBitmap->GetLastStatus();
if (status != Ok)
{
delete m_pBitmap;
m_pBitmap = NULL;
}
return status;
}
The function begins by obtaining the instance handle of the application and locating the specified PNG resource within the application's executable file.
If the resource is not found, the function returns a GenericError
status.
Next, the function retrieves the size of the resource and allocates global memory to hold the resource data. If memory allocation fails, the function returns an OutOfMemory
status.
Subsequently, the function creates an IStream
object from the allocated memory and uses it to create a GDI+ Bitmap object representing the PNG image.
If the bitmap creation fails, the function returns an OutOfMemory
status.
Finally, the function checks the status of the bitmap creation process, and if any errors occur, it cleans up resources and returns the corresponding status.
Otherwise, it returns the status indicating the success of the operation. Overall, the SetPNGImage
function provides a comprehensive mechanism for loading PNG images into the SG_PNGView control, ensuring robust error handling and reliable initialization.
Handling the WM_PAINT event
When using the SG_PNGView control, any image loaded using SetPNGImage()
is drawn as part of the class's internal OnPaint()
event handler.
It is invoked whenever the control needs to be redrawn on the screen.
void SG_PNGView::OnPaint()
{
CPaintDC dc(this); CRect rect;
GetClientRect(&rect);
if (m_pBitmap != nullptr)
{
Graphics graphics(dc.GetSafeHdc());
int imageWidth = m_pBitmap->GetWidth();
int imageHeight = m_pBitmap->GetHeight();
float scaleX = static_cast<float>(rect.Width()) / imageWidth;
float scaleY = static_cast<float>(rect.Height()) / imageHeight;
float scale = min(scaleX, scaleY);
int scaledWidth = static_cast<int>(imageWidth * scale);
int scaledHeight = static_cast<int>(imageHeight * scale);
int xPos = (rect.Width() - scaledWidth) / 2;
int yPos = (rect.Height() - scaledHeight) / 2;
graphics.DrawImage(m_pBitmap, xPos, yPos, scaledWidth, scaledHeight);
}
}
Within this function, the control obtains a device context (CPaintDC) for painting and retrieves the client area's dimensions using GetClientRect()
.
If a valid PNG image has been loaded into the control, it utilizes the GDI+ library to draw the image onto the device context. The drawing process involves calculating the appropriate scaling factors to ensure the image fits proportionally within the control's boundaries while preserving its aspect ratio.
The scaled image is then drawn onto the device context using the Graphics::DrawImage()
method, ensuring that it is centered within the control. This function is essential for ensuring that the PNG image is displayed correctly within the SG_PNGView control, adapting dynamically to changes in control size and ensuring a visually appealing presentation of the image to the user.
Using the code in your own program
The idea of my class is to be hassle free when being used. That is achieved by requiring minimal steps from the user:
- Add the SG_PNGView.cpp and SG_PNGView.h files to your project.
- Include the SG_PNGView.h header file in the source file where you want to use the control.
#include "SG_PNGView.h"
- Add a static control to your dialog resource and set its class to
SG_PNGView
.
SG_PNGView m_pngView;
m_pngView.Create(NULL, _T("SG_PNGView Control"), WS_CHILD | WS_VISIBLE, CRect(10, 10, 200, 200), this);
- Display PNG images using the
SetPNGImage
method of the SG_PNGView control. The images will be shown stretched over the boundaries of the static control, so if you resize the control (even during runtime), the image will be stretched accordingly.
m_pngView.SetPNGImage(IDB_PNG_IMAGE);
Points of Interest
- GDI+ Library Documentation: Learn more about the GDI+ library and its features for working with images and graphics.
- (University of Birmingham) MFC Programming Guide: Explore additional MFC programming techniques and best practices for developing Windows applications.