- 图形学
- 图像处理
- 跨平台
【免费下载链接】SkiaSharp
SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.
本指南以 SkiaSharp 1.68.1 版本中SkiaSharp.Views.Gtk3.dll的 API 变更记录为线索,系统讲解SkiaSharp.Views.Gtk.SKDrawingArea这一 GTK3 绘图控件的完整用法:包括控件类签名、PaintSurface事件与OnPaintSurface两种绘制入口、CanvasSize的像素语义,以及它与 Cairo 渲染管线之间的桥接原理。阅读本文后,你将能够在 GtkSharp 3 桌面程序中直接嵌入 SkiaSharp 画布,绘制矢量图形、位图与文本,并借助类型转换扩展在 SkiaSharp 与 GTK 数据类型之间自由切换。
一、背景:1.68.1 版本中的新命名空间与新类型
在 SkiaSharp 1.68.1 的发布说明(API diff: SkiaSharp.Views.Gtk3.dll)中,SkiaSharp.Views.Gtk3.dll程序集版本由0.0.0.0变更为1.68.0.0,并新增了一个命名空间:
- 新命名空间:
SkiaSharp.Views.Gtk - 新类型:
SkiaSharp.Views.Gtk.SKDrawingArea
这意味着从该版本起,GTK3 桌面应用获得了官方的一等公民绘图控件SKDrawingArea。它继承自 GtkSharp 的标准绘图控件Gtk.DrawingArea,同时通过实现Atk.IImplementor、GLib.IWrapper、System.IDisposable等接口融入 GTK 对象体系,其源码实现位于 source/SkiaSharp.Views/SkiaSharp.Views.Gtk3/SKDrawingArea.cs。
public class SKDrawingArea : Gtk.DrawingArea, Atk.IImplementor, GLib.IWrapper, System.IDisposable { // constructors public SKDrawingArea (); // properties public SkiaSharp.SKSize CanvasSize { get; } // events public event System.EventHandler<SkiaSharp.Views.Desktop.SKPaintSurfaceEventArgs> PaintSurface; // methods protected override void Dispose (bool disposing); protected override bool OnDrawn (Cairo.Context cr); protected virtual void OnPaintSurface (SkiaSharp.Views.Desktop.SKPaintSurfaceEventArgs e); }该控件位于独立的程序集SkiaSharp.Views.Gtk3(RootNamespace为SkiaSharp.Views.Gtk),其工程文件见 source/SkiaSharp.Views/SkiaSharp.Views.Gtk3/SkiaSharp.Views.Gtk3.csproj。工程通过PackageReference引用GtkSharp 3.24.24.95,并依赖核心库SkiaSharp与桌面公共程序集SkiaSharp.Views.Desktop.Common,因此绘制事件参数SKPaintSurfaceEventArgs来自SkiaSharp.Views.Desktop命名空间。
二、项目引用与运行环境
要在自己的 GTK3 应用中启用该控件,需要准备三部分依赖:
- GtkSharp 3:通过 NuGet 包
GtkSharp 3.24.24.95引入(该版本号与 SkiaSharp.Views.Gtk3 工程锁定的版本一致)。 - SkiaSharp 核心库:提供
SKSurface、SKCanvas、SKImageInfo等绘图基础设施。 - SkiaSharp.Views.Gtk3 程序集:即本文主角,同时会连带引入
SkiaSharp.Views.Desktop.Common。
仓库中的示例工程 samples/Basic/Gtk3/SkiaSharpSample/SkiaSharpSample.csproj 展示了完整的引用方式:OutputType为WinExe,目标框架为net10.0,直接以ProjectReference引用上述程序集,并通过IncludeNativeAssets.SkiaSharp.targets(见 binding/IncludeNativeAssets.SkiaSharp.targets)将对应平台的libSkiaSharp原生库复制到输出目录。
运行还需要系统级 GTK3 开发库:
- macOS:
brew install gtk+3 - Ubuntu/Debian:
sudo apt-get install libgtk-3-dev - Fedora:
sudo dnf install gtk3-devel
Linux 下构建运行示例:
dotnet run --project SkiaSharpSample/SkiaSharpSample.csproj说明:GTK 3 不提供 GPU 加速的 SkiaSharp 视图,
SKDrawingArea走的是 CPU 软件渲染路径,这一点在 samples/Basic/Gtk3/README.md 中有明确说明。
三、SKDrawingArea 的两种绘制方式
SKDrawingArea源码的 XML 文档明确指出,在该画布上绘图有两种等价的方式(见 SKDrawingArea.cs):
- 订阅
PaintSurface事件:适用于大多数场景,尤其是 UI 代码与绘制逻辑分离时。 - 重写
OnPaintSurface虚方法:适用于子类化控件、封装自定义绘制逻辑的场景。
PaintSurface事件类型为EventHandler<SKPaintSurfaceEventArgs>,其中SKPaintSurfaceEventArgs定义在 source/SkiaSharp.Views/SkiaSharp.Views.Shared/SKPaintSurfaceEventArgs.cs,提供三个关键成员:
| 成员 | 类型 | 含义 |
|---|---|---|
Surface | SKSurface | 当前正在绘制的 SkiaSharp 表面,通过Surface.Canvas获得画布 |
Info | SKImageInfo | 表面信息,Info.Width/Info.Height即画布像素尺寸 |
RawInfo | SKImageInfo | 底层表面的原始图像信息(实际尺寸与颜色类型) |
事件方式的最小绘制模板:
myView.PaintSurface += (sender, e) => { var surface = e.Surface; var surfaceWidth = e.Info.Width; var surfaceHeight = e.Info.Height; var canvas = surface.Canvas; // draw on the canvas canvas.Flush (); };重写OnPaintSurface的方式:
protected override void OnPaintSurface (SKPaintSurfaceEventArgs e) { // call the base method base.OnPaintSurface (e); var surface = e.Surface; var surfaceWidth = e.Info.Width; var surfaceHeight = e.Info.Height; var canvas = surface.Canvas; // draw on the canvas canvas.Flush (); }[!IMPORTANT] 如果重写了
OnPaintSurface,必须调用base.OnPaintSurface(e),否则PaintSurface事件将不会被触发。这一点在控件源码的 XML 注释中被标记为重要注意事项(SKDrawingArea.cs),其实现也印证了调用关系:OnPaintSurface的默认实现就是PaintSurface?.Invoke(this, e)(SKDrawingArea.cs)。
两种方式都在 GTK 控件重绘时被调用,即由OnDrawn(Cairo.Context cr)驱动(下文详述),因此绘制是"按需触发"的——当窗口尺寸变化、被遮挡后重新显示或显式调用QueueDraw()时,GTK 会请求重绘,进而触发绘制回调。
四、CanvasSize 属性与像素密度语义
CanvasSize返回类型为SkiaSharp.SKSize,其文档注释说明了关键语义(SKDrawingArea.cs):
The canvas size may be different to the view size as a result of the current device's pixel density.
即画布尺寸(像素)可能与控件尺寸(逻辑单位)不一致,因为高分屏(HiDPI)下存在像素密度换算。源码实现为:
public SKSize CanvasSize => pix == null ? SKSize.Empty : new SKSize(pix.Width, pix.Height);其中pix是内部持有的 CairoImageSurface。当控件尚未完成首次分配(pix为null)时返回SKSize.Empty;否则返回底层像素缓冲的实际宽高。在示例 DrawingPage.cs 中可以看到处理这一差异的典型手法——用e.Info.Width / skiaView.AllocatedWidth计算缩放系数并canvas.Scale(sx, sy),从而让逻辑坐标与控件坐标保持一致。
五、渲染管线:从 GTK Drawn 事件到 SkiaSharp 表面
SKDrawingArea的核心是把 SkiaSharp 的绘制结果呈现到 GTK3 的 Cairo 上下文中。理解整个流程有助于调试像素格式、HiDPI 等问题。关键路径在OnDrawn(Cairo.Context cr)(SKDrawingArea.cs):
- 获取控件分配区域:通过
Allocation(Gdk.Rectangle)得到控件的实际宽高。 - 构建
SKImageInfo:new SKImageInfo(w, h, SKImageInfo.PlatformColorType, SKAlphaType.Premul),即使用平台默认颜色类型、预乘 alpha。 - 创建或复用绘图对象:
CreateDrawingObjects()按需创建 CairoImageSurface(Format.Argb32, w, h),并通过SKSurface.Create(imgInfo, pix.DataPtr, imgInfo.RowBytes)让 SkiaSharp 直接包装 Cairo 的像素缓冲——两者共享同一块内存,避免了拷贝(SKDrawingArea.cs)。当尺寸变化时,旧的 surface 与 pixbuf 会被释放重建。 - 触发绘制回调:用
SKAutoCanvasRestore包裹OnPaintSurface(new SKPaintSurfaceEventArgs(surface, imgInfo)),保证绘制结束后画布状态自动恢复(SKDrawingArea.cs)。 - 刷新与标记脏:
surface.Canvas.Flush()将 SkiaSharp 的绘制命令实际写入共享像素缓冲,随后pix.MarkDirty()通知 Cairo 缓冲内容已变化。 - 通道顺序修正(RGBA ↔ ARGB32):当颜色类型为
Rgba8888时,用SKSwizzle.SwapRedBlue交换 R/B 通道,使 SkiaSharp 的 RGBA 布局适配 Cairo 的 ARGB32 内存布局(SKDrawingArea.cs)。 - 呈现到 GTK 窗口:
cr.SetSourceSurface(pix, 0, 0)+cr.Paint()把 pixbuf 画到 GTK 的 Cairo 上下文,返回true表示事件已处理。
注意:
OnDrawn收到的Cairo.Context在方法返回后即被释放,因此不应在方法作用域之外持有该 Cairo 上下文的引用(SKDrawingArea.cs)。
Dispose(bool)重写则在释放托管资源时调用FreeDrawingObjects(),同步释放 Cairo pixbuf 与 SkiaSharp surface(SKDrawingArea.cs),避免原生资源泄漏。
六、类型转换扩展:GTKExtensions
与控件同程序集的 GTKExtensions.cs 提供了一组静态扩展方法,用于在 SkiaSharp 与 GTK/GDK 类型间互相转换,按用途可分为四类:
1. 几何类型转换
| 扩展方法 | 方向 | 说明 |
|---|---|---|
Point.ToSKPointI()/SKPointI.ToPoint() | Gdk.Point ↔ SKPointI | 点 |
Rectangle.ToSKRectI()/SKRectI.ToRect() | Gdk.Rectangle ↔ SKRectI | 矩形 |
Size.ToSKSizeI()/SKSizeI.ToSize() | Gdk.Size ↔ SKSizeI | 尺寸 |
2. 颜色转换
Color.ToSKColor():将Gdk.Color(分量范围为 0~65535)按65535f / 255f缩放为 0~255 的SKColor字节分量;SKColor.ToColor():反向转换回Gdk.Color。
3. 图像转换(SK → Gdk.Pixbuf)
SKPicture.ToPixbuf(SKSizeI dimensions):先把图片栅格化为指定尺寸的SKImage,再转 pixbuf;SKImage.ToPixbuf():创建Colorspace.Rgb、带 alpha、每像素 8 位的Pixbuf,通过SKPixmap读取像素;若颜色类型为Bgra8888,同样执行SwapRedBlue修正通道顺序;SKBitmap.ToPixbuf()与SKPixmap.ToPixbuf():经由SKImage.FromPixels中转。
4. 图像转换(Gdk.Pixbuf → SK)
Pixbuf.ToSKBitmap()/Pixbuf.ToSKImage():将 GDK 像素缓冲包装为 SkiaSharp 位图或图像;Pixbuf.ToSKPixmap(SKPixmap):直接把 pixbuf 像素数据拷贝进已有 pixmap,并在Bgra8888下交换 R/B。
这些扩展让开发者可以在 GTK 控件(如Gtk.Image、Gdk.Pixbuf相关控件)与 SkiaSharp 之间无缝搬运图像数据,是集成时的高频工具。
七、实战示例:在 GTK3 应用中嵌入 SKDrawingArea
仓库中的 Basic/Gtk3 示例是完整的可运行参考,包含"CPU 画布"与"自由绘画"两个页面,其 UI 布局由 Glade.glade文件定义、以嵌入资源方式编译(见 SkiaSharpSample.csproj)。由于SKDrawingArea不是 Glade 内置控件,示例采用"占位容器 + 代码注入"的模式:
// Load layout from Glade — provides the placeholder container var builder = MainWindow.LoadBuilder("SkiaSharpSample.CpuPage.glade"); var page = (Box)builder.GetObject("cpuPage"); // Insert SKDrawingArea (custom widget not available in Glade) var skiaView = new SKDrawingArea(); skiaView.PaintSurface += OnPaintSurface; page.PackStart(skiaView, true, true, 0);(完整代码见 samples/Basic/Gtk3/SkiaSharpSample/CpuPage.cs)
场景一:静态场景渲染
CpuPage演示了典型的"事件订阅 + SkiaSharp 绘制"流程(CpuPage.cs):
canvas.Clear(SKColors.White)清空画布;SKShader.CreateRadialGradient创建径向渐变背景,配合SKPaint { IsAntialias = true, Shader = shader }绘制全屏矩形;canvas.DrawCircle绘制一组半透明彩色圆;canvas.DrawText("SkiaSharp", ..., SKTextAlign.Center, font, textPaint)居中绘制文字。
这展示了在 GTK3 中完成"渐变 + 几何图形 + 文本"组合渲染的最小闭环。
场景二:交互式自由绘画
DrawingPage更进一步,演示了如何让SKDrawingArea响应鼠标输入并增量重绘(DrawingPage.cs):
- 注册事件掩码:
skiaView.AddEvents(EventMask.ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ScrollMask | EnterNotifyMask | LeaveNotifyMask),并订阅ButtonPressEvent、ButtonReleaseEvent、MotionNotifyEvent、ScrollEvent等 GTK 事件; - 记录笔画:按下时创建
SKPathBuilder并MoveTo,移动时LineTo,抬起时将路径快照存入笔画列表; - 按需重绘:每次状态变化调用
skiaView.QueueDraw()请求 GTK 重绘,触发OnDrawn→PaintSurface的渲染管线; - 绘制回放:在
OnPaintSurface中先清空画布,再逐条回放strokes列表中的路径,实现笔画在改色、调粗细后依然保留。
其中e.Info.Width / skiaView.AllocatedWidth的坐标缩放、滚动滚轮调整笔刷大小(ScrollDirection.Smooth与DeltaY判断)都是 GTK3 + SkiaSharp 集成中的实用细节。
八、版本与适用前提小结
| 项目 | 值 |
|---|---|
| 程序集 | SkiaSharp.Views.Gtk3.dll(1.68.1 起,程序集版本 1.68.0.0) |
| 命名空间 | SkiaSharp.Views.Gtk |
| 依赖 | GtkSharp 3.24.24.95、SkiaSharp、SkiaSharp.Views.Desktop.Common |
| 渲染方式 | CPU 软件渲染(GTK3 无 GPU 加速视图) |
| 参考示例 | samples/Basic/Gtk3 |
| 相关测试 | GTK 4 版本存在对应的SKDrawingAreaTest与GTKExtensionsTest(见 tests/SkiaSharp.Views.Gtk4.Tests),可作行为参考 |
从 API 变更记录到源码实现再到示例工程,SKDrawingArea构成了 GTK3 桌面应用接入 SkiaSharp 的完整链路:Gtk.DrawingArea负责窗口集成与事件分发,CairoImageSurface提供共享像素缓冲,SKSurface提供高性能的 2D 绘制 API,GTKExtensions则打通了两套类型系统。掌握这些机制后,你便可以在 GTK3 应用中自由使用 SkiaSharp 的完整绘图能力。
- 图形学
- 图像处理
- 跨平台
【免费下载链接】SkiaSharp
SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.
相关推荐
解读 SkiaSharp GTK 视图的完整 API 面:从 1.60.0 引入的 SKWidget 到现代 SKDrawingArea
解读 SkiaSharp GTK 视图的完整 API 面:从 1.60.0 引入的 SKWidget 到现代 SKDrawingArea 本篇基于 SkiaSh
图形学图像处理跨平台Synergy-core与云桌面集成:在远程桌面环境中的应用
Synergy core与云桌面集成:在远程桌面环境中的应用 Synergy core作为开源的跨平台键盘鼠标共享工具,正在成为云桌面和远程办公环境中的重要技术
桌面应用网络通信Anime.js桌面应用:Electron集成动画方案
Anime.js桌面应用:Electron集成动画方案 痛点:桌面应用动画开发的挑战 你是否在为桌面应用寻找高性能的动画解决方案?传统Web动画在Electro
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考