skia 之canvas

canvas是skia的核心部分,skia的逻都是围绕skcanvas对象来组织管理的,通过skcanvas可以指定不同的渲染上下文、draw call(绘制命令)、以及绘制状态管理(如绘图矩阵、操作栈等)

skcanvas的状态

skcanvasskpaint共同提供了sksurface和skbasedevice上绘制的状态,skcanvas保存了所有操作的堆栈,通过 saverestore这两个方法来管理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矢量图形,与绘制达到一样的效果(同样支持各种裁剪)

//将绘制结果保存到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();
}

Skia是一个开源的2D图形库,可以用于创建高质量的用户界面和图形应用程序。Skia支持多种平台,包括Linux。在Linux中使用Skia实现鼠标功能可以通过以下步骤实现: 1. 安装Skia库。Skia可以通过源代码下载和编译,也可以通过包管理器安装。在Ubuntu上,可以使用以下命令安装Skia: ``` sudo apt-get install libskia-dev ``` 2. 创建Skia应用程序。可以使用C++编写Skia应用程序,并使用Skia库中的函数绘制图形和处理输入事件。下面是一个示例程序,可以创建一个窗口并在窗口中绘制一个圆形: ``` #include <SkCanvas.h> #include <SkSurface.h> #include <GL/gl.h> #include <GL/glu.h> #include <GL/glut.h> void display() { SkImageInfo info = SkImageInfo::MakeN32Premul(512, 512); SkSurface* surface = SkSurface::MakeRaster(info); SkCanvas* canvas = surface->getCanvas(); canvas->clear(SK_ColorWHITE); SkPaint paint; paint.setColor(SK_ColorBLUE); canvas->drawCircle(256, 256, 128, paint); glDrawPixels(512, 512, GL_RGBA, GL_UNSIGNED_BYTE, surface->getCanvas()->getDevice()->accessPixels()); glutSwapBuffers(); } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); glutInitWindowSize(512, 512); glutCreateWindow("Skia Demo"); glutDisplayFunc(display); glutMainLoop(); return 0; } ``` 上面的程序使用Skia库创建一个512x512的窗口,并在窗口中绘制一个蓝色的圆形。程序使用OpenGL将绘制的图像显示在窗口中。 3. 实现鼠标功能。Skia库提供了处理输入事件的函数,可以使用这些函数来实现鼠标功能。例如,下面的代码可以在鼠标单击时输出鼠标位置: ``` void mouse(int button, int state, int x, int y) { if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) { printf("Mouse clicked at (%d, %d)\n", x, y); } } int main(int argc, char** argv) { // ... glutMouseFunc(mouse); // ... } ``` 上面的代码将鼠标单击事件绑定到`mouse`函数,当鼠标左键按下时,将输出鼠标位置。 通过以上步骤,就可以在Linux中使用Skia实现鼠标功能。需要注意的是,Skia是一个2D图形库,并不包括窗口管理和输入事件处理等功能,需要结合其他库一起使用。在Linux中,可以使用OpenGL和GLUT库来创建窗口和处理输入事件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

揽月凡尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值