[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)

[C++] 55 FLTK : 画像加工アプリ / iOSアプリ用iconset作成機能の実装 Contents.json

iOSアイコンのAppicon.appiconset内にあるContents.jsonはアイコン作成サイトで作ったものを一部書き換えて使っています。

内容は以下の通りです。

{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"3x"},
        {"size":"40x40","expected-size":"80","filename":"80.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
        {"size":"40x40","expected-size":"120","filename":"120.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"3x"},
        {"size":"60x60","expected-size":"120","filename":"120.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
        {"size":"57x57","expected-size":"57","filename":"57.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"1x"},
        {"size":"29x29","expected-size":"58","filename":"58.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
        {"size":"29x29","expected-size":"29","filename":"29.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"1x"},
        {"size":"29x29","expected-size":"87","filename":"87.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"3x"},
        {"size":"57x57","expected-size":"114","filename":"114.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
        {"size":"20x20","expected-size":"40","filename":"40.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
        {"size":"20x20","expected-size":"60","filename":"60.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"3x"},
        {"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"AppIcon.appiconset/","scale":"1x"},
        {"size":"40x40","expected-size":"80","filename":"80.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
        {"size":"72x72","expected-size":"72","filename":"72.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
        {"size":"76x76","expected-size":"152","filename":"152.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
        {"size":"50x50","expected-size":"100","filename":"100.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
        {"size":"29x29","expected-size":"58","filename":"58.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
        {"size":"76x76","expected-size":"76","filename":"76.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
        {"size":"29x29","expected-size":"29","filename":"29.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
        {"size":"50x50","expected-size":"50","filename":"50.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
        {"size":"72x72","expected-size":"144","filename":"144.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
        {"size":"40x40","expected-size":"40","filename":"40.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
        {"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
        {"size":"20x20","expected-size":"20","filename":"20.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
        {"size":"20x20","expected-size":"40","filename":"40.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]}  

[C++] 54 FLTK : 画像加工アプリ / iOSアプリ用iconset作成機能の実装 appiconset

とりあえずmacOSアプリとiOSアプリに必要なアイコン類を作成できるようにしました。画像処理ライブラリとしてOpenCV(開発はインテル)を使っています。用意するのは2048*2048のpngファイルだけです。

iOSに必要なContents.jsonは前もってホームディレクトリに配置したものをコピーしました。

iOS
macOS
#include <opencv2/opencv.hpp>

void makeIcnsIOS(const char* filename){
	// AppIcon.appiconsetパス作成
	string path_str = string(filename);
	char del = '/';
	vector<string> list = split(path_str, del);
	string imagename = list.back(); 
	list.pop_back();

	const char* del2 = "/";
	string prefix = join(list,del2);
	string dir = prefix + "/" + "AppIcon.appiconset";

	cout<<"dir "<<dir<<endl;

	// iconsetディレクトリ作成
	mkdir(dir.c_str(),
		S_IRUSR | S_IWUSR | S_IXUSR |  // USR RWX
		S_IRGRP | S_IWGRP | S_IXGRP |  // GRP RWX
		S_IROTH | S_IWOTH | S_IXOTH);  // OTH RWX

	// 各種pngファイル作成
	int pixels[19] = {20, 29, 40, 50, 57, 58, 60, 72, 76, 80, 87, 100, 114, 120, 144, 152, 167, 180, 1024};
	string filenames[19] = {"20.png","29.png","40.png","50.png","57.png","58.png","60.png","72.png","76.png","80.png","87.png","100.png","114.png","120.png","144.png","152.png","167.png","180.png","1024.png"};
	
	int num = 0;
	for (int pixel:pixels){
		cv::Mat img,img_resize;
		img = cv::imread(filename,cv::IMREAD_UNCHANGED);
		cv::resize(img,img_resize, cv::Size(pixel,pixel),0,0,cv::INTER_LINEAR);
		// リサイズファイルパス作成
		string filename2 = filenames[num];
		string filepath = dir + "/" + filename2;
		cv::imwrite(filepath, img_resize);

		num += 1;
	}

	// JSONファイルコピー
	std::string jsonname1 = "/Users/[ユーザID]/ImageInspector/Contents.json";
    std::string jsonname2 = prefix + "/AppIcon.appiconset/Contents.json";
	system(("cp " + jsonname1 + " " + jsonname2).c_str());
}

[C++] 53 FLTK : 画像加工アプリ / iOSアプリ用iconset作成機能の実装 Fl_Choice

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

macOSアプリ, iOSアプリではアイコン登録に必要な画像ファイル数、サイズおよびファイルタイプが異なるため、都度選択し作成できるよう自製アプリの機能追加に着手しました。XCodeへのiOSアプリ登録に必要なのは正確にはicnsファイルではなくappiconsetフォルダです。

まずはガワだけ、プルダウンメニューを配置しました。中身はこれから書きます。

<該当箇所のみ>

Fl_Choice *choice;

// icnsタイプ選択項目
Fl_Menu_Item icns_type[3] = {
    {"macOS"},
    {"iOS"},
};

// icnsタイプ選択メニュー
choice = new Fl_Choice(135, 90, 80, 20, nullptr);
choice->menu(icns_type);

[C++] 52 FLTK : findコマンド生成アプリ/ FL_Menu_Itemの設定ミス その2

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

前回の続きです。

FL_Menu_Itemの配列の書き方でイテレータの存在を忘れていて修正を施しましたが、コードの見た目がいまひとつなので以下のように書き換えました。

Fl_Menu_Item pulldown[4] = {
  {"and"},
  {"or"},
  {"not"}
};

実際のアイテム数3に最後のヌルポインタを加えた4を記述する必要があります。こちらの方がすっきりしている感じがします。公式サイトにあるような複雑なメニューであれば最後に0を書くでしょう。

Fl_Menu_Item popup[] = {
 {"&alpha",    FL_ALT+'a', the_cb, (void*)1},
 {"&beta",     FL_ALT+'b', the_cb, (void*)2},
 {"gamma",     FL_ALT+'c', the_cb, (void*)3, FL_MENU_DIVIDER},
 {"&strange",  0,          strange_cb},
 {"&charm",    0,          charm_cb},
 {"&truth",    0,          truth_cb},
 {"b&eauty",   0,          beauty_cb},
 {"sub&menu",  0,          0, 0, FL_SUBMENU},
 {"one"},
 {"two"},
 {"three"},
 {0},
 {"inactive", FL_ALT+'i', 0, 0, FL_MENU_INACTIVE|FL_MENU_DIVIDER},
 {"invisible",FL_ALT+'i', 0, 0, FL_MENU_INVISIBLE},
 {"check",    FL_ALT+'i', 0, 0, FL_MENU_TOGGLE|FL_MENU_VALUE},
 {"box",      FL_ALT+'i', 0, 0, FL_MENU_TOGGLE},
 {0}};

お遊びで私のアプリにこの複雑なメニューを実装してみました。余談ですが、今になって何故altキーの記号が⎇なのかが理解できました。確かに左から右にまっすぐ進まずもう一方に変わっている様子で意味が伝わります。

それにしてもMontereyでは何故間違ったコードでも動いたのか、謎です。

[C++] 51 FLTK : findコマンド生成アプリ/ FL_Menu_Itemの設定ミス

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

macOS MontereyからBig Surにダウングレードしたところ、コマンドキーを押すとアプリが落ちるようになりました。エラー発生時に表示される解析情報を読むとどうやらFL_Menu_Itemまわりがおかしいようでした。

参考サイトのコードと全く同じようにプルダウンメニュー項目の最後に0を追加すると直りました。Montereyやアップグレード前のBig Surでは0がなくても正常に動いていたのに不思議です。

2022/5/18追記:
0(ヌルポインタ)が必要なのはイテレータに次の要素がないことを知らせるためでした。こんなことがすぐに分からないようではまだまだです。

Process:               FileFinder [8791]
Path:                  /Volumes/VOLUME/*/FileFinder
Identifier:            FileFinder
Version:               0
Code Type:             ARM-64 (Native)
Parent Process:        bash [8781]
Responsible:           Terminal [414]
User ID:               501

Date/Time:             2022-05-17 00:09:08.114 +0900
OS Version:            macOS 11.6.5 (20G527)
Report Version:        12
Anonymous UUID:        8099E198-41F7-976F-694A-6B1FB4EBE622


Time Awake Since Boot: 45000 seconds

System Integrity Protection: enabled

Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       KERN_INVALID_ADDRESS at 0x1600000000000000 -> 0x0000000000000000 (possible pointer authentication failure)
Exception Note:        EXC_CORPSE_NOTIFY

Termination Signal:    Segmentation fault: 11
Termination Reason:    Namespace SIGNAL, Code 0xb
Terminating Process:   exc handler [8791]

VM Regions Near 0:
--> 
    __TEXT                      102480000-10248c000    [   48K] r-x/r-x SM=COW  /Volumes/*

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libfltk.1.3.dylib             	0x00000001026d90e0 Fl_Menu_Item::test_shortcut() const + 20
1   libfltk.1.3.dylib             	0x00000001026d912c Fl_Menu_Item::test_shortcut() const + 96
2   libfltk.1.3.dylib             	0x00000001026d912c Fl_Menu_Item::test_shortcut() const + 96
3   libfltk.1.3.dylib             	0x00000001026bcdbc Fl_Choice::handle(int) + 384
4   libfltk.1.3.dylib             	0x00000001026c69b8 Fl_Group::handle(int) + 960
5   libfltk.1.3.dylib             	0x00000001026b4650 send_event(int, Fl_Widget*, Fl_Window*) + 148
6   libfltk.1.3.dylib             	0x00000001026b45ac Fl::handle_(int, Fl_Window*) + 1656
7   libfltk.1.3.dylib             	0x00000001026b4388 Fl::handle_(int, Fl_Window*) + 1108
8   libfltk.1.3.dylib             	0x00000001026aa2c0 -[FLView flagsChanged:] + 308
9   com.apple.AppKit              	0x00000001a1594c10 -[NSWindow(NSEventRouting) _reallySendEvent:isDelayedEvent:] + 5644
10  com.apple.AppKit              	0x00000001a1593398 -[NSWindow(NSEventRouting) sendEvent:] + 352
11  com.apple.AppKit              	0x00000001a1592270 -[NSApplication(NSEvent) sendEvent:] + 2568
12  libfltk.1.3.dylib             	0x00000001026a636c fl_mac_flush_and_wait(double) + 536
13  libfltk.1.3.dylib             	0x00000001026b373c Fl::run() + 44
14  FileFinder                    	0x0000000102485e88 main + 3836 (FileFinder.cpp:491)
15  libdyld.dylib                 	0x000000019eb41430 start + 4
# 最後の要素の0は無意味ではなかったようです

Fl_Menu_Item pulldown[] = {
  {"and"},
  {"or"},
  {"not"},
  0
};