Pythonでオリジナルのドット絵エディタを作る(4) - ベクター出力機能の追加

#はじめに
[前回の記事](https://qiita.com/Hisan_twi/items/219eb88c535f9a6171bd)で作成したドット絵エディタに、描いたドット絵をベクター画像（SVG形式）で出力する機能を追加します。。
搭載する機能の仕様は以下の通りです。

- ドットの角ばった形状を保ったままの形で書き出す。
- 色別に複合パスとして書き出す。
- 同じ色が隣接したドットは連結した形状として扱う。
- 白のドットは透明色として扱う（パスとして書き出さない）。

事前にこの機能を実装するためのアルゴリズムを調べたところ、[DEFGHI1977さんのサイト](http://defghi1977.html.xdomain.jp/tech/dotrace/dotrace.htm)で公開されている『ドット絵SVG化スクリプト:dotrace.js』に行き着きました。node.jsのコードとアルゴリズムが公開されているので参考にしようと思いましたが、Javascriptに不慣れなこともありうまくPythonで記述することができませんでした。
そのため、SVG形式への変換は独自の方法で構築することにしました。なので、本記事で解説する方法は最適ではないかもしれないことをあらかじめご留意ください。

#環境

前回から使用している環境に加えてsvgwriteライブラリを使用します。
svgwriteは事前にインストールする必要があります。
pipでインストール可能です。

```bash
pip3 install svgwrite
```

#解説
SVG形式に変換する手順を解説します。

###色分け、2値化
まず、原画像キャンバスの画像の全セルを調べ、透明色である白以外のセルを色別に抜き出します。抜き出したセルは、次のラベリング処理で扱いやすいように、背景を黒、抜き出したセルを白とした2値化画像にします。

```python
		imgSize = self.canvas.imgSize
		alphaColor = QColor(255, 255, 255)		#透明色の指定
		white = QColor(255, 255, 255)
		black = QColor(0, 0, 0)
		colorList = []
		codedImg = []
		#画像を色分け
		for j in range(self.canvas.imgSize):
			for i in range(self.canvas.imgSize):
				targetColor = pixImg.pixelColor(i, j)
				if not targetColor == alphaColor:
					if not targetColor in colorList:
						colorList.append(targetColor)
						codedImg.append(QImage(QSize(imgSize, imgSize), QImage.Format_Grayscale8))
						codedImg[-1].fill(black)
					codedImg[colorList.index(targetColor)].setPixelColor(i, j, white)
```

###ラベリング
同じ色が連結したセルを塊ごとに検出するためラベリングを行います。ラベリングはOpenCVのconnectedComponentsWithAlgorithm関数を使います。この関数の詳細は[こちらの記事](https://qiita.com/spc_ehara/items/72e878fc131b151c01fa)が参考になります。近傍指定は4近傍を指定します。

```python
			arrImg = qimage_to_cv(qimage)
			h, w = arrImg.shape[:2]
			#４近傍でラベリング
			retval, labels = cv2.connectedComponentsWithAlgorithm(arrImg, 4, cv2.CV_16U, cv2.CCL_DEFAULT)	
```
cv2.connectedComponentsWithAlgorithmの第１戻り値`retval`はラベル数で、第２戻り値`labels`は塊ごとにセルの値がラベル番号に変換された画像となります。次の輪郭抽出の処理も2値化画像しか扱えないので、`numpy.where`関数でラベル番号ごとの2値化画像に変換します。

```python
			for m in range(1,retval):
				image = np.uint8(np.where(labels == m, 255, 0))
```

以降は下の画像を例に画像処理の様子を説明します（わかりやすいように列と行に番号を振っています）。なお、画像は処理中のものを表しており、入出力画像はこれの白黒が反転した絵となります。
![SVG変換-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484445/b6b22a1c-75b7-6eb5-c13e-40306b2b8928.png)



###セルの4分割
各セルを4分割するため、OpenCVの`resize`関数を使って画像のサイズを縦2倍・横2倍に変換します。オプションの補間方法(`interpolation`)には最近傍補間(`cv2.INTER_NEAREST`)を指定します。

```python
				#画像サイズを最近傍補間で縦横２倍に変換
				resizeImg = cv2.resize(image, (w*2, h*2), interpolation=cv2.INTER_NEAREST)
```
例の画像は下の画像のように変換されます。
![SVG変換-2_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484445/3d96ef1a-1186-e0a0-5eb5-2c65a69b48c1.png)

###輪郭抽出
連結したセルの輪郭を抽出します。輪郭抽出にはOpenCVの`findContours`関数を使います。この関数の動作は[こちらの記事](https://qiita.com/anyamaru/items/fd3d894966a98098376c)が参考になります。抽出モードは`cv2.RETR_CCOMP`、近似手法は`cv2.CHAIN_APPROX_SIMPLE`を指定します。

```python
				#輪郭抽出
				contours, _ = cv2.findContours(resizeImg, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
```
輪郭抽出を行うと輪郭のセルの位置が配列で書き出されます。近似手法を`cv2.CHAIN_APPROX_SIMPLE`に指定した場合は、角以外のセルが省かれます。例の画像は下に示す画像のように輪郭抽出が行われます。外周をピンク色、内周を水色で表しています。書き出しが行われるセル位置を点で表し、その間を矢印で結んでいます。厄介なことに、`findContours`関数は8近傍にしか対応していないため、角に斜め方向の移動が入っています。
![SVG変換-2_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484445/2b6c1cb9-c7d3-0726-2887-0430e76b8e78.png)


###複合パスへの変換
SVG形式の複合パスは、縦横の線のみを扱う場合、M(x,y)で開始位置(x,y)に移動、H(x)でxの位置まで移動して水平方向の線を描く、V(y)でyの位置まで移動して垂直方向の線を描く、Zでパスを閉じるというコマンドの記述の仕方をします。詳しくは[svgwriteの公式ドキュメント](https://svgwrite.readthedocs.io/en/latest/classes/path.html)を参照してください。抽出した輪郭情報を複合パスに変換するため、下の画像の赤と青の矢印で示した軌跡に変換します。
![SVG変換-2_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484445/c71d4183-6e9f-1184-ba70-26371a0527ef.png)

輪郭情報の配列(`contour`)から複合パスのコマンド記述(`path`)に変換する手順を示します。まず、`contour`を順番に読み込みます。`contour`の最初のセルは初期位置とするためM(x,y)を記述します。

```python
path += "{0},{1}".format(contour[0, 0, 0], contour[0, 0, 1])
```
`contour`の２番目以降のセルは１つ前のセルを差し引いてベクトルを作ります。

```python
v = contour[i, 0] - contour[i-1, 0]
```
作ったベクトルの方向を調べます。水平方向または垂直方向ならばその方向をコマンドとして記述します。ただし、右方向または下方向の場合は移動量が1増えるように補正します。

```python
if v[1] == 0 :
	if v[0] > 0:	#右方向
		path += "H{0}".format(contour[i, 0, 0] + 1)
	else:			#左方向
		path += "H{0}".format(contour[i, 0, 0])
elif v[0] == 0:
	if v[1] > 0:	#下方向
		path += "V{0}".format(contour[i, 0, 1] + 1)
	else:			#上方向
		path += "V{0}".format(contour[i, 0, 1])
```
ベクトルの方向が斜め方向だった場合、前のコマンドの位置を削除して同じ方向で新たな移動先位置を指定しなおします。上の場合とは逆で、左方向および上方向へ移動量が1減るように補正します。ただし、前のコマンドがMの場合はベクトルの方向に応じて初期位置をずらして記述しなおします。

```python
elif v[0] == v[1]:
	if v[0] > 0:	#右下方向
		if "H" in path:
			path = path[:path.rfind("H")]
			path += "H{0}".format(contour[i, 0, 0])
		else:
			path = "M{0},{1}".format(contour[0, 0, 0] + 1, contour[0, 0, 1] + 1)
	else:			#左上方向
		if "H" in path:
			path = path[:path.rfind("H")]
			path += "H{0}".format(contour[i, 0, 0] + 1)
elif v[0] != v[1]:
	if v[0] > 0:	#右上方向
		if "V" in path:
			path = path[:path.rfind("V")]
			path += "V{0}".format(contour[i, 0, 1] + 1)
		else:
			path = "M{0},{1}".format(contour[0, 0, 0] + 1, contour[0, 0, 1])
	else:			#左下方向
		if "V" in path:
			path = path[:path.rfind("V")]
			path += "V{0}".format(contour[i, 0, 1])
		else:
			path = "M{0},{1}".format(contour[0, 0, 0], contour[0, 0, 1] + 1)
```
以上の処理をまとめて`makePath`関数として定義します。

```python:makePath
	def makePath(self, contour):
		path = "M"
		for i in range(len(contour)):
			if i == 0:
				path += "{0},{1}".format(contour[0, 0, 0], contour[0, 0, 1])
			else:
				v = contour[i, 0] - contour[i-1, 0]
				if v[1] == 0 :
					if v[0] > 0:	#右方向
						path += "H{0}".format(contour[i, 0, 0] + 1)
					else:			#左方向
						path += "H{0}".format(contour[i, 0, 0])
				elif v[0] == 0:
					if v[1] > 0:	#下方向
						path += "V{0}".format(contour[i, 0, 1] + 1)
					else:			#上方向
						path += "V{0}".format(contour[i, 0, 1])
				elif v[0] == v[1]:
					if v[0] > 0:	#右下方向
						if "H" in path:
							path = path[:path.rfind("H")]
							path += "H{0}".format(contour[i, 0, 0])
						else:
							path = "M{0},{1}".format(contour[0, 0, 0] + 1, contour[0, 0, 1] + 1)
					else:			#左上方向
						if "H" in path:
							path = path[:path.rfind("H")]
							path += "H{0}".format(contour[i, 0, 0] + 1)
				elif v[0] != v[1]:
					if v[0] > 0:	#右上方向
						if "V" in path:
							path = path[:path.rfind("V")]
							path += "V{0}".format(contour[i, 0, 1] + 1)
						else:
							path = "M{0},{1}".format(contour[0, 0, 0] + 1, contour[0, 0, 1])
					else:			#左下方向
						if "V" in path:
							path = path[:path.rfind("V")]
							path += "V{0}".format(contour[i, 0, 1])
						else:
							path = "M{0},{1}".format(contour[0, 0, 0], contour[0, 0, 1] + 1)
		path += "Z"
		return path
```

###SVGファイルへの出力
SVGファイルへの出力はsvgwriteの`Drawing`オブジェクトを用意し、`add`で複合パスのエレメントを追加して、`save()`で保存します。複合パスを塗りつぶすにはfillを指定する必要があるので、色分けの際に作成した`colorList`から色情報を取得して指定します。

```python
color = colorList[n].getRgb()
fill = "rgb" + str(color[0:3])
dwg = svgwrite.Drawing(fileName, size=(imgSize*2, imgSize*2), profile="tiny")
dwg.add(dwg.path(d=path, fill=fill))
dwg.save()
```
※この方法ではfillが10進数のrgb(_r_,_g_,_b_)形式でしています。ブラウザーやAdobe Illustrator等で扱う場合には問題無く表示されますが、Blenderにインポートする場合は色が反映されません。Blenderでも色を反映させたい場合は16進数の色コードに変換してください。

以上の変換処理をまとめて`pixels2svg`関数として定義します。

```python:pixels2svg
	def pixels2svg(self, pixImg, fileName):
		imgSize = self.canvas.imgSize
		alphaColor = QColor(255, 255, 255)		#透明色の指定
		white = QColor(255, 255, 255)
		black = QColor(0, 0, 0)
		colorList = []
		codedImg = []
		#画像を色分け
		for j in range(self.canvas.imgSize):
			for i in range(self.canvas.imgSize):
				targetColor = pixImg.pixelColor(i, j)
				if not targetColor == alphaColor:
					if not targetColor in colorList:
						colorList.append(targetColor)
						codedImg.append(QImage(QSize(imgSize, imgSize), QImage.Format_Grayscale8))
						codedImg[-1].fill(black)
					codedImg[colorList.index(targetColor)].setPixelColor(i, j, white)
		dwg = svgwrite.Drawing(fileName, size=(imgSize*2, imgSize*2), profile="tiny")
		n = 0
		for qimage in codedImg:
			path = ""
			arrImg = qimage_to_cv(qimage)
			h, w = arrImg.shape[:2]
			#４近傍でラベリング
			retval, labels = cv2.connectedComponentsWithAlgorithm(arrImg, 4, cv2.CV_16U, cv2.CCL_DEFAULT)	
			for m in range(1,retval):
				image = np.uint8(np.where(labels == m, 255, 0))
				#画像サイズを最近傍補間で縦横２倍に変換
				resizeImg = cv2.resize(image, (w*2, h*2), interpolation=cv2.INTER_NEAREST)
				#輪郭抽出
				contours, _ = cv2.findContours(resizeImg, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
				for contour in contours:
					path += self.makePath(contour)
			color = colorList[n].getRgb()
			fill = "rgb" + str(color[0:3])
			dwg.add(dwg.path(d=path, fill=fill))
			n += 1
		dwg.save()
```

#出力結果
以上の変換処理を行うことでSVGファイルが出力されます。例で用いた画像は下記に示す内容で出力されます。

```:svgsample.svg
<?xml version="1.0" encoding="utf-8" ?>
<svg baseProfile="tiny" height="64" version="1.2" width="64" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs /><path d="M4,2V4H2V10H4V12H10V10H12V4H10V2ZM6,4H8V6H10V8H8V10H6V8H4V6H6Z" fill="rgb(0, 0, 0)" /></svg>
```

このファイルをAdobe Illustratorで読み込んでみると元の絵が複合パスとして描き出されていることがわかります。
![スクリーンショット 2021-08-22 18.05.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484445/91b9c821-8c8c-9ec1-b5b9-f7473812790a.png)

#Blenderへのインポート
作成したSVGファイルはBlenderで読み込んで立体化することが可能です。
手順は以下のとおりです。

- Blenderで「ファイル」→「インポート」→「Scalable Vector Graphics(.svg)」をクリック
- ダイアログで作成したSVGファイルを選択して「SVGでインポート」をクリック
- 追加されたカーブを選択し、オブジェクトメニューから「変換」→「メッシュ」をクリック
- 編集モードに切り替えて「押し出し」を実行して厚みをつける。

この様子は下のツィートの埋め込み動画を参照してください。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">このドット絵エディタにはFFT機能の他に、ドット絵をそのままSVG形式でエクスポートする機能があります。 <a href="https://t.co/cgJuAudnKa">pic.twitter.com/cgJuAudnKa</a></p>&mdash; ヒサン＠電子材料・デバイスbot (@Hisan_twi) <a href="https://twitter.com/Hisan_twi/status/1421123486857129984?ref_src=twsrc%5Etfw">July 30, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#全体のソースコード
今回の作成部分を追加したドット絵エディタの全体のソースコードを示します。

```python:SVG-paint.py
import sys
import copy
import math
import cv2
import numpy as np
import svgwrite
from collections import deque
from PySide6.QtCore import *
from PySide6.QtWidgets import *
from PySide6.QtGui import *

class MainWindow(QMainWindow):
	def __init__(self, parent=None):
		super(MainWindow, self).__init__(parent)

		self.canvas = Canvas()
		self.setCentralWidget(self.canvas)
		self.setFocus()
		self.setFocusPolicy(Qt.ClickFocus)
		#ウィンドウ設定
		self.initUI()

	def initUI(self):
		self.setGeometry(100, 200, 1800, 650)
		self.setWindowTitle("PAINTapl")
		#メニューバーの設定
		menubar = self.menuBar()
		openAct = QAction("&画像開く", self)
		openPowerImgAct = QAction("&パワースペクトルを読み込む", self)
		openPhaseImgAct = QAction("&位相像を読み込む", self)
		saveAct = QAction("&保存", self)
		savePowerImgAct = QAction("&パワースペクトルを保存", self)
		savePhaseImgAct = QAction("&位相像を保存", self)
		exportAct = QAction("&SVG形式でエクスポート", self)
		exitAct = QAction("&終了", self)
		openAct.setShortcut("Ctrl+O")
		openAct.triggered.connect(self.openFile)
		openPowerImgAct.triggered.connect(self.openPowerImg)
		openPhaseImgAct.triggered.connect(self.openPhaseImg)
		saveAct.setShortcut("Ctrl+S")
		saveAct.triggered.connect(self.saveFile)
		savePowerImgAct.triggered.connect(self.savePowerImg)
		savePhaseImgAct.triggered.connect(self.savePhaseImg)
		exportAct.triggered.connect(self.exportFile)
		exitAct.triggered.connect(QCoreApplication.instance().quit)
		fileMenu = menubar.addMenu("&ファイル")
		fileMenu.addAction(openAct)
		fileMenu.addAction(openPowerImgAct)
		fileMenu.addAction(openPhaseImgAct)
		fileMenu.addAction(saveAct)
		fileMenu.addAction(savePowerImgAct)
		fileMenu.addAction(savePhaseImgAct)
		fileMenu.addAction(exportAct)
		fileMenu.addAction(exitAct)

		#画像ラベル
		font = QFont()
		font.setPointSize(20)
		self.ImgLabel = QLabel(self)
		self.ImgLabel.move(378, 570)
		self.ImgLabel.resize(200, 50)
		self.ImgLabel.setText("原画像")
		self.ImgLabel.setFont(font)
		self.PowerImgLabel = QLabel(self)
		self.PowerImgLabel.move(885, 570)
		self.PowerImgLabel.resize(200, 50)
		self.PowerImgLabel.setText("パワースペクトル")
		self.PowerImgLabel.setFont(font)
		self.PhaseImgLabel = QLabel(self)
		self.PhaseImgLabel.move(1450, 570)
		self.PhaseImgLabel.resize(200, 50)
		self.PhaseImgLabel.setText("位相像")
		self.PhaseImgLabel.setFont(font)
		#FFTボタン設定
		self.FFTButton = QPushButton(self)
		self.FFTButton.move(594, 570)
		self.FFTButton.resize(70, 30)
		self.FFTButton.setText("FFT")
		self.FFTButton.clicked.connect(self.performeFFT)
		#IFFTボタン設定
		self.IFFTButton = QPushButton(self)
		self.IFFTButton.move(700, 570)
		self.IFFTButton.resize(70, 30)
		self.IFFTButton.setText("IFFT")
		self.IFFTButton.clicked.connect(self.performeIFFT)
		#ライブFFTボタン設定
		self.liveFFTButton = QPushButton(self)
		self.liveFFTButton.move(650, 600)
		self.liveFFTButton.resize(70, 30)
		self.liveFFTButton.setText("ライブFFT")
		self.liveFFTButton.setCheckable(True)
		self.liveFFTButton.clicked.connect(self.liveFFT)
		#原画像のグリッド表示切り替え
		self.cb_image1 = QCheckBox(self)
		self.cb_image1.move(300, 20)
		self.cb_image1.resize(200, 30)
		self.cb_image1.setText("グリッドを表示")
		self.cb_image1.setCheckState(Qt.Checked)
		self.cb_image1.stateChanged.connect(self.gridControl1)
		#パワースペクトルのグリッド表示切り替え
		self.cb_image2 = QCheckBox(self)
		self.cb_image2.move(850, 20)
		self.cb_image2.resize(200, 30)
		self.cb_image2.setText("グリッドを表示")
		self.cb_image2.setCheckState(Qt.Checked)
		self.cb_image2.stateChanged.connect(self.gridControl2)
		#位相像のグリッド表示切り替え
		self.cb_image3 = QCheckBox(self)
		self.cb_image3.move(1370, 20)
		self.cb_image3.resize(200, 30)
		self.cb_image3.setText("グリッドを表示")
		self.cb_image3.setCheckState(Qt.Checked)
		self.cb_image3.stateChanged.connect(self.gridControl3)

		#幅入力のスピンボックス
		self.sb_width = QSpinBox(self)
		self.sb_width.setFocusPolicy(Qt.NoFocus)
		self.sb_width.move(70, 350)
		self.sb_width.resize(40,18)
		self.sb_width.setMinimum(1)
		self.sb_width.valueChanged.connect(self.valueToCanvas)
		self.sb_width_label = QLabel(self)
		self.sb_width_label.move(10, 350)
		self.sb_width_label.resize(50,18)
		self.sb_width_label.setText("横:")
		self.sb_width_label.setAlignment(Qt.AlignRight)
		#スペース入力のスピンボックス
		self.sb_height = QSpinBox(self)
		self.sb_height.setFocusPolicy(Qt.NoFocus)
		self.sb_height.move(70, 370)
		self.sb_height.resize(40,18)
		self.sb_height.setMinimum(1)
		self.sb_height.valueChanged.connect(self.valueToCanvas)
		self.sb_height_label = QLabel(self)
		self.sb_height_label.move(10, 370)
		self.sb_height_label.resize(50,18)
		self.sb_height_label.setText("縦:")
		self.sb_height_label.setAlignment(Qt.AlignRight)
		#スペース入力のスピンボックス
		self.sb_space = QSpinBox(self)
		self.sb_space.setFocusPolicy(Qt.NoFocus)
		self.sb_space.move(70, 390)
		self.sb_space.resize(40,18)
		self.sb_space.setMinimum(1)
		self.sb_space.valueChanged.connect(self.valueToCanvas)
		self.sb_space_label = QLabel(self)
		self.sb_space_label.move(10, 390)
		self.sb_space_label.resize(50,18)
		self.sb_space_label.setText("スペース:")
		self.sb_space_label.setAlignment(Qt.AlignRight)

		#ボタングループの設定
		self.bg_paint = QButtonGroup()
		self.bg_image1 = QButtonGroup()
		self.bg_image2 = QButtonGroup()
		self.bg_image3 = QButtonGroup()
		#ペイントツール用のトグルボタン
		self.dt_paint = [
			(0, 0, 0, "Pen", "Icon_Pen.png"),
            (0, 1, 1, "Line", "Icon_Line.png"),
			(0, 2, 2, "RectLine", "Icon_RectLine.png"),
			(0, 3, 3, "RectFill", "Icon_RectFill.png"),
			(0, 4, 4, "Dropper", "Icon_Dropper.png"),
			(0, 5, 5, "PaintFill", "Icon_PaintFill.png"),
			(0, 6, 6, "Lattice", "Icon_Lattice.png"),
        ]
		for x, y, id, _, iconImg in self.dt_paint:
			qpix = QPixmap.fromImage(iconImg)
			icon = QIcon(qpix)
			btn = QPushButton(icon, "", self)
			btn.move(40*x + 60, 40*y + 60)
			btn.resize(40, 40)
			btn.setIconSize(qpix.size())
			btn.setCheckable(True)
			if id == 0:
				btn.setChecked(True)
			self.bg_paint.addButton(btn, id)
		self.bg_paint.setExclusive(True)  # 排他的なボタン処理にする
		self.bg_paint.buttonClicked.connect(self.changedButton)
		#編集ボタン
		self.dt_image1 = [
			(0, 0, 0, "ClearAll", "Icon_Clear.png"),
			(1, 0, 1, "Previous", "Icon_Previous.png"),
			(2, 0, 2, "Next", "Icon_Next.png"),
        ]
		for x, y, id, _, iconImg in self.dt_image1:
			qpix = QPixmap.fromImage(iconImg)
			icon = QIcon(qpix)
			btn = QPushButton(icon, "", self)
			btn.move(40*x + self.canvas.offsetx_image1, 40*y + 10)
			btn.resize(40, 40)
			btn.setIconSize(qpix.size())
			self.bg_image1.addButton(btn, id)
		self.bg_image1.buttonClicked.connect(self.pushedButton_image1)
		#パワースペクトル用の編集ボタン
		self.dt_image2 = [
			(0, 0, 0, "ClearAll", "Icon_Erase.png"),
			(1, 0, 1, "Previous", "Icon_Previous.png"),
			(2, 0, 2, "Next", "Icon_Next.png"),
        ]
		for x, y, id, _, iconImg in self.dt_image2:
			qpix = QPixmap.fromImage(iconImg)
			icon = QIcon(qpix)
			btn = QPushButton(icon, "", self)
			btn.move(40*x + self.canvas.offsetx_image2, 40*y + 10)
			btn.resize(40, 40)
			btn.setIconSize(qpix.size())
			self.bg_image2.addButton(btn, id)
		self.bg_image2.buttonClicked.connect(self.pushedButton_image2)
		#位相像用の編集ボタン
		self.dt_image3 = [
			(0, 0, 0, "ClearAll", "Icon_Erase.png"),
			(1, 0, 1, "Previous", "Icon_Previous.png"),
			(2, 0, 2, "Next", "Icon_Next.png"),
        ]
		for x, y, id, _, iconImg in self.dt_image3:
			qpix = QPixmap.fromImage(iconImg)
			icon = QIcon(qpix)
			btn = QPushButton(icon, "", self)
			btn.move(40*x + self.canvas.offsetx_image3, 40*y + 10)
			btn.resize(40, 40)
			btn.setIconSize(qpix.size())
			self.bg_image3.addButton(btn, id)
		self.bg_image3.buttonClicked.connect(self.pushedButton_image3)

	def changedButton(self):
		id = self.bg_paint.checkedId()
		self.canvas.drawMode = self.dt_paint[id][3]

	def pushedButton_image1(self, obj):
		id = self.bg_image1.id(obj)
		processMode = self.dt_image1[id][3]
		self.pushedButton(processMode, self.canvas.image1, self.canvas.back1, self.canvas.next1)

	def pushedButton_image2(self, obj):
		id = self.bg_image2.id(obj)
		processMode = self.dt_image2[id][3]
		self.pushedButton(processMode, self.canvas.image2, self.canvas.back2, self.canvas.next2)

	def pushedButton_image3(self, obj):
		id = self.bg_image3.id(obj)
		processMode = self.dt_image3[id][3]
		self.pushedButton(processMode, self.canvas.image3, self.canvas.back3, self.canvas.next3)

	def pushedButton(self, processMode, image, queBack, queNext):
		if processMode == "ClearAll":
			queBack.append(self.canvas.resizeImage(image, image.size()))
			if id(image) == id(self.canvas.image1):
				image.fill(255)
			elif id(image) == id(self.canvas.image2):
				image.fill(0)
				image.setPixelColor(self.canvas.imgSize/2, self.canvas.imgSize/2, QColor(255, 255, 255))
			elif id(image) == id(self.canvas.image3):
				image.fill(127)
		elif processMode == "Previous":
			if queBack:
				back_ = queBack.pop()
				queNext.append(self.canvas.resizeImage(image, image.size()))
				image.swap(back_)
				self.update()
		elif processMode == "Next":
			if queNext:
				next_ = queNext.pop()
				queBack.append(self.canvas.resizeImage(image, image.size()))
				image.swap(next_)
				self.update()
		if self.canvas.check_liveFFT:
			if id(image) == id(self.canvas.image1):
				self.canvas.drawFFT()
			elif id(image) == id(self.canvas.image2) or id(image) == id(self.canvas.image3):
				self.canvas.drawIFFT()

	def gridControl1(self):
		if self.cb_image1.checkState() == Qt.Checked:
			self.canvas.gridOn(self.canvas.image1_grid)
		elif self.cb_image1.checkState() == Qt.Unchecked:
			self.canvas.gridOff(self.canvas.image1_grid)
		self.canvas.update()

	def gridControl2(self):
		if self.cb_image2.checkState() == Qt.Checked:
			self.canvas.gridOn(self.canvas.image2_grid)
		elif self.cb_image2.checkState() == Qt.Unchecked:
			self.canvas.gridOff(self.canvas.image2_grid)
		self.canvas.update()

	def gridControl3(self):
		if self.cb_image3.checkState() == Qt.Checked:
			self.canvas.gridOn(self.canvas.image3_grid)
		elif self.cb_image3.checkState() == Qt.Unchecked:
			self.canvas.gridOff(self.canvas.image3_grid)
		self.canvas.update()

	def openFile(self):
		path = QDir.currentPath()
		fileName, _ = QFileDialog.getOpenFileName(self, "Open File", path)
		if fileName:
			self.canvas.openImage(fileName, self.canvas.image1)

	def openPowerImg(self):
		path = QDir.currentPath()
		fileName, _ = QFileDialog.getOpenFileName(self, "Open File", path)
		if fileName:
			self.canvas.openImage(fileName, self.canvas.image2)

	def openPhaseImg(self):
		path = QDir.currentPath()
		fileName, _ = QFileDialog.getOpenFileName(self, "Open File", path)
		if fileName:
			self.canvas.openImage(fileName, self.canvas.image3)

	def saveFile(self):
		path = QDir.currentPath()
		fileName, _ = QFileDialog.getSaveFileName(self, "Save as", path)
		if fileName:
			if not fileName.endswith(".png"):
				fileName = fileName + ".png"
			return self.canvas.saveImage(fileName, self.canvas.image1)
		return False

	def savePowerImg(self):
		path = QDir.currentPath()
		fileName, _ = QFileDialog.getSaveFileName(self, "Save as", path)
		if fileName:
			if not fileName.endswith(".png"):
				fileName = fileName + ".png"
			return self.canvas.saveImage(fileName, self.canvas.image2)
		return False

	def savePhaseImg(self):
		path = QDir.currentPath()
		fileName, _ = QFileDialog.getSaveFileName(self, "Save as", path)
		if fileName:
			if not fileName.endswith(".png"):
				fileName = fileName + ".png"
			return self.canvas.saveImage(fileName, self.canvas.image3)
		return False

	def exportFile(self):
		path = QDir.currentPath()
		fileName, _ = QFileDialog.getSaveFileName(self, "Save as", path)
		if fileName:
			if not fileName.endswith(".svg"):
				fileName = fileName + ".svg"
			return self.pixels2svg(self.canvas.image1, fileName)
		return False

	def pixels2svg(self, pixImg, fileName):
		imgSize = self.canvas.imgSize
		alphaColor = QColor(255, 255, 255)		#透明色の指定
		white = QColor(255, 255, 255)
		black = QColor(0, 0, 0)
		colorList = []
		codedImg = []
		#画像を色分け
		for j in range(self.canvas.imgSize):
			for i in range(self.canvas.imgSize):
				targetColor = pixImg.pixelColor(i, j)
				if not targetColor == alphaColor:
					if not targetColor in colorList:
						colorList.append(targetColor)
						codedImg.append(QImage(QSize(imgSize, imgSize), QImage.Format_Grayscale8))
						codedImg[-1].fill(black)
					codedImg[colorList.index(targetColor)].setPixelColor(i, j, white)
		dwg = svgwrite.Drawing(fileName, size=(imgSize*2, imgSize*2), profile="tiny")
		n = 0
		for qimage in codedImg:
			path = ""
			arrImg = qimage_to_cv(qimage)
			h, w = arrImg.shape[:2]
			#４近傍でラベリング
			retval, labels = cv2.connectedComponentsWithAlgorithm(arrImg, 4, cv2.CV_16U, cv2.CCL_DEFAULT)	
			for m in range(1,retval):
				image = np.uint8(np.where(labels == m, 255, 0))
				#画像サイズを最近傍補間で縦横２倍に変換
				resizeImg = cv2.resize(image, (w*2, h*2), interpolation=cv2.INTER_NEAREST)
				#輪郭抽出
				contours, _ = cv2.findContours(resizeImg, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
				for contour in contours:
					path += self.makePath(contour)
			color = colorList[n].getRgb()
			fill = "rgb" + str(color[0:3])
			dwg.add(dwg.path(d=path, fill=fill))
			n += 1
		dwg.save()
		
	def makePath(self, contour):
		for i in range(len(contour)):
			if i == 0:
				path = "M{0},{1}".format(contour[0, 0, 0], contour[0, 0, 1])
			else:
				v = contour[i, 0] - contour[i-1, 0]
				if v[1] == 0 :
					if v[0] > 0:	#右方向
						path += "H{0}".format(contour[i, 0, 0] + 1)
					else:			#左方向
						path += "H{0}".format(contour[i, 0, 0])
				elif v[0] == 0:
					if v[1] > 0:	#下方向
						path += "V{0}".format(contour[i, 0, 1] + 1)
					else:			#上方向
						path += "V{0}".format(contour[i, 0, 1])
				elif v[0] == v[1]:
					if v[0] > 0:	#右下方向
						if "H" in path:
							path = path[:path.rfind("H")]
							path += "H{0}".format(contour[i, 0, 0])
						else:
							path = "M{0},{1}".format(contour[0, 0, 0] + 1, contour[0, 0, 1] + 1)
					else:			#左上方向
						if "H" in path:
							path = path[:path.rfind("H")]
							path += "H{0}".format(contour[i, 0, 0] + 1)
				elif v[0] != v[1]:
					if v[0] > 0:	#右上方向
						if "V" in path:
							path = path[:path.rfind("V")]
							path += "V{0}".format(contour[i, 0, 1] + 1)
						else:
							path = "M{0},{1}".format(contour[0, 0, 0] + 1, contour[0, 0, 1])
					else:			#左下方向
						if "V" in path:
							path = path[:path.rfind("V")]
							path += "V{0}".format(contour[i, 0, 1])
						else:
							path = "M{0},{1}".format(contour[0, 0, 0], contour[0, 0, 1] + 1)
		path += "Z"
		return path

	def performeFFT(self):
		self.canvas.drawFFT()

	def performeIFFT(self):
		self.canvas.drawIFFT()

	def liveFFT(self):
		if self.liveFFTButton.isChecked():
			self.canvas.check_liveFFT = True
		else:
			self.canvas.check_liveFFT = False

	def DrawLineMode(self):
		self.canvas.drawMode = "Line"

	def valueToCanvas(self):
		self.canvas.LatticeWidth = self.sb_width.value()
		self.canvas.LatticeHeight = self.sb_height.value()
		self.canvas.LatticeSpace = self.sb_space.value()
		
	def keyPressEvent(self, event):
		if event.isAutoRepeat():
			return
		pressed = event.key()
		if pressed == Qt.Key_Shift:
			self.canvas.shiftKey = True

	def keyReleaseEvent(self, event):
		pressed = event.key()
		if pressed == Qt.Key_Shift:
			self.canvas.shiftKey = False

class Canvas(QWidget):
	def __init__(self, parent=None):
		super(Canvas, self).__init__(parent)

		self.imgMag = 16									#画像の拡大倍率
		self.canvasSize = 512								#画像領域の大きさ
		self.imgSize = int(self.canvasSize/self.imgMag)		#画像の実際の画像サイズ
		self.divColor = 17									#色の分割数
		self.paletteMag = 16								#パレットの拡大倍率

		self.myPenWidth = 1
		self.myPenColor = QColor(0,0,0)
		self.image1 = QImage()
		self.image2 = QImage()
		self.image3 = QImage()
		self.guide_image1 = QImage()
		self.guide_image2 = QImage()
		self.guide_image3 = QImage()
		self.image1_grid = QImage(self.canvasSize, self.canvasSize, QImage.Format_ARGB32)
		self.image2_grid = QImage(self.canvasSize, self.canvasSize, QImage.Format_ARGB32)
		self.image3_grid = QImage(self.canvasSize, self.canvasSize, QImage.Format_ARGB32)
		self.palette = QImage(1, self.divColor, QImage.Format_Grayscale8)
		self.Frame = QImage()
		self.shiftKey = False
		self.pix = QPoint(0,0)

		self.check1 = False
		self.check2 = False
		self.check3 = False
		self.check_palette = False
		self.check_liveFFT = False

		self.back1 = deque(maxlen = 10)
		self.next1 = deque(maxlen = 10)
		self.back2 = deque(maxlen = 10)
		self.next2 = deque(maxlen = 10)
		self.back3 = deque(maxlen = 10)
		self.next3 = deque(maxlen = 10)

		#ウィンドウ左上からの原画像キャンバスの位置
		self.offsetx_image1 = 150
		self.offsety_image1 = 60
		#ウィンドウ左上からのパワースペクトルキャンバスの位置
		self.offsetx_image2 = 700
		self.offsety_image2 = 60
		#ウィンドウ左上からの位相像キャンバスの位置
		self.offsetx_image3 = 1220
		self.offsety_image3 = 60
		#ウィンドウ左上からのパレットの位置
		self.offsetx_palette = 120
		self.offsety_palette = 300

		#各キャンバスの領域
		self.rect1_place = QRect(self.offsetx_image1, self.offsety_image1, self.canvasSize, self.canvasSize)
		self.rect2_place = QRect(self.offsetx_image2, self.offsety_image2, self.canvasSize, self.canvasSize)
		self.rect3_place = QRect(self.offsetx_image3, self.offsety_image3, self.canvasSize, self.canvasSize)
		#パレットの領域
		self.rect_palette_place = QRect(self.offsetx_palette, self.offsety_palette, self.paletteMag, self.paletteMag*(self.divColor))

		self.setGrid(self.image1_grid)
		self.setGrid(self.image2_grid)
		self.setGrid(self.image3_grid)
		self.setPalette(self.palette)

		self.drawMode = "Pen"
		self.LatticeWidth = 1
		self.LatticeHeight = 1
		self.LatticeSpace = 1
	
	def mousePressEvent(self, event):
		if event.button() == Qt.LeftButton:
			self.lastPos = event.position().toPoint()
			if self.rect1_place.contains(self.lastPos.x(), self.lastPos.y()):
				if self.drawMode == "Pen" or self.drawMode == "Line" or self.drawMode == "RectLine" or self.drawMode == "RectFill":
					self.back1.append(self.resizeImage(self.image1, self.image1.size()))
				self.check1 = True
			elif self.rect2_place.contains(self.lastPos.x(), self.lastPos.y()):
				if self.drawMode == "Pen" or self.drawMode == "Line" or self.drawMode == "RectLine" or self.drawMode == "RectFill":
					self.back2.append(self.resizeImage(self.image2, self.image2.size()))
				self.check2 = True
			elif self.rect3_place.contains(self.lastPos.x(), self.lastPos.y()):
				if self.drawMode == "Pen" or self.drawMode == "Line" or self.drawMode == "RectLine" or self.drawMode == "RectFill":
					self.back3.append(self.resizeImage(self.image3, self.image3.size()))
				self.check3 = True
			elif self.rect_palette_place.contains(self.lastPos.x(), self.lastPos.y()):
				self.check_palette = True

	def mouseMoveEvent(self, event):
		if event.buttons() and Qt.LeftButton:
			if self.check1 or self.check2 or self.check3:
				if self.check1:
					image = self.image1
					guide_image = self.guide_image1
					rect = self.rect1_place
				elif self.check2:
					image = self.image2
					guide_image = self.guide_image2
					rect = self.rect2_place
				elif self.check3:
					image = self.image3
					guide_image = self.guide_image3
					rect = self.rect3_place
				guide_image.fill(qRgba(255, 255, 255,0))
				if self.drawMode == "Pen":
					self.drawLine(event.position().toPoint(), image, rect)
				elif self.drawMode == "Line":
					self.drawLine(event.position().toPoint(), guide_image, rect)
				elif self.drawMode == "RectLine":
					self.drawRect(event.position().toPoint(), guide_image, rect, False)
				elif self.drawMode == "RectFill":
					self.drawRect(event.position().toPoint(), guide_image, rect, True)
				elif self.drawMode == "Lattice":
					self.drawLattice(event.position().toPoint(), guide_image, rect)

	def mouseReleaseEvent(self, event):
		if event.button() == Qt.LeftButton:
			if self.check_palette:
				if self.rect_palette_place.contains(event.position().toPoint().x(), event.position().toPoint().y()):
					self.pix = (event.position().toPoint() - QPoint(self.rect_palette_place.topLeft()) - QPoint(self.paletteMag, self.paletteMag)/2)/self.paletteMag
					self.myPenColor = self.palette.pixelColor(self.pix)
					self.drawFrame(self.pix, self.Frame)
			elif self.check1 or self.check2 or self.check3:	
				if self.check1:
					image = self.image1
					rect = self.rect1_place
					self.guide_image1.fill(qRgba(255, 255, 255,0))
				elif self.check2:
					image = self.image2
					rect = self.rect2_place
					self.guide_image2.fill(qRgba(255, 255, 255,0))
				elif self.check3:
					image = self.image3
					rect = self.rect3_place
					self.guide_image3.fill(qRgba(255, 255, 255,0))
				if self.drawMode == "Pen" or self.drawMode == "Line":
					self.drawLine(event.position().toPoint(), image, rect)
				elif self.drawMode == "RectLine":
					self.drawRect(event.position().toPoint(), image, rect, False)
				elif self.drawMode == "RectFill":
					self.drawRect(event.position().toPoint(), image, rect, True)
				elif self.drawMode == "Dropper":
					self.pickColor(event.position().toPoint(), image, rect)
				elif self.drawMode == "PaintFill":
					self.paintFill(event.position().toPoint(), image, rect)
				elif self.drawMode == "Lattice":
					self.drawLattice(event.position().toPoint(), image, rect)
			if self.check_liveFFT:
				if self.check1:
					self.drawFFT()
				elif self.check2 or self.check3:
					self.drawIFFT()
			self.check1 = False
			self.check2 = False
			self.check3 = False
			self.check_palette = False

	#ペンツール、線ツール
	def drawLine(self, endPos, image, rect):
		painter = QPainter(image)
		if id(image) == id(self.guide_image1) or id(image) == id(self.guide_image2) or id(image) == id(self.guide_image3):
			painter.setPen(
				QPen(QColor(0,0,255,191), self.myPenWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
			)
		else:
			painter.setPen(
				QPen(self.myPenColor, self.myPenWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
			)
		v1 = (self.lastPos-rect.topLeft()-QPoint(self.imgMag, self.imgMag)/2)/self.imgMag
		v2 = (endPos-rect.topLeft()-QPoint(self.imgMag, self.imgMag)/2)/self.imgMag
		#Shiftキーを押している時は線を水平・垂直・45°方向に限定する
		if self.shiftKey:
			if abs((v2 - v1).x()) > abs((v2 - v1).y()) * 1.5:
				v2.setY(v1.y())
			elif abs((v2 - v1).y()) > abs((v2 - v1).x()) * 1.5:
				v2.setX(v1.x())
			else:
				if v2.x() > v1.x():
					if v2.y() > v1.y():						#右下方向
						v2.setY(v1.y()+(v2.x()-v1.x()))
					else:									#右上方向
						v2.setY(v1.y()-(v2.x()-v1.x()))
				else:
					if v2.y() > v1.y():						#左下方向
						v2.setY(v1.y()-(v2.x()-v1.x()))
					else:									#右下方向
						v2.setY(v1.y()+(v2.x()-v1.x()))
		painter.drawLine(v1, v2 )
		self.update()
		if self.drawMode == "Pen":
			self.lastPos = QPoint(endPos)

	#四角形ツール（fillがTrueで塗りつぶし有り、Falseで塗りつぶし無し）
	def drawRect(self, endPos, image, rect, fill):
		painter = QPainter(image)
		if id(image) == id(self.guide_image1) or id(image) == id(self.guide_image2) or id(image) == id(self.guide_image3):
			penColor = QColor(0,0,255,191)
		else:
			penColor = self.myPenColor
		painter.setPen(
			QPen(penColor, self.myPenWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
		)
		v1 = (self.lastPos-rect.topLeft()-QPoint(self.imgMag, self.imgMag)/2)/self.imgMag
		v2 = (endPos-rect.topLeft()-QPoint(self.imgMag, self.imgMag)/2)/self.imgMag
		#Shiftキーを押している時は正方形にする
		if self.shiftKey:
			if v2.x() > v1.x():
				if v2.y() > v1.y():						#右下方向
					v2.setY(v1.y()+(v2.x()-v1.x()))
				else:									#右上方向
					v2.setY(v1.y()-(v2.x()-v1.x()))
			else:
				if v2.y() > v1.y():						#左下方向
					v2.setY(v1.y()-(v2.x()-v1.x()))
				else:									#右舌方向
					v2.setY(v1.y()+(v2.x()-v1.x()))
		rect = QRect(v1, v2)
		if fill:
			painter.fillRect(rect, penColor)
		else:
			painter.drawRect(v1.x(), v1.y(),rect.width()-1, rect.height()-1)
		self.update()
		if self.drawMode == "Pen":
			self.lastPos = QPoint(endPos)

	#スポイトツール
	def pickColor(self, endPos, image, rect):
		v = (endPos-rect.topLeft()-QPoint(self.imgMag, self.imgMag)/2)/self.imgMag
		self.myPenColor = image.pixelColor(v)
		n = image.pixelColor(v).red() / (self.divColor - 1)
		self.pix = QPoint(0, n)
		self.drawFrame(self.pix, self.Frame)

	#塗りつぶしツール
	def paintFill(self, endPos, image, rect):
		v = (endPos-rect.topLeft()-QPoint(self.imgMag, self.imgMag)/2)/self.imgMag
		fillColor = self.myPenColor.getRgb()
		cvImage = qimage_to_cv(image)
		_, fillImage, _, _ = cv2.floodFill(cvImage, None, (v.x(), v.y()), fillColor[0:3], flags = 4)
		image.swap(cv_to_pixmap(fillImage))

	#格子ツール
	def drawLattice(self, endPos, image, rect):
		painter = QPainter(image)
		if id(image) == id(self.guide_image1) or id(image) == id(self.guide_image2) or id(image) == id(self.guide_image3):
			penColor = QColor(0,0,255,191)
		else:
			penColor = self.myPenColor
		painter.setPen(
			QPen(penColor, self.myPenWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
		)
		v1 = (self.lastPos-rect.topLeft()-QPoint(self.imgMag, self.imgMag)/2)/self.imgMag
		v2 = (endPos-rect.topLeft()-QPoint(self.imgMag, self.imgMag)/2)/self.imgMag
		width = self.LatticeWidth
		height = self.LatticeHeight
		space = self.LatticeSpace
		y = abs((v2-v1).y()) + 1
		x = abs((v2-v1).x()) + 1
		for j in range(y):
			if (v2 - v1).y() >= 0:
				dj = j
			else:
				dj = -j
			if j % (height + space) < height:
				for i in range(x):
					if (v2 - v1).x() >= 0:
						di = i
					else:
						di = -i
					if i % (width + space) < width:
						painter.drawLine(v1.x() + di, v1.y() + dj, v1.x() + di, v1.y() + dj)
		self.update()

	#グリッドの描画
	def setGrid(self, image):
		image.fill(qRgba(255, 255, 255,0))
		for i in range(0, self.canvasSize + 1, self.imgMag):
			for j in range(0, self.canvasSize):
				if not i == self.canvasSize:
					image.setPixelColor(i, j, QColor(193,193,225,255))
					image.setPixelColor(j, i, QColor(193,193,225,255))
				if not i == 0:
					image.setPixelColor(i-1, j, QColor(193,193,225,255))
					image.setPixelColor(j, i-1, QColor(193,193,225,255))
		#中心の線を赤色にする
		for j in range(0, self.canvasSize):
			image.setPixelColor(self.canvasSize/2, j, QColor(255,127,127,255))
			image.setPixelColor(j, self.canvasSize/2, QColor(255,127,127,255))
			image.setPixelColor(self.canvasSize/2-1, j, QColor(255,127,127,255))
			image.setPixelColor(j, self.canvasSize/2-1, QColor(255,127,127,255))

	#パレットの描画
	def setPalette(self, image):
		for i in range(self.divColor-1):
			gray = i*256/(self.divColor - 1)
			image.setPixelColor(QPoint(0,i), QColor(gray,gray,gray))
		else:
			i += 1
			image.setPixelColor(QPoint(0,i), QColor(255,255,255))

	#色選択枠の描画
	def drawFrame(self, pix, image):
		painter = QPainter(image)
		image.fill(qRgba(255, 255, 255,0))
		painter.setPen(
			QPen(QColor(255,0,0,255), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
		)
		topleft = pix * self.paletteMag
		rect = QRect(topleft, topleft + QPoint(self.paletteMag-1, self.paletteMag-1))
		painter.drawRect(rect)
		self.update()

	def drawFFT(self):
		arrImg = qimage_to_cv(self.image1)
		f = np.fft.fft2(arrImg).copy()
		fshift = np.fft.fftshift(f)
		mag = np.uint8(20*np.log(np.abs(fshift)+1))
		power = cv_to_pixmap(mag)
		ang = np.angle(fshift)
		z = np.uint8((ang/math.pi + 1) * (255/2))
		phase = cv_to_pixmap(z)
		self.back2.append(self.resizeImage(self.image2, self.image2.size()))
		self.image2 = power
		self.back3.append(self.resizeImage(self.image3, self.image3.size()))
		self.image3 = phase

	def drawIFFT(self):
		arrImg = qimage_to_cv(self.image2)
		amplitude = (np.exp((arrImg / 20)-1))
		phaseImg = qimage_to_cv(self.image3)
		phase = np.float32((phaseImg / 127.5 - 1)*math.pi)
		combined = np.multiply(amplitude, np.exp(1j*phase))
		ifshift = np.fft.ifftshift(combined)
		ifimg = np.fft.ifft2(ifshift)
		absifimg = np.abs(ifimg)
		y = np.uint8(absifimg * 255 / np.amax(absifimg)).copy()
		ifftqImg = cv_to_pixmap(y)
		self.back1.append(self.resizeImage(self.image1, self.image1.size()))
		self.image1 = ifftqImg
		
	def paintEvent(self, event):
		#原画像キャンバスの描画
		painter1 = QPainter(self)
		rect1_size = QRect(0, 0, self.imgSize, self.imgSize)
		painter1.drawImage(self.rect1_place, self.image1, rect1_size)
		painter1.drawImage(self.rect1_place, self.guide_image1, rect1_size)
		#パワースペクトルキャンバスの描画
		painter2 = QPainter(self)
		painter2.drawImage(self.rect2_place, self.image2, rect1_size)
		painter2.drawImage(self.rect2_place, self.guide_image2, rect1_size)
		#位相像キャンバスの描画
		painter3 = QPainter(self)
		painter3.drawImage(self.rect3_place, self.image3, rect1_size)
		painter3.drawImage(self.rect3_place, self.guide_image3, rect1_size)
		#原画像グリッドの描画
		rect_grid = QRect(0, 0, self.canvasSize, self.canvasSize)
		painter1_grid = QPainter(self)
		painter1_grid.drawImage(self.rect1_place, self.image1_grid, rect_grid)
		#パワースペクトルグリッドの描画
		painter2_grid = QPainter(self)
		painter2_grid.drawImage(self.rect2_place, self.image2_grid, rect_grid)
		#位相像グリッドの描画
		painter3_grid = QPainter(self)
		painter3_grid.drawImage(self.rect3_place, self.image3_grid, rect_grid)
		#パレットの描画
		painter_palette = QPainter(self)
		rect_palette_size = QRect(0, 0, 1, self.divColor)
		painter_palette.drawImage(self.rect_palette_place, self.palette, rect_palette_size)
		#パレットの選択枠の描画
		painter_Frame = QPainter(self)
		rect_Frame = QRect(0, 0, self.paletteMag, self.paletteMag*(self.divColor))
		self.drawFrame(self.pix, self.Frame)
		painter_Frame.drawImage(self.rect_palette_place, self.Frame, rect_Frame)

	#ウィンドウサイズを固定している場合、ウィンドを開いたときだけ動作する処理
	def resizeEvent(self, event):
		if self.image1.width() < self.width() or self.image1.height() < self.height():
			changeWidth = max(self.width(), self.image1.width())
			changeHeight = max(self.height(), self.image1.height())
			self.image1 = self.resizeImage(self.image1, QSize(changeWidth, changeHeight))
			self.guide_image1 = self.resizeimage_grid(self.guide_image1, QSize(changeWidth, changeHeight))
			self.image1_grid = self.resizeimage_grid(self.image1_grid, QSize(changeWidth, changeHeight))
			self.update()
		if self.image2.width() < self.width() or self.image2.height() < self.height():
			changeWidth = max(self.width(), self.image2.width())
			changeHeight = max(self.height(), self.image2.height())
			self.image2 = self.resizeImage(self.image2, QSize(changeWidth, changeHeight))
			self.guide_image2 = self.resizeimage_grid(self.guide_image2, QSize(changeWidth, changeHeight))
			self.update()
		if self.image3.width() < self.width() or self.image3.height() < self.height():
			changeWidth = max(self.width(), self.image3.width())
			changeHeight = max(self.height(), self.image3.height())
			self.image3 = self.resizeImage(self.image3, QSize(changeWidth, changeHeight))
			self.guide_image3 = self.resizeimage_grid(self.guide_image3, QSize(changeWidth, changeHeight))
			self.update()
		if self.palette.width() < self.width() or self.palette.height() < self.height():
			changeWidth = max(self.width(), self.palette.width())
			changeHeight = max(self.height(), self.palette.height())
			self.palette = self.resizeImage(self.palette, QSize(changeWidth, changeHeight))
			self.update()
		if self.Frame.width() < self.width() or self.Frame.height() < self.height():
			changeWidth = max(self.width(), self.Frame.width())
			changeHeight = max(self.height(), self.Frame.height())
			self.Frame = self.resizeimage_grid(self.Frame, QSize(changeWidth, changeHeight))
			self.update()

	def resizeImage(self, image, newSize):
		changeImage = QImage(QSize(self.imgSize, self.imgSize), QImage.Format_Grayscale8)
		changeImage.fill(255)
		painter = QPainter(changeImage)
		painter.drawImage(QPoint(0, 0), image)
		return changeImage

	def resizeimage_grid(self, image, newSize):
		changeImage = QImage(newSize, QImage.Format_ARGB32)
		changeImage.fill(qRgba(255, 255, 255,0))
		painter = QPainter(changeImage)
		painter.drawImage(QPoint(0, 0), image)
		return changeImage

	def gridOn(self, image):
		self.setGrid(image)

	def gridOff(self, image):
		image.fill(qRgba(255, 255, 255,0))

	def openImage(self, filename, image):
		if not image.load(filename):
			return False
		self.update()
		return True

	def saveImage(self, filename, image):
		if image.save(filename, "PNG"):
			return True
		else:
			return False

#QImageをndarrayに変換
def qimage_to_cv(qimage):
	w, h, d = qimage.size().width(), qimage.size().height(), qimage.depth()
	arr = np.array(qimage.bits()).reshape((h, w, d // 8))
	h, w = arr.shape[:2]
	arrImg = cv2.resize(arr, (w, h), interpolation=cv2.INTER_NEAREST)	#正常な画像の配列にするために必要な変換
	return arrImg

#ndarrayをQImageに変換
def cv_to_pixmap(cv_image):
	height, width = cv_image.shape[:2]
	bytesPerLine = cv_image.strides[0]
	image = QImage(cv_image.data, width, height, bytesPerLine, QImage.Format_Grayscale8).copy()
	return image

def main():
	app = QApplication(sys.argv)
	main_window = MainWindow()
	main_window.show()
	sys.exit(app.exec())

if __name__ == "__main__":
	main()
```

#次回予告
今回で主な機能の実装はほぼ完了しました。
次回はキャンバスの画素サイズを変更する機能を追加します。