Cairo 圖形指南(1) —— 基本繪圖

這一部分講述如何繪製一些簡單的圖元,包括直線、填充與筆畫操作、虛線、線端(Cap)與線的交合等圖形的繪製方法。

直線段

直線段是非常基礎的矢量圖形對象。畫一條直線段,需要調用兩個函數:cairo_move_to() 函數,用於設置線段起點;cairo_line_to() 用於設定線段終點。

#include <cairo.h>
#include <gtk/gtk.h>

double coordx[100];
double coordy[100];

int count = 0;

static gboolean
on_expose_event(GtkWidget *widget,
                GdkEventExpose *event,
                gpointer data)
{
        cairo_t *cr;
       
        cr = gdk_cairo_create(widget->window);
       
        cairo_set_source_rgb(cr, 0, 0, 0);
        cairo_set_line_width (cr, 0.5);
       
        int i, j;
        for ( i =0; i <= count -1; i++){
                for ( j  = 0; j <= count -1; j++ ) {
                        cairo_move_to(cr, coordx[i], coordy[i]);
                        cairo_line_to(cr, coordx[j], coordy[j]);
                }
        }
       
        count = 0;
        cairo_stroke(cr);
        cairo_destroy(cr);
       
        return FALSE;
}

gboolean clicked(GtkWidget *widget, GdkEventButton *event,
                 gpointer user_data)
{
        if (event->button ==1){
                coordx[count] = event->x;
                coordy[count++] = event->y;
        }
       
        if (event->button ==3){
                gtk_widget_queue_draw(widget);
        }
       
        return TRUE;
}


int
main (int argc,char *argv[])
{
       
        GtkWidget *window;
       
        gtk_init(&argc, &argv);
       
        window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
       
        gtk_widget_add_events (window, GDK_BUTTON_PRESS_MASK);
       
        g_signal_connect(window, "expose-event",
                         G_CALLBACK(on_expose_event),NULL);
        g_signal_connect(window, "destroy",
                         G_CALLBACK(gtk_main_quit),NULL);
        g_signal_connect(window, "button-press-event",
                         G_CALLBACK(clicked),NULL);
       
        gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
        gtk_window_set_title(GTK_WINDOW(window),"lines");
        gtk_window_set_default_size(GTK_WINDOW(window),400,300);
        gtk_widget_set_app_paintable(window, TRUE);
       
        gtk_widget_show_all(window);
       
        gtk_main();
       
        return 0;
}

該示例會創建一個支持鼠標交互繪製直線段的 GTK+ 窗口。在窗口中使用鼠標左鍵隨便點幾下,每一次點擊時,光標位置的座標都會被記入長度爲 100 的數組;然後點擊鼠標右鍵,所有由鼠標左鍵點擊所得到的點會被彼此連接形成直線段;在窗口中再次點擊鼠標右鍵時,會對窗口繪圖區域進行清除。

下面對該示例程序代碼進行分析:


        cairo_set_source_rgb(cr,0,0, 0);
        cairo_set_line_width (cr, 0.5);

設置顏色爲黑色,線寬爲 0.5pt 爲參數,繪製直線段。


        int i, j;
        for ( i =0; i <= count -1; i++){
                for ( j  = 0; j <= count -1; j++ ) {
                        cairo_move_to(cr, coordx[i], coordy[i]);
                        cairo_line_to(cr, coordx[j], coordy[j]);
                }
        }

用 cairo_move_to() 和 cairo_line_to() 函數在 cr 中定義繪圖路徑 (path),連接 coordx[] 和 coordy[] 所記錄的每個點。


        cairo_stroke(cr);

cairo_stroke() 函數會將 cr 中的路徑繪製出來。


        g_signal_connect(window,"button-press-event",
                         G_CALLBACK(clicked),NULL);

設定 button-press-event 事件的回調函數爲 clicked ()


        if (event->button ==1){
                coordx[count] = event->x;
                coordy[count++] = event->y;
        }

clicked () 函數中,當鼠標左鍵點擊事件發生時,講光標所在位置的 x 和 y 座標分別記入數組coordx 和 coordy


        if (event->button ==3){
                gtk_widget_queue_draw(widget);
        }

 在 clicked () 函數中,當鼠標右鍵單擊時,調用 gtk_widget_queue_draw () 函數重繪窗口區域。



描繪 (Stroke) 與填充 (Fill)

描繪 (Stroke) 可以繪製形狀的輪廓,填充 (Fill) 則用於向形狀內部灌注顏色。 

#include <math.h>
#include <cairo.h>
#include <gtk/gtk.h>

static gboolean
on_expose_event (GtkWidget * widget,
                 GdkEventExpose * event, gpointer data)
{
        cairo_t *cr;

        cr = gdk_cairo_create (widget->window);

        int width, height;
        gtk_window_get_size (GTK_WINDOW (widget), &width, &height);
        cairo_set_line_width (cr, 9);

        cairo_set_source_rgb (cr, 0.69, 0.19, 0);
        cairo_arc (cr, width / 2, height / 2,
                   (width < height ? width : height) /2 -10,0,
                   2 * M_PI);
        cairo_stroke_preserve (cr);

        cairo_set_source_rgb (cr, 0.3, 0.4, 0.6);
        cairo_fill (cr);

        cairo_destroy (cr);

        return FALSE;
}

int
main (int argc,char *argv[])
{
        GtkWidget *window;

        gtk_init (&argc, &argv);

        window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

        g_signal_connect (G_OBJECT (window), "expose-event",
                          G_CALLBACK (on_expose_event),NULL);
        g_signal_connect (G_OBJECT (window), "destroy",
                          G_CALLBACK (gtk_main_quit),NULL);

        gtk_window_set_position (GTK_WINDOW (window),
                                 GTK_WIN_POS_CENTER);
        gtk_window_set_default_size (GTK_WINDOW(window),200,150);

        gtk_widget_set_app_paintable (window, TRUE);
        gtk_widget_show_all (window);

        gtk_main ();

        return 0;
}

這個示例繪製一個內部填充灰色的圓。

下面對代碼進行解析:


#include <math.h>

之所以引入這個頭文件,是因爲程序中使用了圓周率常量 M_PI。


        int width, height;
        gtk_window_get_size (GTK_WINDOW (widget), &width, &height);

獲取窗口的寬度與高度尺寸。程序中將使用這些值作爲繪製圓形的參考尺寸,以實現窗口尺寸變化時,所繪製的圓的尺寸也會相應變化。


        cairo_set_source_rgb (cr, 0.69, 0.19, 0);
        cairo_arc (cr, width / 2, height / 2,
                   (width < height ? width : height) /2 -10,0,
                   2 * M_PI);
        cairo_stroke_preserve (cr);

描繪圓的輪廓。這裏要注意一下 cairo_stroke_preserve () 函數與 cairo_stroke () 函數的區別(最好的辦法是用後者替換一下前者,看看程序執行效果)。cairo_stroke_preserve () 函數會將它繪製的路徑依然保存在 cairo 環境中,而 cairo_stroke () 所繪製的路徑,在繪製完成後,就從 cairo的環境中清除了。


        cairo_set_source_rgb (cr, 0.3, 0.4,0.6);
        cairo_fill (cr);

對使用 cairo_stroke_preserve () 函數繪製的路徑進行藍色填充。



虛線 (Dash)

每條線都可以用不同的虛線筆 (dash pen) 來畫。虛線模式是通過 cairo_set_dash () 函數來設定。模式類型通過一個數組來定義,數組中的值均爲正數,它們用於設置虛線的虛部分與實部分。數組的長度與偏移量可以在程序中設定。如果數組的長度 爲 0,虛線模式就是被禁止了,那所繪製的線是實線。如果數組長度爲 1,則對應着虛實均勻分佈的虛線模式。偏移量是用來設置在虛線的始端在一個虛線週期(包含一個實部單元和一個虛部單元)內的起始位置。

#include <cairo.h>
#include <gtk/gtk.h>

static gboolean
on_expose_event (GtkWidget * widget,
                 GdkEventExpose * event, gpointer data)
{
        cairo_t *cr;

        cr = gdk_cairo_create (widget->window);

        cairo_set_source_rgba (cr, 0, 0, 0,1);

        static constdouble dashed1[] ={4.0,1.0 };
        static int len1 =sizeof(dashed1) /sizeof(dashed1[0]);

        static constdouble dashed2[] ={4.0,10.0, 4.0 };
        static int len2 =sizeof(dashed2) /sizeof(dashed2[0]);

        static constdouble dashed3[] ={1.0};

        cairo_set_line_width (cr, 1.5);

        cairo_set_dash (cr, dashed1, len1, 0);

        cairo_move_to (cr, 40, 60);
        cairo_line_to (cr, 360, 60);
        cairo_stroke (cr);

        cairo_set_dash (cr, dashed2, len2, 10);

        cairo_move_to (cr, 40, 120);
        cairo_line_to (cr, 360, 120);
        cairo_stroke (cr);

        cairo_set_dash (cr, dashed3, 1, 0);

        cairo_move_to (cr, 40, 180);
        cairo_line_to (cr, 360, 180);
        cairo_stroke (cr);

        cairo_destroy (cr);

        return FALSE;
}


int
main (int argc,char *argv[])
{

        GtkWidget *window;
        GtkWidget *darea;

        gtk_init (&argc, &argv);

        window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

        darea = gtk_drawing_area_new ();
        gtk_container_add (GTK_CONTAINER (window), darea);

        g_signal_connect (darea, "expose-event",
                          G_CALLBACK (on_expose_event),NULL);
        g_signal_connect (window, "destroy",
                          G_CALLBACK (gtk_main_quit),NULL);

        gtk_window_set_position (GTK_WINDOW (window),
                                 GTK_WIN_POS_CENTER);
        gtk_window_set_default_size (GTK_WINDOW(window),400,300);

        gtk_widget_show_all (window);

        gtk_main ();

        return 0;
}

該示例演示了三種虛線模式的設置及繪製。

下面分析一下關鍵代碼。


        static constdouble dashed1[] ={4.0,1.0 };

設定第一條虛線的模式,它的實部是 4 個像素,虛部是 1 個像素。


        static int len1 =sizeof (dashed1) /sizeof(dashed1[0]);

計算數組 dashed1 的長度。


        cairo_set_dash (cr, dashed1, len1,0);

設置虛線模式。


        darea = gtk_drawing_area_new ();
        gtk_container_add (GTK_CONTAINER (window), darea);

這次,我們是在 drawing_area 部件上繪圖,不再是窗口區域了。



線帽 (Line caps)

線帽是針對直線段的端點形狀而言的,分爲三種:

  • CAIRO_LINE_CAP_SQUARE
  • CAIRO_LINE_CAP_ROUND
  • CAIRO_LINE_CAP_BUTT

對應形狀如下圖所示:

同一條直線段,CAIRO_LINE_CAP_SQUARE 線帽與 CAIRO_LINE_CAP_BUTT 線帽會導致直線段長度有所差別,前者會比後者長一個線寬尺寸。

#include <cairo.h>
#include <gtk/gtk.h>

static gboolean
on_expose_event (GtkWidget * widget,
                 GdkEventExpose * event, gpointer data)
{
        cairo_t *cr;

        cr = gdk_cairo_create (widget->window);

        cairo_set_source_rgba (cr, 0, 0, 0,1);
        cairo_set_line_width (cr, 10);

        cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
        cairo_move_to (cr, 40, 60);
        cairo_line_to (cr, 360, 60);
        cairo_stroke (cr);

        cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
        cairo_move_to (cr, 40, 150);
        cairo_line_to (cr, 360, 150);
        cairo_stroke (cr);

        cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
        cairo_move_to (cr, 40, 240);
        cairo_line_to (cr, 360, 240);
        cairo_stroke (cr);

        cairo_set_line_width (cr, 1.5);

        cairo_move_to (cr, 40, 40);
        cairo_line_to (cr, 40, 260);
        cairo_stroke (cr);

        cairo_move_to (cr, 360, 40);
        cairo_line_to (cr, 360, 260);
        cairo_stroke (cr);

        cairo_move_to (cr, 365, 40);
        cairo_line_to (cr, 365, 260);
        cairo_stroke (cr);

        cairo_destroy (cr);

        return FALSE;
}

該示例繪製三條具有不同線帽的直線段,同時也展示了不同線帽對線的長度的影響。

下面對關鍵代碼進行簡單分析:


        cairo_set_line_width (cr, 10);

設置線的寬度爲 10px。


        cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
        cairo_move_to (cr, 40, 150);
        cairo_line_to (cr, 360, 150);
        cairo_stroke (cr);

 畫了一條線帽爲 CAIRO_LINE_CAP_ROUND 的直線段。


        cairo_move_to (cr,40,40);
        cairo_line_to (cr, 40, 260);
        cairo_stroke (cr);

這是三條豎線之一,用於表現線帽對線的長度的影響。



線的交合 (Line joins)

線的交合存在以下三種風格:

  • CAIRO_LINE_JOIN_MITER
  • CAIRO_LINE_JOIN_BEVEL
  • CAIRO_LINE_JOIN_ROUND

對應形狀如下圖所示。

#include <cairo.h>
#include <gtk/gtk.h>

static gboolean
on_expose_event (GtkWidget * widget,
                 GdkEventExpose * event, gpointer data)
{
        cairo_t *cr;

        cr = gdk_cairo_create (widget->window);

        cairo_set_source_rgb (cr, 0.1, 0, 0);

        cairo_rectangle (cr, 30, 30, 100,100);
        cairo_set_line_width (cr, 14);
        cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
        cairo_stroke (cr);

        cairo_rectangle (cr, 160, 30, 100,100);
        cairo_set_line_width (cr, 14);
        cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL);
        cairo_stroke (cr);

        cairo_rectangle (cr, 100, 160, 100,100);
        cairo_set_line_width (cr, 14);
        cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
        cairo_stroke (cr);

        cairo_destroy (cr);

        return FALSE;
}

int
main (int argc,char *argv[])
{
        GtkWidget *window;
        GtkWidget *darea;

        gtk_init (&argc, &argv);

        window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

        darea = gtk_drawing_area_new ();
        gtk_container_add (GTK_CONTAINER (window), darea);

        g_signal_connect (darea, "expose-event",
                          G_CALLBACK (on_expose_event),NULL);
        g_signal_connect (window, "destroy",
                          G_CALLBACK (gtk_main_quit),NULL);

        gtk_window_set_position (GTK_WINDOW (window),
                                 GTK_WIN_POS_CENTER);
        gtk_window_set_default_size (GTK_WINDOW(window),300,280);

        gtk_widget_show_all (window);

        gtk_main ();

        return 0;
}
 

該示例採用不同的交合類型繪製了三個矩形。

下面對關鍵代碼進行簡單分析:


        cairo_rectangle (cr, 30, 30,100,100);
        cairo_set_line_width (cr, 14);
        cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
        cairo_stroke (cr);

繪製了一個線寬爲 14px,交合類型爲 CAIRO_LINE_JOIN_MITER 的矩形。



發佈了47 篇原創文章 · 獲贊 235 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章