# GTK# 中的 Cario 绘图 II 

> 原文： [http://zetcode.com/gui/gtksharp/drawingII/](http://zetcode.com/gui/gtksharp/drawingII/)

在 GTK# 编程教程的这一部分中，我们将继续使用 Cairo 库进行绘制。

## 甜甜圈

在下面的示例中，我们通过旋转一堆椭圆来创建复杂的形状。

`donut.cs`

```cs
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();
    }
}

```

在此示例中，我们创建一个甜甜圈。 形状类似于曲奇，因此称为甜甜圈。

```cs
cr.Translate(width/2, height/2);
cr.Arc(0, 0, 120, 0, 2*Math.PI);
cr.Stroke();

```

刚开始时有一个椭圆。

```cs
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();
}

```

旋转几圈后，有一个甜甜圈。

![Donut](img/7333de34f34dadc50be5d9614bd937f4.jpg)

图：多纳圈

## 渐变

在计算机图形学中，渐变是从浅到深或从一种颜色到另一种颜色的阴影的平滑混合。 在 2D 绘图程序和绘图程序中，渐变用于创建彩色背景和特殊效果以及模拟灯光和阴影。 （answers.com）

`gradients.cs`

```cs
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();
    }
}

```

在我们的示例中，我们绘制了三个具有三个不同渐变的矩形。

```cs
LinearGradient lg1 = new LinearGradient(0.0, 0.0, 350.0, 350.0);

```

在这里，我们创建一个线性渐变图案。 参数指定直线，沿着该直线绘制渐变。 在我们的情况下，这是一条垂直线。

```cs

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，我们再次开始用黑色绘图，直到结束。

![Gradients](img/9ebd7ae97d1fb9288b207cec225a0197.jpg)

图：渐变

## 泡泡

在以下示例中，我们创建一个粉扑效果。 该示例将显示一个不断增长的居中文本，该文本将从某个点逐渐淡出。 这是一个非常常见的效果，您经常可以在 Flash 动画中看到它。

`puff.cs`

```cs
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();
    }
}

```

该示例在窗口上创建一个逐渐增长和褪色的文本。

```cs
GLib.Timeout.Add(14, new GLib.TimeoutHandler(OnTimer));

```

每隔 14 毫秒调用一次`OnTimer()`方法。

```cs
bool OnTimer() 
{ 
    if (!timer) return false;

    darea.QueueDraw();
    return true;
}      

```

在`OnTimer()`方法中，我们在绘图区域上调用`QueueDraw()`方法，该方法会触发`ExposeEvent`。

```cs
int x = Allocation.Width / 2;
int y = Allocation.Height / 2;

```

中间点的坐标。

```cs
cr.SetSourceRGB(0.5, 0, 0);
cr.Paint();

```

我们将背景色设置为深红色。

```cs
size += 0.8;

```

每个周期，字体大小将增加 0.8 个单位。

```cs
if (size > 20) {
    alpha -= 0.01;
}

```

字体大小大于 20 后开始淡出。

```cs
TextExtents extents = cr.TextExtents("ZetCode");

```

我们得到了文本指标。

```cs
cr.MoveTo(x - extents.Width/2, y);

```

我们使用文本指标将文本放在窗口的中心。

```cs
cr.TextPath("ZetCode");
cr.Clip();

```

我们获取文本的路径，并为其设置当前的片段区域。

```cs
cr.Stroke();
cr.PaintWithAlpha(alpha);

```

我们绘制当前路径并考虑 alpha 值。

![Puff](img/564045fef6b305e99c17e5bb75f90568.jpg)

图：粉扑

## 反射

在下一个示例中，我们显示反射图像。 这种美丽的效果使人产生幻觉，好像图像在水中被反射一样。

`reflection.cs`

```cs
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();
    }
}

```

该示例显示了一个反射的城堡。

```cs
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();

```

背景充满了渐变的油漆。 涂料是从黑色到深灰色的平滑混合。

```cs
cr.Translate(0, 2 * imageHeight + gap);
cr.Scale(1, -1);

```

此代码翻转图像并将其转换为原始图像下方。 平移操作是必需的，因为缩放操作会使图像上下颠倒并向上平移图像。 要了解发生了什么，只需拍摄一张照片并将其放在桌子上即可。 现在翻转它。

```cs
cr.Rectangle(new Rectangle(border, imageHeight-i, imageWidth, 1));

i++;

cr.Clip();
cr.SetSource(surface, border, border);

cr.PaintWithAlpha(alpha-=step);
cr.ResetClip();

```

代码的关键部分。 我们使第二个图像透明。 但是透明度不是恒定的。 图像逐渐淡出。 这是通过`GradientPaint`实现的。

![Reflection](img/4340eb394adacbc4b1558235ea273f2b.jpg)

图：反射

## 等待

在此示例中，我们使用透明效果创建一个等待演示。 我们将绘制 8 条线，这些线将逐渐消失，从而产生一条线在移动的错觉。 这种效果通常用于通知用户，一项艰巨的任务正在幕后进行。 一个示例是通过互联网流式传输视频。

`waiting.cs`

```cs
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 值绘制八条线。

```cs
GLib.Timeout.Add(100, new GLib.TimeoutHandler(OnTimer));

```

我们使用计时器函数来创建动画。

```cs
private double [,] trs = new double[,] {
    { 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 },
    ...
};

```

这是此演示中使用的透明度值的二维数组。 有 8 行，每行一种状态。 8 行中的每行将连续使用这些值。

```cs
cr.LineWidth = 3;
cr.LineCap = LineCap.Round;

```

我们使线条更粗一些，以便更好地显示它们。 我们用带帽的线画线。

```cs
cr.SetSourceRGBA(0, 0, 0, trs[count%8, i]);

```

在这里，我们定义了一条线的透明度值。

```cs
cr.MoveTo(0.0, -10.0);
cr.LineTo(0.0, -40.0);
cr.Rotate(Math.PI/4);
cr.Stroke();

```

这些代码行将绘制八行中的每行。

![Waiting](img/7dc17f8024eedbeddc3f451c169663e6.jpg)

图：等待

在 GTK# 编程库的这一章中，我们使用 Cairo 库进行了一些更高级的绘制。