前回の投稿では,ウィンドウ上でマウスを押している間にそのマウスの位置情報を得ることができるようになりました.今回の投稿では前回の最後に予告した通り,それを応用したアプリとして簡単な「お絵かきアプリ」を作ってみようと思います.実際にやることは,前回の投稿で作ったプログラムに少し手を加えるだけなので,前回の投稿を見ていただくと理解しやすいのではないかと思います.
前回と同様,今回もkvファイルは用いません.
今回習う内容はこちらの動画になります.
筆者はこちらの動画の通り従ってやってみた感想ですが,従えばとりあえず作ることはできるのですが,個の動画に限っては説明が足りないため中身がチンプンカンプンのままで終わってしまいました.なので,こちらの投稿では動画ではできていない説明も分かった範囲で加えつつ書いていきたいと思います.
下準備:前回の内容
前回の最後の結果をもとに話を進めたいと思います.
ほかのページを開いて再度確認するのがめんどくさい方のために,前回の投稿で最終的に作成したコードを載せておきます.中身はほんの少し変わってて,クラスの名前が前回は KurikkuInput だったのが 今回は DrawInput になっているぐらいの違いです.
from kivy.app import App from kivy.uix.widget import Widget class DrawInput(Widget): def on_touch_down(self, touch): print(touch) def on_touch_move(self, touch): print(touch) def on_touch_up(self, touch): print("Released!",touch) class IntroKivy(App): def build(self): return DrawInput() if __name__ == "__main__": IntroKivy().run()
マウスの軌跡を線として描く
書く前に:線=点の集まり
微積分に触れた事のある方はクドイだけかもしれないので,飛ばしていただいて問題ないです.
GUIで線を書く際の基本な考え方なのですが,コンピューターの画面はドットの集まりなので,「滑らかな」線を画面上で描くことは不可能です.ただし「滑らかな線もどき」は作ることができ,その「滑らかな線もどきは」階段のようにカクカクしているものの私たちの目には十分滑らかに見えるモノを言います.カクカクしていることを「離散的」,滑らかなことを「連続的」と表現したいと思います.
コンピューターの画面がドットでできている時点で離散的であるので,そもそも画面上でのマウスの動き自体も離散的です.滑らかに見えて実はカクカク折れ曲がっていますが,そのカクカク折れ曲がっている地点がまさに,”on_touch_move” 得られた座標です.”on_touch_move” の中にprint文を書いたときに膨大な量の座標が吐かれましたが,これはなるべく滑らかな線にしようと折れ曲がりの点をたくさん取り,人には滑らかに見えるようにするためです.
このように考えると,GUIで滑らかに見える線を表示させるという手続きは,次のように考えることができます.
- マウスをクリックした点の座標をゲットする.それを1番座標とする.
- マウスが少しだけ動いたとき,1番座標からちょっとずれたときの新しい座標を2番座標とする.
- 1番座標と2番座標をつなぐ直線を書く.直線というよりは「直線に見える」線というのが正しいだろう.
- 次にマウスが少しだけ動いたとき,2番座標からちょっとずれたときの新しい座標を3番座標とする.
- 2番座標と3番座標をつなぐ直線を書く.
- このようにして点をつないでいくと同時に,その点の座標をどんどん保存していく.
- マウスのクリックを離す直前の点がN番座標になったとする.
- 画面には線が描かれ,その線の情報の実態は1番からN番座標までの点の集りのことである.
手続きをいちいち見ているととても果てしない作業に見えますが,この作業のほとんどは”on_touch_move”の機能で賄えることがわかるかと思います.「座標をどんどん保存してく」という部分は今回新しく習う部分ですね.
線を作るための機能をインポート:Line
では,実際のプログラムの話に移ります.徐々にコードを追加していきながらkivyにどのような機能があるのか勉強していきましょう.まずは準備したコードの3行目に次の行を追加しましょう.
from kivy.graphics import Line
“kivy.graphics” はお絵かきに必要な基本的な機能の集りになります.公式ドキュメントには次のように書いてあります.
Module: kivy.graphics
This package assembles many low level functions used for drawing.
今回はその中でも”Line“という機能を持ってきます.この“Line”は「座標の集り」をつないで図に書いてくれる機能です.”Line” に座標をどんどん足してあげると,こいつが勝手につないで線を書いてくれます.
ペンを下した位置を線のスタートとして保存
まず線のスタート地点としてマウスを押したところの座標をゲットして,線の情報として保存しましょう.”def on_touch_down”のところを次のように編集しましょう.
class DrawInput(Widget): def on_touch_down(self, touch): print(touch) with self.canvas: touch.ud["Sen"] = Line(points=(touch.x, touch.y))
printの次の行からが新しい部分です.順に見ていきましょう.
canvas
まず”self.canvas“についてです.Pythonのクラスの中でselfを使ったとき何がselfを示すか理解していらっしゃるのであれば,上の例で”self”は”DranwInput”というクラスであることがわかります.このDrawInputはWidgetを継承しているので,Widgetクラスの機能を使うことができます.”.canvas“の部分ですが,これはLabelやButtonと同様にWidgetクラスの機能で「今のウィンドウ(の中にできているウィジェット)をお絵かきのキャンバスとみなす」という意味です.ウィンドウ(正確にはウィジェット)に図形や絵などをかきたいときはこのcanvasという機能を使います.ということで,“with self.canvas:”と書くことで「ウィンドウの中のDrawInputというウィジェットをキャンバスのように扱い,次の操作をしてください」という指示になります.
touch.ud
“ud” は”User data Dictionary”の略です.touch は”on_touch_down”ではクリックした位置についての情報(つまり「touchのデータ」)でしたが,”touch.ud”には「touchについてのデータ」をプログラムを書いている私自身が追加することができます.使い方としては
touch.ud["属性名"] = (属性の値)
のようになります.この結果,touch.ud には{“属性名”:属性の値}と辞書型で保存されます.属性の値は数字でも文字列でもどんな方でも構いません.なので,先ほどのコードでは「属性名は”Sen”,そしてその値にはLine(points=(touch.x, touch.y))というLineクラスのデータ」という辞書の要素を入れることをしています.ちなみに,”print(touch.ud)”という行を最後加えて実行するとコンソールに次のようなメッセージがprintされます.
{'Sen': <kivy.graphics.vertex_instructions.Line object at 0x0000014786E4A5C0>}
この”Sen”という名前がついたLineクラスのデータ(小難しく言うとobject,オブジェクトともいう)はパソコン上のどこにあるかというと,メモリの”0x0000014786E4A5C0″というアドレスにありますということをこの実行結果は言っています.数字でごちゃごちゃしてますが,アドレスは住所のことなので「**県**市** 1-2-3 ***ビル 456号室」みたいなもんです.メモリの物理的にどの部分なのを表した数字が”0x0000014786E4A5C0″というアドレスです.
ちなみにですが,”touch.ud[“Sen”]…”という部分で辞書に追加をしているので,”touch.ud[“Sen”]…”より以前の行にprint(touch.ud)を書いても,それは空の辞書,つまり{}を表示してくれるだけになります.
Lineクラス
先ほども説明しましたが,”Line“という機能は「座標の集り」をつないで図に書いてくれる機能です.”Line” に座標をどんどん足してあげると,こいつが勝手につないで線を書いてくれます.”Line(points=(touch.x, touch.y))“というところでは,「Lineクラスをつくって,そのpoints(座標の集り)に(touch.x, touch.y)という絶対座標を追加してください」ということを言っています.今の段階ではまだボタンを押しただけでドラッグはしていないので,線は1つの点からできています.
ドラッグしている点の情報をどんどん足していく
次に,上の touch.ud というLineクラスにドラッグしている位置の情報を足していきましょう.
def on_touch_move(self, touch): touch.ud["Sen"].points += touch.x, touch.y
ドラッグしている間の座標の情報を「touch.udという辞書の,”Sen”が示す値の,pointsという要素」に足していってあげてます.Pythonで”+=“は「続けて足していく」という意味です.例えば,“X=1″とした後に”X+=2″としたときには,「まずXは1です,次に元のXの値に2を代入して,それを新しくXの値にします.つまり今はX=3です」となります.今回はこの”+=”をLine.pointsに対して用いているので「もとからあるLine.pointsの座標に続けて,新しく座標(の情報)を足していく」ということをしています.
一通り線を書いた後,線のデータを見る
クリックを離した際の”on_touch_up”の中身も次のようにいじってみましょう.
print(touch.ud["Sen"].points)
最終的に書かれた線の座標の情報は touch.ud[“Sen”].points にはいっているので,それをコンソールで確認するために付け加えました.これでクリックを離した際に”Sen”というオブジェクトに保存されている「線のもとになった点の座標の一覧」をすべてコンソールに出力させています.長い線を書いたのであればたくさん出力されるし,短い線を書いたのであれば少し出力されるはずです.
お絵かきアプリの最終的な形
ということで,これらの変更を加えてできる最終的なコードは以下のようになるでしょう.
from kivy.app import App from kivy.uix.widget import Widget from kivy.graphics import Line class DrawInput(Widget): def on_touch_down(self, touch): with self.canvas: touch.ud["Sen"] = Line(points=(touch.x, touch.y)) def on_touch_move(self, touch): touch.ud["Sen"].points += touch.x, touch.y def on_touch_up(self, touch): print(touch.ud["Sen"].points) class IntroKivy(App): def build(self): return DrawInput() if __name__ == "__main__": IntroKivy().run()
実行して表示されるウィンドウに絵をかきながらコンソールを見比べることで,プログラムの中身を復習してみましょう.
次回予告:いろんな画面を行ったり来たり
今までは実行すると一つのウィンドウが表示され,その中の配置を決めたり,マウスの座標を得たりなどをしていました.次からは画面移動を考え,ボタンを押すと次のページに行くのような操作ができるようになります.この内容までカバーすれば,sentdex さんの”Kivy Tutorial”シリーズで学ぶ内容は終わりになるので,ひとまず次の投稿で kivy 超入門シリーズの最後に使用かなと思っております.
他のサイトにて、Pythonをわかっている方がコードの解説をすると、
文法等の基本的なところは大幅にカットされてしまい、
結局のところPythonに割と精通している人でないとすんなりと読めない記事が多くあるのですが、
本ブログ(Kivyの記事しか拝見しておりませんが。)は、Python初学者である私でもすらすらと読むことができ、すんなりと頭の中に知識が入ってくるので、とっっっても役に立っております。
お忙しいとは思いますが、これからも記事の更新楽しみにしております!
(勘違いかもしれませんが)褒めていただいてありがとうございます!
お気づきかもしれませんがやっていること自体は、リンクに貼ってある Youtube のリンク先の方がやっていることを、日本語にして右に流しているだけなんですけどね(笑)
Kivyでなんかもう少し凝ったことやりたいんですけど、なかなか難しいんですよね… 以前、自分用の Todo 管理アプリを作ったのですが、ボタンを押した際色んな画面に移動するところとか、SQLite のデータベースから情報もってくるとか、デザインとかで、結局使ってません。一番むずいのって Kivy でクールなデザイン作ることではないかと、自分は思っています。なんかネタとかイイこと思いついたら更新します!