Pythonで画像描画
Pythonを独学して3日目ですが、数式からハートマークを作成する画像描画プログラムを作成してみました。
修正履歴
facebookである方より、アドバイス頂いたので修正しました。
※アドバイスありがとうございました。
[アドバイス内容]
pow()なのか**なのか統一しません?
つーか、なんで混在しているんですか?
違いはわかっていますか?
[修正内容]
「ソースコード」の章に記述した「HeartSymbol()」内のべき乗を「**」に統一しました。
尚、
混在した理由は、べき乗の演算を「**」にすべきか、
「pow」にするべきかを迷っており、そのまま投稿してしまいました。
[新たな発見]
今回のアドバイスで「べき乗」は、「**」と「pow」と「math.pow」があることが分かりました。
この3つの演算の演算速度を測りました。(time.perf_counter_ns()で計測)
「**」 :2,103,908,740 ns (2.103秒)
「pow」:2,153,195,800 ns (2.153秒)
「math.pow」:2,123,512,380 ns (2.123秒)
※各5回実行して、平均した秒数となります。
今回の修正は、実行速度の速い「**」に統一しました。
[新たな疑問]
dis()を使ってコードを見たのですが、「math.pow」が一番多いのに、なぜ速度が2番目に速いのか?
本文の構成(目次)は、以下のようになります。
(出力画像は、下の方に添付しています。)
ソース構成
ソース構成は、以下の通りです。
演算式
ハートマークを生成するための演算式は、以下の通りです。
私がキレイと思うハートマークを作るため、変数xとyに係数を掛けています。
動作説明
プログラムの主な動作について説明します。
※ソースコードは、後述でそのまま添付してしています。
1. 下地を生成
変数xとyの値(座標)から点として色成分(範囲:0~255)を置いていくため、オブジェクトPhotoImage()を使って512×512の下地を作っています。
これにより、メソッドput()を使用してxとy座標に色を設定していきます。
オブジェクトPhotoImageは、PNGなどのファイルを読み込み使用するみたいですが、幅と高さを指定するだけだと、画像を置くための下地を作ってくれるみたいでした。
※tkinterの__init__.plを見ながら施行錯誤しました。
2. 画像生成
出力画像サイズを512×512としたため、X方向とY方向の位置を管理する変数pxとpyを準備しています。
また、
演算式ではX及びY座標を-32~32の範囲にしたかったため、変数txとtyで実際の座標位置を管理しています。
従って、座標精度(座標点の間隔)は0.125となります。(精度=64÷512)
変数txとtyから上記演算式の計算を行い(関数:HeartSymbol)、算出値の整数部を画素値(色値)としています。
ただし、
算出値が255を超える場合は255とし(関数:ClipValue)、"255-算出値"の値をR成分に設定しています。
※中心(座標x,y=0,0)に行くほど算出値が0になる性質の式のため、255から算出値を引いています。
(ハートマークなので、中心に近づくほど「赤色」が良いかな?と思った次第です)
尚、G成分及び及びB成分は0にしています。背景色が黒、ハートマークは赤となり、
中心に近づくほど、赤色が濃くなっていきます。
3. Windowの表示
メインフレームにCanvas領域(512×512)を作成し、上記「2.画像生成」で作成した画像を乗せています。
また、
ユーザーがメインフレームサイズを小さくした場合でも、視覚範囲で画像が見れるようスクロールバーを追加しました。
ファイル保存
画像表示だけでなくファイルにも保存したかったため、メソッドwriteを使ってPNG形式で保存しています。
補足)
オブジェクトPhotoImage()に「幅」と「高さ」のみを設定すると、データタイプは「photo」になりました。(おそらく、初期値だと思っています。)
RGBの設定がしたかったため、そのままにしていますが、「bitmap」(2値画像)にする方法までは確認していません。
ソースコード
ソースコードを以下に示します。
モジュール「csv」をインポートしていますが、演算結果などをExcelに保存して確認したかったために使用しました。下記ソース内では、モジュールcsvを使用したコードは削除しています。
import math import tkinter as tk import csv #-- #-- 関数 #----------------------------------------------------------- # @@ 座標からハートマークを生成する。 # @@ # @@ f(x,y) = (x*a)^2 + ((y*b) - ((x*c)^2)^(1/3))^2 # @@ a,b,c : 係数 # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ def HeartSymbol( PointX , # X座標 PointY # Y座標 ): # べき乗を「**」に統一 ######################################### ResVal = (PointX*0.60)**2 + (PointY*0.75 - (((PointX*1.5)**2)**(1/3)))**2 # ResVal = pow( PointX*0.60 ,2) + \ # pow((PointY*0.75 - pow(pow((PointX*1.5),2),1/3)),2) # ResVal = math.pow( PointX*0.60 ,2) + \ # math.pow((PointY*0.75 - math.pow(math.pow((PointX*1.5),2),1/3)),2) return ResVal # @@ クリップ処理 # @@ 最大値から最小値内のValueを返す # @@ 変数Valueが最大値以上、最大値を返す。 # @@ 変数Valueが最小値未満、最小値を返す。 # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ def ClipValue( Value , # 入力値 MaxVal , # 最大値 MinVal # 最小値 ): if( Value > MaxVal ): ResVal = MaxVal elif( Value < MinVal ): ResVal = MinVal else: ResVal = Value return ResVal #-- #-- Window 生成 #----------------------------------------------------------- class WindowApp(tk.Frame): def __init__(self, master = None , # メインWindow title = "Nao-Milk" , # Windowタイトル width = 64 , # Canvasの幅 height = 32 , # Canvasの高さ filename = "play.png" # 出力画像ファイル名 ): super().__init__(master) self.pack() self.master.title(title) self.master.geometry("640x640") # [ Create Screen ] self.image = tk.PhotoImage(width=width,height=height) #++ 下地作成 self.sizex = self.image.width() #++ 下地の幅(確認) self.sizey = self.image.height() #++ 下地の高さ(確認) self.type = self.image.type() #++ 下地のType(確認) #++ [e.g. "photo" or "bitmap".] print(" --> W x H = ",self.sizex, self.sizey , "type[",self.type ,"]") # [ Generate Image ] R成分のみ演算 #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HalfTX = 32.0; #++ X方向 座標領域の半分 HalfTY = 32.0; #++ Y方向 座標領域の半分 HalfPX = int(self.sizex/2); HalfPY = int(self.sizey/2); #++ 出力領域の半分 StartX = HalfTX*-1; StartY = HalfTY* 1; #++ 座標の開始位置 ResolX = (1/HalfPX*HalfTX)* 1; ResolY = (1/HalfPY*HalfTY)*-1 #++ 座標間隔 ty = StartY for py in range(0,self.sizey): tx = StartX for px in range(0,self.sizex): # ----- 演算 ----- fr = HeartSymbol(tx,ty) fg = 0 fb = 0 r = 255-ClipValue(math.floor(fr),255,0) #++ R成分 g = ClipValue(math.floor(fg),255,0) #++ G成分 b = ClipValue(math.floor(fb),255,0) #++ B成分 # ----- 出力位置に色を設定 ----- color = "#" + format(r, '02x') + format(g, '02x') + format(b, '02x') self.image.put(color,to=(px,py)) tx = tx + ResolX ty = ty + ResolY #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # [ display graphical elements ] self.canvas = tk.Canvas(self.master,width=self.sizex,height=self.sizey,bg="black") self.canvas.create_image(0,0,image=self.image,anchor=tk.NW) # [ Scrollbar ] bar_y = tk.Scrollbar(self.master, orient=tk.VERTICAL) bar_x = tk.Scrollbar(self.master, orient=tk.HORIZONTAL) bar_y.pack(side=tk.RIGHT , fill=tk.Y) bar_x.pack(side=tk.BOTTOM, fill=tk.X) bar_y.config(command=self.canvas.yview) bar_x.config(command=self.canvas.xview) self.canvas.config(yscrollcommand=bar_y.set, xscrollcommand=bar_x.set) self.canvas.config(scrollregion=(0, 0, self.sizex, self.sizey)) self.canvas.pack(anchor=tk.NW, side=tk.LEFT) # [ File Save ] self.image.write(filename=filename) #-- #-- Main #----------------------------------------------------------- if __name__ == "__main__": Frame1 = tk.Tk() # ++ Toplevel widget WinAp1 = WindowApp( master = Frame1 , # ++ メインWindow title = "Nao-Milk Play2" , # ++ Windowタイトル width = 512 , # ++ Canvasの幅 height = 512 , # ++ Canvasの高さ filename = "play2.png" # ++ 出力画像ファイル名 ) WinAp1.mainloop()
出力画像
プログラムを実行して生成した画像を以下に示します。
最後に
全てのモジュールが分かってないため、もっとスムーズなやり方があるかもしれませんが、今の知識ではここまででした。
尚、「やりたいこと」の思考からネットで「記述例」を調べ参考にしつつ、モジュールのソースも見ながら、試行錯誤しながら作成してみました。
(各モジュールの気持ち[意図]を知るため。)
※Instagramでは、緑のハート、青のハートも載せています。
https://www.instagram.com/nao_nari0504/