基于GDI显示png图像

Posted zjutzz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于GDI显示png图像相关的知识,希望对你有一定的参考价值。

intro

先前基于GDI已经能够显示BITMAP图像:windows下控制台程序实现窗口显示 ,其中BMP图像是使用LoadImage()这一Win32 API函数来做的。考虑到LoadImage()函数并不能读取png图像,因此需要libpng或stb等png编解码库的帮助。

网上找到相关代码不多,稍加修改可以运行,具备特点:

  • 纯C,单个文件(依赖的libpng和zlib可以忽略)

  • 直接读取png图像而不是通过读取.rc文件(资源文件)再读取png图像

  • png图像的读取:基于libpng(以及zlib),我直接用的opencv345 windows预编译报里的.h文件和库文件

  • 入口函数为main()而非WinMain(),也即控制台程序,方便作为库函数、移植

  • 不同于BMP的地方:在窗口处理函数的创建阶段有所不同:
    C case WM_CREATE: if (image_type == BMP) { my_window->hBmp = (HBITMAP)LoadImage(NULL, "D:/work/libfc/imgs/lena512.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); } else if (image_type == PNG) { ReadPngData("D:/work/libfc/imgs/Lena.png", &im_width, &im_height, &my_window->imdata); my_window->hBmp = CreateBitmap(im_width, im_height, 32, 1, my_window->imdata); }

  • 缺点:窗口client区域大小固定,没有能根据图像尺寸来变化

代码

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


#define CRTDBG_MAP_ALLOC 
#include <crtdbg.h>


#pragma comment(lib, "D:/work/libfc/lib/libpng.lib")
#pragma comment(lib, "D:/work/libfc/lib/zlib.lib")

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

const char* project_root = "D:/work/libfc";
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;            //文件打开错误则返回 FILE_ERROR

    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);        //创建png读取结构
    info_ptr = png_create_info_struct(png_ptr);        //png 文件信息结构
    png_init_io(png_ptr, fp);                //初始化文件 I\\O
    png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0);                //读取png文件

    *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;                    //计算需要存储RGB(A)数据所需的内存大小
    *cbData = (unsigned char *)malloc(file_size);            //申请所需的内容, 并将 *cbData 指向申请的这块内容

    row_point = png_get_rows(png_ptr, info_ptr);            //读取RGB(A)数据

    block_size = color_type == 6 ? 4 : 3;                    //根据是否具有a通道判断每次所要读取的数据大小, 具有Alpha通道的每次读4位, 否则读3位

    //将读取到的RGB(A)数据按规定格式读到申请的内存中
    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, "D:/work/libfc/imgs/lena512.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
        }
        else if (image_type == PNG) {
            ReadPngData("D:/work/libfc/imgs/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;
    
}

参考

使用 Libpng 配合 GDI 完成对 png 图片的解析与显示 , 讲解细致

How would I load a PNG image using Win32/GDI (no GDI+ if possible)? (没用)

使用libpng和GDI读取显示png图片 (代码无法运行)

以上是关于基于GDI显示png图像的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Win32/GDI 加载 PNG 图像(如果可能,不要使用 GDI+)?

使用 gdiplus 打印 png24 图像时 GDI 内存泄漏

C++ gdi::Bitmap 到内存中的 PNG 图像

C++ gdi::Bitmap 到内存中的 PNG 图像

在运行时更新 PNG 图像会引发 GDI+ 溢出异常

GDI+、PNG 图像到 UNC 中发生一般错误