お知らせ

  • 利用規約を守って投稿してください。また、よくある質問および投稿の手引きも参照してください。
  • メッセージの投稿にはアカウントが必要です。未登録の方は、ユーザ登録ページからアカウントを作成することができます。

#1 2014-12-26 22:28:24

danjiro
新しいメンバ
登録日: 2014-12-26

cairo の描画をウィンドウに反映させる方法

はじめまして。danjiro ともうします。

今、Raspberry Pi 用の「9va-pi」というアニメーションエディタを、Ubuntu に移植しているのですが、
cairo の描画のふるまいが、Raspbian(ラズベリーパイのLinux OS) と、 Ubuntu で異なるので、困っています。
以下の問題について、ご存じの方はヒントでもいただければ幸いです。

アニメーションエディタで、図形の入力や選択などを行うときに、編集画面全体を毎回書き直していると遅くなるため、
マウスイベントに従って、ガイドとなる線だけ先に描画する方法をとっています。Raspbian だとこれがうまく行くのですが、
Ubuntu だと、マウス移動時には全く描画されず、ウィンドウの位置をかえると、それまでためていた図形をまとめて描画します。

マウスイベントに応じて、描画のタイミングをコントロールしたいのですが、
cairoの描画をただちにウィンドウに反映させる方法はないのでしょうか。

サンプルプログラムを示します。

コード:

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

GtkWidget *window;

gint cb_motion_notify_event( GtkWidget *widget, GdkEventMotion *event, gpointer user_data )
{
    cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(window));
    cairo_set_line_width(cr, 5.0); //線の太さ
    cairo_rectangle(cr, event->x, event->y, 10, 10);
    cairo_stroke (cr);
    cairo_destroy(cr);
    return TRUE;
}

int main(int argc, char *argv[])
{
    gtk_init(&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
    //マウスイベントの処理
    g_signal_connect(window, "motion_notify_event", G_CALLBACK( cb_motion_notify_event ), NULL );
    gtk_widget_set_events( window,  GDK_POINTER_MOTION_MASK   );

    gtk_widget_set_app_paintable(window, TRUE);
    gtk_widget_show(window);
    gtk_main();
    return 0;
}

このプログラムを、Raspbian で実行すると、マウスの動きにしたがって、ウィンドウの上に黒い四角が書かれるのですが
Ubuntu で実行すると、マウスの移動では何も描画されず、ウィンドウの場所をかえると、一気にまとめて描画が
おこなわれるのです。

マウスの移動に従って線がひかれるようにするにはどうしたらよいでしょうか。
環境は、GTK+3.0 です。

オフライン

 

#2 2014-12-29 21:44:53

taka.zoo.n
メンバ
登録日: 2013-05-30

Re: cairo の描画をウィンドウに反映させる方法

私は cairo も gtk/gdk も使ったことがありませんのでまったく的外れかも知れませんが、もしかして、
http://stackoverflow.com/questions/8932 … -with-gtk3
とその response で引用されている
http://www.gtkforums.com/viewtopic.php? … K3#p195286
https://developer.gnome.org/gtk3/3.2/Gt … gArea.html
あたりが参考になるのでは??(motion_notify_event ハンドラーでは描画イメージをメモリー上に作成し再描画要求を出す、draw ハンドラーではそのイメージを widget に反映させるというふうになるのでは???)

#1 のコードを ubuntu 12.04 で手元の stacking window manager から実行したらご所望のような動作をしました。
一旦終了させて compositor (具体的には xcompmgr) を動かしてから再度実行すると「マウス移動時には全く描画されず、ウィンドウの位置をかえると、それまでためていた図形をまとめて描画」という状態になりました。#1 からは Raspberry 側も Ubuntu 側も使われた window manager も分かりませんが、ubuntu で使われている compiz は composite window manager  です。compositor の有無で挙動が変わるとなると Xlib レベルでは Expose イベントの処理が真っ先に疑われます。これが gtk/gdk レベルでどうなるかは分かりませんが、それでもこの辺が怪しいように思えます。(ただ、私の経験では compositor を外したら動かなくなったという事は有っても compositor を有効にしたら動かなくなったという事はないのですが....)

初めにも書きましたが私は cairo も gtk/gdk も使ったことがありません。外していたらごめんなさい。

オフライン

 

#3 2015-01-05 17:35:41

danjiro
新しいメンバ
登録日: 2014-12-26

Re: cairo の描画をウィンドウに反映させる方法

情報ありがとうございました。
ウィンドウを合成する compositor が関係しているようですね。

調べてみると
Raspberry Pi は、LXDE で、Openboxというウィンドウマネージャーを使っていました。
Ubuntu は、Unityで、Compizというウィンドウマネージャーを使用。

描画の違いは、OpenboxとCompiz の動作の違いが原因みたいです。

想像ですが、Openbox は軽量で、ウィンドウの半透明合成を考慮しないため、
ウィンドウに書いたものをすぐ描画する。

対して、Unity Compizは、ウィンドウの半透明合成も考慮しているので、
ウィンドウに書かれたものをためておき、ウィンドウの下を描画してから
描くのに備えて、すぐには描かないという感じでしょうか。

以下のように オフスクリーンoffscreen を追加し、
motion_notify_event ハンドラーでは
描画イメージをoffscreen上に作成し再描画要求を出す、
draw ハンドラーではそのイメージを widget に転送する
という構造にすると
マウスの動きによって描画するようになりました。

この基本を知らないと、cairoで描画してもウィンドウに反映されないという
結果になりそうですね。

コード:

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

GtkWidget *window;
cairo_surface_t *offscreen=NULL;
int offWidth = 0;
int offHeight = 0;

gboolean cb_expose_event(GtkWidget *widget, cairo_t *cr, gpointer data)
{
    int ww = gdk_window_get_width(gtk_widget_get_window(window));
    int hh = gdk_window_get_height(gtk_widget_get_window(window));
    if(offWidth!=ww || offHeight!=hh){
        if(offscreen != NULL){
            cairo_surface_destroy(offscreen);
        }
        offscreen = cairo_surface_create_similar(cairo_get_target(cr), 
                         CAIRO_CONTENT_COLOR_ALPHA, ww, hh);
        offWidth  = ww;
        offHeight = hh;
    }
    cairo_set_source_surface(cr, offscreen, 0, 0);
    cairo_paint (cr);
    return FALSE;
}

gint cb_motion_notify_event( GtkWidget *widget, GdkEventMotion *event, gpointer user_data )
{
    if(offscreen){ //// Draw Offscreen
        cairo_t *cr = cairo_create(offscreen);
        cairo_set_line_width(cr, 5.0);
        cairo_set_source_rgb (cr, 1., 0., 0.);
        cairo_rectangle(cr, event->x, event->y, 10, 10);
        cairo_stroke (cr);
        cairo_destroy(cr);    
        gdk_window_invalidate_rect(gtk_widget_get_window(window),NULL,FALSE);
    }
    return TRUE;
}

int main(int argc, char *argv[])
{
    gtk_init(&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
    g_signal_connect(window, "motion_notify_event", G_CALLBACK( cb_motion_notify_event ), NULL ); 
    g_signal_connect(window, "draw", G_CALLBACK(cb_expose_event), NULL);
    gtk_widget_set_events( window, GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK   );

    gtk_widget_set_app_paintable(window, TRUE);
    gtk_widget_show(window);
    gtk_main();

    if(offscreen != NULL){
        cairo_surface_destroy(offscreen);
    }
    return 0;
}

コンパイルは、
gcc gtst.c -o gtst `pkg-config --cflags --libs gtk+-3.0`

実行は
./gtst
です。(上のコードを gtst.c に保存した場合)

なお
gdk_window_invalidate_rect(gtk_widget_get_window(window),NULL,FALSE);
で、毎回画面全体の書き直しを行うよう指示していますが
NULLの部分には、書き直しが必要な範囲をいれるとさらによいみたいです。
ウィンドウサイズの変更は別のイベント処理でやるのが正式らしいです。

この方法だと、画面の書き直しを毎回行うことになるので
画面全体の再描画に時間がかかる場合は、

オフスクリーンへの描画が途中でも、ウィンドウに転送できるようにする。
マウスイベントに応答してすぐ描画したいものは、
オフスクリーンとは別に再描画する

といった工夫が必要みたいです。

オフライン

 

Board footer

Powered by FluxBB