GTK+中文社区(gtk.awaysoft.com)

 找回密码
 马上加入

QQ登录

只需一步,快速开始

查看: 3075|回复: 0

[转]Cairo 图形指南 (9) —— 变换

[复制链接]
  • TA的每日心情
    奋斗
    2021-11-19 13:15
  • 签到天数: 20 天

    连续签到: 1 天

    [LV.4]偶尔看看III

    发表于 2011-2-3 02:19:23 | 显示全部楼层 |阅读模式
       

    这一篇讲述变换(Transformation)  仿射变换是由一些线性变换与平移构成的。线性变换可以写为单个矩阵的形式。旋转是让一个刚体绕一点运动的变换。缩放变换是让物体的形状扩大与减小,并且在各个方向上的缩放因子都相同。平移变换将每个点沿着指定的方向移动常量距离。错切对于给定轴线,沿垂直于它的方向对物体进行移动的变换,并且在轴线的一侧的移动距离大于另一侧。
    ——上述内容来自维基百科全书
    平移
    下面这个例子演示了一个简单的平移变换。
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    #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.6, 0.6, 0.6);
      cairo_rectangle(cr, 20, 20, 80, 50);
      cairo_stroke_preserve(cr);
      cairo_set_source_rgb(cr, 1, 1, 1);
      cairo_fill(cr);

      cairo_translate(cr, 100, 100);

      cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
      cairo_rectangle(cr, 20, 20, 80, 50);
      cairo_stroke_preserve(cr);
      cairo_set_source_rgb(cr, 1, 1, 1);
      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(window, "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, 230);
      gtk_widget_set_app_paintable(window, TRUE);

      gtk_widget_show_all(window);

      gtk_main();

      return 0;
    }



    这个例子先是画了个矩形,然后将它平移并绘制出平移结果。
    ?
    1
    cairo_translate(cr, 100, 100);



    cairo_translate() 函数可通过平移用于空间的原点来修改当前的变换矩阵。在这个示例中,是将原点沿水平和竖直方向平移了 100 个单位长度。

    旋转
    下面这个例子演示了一个简单的旋转变换。
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    #include <cairo.h>
    #include <gtk/gtk.h>
    #include <math.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.6, 0.6, 0.6);
      cairo_rectangle(cr, 20, 20, 80, 50);
      cairo_stroke_preserve(cr);
      cairo_set_source_rgb(cr, 1, 1, 1);
      cairo_fill(cr);

      cairo_translate(cr, 150, 100);
      cairo_rotate(cr, M_PI/2);

      cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
      cairo_rectangle(cr, 20, 20, 80, 50);
      cairo_stroke_preserve(cr);
      cairo_set_source_rgb(cr, 1, 1, 1);
      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(window, "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, 230);
      gtk_widget_set_app_paintable(window, TRUE);

      gtk_widget_show_all(window);

      gtk_main();

      return 0;
    }



    这个例子先是画了个矩形,然后对它进行了平移和旋转变换,并绘制出变换结果。
    ?
    1
    2
    cairo_translate(cr, 150, 100);
    cairo_rotate(cr, M_PI/2);



    首先对用户空间的原点进行平移,然后再围绕它旋转 180°。注意:旋转角度是弧度,而非角度。

    缩放
    下面这个例子演示了一个对象的缩放变换。(作者还真是沉闷阿,相同的句式连用了 n 次,这个可怜的矩形被折腾的痛苦不堪!)
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    #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_save(cr);
      cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
      cairo_rectangle(cr, 20, 30, 80, 50);
      cairo_stroke_preserve(cr);
      cairo_set_source_rgb(cr, 1, 1, 1);
      cairo_fill(cr);
      cairo_restore(cr);

      cairo_save(cr);
      cairo_translate(cr, 130, 30);
      cairo_scale(cr, 0.7, 0.7);

      cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
      cairo_rectangle(cr, 0, 0, 80, 50);
      cairo_stroke_preserve(cr);
      cairo_set_source_rgb(cr, 1, 1, 1);
      cairo_fill(cr);
      cairo_restore(cr);

      cairo_save(cr);
      cairo_translate(cr, 220, 30);
      cairo_scale(cr, 1.5, 1.5);

      cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
      cairo_rectangle(cr, 0, 0, 80, 50);
      cairo_stroke_preserve(cr);
      cairo_set_source_rgb(cr, 1, 1, 1);
      cairo_fill(cr);
      cairo_restore(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(window, "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), 360, 140);
      gtk_widget_set_app_paintable(window, TRUE);

      gtk_widget_show_all(window);

      gtk_main();

      return 0;
    }



    这次的例子是用指定的缩放因子,把初始的矩形变的小了点,然后又把它变的大了点。
    ?
    1
    2
    3
    cairo_save(cr);
    ...
    cairo_restore(cr);



    若对初始的矩形完成两次缩放操作,需要将初始的变换矩阵保存一下,这个可通过 cairo_save() 和 cairo_restore() 函数来实现。
    ?
    1
    2
    cairo_translate(cr, 130, 30);
    cairo_scale(cr, 0.7, 0.7);



    这里首先将用户空间的原点平移了一下,然后又开始用 0.7 作为因子进行缩放变换。


    错切
    在下面的示例中,我们来实现错切变换。
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    #include <cairo.h>
    #include <gtk/gtk.h>


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

      cr = gdk_cairo_create (widget->window);

      cairo_save(cr);
      cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
      cairo_rectangle(cr, 20, 30, 80, 50);
      cairo_stroke_preserve(cr);
      cairo_set_source_rgb(cr, 1, 1, 1);
      cairo_fill(cr);
      cairo_restore(cr);

      cairo_save(cr);
      cairo_translate(cr, 130, 30);
      cairo_matrix_init(&matrix,
          1.0, 0.5,
          0.0, 1.0,
          0.0, 0.0);

      cairo_transform (cr, &matrix);

      cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
      cairo_rectangle(cr, 0, 0, 80, 50);
      cairo_stroke_preserve(cr);
      cairo_set_source_rgb(cr, 1, 1, 1);
      cairo_fill(cr);
      cairo_restore(cr);

      cairo_save(cr);
      cairo_translate(cr, 220, 30);
      cairo_matrix_init(&matrix,
          1.0, 0.0,
          0.7, 1.0,
          0.0, 0.0);

      cairo_transform(cr, &matrix);

      cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
      cairo_rectangle(cr, 0, 0, 80, 50);
      cairo_stroke_preserve(cr);
      cairo_set_source_rgb(cr, 1, 1, 1);
      cairo_fill(cr);
      cairo_restore(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(window, "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), 360, 140);
      gtk_widget_set_app_paintable(window, TRUE);

      gtk_widget_show_all(window);

      gtk_main();

      return 0;
    }



    这份示例代码实现了两次错切变换。对于错切变换,没有特定的函数,必须使用矩阵来实现。
    ?
    1
    cairo_matrix_t matrix;



    这个 cairo_matrix 是存储仿射变换的数据结构。
    ?
    1
    2
    3
    4
    5
    6
    cairo_matrix_init(&matrix,
       1.0, 0.5,
       0.0, 1.0,
       0.0, 0.0);

    cairo_transform (cr, &matrix);



    这一变换的数学形式可表示为:


    ?
    1
    2
    3
    4
    5
    6
    cairo_matrix_init(&matrix,
        1.0, 0.0,
        0.7, 1.0,
        0.0, 0.0);

    cairo_transform(cr, &matrix);




    这一变换的数学形式可表示为:




    椭圆
    下面的这个例子,画了一个灰常复杂的形状,它由一串旋转的椭圆形成。
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    #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);

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

      cairo_set_line_width(cr, 0.5);
      cairo_translate(cr, width/2, height/2);
      cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
      cairo_stroke(cr);

      gint i;

      cairo_save(cr);
      for ( i = 0; i < 36; i++) {
          cairo_rotate(cr, i*M_PI/36);
          cairo_scale(cr, 0.3, 1);
          cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
          cairo_restore(cr);
          cairo_stroke(cr);
          cairo_save(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), 350, 250);

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

      gtk_main();

      return 0;
    }



    ?
    1
    2
    3
    cairo_translate(cr, width/2, height/2);
    cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
    cairo_stroke(cr);



    在 GTK+ 的窗口中间,绘制了一个圆,它是那些椭圆的边界圆。

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    cairo_save(cr);
    for ( i = 0; i < 36; i++) {
        cairo_rotate(cr, i*M_PI/36);
        cairo_scale(cr, 0.3, 1);
        cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
        cairo_restore(cr);
        cairo_stroke(cr);
        cairo_save(cr);
    }



    沿着边界圆画 36 个椭圆。椭圆可用圆的缩放变换而获得。旋转这个椭圆,这样就创建了一个有趣的形状。


    星星
    下面的示例绘制了一个又旋转又缩放的星星,可惜不会发光呃。
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    #include <cairo.h>
    #include <gtk/gtk.h>
    #include <math.h>

    int points[11][2] = {
        { 0, 85 },
        { 75, 75 },
        { 100, 10 },
        { 125, 75 },
        { 200, 85 },
        { 150, 125 },
        { 160, 190 },
        { 100, 150 },
        { 40, 190 },
        { 50, 125 },
        { 0, 85 }
    };


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

      static gdouble angle = 0;
      static gdouble scale = 1;
      static gdouble delta = 0.01;

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

      cr = gdk_cairo_create(widget->window);

      cairo_set_source_rgb(cr, 0, 0.44, 0.7);
      cairo_set_line_width(cr, 1);

      cairo_translate(cr, width / 2, height / 2 );
      cairo_rotate(cr, angle);
      cairo_scale(cr, scale, scale);

      gint i;

      for ( i = 0; i < 10; i++ ) {
          cairo_line_to(cr, points[0], points[1]);
      }

      cairo_close_path(cr);
      cairo_fill(cr);
      cairo_stroke(cr);

      if ( scale < 0.01 ) {
          delta = -delta;
      } else if (scale > 0.99) {
          delta = -delta;
      }

      scale += delta;
      angle += 0.01;

      cairo_destroy(cr);

      return FALSE;
    }

    static gboolean
    time_handler (GtkWidget *widget)
    {
      if (widget->window == NULL) return FALSE;
      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);

      
      gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
      gtk_window_set_title(GTK_WINDOW(window), "star");
      gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
      gtk_widget_set_app_paintable(window, TRUE);

      g_timeout_add(10, (GSourceFunc) time_handler, (gpointer) window);  

      gtk_widget_show_all(window);

      gtk_main();

      return 0;
    }



    在这个示例中,画了一颗星星,然后平移它,旋转它,缩放它。
    ?
    1
    2
    3
    cairo_translate(cr, width / 2, height / 2 );
    cairo_rotate(cr, angle);
    cairo_scale(cr, scale, scale



    先将星星平移到窗口中间,旋转它,缩放它。(作者还真不是一般的罗嗦)
    ?
    1
    2
    3
    4
    5
    6
    7
    for ( i = 0; i < 10; i++ ) {
        cairo_line_to(cr, points[0], points[1]);
    }

    cairo_close_path(cr);
    cairo_fill(cr);
    cairo_stroke(cr);



    画它!
    ?
    1
    2
    3
    4
    5
    if ( scale < 0.01 ) {
        delta = -delta;
    } else if (scale > 0.99) {
        delta = -delta;
    }



    这几行代码控制星星的缩放过程。

    原文地址:http://liyanrui.is-programmer.com/2010/4/6/cairo-transformation.16627.html


      
    *滑块验证:
    您需要登录后才可以回帖 登录 | 马上加入

    本版积分规则

    申请友链|Archiver|小黑屋|手机版|GTK+中文社区 ( 粤ICP备13080851号 )

    我要啦免费统计

    GMT+8, 2024-4-20 18:10 , Processed in 0.059621 second(s), 7 queries , Redis On.

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表