「Krita」と「Python」で「ベジェ曲線」を描いてみよう
はじめに
今回は「ベジェ曲線」を描きます。ベジェ曲線とは「始点」「終点」「コントロールポイント」を元に曲線を描きます。始点から終点にかけて線を描くのですが、コントロールポイントの位置によって磁石で引っ張られたように線が曲がります。
ベジェ曲線を扱うPythonの「Qt(キュート、クロスプラットフォームで動作するUIライブラリ)」の「QPainterPath」クラスには「quadTo」メソッドと「cubicTo」メソッドの2つがあります。quadToメソッドはコントロールポイントが1つだけのベジェ曲線で描かれます(図1)。
cubicToメソッドはコントロールポイントが2つのベジェ曲線で描かれます(図2)。2つコントロールポイントがあるため、quadToメソッドよりも自由な形の曲線が描けます。
フキダシを描く
それでは、ベジェ曲線を使う例としてフキダシを描いてみます。まず思考中のフキダシをquadToメソッド(1個のコントロールポイントだけのベジェ曲線)で描き、次にフォントを指定して文字を書きます。最後に会話中のフキダシをcubicToメソッド(2個のコントロールポイントのベジェ曲線)で描きます。
思考中のフキダシを描く
次のサンプルスクリプト「thought.py」はquadToメソッドを使うサンプルで、第3回のテンプレート「template.py」ファイルに追記していきます。「create_doc」関数と「set_layer」関数はそのままで、「begin_draw」関数もほとんど同じです。quadToメソッドやcubicToメソッド、「lineTo」メソッドのラインは「QPen」の色で塗られ、囲った部分は「QBrush」の色で塗り潰されます(図3)。
・サンプルスクリプト「thought.py」# モジュール from PyQt5.Qt import * # ドキュメントの作成 def create_doc(width,height): doc = Krita.instance().createDocument(width,height, "Document name", "RGBA", "U8", "", 300.0) Krita.instance().activeWindow().addView(doc) return doc # 描画 def draw_fukidashi(doc): pixmap = QPixmap(doc.bounds().size()) pixmap.fill(QColor(255,255,255)) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing,True) color = QColor(0, 0, 0, 255) pen = QPen(color) pen.setWidth(4) painter.setPen(pen) color = QColor(255, 255, 255, 255) brush = QBrush(color) painter.setBrush(brush) draw_bezier(painter) painter.end() return pixmap.toImage() # ベジェ曲線でフキダシと円を描く def draw_bezier(painter): path = QPainterPath() path.moveTo(100,400) path.quadTo(100,700,400,700) path.quadTo(700,700,700,400) path.quadTo(700,100,400,100) path.quadTo(100,100,100,400) painter.drawPath(path) painter.drawEllipse(QPoint(660,660),30,30) painter.drawEllipse(QPoint(710,710),20,20) # レイヤーのセット def set_layer(doc,img): # 修正箇所 root = doc.rootNode() # 修正箇所 layer = root.childNodes()[0] if img.sizeInBytes() == 4 * layer.channels()[0].channelSize() * doc.width() * doc.height(): ptr = img.bits() ptr.setsize(img.byteCount()) layer.setPixelData(QByteArray(ptr.asstring()), 0, 0, doc.width() , doc.height()) else: print('Error') # メイン関数(main()という関数名にしたいところだが既に使われている) def begin_draw(): doc = create_doc(800,800) img = draw_fukidashi(doc) set_layer(doc,img) doc.refreshProjection() # メイン関数の呼び出し begin_draw()
【サンプルスクリプトの解説】
「draw_fukidashi」関数でイメージのインスタンスを用意して描画を開始します。ペンとブラシを設定して「draw_bezier」関数を呼び出し、描画を終了して「QImage」に変換し戻り値を返します。
「draw_bezier」関数でQPainterPathクラスのインスタンスを生成して(100,400)→(400,700)→(700,400)→(400,100)→(100,400)の順にベジェ曲線でフキダシを描き、さらに円を2個描きます。
「begin_draw」関数でdraw_fukidashi関数を呼び出します。
フォントで文字を書く
次のサンプルスクリプト「font.py」は文字を描画するサンプルで、thought.pyに追記していきます。OSによってフォントが違いますが、今回はWindows向けに「メイリオ」フォントだけ指定します(図4)。指定したフォントが存在しない場合は、デフォルトのフォントが使われます。
・サンプルスクリプト「font.py」(前略) # 描画 def draw_fukidashi(doc): pixmap = QPixmap(doc.bounds().size()) pixmap.fill(QColor(255,255,255)) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing,True) color = QColor(0, 0, 0, 255) pen = QPen(color) pen.setWidth(4) painter.setPen(pen) color = QColor(255, 255, 255, 255) brush = QBrush(color) painter.setBrush(brush) draw_bezier(painter) draw_font(painter) painter.end() return pixmap.toImage() # 文字列描画 def draw_font(painter): font = QFont("メイリオ") font.setPointSizeF(36) try: fm = QFontMetricsF(font) painter.setFont(font) painter.drawText(QPointF(150,320),"安く買って") painter.drawText(QPointF(150,420),"高く売るだけなのに") painter.drawText(QPointF(150,520),"何て奥が深いんだろう") finally: pass (後略)
【サンプルスクリプトの解説】
「draw_fukidashi」関数で「draw_font」関数を呼び出します。
「draw_font」関数で、メイリオフォントを指定した「QFont」クラスのインスタンスを生成し、サイズを36ptにしてフォントをセットします。
「drawText」メソッドでQPointF(X,Y)座標に文字列を描画します。
「try」文は例外が起こらないか処理し、最終的に(finally)パス(pass)します。
会話中のフキダシを描く
次のサンプルスクリプト「fukidashi.py」はcubicToメソッドとlineToメソッドを使うサンプルで、font.pyに追記していきます。今度は会話中のフキダシで口を飛び出させて描きます(図5)。ここではやっていませんが、もちろんcubicToメソッドやlineToメソッドとともにquadToメソッドも使えます。
・サンプルスクリプト「fukidashi.py」(前略) # ベジェ曲線でフキダシを描く def draw_bezier(painter): path = QPainterPath() path.moveTo(100,400) path.cubicTo(100,580,220,700,400,700) path.cubicTo(460,700,530,700,620,620) path.lineTo(730,650) path.lineTo(650,580) path.cubicTo(700,530,700,420,700,400) path.cubicTo(700,280,580,100,400,100) path.cubicTo(230,100,100,230,100,400) painter.drawPath(path) (後略)
【サンプルスクリプトの解説】
「draw_bezier」関数でQPainterPathクラスのインスタンスを生成し、(100,400)→(400,700)→(620,620)→(730,650)→(650,580)→(700,400)→(400,100)→(100,400)の順にベジェ曲線と直線でフキダシを描きます。lineToメソッドは直線を描きます。
おわりに
今回はコントロールポイントが1個と2個のベジェ曲線を描きました。プログラミングしてフキダシをデザインするのは、ものすごく面倒くさいです。2DCGツールを使えばフキダシをデザインするのは簡単ですが、プログラミングでデザインするのと、2DCGツールでグラフィカルにデザインするのとでは、それぞれ作りやすいデザインは異なります。第3回で作ったランダムに無数の正方形を描画するサンプルのような場合は、プログラミングした方がはるかに簡単な場合もあります。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- 「Krita」と「Python」でアーティスティックな絵を描こう
- 「Krita」で「Python」をプログラミングしてはじめての画像を描こう
- 「Krita」と「Python」でアニメーションを描いてみよう
- HTML+JavaScriptだけでブラウザに図形描画- Canvas API-
- 動画・音声のブラウザ対応状況、canvasによる描画
- 「TAURI」にも必要な「Rust」の「クレート」を使う
- HTML+JavaScriptだけでブラウザに図形描画(2)- Canvas API-
- オブジェクト指向で作った15パズルの完成
- 「Ace」を使って「TAURI」で「テキストエディタ」アプリを作ろう
- 撮影した写真を分離ストレージとPicturesHUBに保存する