base.h

#pragma once

#include<iostream>
#include<vector>
#include<map>
#include<cmath>
#include<assert.h>

#define PI 3.14159265358979323
#define DEG2RAD(theta) (0.01745329251994329 * (theta))
#define FRACTION(v) ((v) - (int)(v))

using byte = unsigned char;

struct RGBA {
byte mB;
byte mG;
byte mR;
byte mA;

RGBA(
byte r = 255,
byte g = 255,
byte b = 255,
byte a = 255)
{
mR = r;
mG = g;
mB = b;
mA = a;
}
};

struct Point {
int32_t x;
int32_t y;
RGBA color;
};

application.h

#pragma once
#include "../global/base.h"
#include<Windows.h>

#define app Application::getInstance()

class Application {
public:
static Application* getInstance();
Application();
~Application();

//注册窗体类,创建一个窗体,显示
bool initApplication(HINSTANCE hInstance, const uint32_t& width = 800, const uint32_t& height = 600);

//托管了wndProc捕获的消息,并且进行处理
void handleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

//每一帧/每一次循环,都会调用,捕获以及分发窗体消息
bool peekMessage();

void show();

uint32_t getWidth() const { return mWidth; }
uint32_t getHeight() const { return mHeight; }
void* getCanvas() const { return mCanvasBuffer; }

private:
BOOL createWindow(HINSTANCE hInstance);
ATOM registerWindowClass(HINSTANCE hInstance);

private:
static Application* mInstance;

//为true表示当前窗体仍然在继续显示,程序一直在跑
//为false表示窗体已经被命令关闭,程序需要退出
bool mAlive{ true };

HINSTANCE mWindowInst;
WCHAR mWindowClassName[100] = L"AppWindow";
HWND mHwnd;

int mWidth = 800;
int mHeight = 600;

HDC mhDC;//当前窗口主dc
HDC mCanvasDC;//创建的于mhDC相兼容的绘图用的dc
HBITMAP mhBmp;//mCanvasDC内部生成的bitmap
void* mCanvasBuffer{ nullptr };//mhBmp对应的内存起始位置指针
};

application.cpp

#include"application.h"

Application* Application::mInstance = nullptr;
Application* Application::getInstance() {
if (mInstance == nullptr) {
mInstance = new Application();
}

return mInstance;
}

Application::Application() {}
Application::~Application() {}

LRESULT CALLBACK Wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
Application::getInstance()->handleMessage(hWnd, message, wParam, lParam);
return(DefWindowProc(hWnd, message, wParam, lParam));
}

bool Application::initApplication(HINSTANCE hInstance, const uint32_t& width, const uint32_t& height) {
mWidth = width;
mHeight = height;

//初始化窗体类型,并且注册
registerWindowClass(hInstance);

//生成一个窗体,并且显示
if (!createWindow(hInstance)) {
return false;
}

//初始化画布
/*
* DC:Device Context 设备上下文描述对象
* 每个窗口都有自己对应的设备区域映射,即mhDC
* 这里创建一个与本窗口兼容的DC,mCanvasDC
* 可以从mCanvasDC向mhDC拷贝绘图数据内容
*/
mhDC = GetDC(mHwnd);
mCanvasDC = CreateCompatibleDC(mhDC);

BITMAPINFO bmpInfo{};
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = mWidth;
bmpInfo.bmiHeader.biHeight = mHeight;
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 32;
bmpInfo.bmiHeader.biCompression = BI_RGB; //实际上存储方式为bgra

//创建与mhMem兼容的位图,其实实在mhMem指代的设备上划拨了一块内存,让mCanvasBuffer指向它
mhBmp = CreateDIBSection(mCanvasDC, &bmpInfo, DIB_RGB_COLORS, (void**)&mCanvasBuffer, 0, 0);//在这里创建buffer的内存

//一个设备可以创建多个位图,本设备使用mhBmp作为激活位图,对mCanvasDC的内存拷出,其实就是拷出了mhBmp的数据
SelectObject(mCanvasDC, mhBmp);//选择位图mCanvasDC给作画
memset(mCanvasBuffer, 0, mWidth * mHeight * 4); //清空buffer为0

return true;
}

ATOM Application::registerWindowClass(HINSTANCE hInstance)
{
WNDCLASSEXW wndClass;

wndClass.cbSize = sizeof(WNDCLASSEX);
wndClass.style = CS_HREDRAW | CS_VREDRAW; //水平/垂直大小发生变化重绘窗口
wndClass.lpfnWndProc = Wndproc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInstance; //应用程序句柄
wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);//应用程序图标,即任务栏的大图标
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);//鼠标图标
wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);//窗口背景色
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = mWindowClassName;//窗口类名
wndClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO);//窗口标题图标

return RegisterClassExW(&wndClass);
}

BOOL Application::createWindow(HINSTANCE hInstance)
{
mWindowInst = hInstance;

/*
* WS_POPUP:不需要标题栏,则不需要边框
* WS_OVERLAPPEDWINDOW:拥有普通程序主窗口的所有特点,必须有标题且有边框
*
* WS_CLIPSIBLINGS:被兄弟窗口挡住区域不绘制
* WS_CLIPCHILDREN:被子窗口遮挡住的区域不绘制
*/

auto dwExStyle = WS_EX_APPWINDOW;
auto dwStyle = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;

//由于存在标题栏等,所以需要计算中间显示区域的大小,比如PopUp的窗体,就没有标题栏,则不会改变
RECT windowRect;
windowRect.left = 0L;
windowRect.top = 0L;
windowRect.right = (long)mWidth;
windowRect.bottom = (long)mHeight;
AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle);

mHwnd = CreateWindowW(
mWindowClassName,
(LPCWSTR)"GraphicLearning", //窗体标题
dwStyle,
500,//x位置,相对左上角
500,//y位置,相对左上角
windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top,
nullptr,//父窗体
nullptr,//菜单栏
hInstance,//程序实例
nullptr);//额外参数


if (!mHwnd)
{
return FALSE;
}

ShowWindow(mHwnd, true);
UpdateWindow(mHwnd);

return TRUE;
}

bool Application::peekMessage() {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return mAlive;
}

void Application::handleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message)
{
case WM_CLOSE: {
DestroyWindow(hWnd);//此处销毁窗体,会自动发出WM_DESTROY
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY: {
PostQuitMessage(0);//发出线程终止请求
mAlive = false;
break;
}
}
}

void Application::show() {
BitBlt(mhDC, 0, 0, mWidth, mHeight, mCanvasDC, 0, 0, SRCCOPY);
}