「Krita」と「Python」で「ベジェ曲線」を描いてみよう

2024年9月5日(木)
大西 武 (オオニシ タケシ)
第5回の今回は「Krita」でベジェ曲線のフキダシを描いたり、指定したフォントで文字を書いたりする解説をします。

はじめに

今回は「ベジェ曲線」を描きます。ベジェ曲線とは「始点」「終点」「コントロールポイント」を元に曲線を描きます。始点から終点にかけて線を描くのですが、コントロールポイントの位置によって磁石で引っ張られたように線が曲がります。

ベジェ曲線を扱うPythonの「Qt(キュート、クロスプラットフォームで動作するUIライブラリ)」の「QPainterPath」クラスには「quadTo」メソッドと「cubicTo」メソッドの2つがあります。quadToメソッドはコントロールポイントが1つだけのベジェ曲線で描かれます(図1)。

図1:コントロールポイントが1つだけのベジェ曲線「quadTo」メソッド

cubicToメソッドはコントロールポイントが2つのベジェ曲線で描かれます(図2)。2つコントロールポイントがあるため、quadToメソッドよりも自由な形の曲線が描けます。

図2:コントロールポイントが2つのベジェ曲線「cubicTo」メソッド

フキダシを描く

それでは、ベジェ曲線を使う例としてフキダシを描いてみます。まず思考中のフキダシをquadToメソッド(1個のコントロールポイントだけのベジェ曲線)で描き、次にフォントを指定して文字を書きます。最後に会話中のフキダシをcubicToメソッド(2個のコントロールポイントのベジェ曲線)で描きます。

思考中のフキダシを描く

次のサンプルスクリプト「thought.py」はquadToメソッドを使うサンプルで、第3回のテンプレート「template.py」ファイルに追記していきます。「create_doc」関数と「set_layer」関数はそのままで、「begin_draw」関数もほとんど同じです。quadToメソッドやcubicToメソッド、「lineTo」メソッドのラインは「QPen」の色で塗られ、囲った部分は「QBrush」の色で塗り潰されます(図3)。

図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)。指定したフォントが存在しない場合は、デフォルトのフォントが使われます。

図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メソッドも使えます。

図5:会話中のフキダシの描画

・サンプルスクリプト「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回で作ったランダムに無数の正方形を描画するサンプルのような場合は、プログラミングした方がはるかに簡単な場合もあります。

著者
大西 武 (オオニシ タケシ)
1975年香川県生まれ。大阪大学経済学部経営学科中退。プログラミング入門書など30冊以上を商業出版する作家。Microsoftで大賞やNTTドコモでグランプリなど20回以上全国区のコンテストに入賞するアーティスト。オリジナルの間違い探し「3Dクイズ」が全国放送のTVで約10回出題。
https://profile.vixar.jp

連載バックナンバー

開発言語技術解説
第10回

「Krita」と「Python」のクラスで画像を加工しよう

2024/12/13
第10回の今回は「Krita」で「Python」のクラスを使ってプログラミングして、開いた画像ファイルに擦りガラス加工などをする解説をします。
開発言語技術解説
第9回

「Krita」と「Python」でクラスの文法を身につけよう

2024/11/22
第9回の今回は「Krita」と「Python」で、オブジェクト指向プログラミングができる「クラス」の文法を解説します。
開発言語技術解説
第8回

「Krita」と「Python」でUIパーツを構築してみよう

2024/11/1
第8回の今回は「Krita」で「Python」をプログラミングして、ボタンやスライダーなどのUIパーツを構築する解説をします。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています