canvas是skia的核心部分,skia的逻都是围绕skcanvas对象来组织管理的,通过skcanvas可以指定不同的渲染上下文、
draw call
(绘制命令)、以及绘制状态管理(如绘图矩阵、操作栈等)
skcanvas
和 skpaint
共同提供了sksurface和skbasedevice上绘制的状态,skcanvas保存了所有操作的堆栈,通过 save
和restore
这两个方法来管理skcanvas的操作状态信息。
skcanvas是可以指定在特定的像素上进行绘制,为其提供这种能力主要是不同画布来提供的(sksurface),skcanvas本身也是一个基类,画布主要可以分为两种,Raster Surface 和 GPU Suface。
Raster Surface
生成的canvas可以将绘制生成在cpu的内存上(如在不同的图片绘制)
CPU Surface
生成的canvas 可以内部通过opengl、webgl、vulkan来进行绘制(目前skia可以在webassembly技术在web上使用)
skia提供的backends
(理解为不同的后端渲染设备),有以下几种:
raster
cpu渲染(如将结果绘制在不同的图片上,可以理解为只要绘制在CPU的内存上,都可以使用这种方式)
gpu
绘制在GPU上,可以创建opengl、vulkan的绘制
skPdf
将结果绘制在PDF文档上
skPicture
将结果绘制在显示列表中(类似于内存结构,下次绘制可以用来加速)
skSvg
将结果绘制在Svg文档上。
skXPS
将结果绘制在XPS文档上.
Raster是通过指定一块内存,canvas会将draw calls绘制在一块CPU的内存上,这个内存一般来说是一个栅格图片.下面的代码有三种实现方式(指定图片、指定内存、指定窗口句柄Hwnd)来实现绘制。
#include "SkData.h"
#include "SkImage.h"
#include "SkStream.h"
#include "SkSurface.h"
//内部创建内存,将结果保存在一张图片中;
void raster(int width, int height,
void (*draw)(SkCanvas*),
const char* path) {
sk_sp<SkSurface> rasterSurface =
SkSurface::MakeRasterN32Premul(width, height);
SkCanvas* rasterCanvas = rasterSurface->getCanvas();
draw(rasterCanvas);
sk_sp<SkImage> img(rasterSurface->makeImageSnapshot());
if (!img) { return; }
sk_sp<SkData> png(img->encode());
if (!png) { return; }
SkFILEWStream out(path);
(void)out.write(png->data(), png->size());
}
//指定一片内存,将结果绘制在这个内存中
void raster(char* pixel,int width,int height,void (*draw)(SkCanvas*))
{
const SkImageInfo image_info = SkImageInfo::Make(data->width(), data->height(),kN32_SkColorType, SkAlphaType::kPremul_SkAlphaType);
//获取每一行的字节大小;
const int bytes_per_line = sizeof(uint32_t)*width;
auto canvas = SkCanvas::MakeRasterDirect(image_info, pixel, bytes_per_line);
draw(canvas)//绘制回调;
}
//windows 下绑定窗口句柄的CPU绘制
#include <windows.h>
SkAutoMalloc fSurfaceMemory;
void renderWindows(Hwnd hwnd)
{
//获取窗口大小;
RECT rect;
GetClientRect(fWnd, &rect);
int w=rect.right-rect.left;
int h=rect.bottom-rect.top;
const size_t bmpSize = sizeof(BITMAPINFOHEADER) + w * h * sizeof(uint32_t);
//创建一片内存与窗口的内存大小相同;
fSurfaceMemory.reset(bmpSize);
BITMAPINFO* bmpInfo = reinterpret_cast<BITMAPINFO*>(fSurfaceMemory.get());
ZeroMemory(bmpInfo, sizeof(BITMAPINFO));
bmpInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo->bmiHeader.biWidth = w;
bmpInfo->bmiHeader.biHeight = -h; // negative means top-down bitmap. Skia draws top-down.
bmpInfo->bmiHeader.biPlanes = 1;
bmpInfo->bmiHeader.biBitCount = 32;
bmpInfo->bmiHeader.biCompression = BI_RGB;
void* pixels = bmpInfo->bmiColors;
SkImageInfo info = SkImageInfo::Make(w, h, fDisplayParams.fColorType, kPremul_SkAlphaType, fDisplayParams.fColorSpace);
//将窗口的内存位置给到内存图片中.
fBackbufferSurface = SkSurface::MakeRasterDirect(info, pixels, sizeof(uint32_t) * w);
}
//将结果同步到hwnd上;
void swapBuffers() {
BITMAPINFO* bmpInfo = reinterpret_cast<BITMAPINFO*>(fSurfaceMemory.get());
HDC dc = GetDC(fWnd);
RECT rect;
GetClientRect(fWnd, &rect);
int w=rect.right-rect.left;
int h=rect.bottom-rect.top;
StretchDIBits(dc, 0, 0, w, h, 0, 0, w, h, bmpInfo->bmiColors, bmpInfo,
DIB_RGB_COLORS, SRCCOPY);
ReleaseDC(fWnd, dc);
}
必须有一个GrContext对象来管理gpu context,skia自身没有直接通过窗口创建上下文的方法,不过有一个示例的库(tools/sk_app)可以创建不同平台、Surface窗口的方法。
#include "GrContext.h"
#include "gl/GrGLInterface.h"
#include "SkData.h"
#include "SkImage.h"
#include "SkStream.h"
#include "SkSurface.h"
void gl_example(int width, int height, void (*draw)(SkCanvas*), const char* path) {
// You've already created your OpenGL context and bound it.
const GrGLInterface* interface = nullptr;
// Leaving interface as null makes Skia extract pointers to OpenGL functions for the current
// context in a platform-specific way. Alternatively, you may create your own GrGLInterface and
// initialize it however you like to attach to an alternate OpenGL implementation or intercept
// Skia's OpenGL calls.
sk_sp<GrContext> context = GrContext::MakeGL(interface);
SkImageInfo info = SkImageInfo:: MakeN32Premul(width, height);
sk_sp<SkSurface> gpuSurface(
SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info));
if (!gpuSurface) {
SkDebugf("SkSurface::MakeRenderTarget returned null\n");
return;
}
SkCanvas* gpuCanvas = gpuSurface->getCanvas();
draw(gpuCanvas);
sk_sp<SkImage> img(gpuSurface->makeImageSnapshot());
if (!img) { return; }
sk_sp<SkData> png(img->encode());
if (!png) { return; }
SkFILEWStream out(path);
(void)out.write(png->data(), png->size());
}
使用显示列表的方式有点类似opengl的 displaylist,即将绘制的结果存储起来,下次绘制的时候只需要调用drawcall绘制指令即可,不需要重新准备数据。只要不改变数据,画布的放大缩小等不需要重新更新数据绘制。
//使用SkPictureRecorder做记录
void picture(int width, int height void (*draw)(SkCanvas*))
{
SkPictureRecorder recorder;
auto recording_canvas = recorder.beginRecording(SkIntToScalar(width),
SkIntToScalar(height));
//调用普通的绘制方法即可;
draw(recording_canvas);
//将记录存储在canvas中
auto picture = recorder.finishRecordingAsPicture();
}
//使用回放;
void drawPicture(sk_sp<SkPicture> picture)
{
sk_sp<SkSurface> rasterSurface =
SkSurface::MakeRasterN32Premul(width, height);
SkCanvas* raster_canvas = rasterSurface->getCanvas();
//使用playback绘制
picture->playback(&raster_canvas);
}
SkSvg
通过canvas的方式输出Svg矢量图形,与绘制达到一样的效果(同样支持各种裁剪)
//将绘制结果保存到Svg文件中;
void drawSvg(int width,int height,void (*draw)(SkCanvas*),const char* path)
{
const auto wStream = std::make_shared<SkFILEWStream>(path);
auto canvas = SkSVGCanvas::Make(SkRect::MakeWH(width, height), wStream.get());
draw(canvas);
wStream->flush();
}
//读写svg文件并进行绘制(skia需要静态编译才能编译sksvg模块)
void drawSvg(skCavas* canvas,double x,double y,double width,double height)
{
SkFILEStream text_stream(image_file.toLocal8Bit());
SkTDArray<char> inData;
inData.setCount((int)text_stream.getLength());
size_t inLen = inData.count();
text_stream.read(inData.begin(), inLen);
text_stream.close();
const auto svg_stream = SkMemoryStream::MakeDirect(inData.begin(), inLen);
if(svg_stream == nullptr)
{
return { nullptr };
}
auto svg_dom = SkSVGDOM::Builder().make(*svg_stream);
if(svg_dom == nullptr)
{
return ;
}
canvas->save();
double widthRatio = 1;
double heightRatio = 1;
const double w=svg_dom->getRoot()->getWidth().value();
const double h= svg_dom->getRoot()->getHeight().value();
if(rect.isValid())
{
widthRatio = width /w ;
heightRatio = rect.height() / h;
}
canvas->translate(x,y);
canvas->scale(widthRatio, heightRatio);
svg_dom->render(canvas);
canvas->restore();
}
SkPDF 输出PDF矢量图形,PDF可以分页打印,也可以通过skPDFOjbect来扩展实现自定义信息的填写。SKPDF与SKXPS的使用方式基本一致;
void drawPDF(const char* pdf_name)
{
const auto wStream = std::make_shared<SkFILEWStream>(byte_array.constData());
SkPDF::Metadata metadata;
sk_sp<SkDocument> pdf_doc = SkPDF::MakeDocument(wStream.get(), metadata);
//
auto pdf_canvas =pdf_doc->beginPage(width, height);
//与正常方法一样调用绘制;
skPaint _paint;
pdf_canvas->drawLine(0,0,100,100,_paint);
pdf_doc->endPage();
wstream->flush();
wstream->close();
}