skia 之canvas
canvas是skia的核心部分,skia的邏都是圍繞skcanvas對象來組織管理的,通過skcanvas可以指定不同的渲染上下文、draw call(繪制命令)、以及繪制狀態管理(如繪圖矩陣、操作棧等)
skcanvas的狀態
skcanvas和 skpaint共同提供了sksurface和skbasedevice上繪制的狀態,skcanvas保存了所有操作的堆棧,通過 save和restore這兩個方法來管理skcanvas的操作狀態信息。
Backend
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 Backend
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); }GPU Surface
必須有一個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()); }SkPicture
使用顯示列表的方式有點類似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矢量圖形,與繪制達到一樣的效果(同樣支持各種裁剪)
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(); }總結
以上是生活随笔為你收集整理的skia 之canvas的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 立体相册,你值得拥有
- 下一篇: c语言---输入输出函数 printf