Win32编程之从内存中加载位图,并显示到hdc上

近期在项目中遇到一个问题,如何在实现从内存中加载RBG帧数据,然后提交到hdc上显示,这里假设大家对win32程序已经很熟了,有了相关的框架,如果不熟的小伙伴可以看我的老师编写的一本书《游戏程序设计基础》,书中的附加代码对于2d和3d的项目都很实用。

下面我们来介绍如何在MyDraw函数中从内存中加载帧数据。首先帧数据是指RGB值,由于目前我只了解到在Win32中显示的图像都是位图,Bitmap,所以我们需要定义对应的格式数据,不过这里只需要两个:

1. 位图信息:

	int nx = WINDOW_WIDTH; // 图像的宽度
	int ny = WINDOW_HEIGHT; // 图像的高度
	int channels = 3; // 3代表rgb三通道,4则表示还有A,Alpha透明通道
	// unsigned char *data = new unsigned char[nx*ny*channels];

	BITMAPINFO bmi; // 创建一个位图信息数据
	::ZeroMemory(&bmi, sizeof(BITMAPINFO)); 
	bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmi.bmiHeader.biWidth = nx;  // 指定位图宽度
	bmi.bmiHeader.biHeight = ny; // 指定位图高度
	bmi.bmiHeader.biPlanes = 1;  // 指定层数,位图有能是多维的
	bmi.bmiHeader.biBitCount = 24; // 指定每个像素的位数,这里是rgb,分别用char类型表示,共24位
                                   // 如果你的帧数据是rgba,这里应制定32
	bmi.bmiHeader.biCompression = BI_RGB; // BI_RGB = 0,表示压缩程度,0是无损耗,图像质量高
	bmi.bmiHeader.biSizeImage = nx * ny * 3; // 图像的大小

2. 帧数据 data,格式为一维rgb(a)字符数组

绘制内存中的图像

有两种方式可以直接将内存中的帧数据data绘制到hdc上:

第一种方式:

	HDC hCompatibleDC = CreateCompatibleDC(hdc);
	HBITMAP hCompatibleBitmap = CreateCompatibleBitmap(hdc, bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight);
	HBITMAP hOldBitmap = (HBITMAP)SelectObject(hCompatibleDC, hCompatibleBitmap);
	SetDIBits(hdc, hCompatibleBitmap, 0, bmi.bmiHeader.biHeight, data, (BITMAPINFO*)&bmi.bmiHeader, DIB_RGB_COLORS);
	BitBlt(hdc, 0, 0, bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight, hCompatibleDC, 0, 0, SRCCOPY);
	SelectObject(hCompatibleDC, hOldBitmap);
	DeleteObject(hCompatibleDC);

第二种方式:

	StretchDIBits(g_hDc, 0, 0, bmi.bmiHeader.biWidth,
		bmi.bmiHeader.biHeight, 0, 0, bmi.bmiHeader.biWidth,
		bmi.bmiHeader.biHeight, data, (BITMAPINFO*)&bmi.bmiHeader,
		DIB_RGB_COLORS, SRCCOPY);

两种方式的效果是一样的,但是很明显第二种更简洁。参考了如下文章:

https://www.cnblogs.com/lidabo/p/3542732.html

这里需要注意,date中的帧数据(rgb(a)字符数组)中数据的顺序需要修正一下,否则得到的图像是上下颠倒的。

另外,如果结果图像倾斜了,很有可能是因为我们的原始数据是rgba格式,而我们却在位图信息中制定了rgb三通道(或者相反)。

附上全部代码:

WinMain.cpp(您需要创建win32项目才能运行)

#include <stdio.h>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>


#include <windows.h>
#pragma comment(lib,"winmm.lib")		// 调用PlaySound函数所需库文件

#define WINDOW_WIDTH	1500	
#define WINDOW_HEIGHT	800
#define WINDOW_TITLE	L"【从内存中输出图像演示】客户端"	

#include "vec3.h"   // 双引号应用的是程序目录的相对路径中的头文件

// 控制台输出
HANDLE hOutput;
unsigned long lgsize;


HINSTANCE g_hInst;
HWND g_hWnd;
HDC g_hDc;
DWORD   g_tPre = 0, g_tNow = 0;       //声明l两个变量来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间

int				    MyWindowsClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
void     			MyDraw(HWND hwnd);

int APIENTRY WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR     lpCmdLine,
	int       nCmdShow)
{
	MSG msg = { 0 };
	MyWindowsClass(hInstance);

	if (!InitInstance(hInstance, nCmdShow))
	{
		return FALSE;
	}
	while (msg.message != WM_QUIT)		//使用while循环,如果消息不是WM_QUIT消息,就继续循环
	{
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))   //查看应用程序消息队列,有消息时将队列中的消息派发出去。
		{
			TranslateMessage(&msg);		//将虚拟键消息转换为字符消息
			DispatchMessage(&msg);			//分发一个消息给窗口程序。
		}
		else
		{
			g_tNow = GetTickCount();   //获取当前系统时间
			if (g_tNow - g_tPre >= 5)        //当此次循环运行与上次绘图时间相差0.005秒时再进行重绘操作
				MyDraw(g_hWnd);
		}

	}

	return msg.wParam;
}

int MyWindowsClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;
	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = (WNDPROC)WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = NULL;
	wcex.hCursor = NULL;
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = L"gamebase";
	wcex.hIconSm = NULL;

	return RegisterClassEx(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	int i;
	g_hInst = hInstance;
	HBITMAP bmp;

	g_hWnd = CreateWindow(L"gamebase", WINDOW_TITLE,
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
		WINDOW_HEIGHT, NULL, NULL, hInstance, NULL);
	if (!g_hWnd)
	{
		return FALSE;
	}
	MoveWindow(g_hWnd, 250, 80, WINDOW_WIDTH, WINDOW_HEIGHT, true); //调整窗口显示时的位置,使窗口左上角位于(250,80)处
	ShowWindow(g_hWnd, nCmdShow); //调用ShowWindow函数来显示窗口
	UpdateWindow(g_hWnd);

	g_hDc = GetDC(g_hWnd);

	// 开启命令行用于显示输出
	AllocConsole();
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

	char strbuf[50] = "******HelloWorld!************\n";
	WriteFile(hOutput, strbuf, strlen(strbuf), &lgsize, 0);

	return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	int i;

	switch (message)
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		FreeConsole();
		ReleaseDC(g_hWnd, g_hDc);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}


void MyDraw(HWND hwnd)
{
	int nx = 200; // 图像宽度
	int ny = 100; // 图像高度
	int channels = 3; // 代表rgb三通道 恢复tcpsocket时改成4!
	unsigned char *data = new unsigned char[nx*ny*channels];

    // 构建内存rgb图像
	for (int j = ny - 1; j >= 0; j--) {
		for (int i = 0; i < nx; i++) {
			/*float r = float(i) / float(nx);
			float g = float(j) / float(ny);
			float b = 0.2;*/
			vec3 col(float(i) / float(nx), float(j) / float(ny), 0.2);
			int ir = int(255.99*col[0]);
			int ig = int(255.99*col[1]);
			int ib = int(255.99*col[2]);
			data[(ny - j - 1)*nx * 3 + 3 * i] = ib;
			data[(ny - j - 1)*nx * 3 + 3 * i + 1] = ig;
			data[(ny - j - 1)*nx * 3 + 3 * i + 2] = ir;

			// fs << ir << " " << ig << " " << ib << "\n";
		}
	}

	unsigned char *dataflip = new unsigned char[nx*ny*channels];

	// 旋转90°
	//for (int i = 0; i < 200 * 100 * 3; i++)
	//{
	//	dataflip[i] = data[200 * 100 * 3 - i];
	//}

	// 次对角线对称
	//for (int j = 0; j < 100; j++) 
	//{
	//	for (int i = 0; i < 200 * 3; i+=3)
	//	{
	//		dataflip[j * 200 * 3 + i] = data[j * 200 * 3 + (199 * 3 - i)];
	//		dataflip[j * 200 * 3 + i + 1] = data[j * 200 * 3 + (199 * 3 - i -1)];
	//		dataflip[j * 200 * 3 + i + 2] = data[j * 200 * 3 + (199 * 3 - i - 2)];
	//	}
	//}

	// 垂直对称
	for (int i = 0; i < 200 * 3; i++)
	{
		for (int j = 0; j < 100; j++)
		{
			dataflip[j * 200 * 3 + i] = data[(99 - j) * 200 * 3 + i];
		}
	}

	BITMAPINFO bmi;
	::ZeroMemory(&bmi, sizeof(BITMAPINFO));
	bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmi.bmiHeader.biWidth = nx;
	bmi.bmiHeader.biHeight = ny;
	bmi.bmiHeader.biPlanes = 1;
	bmi.bmiHeader.biBitCount = 24; // 恢复tcpsocket时改成32!
	bmi.bmiHeader.biCompression = BI_RGB;
	bmi.bmiHeader.biSizeImage = nx * ny * channels; // 恢复tcpsocket时改成4!

	//HDC hCompatibleDC = CreateCompatibleDC(g_hDc);
	//HBITMAP hCompatibleBitmap = CreateCompatibleBitmap(g_hDc, bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight);
	//HBITMAP hOldBitmap = (HBITMAP)SelectObject(hCompatibleDC, hCompatibleBitmap);
	//SetDIBits(g_hDc, hCompatibleBitmap, 0, bmi.bmiHeader.biHeight, data, (BITMAPINFO*)&bmi.bmiHeader, DIB_RGB_COLORS);
	//BitBlt(g_hDc, 0, 0, bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight, hCompatibleDC, 0, 0, SRCCOPY);
	//SelectObject(hCompatibleDC, hOldBitmap);
	//DeleteObject(hCompatibleDC);

	// 执行绘制
	StretchDIBits(g_hDc, 0, 0, bmi.bmiHeader.biWidth,
		bmi.bmiHeader.biHeight, 0, 0, bmi.bmiHeader.biWidth,
		bmi.bmiHeader.biHeight, dataflip, (BITMAPINFO*)&bmi.bmiHeader,
		DIB_RGB_COLORS, SRCCOPY);

	delete data;
	delete dataflip;
	g_tPre = GetTickCount();
}

vec3.h(取自《Raytracing in One Weekend》绪论部分,用于辅助构建内存rgb图像)

//
// Created by YouXin on 2020/2/15.
//

#ifndef INC_2_1_OUTPUTANIMAGE_VEC3_H
#define INC_2_1_OUTPUTANIMAGE_VEC3_H

#include <iostream>
#include <math.h>
#include <stdlib.h>

class vec3 {
public:
    vec3() {}
    vec3(float e0, float e1, float e2) { e[0] = e0; e[1] = e1; e[2] = e2; }
    inline float x() const { return e[0]; }
    inline float y() const { return e[1]; }
    inline float z() const { return e[2]; }
    inline float r() const { return e[0]; }
    inline float g() const { return e[1]; }
    inline float b() const { return e[2]; }

    inline const vec3& operator+() const { return *this; }
    inline vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); }
    inline float operator[](int i) const { return e[i]; }
    inline float& operator[](int i) { return e[i]; }

    inline vec3& operator+=(const vec3 &v2);
    inline vec3& operator-=(const vec3 &v2);
    inline vec3& operator*=(const vec3 &v2);
    inline vec3& operator/=(const vec3 &v2);
    inline vec3& operator*=(const float t);
    inline vec3& operator/=(const float t);

    inline float length() const { return sqrt(e[0]*e[0] + e[1]*e[1] + e[2]*e[2]); }
    inline float squared_length() const { return e[0]*e[0] + e[1]*e[1] + e[2]*e[2]; }
    inline void make_unit_vector();



    float e[3];
};
inline std::istream& operator>>(std::istream &is, vec3 &t) {
    is >> t.e[0] >> t.e[1] >> t.e[2];
    return is;
}

inline std::ostream& operator<<(std::ostream &os, const vec3 &t) {
    os << t.e[0] << " " << t.e[1] << " " << t.e[2];
    return os;
}

inline void vec3::make_unit_vector() {
    float k = 1.0 / sqrt(e[0]*e[0] + e[1]*e[1] + e[2]*e[2]);
    e[0] *= k; e[1] *= k; e[2] *= k;
}

inline vec3 operator+(const vec3 &v1, const vec3 &v2) {
    return vec3(v1.e[0] + v2.e[0], v1.e[1] + v2.e[1], v1.e[2] + v2.e[2]);
}

inline vec3 operator-(const vec3 &v1, const vec3 &v2) {
    return vec3(v1.e[0] - v2.e[0], v1.e[1] - v2.e[1], v1.e[2] - v2.e[2]);
}

inline vec3 operator*(const vec3 &v1, const vec3 &v2) {
    return vec3(v1.e[0] * v2.e[0], v1.e[1] * v2.e[1], v1.e[2] * v2.e[2]);
}

inline vec3 operator*(float t, const vec3 &v) {
    return vec3(t*v.e[0], t*v.e[1], t*v.e[2]);
}

inline vec3 operator*(const vec3 &v, float t) {
    return vec3(t*v.e[0], t*v.e[1], t*v.e[2]);
}

inline vec3 operator/(const vec3 &v1, const vec3 &v2) {
    return vec3(v1.e[0] / v2.e[0], v1.e[1] / v2.e[1], v1.e[2] / v2.e[2]);
}

inline vec3 operator/(vec3 v, float t) {
    return vec3(v.e[0]/t, v.e[1]/t, v.e[2]/t);
}

inline float dot(const vec3 &v1, const vec3 &v2) {
    return v1.e[0]*v2.e[0]
           + v1.e[1]*v2.e[1]
           + v1.e[2]*v2.e[2];
}

inline vec3 cross(const vec3 &v1, const vec3 &v2) {
    return vec3(v1.e[1] * v2.e[2] - v1.e[2] * v2.e[1],
                v1.e[2] * v2.e[0] - v1.e[0] * v2.e[2],
                v1.e[0] * v2.e[1] - v1.e[1] * v2.e[0]);
}

inline vec3& vec3::operator+=(const vec3 &v) {
    e[0] += v.e[0];
    e[1] += v.e[1];
    e[2] += v.e[2];
    return *this;
}

inline vec3& vec3::operator-=(const vec3& v) {
    e[0] -= v.e[0];
    e[1] -= v.e[1];
    e[2] -= v.e[2];
    return *this;
}

inline vec3& vec3::operator*=(const vec3 &v) {
    e[0] *= v.e[0];
    e[1] *= v.e[1];
    e[2] *= v.e[2];
    return *this;
}

inline vec3& vec3::operator*=(const float t) {
    e[0] *= t;
    e[1] *= t;
    e[2] *= t;
    return *this;
}

inline vec3& vec3::operator/=(const vec3 &v) {
    e[0] /= v.e[0];
    e[1] /= v.e[1];
    e[2] /= v.e[2];
    return *this;
}

inline vec3& vec3::operator/=(const float t) {
    float k = 1.0/t;

    e[0] *= k;
    e[1] *= k;
    e[2] *= k;
    return *this;
}

inline vec3 unit_vector(vec3 v) {
    return v / v.length();
}

#endif //INC_2_1_OUTPUTANIMAGE_VEC3_H

vec3.cpp

//
// Created by YouXin on 2020/2/15.
//

#include "vec3.h"


显示效果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值