在 GTK# 中使用 Cairo 绘图 II
最后修改于 2023 年 10 月 18 日
在本 GTK# 编程教程中,我们将继续使用 Cairo 库进行绘图。
甜甜圈
在下面的例子中,我们通过旋转一系列椭圆来创建一个复杂的形状。
using Gtk;
using Cairo;
using System;
class SharpApp : Window {
public SharpApp() : base("Donut")
{
SetDefaultSize(350, 250);
SetPosition(WindowPosition.Center);
DeleteEvent += delegate { Application.Quit(); };
DrawingArea darea = new DrawingArea();
darea.ExposeEvent += OnExpose;
Add(darea);
ShowAll();
}
void OnExpose(object sender, ExposeEventArgs args)
{
DrawingArea area = (DrawingArea) sender;
Cairo.Context cr = Gdk.CairoHelper.Create(area.GdkWindow);
cr.LineWidth = 0.5;
int width, height;
width = Allocation.Width;
height = Allocation.Height;
cr.Translate(width/2, height/2);
cr.Arc(0, 0, 120, 0, 2*Math.PI);
cr.Stroke();
cr.Save();
for (int i = 0; i < 36; i++) {
cr.Rotate( i*Math.PI/36);
cr.Scale(0.3, 1);
cr.Arc(0, 0, 120, 0, 2*Math.PI);
cr.Restore();
cr.Stroke();
cr.Save();
}
((IDisposable) cr.Target).Dispose();
((IDisposable) cr).Dispose();
}
public static void Main()
{
Application.Init();
new SharpApp();
Application.Run();
}
}
在这个例子中,我们创建一个甜甜圈。 形状类似于饼干,因此得名甜甜圈。
cr.Translate(width/2, height/2); cr.Arc(0, 0, 120, 0, 2*Math.PI); cr.Stroke();
首先是一个椭圆。
for (int i = 0; i < 36; i++) {
cr.Rotate( i*Math.PI/36);
cr.Scale(0.3, 1);
cr.Arc(0, 0, 120, 0, 2*Math.PI);
cr.Restore();
cr.Stroke();
cr.Save();
}
经过几次旋转后,就有了甜甜圈。
渐变
在计算机图形学中,渐变是从亮到暗或从一种颜色到另一种颜色的平滑混合。 在 2D 绘图程序和绘图程序中,渐变用于创建色彩鲜艳的背景和特殊效果,以及模拟光线和阴影。(answers.com)
using Gtk;
using Cairo;
using System;
class SharpApp : Window {
public SharpApp() : base("Gradients")
{
SetDefaultSize(340, 390);
SetPosition(WindowPosition.Center);
DeleteEvent += delegate { Application.Quit(); };
DrawingArea darea = new DrawingArea();
darea.ExposeEvent += OnExpose;
Add(darea);
ShowAll();
}
void OnExpose(object sender, ExposeEventArgs args)
{
DrawingArea area = (DrawingArea) sender;
Cairo.Context cr = Gdk.CairoHelper.Create(area.GdkWindow);
LinearGradient lg1 = new LinearGradient(0.0, 0.0, 350.0, 350.0);
int count = 1;
for (double j=0.1; j<1.0; j+= 0.1) {
if (Convert.ToBoolean(count % 2)) {
lg1.AddColorStop(j, new Color(0, 0, 0, 1));
} else {
lg1.AddColorStop(j, new Color(1, 0, 0, 1));
}
count++;
}
cr.Rectangle(20, 20, 300, 100);
cr.Pattern = lg1;
cr.Fill();
LinearGradient lg2 = new LinearGradient(0.0, 0.0, 350.0, 0);
count = 1;
for (double i=0.05; i<0.95; i+= 0.025) {
if (Convert.ToBoolean(count % 2)) {
lg2.AddColorStop(i, new Color(0, 0, 0, 1));
} else {
lg2.AddColorStop(i, new Color(0, 0, 1, 1));
}
count++;
}
cr.Rectangle(20, 140, 300, 100);
cr.Pattern = lg2;
cr.Fill();
LinearGradient lg3 = new LinearGradient(20.0, 260.0, 20.0, 360.0);
lg3.AddColorStop(0.1, new Color (0, 0, 0, 1) );
lg3.AddColorStop(0.5, new Color (1, 1, 0, 1) );
lg3.AddColorStop(0.9, new Color (0, 0, 0, 1) );
cr.Rectangle(20, 260, 300, 100);
cr.Pattern = lg3;
cr.Fill();
lg1.Destroy();
lg2.Destroy();
lg3.Destroy();
((IDisposable) cr.Target).Dispose ();
((IDisposable) cr).Dispose ();
}
public static void Main()
{
Application.Init();
new SharpApp();
Application.Run();
}
}
在我们的例子中,我们用三种不同的渐变绘制三个矩形。
LinearGradient lg1 = new LinearGradient(0.0, 0.0, 350.0, 350.0);
在这里,我们创建一个线性渐变模式。 参数指定了我们绘制渐变的线条。 在我们的例子中,它是一条垂直线。
LinearGradient lg3 = new LinearGradient(20.0, 260.0, 20.0, 360.0); lg3.AddColorStop(0.1, new Color (0, 0, 0, 1) ); lg3.AddColorStop(0.5, new Color (1, 1, 0, 1) ); lg3.AddColorStop(0.9, new Color (0, 0, 0, 1) );
我们定义颜色停止点以生成我们的渐变模式。 在这种情况下,渐变是黑色和黄色之间的混合。 通过添加两个黑色和一个黄色停止点,我们创建一个水平渐变模式。 这些停止点实际上意味着什么? 在我们的例子中,我们从黑色开始,它将停止在大小的 1/10 处。 然后我们开始逐渐涂上黄色,它将在形状的中心达到顶峰。 黄色停止在大小的 9/10 处,我们再次开始涂上黑色,直到结束。
喷雾
在下面的例子中,我们创建一个膨胀效果。 该示例将显示一个不断增长的居中文本,该文本将从某个点逐渐淡出。 这是一种非常常见的效果,您经常可以在 Flash 动画中看到。
using Gtk;
using Cairo;
using System;
class SharpApp : Window {
private bool timer = true;
private double alpha = 1.0;
private double size = 1.0;
private DrawingArea darea;
public SharpApp() : base("Puff")
{
SetDefaultSize(350, 200);
SetPosition(WindowPosition.Center);
DeleteEvent += delegate { Application.Quit(); };
GLib.Timeout.Add(14, new GLib.TimeoutHandler(OnTimer));
darea = new DrawingArea();
darea.ExposeEvent += OnExpose;
Add(darea);
ShowAll();
}
bool OnTimer()
{
if (!timer) return false;
darea.QueueDraw();
return true;
}
void OnExpose(object sender, ExposeEventArgs args)
{
DrawingArea area = (DrawingArea) sender;
Cairo.Context cr = Gdk.CairoHelper.Create(area.GdkWindow);
int x = Allocation.Width / 2;
int y = Allocation.Height / 2;
cr.SetSourceRGB(0.5, 0, 0);
cr.Paint();
cr.SelectFontFace("Courier", FontSlant.Normal, FontWeight.Bold);
size += 0.8;
if (size > 20) {
alpha -= 0.01;
}
cr.SetFontSize(size);
cr.SetSourceRGB(1, 1, 1);
TextExtents extents = cr.TextExtents("ZetCode");
cr.MoveTo(x - extents.Width/2, y);
cr.TextPath("ZetCode");
cr.Clip();
cr.Stroke();
cr.PaintWithAlpha(alpha);
if (alpha <= 0) {
timer = false;
}
((IDisposable) cr.Target).Dispose();
((IDisposable) cr).Dispose();
}
public static void Main()
{
Application.Init();
new SharpApp();
Application.Run();
}
}
该示例在窗口上创建一个不断增长和淡出的文本。
GLib.Timeout.Add(14, new GLib.TimeoutHandler(OnTimer));
每 14 毫秒调用 OnTimer 方法。
bool OnTimer()
{
if (!timer) return false;
darea.QueueDraw();
return true;
}
在 OnTimer 方法中,我们在绘图区域上调用 QueueDraw 方法,这会触发 ExposeEvent。
int x = Allocation.Width / 2; int y = Allocation.Height / 2;
中点的坐标。
cr.SetSourceRGB(0.5, 0, 0); cr.Paint();
我们将背景颜色设置为深红色。
size += 0.8;
每个周期,字体大小将增加 0.8 个单位。
if (size > 20) {
alpha -= 0.01;
}
淡出开始于字体大小大于 20 之后。
TextExtents extents = cr.TextExtents("ZetCode");
我们获取文本指标。
cr.MoveTo(x - extents.Width/2, y);
我们使用文本指标将文本居中于窗口。
cr.TextPath("ZetCode");
cr.Clip();
我们获取文本的路径,并将当前剪切区域设置为该路径。
cr.Stroke(); cr.PaintWithAlpha(alpha);
我们绘制当前路径并考虑 alpha 值。
反射
在下一个例子中,我们展示了一个反射图像。 这种美丽的效果产生了一种幻觉,好像图像被反射在水中。
using Gtk;
using Cairo;
using System;
class SharpApp : Window {
private ImageSurface surface;
private int imageWidth;
private int imageHeight;
private int gap;
private int border;
public SharpApp() : base("Reflection")
{
try {
surface = new ImageSurface("slanec.png");
} catch {
Console.WriteLine("File not found");
Environment.Exit(1);
}
imageWidth = surface.Width;
imageHeight = surface.Height;
gap = 40;
border = 20;
SetDefaultSize(300, 350);
SetPosition(WindowPosition.Center);
DeleteEvent += delegate { Application.Quit(); };
DrawingArea darea = new DrawingArea();
darea.ExposeEvent += OnExpose;
Add(darea);
ShowAll();
}
void OnExpose(object sender, ExposeEventArgs args)
{
DrawingArea area = (DrawingArea) sender;
Cairo.Context cr = Gdk.CairoHelper.Create(area.GdkWindow);
int width = Allocation.Width;
int height = Allocation.Height;
LinearGradient lg = new LinearGradient(width/2, 0, width/2, height*3);
lg.AddColorStop(0, new Color(0, 0, 0, 1));
lg.AddColorStop(height, new Color(0.2, 0.2, 0.2, 1));
cr.Pattern = lg;
cr.Paint();
cr.SetSourceSurface(surface, border, border);
cr.Paint();
double alpha = 0.7;
double step = 1.0 / imageHeight;
cr.Translate(0, 2 * imageHeight + gap);
cr.Scale(1, -1);
int i = 0;
while(i < imageHeight) {
cr.Rectangle(new Rectangle(border, imageHeight-i, imageWidth, 1));
i++;
cr.Clip();
cr.SetSource(surface, border, border);
cr.PaintWithAlpha(alpha-=step);
cr.ResetClip();
}
((IDisposable) cr.Target).Dispose();
((IDisposable) cr).Dispose();
}
public static void Main()
{
Application.Init();
new SharpApp();
Application.Run();
}
}
该示例显示了一个反射的城堡。
LinearGradient lg = new LinearGradient(width/2, 0, width/2, height*3); lg.AddColorStop(0, new Color(0, 0, 0, 1)); lg.AddColorStop(height, new Color(0.2, 0.2, 0.2, 1)); cr.Pattern = lg; cr.Paint();
背景填充有渐变色。 渐变是从黑色到深灰色的平滑混合。
cr.Translate(0, 2 * imageHeight + gap); cr.Scale(1, -1);
此代码翻转图像并将其平移到原始图像下方。 平移操作是必要的,因为缩放操作会使图像上下颠倒并将图像向上平移。 要理解发生的事情,只需拿一张照片并将其放在桌子上。 现在翻转它。
cr.Rectangle(new Rectangle(border, imageHeight-i, imageWidth, 1));
i++;
cr.Clip();
cr.SetSource(surface, border, border);
cr.PaintWithAlpha(alpha-=step);
cr.ResetClip();
代码的关键部分。 我们使第二张图像透明。 但透明度不是恒定的。 图像逐渐淡出。 这是通过 GradientPaint 实现的。
等待
在这个例子中,我们使用透明度效果来创建一个等待演示。 我们绘制 8 条线,这些线将逐渐淡出,从而产生一条线正在移动的幻觉。 这种效果经常用于通知用户,后台正在进行一项耗时的任务。 例如,通过互联网流式传输视频。
using Gtk;
using Cairo;
using System;
class SharpApp : Window {
private double [,] trs = new double[,] {
{ 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 },
{ 1.0, 0.0, 0.15, 0.30, 0.5, 0.65, 0.8, 0.9 },
{ 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65, 0.8 },
{ 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65},
{ 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5 },
{ 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3 },
{ 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15 },
{ 0.15, 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, }
};
private short count = 0;
private DrawingArea darea;
public SharpApp() : base("Waiting")
{
SetDefaultSize(250, 150);
SetPosition(WindowPosition.Center);
DeleteEvent += delegate { Application.Quit(); };
GLib.Timeout.Add(100, new GLib.TimeoutHandler(OnTimer));
darea = new DrawingArea();
darea.ExposeEvent += OnExpose;
Add(darea);
ShowAll();
}
bool OnTimer()
{
count += 1;
darea.QueueDraw();
return true;
}
void OnExpose(object sender, ExposeEventArgs args)
{
DrawingArea area = (DrawingArea) sender;
Cairo.Context cr = Gdk.CairoHelper.Create(area.GdkWindow);
cr.LineWidth = 3;
cr.LineCap = LineCap.Round;
int width, height;
width = Allocation.Width;
height = Allocation.Height;
cr.Translate(width/2, height/2);
for (int i = 0; i < 8; i++) {
cr.SetSourceRGBA(0, 0, 0, trs[count%8, i]);
cr.MoveTo(0.0, -10.0);
cr.LineTo(0.0, -40.0);
cr.Rotate(Math.PI/4);
cr.Stroke();
}
((IDisposable) cr.Target).Dispose();
((IDisposable) cr).Dispose();
}
public static void Main()
{
Application.Init();
new SharpApp();
Application.Run();
}
}
我们绘制八条线,每条线有八个不同的 alpha 值。
GLib.Timeout.Add(100, new GLib.TimeoutHandler(OnTimer));
我们使用定时器函数来创建动画。
private double [,] trs = new double[,] {
{ 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 },
...
};
这是一个二维的透明度值数组,用于此演示。 有 8 行,每行代表一个状态。 这 8 条线中的每一条将连续使用这些值。
cr.LineWidth = 3; cr.LineCap = LineCap.Round;
我们使线条稍粗,以便它们更可见。 我们用圆角绘制线条。
cr.SetSourceRGBA(0, 0, 0, trs[count%8, i]);
在这里,我们定义一条线的透明度值。
cr.MoveTo(0.0, -10.0); cr.LineTo(0.0, -40.0); cr.Rotate(Math.PI/4); cr.Stroke();
这些代码行将绘制八条线中的每一条。
在本 GTK# 编程库的这一章中,我们使用 Cairo 库进行了一些更高级的绘图。