大海撈針 Skia(C++) 第 4.1 期(特別篇):將繪製結果輸出到視窗

AquerKing發表於2024-03-29

前言

由於本人(我)沒有系統學習過圖形學,無法提供準確的術語表達,如果哪位大佬看到我的一些錯誤,還請友善指出!

第四期之後,我一直糾結於應該講些什麼。圖形學的東西我真的學的不多,未來也不是很想走這個方向。但是我仍然希望透過我的一些綿薄之力為一些苦苦尋找關於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;
}

相關文章