#python #Pillow #画像 #直線 #line #色 #RGB
この記事では、pythonのPILというモジュール(Pillow)で直線を描く方法について、わかりやすく解説します。
絵を描くための準備
本記事では、画像を描くための準備は、下記のプログラムで行います。
<ファイル名:line.py>
from PIL import Image, ImageDraw # PILからImage, ImageDrawクラスを取り出す
im = Image.new('RGB', ( 500, 600), (255, 255, 255)) # 500 ✕ 600ドットで白(=(255,255,255)の
# キャンバスを準備する
draw = ImageDraw.Draw(im) # 絵を描き込むバケツ(ImageDraw.Drwオブジェクト)を準備する
im.show() # 画面に表示する
実行は以下のようにして行います。
python line.py
結果は以下のとおり、真っ白な500✕600ドットのキャンバスが表示されます。
なお、小生のPCでは画像表示にXnViewというソフトを使っているため、プログラムで、im.show()関数を実行したときに、これが起動されました。
読者の方は、違うソフトが起動されると思います。
円を描くことに集中するため、この部分のプログラムの詳細な説明はここではしません。コメントに簡単な説明をしていますので、参照してください。
詳しい説明が必要な場合は、『【第4回】にっこちゃんを描いてやんわりと関数・モジュールを学ぶ(解説編)』の『画像を描くキャンバスの作成』以降を参照してください。
座標について
画像の表示で使う座標は、下図のように
左上が (x, y)=(0, 0) で、
左に行くにつれて x座標が増え、
下に行くにつれて y座標が増えます。
中学で学ぶ数学の座標では、y座標は下が0で、上に行くにつれて増えますが、コンピュータでは、逆になるため、注意が必要です。
直線を描いてみる
Pillowで直線を描くには、line関数を使います。
以下の入力が必要です。
・ 直線の開始位置x座標, y座標
・ 直線の終了位置x座標, y座標
・ 直線の色の赤(Red)成分, 緑(Green)成分, 青(Blue)成分
プログラム中に書くと、以下になります。
draw.line( ( (直線の開始位置x座標, y座標), (直線の終了位置x座標, y座標) ),
fill=( 直線の色のRed成分, Green成分, Blue成分))
実は直線の座標の指定方法にはいくつかの書式がありますが、この記事では現時点で最も問題のない(Pillowのバグを発生させない)書式で記載しています。
では、まずオーソドックスに、
キャンバスの左上(0, 0)から、(500, 500)に黒い線を描いてみます。
以下の行を付け加えます。
draw.line((( 0, 0),( 500, 500)), fill=( 0, 0, 0))
line関数の最初の入力 (( 0, 0), ( 500, 500)) は、
直線の開始位置x座標, y座標, 直線の終了位置x座標, y座標
です。
括弧 ”(“, “)” が外側に余分についていますが、これについては後述します。
2つ目の入力 fill=( 0, 0, 0) は、
fill=(直線の色の赤(Red成分), 緑(Green成分), 青(Blue成分)),
です。
Red成分, Green成分, Blue成分の全てが0の場合に黒となります。
付け加えた後のプログラムは以下になります。
<ファイル名:line.py>
from PIL import Image, ImageDraw # PILからImage, ImageDrawクラスを取り出す
im = Image.new('RGB', ( 500, 600), (255, 255, 255)) # 500 ✕ 600ドットで白(=(255,255,255)の
# キャンバスを準備する
draw = ImageDraw.Draw(im) # 絵を描き込むバケツ(ImageDraw.Drwオブジェクト)を準備する
draw.line((( 0, 0),( 500, 500)), fill=( 0, 0, 0))
im.show() # 画面に表示する
実行した結果は以下のとおりに表示されます。
直線をいくつか描いてみる
直線をいくつか描いてみます。
座標をずらして5つ追加して並べます。
<ファイル名:line.py>
from PIL import Image, ImageDraw # PILからImage, ImageDrawクラスを取り出す
im = Image.new('RGB', ( 500, 600), (255, 255, 255)) # 500 ✕ 600ドットで白(=(255,255,255)の
# キャンバスを準備する
draw = ImageDraw.Draw(im) # 絵を描き込むバケツ(ImageDraw.Drwオブジェクト)を準備する
draw.line((( 0, 0),( 500, 500)), fill=( 0, 0, 0))
draw.line((( 83, 0),( 416, 500)), fill=( 0, 0, 0))
draw.line((( 166, 0),( 333, 500)), fill=( 0, 0, 0))
draw.line((( 250, 0),( 250, 500)), fill=( 0, 0, 0))
draw.line((( 333, 0),( 166, 500)), fill=( 0, 0, 0))
draw.line((( 416, 0),( 83, 500)), fill=( 0, 0, 0))
draw.line((( 500, 0),( 0, 500)), fill=( 0, 0, 0))
im.show() # 画面に表示する
プログラムの着色部分を見るとわかりますが、x座標を83ずつずらしました。
結果は以下のとおりになります。
色を変える
黒では面白くないので、色を変えてみます。
色は、赤(Red), 緑(Green), 青(Blue)の3色を指定して混ぜた色になります。
それぞれ、0 ~ 255の数値で濃さを指定します。
ただしこの色は、光を重ね合わせて混ぜる色になるため、絵の具で混ぜた色とは異なります。たとえば、絵の具の場合は、赤、緑、青を全て混ぜると黒くなりますが、パソコンのモニタやスマホの画面では、白になります。
この色の指定形式をRGB形式といい、pythonに限らず、どのような言語で使用する場合でも共通です。
では、色を0か255にして順に変えてみます。
<ファイル名:line.py>
from PIL import Image, ImageDraw # PILからImage, ImageDrawクラスを取り出す
im = Image.new('RGB', ( 500, 600), (255, 255, 255)) # 500 ✕ 600ドットで白(=(255,255,255)の
# キャンバスを準備する
draw = ImageDraw.Draw(im) # 絵を描き込むバケツ(ImageDraw.Drwオブジェクト)を準備する
draw.line((( 0, 0),( 500, 500)), fill=( 0, 0, 0))
draw.line((( 83, 0),( 416, 500)), fill=(255, 0, 0))
draw.line((( 166, 0),( 333, 500)), fill=( 0, 255, 0))
draw.line((( 250, 0),( 250, 500)), fill=( 0, 0, 255))
draw.line((( 333, 0),( 166, 500)), fill=(255, 255, 0))
draw.line((( 416, 0),( 83, 500)), fill=( 0, 255, 255))
draw.line((( 500, 0),( 0, 500)), fill=(255, 255, 255))
im.show() # 画面に表示する
結果は以下となります。
黄色が見えにくいですねえ。
あと、実は消えているように見えるところは白です。
せっかくなので、0と255だけではなく、中間の色も表示してみます。
量が多くなるので、プログラムが見にくくなりますが、受け入れてください。
<ファイル名:line.py>
from PIL import Image, ImageDraw # PILからImage, ImageDrawクラスを取り出す
im = Image.new('RGB', ( 500, 600), (255, 255, 255)) # 500 ✕ 600ドットで白(=(255,255,255)の
# キャンバスを準備する
draw = ImageDraw.Draw(im) # 絵を描き込むバケツ(ImageDraw.Drwオブジェクト)を準備する
draw.line((( 0, 0),( 500, 500)), fill=( 0, 0, 0))
draw.line((( 41, 0),( 458, 500)), fill=(128, 0, 0))
draw.line((( 83, 0),( 416, 500)), fill=(255, 0, 0))
draw.line((( 124, 0),( 375, 500)), fill=(128, 128, 0))
draw.line((( 166, 0),( 333, 500)), fill=( 0, 255, 0))
draw.line((( 207, 0),( 292, 500)), fill=( 0, 128, 128))
draw.line((( 250, 0),( 250, 500)), fill=( 0, 0, 255))
draw.line((( 291, 0),( 208, 500)), fill=( 1, 0, 128))
draw.line((( 333, 0),( 166, 500)), fill=(255, 255, 0))
draw.line((( 375, 0),( 124, 500)), fill=(128, 255, 128))
draw.line((( 416, 0),( 83, 500)), fill=( 0, 255, 255))
draw.line((( 458, 0),( 41, 500)), fill=(128, 255, 255))
draw.line((( 500, 0),( 0, 500)), fill=(255, 255, 255))
im.show() # 画面に表示する
実行結果です。
いくつかの中間色の線が描画できました。
計算をして線をもっと細かく表示させる
上述のように、線の座標を何度も繰り返して書くのは非常に疲れます。
そのため、せっかくですので、線の座標を計算で求めてみます。
繰り返し座標を変えていくためにはwhile文かfor文を使いますが、ここではfor文が適しているため、for文を使います。
値の集まり[集合]の数だけ、[行]を実行するという文法は、下記になります。
[集合]から、値を1つずつ[値]に取り出して、[行]の中で使って処理します。
for [値] in [集合] :
[行]
[行]
今回の線画では、x座標を一定の値で増やしたり、減らしたりする必要があります。
一定の値で増加する値の集まりは、以下のrange関数で作ることができます。
range( [開始値], [終了値]+1, [増分] )
range関数は、[開始値]から[終了値]まで、[増分]で増加する値を生成できます。
[終了値]ではなく、[終了値]+1を指定するところに注意が必要です。
このrange関数を以下のようにfor文の[集合]に指定することで、
[開始値]から[増分]で増加させつつ、[終了値]まで[行]を実行する
という処理を行なうことができます。
for [値] in range( [開始値], [終了値]+1, [増分] ) :
[行]
[行]
実際のプログラムを書いてみます。
0から500まで1ドットずつ増加させて線を描画します。
値は変数deltaに取り出して、(delta, 0)から{500-delta, 500)まで、黒=(0,0,0)の線で描画します。
for delta in range( 0, 501, 1) :
draw.line((( delta, 0),( 500-delta, 500)), fill=(0, 0, 0))
完全なプログラムは以下になります。
<ファイル名:line.py>
from PIL import Image, ImageDraw # PILからImage, ImageDrawクラスを取り出す
im = Image.new('RGB', ( 500, 600), (255, 255, 255)) # 500 ✕ 600ドットで白(=(255,255,255)の
# キャンバスを準備する
draw = ImageDraw.Draw(im) # 絵を描き込むバケツ(ImageDraw.Drwオブジェクト)を準備する
for delta in range( 0, 501, 1) :
draw.line((( delta, 0),( 500-delta, 500)), fill=(0, 0, 0))
im.show() # 画面に表示する
実行結果は以下になります。
黒のみで描画したため、塗りつぶされたようになりました。
虹色にしてしまう
色が黒のままでは地味ですので、この際、虹色にします。
ただし、虹色にする場合も、色の値(色値)を細かく指定することになるため、座標の場合と同じようにかなり大変なプログラムになります。
ここは、徐々に色値を変えるように計算させてみます。
計算は以下のプログラムで行います。
なお、下記プログラムでは、print関数で色値を表示させています。
red = 0
green = 0
blue = 0
red_step = 5
green_step = 5
blue_step = -5
red_cnt = 0
green_cnt = -250
blue_cnt = 250
for delta in range( 0, 501, 1) :
red_cnt = red_cnt + red_step
if red_cnt > 0 :
red = red + red_step
if red > 250 :
red = 250
if red < 0 :
red = 0
if red_cnt >= 500 or red_cnt <= -250 :
red_step = -red_step
green_cnt = green_cnt + green_step
if green_cnt > 0 :
green = green + green_step
if green > 250 :
green = 250
if green < 0 :
green = 0
if green_cnt >= 500 or green_cnt <= -250 :
green_step = -green_step
blue_cnt = blue_cnt + blue_step
if blue_cnt > 0 :
blue = blue + blue_step
if blue > 250 :
blue = 250
if blue < 0 :
blue = 0
if blue_cnt >= 500 or blue_cnt <= -250 :
blue_step = -blue_step
print("red , green, blue = " + str(red) + " " + str(green) + " " + str(blue) )
上記のプログラムはおおよそ、以下の処理を行っています。
ここでは、線を描くことに集中するものとして、これ以上の詳細については解説しません。
1.赤(red)、緑(green)、青(blue)ごとにカウンタ(red_cnt, green_cnt, blue_cnt)を用意
2.for文の中でカウントアップする
3.カウンタが0より大きくなったら色値(red, green, blue)を
増減値(red_step, green_step, blue_step)だけ増やす(または減らす)
4.色値が250を超えたら250に、0未満になったら0にする
(色値を指定可能な値の範囲に維持する)
5.カウンタが500以上、または-250以下になった場合、増減値の符号を反転させる
実行結果は以下になります。
red , green, blue = 5 0 0
red , green, blue = 10 0 0
red , green, blue = 15 0 0
red , green, blue = 20 0 0
red , green, blue = 25 0 0
red , green, blue = 30 0 0
red , green, blue = 35 0 0
:
red , green, blue = 235 0 0
red , green, blue = 240 0 0
red , green, blue = 245 0 0
red , green, blue = 250 0 0
red , green, blue = 250 5 0
red , green, blue = 250 10 0
red , green, blue = 250 15 0
red , green, blue = 250 20 0
red , green, blue = 250 25 0
:
red , green, blue = 15 250 0
red , green, blue = 10 250 0
red , green, blue = 5 250 0
red , green, blue = 0 250 0
red , green, blue = 0 245 5
red , green, blue = 0 240 10
red , green, blue = 0 235 15
red , green, blue = 0 230 20
:
以上により、赤、緑、青の成分をバラバラに増減することで、虹色が作成できます。
ちなみに、赤、緑、青を一斉に増減させてしまうと、黒⇔白のグラデーションになってしまいます。
もとの描画プログラムに上記の計算部分を入れたものが、以下になります。
<ファイル名:line.py>
from PIL import Image, ImageDraw # PILからImage, ImageDrawクラスを取り出す
im = Image.new('RGB', ( 500, 600), (255, 255, 255)) # 500 ✕ 600ドットで白(=(255,255,255)の
# キャンバスを準備する
draw = ImageDraw.Draw(im) # 絵を描き込むバケツ(ImageDraw.Drwオブジェクト)を準備する
red = 0
green = 0
blue = 0
red_step = 5
green_step = 5
blue_step = -5
red_cnt = 0
green_cnt = -250
blue_cnt = 250
for delta in range( 0, 501, 1) :
red_cnt = red_cnt + red_step
if red_cnt > 0 :
red = red + red_step
if red > 250 :
red = 250
if red < 0 :
red = 0
if red_cnt >= 500 or red_cnt <= -250 :
red_step = -red_step
green_cnt = green_cnt + green_step
if green_cnt > 0 :
green = green + green_step
if green > 250 :
green = 250
if green < 0 :
green = 0
if green_cnt >= 500 or green_cnt <= -250 :
green_step = -green_step
blue_cnt = blue_cnt + blue_step
if blue_cnt > 0 :
blue = blue + blue_step
if blue > 250 :
blue = 250
if blue < 0 :
blue = 0
if blue_cnt >= 500 or blue_cnt <= -250 :
blue_step = -blue_step
draw.line((( delta, 0),( 500-delta, 500)), fill=(red, green, blue))
draw.line((( 0, delta),( 500, 500-delta)), fill=(red, green, blue))
im.show() # 画面に表示する
実行結果は以下になります。
最後に
いかがでしたでしょうか?
直線を描くだけでも、以下を知る必要がありました。
- 座標の指定方法
- 色の指定方法
これらの要素は、直線以外の描画をおこなうときにも基本的に同じです。
また、
- 座標位置を自動計算する処理
- 虹色を描画する処理
など、プログラミングならではの特別な処理についても解説しました。
ということで、また別の描画方法を解説しますので、ご期待ください!
この記事へのお問い合わせや、無料のサポート、一歩踏み込んだ有料サポートが必要な場合は、ホームページからメールで受け付けています。お気軽にご連絡ください。
コメント