在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(); }
经过几次旋转,有一个甜甜圈。
在计算机图形学中,梯度是一种从明到暗或从一种颜色到另一种颜色的平滑混合色调。在二维绘图程序和绘画程序中,梯度被用来创造多彩的背景和特殊效果,以及模拟灯光和阴影。(answer.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处停止,在那里我们再次开始涂抹黑色,直到结束。
在下面的例子中,我们创建一个泡芙效果。这个例子将显示一个越来越居中的文本,它将从某个点逐渐淡出。这是一个非常常见的效果,你可以在Flas***中经常看到。
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));
每14ms调用一次 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);
我们画出当前的路径并考虑到阿尔法值。
在下一个例子中,我们展示了一个反射图像。这种美丽的效果使人产生错觉,仿佛图像被反射在水中。
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(); } }
我们用八个不同的α值画八条线。
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库做了一些更高级的绘图。