SkCanvas
首先,阅读SkCanvasAPI
概述.
Skia
有多个接收SkCanvas
绘图命令的后端.每个后端都有创建SkCanvas
的独特方式.本页给出了每个示例:
光栅化
后端将绘画到可由Skia
或客户
管理的内存块
.
推荐用管理画布命令要绘画
内存对象的SkSurface
为Raster
和Ganesh
后端创建
画布.
#include "include/core/SkData.h"
#include "include/core/SkImage.h"
#include "include/core/SkStream.h"
#include "include/core/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 = SkPngEncoder::Encode(nullptr, img, {});
if (!png) { return; }
SkFILEWStream out(path);
(void)out.write(png->data(), png->size());
}
或,可显式
指定表面
的内存,而不是让Skia
管理它.
#include
#include "include/core/SkSurface.h"
std::vector<char> raster_direct(int width, int height, void (*draw)(SkCanvas*)) {
SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
size_t rowBytes = info.minRowBytes();
size_t size = info.getSafeSize(rowBytes);
std::vector<char> pixelMemory(size); // 分配内存
sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect( info, &pixelMemory[0], rowBytes);
SkCanvas* canvas = surface->getCanvas();
draw(canvas);
return pixelMemory;
}
GPU
表面必须有管理GPU
环境及纹理和字体
相关缓存的GrContext
对象.GrContexts
与OpenGL
环境或Vulkan
设备一一匹配
.
也即,使用相同的OpenGL
环境或Vulkan
设备渲染到的所有SkSurfaces
都应共享一个GrContext
.Skia
不会为你创建OpenGL
环境或Vulkan
设备.
在OpenGL
模式下,还假定在调用Skia
时,已为当前线程的当前环境
设置了正确的OpenGL
环境.
#include "include/gpu/GrDirectContext.h"
#include "include/gpu/gl/GrGLInterface.h"
#include "include/gpu/ganesh/gl/GrGLInterface.h"
#include "include/core/SkData.h"
#include "include/core/SkImage.h"
#include "include/core/SkStream.h"
#include "include/core/SkSurface.h"
void gl_example(int width, int height, void (*draw)(SkCanvas*), const char* path) {
// 已经创建了`OpenGL`上下文并绑定了它
sk_sp<const GrGLInterface> interface = nullptr;
//将`interface`保留为`null`会使`Skia`按特定平台方式,提取当前上下文的`OpenGL`函数的指针.或,可创建自己的`GrGLInterface`,并初化它,以附加到备用`OpenGL`实现或拦截`Skia`的`OpenGL`调用
sk_sp<GrDirectContext> context = GrDirectContexts::MakeGL(interface);
SkImageInfo info = SkImageInfo:: MakeN32Premul(width, height);
sk_sp<SkSurface> gpuSurface(
SkSurface::MakeRenderTarget(context.get(), skgpu::Budgeted::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 = SkPngEncoder::Encode(context.get(), img, {});
if (!png) { return; }
SkFILEWStream out(path);
(void)out.write(png->data(), png->size());
}
SKPDF
格式因为文档
必须包含多页,SkPDF
后端使用SkDocument
而不是SkSurface
.
#include "include/docs/SkPDFDocument.h"
#include "include/core/SkStream.h"
void skpdf(int width, int height, void (*draw)(SkCanvas*), const char* path) {
SkFILEWStream pdfStream(path);
auto pdfDoc = SkPDF::MakeDocument(&pdfStream);
SkCanvas* pdfCanvas = pdfDoc->beginPage(SkIntToScalar(width), SkIntToScalar(height));
draw(pdfCanvas);
pdfDoc->close();
}
SkPicture
后端使用SkPictureRecorder
而不是SkSurface
.
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkPicture.h"
#include "include/core/SkStream.h"
void picture(int width, int height, void (*draw)(SkCanvas*), const char* path) {
SkPictureRecorder recorder;
SkCanvas* recordingCanvas = recorder.beginRecording(SkIntToScalar(width), SkIntToScalar(height));
draw(recordingCanvas);
sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
SkFILEWStream skpStream(path);
// 用`viewer --skps PATH_TO_SKP --slide SKP_FILE`,打开SKP文件
picture->serialize(&skpStream);
}
空画布是忽略
所有绘图命令
且无操作
的画布.
#include "include/utils/SkNullCanvas.h"
void null_canvas_example(int, int, void (*draw)(SkCanvas*), const char*) {
std::unique_ptr<SkCanvas> nullCanvas = SkMakeNullCanvas();
draw(nullCanvas.get()); // 闲着
}
SkXPS
(仍在实验阶段)把SkXPS
画布写入XPS
文档.
#include "include/core/SkDocument.h"
#include "include/core/SkStream.h"
#ifdef SK_BUILD_FOR_WIN
void skxps(IXpsOMObjectFactory* factory; int width, int height, void (*draw)(SkCanvas*), const char* path) {
SkFILEWStream xpsStream(path);
sk_sp<SkDocument> xpsDoc = SkDocument::MakeXPS(&pdfStream, factory);
SkCanvas* xpsCanvas = xpsDoc->beginPage(SkIntToScalar(width), SkIntToScalar(height));
draw(xpsCanvas);
xpsDoc->close();
}
#endif
SkSVG
(仍在实验阶段)把SkSVG
画布写入SVG
文档.
#include "include/core/SkStream.h"
#include "include/svg/SkSVGCanvas.h"
#include "SkXMLWriter.h"
void sksvg(int width, int height, void (*draw)(SkCanvas*), const char* path) {
SkFILEWStream svgStream(path);
std::unique_ptr<SkXMLWriter> xmlWriter( new SkXMLStreamWriter(&svgStream));
SkRect bounds = SkRect::MakeIWH(width, height);
std::unique_ptr<SkCanvas> svgCanvas = SkSVGCanvas::Make(bounds, xmlWriter.get());
draw(svgCanvas.get());
}
例
要试用此代码,请使用新的单元测试
,并把这些函数包装
在一起:
#include "include/core/SkCanvas.h"
#include "include/core/SkPath.h"
#include "tests/Test.h"
void example(SkCanvas* canvas) {
const SkScalar scale = 256.0f;
const SkScalar R = 0.45f * scale;
const SkScalar TAU = 6.2831853f;
SkPath path;
for (int i = 0; i < 5; ++i) {
SkScalar theta = 2 * i * TAU / 5;
if (i == 0) {
path.moveTo(R * cos(theta), R * sin(theta));
} else {
path.lineTo(R * cos(theta), R * sin(theta));
}
}
path.close();
SkPaint p;
p.setAntiAlias(true);
canvas->clear(SK_ColorWHITE);
canvas->translate(0.5f * scale, 0.5f * scale);
canvas->drawPath(path, p);
}
DEF_TEST(FourBackends, r) {
raster( 256, 256, example, "out_raster.png" );
gl_example( 256, 256, example, "out_gpu.png" );
skpdf( 256, 256, example, "out_skpdf.pdf" );
picture( 256, 256, example, "out_picture.skp");
}
SkCanvas
是Skia
的绘图环境
.它知道直接在哪绘图
(即屏幕外像素
的屏幕
位置),并维护一堆矩阵和剪切
.
但注意,与其他API
(如postscript,cairo
或awt
)中的类似环境不同,Skia
不会在环境
中存储其他(如颜色,笔大小
)绘图属性
.相反,在每次
绘画调用中,通过SkPaint
显式指定它们.
确切地说,画笔
而不是画布
,描述绘画
的颜色和风格
.
首先,可能想要擦除
整个画布.可绘画巨大
矩形来完成,但有更简单
方法.
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas->drawPaint(paint);
}
这(当然,尊重当前剪切)指定
绘画用的颜色或着色器
(和xfermode
),并填充整个画布
.如果画笔
中有个着色器
,则它也会遵循画布
上的当前矩阵
(见SkShader
).
如果(用可选的xfermode
)只想绘画
个颜色,你可直接调用drawColor()
,这样就不必赋值
画笔了.
void draw(SkCanvas* canvas) {
canvas->drawColor(SK_ColorWHITE);
}
所有其他绘画API
类似,都以画笔
参数结尾.
在某些
调用中,传递画笔
的指针,而不是引用
.这时,paint
参数可能为null
.其他时候,必需要有paint
参数.
每当你在Skia
中画笔
某些内容时,想要指定颜色
,或如何与背景
混合,或使用的风格或字体
,都可在画笔
中指定这些属性.
与SkCanvas
不同,画笔
不维护内部状态栈
(即画笔上不保存/恢复
).然而,画笔
是轻量
的,因此客户可创建和维护
多个用途不同的画笔
对象.
从画布
状态中,分解出所有这些颜色和风格
属性,并转换为(多个
)画笔
对象中,因为只需维护矩阵
和剪切
设置栈,可更加高效的保存/恢复画布
.
可显示三个不同
画笔,每种画笔
都按以不同的风格设置.现在,调用者
可自由地混合
这些画笔
,可原样使用它们,也可在绘图
过程中修改它们.
除了颜色,描边和文本值
等简单属性外,画笔还支持特效
.它是绘图管线
不同方面的子类
,引用画笔
时(每个子类
都按引用计数
),调用它以覆盖
绘图管线的某些部分
.
如,要用渐变
而不是单色
画笔,请为画笔
指定SkShader
.
现在,使用该画笔的内容都使用调用MakeLinear()
中指定的渐变画笔
.返回的着色器
对象是引用计数
的.每当赋值着色
器等特效对象
给画笔
时,画笔都会增加引用计数
.
为了平衡,上面赋值给画笔后,在着色器
上马上调用unref()
.现在,画笔
是该着色器的唯一"物主"
,当画笔
出域或为其赋值另一个着色器(或null
)时,它自动在着色器
上调用unref()
.
有6种类型的特效可赋值
给画笔:
1,SkPathEffect
,在生成α
掩码(如破折号)修改几何路径
2,SkRasterizer
,合成
自定义掩码图层(如阴影
)
3,SkMaskFilter
,在着色及绘画前修改α
掩码(如模糊)
4,SkShader
:如渐变
(线性,径向,扫描
),位图模式(夹,重复,镜像
)
5,SkColorFilter
,在混合
前修改源颜色
(如颜色矩阵
)
6,SkBlendMode
:如porter-duff
传输模式,混合模式等
画笔
还有SkTypeface
的引用.字体
表示来测量和绘画
文本的特定字体风格
.画笔不仅可绘画
文本,还可测量
文本.
paint.measureText(...);
paint.getTextBounds(...);
paint.textToGlyphs(...);
paint.getFontMetrics(...);
SkBlend
模式以下示例演示了Skia
的所有标准混合模式
.此例中,源
是有水平α
渐变的纯洋红色
,目标为有垂直α
渐变的纯青色
.
SkShader
定义了几个着色器(除了已提到的线性渐变):
1,位图
着色器
2,径向渐变
着色器
3,两点锥形
渐变着色器
4,扫描
渐变着色器
5,分形
噪声着色器
6,湍流
噪声着色器
7,合成
着色器
SkMask
过滤器1,模糊
掩码过滤器
SkColor
过滤器ColorMatrix
颜色过滤器
颜色表
颜色过滤器
SkPathEffect
1,SkPath2DPathEffect
:用矩阵
定义晶格,标记指定路径
以填充
形状.
2,SkLine2DPathEffect
:路径是要描边
而不是要填充
的直线路径的SkPath2DPathEffect
的特例.
3,SkPath1DPathEffect
:通过复制指定路径
沿着画笔
路径,创建
类似破折号
的特效.
4,SkCornerPathEffect
:一个可变形尖角
的特效(如圆角).
5,SkDashPathEffect
:实现破折号
的路径特效.
6,SkDiscretePathEffect
:此路径特效把路径切成离散段
,并随机替换它们.
7,SkComposePathEffect
:先应用内部pathEffect
,再外部pathEffect
(即outer(inner(path))
)的pathEffect
特效.
8,SkSumPathEffect
:按如下应用两个特效的pathEffect
特效:
sequence (first(path) + second(path)).