近期在项目中遇到一个问题,如何在实现从内存中加载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"
显示效果如下: