2
1

More than 3 years have passed since last update.

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

Posted at

はじめに

前回の記事で作成したドット絵エディタに、描いたドット絵をベクター画像(SVG形式)で出力する機能を追加します。。
搭載する機能の仕様は以下の通りです。

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

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

環境

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

pip3 install svgwrite

解説

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

色分け、2値化

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

        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関数を使います。この関数の詳細はこちらの記事が参考になります。近傍指定は4近傍を指定します。

            arrImg = qimage_to_cv(qimage)
            h, w = arrImg.shape[:2]
            #4近傍でラベリング
            retval, labels = cv2.connectedComponentsWithAlgorithm(arrImg, 4, cv2.CV_16U, cv2.CCL_DEFAULT)   

cv2.connectedComponentsWithAlgorithmの第1戻り値retvalはラベル数で、第2戻り値labelsは塊ごとにセルの値がラベル番号に変換された画像となります。次の輪郭抽出の処理も2値化画像しか扱えないので、numpy.where関数でラベル番号ごとの2値化画像に変換します。

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

以降は下の画像を例に画像処理の様子を説明します(わかりやすいように列と行に番号を振っています)。なお、画像は処理中のものを表しており、入出力画像はこれの白黒が反転した絵となります。
SVG変換-1.png

セルの4分割

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

                #画像サイズを最近傍補間で縦横2倍に変換
                resizeImg = cv2.resize(image, (w*2, h*2), interpolation=cv2.INTER_NEAREST)

例の画像は下の画像のように変換されます。
SVG変換-2_1.png

輪郭抽出

連結したセルの輪郭を抽出します。輪郭抽出にはOpenCVのfindContours関数を使います。この関数の動作はこちらの記事が参考になります。抽出モードはcv2.RETR_CCOMP、近似手法はcv2.CHAIN_APPROX_SIMPLEを指定します。

                #輪郭抽出
                contours, _ = cv2.findContours(resizeImg, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)

輪郭抽出を行うと輪郭のセルの位置が配列で書き出されます。近似手法をcv2.CHAIN_APPROX_SIMPLEに指定した場合は、角以外のセルが省かれます。例の画像は下に示す画像のように輪郭抽出が行われます。外周をピンク色、内周を水色で表しています。書き出しが行われるセル位置を点で表し、その間を矢印で結んでいます。厄介なことに、findContours関数は8近傍にしか対応していないため、角に斜め方向の移動が入っています。
SVG変換-2_2.png

複合パスへの変換

SVG形式の複合パスは、縦横の線のみを扱う場合、M(x,y)で開始位置(x,y)に移動、H(x)でxの位置まで移動して水平方向の線を描く、V(y)でyの位置まで移動して垂直方向の線を描く、Zでパスを閉じるというコマンドの記述の仕方をします。詳しくはsvgwriteの公式ドキュメントを参照してください。抽出した輪郭情報を複合パスに変換するため、下の画像の赤と青の矢印で示した軌跡に変換します。
SVG変換-2_3.png

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

path += "{0},{1}".format(contour[0, 0, 0], contour[0, 0, 1])

contourの2番目以降のセルは1つ前のセルを差し引いてベクトルを作ります。

v = contour[i, 0] - contour[i-1, 0]

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

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の場合はベクトルの方向に応じて初期位置をずらして記述しなおします。

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関数として定義します。

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から色情報を取得して指定します。

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関数として定義します。

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]
            #4近傍でラベリング
            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))
                #画像サイズを最近傍補間で縦横2倍に変換
                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

Blenderへのインポート

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

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

この様子は下のツィートの埋め込み動画を参照してください。

全体のソースコード

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

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]
            #4近傍でラベリング
            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))
                #画像サイズを最近傍補間で縦横2倍に変換
                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()

次回予告

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

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1