Cairo图形指南(4)

基本绘图

这一部分讲述如何绘制一些简单的图元,包括直线、填充与笔画操作、虚线、线端(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 () 函数重绘窗口区域。

Cairo图形指南(4)_第1张图片

描绘 (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 () 函数绘制的路径进行蓝色填充。

Cairo图形指南(4)_第2张图片

虚线 (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 const double dashed1 [ ] = { 4.0 , 1.0 } ;
        static int len1 = sizeof ( dashed1 ) / sizeof ( dashed1 [ 0 ] ) ;

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

        static const double 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 const double 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 部件上绘图,不再是窗口区域了。

Cairo图形指南(4)_第3张图片

线帽 (Line caps)

线帽是针对直线段的端点形状而言的,分为三种:

  • CAIRO_LINE_CAP_SQUARE
  • CAIRO_LINE_CAP_ROUND
  • CAIRO_LINE_CAP_BUTT

对应形状如下图所示:

Cairo图形指南(4)_第4张图片

同一条直线段,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 ) ;

这是三条竖线之一,用于表现线帽对线的长度的影响。

Cairo图形指南(4)_第5张图片

线的交合 (Line joins)

线的交合存在以下三种风格:

  • CAIRO_LINE_JOIN_MITER
  • CAIRO_LINE_JOIN_BEVEL
  • CAIRO_LINE_JOIN_ROUND

对应形状如下图所示。

Cairo图形指南(4)_第6张图片

#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 的矩形。

Cairo图形指南(4)_第7张图片

你可能感兴趣的:(JOIN,null,callback,图形,Signal,gtk)