ラズベリーパイに、ワコムのペンタブレットを接続して、毛筆のような線が書けるアプリの作り方を解説します。
はじめに
- ワコムのペンタブレットの筆圧を Linuxで検知する方法を調べた結果、ラズベリーパイのOS Raspbian Jessie with PIXEL でも、筆圧をちゃんと検出できることがわかりました。
- そこでラズベリーパイで、毛筆のような線が描けるアプリの作り方を解説します。初心者が実際に試せるように記事を書きました。Scratch の次に体験してみるとよいと思います。
- アニメーション作成ソフト「9VAeきゅうべえ(9va-pi)」もこれと同じ方法で開発しています。
必要なもの
- ラズベリーパイに、Raspbian Jessie with PIXEL がインストールされていること
- ワコムペンタブレット(なくてもOK)
ここで学習できるソフト
以下の項目が体験できます。
項目 | 内容 |
---|---|
C | プログラム言語 |
Gtk3 | ウィンドウやボタンなどユーザーインタフェースを作るライブラリ |
cairo | Gtkに含まれる2次元の図形を描くライブラリ |
geany | 開発環境 |
wacom9va | 筆描画を行うサンプルプログラム。githubというサイトで公開されています |
git | サンプルプログラムの取得 |
シェル | ターミナルの中で使う命令 |
サンプルプログラム wacom9va の取得
ターミナルの中で以下のコマンドを入力してサンプルプログラムを取得します。
項目 | 意味 |
---|---|
cd xxx | xxxという名前のフォルダに移動 |
cd ~ | ホームフォルダに移動 |
mkdir xxx | xxxという名前のフォルダを作成 |
ls | 今のフォルダの中身を表示 |
ls xxx | xxxというフォルダの中身を表示 |
git clone xxx | xxx からプロジェクトを取得する |
& | 命令をバックグランドで実行 |
タブ(Tab)キー | 後に続く文字を自動的に選んで補完する |
↑↓キー | 前に実行した命令を読み出す |
Ctrl+C キー | プログラムの中止 |
-
$ cd ~
ホームフォルダに移動します。 - 作業用フォルダ「test」を作成し、その中に移動します。
$ mkdir test
$ cd test
- 筆の描画を行うサンプルプログラムを読み込みます。
$ git clone https://github.com/dnjiro/wacom9va
- 取得したフォルダを確認
-
$ ls
wacom9va
と表示されればOKです。中を見てみると -
$ ls wacom9va
linux mac README.md
中には、Mac と linux 用のフォルダがはいっています。
-
プログラム開発環境 geany
- プログラム開発するツールとして geany を使います。
-
Raspbian Jessie with PIXEL には、最初から geany が入っています。もし、geany がはいっていなければ、
$ sudo apt-get install geany geany-plugins
で取得できます。
geany の起動
- linux フォルダの中の makefile を geany で開きます。最後に「&」をつけると、すぐターミナルに戻ります。
-
$ geany wacom9va/linux/makefile &
最初の数文字入力してからタブキー(Tab)を押せば自動入力されます。上の例なら geany wa タブキー li タブキー mak タブキーで入力できます。
-
メイク(プログラムの作成)
早速プログラムを作成して、geany の機能をみてみましょう。
-
geany メニューバー > ビルド(Build) 下図A > メイク(Make) をクリック。これでプログラムの作成が始まります。
-
結果「Package gtk+-3.0 was not found」 といったエラーメッセージがずらずらと表示されるでしょう。
-
下のエラーの中から、
main.c:8:21: fatal error: gtk/gtk.h: そのようなファイルやディレクトリはありません(No such file or directory)
という行(B)をクリックしてください。 main.c のエラーが発生した場所(C)が表示されます。 -
また、左側の関数名(D)をクリックすると、関数の場所が表示されます。
これで、geany の働きがわかったでしょうか。
- makefile というファイルには、プログラムの作り方が記述されています。「ビルド>メイク」はそれに従ってプログラムを作ります。
- main.c が c言語で書かれたプログラムです。
- エラーが発生した
#include <gtk/gtk.h>
(上図C)は、「gtk フォルダの中の gtk.h」ファイルをこの場所に挿入するという意味です。Gtk の開発用ファイルがラズベリーパイの中にないためエラーになりました。 - そこで、gtk3 を入手しましょう。
Gtk3 の取得
- ターミナルから
$ sudo apt-get install libgtk-3-dev
- 続行しますか(Do you want ti continue?) [Y/n] に対して 'y'を入力
※ apt-get でエラーが出る場合、次の記事が参考になります。rasbianでapt-get updateがうまく行かない場合の可能性について
もう一度 geany でメイク
- geany メニューバー ビルド「Build」(上図A)>メイク「Make」をクリック
今度はエラーなく「コンパイル完了」と表示されたはずです。 - linuxフォルダの中を見てみましょう。
-
$ ls wacom9va/linux
main.c main.o makefile wacom9va
main.c から main.o が作られ(この作業をコンパイルといいます)、その後、wacom9va が作られました(この作業をリンクといいます)。
-
プログラムの実行
- もしタブレットのペンを動かしてもカーソルが反応しない場合、次のコマンドで、xserver-xorg-input-wacom を取得してから再起動してみてください。
$ sudo apt-get install xserver-xorg-input-wacom
- プログラムの終了は、右上の「×」ボタンもしくは、ターミナルから Ctrl+C キーです。
プログラムの改良
ここまでできたら、いよいよプログラムを改良してみましょう。main.c の中身を解説します。
- サンプルプログラムの改良は初心者におすすめです。ちゃんと動作する状態から始めれば、動かなくなった時に直前の修正が間違っていたことがわかります。いろいろ試してみてください。
1.線の色を黒にする
- 線の色が緑色であることをヒントに、main.c の中から色を指定している場所を探します。
cairo_set_source_rgb (qp, 0., 1., 0.);
がその箇所で、144行(cb_expose_event)、189行(cb_buton_release) にあります。rgb(red,green blue) が、0,1,0 なので、緑色(green)になります。これを、0,0,0 に変更すれば黒になります。プログラムを修正してほんとに色が黒くなるか確かめてみてください。main.c を修正してから、Ctr+S キーで保存。ビルド>メイク。実行は、ターミナルから「./wacom9va/linux/wacom9va」です。ターミナルで上矢印キーを押せば以前使った命令を実行できます。 - 144行(cb_expose_event)、189行(cb_buton_release) の2箇所にあるのはなぜでしょうか。片方だけ黒にして、この2つがどういういう働きをしているか試してみてください。
- 144行は、
if(offWidth!=ww || offHeight!=hh || offDelta != whDelta){
という if 文(条件分岐)の中にはいっています。「!=」は、値が異なるの意味。「||」は「または」です。offWidth、offHeight、offDelta は、それぞれオフスクリーンの幅、高さ、スクロール量です。この部分はオフスクリーンという画像の状態が、ウィンドウの状態と違った場合、オフスクリーンの中身を書き直すという処理を行っています。 - 189行(cb_buton_release) では、ペンを離したときオフスクリーンに1本の線だけ書いています。187行
cairo_t *qp = cairo_create(offscreen)
が書き込み先をオフスクリーン(offscreen)にするという意味です。 -
cairo は、Gtk の中に含まれる2次元の描画を行うライブラリです。cairo では、cairo_tで指定される画面に、別の画面を転送することで絵を書きます。
cairo_set_source_rgb (qp, 0., 1., 0.);
は、転送元を固定色にする命令です。ほかに転送元をグラデーションにしたり、画像にしたりできます。cb_expose_event 関数の中にある
cairo_set_source_surface(cr, offscreen, 0, 0);
cairo_paint (cr);
は、転送元をオフスクリーン(offscreen)にして、全体を塗りつぶし(cairo_paint)ています。その結果、オフスクリーンからウィンドウへ画面を転送するという意味になります。
- オフスクリーンを使うのは高速化のためです。線を書くたびにオフスクリーンに書き込んでいき、画面を再描画するときにオフスクリーンから画面転送しています。こうすれば線の数が増えても遅くなりません。
2.起動時のウィンドウサイズを変える
- 217行(main)
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
でウィンドウを作成しています。その後何も設定していません。ここにウィンドウサイズの設定を入れましょう。 - ウィンドウ関係の関数を調べるために、gtk_window_new をネットで検索すれば、GtkWindow gtk+3 が見つかります。
- 多くの関数がありますが、中からウィンドウサイズを設定しそうな名前を探します。
gtk_window_resize ()
が見つかりました。gtk_window_resize (window,800,600);
と入れてみて、ウィンドウサイズが変わるかどうか確かめてみましょう。 - この関数は、214行、
int main(int argc, char *argv[])
という関数の中にあります。ターミナルから起動するプログラムは、main 関数からスタートします。
3.上下矢印キーでスクロールできるようにする
- main() 関数からプログラムがどう動くか説明しましょう。main()関数からスタートしたあと、gtk_main(); まで進んでイベント待ちになります。
- その前にどんなイベントに対して、どんな処理をするかが設定されます。例えば、221行め
g_signal_connect(window, "button_press_event", G_CALLBACK( cb_button_press_event ), NULL );
は、 "button_press_event" というイベントに対し、関数cb_button_press_event()
を実行するという意味です。これは、ボタンが押されたときの処理です。 - もうひとつ必要な設定が、225-228行
gtk_widget_set_events( window, GDK_EXPOSURE_MASK
| GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
| GDK_SCROLL_MASK
);
です。これは、このプログラムがどのようなイベントを受け付けるかの設定で、「GDK_BUTTON_PRESS_MASK」を設定しておかなければ、"button_press_event" が送られてきません。「|」はビット演算のORで、どんなイベントを受け付けるかを設定しています。
- このプログラムのイベント処理は次のようになります。
|イベント|処理関数|マスク|
|:--|:--|:--|:--
|"button_press_event"
ペンダウン| cb_button_press_event | GDK_BUTTON_PRESS_MASK |
|"motion_notify_event"
ペンの移動 | cb_motion_notify_event | GDK_POINTER_MOTION_MASK |
|"button_release_event"
ペンアップ | cb_button_release_event |GDK_BUTTON_RELEASE_MASK |
| "draw"
再描画 | cb_expose_event | GDK_EXPOSURE_MASK |
|"scroll_event"
スクロール | cb_scroll_event | GDK_SCROLL_MASK |
| "destroy"
終了| gtk_main_quit ||
-
上下矢印キーで画面がスクロールするようにするために、次の情報を調べましょう。
- キーを押したときに発生するイベント> "key-press-event"
- キーイベントを受け取るマスク> GDK_KEY_PRESS_MASK
- 押されたキーが上下矢印であることの判定。
-
このプログラムは、マウスホイールを回転させると画面が上下にスクロールします。その処理は、cb_scroll_event()関数です。
gint cb_scroll_event( GtkWidget *widget, GdkEventScroll *event, gpointer user_data )
{
switch ( event->direction ) {
case GDK_SCROLL_DOWN: whDelta += 10; break;
case GDK_SCROLL_UP: whDelta -= 10; break;
}
gdk_window_invalidate_rect(gtk_widget_get_window(widget),NULL,FALSE);
return TRUE;
}
- スクロールの方向によって、whDelta という変数の値を増やしたり減らしたりしています。
whDelta += 10;
は、whDeltaに10を加えるという意味です。これと同じように、上矢印キー、下矢印キーがきたら、whDelta を増減させる関数を追加してみてください。
4.u キーで Undo、 r キーで Redoできるようにする
- 次は、線のデータがどのように処理されているか見てみましょう。ペンダウンの処理は、cb_button_press_event() です。
gint cb_button_press_event( GtkWidget *widget, GdkEventMotion *event, gpointer user_data )
{
isDown = 1;
m_points[m_index] = m_pointX[m_index][0] = m_pointY[m_index][0] = 0;
m_index++;
return TRUE;
}
- ここで、m_index、m_points、m_pointX、m_pointYは次の意味を持ちます。m_points、m_pointX、m_pointY、m_pressure は配列です。
変数 | 意味 |
---|---|
m_index | 入力した線の本数 |
m_points[nn] | nn番目の線に含まれる点の数 |
m_pointX[nn][kk] | nn番目の線のkk番目の点のX座標 |
m_pointY[nn][kk] | nn番目の線のkk番目の点のY座標 |
m_pressure[nn][kk] | nn番目の線のkk番目の点の太さ |
- そこで、m_index を一つ減らせば、最後に描いた線を書かなかったことになり(Undo)。逆に増やせば、線が復活するはずです(Redo)。例えば、次のようにしてみましょう。 最後の offWidth = 0; と gdk_window_invalidate_rect() 画面を書き直す関数です。これをいれないと、画面が変化しません。
gint cb_key_press_event( GtkWidget *widget, GdkEventKey *event,gpointer *user_data)
{
switch(event -> keyval){
case 'u':
if(m_index > 0){
m_index--;
}
break;
case 'r':
if(m_points[m_index] > 0){
m_index++;
}
break;
default:
return 0;
}
offWidth = 0;
gdk_window_invalidate_rect( gtk_widget_get_window(widget), NULL, FALSE);
return TRUE;
}
-
gdk_window_invalidate_rect()は、画面が変化したことを伝える関数です。この関数の中で画面が描画されるわけではありません。画面の書き直しはウィンドウのサイズを変えたときなど、アプリの状態とは無関係に発生します。様々な状況を判断し、適切なタイミングで、"draw"イベントが発生し、cb_expose_event() 関数で再描画が行われます。 offWidth = 0 としておけば、オフスクリーンの書き直しが行われます。
-
キーに反応するためには、main 関数に、次のように g_signal_connect関数と GDK_KEY_PRESS_MASK の追加が必要です。
g_signal_connect(window, "key_press_event",G_CALLBACK( cb_key_press_event ), NULL );
gtk_widget_set_events( window, GDK_EXPOSURE_MASK
| GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
| GDK_SCROLL_MASK | GDK_KEY_PRESS_MASK
);
- さて、上の例では、'r' を押したとき、前に書いた線まで書いてしまう問題があります。どう修正すればよいか考えてみてください。
5.マウスで描いても線の太さを変える
- ワコムのペンタブレットがない場合でも、線の太さを変えるようにしてみましょう。筆圧を取得しているのは、ペンの移動に対応した関数 cb_motion_notify_event() です。
gint cb_motion_notify_event( GtkWidget *widget, GdkEventMotion *event, gpointer user_data )
{
if(offscreen){
if(isDown){
gdouble pressure =0;
if(!gdk_event_get_axis ((GdkEvent *)event, GDK_AXIS_PRESSURE, &pressure)){
pressure =0;
}
AddPolygonPoint(m_index-1, event->x, event->y, pressure * 16);
}
}
return TRUE;
}
-
gdk_event_get_axis ((GdkEvent *)event, GDK_AXIS_PRESSURE, &pressure)
で筆圧を検出しています。ここで、pressure は筆圧を取得する浮動小数点(gdouble)の変数です。&pressure で変数が配置されている場所(ポインタ)を関数にわたし、結果を、pressure の中に入れてもらいます。その戻り値が0(失敗)だった場合、関数の前の「!」(Not)によって、1に変わり、if 文の中(pressure = 0)が実行されます。 - そこで、
pressure = 0;
を、`pressure = (gdouble)(rand()%100) / 100;' に変更してみましょう。rand()%100 は、整数の乱数 rand() を 100 で割った余りで 0 から 99 になります。(gdouble) は整数をを小数点に変換するという意味で、これがないと整数演算になってしまい割り算の結果が0になってしまいます。 - マウスで書くと、おもしろい線が書けたと思います。pressure の値をうまく設定すれば、筆のような線がかけるでしょう。挑戦してみてください。
6.線の太さを変えるアルゴリズム
- オフスクリーンに対して、全部の線を書き直している部分が、cb_expose_event() 関数の中にあります。
{
cairo_t *qp = cairo_create(offscreen);
cairo_set_line_width(qp, 0);
cairo_set_source_rgb (qp, 0., 1., 0.);
cairo_new_path (qp);
for(in=0; in < m_index; in++){
for(pnt=0; pnt< m_points[in]; pnt++){
MakeBrushLine(qp, in, pnt);
}
}
cairo_fill (qp);
cairo_destroy(qp);
}
- cairo_new_path (qp);で、パス(多角形)を初期化します。
- MakeBrushLine(qp, in, pnt); の中で、多角形の頂点を追加していきます。実際に追加しているのは、 asSetPoint() 関数です。
void asSetPoint(cairo_t *qp, int pnt, int x, int y)
{
if(!pnt) cairo_move_to(qp, x, y - whDelta );
else cairo_line_to(qp, x, y - whDelta );
}
- 最初(pntが0)の点には移動(cairo_move_to)し、次の点からつなぎます(cairo_line_to)。
- MakeBrushLine 関数の中では、記録した頂点から、m_pressure に記録された大きさの12角形の頂点を計算し、2つの12角形を囲む多角形を何個も作っていきます。
- 最後に、cairo_fill (qp) で多角形を一気に塗りつぶします。
- この12角形を作っている部分を修正すれば、縦が太く、横が細い、Gペンのような線がひけるでしょう。
7.この後の展開
これをベースに以下のような改良を考えてみてはどうでしょうか。
- 数字キーを押して、色を変更する
- 'h' キーを押すと半透明で書けるようにする。cairo には半透明で描画する機能があります。
- 画像の読み込み。cairo に画像を読み込む関数があります。オフスクリーンに画像を描くようにすれば、写真の上に筆で書き込むプログラムが作れるでしょう。
- メニューバーをつける。難しいかもしれません。ぜひ挑戦してみてください。
参考にしたサイト
- GTK tutorial 1.2 Scribble, A Simple Example Drawing Program
- The Linux Wacom Project
- Wacom Tablet
- ワコム筆圧ペンで太さが変わる線をひくアルゴリズム