[C++] 61 FLTK : xlsx変換アプリ / appファイルの不具合

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]

製作中のアプリですが、前回の記事でも書いたように実行ファイルでは正常に動作し、appファイルは起動はするもののボタンを押すと落ちてしまいます。

原因を探ったところ、どうやらPYTHONPATHを認識できないためにPyImport_ImportModuleが働かずモジュールが生成していないようでした。PYTHONPATHの__pycache__ディレクトリにpycファイルが生成していないことで判明しました。

appファイル内の/Contents/Resourcesにpyファイルを置き、info.plistで認識させようとするなどいろいろ試しましたが、うまくいきませんでした。

結局解決には至らず、やむなくこのまま次に進みます。Objective-Cであれば何らかの方法が見つかるかもしれません。

#define PY_SSIZE_T_CLEAN
#include "process.h"
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>
#include <iostream>
#include <string.h>

using std::string;

const char* XlsxToList(const char* path) {

    Py_Initialize();

    // sys.pathを確認 wchar_tなのでwcoutで出力
    std::wcout << Py_GetPath() << std::endl;

    // pyファイルのモジュール化 appファイルではモジュール化できない PYTHONPATHに到達できていない
    PyObject* myModule = PyImport_ImportModule("test");

    // pyファイル内の関数を指定
    PyObject* myFunction = PyObject_GetAttrString(myModule,(char*)"xlsx_to_list");

    // 関数の引数を設定
    PyObject* args = PyTuple_Pack(1,PyUnicode_FromString(path));
    
    // 関数を実行し戻り値をPyObjectとして取得
    PyObject* myResult = PyObject_CallObject(myFunction,args);
    
    // PyObjectをconst char*に変換
    const char* result = PyUnicode_AsUTF8(myResult);

    std::cout << result << std::endl;

    return result;

    Py_FinalizeEx();
    
}

[C++] 60 FLTK : xlsx変換アプリ / Pythonスクリプトのモジュール化

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]

前回まではPythonスクリプトの埋め込みについて調べていましたが、ついにモジュール化に成功しました。

埋め込みでは処理が一方通行だったのが、モジュール化により双方向でデータのやり取りができるようになります。Pythonスクリプトを1行ずつバラして埋め込む必要がなくなるのでだいぶ楽です。

今回のケースでは、Pythonスクリプトで作成したリストをC++コードが文字列として受け取り、Fl_Multiline_Outputに表示させています。

現段階では実行ファイルでしかできませんが、早くappファイルでもできるようにしたいところです。appファイルでは実行ボタンを押すとなぜか落ちてしまいます。

なお.bash_profileにてPYTHONPATHを設定しないとPythonスクリプトを読み込めないので要注意です。

#define PY_SSIZE_T_CLEAN
#include "process.h"
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>
#include <iostream>
#include <string.h>

using std::string;

string XlsxToList(const char* path) {
    Py_Initialize();

    // pyファイルの指定(test.py)
    PyObject* myModuleString = PyUnicode_FromString((char*)"test");

    // pyファイルのモジュール化
    PyObject* myModule = PyImport_Import(myModuleString);

    // pyファイル内の関数を指定
    PyObject* myFunction = PyObject_GetAttrString(myModule,(char*)"xlsx_to_list");

    // 関数の引数を設定
    PyObject* args = PyTuple_Pack(1,PyUnicode_FromString(path));

    // 関数を実行し戻り値をPyObjectとして取得
    PyObject* myResult = PyObject_CallObject(myFunction,args);

    // PyObjectをconst char*に変換
    const char* result = PyUnicode_AsUTF8(myResult);

    return string(result);

    Py_Finalize();
}
import openpyxl

def xlsx_to_list(path):
    wb = openpyxl.load_workbook(path)
    ws = wb.worksheets[0]

    color_name = []
    for cell in ws['A']:
        color_name.append(cell.value)
        
    return str(color_name)
<該当箇所のみ>
void xtol(){
    const char *path = input_line->value();
    string result = XlsxToList(path);
    output_line->insert(result.c_str());
}
# pyファイルのあるディレクトリを指定

export PYTHONPATH="/Python/library/python_module"

Python/C API リファレンスマニュアル

[C++] 58 FLTK : xlsx変換アプリ / Pythonの openpyxlライブラリ導入

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]

Excelファイルを扱うFLTKアプリの作成に着手しました。GUIはよく使うガワの使い回しです。

まずは前々回から取り組んでいるExcelファイルから列データを取り出してリストにする機能を実装しました。A列の値とセル色をリストにします。色データはRGB16進数の頭にFFが付きます。このFFはいらないのでそのうち除去するようにします。

列数や取り出すデータの種類はいずれ選択できるようにしたいです。

#define PY_SSIZE_T_CLEAN
#include "process.h"
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>
#include <iostream>
#include <string.h>

using std::string;

int XlsxToList(const char* path) {

    Py_Initialize();

    PyRun_SimpleString("import openpyxl");

    string path_str = string(path);
    string wb_str = "wb = openpyxl.load_workbook('" + path_str +"')";
    PyRun_SimpleString(wb_str.c_str());
    PyRun_SimpleString("ws = wb.worksheets[0]");

    PyRun_SimpleString("color_name = []");
    PyRun_SimpleString("color_code = []");

    string for_str = "for cell in ws['A']: color_name.append(cell.value), color_code.append(cell.fill.fgColor.rgb)";
    PyRun_SimpleString(for_str.c_str());
        
    PyRun_SimpleString("print(color_name)");
    PyRun_SimpleString("print(color_code)");

    Py_Finalize();
    return 0;
}

[C++] 57 Pythonスクリプトを使ってExcelを操作する / for文

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]

前回の続きです。

for文はワンライナーで書けば、PyRun_SimpleString()に使えます。バックスラッシュを入れてもOKです。

リスト内包表記のワンライナーは多用していましたが、if文やfor文の中身をカンマでつなぐとワンライナーになるというのは知りませんでした。

#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>
#include <iostream>
#include <string.h>

using std::string;

int main() {
    Py_Initialize();

    PyRun_SimpleString("import openpyxl");
    PyRun_SimpleString("wb = openpyxl.load_workbook('test.xlsx')");
    PyRun_SimpleString("ws = wb.worksheets[0]");

    PyRun_SimpleString("color_name = []");
    PyRun_SimpleString("color_code = []");

    string str = "for cell in ws['A']: color_name.append(cell.value), color_code.append(cell.fill.fgColor.rgb)";

    // バックスラッシュを使う場合
    // string str = "for cell in ws['A']: \
    //     color_name.append(cell.value), color_code.append(cell.fill.fgColor.rgb)";

    PyRun_SimpleString(str.c_str());
        
    PyRun_SimpleString("print(color_name)");
    PyRun_SimpleString("print(color_code)");

    Py_Finalize();
    return 0;
}

[C++] 56 Pythonスクリプトを使ってExcelを操作する

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]

C++コードからPythonスクリプトを動かしてExcelを操作してみました。Pythonは公式サイトからダウンロード&インストールしたVer. 3.10.4を使っています。

以下のコードはExcelファイルのA列に入力された文字列とセル色をリストにして出力します。

モジュールではないのでデータの双方向なやりとりは直接できませんが、ファイルを介してなら可能でしょう。

C++でExcelを扱うにはLibXLのような3万円もする有償ライブラリが必要になるので、間接的とはいえopenpyxlで操作できるのであればこれで十分です。

FLTKのような軽量なGUIでopenpyxlやpandasが使えないかと思い、試してみた次第です。TkinterやPyQtといったウィジェットツールキットで重量級GUIにはしたくなかったものですから。

#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>

int main() {
    Py_Initialize();

    PyRun_SimpleString("import openpyxl");
    PyRun_SimpleString("wb = openpyxl.load_workbook('test.xlsx')");
    PyRun_SimpleString("ws = wb.worksheets[0]");
    PyRun_SimpleString("color_name = [cell.value for cell in ws['A']]");
    PyRun_SimpleString("color_code = [cell.fill.fgColor.rgb for cell in ws['A']]");
    PyRun_SimpleString("print(color_name)");
    PyRun_SimpleString("print(color_code)");

    Py_Finalize();
    return 0;
}
# コンパイラ
COMPILER = clang++
DEBUG = -g

# オプション
CPPFLAGS =

# includeパス(-I)
INCLUDE = -I/Library/Frameworks/Python.framework/Versions/3.10/include/python3.10

# ライブラリパス(-l)
LIBRARY0 = -lpython3.10

# 優先ライブラリパス(-L)
LIBRARY = -L/Library/Frameworks/Python.framework/Versions/3.10/lib

# ソースファイル
SRCDIR = ./src
SRCS = $(SRCDIR)/Openpyxl.cpp

# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(OBJDIR)/Openpyxl.o

# 実行ファイル
TARGETDIR = ./bin
TARGET = Openpyxl

# cppファイルからoファイル作成 $<:依存ファイル
$(OBJDIR)/Openpyxl.o: $(SRCDIR)/Openpyxl.cpp
	$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<

# oファイルから実行ファイル作成
$(TARGET):$(OBJS)
	$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LIBRARY0) $(LDFLAGS) $(LIBRARY)

# ファイル削除&実行ファイル作成
.PHONY:all
all: clean $(TARGET)

# ファイル削除
.PHONY:clean
clean:
	rm -rf $(OBJS) $(TARGETDIR)/$(TARGET)

[Python] 96 再掲 Nikon画像ファイルの日付別取り込み

[M1 Mac, Big Sur 11.6.5, Python 3.10.0]

前のブログサイトからの引っ越しです。画像ファイルをSDカードから外部SSDへ日付振り分けしてコピーします。

Nikon Trasnferという配布アプリで日付振り分けができることを知らずにコードを書きました。今も愛用しています。

# coding: UTF-8

import shutil ,os ,glob
from PIL import Image, ExifTags
import collections

path_list = glob.glob('/NIKON D500 /DCIM/101ND500/*.JPG')

exif_datetime_list = []
for path in path_list: 
    img = Image.open(path)

    exif = { ExifTags.TAGS[k]: v for k, v in img._getexif().items() if k in ExifTags.TAGS }

    # print(exif)
    exif_datetime = [v for k, v in exif.items() if k == "DateTimeOriginal"]
    print(exif_datetime[0])

    exif_datetime_str = (exif_datetime[0].split(':')[0])[2:4] + exif_datetime[0].split(':')[1] + (exif_datetime[0].split(':')[2])[0:2]
    print(exif_datetime_str)
    exif_datetime_list.append(exif_datetime_str)

print(exif_datetime_list)

# 作成日の内訳を集計し作成日のリストを作成する
keys = collections.Counter(exif_datetime_list).keys()
print(keys)

# 作成日フォルダを作成する
for key in keys:
    try:
        os.mkdir('/photo/D500/' + str(key))
    except FileExistsError:
        pass

# 画像ファイルを各フォルダにコピーする
for path in path_list:
    
    # 画像ファイルを読み込む
    img = Image.open(path)

    exif = { ExifTags.TAGS[k]: v for k, v in img._getexif().items() if k in ExifTags.TAGS }

    exif_datetime = [v for k, v in exif.items() if k == "DateTimeOriginal"]
    
    exif_datetime_str = (exif_datetime[0].split(':')[0])[2:4] + exif_datetime[0].split(':')[1] + (exif_datetime[0].split(':')[2])[0:2]

    # ファイルパスを文字列に変換する
    path_str = str(path)

    # ファイル名を抽出する(スラッシュで分割した最後の文字列)
    filename = path_str.split('/')[-1]

    # RAWファイル名を生成する
    filename_raw = filename.split('.')[0] + '.NEF'

    # RAWファイルのコピー元を生成する
    filename_raw_src = '/NIKON D500 /DCIM/101ND500/' + str(filename_raw)
    # RAWファイルのコピー先を生成する
    filename_raw_dest = '/photo/D500/'+ str(exif_datetime_str) + '/' + str(filename_raw)

    print(f'RAWコピー元 {filename_raw_src}')
    print(f'RAWコピー先 {filename_raw_dest}\n')

    filename_jpeg_dest = '/photo/D500/'+ str(exif_datetime_str) + '/' + str(filename)

    print(f'JPEGコピー元 {path}')
    print(f'JPEGコピー先 {filename_jpeg_dest}\n')

    # JPGファイルをコピーする
    shutil.copy2(path,filename_jpeg_dest)

    # RAWファイルをコピーする
    try:
        shutil.copy2(filename_raw_src,filename_raw_dest)
    except FileNotFoundError :
        pass

# Macのデスクトップ通知
def main():
    os.system("osascript -e 'display notification \"Nikonファイルコピー\nコード実行完了\"'")

if __name__ == '__main__':
    main()

[Python]332 画像ファイルの全ピクセル色情報をCSV化

[M1 Mac, Big Sur 11.6.5, Python 3.10.0]

[Python]330に関連して画像ファイルの全ピクセル色情報を取得しCSVファイルとして出力するコードを記録しておきます。

from PIL import Image
import numpy as np
import csv

img_array = np.array(Image.open("test.png"))

# 全ピクセルの色情報を取得
list_rgba = img_array[:, :, (0, 1, 2, 3)]
# list_rgb = img_array[:, :, (0, 1, 2)] jpgの場合

with open("test.csv", 'w') as f:
    writer = csv.writer(f,lineterminator='\n')
    writer.writerows(list_rgba)

[Python]331 PyQt6 icnsファイル作成コード修正

[M1 Mac, Big Sur 11.6.5, Python 3.10.0]

[Python]329のコードを修正しました。以下の通りになります。

修正前のコードでも動作しますが、jpgファイルへの変換により透過部分が黒くなる上に、不可逆圧縮することで図形周辺画素の色データにズレが生じています。pngのままでも解像度を上げられるのでその方が良いでしょう。

前回330の記事でnumpyを使った透過部分黒色化対策方法を紹介しましたが、pngのままicnsファイルを作れるようになったためnumpyは不要になりました。Qt5への移植もスムーズに進みそうです。

from PIL import Image
import subprocess, os
from PyQt6.QtWidgets import QDialog,QPushButton,QLabel
from PyQt6.QtCore import Qt

class MakeIcns():
    def make(self, filepath0, window):
        img = Image.open(filepath0)
        print(str(img.size))
        if str(img.size)=="(2048, 2048)": # 2048*2048 dpi=72
            filepath1 = ".".join(filepath0.split(".")[:-1]) + "1.png" # 1024*1024 dpi=144
            filepath2 = ".".join(filepath0.split(".")[:-1]) + "2.png" # 512*512 dpi=72
            filedir = "/".join(filepath0.split("/")[:-1]) + "/" + (filepath0.split("/")[-1]).split(".")[-2] + ".iconset/"
            
            os.mkdir(filedir)

            img = Image.open(filepath0)
            
            # 1024*1024 dpi=144
            img_resize2 = img.resize((1024,1024))
            img_resize2.save(filepath1,dpi = (144, 144))
            
            # 512*512 dpi=72
            img_resize = img.resize((512,512))
            img_resize.save(filepath2,dpi = (72, 72))

            pixels = [32, 64, 256, 512, 1024]
            pixels2 = [16, 32, 128, 256, 512]
            filepaths = ['icon_16x16@2x.png','icon_32x32@2x.png','icon_128x128@2x.png','icon_256x256@2x.png','icon_512x512@2x.png']
            filepaths2 = ['icon_16x16.png','icon_32x32.png','icon_128x128.png','icon_256x256.png','icon_512x512.png']

            # dpi=144の各種pngファイル作成
            for pixel,file in zip(pixels,filepaths):
                img = Image.open(filepath1)
                img_resize = img.resize((pixel,pixel))
                img_resize.save(filedir + file, dpi = (144, 144))
                img.close()

            # dpi=72の各種pngファイル作成    
            for pixel,file in zip(pixels2,filepaths2):
                img = Image.open(filepath2)
                img_resize = img.resize((pixel,pixel))
                img_resize.save(filedir + file, dpi = (72, 72))
                img.close()

            # icnsファイル作成
            dir = "/".join(filepath0.split("/")[:-1])
            iconset = (filepath0.split("/")[-1]).split(".")[-2] + ".iconset"
            cmd = f'iconutil -c icns {iconset}'
            subprocess.run(cmd, cwd=dir,shell=True)
            
            os.remove(filepath1)
            os.remove(filepath2)
        else:
            MakeIcns.showDialog(self,window)
            
    def showDialog(self,window):
        dlg = QDialog(window)
        dlg.setFixedSize(250,100)
        label = QLabel('This file is invalid.\nPNG file[2048*2048,72dpi] required',dlg)
        label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        label.move(15,20)
        label.setStyleSheet('font-size:14px')
        btn = QPushButton("OK",dlg)
        btn.move(90,60)
        def action():
            dlg.close()
        btn.released.connect(action)
        dlg.setWindowTitle("Attention")
        dlg.exec()

[Python]330 pngファイル透過部黒色化への対策

[M1 Mac, Big Sur 11.6.5, Python 3.10.0]

pngファイルをjpgファイルに変換すると透過部が黒色になります。pngファイルの色情報から透明度が削除されたためです。

pngファイル RGBA(0, 0, 0, 0) 透明な黒[見た目は無色透明]

jpgファイル RGB(0, 0, 0) = RGBA(0, 0, 0, 255) 不透明な黒

対策としてpngファイルに再変換された画像の黒っぽい画素についてアルファ値を255から0に置き換えました。自分としては会心の出来ですが、コードにしてみるとあっさりしたものです。ちなみに画像はAdobe Illustratorで作ってみたアプリのアイコンです。フォントは自製しました。無料期間が終わったらどうするか悩みどころです。

jpgの場合は黒を白に変換するなどします。下記コード該当部分をnp.put(pixel,0,255)などに置き換えればできるはずです。

ただ上記の方法では元画像の絵の部分に黒が含まれていればそこも透過してしまいます。その場合は元画像の画素でアルファ値が0のものについて行列インデックスを変数化する、あるいは透過部分を絵にはない色に変換しておく、などの処置が必要です。

numpyと同じことがC++のライブラリでできないか、あるいはC++からnumpyを使えないか調査を進めています。

from PIL import Image
import numpy as np

# pngファイルの色情報を読み込む [red, green, blue, alpha]
img_array = np.array(Image.open('ImageInspector1.png'))

# 黒に近い画素のアルファ値を0にして透過させる
for row in img_array:
    for pixel in row:
        if pixel[0] <= 70 and pixel[1] <= 70 and pixel[2] <= 70 :
            np.put(pixel,3,0)
            
img = Image.fromarray(img_array)

file = "ImageInspector2.png"
img.save(file)
対策前
対策後