大海捞针 Skia(C++) 第 4.1 期(特别篇):将绘制结果输出到窗口

前言

由于本人(我)没有系统学习过图形学,无法提供准确的术语表达,如果哪位大佬看到我的一些错误,还请友善指出!

第四期之后,我一直纠结于应该讲些什么。图形学的东西我真的学的不多,未来也不是很想走这个方向。但是我仍然希望通过我的一些绵薄之力为一些苦苦寻找关于Skia资料的兄弟们提供方便。说实话,我也犹豫要不要继续写下去。说到底我并不了解这些东西,只因为用过Aseprite,惊叹于Skia为它做出那样的界面效果,因此产生兴趣。也许对我来说,写这些可能没办法帮到别人,更像是记录自己学习的一个“日记”吧。

第四期我们带领大家绘制了基本图形,想必通过代码绘制图形是一件非常兴奋的事。但我想也许你会疲倦于到D盘下寻找你的绘制结果。于是,我们这一期将来解决这个问题——让绘制结果直观可见!
因为没有具体的一些案例,仅仅是一种优化,于是我决定叫做 4.1 特别篇!


具体实现

思路分析

要想让图片直接就能看到,而不是打开生成的图片查看,最好的方式想必就是将生成的图片显示出来。那么我们就不难想到写一个GUI程序来显示,那么有什么方法可以让图片显示到窗口呢?这里我想到了以下几个方法:

EasyX,Win32

EasyX 实现

EasyX是一个免费绘图库,简单易用。

用EasyX,我们可以很快创建出一个窗口,并且将生成的图片显示出来。

值得注意的是,这里我选择将绘制代码单独放到Draw.h下声明,Draw.cpp下定义。这是由于不知什么原因,EasyX库的graphics.h头文件会导致绘图失败。我猜想是因为 graphics.h 的某些代码与 Skia 冲突了。所以为了避免相互干扰,只好将绘制部分单独放到另一个编译单元去。

// main.cpp
#include "pch.h"
#include "Draw.h"
#include <graphics.h>
#include <conio.h>

int main()
{
	Draw(); // 具体的绘图函数

	IMAGE img;
	loadimage(&img, L"D:/test.png"); //注意第二个参数是宽字节字符串,如果项目用多字节字符集,可使用窄字节
	initgraph(img.getwidth(), img.getheight()); // 根据图片长宽设置窗口大小
	putimage(0, 0, &img); // 从窗口客户区左上角开始绘制

	_getch(); // 阻塞程序,让窗口保持,直到我们需要关闭的时候
	closegraph();

	return 0;
}

Draw.h 中仅仅包含了预编译头和Draw函数的声明,这里就不给出代码了。

// Draw.cpp
#include "pch.h"
#include "Draw.h"

bool Draw()
{
	SkBitmap bitmap;
	SkImageInfo bitmapInfo = SkImageInfo::Make(600, 400, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
	bitmap.allocPixels(bitmapInfo);
	SkCanvas canvas(bitmap);
	canvas.clear(0xffffffff);

	SkColor4f color = SkColor4f::FromColor(SkColor(0xff00ffff));
	SkPaint paint = SkPaint::SkPaint(color);
	canvas.drawCircle(SkPoint::Make(100, 100),
		SkScalar(50), paint);

	SkFILEWStream stream("D:/test.png");
	return SkEncodeImage(&stream, bitmap, SkEncodedImageFormat::kPNG, 0);
}

Win32 实现

Win32实现原理是通过StrechDIBits函数将内容显示到窗口中。这里可以参考如下文章。
【skia】win32中使用skia图形库

那么由于上述代码在实际测试的时候发现有一定问题,并且为了让读者专注于绘制过程而非展示过程,我将绘制操作封装成一个类,并且对上述链接中的代码进行改动(按照文中代码实现,当调整成窗口大小时,若窗口客户区宽高为0将导致程序崩溃)。

WinMain.cpp
#include "pch.h"
#include "CSkiaDraw.h"

// Global Variable
CSkiaDraw skd;

ATOM MyRegisterClass(HINSTANCE hInstance);
HWND InitWindow(HINSTANCE hInstance);
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, LPARAM lParam, WPARAM wParam);

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, INT iCmdShow)
{
	MyRegisterClass(hInstance);

	HWND hWnd = InitWindow(hInstance);

	ShowWindow(hWnd, iCmdShow);
	UpdateWindow(hWnd);

	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{

		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return 0;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX cls;
	cls.cbClsExtra = NULL;
	cls.cbSize = sizeof(cls);
	cls.lpfnWndProc = (WNDPROC)WndProc;
	cls.lpszClassName = "SkiaPaintingResult";
	cls.hInstance = hInstance;
	cls.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
	cls.cbWndExtra = NULL;
	cls.hCursor = LoadCursor(hInstance, IDC_ARROW);
	cls.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
	cls.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);
	cls.lpszMenuName = NULL;
	cls.style = CS_HREDRAW | CS_VREDRAW;

	return RegisterClassEx(&cls);
}

HWND InitWindow(HINSTANCE hInstance)
{
	return CreateWindow("SkiaPaintingResult", "Skia Painting Result", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 500, NULL, NULL, hInstance, NULL);
}

LRESULT WndProc(HWND hWnd, UINT uMsg, LPARAM lParam, WPARAM wParam)
{
	switch (uMsg)
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	case WM_PAINT:
	{
		PAINTSTRUCT ps;
		HDC hdc = BeginPaint(hWnd, &ps);

		RECT rt;
		GetClientRect(hWnd, &rt);
		int bmpw = rt.right - rt.left;
		int bmph = rt.bottom - rt.top;

		const size_t bmpSize = sizeof(BITMAPINFOHEADER) + bmpw * bmph * sizeof(uint32_t);
		BITMAPINFO* bmpInfo = (BITMAPINFO*)new BYTE[bmpSize]();
		bmpInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
		bmpInfo->bmiHeader.biWidth = bmpw;
		bmpInfo->bmiHeader.biHeight = -bmph;

		bmpInfo->bmiHeader.biPlanes = 1;
		bmpInfo->bmiHeader.biBitCount = 32;
		bmpInfo->bmiHeader.biCompression = BI_RGB;
		void* pixels = bmpInfo->bmiColors;

		SkImageInfo info = SkImageInfo::Make(bmpw, bmph,
			kBGRA_8888_SkColorType, kPremul_SkAlphaType);

		sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(info, pixels, bmpw * sizeof(uint32_t));

		if (bmpw > 0 && bmph > 0) //改动部分!!!
		{
			skd.SetCanvas(surface->getCanvas());
			skd.Draw(bmpw, bmph);

			StretchDIBits(hdc, 0, 0, bmpw, bmph,
				0, 0, bmpw, bmph,
				pixels, bmpInfo,
				DIB_RGB_COLORS, SRCCOPY);

			delete[] bmpInfo;
		}

		EndPaint(hWnd, &ps);
	}
	break;
	default:
		break;
	}
	return DefWindowProc(hWnd, uMsg, lParam, wParam);
}

CSkiaDraw.h
#pragma once
#include "pch.h"

class CSkiaDraw
{
public:
	CSkiaDraw();
	~CSkiaDraw();

	void Draw(int width, int height);
	void SetCanvas(SkCanvas* canvas);

private:
	SkBitmap _bitmap;
	SkImageInfo _bitmapInfo;
	SkCanvas* _canvas;
	bool _selfCreatedCanvas;

	SkColor4f _defaultBackgroundColor;
};
CSkiaDraw.cpp
#include "CSkiaDraw.h"

CSkiaDraw::CSkiaDraw()
{
	_defaultBackgroundColor = SkColor4f::FromColor(0xffffffff);
	_bitmapInfo = SkImageInfo::Make(600, 400, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
	_bitmap.setInfo(_bitmapInfo, 0);
	_bitmap.allocPixels(_bitmapInfo);
	_canvas = new SkCanvas(_bitmap);
	_canvas->clear(_defaultBackgroundColor);
	_selfCreatedCanvas = true;
}

CSkiaDraw::~CSkiaDraw()
{
	if (_canvas != nullptr && _selfCreatedCanvas == true)
		delete _canvas;
}

void CSkiaDraw::Draw(int width, int height)
{
	// 我们将在此编写绘制代码
	SkColor4f color = SkColor4f::FromColor(SkColor(0xff00ffff));
	SkPaint paint = SkPaint::SkPaint(color);
	paint.setAntiAlias(true);
	_canvas->clear(_defaultBackgroundColor);
	_canvas->drawCircle(SkPoint::Make(100, 100),
		SkScalar(50), paint);
	_canvas->flush();
}

void CSkiaDraw::SetCanvas(SkCanvas* canvas)
{
	if (_canvas != nullptr && _selfCreatedCanvas == true)
		delete _canvas;

	_canvas = canvas;
	_selfCreatedCanvas = false;
}

posted @ 2024-03-29 21:33  AquerKing  阅读(655)  评论(0)    收藏  举报