30

Is it possible to load a PNG from a file into an HBITMAP using Win32 GDI functions? If not, what would be the lightest solution without using external libraries (like libpng)?

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
jnm2
  • 7,960
  • 5
  • 61
  • 99
  • 2
    I'm just trying to keep everything as small and fast as possible. I've had bad experiences with GDI+'s speed in the past. I need an HBITMAP and GDI+ doesn't load directly into an HBITMAP, thus requiring another copy. GDI+ is an option, just not my favorite. – jnm2 Dec 31 '10 at 14:21

5 Answers5

29

You can use the Windows Imaging Component to load PNG files (on Windows XP SP2 and later). See MSDN Magazine (original in web archive - a bit better formatting) for an introduction on how to use the API and my blog post for a code sample that loads a PNG from an IStream and converts it to an HBITMAP.

Alexander Malakhov
  • 3,383
  • 2
  • 33
  • 58
Bradley Grainger
  • 27,458
  • 4
  • 91
  • 108
  • 1
    Hi Bradley, It looks like the MSDN magazine link is down, could you update the link and maybe add some sample code into the answer from the article? Thanks! – jrh Mar 07 '17 at 21:49
  • 3
    @jrh Yes, it looks like Microsoft pulled that page down, but an archived copy is available: http://web.archive.org/web/20080507014245/http://msdn.microsoft.com/en-us/magazine/cc500647.aspx – Bradley Grainger Mar 08 '17 at 18:29
  • 1
    Note that Microsoft [converted the older issues to chm format](http://download.microsoft.com/download/3/A/7/3A7FA450-1F33-41F7-9E6D-3AA95B5A6AEA/MSDNMagazineApril2008en-us.chm) -- if you want I can suggest an edit that adds code into the answer. – jrh Mar 08 '17 at 19:34
10

There is no need to use Windows Imaging Component, GDI+ or PNG library. You can use Icon functionality.

  1. Add new icon (ICO_PNG) to VC project resources with custom Width and Height (Resource Editor->Image->New Image Type). Copy Your png image here and use Fill Tool+transparent color to make icon transparent.

  2. Add Picture Control (IDC_PNG) to Your dialog (Type = Owner draw).

  3. Dialog procedure code:

switch (msg)
{
    ...

    case WM_DRAWITEM:
    {
        LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
        if (pDIS->CtlID == IDC_PNG)
        {
            HICON hIcon = (HICON)LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(ICO_LOGO), IMAGE_ICON, 0, 0, LR_LOADTRANSPARENT); 
            DrawIconEx(pDIS->hDC, 0, 0, hIcon, 0, 0, 0, NULL, DI_NORMAL);
            DestroyIcon(hIcon);
            return TRUE;
        }
    }
}
vadim_hr
  • 533
  • 5
  • 11
  • Related: [How would I draw a PNG image using LoadImage and StretchDIBits?](http://stackoverflow.com/questions/4568057/how-would-i-draw-a-png-image-using-loadimage-and-stretchdibits). Note that this answer seems to only work if the png is a *resource*, I was not able to load a png straight from a standalone file (e.g., myimage.png). It seems that `LoadImage` only supports loading .ico icons from file. – jrh Mar 10 '17 at 20:06
  • 1
    You sure LoadImage can load PNG resource? I did not create IDC, just need to load resource, but looks like its not loading. – Sergei Krivonos Mar 20 '18 at 13:16
4

You can do it with StretchDIBits API, but limited by OS/driver availability.

Consult MSDN documentation for details:

http://msdn.microsoft.com/en-us/library/dd145121(v=VS.85).aspx

http://msdn.microsoft.com/en-us/library/dd145107(VS.85).aspx


I sincerely apologize for misleading you guys interested in this issue. Let me correct my mistake. No StretchDIBits for PNG drawing. You'd better try WIC method or consider way to integrate GDI+ in your projects.

9dan
  • 4,222
  • 2
  • 29
  • 44
  • +1... after banging my head on the wall numerous times before, I'm glad to finally see this answer! :) – user541686 Dec 31 '10 at 03:59
  • 3
    Ooch! I did some research on this. It's seems like there is no working code actually drawing PNG image to GDI device context. Many pointing out StretchDIBits PNG support totally useless. – 9dan Dec 31 '10 at 06:55
0

We can display png image via GDI, by the following two steps when creating your window(case WM_CREATE in window procedure function):

  1. load png file (via libpng or stb image), pixel values saved in a variable, say buffer
  2. create HBITMAP instance using buffer in CreateBitmap() function

Here's the runnable code, which is in pure C and main() as entry point function (libpng and zlib are from my own opencv compilation)

#include <stdio.h>
#include <windows.h>
#include "png.h"


#define CRTDBG_MAP_ALLOC 
#include <crtdbg.h>

// **NB**: You may use OpenCV prebuilt package's self contained libpng.lib file
// or, maybe, you can also compile it from source (which cost time and not necessary), see: `http://www.libpng.org` and  `https://www.zlib.net`

#define LIBPNG_PTH "D:/opencv_249/build/x64/vc12/staticlib/libpng.lib"
#define ZLIB_PTH "D:/opencv_249/build/x64/vc12/staticlib/zlib.lib"

#pragma comment(lib, LIBPNG_PTH)
#pragma comment(lib, ZLIB_PTH)

typedef struct MyRect {
    int x, y, width, height;
} MyRect;

char bitmap_im_pth[100];

typedef struct MyWindow {
    HDC dc;
    //HGDIOBJ image;
    HBITMAP hBmp;
    unsigned char* imdata;
} MyWindow;

MyWindow* my_window;
enum ImageType {BMP, PNG};

long ReadPngData(const char *szPath, int *pnWidth, int *pnHeight, unsigned char **cbData)
{
    FILE *fp = NULL;
    long file_size = 0, pos = 0, mPos = 0;
    int color_type = 0, x = 0, y = 0, block_size = 0;

    png_infop info_ptr;
    png_structp png_ptr;
    png_bytep *row_point = NULL;

    fp = fopen(szPath, "rb");
    if (!fp)    return -1;

    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
    info_ptr = png_create_info_struct(png_ptr);
    png_init_io(png_ptr, fp);
    png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0);

    *pnWidth = png_get_image_width(png_ptr, info_ptr);
    *pnHeight = png_get_image_height(png_ptr, info_ptr);
    color_type = png_get_color_type(png_ptr, info_ptr);
    file_size = (*pnWidth) * (*pnHeight) * 4;
    *cbData = (unsigned char *)malloc(file_size);
    row_point = png_get_rows(png_ptr, info_ptr);

    block_size = color_type == 6 ? 4 : 3;

    for (x = 0; x < *pnHeight; x++)
        for (y = 0; y < *pnWidth*block_size; y += block_size)
        {
            (*cbData)[pos++] = row_point[x][y + 2];        //B
            (*cbData)[pos++] = row_point[x][y + 1];        //G
            (*cbData)[pos++] = row_point[x][y + 0];        //R
            (*cbData)[pos++] = row_point[x][y + 3];        //alpha
        }

    png_destroy_read_struct(&png_ptr, &info_ptr, 0);
    fclose(fp);

    return file_size;
}


LRESULT __stdcall WindowProcedure(HWND window, unsigned int msg, WPARAM wp, LPARAM lp)
{
    int im_width, im_height;

    int image_type = PNG;
    switch (msg)
    {
    case WM_CREATE:
        if (image_type == BMP) {
            my_window->hBmp = (HBITMAP)LoadImage(NULL, "lena512.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
        }
        else if (image_type == PNG) {
            ReadPngData("Lena.png", &im_width, &im_height, &my_window->imdata);
            my_window->hBmp = CreateBitmap(im_width, im_height, 32, 1, my_window->imdata);
        }
        if (my_window->hBmp == NULL)
            MessageBox(window, "Could not load image!", "Error", MB_OK | MB_ICONEXCLAMATION);
        break;

    case WM_PAINT:
    {
        BITMAP bm;
        PAINTSTRUCT ps;

        HDC hdc = BeginPaint(window, &ps);
        SetStretchBltMode(hdc, COLORONCOLOR);

        my_window->dc = CreateCompatibleDC(hdc);
        HBITMAP hbmOld = SelectObject(my_window->dc, my_window->hBmp);

        GetObject(my_window->hBmp, sizeof(bm), &bm);

#if 1
        BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, my_window->dc, 0, 0, SRCCOPY);
#else
        RECT rcClient;
        GetClientRect(window, &rcClient);
        int nWidth = rcClient.right - rcClient.left;
        int nHeight = rcClient.bottom - rcClient.top;
        StretchBlt(hdc, 0, 0, nWidth, nHeight, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
#endif

        SelectObject(my_window->dc, hbmOld);
        DeleteDC(my_window->dc);

        EndPaint(window, &ps);
    }
    break;

    case WM_DESTROY:
        printf("\ndestroying window\n");
        PostQuitMessage(0);
        return 0L;

    case WM_LBUTTONDOWN:
        printf("\nmouse left button down at (%d, %d)\n", LOWORD(lp), HIWORD(lp));

        // fall thru
    default:
        //printf(".");
        return DefWindowProc(window, msg, wp, lp);
    }
}

const char* szWindowClass = "myclass";


ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    /* Win 3.x */
    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = WindowProcedure;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = GetModuleHandle(0);
    wc.hIcon = LoadIcon(0, IDI_APPLICATION);
    wc.hCursor = LoadCursor(0, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = 0;
    wc.lpszClassName = szWindowClass;
    /* Win 4.0 */
    wc.hIconSm = LoadIcon(0, IDI_APPLICATION);

    return RegisterClassEx(&wc);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    MyRect rect;
    rect.x = 300;
    rect.y = 300;
    rect.width = 640;
    rect.height = 480;

    DWORD defStyle = WS_VISIBLE | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU;

    HWND hwnd = CreateWindowEx(0, szWindowClass, "title",
        defStyle, rect.x, rect.y,
        rect.width, rect.height, 0, 0, hInstance, 0);

    if (!hwnd)
    {
        return FALSE;
    }
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    return TRUE;
}

void create_my_window(MyWindow** _my_window) {
    MyWindow* my_window = (MyWindow*)malloc(sizeof(MyWindow));
    my_window->dc = NULL;
    my_window->imdata = NULL;
    my_window->hBmp = NULL;

    *_my_window = my_window; // write back
}

void destroy_my_window(MyWindow* my_window) {
    if (my_window) {
        if (my_window->imdata) free(my_window->imdata);
        free(my_window);
    }
}

int main()
{
    printf("hello world!\n");

    HINSTANCE hInstance = GetModuleHandle(0);
    int nCmdShow = SW_SHOWDEFAULT;

    MyRegisterClass(hInstance);
    create_my_window(&my_window);

    if (!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }

    MSG msg;
    while (GetMessage(&msg, 0, 0, 0)) {
        DispatchMessage(&msg);
    }

    destroy_my_window(my_window);


    return 0;

}

Reference: https://www.cnblogs.com/mr-wid/archive/2013/04/22/3034840.html

ChrisZZ
  • 1,521
  • 2
  • 17
  • 24
  • Please don't use hardcoded paths. They won't work on other's PCs. Not all of us have "d:" ... #pragma comment(lib, "D:/opencv345/mybuild/libpng.lib") - I edited your answer accordingly – Michael Haephrati Aug 25 '19 at 20:25
  • @MichaelHaephrati Sorry, I'm not very agree with your opinion. I paste the specific path of libpng.lib as something like "F:\zhangzhuo\lib\opencv_249\build\x64\vc12\staticlib\libpng.lib", is to tell people that they can use the opencv 249 prebuild package self contained library. People would easily get that version of prebuilt opencv, thus they don't have to compile the zlib and libpng manually, which is time-consuming. People of course should modify that path since people may have different path. – ChrisZZ Aug 26 '19 at 03:09
  • 2
    Ok. I understand – Michael Haephrati Aug 26 '19 at 07:43
  • 1
    The question is asking how to load a PNG image using *only* services provided by the OS (Win32/GDI). Suggesting to use some random 3rd party library does not answer that question. The question even explicitly says *"without using external libraries (**like libpng**)"*. This isn't useful. – IInspectable Apr 21 '22 at 16:26
-3

The answer by vladimir_hr is simplicity itself.

Simple steps to follow.

In the resources header file declare like: #define IDI_PNG 1000

In the resource file *.rc have: IDI_PNG ICON "protractor.ico"

The icon file. Convert your (transparent) png file into an icon file by using an icon editor that support custom size instead of the standard Windows' icon sizes, save this png image as an icon image.

The rest is just blitting between DC's.

khalfan
  • 23
  • 6
  • 1
    If you are referring to another answer, please write a comment to that aswer. – EFrank Dec 10 '20 at 11:15
  • 1
    The question is asking how to load a PNG image. Recommending to use a different image format does not provide an answer to that question. – IInspectable Apr 21 '22 at 16:28