Click here to Skip to main content
16,004,564 members
Articles / Desktop Programming / MFC

The SG_PNG Custom Control for MFC

Rate me:
Please Sign up or sign in to vote.
5.00/5 (9 votes)
2 Jun 2024CPOL5 min read 9.4K   201   8   17
SG_PNG is a custom MFC control for displaying PNG resources with transparency
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

How SG_PNGView looks like

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.

C++
// Loads a PNG image from the specified resource ID
Status SG_PNGView::SetPNGImage(UINT nIDResource)
{
    // Get the instance handle of the application
    HINSTANCE hInstance = AfxGetInstanceHandle();

    // Find the specified resource in the application's executable file
    HRSRC hResource = ::FindResource(hInstance, MAKEINTRESOURCE(nIDResource), _T("PNG"));
    if (!hResource)
    {
        return GenericError; // Resource not found
    }

    // Get the size of the resource
    DWORD imageSize = ::SizeofResource(hInstance, hResource);

    // Get a pointer to the resource data
    const void* pResourceData = ::LockResource(::LoadResource(hInstance, hResource));
    if (!pResourceData)
    {
        return OutOfMemory; // Failed to lock resource
    }

    // Allocate global memory to hold the resource data
    HGLOBAL hBuffer = ::GlobalAlloc(GMEM_MOVEABLE, imageSize);
    if (!hBuffer)
    {
        return OutOfMemory; // Memory allocation failed
    }

    // Lock the allocated memory and copy the resource data into it
    void* pBuffer = ::GlobalLock(hBuffer);
    if (!pBuffer)
    {
        ::GlobalFree(hBuffer); // Failed to lock memory, free the buffer
        return OutOfMemory;
    }
    CopyMemory(pBuffer, pResourceData, imageSize);

    // Create an IStream object from the allocated memory
    IStream* pStream = NULL;
    if (::CreateStreamOnHGlobal(hBuffer, FALSE, &pStream) != S_OK)
    {
        ::GlobalUnlock(hBuffer);
        ::GlobalFree(hBuffer);
        return GenericError; // Failed to create stream
    }

    // Delete the previous bitmap if it exists
    delete m_pBitmap;

    // Create a GDI+ Bitmap object from the stream
    m_pBitmap = Bitmap::FromStream(pStream);

    // Release the IStream object
    pStream->Release();

    // Unlock and free the allocated memory
    ::GlobalUnlock(hBuffer);
    ::GlobalFree(hBuffer);

    // Check if the bitmap was created successfully
    if (m_pBitmap == NULL)
    {
        return OutOfMemory; // Failed to create bitmap
    }

    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.

C++
void SG_PNGView::OnPaint()
{
    CPaintDC dc(this); // device context for painting
    CRect rect;
    GetClientRect(&rect);

    if (m_pBitmap != nullptr)
    {
        Graphics graphics(dc.GetSafeHdc());

        // Get the dimensions of the image
        int imageWidth = m_pBitmap->GetWidth();
        int imageHeight = m_pBitmap->GetHeight();

        // Calculate the scaling factors to fit the image inside the control
        float scaleX = static_cast<float>(rect.Width()) / imageWidth;
        float scaleY = static_cast<float>(rect.Height()) / imageHeight;
        float scale = min(scaleX, scaleY); // Use the minimum scaling factor to preserve aspect ratio

        // Calculate the dimensions of the scaled image
        int scaledWidth = static_cast<int>(imageWidth * scale);
        int scaledHeight = static_cast<int>(imageHeight * scale);

        // Calculate the position to center the scaled image within the control
        int xPos = (rect.Width() - scaledWidth) / 2;
        int yPos = (rect.Height() - scaledHeight) / 2;

        // Draw the scaled image
        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:

  1. Add the SG_PNGView.cpp and SG_PNGView.h files to your project.
  2. Include the SG_PNGView.h header file in the source file where you want to use the control.
    C++
    #include "SG_PNGView.h"
  3. Add a static control to your dialog resource and set its class to SG_PNGView.
    C++
    SG_PNGView m_pngView;
    m_pngView.Create(NULL, _T("SG_PNGView Control"), WS_CHILD | WS_VISIBLE, CRect(10, 10, 200, 200), this);
  4. 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.
    C++
    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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO Secured Globe, Inc.
United States United States
Michael Haephrati is a music composer, an inventor and an expert specializes in software development and information security, who has built a unique perspective which combines technology and the end user experience. He is the author of a the book Learning C++ , which teaches C++ 20, and was published in August 2022.

He is the CEO of Secured Globe, Inc., and also active at Stack Overflow.

Read our Corporate blog or read my Personal blog.





Comments and Discussions

 
Questionwindow resize with buttons Pin
merano995-Jun-24 8:18
mvemerano995-Jun-24 8:18 
AnswerRe: window resize with buttons Pin
Michael Haephrati6-Jun-24 0:58
professionalMichael Haephrati6-Jun-24 0:58 
AnswerRe: window resize with buttons Pin
Michael Haephrati6-Jun-24 1:05
professionalMichael Haephrati6-Jun-24 1:05 
GeneralMFC Pin
dandy723-Jun-24 10:36
dandy723-Jun-24 10:36 
GeneralRe: MFC Pin
Michael Haephrati4-Jun-24 6:21
professionalMichael Haephrati4-Jun-24 6:21 
GeneralRe: MFC Pin
dandy724-Jun-24 9:07
dandy724-Jun-24 9:07 
GeneralRe: MFC Pin
Member 109675914-Jun-24 6:27
Member 109675914-Jun-24 6:27 
GeneralRe: MFC Pin
dandy7214-Jun-24 10:13
dandy7214-Jun-24 10:13 
GeneralRe: MFC Pin
Member 109675918-Jun-24 8:32
Member 109675918-Jun-24 8:32 
GeneralRe: MFC Pin
dandy7219-Jun-24 3:44
dandy7219-Jun-24 3:44 
GeneralRe: MFC Pin
Michael Haephrati20-Jun-24 23:09
professionalMichael Haephrati20-Jun-24 23:09 
GeneralRe: MFC Pin
dandy7221-Jun-24 3:14
dandy7221-Jun-24 3:14 
GeneralRe: MFC Pin
Michael Haephrati23-Jun-24 10:07
professionalMichael Haephrati23-Jun-24 10:07 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA2-Jun-24 12:38
professionalȘtefan-Mihai MOGA2-Jun-24 12:38 
This is a great inspiring article. I am pretty much pleased with your good work. You put really very helpful information. Keep it up once again.
GeneralRe: My vote of 5 Pin
Michael Haephrati3-Jun-24 0:00
professionalMichael Haephrati3-Jun-24 0:00 
GeneralRe: My vote of 5 Pin
Member 109675914-Jun-24 6:28
Member 109675914-Jun-24 6:28 
GeneralRe: My vote of 5 Pin
Michael Haephrati15-Jun-24 7:36
professionalMichael Haephrati15-Jun-24 7:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.