基于GDI的imshow:使用stb_image读取图像并修正绘制

基于GDI的imshow:使用stb_image读取图像并修正绘制

文字描述

OpenCV的影响可以说是巨大的,无论是传统算法还是DL网络,开源代码大都难以摆脱OpenCV图像格式的魔爪。实际上真正上设备的代码并不需要imread和imshow的功能,读取图像也有相应的编解码方案;另一方面,OpenCV的模块很多,版本也很多,配置起来神烦。我只是想imshow一下,为啥要依赖你OpenCV呢?我想写纯C,为啥你OpenCV现在也傲娇的不提供C接口一定要CPP呢?

前面已经做到:windows下用纯C实现一个简陋的imshow:基于GDI。不过它存在一些问题:

  • 使用的是libpng的简单封装读取的图像,读取到的data并不是BGRBGRBGR的OpenCV风格的数据排布顺序

  • 使用libpng需要自行编译,如果要处理jpg或其他格式,不如stb_image这个单文件的库方便

今天改进完善了上述两个方面,修改的代码也比较容易。一个插曲是,看了大半天的window_w32.cpp(opencv的highgui模块下),还是没看懂它是如何把image的数据转换为bitmap数据的,流程细节比较多。基于先前的工作,我调用的是CreateBitmap函数,只能显示32位深度的位图,也就是一定要有alpha通道,因而简单的做法是把BGRBGRBGR数据排布改成BGRABGRABGRA即可(也许可以用CreateBitmap之外的做法,目前主要是简单粗暴的实现起来):

    case WM_CREATE:
        if (fc_window->im_type == FC_IMAGE_BMP) {
            fc_window->image = (HBITMAP)LoadImage(NULL, "D:/work/libfc/imgs/lena512.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
        }
        else if (fc_window->im_type == FC_IMAGE_PNG || fc_window->im_type == FC_IMAGE_JPG) {
            assert(fc_window->channel == 3);
            size_t buf_size = sizeof(unsigned char*)*fc_window->width * fc_window->height * 4; // rgba
            fc_window->buf = (unsigned char*)malloc(buf_size);
            memset(fc_window->buf, 0, buf_size);
            
            // BGR => BGRA, since CreateBitmap(..,24,..,..) doesn't work
            for (int nh = 0; nh < fc_window->height; nh++) {
                for (int nw = 0; nw < fc_window->width; nw++) {
                    for (int nc = 0; nc < 3; nc++) {
                        size_t src_idx = nh * fc_window->width * 3 + nw * 3 + nc;
                        size_t dst_idx = nh * fc_window->width * 4 + nw * 4 + nc;
                        fc_window->buf[dst_idx] = fc_window->im_data[src_idx];
                    }
                }
            }
            fc_window->image = CreateBitmap(fc_window->width, fc_window->height, 32, 1, fc_window->buf);
        }
        if (fc_window->image == NULL)
            MessageBox(hwnd, "Could not load image!", "Error", MB_OK | MB_ICONEXCLAMATION);
        break;

放码过来

image
对stb_image进行了简单封装得到fc_image。借鉴于vision-hw0

fc_image.h

#ifndef FC_IMAGE_H
#define FC_IMAGE_H

typedef enum FcImageType { FC_IMAGE_BMP, FC_IMAGE_PNG, FC_IMAGE_JPG } FcImageType;

typedef struct FcImage
{
    int w, h, c;
    unsigned char* data;
    size_t elem_size;
    FcImageType type;
} FcImage;

#ifdef __cplusplus
extern "C" {
#endif

    // load image. get BGRBGRBGR (opencv style) data
    FcImage fc_load_image(const char *filename);

#ifdef __cplusplus
}
#endif


#endif

fc_image.c

#include "fc_image.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

//------------------------------------------------------------
// static function declarations
//------------------------------------------------------------
static FcImage make_empty_image(int w, int h, int c);
static FcImage make_image(int w, int h, int c);
static FcImage load_image_stb(const char* filename, int channels);


//------------------------------------------------------------
// static function implementations
//------------------------------------------------------------
static FcImage make_empty_image(int w, int h, int c)
{
    FcImage out;
    out.data = 0;
    out.h = h;
    out.w = w;
    out.c = c;
    return out;
}

static FcImage make_image(int w, int h, int c)
{
    FcImage out = make_empty_image(w, h, c);
    out.data = (unsigned char*)calloc(h*w*c, sizeof(float));
    return out;
}

static FcImage load_image_stb(const char* filename, int channels)
{
    int w, h, c;
    unsigned char *data = stbi_load(filename, &w, &h, &c, channels);
    if (!data) {
        fprintf(stderr, "Cannot load image \"%s\"\nSTB Reason: %s\n",
            filename, stbi_failure_reason());
        exit(0);
    }
    if (channels) c = channels;
    int i, j, k;
    FcImage im = make_image(w, h, c);
    for (k = 0; k < c; ++k) {
        for (j = 0; j < h; ++j) {
            for (i = 0; i < w; ++i) {
                // RGB => BGR
                int src_index = k + c * i + c * w*j;
                int dst_index = (2 - k) + c * i + c * w*j;
                //cv_im2->imageData[dst_index] = data[src_index];
                im.data[dst_index] = data[src_index];
            }
        }
    }
    //We don't like alpha channels, #YOLO
    if (im.c == 4) im.c = 3;
    free(data);
    return im;
}


//------------------------------------------------------------
// function implementations
//------------------------------------------------------------
// load image. get BGRBGRBGR (opencv style) data
FcImage fc_load_image(const char *filename)
{
    FcImage im = load_image_stb(filename, 0);
    //TODO: assign im's type inside load_image_stb()
    return im;
}

show image

  • fc_higui.h

  • fc_higui_gdi.c

fc_higui.h

#ifndef FC_HIGUI_H
#define FC_HIGUI_H

#include "fc_image.h"

#ifdef __cplusplus
extern "C" {
#endif

    void fc_show_image(const char* winname, const FcImage* im);

    void fc_wait_key();


#ifdef __cplusplus
}
#endif


#endif

fc_gui_gdi.c

#include <stdio.h>
#include <stdbool.h>
#include <windows.h>
#include <assert.h>
#include "fc_image.h"

//#pragma comment(lib, "msimg32.lib") // for png's alpha channel

static const char* fc_window_class_name = "fc GUI class";

typedef struct FcWindow {
    HDC dc;
    HGDIOBJ image;
    unsigned char* im_data;
    unsigned char* buf;
    FcImageType im_type;
    int width;
    int height;
    int channel;
    int top_left_x; // window position, top left point's x coordinate
    int top_left_y; // window posttion, top left point's y coordinate
    bool adjusted;
    const char* title;
    //int bit_depth; // each pixel consist of how many bits?
    int elem_size;
} FcWindow;

FcWindow* fc_window;


//int DEFAULT_WIDTH, DEFAULT_HIGHT;
void SetWindowSize(HWND hWnd)
{
    if (fc_window->adjusted == false) {
        RECT WindowRect;
        RECT ClientRect;
        GetWindowRect(hWnd, &WindowRect);
        GetClientRect(hWnd, &ClientRect);
        WindowRect.right += (fc_window->width - ClientRect.right);
        WindowRect.bottom += (fc_window->height - ClientRect.bottom);
        MoveWindow(hWnd, WindowRect.left, WindowRect.top, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top, true);
    }

    fc_window->adjusted = true;
}

static void FillBitmapInfo(BITMAPINFO* bmi, int width, int height, int bpp, int origin)
{
    assert(bmi && width >= 0 && height >= 0 && (bpp == 8 || bpp == 24 || bpp == 32));

    BITMAPINFOHEADER* bmih = &(bmi->bmiHeader);

    memset(bmih, 0, sizeof(*bmih));
    bmih->biSize = sizeof(BITMAPINFOHEADER);
    bmih->biWidth = width;
    bmih->biHeight = origin ? abs(height) : -abs(height);
    bmih->biPlanes = 1;
    bmih->biBitCount = (unsigned short)bpp;
    bmih->biCompression = BI_RGB;

    if (bpp == 8)
    {
        RGBQUAD* palette = bmi->bmiColors;
        int i;
        for (i = 0; i < 256; i++)
        {
            palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = (BYTE)i;
            palette[i].rgbReserved = 0;
        }
    }
}

// returns TRUE if there is a problem such as ERROR_IO_PENDING.
static bool fc_get_bitmap_data(FcWindow* window, SIZE* size, int* channels, void** data)
{
    BITMAP bmp;
    GdiFlush();
    HGDIOBJ h = GetCurrentObject(window->dc, OBJ_BITMAP);
    if (size)
        size->cx = size->cy = 0;
    if (data)
        *data = 0;

    if (h == NULL)
        return true;
    if (GetObject(h, sizeof(bmp), &bmp) == 0)
        return true;

    if (size)
    {
        size->cx = abs(bmp.bmWidth);
        size->cy = abs(bmp.bmHeight);
    }

    if (channels)
        *channels = bmp.bmBitsPixel / 8;

    if (data)
        *data = bmp.bmBits;

    return false;
}

LRESULT __stdcall WindowProcedure(HWND hwnd, unsigned int msg, WPARAM wp, LPARAM lp)
{
    switch (msg)
    {
    case WM_CREATE:
        if (fc_window->im_type == FC_IMAGE_BMP) {
            fc_window->image = (HBITMAP)LoadImage(NULL, "D:/work/libfc/imgs/lena512.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
        }
        else if (fc_window->im_type == FC_IMAGE_PNG || fc_window->im_type == FC_IMAGE_JPG) {
            assert(fc_window->channel == 3);
            size_t buf_size = sizeof(unsigned char*)*fc_window->width * fc_window->height * 4; // rgba
            fc_window->buf = (unsigned char*)malloc(buf_size);
            memset(fc_window->buf, 0, buf_size);
            
            // BGR => BGRA, since CreateBitmap(..,24,..,..) doesn't work
            for (int nh = 0; nh < fc_window->height; nh++) {
                for (int nw = 0; nw < fc_window->width; nw++) {
                    for (int nc = 0; nc < 3; nc++) {
                        size_t src_idx = nh * fc_window->width * 3 + nw * 3 + nc;
                        size_t dst_idx = nh * fc_window->width * 4 + nw * 4 + nc;
                        fc_window->buf[dst_idx] = fc_window->im_data[src_idx];
                    }
                }
            }
            fc_window->image = CreateBitmap(fc_window->width, fc_window->height, 32, 1, fc_window->buf);
        }
        if (fc_window->image == NULL)
            MessageBox(hwnd, "Could not load image!", "Error", MB_OK | MB_ICONEXCLAMATION);
        break;

    case WM_PAINT:
    {
        SetWindowSize(hwnd);
        PAINTSTRUCT ps;

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

        fc_window->dc = CreateCompatibleDC(hdc);
        HBITMAP hbmOld = SelectObject(fc_window->dc, fc_window->image);

        #if 1
        //copy original, no stretch
        BitBlt(hdc, 0, 0, fc_window->width, fc_window->height, fc_window->dc, 0, 0, SRCCOPY);
        //AlphaBlend(hdc, 100, 0, bm.bmWidth, bm.bmHeight, my_window->dc, 0, 0, bm.bmWidth, bm.bmHeight, bf);
        #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(fc_window->dc, hbmOld);
        DeleteDC(fc_window->dc);

        EndPaint(hwnd, &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(hwnd, msg, wp, lp);
    }
}

void fc_destroy_window() {
    if (fc_window) {
        if (fc_window->buf) {
            free(fc_window->buf);
            fc_window->buf = NULL;
        }
        free(fc_window);
        fc_window = NULL;
    }
}

static void 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 = fc_window_class_name;
    /* Win 4.0 */
    wc.hIconSm = LoadIcon(0, IDI_APPLICATION);

    RegisterClassEx(&wc);
}

static BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    // hard code here. since x=600 and y=300 is good for most cases
    fc_window->top_left_x = 600;
    fc_window->top_left_y = 300;

    DWORD defStyle = WS_VISIBLE | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU;

    HWND hwnd = CreateWindowEx(0, fc_window_class_name, fc_window->title,
        defStyle, fc_window->top_left_x, fc_window->top_left_y,
        fc_window->width, fc_window->height, 0, 0, hInstance, 0);

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

    return TRUE;
}

static void fc_create_window(FcWindow** _my_window) {
    FcWindow* fc_window = (FcWindow*)malloc(sizeof(FcWindow));
    fc_window->dc = NULL;
    fc_window->im_data = NULL;
    fc_window->image = NULL;

    fc_window->adjusted = false;

    *_my_window = fc_window; // write back
}

void fc_show_image(const char* title, FcImage* im) {
    fc_create_window(&fc_window);
    fc_window->width = im->w;
    fc_window->height = im->h;
    fc_window->im_data = im->data;
    fc_window->im_type = FC_IMAGE_PNG;
    fc_window->title = title;
    fc_window->channel = im->c;
    fc_window->elem_size = im->elem_size * 8;

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

    MyRegisterClass(hInstance);

    if (!InitInstance(hInstance, nCmdShow))
    {
        fprintf(stderr, "Error! failed to init window\n");
    }
}

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

test
测试程序。如果不开USE_OPENCV宏,保存为.c文件即可;如果开启了USE_OPENCV宏,需要保存为.cpp文件,并且引入OpenCV库(用来验证效果是否一致),推荐用CMake构建。

fc_show_image_test.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "fc_image.h"
#include "fc_higui.h"

//#define USE_OPENCV

#ifdef USE_OPENCV
#include <opencv2/opencv.hpp>
#endif

int main() {
    //const char* im_pth = "D:/work/libfc/imgs/Lena.png";
    const char* im_pth = "D:/work/libfc/imgs/fruits.jpg"; 

    FcImage im = fc_load_image(im_pth);
    //im.type = FC_IMAGE_PNG;
    im.type = FC_IMAGE_JPG;
    fc_show_image("fruits.jpg", &im);
    fc_wait_key();

    #ifdef USE_OPENCV
        int depth = 8;
        IplImage* cv_im2 = cvCreateImage(CvSize(im.w, im.h), depth, im.c);

        cv_im2->imageData = (char*)im.data;
        cvShowImage("image2", cv_im2);

        cvWaitKey(0);
    #endif

    return 0;
}

猜你喜欢

转载自www.cnblogs.com/zjutzz/p/10928442.html