当前位置: 首页 > ds >正文

Skia如何在窗口上绘图

Skia如何在 Windows 窗口中绘图,涉及到了一些基本的 Windows API 开发知识,掌握这些知识对大家使用 C++ 创建 Windows 应用非常有帮助。

示例代码在 Windows 窗口的右下角绘制了一个矩形,调整窗口大小,矩形始终位于窗口右下角,如下图所示:

入口函数

示例入口方法的代码如下所示:

// #include <windows.h>int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow)
{initWindow();MSG msg;while (GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}return 0;
}

这段代码主要完成了两个任务:

  1. initWindow 是一个自定义方法,用于使用 Windows API 创建一个窗口。

  2. while 循环是 Windows 应用程序的消息循环,应用程序退出前(收到 Quit 消息前),此循环不会退出。

接下来我们就介绍一下创建窗口的代码。

创建窗口

这段代码我们首先定义了全局变量 w 和 h ,它们用来存储窗口的宽度和高度。

initWindow方法负责创建并显示窗口,代码如下所示:

int w{ 800 }, h{ 600 };void initWindow() {std::wstring clsName{ L"DrawInWindow" };auto hinstance = GetModuleHandle(NULL);WNDCLASSEX wcx{};wcx.cbSize = sizeof(WNDCLASSEX);wcx.style = CS_HREDRAW | CS_VREDRAW;wcx.lpfnWndProc = wndProc;wcx.hCursor = LoadCursor(nullptr, IDC_ARROW);wcx.lpszClassName = clsName.c_str();if (!RegisterClassEx(&wcx)) {return;}auto hwnd = CreateWindow(clsName.c_str(), clsName.c_str(), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, w, h, nullptr, nullptr, hinstance, nullptr);ShowWindow(hwnd, SW_SHOW);
}

这段代码完全与 Skia 无关,所以简单介绍一下,代码主要做了以下三个工作:

  1. 注册窗口类 (RegisterClassEx)这里定义了窗口消息处理函数wndProc

  2. 创建窗口(CreateWindow)此处用到了全局变量 w 和 h 来控制窗口初始化时的宽度和高度。

  3. 显示窗口(ShowWindow)此处使用的 hwnd 是窗口句柄,姑且把它理解为窗口指针(实际上不是)

窗口创建成功后,窗口的消息处理函数会陆续收到与窗口有关的消息,比如窗口大小调整消息(WM_SIZE), 窗口重绘消息(WM_PAINT)等。

接下来看一下 wndProc 函数是如何处理这些窗口消息的。

窗口消息处理函数

一个窗口可能会接收到很多类型的窗口消息,譬如 WM_SIZE 、 WM_PAINT 和 WM_CLOSE 消息。如下代码所示:

LRESULT CALLBACK wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{    switch (message) {case WM_SIZE: {w = LOWORD(lParam);h = HIWORD(lParam);break;}case WM_PAINT: {paint(hWnd);break;}case WM_CLOSE: {PostQuitMessage(0);break;}default: {break;}}return DefWindowProc(hWnd, message, wParam, lParam);
}
  1. WM_SIZE 消息 ShowWindow 代码执行后,窗口的消息处理函数会先收到 WM_SIZE 消息, 不仅如此,当用户通过拖拽窗口边框改变窗口大小时、窗口最大化时、最小化时都会收到 WM_SIZE 消息。 在处理 WM_SIZE 消息时,重置在全局变量中缓存的窗口宽度和高度(全局变量 w 和 h )。

  2. WM_PAINT消息 当系统需要重绘窗口时,会向窗口发送 WM_PAINT 消息, 比如窗口大小改变或应用程序内调用InvalidateRect系统API(强制重绘窗口)时,系统都会向窗口发送WM_PAINT 消息。 在处理 WM_PAINT 消息时会执行一个自定义的 paint 方法。

  3. WM_CLOSE消息 当用户点击窗口标题栏的关闭按钮时,操作系统会向窗口发送 WM_CLOSE 消息, 收到这个消息之后,马上就调用了系统 API: PostQuitMessage ,此 API 会向应用程序主消息循环发送退出消息。 此时 wWinMain 入口方法的 while 循环会退出,整个应用程序也就退出了。 如果不调用 PostQuitMessage API,虽然窗口会关闭,但应用程序不会退出。

在窗口上绘制矩形

最核心的代码就是处理 WM_PAINT 消息时执行的 paint 方法了,此方法的代码如下所示:

void paint(const HWND hWnd)
{if (w <= 0 || h <= 0){return;}SkColor *surfaceMemory = new SkColor[w * h]{SK_ColorBLACK};SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirect(info, surfaceMemory, w * sizeof(SkColor));SkPaint paint;paint.setColor(SK_ColorRED);SkRect rect = SkRect::MakeXYWH(w - 150, h - 150, 140, 140);canvas->drawRect(rect, paint);PAINTSTRUCT ps;auto dc = BeginPaint(hWnd, &ps);BITMAPINFO bmpInfo = {sizeof(BITMAPINFOHEADER), w, 0 - h, 1, 32, BI_RGB, h * 4 * w, 0, 0, 0, 0};StretchDIBits(dc, 0, 0, w, h, 0, 0, w, h, surfaceMemory, &bmpInfo, DIB_RGB_COLORS, SRCCOPY);ReleaseDC(hWnd, dc);EndPaint(hWnd, &ps);delete[] surfaceMemory;
}

这段代码有以下几点需要注意:

  1. 判断当前窗口的宽度和高度是否为 0 ,                                                                                  如果宽度或高度为 0 ,则不执行后面的渲染工作。 当窗口最小化时,窗口的宽度和高度为 0 ,在一些特殊情况下,系统会向最小化状态的窗口发送重绘指令。 这行代码的作用就是处理类似的特殊情况,避免不必要的渲染工作消耗资源。

  2. 创建并初始化像素数组                                                                                               surfaceMemory是一个二维像素数组,这个二维像素数组的行数是窗口的高度,列数是窗口的宽度。 将把这个像素数组里的像素铺满整个窗口表面。 像素数组用 SK_ColorBLACK 初始化,也就是说,这个像素数组内所有的像素颜色都是黑色。

  3. 创建画布对象                                                                                      SkCanvas::MakeRasterDirect 方法根据像素数据创建了 canvas 对象。 这个方法的第一个参数为图像信息(SkImageInfo), 第二个参数为 指向目标像素缓冲区的指针(也就是刚刚创建的像素数组的地址)。 第三个参数为是每行字节数量,这里用 w * sizeof(SkColor) 表示每行数据的字节数量。

sizeof(SkColor) 用于获取一个像素的字节数量。

SkColor 是 uint32_t 的别名,占据 4 个字节的空间(uint32_t位宽为 32 位,字节unsigned char的位宽为 8 位)。

sizeof(SkColor) 得到的 1 个像素的字节数量是4(4 = 32/8)。

可以简单的认为一个 SkColor 占据的4个字节分别为透明度、红、绿、蓝四个颜色分量的值(实际内存中的数据并不一定是这样分布的)。

 

4,在窗口右下角绘制矩形

在窗口的右下角绘制了一个矩形(正方形),正方形边长为 140 像素,正方形距离窗口右下角边距为 10 像素。

改变窗口大小会更新全局变量 w 和 h ,会触发窗口的重绘消息,会重新执行paint方法,重新创建像素数组,重新在窗口右下角绘制矩形。

当矩形绘制完成后,存储在 surfaceMemory 的像素就代表着一个包含黑色背景、红色矩形的图像了,可以被绘制到窗口上了。

5,复制像素数据到图形输出设备存储区

StretchDIBits 是 Windows 操作系统 API ,它负责把 surfaceMemory 管理的像素数据复制到 图形输出设备存储区 。

当像素数据被复制到图形输出设备存储区后,窗口就变成了一个包含黑色背景、红色矩形的窗口了。

值得注意的是,在绘制完成之后就删除了 surfaceMemory 管理的内存。

也就是说,每次窗口重绘都会重新分配像素内存,每次重绘完成后,都会释放像素内存(改变窗口大小时会自动触发窗口重绘)。

这样做虽然可以让应用程序占用更少的内存,但如果你的窗口尺寸固定,且有大量的动态元素,需要不断的执行重绘的话,那你的程序可能会卡。

如果真是这样的话,你最好把 surfaceMemory做成全局变量,在应用启动时初始化一次即可,避免不必要的CPU消耗(初始化和销毁内存都是CPU消耗)。

运行一下程序,就是开始时提供的动图效果啦。

http://www.xdnf.cn/news/8284.html

相关文章:

  • 突破免疫研究瓶颈!Elabscience IL - 4 抗体 [11B11](APC 偶联)靶向识别小鼠细胞因子
  • 纯JS前端转图片成tiff格式
  • 选择第三方软件检测机构做软件测试的三大原因
  • 从零开始学习QT——第二步
  • Rabbit MQ
  • CSS:vertical-align用法以及布局小案例(较难)
  • Spring AI Alibaba 调用文生语音模型(CosyVoice)
  • 基于labview的声音采集与存储分析系统
  • 深入浅出DDD:从理论到落地的关键
  • 海南藏族自治州政府门户网站集约化建设实践与动易解决方案应用
  • Java集合框架入门指南:从小白到基础掌握
  • 聚水潭ERP(奇门)集成用友ERP(用友U8、U9、NC、BIP、畅捷通T+、好业财)
  • 位图算法——判断唯一字符
  • 百度智能云千帆AppBuilder RAG流程技术文档
  • 佰力博科技与您探讨半导体电阻测试常用的一些方法
  • Qt 布局管理器的层级关系
  • 【I2C】高效实现I2C寄存器读取函数
  • 如何免费申请SSL证书并无限续期!
  • 使用Node开发需要知道的背景知识
  • 基于机器学习的策略开发和Backtrader回测
  • “2025香港国际法律服务大会探讨“跨法域 链全球”新格局”
  • 基于LangManus深入理解系统提示设计
  • Origin绘制多因子柱状点线图
  • 0x90属性中的属性名$I30和Scb->AttributeName的关系
  • day19-20-四剑客-find-grep-sed-awk
  • OpenCV CUDA 模块图像过滤-----创建一个计算图像导数的滤波器函数createDerivFilter()
  • 深入剖析小红书笔记详情接口:技术原理与实战应用
  • 技术篇-2.1.C\C++应用场景及开发工具安装
  • Python训练营打卡——DAY33(2025.5.22)
  • 并发编程之异步线程池