前言
由于本人(我)没有系统学习过图形学,无法提供准确的术语表达,如果哪位大佬看到我的一些错误,还请友善指出!
第四期之后,我一直纠结于应该讲些什么。图形学的东西我真的学的不多,未来也不是很想走这个方向。但是我仍然希望通过我的一些绵薄之力为一些苦苦寻找关于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;
}