前言
由於本人(我)沒有系統學習過圖形學,無法提供準確的術語表達,如果哪位大佬看到我的一些錯誤,還請友善指出!
第四期之後,我一直糾結於應該講些什麼。圖形學的東西我真的學的不多,未來也不是很想走這個方向。但是我仍然希望透過我的一些綿薄之力為一些苦苦尋找關於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;
}