[C++] 27 FLTKとOpenCV併用時のトラブル対応 M1 Mac

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8, OpenCV 4.5.5]

FLTKアプリにOpenCVの画像リサイズ機能を導入する際、トラブルが発生しました。何とか解決できたので、記録として残しておきます。OpenCVはHomebrewからインストールしています。

タイムリーなことに英語版Stack Overflowに本日付で解決例が投稿されていました。この内容と他の記事との合わせ技で解決できました。

Apple Silicon MacでOpenCVをフルに使えなくて困っていた方々には朗報でしょう。

トラブル1:
コンパイル時にFLTKのmath.hが存在しない/usr/include/math.hを読み込もうとしてfile not foundエラー発生。

原因:以下の流れです。
1.OpenCVがmath.hにアクセスしようとしている。
2.-Iオプションで指定されているFLディレクトリのmath.hに優先アクセスする。
3.FL/math.hは/usr/include/math.hをインクルードしようとする。
4.ファイルがないのでエラー発生。

解決策:
以前のXCodeは/usr/includeディレクトリにヘッダファイルを置いていました。FL/math.hのインクルード先を標準のmath.hに書き換えても、FL/math.hの内容が古いためエラーになります。私自身はFLTKのmath.hを使っていないので荒技ですが適当なディレクトリに隔離しました。仮にFL/math.hが使用可能だとしてもどう改変されているか分からないので使いたくないですね。この問題、エラーにならなかったら気付かないうちにFL/math.hを使わされていることになります。

トラブル2:
リサイズ機能を記述したソースのビルド時に以下のエラーが発生した。

Undefined symbols for architecture arm64:
  "cv::Mat::Mat()", referenced from:
      resize() in ImageInspector.o
  "cv::Mat::~Mat()", referenced from:
      resize() in ImageInspector.o
  "cv::Mat::operator=(cv::Mat&&)", referenced from:
      resize() in ImageInspector.o
  "cv::imread(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int)", referenced from:
      resize() in ImageInspector.o
  "cv::resize(cv::_InputArray const&, cv::_OutputArray const&, cv::Size_<int>, double, double, int)", referenced from:
      resize() in ImageInspector.o
  "cv::imwrite(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, cv::_InputArray const&, std::__1::vector<int, std::__1::allocator<int> > const&)", referenced from:
      resize() in ImageInspector.o
ld: symbol(s) not found for architecture arm64

原因:
ライブラリの参照先が不十分。今回は当てはまりませんが、makefileのオブジェクトファイル作成部分が抜けていても同種のエラーが出ます。

解決策:
コンパイルおよびビルド時に以下のオプションを追加しました。”pkg-config –cflags –libs opencv4″コマンドでも出てこないオプションです。

<コンパイル>
-lopencv_core -lopencv_imgcodecs -lopencv_highgui -lopencv_imgproc

<ビルド>
-L/opt/homebrew/Cellar/opencv/4.5.5/lib

参考サイト1
参考サイト2

[C++] 26 libpngによるpng情報の取得

libpngを使ってpngファイルの縦横サイズと解像度のデータを取得しました。

width, height, dpi_x, dpi_yの値を配列std::array<int,4>にして戻り値としています。main関数は省略します。

#include "png.h"
#include <array>

std::array<int,4> getInfoPNG(const char*);

#define SIGNATURE_NUM 8
#include "process_image.h"
#include <string>

FILE *fi;
unsigned int width;
unsigned int height;
unsigned int res_x;
unsigned int res_y;
unsigned int readSize;
png_structp png;
png_infop info;
png_byte signature[8];

std::array<int,4> getInfoPNG(const char* filename){
	fi = fopen(filename, "rb");
	readSize = fread(signature, 1, SIGNATURE_NUM, fi);

	png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	info = png_create_info_struct(png);

	png_init_io(png, fi);
	png_set_sig_bytes(png, readSize);
	png_read_png(png, info, PNG_TRANSFORM_PACKING | PNG_TRANSFORM_STRIP_16, NULL);
	
	width = png_get_image_width(png, info);
  	height = png_get_image_height(png, info);
	res_x = png_get_x_pixels_per_inch(png,info);
	res_y = png_get_y_pixels_per_inch(png,info);

	png_destroy_read_struct(&png, &info, NULL);
  	fclose(fi);

	return {(int)width,(int)height,(int)res_x,(int)res_y};
}

参考サイト

[C++] 24 文字列のバイト列への変換 文字化け調査

文字化けの原因調査等に必要なので記録しておきます。

#include <string>
#include <iostream>
#include <stdio.h>
#include <vector>
#include <bitset>

using std::cout; using std::string;
using std::endl; using std::bitset;

void binary_convert(string str){
	for (int i = 0; i < str.length(); ++i) {
		bitset<8> bs4(str[i]);
		cout << str << " " << i+1 << "番目 " << bs4 << endl;
	}
	cout << "end"<< endl;
}

int main(int argc, char **argv){
	string str1 = "123";
	string str2 = "バイト列";

	binary_convert(str1);
	binary_convert(str2);
}
--------------------------------------------------

出力
--------------------------------------------------
123 1番目 00110001
123 2番目 00110010
123 3番目 00110011
end
バイト列 1番目 11100011
バイト列 2番目 10000011
バイト列 3番目 10010000
バイト列 4番目 11100011
バイト列 5番目 10000010
バイト列 6番目 10100100
バイト列 7番目 11100011
バイト列 8番目 10000011
バイト列 9番目 10001000
バイト列 10番目 11100101
バイト列 11番目 10001000
バイト列 12番目 10010111
end

[C++] 23 FLTK : Fl_Inputのカーソルずれ対策

Fl_Input内のテキストを編集しようとした際にカーソルにずれがあることが発覚しました。このずれた位置からでないと正確に編集できません。

Mac版ExcelのVisual Basic Editorでも似たような現象があり、VBAからPythonに移行するきっかけとなりました。

今回も私にとっては一発レッド案件でしたが、フォントをデフォルトのFL_HELVETICAから日本語フォントに変更することで解消しました。

// main関数で設定

Fl::set_font(FL_HELVETICA, "Osaka");
修正前
修正後

[C++] 22 split関数の自製

C++にはsplit関数というものがないのでネット情報から拝借しました。他にも色々方法があります。

下記コードではsplit関数でファイル名の拡張子を取り出し、checkImage関数によりpngであれば1、jpgであれば2、それら以外は-1を返します。

C++11から採用のregex_search関数で大文字・小文字に関係なく検索しています。

C/C++は型変換が面倒ですね。C/C++を常用していれば慣れるのでしょうが、たまに使う程度だと都度調べることになるので疲れます。一覧表を用意したいところです。

#include "process_image.h"
#include <string>
#include <vector>
#include <regex>

// split関数
std::vector<std::string> split(std::string str, char del) {
    int first = 0;
    int last = str.find_first_of(del);
 
    std::vector<std::string> result;
 
    while (first < str.size()) {
        std::string subStr(str, first, last - first);
 
        result.push_back(subStr);
 
        first = last + 1;
        last = str.find_first_of(del, first);
 
        if (last == std::string::npos) {
            last = str.size();
        }
    }
	return result;
}

int checkImage(const char* filename){
	std::string filename_str = std::string(filename);
    char del = '.';
	
	std::vector<std::string> list = split(filename_str, del);
	std::string extension = list.back();

	std::regex png("png", std::regex_constants::icase);
	std::regex jpg("jpg", std::regex_constants::icase);
    std::smatch m;

	if (std::regex_search(extension, m, png)){
		return 1;
	} else if (std::regex_search(extension, m, jpg)){
		return 2;
	} else {
		return -1;
	}
}

参考サイト

[C++] 21 Makefile作例 srcディレクトリ単層

以前紹介したMakefileはsrcディレクトリ内にサブディレクトリがあるケースのものでしたが、サブディレクトリなしのMakefileを記しておきます。

# コンパイラ設定
COMPILER = clang++
DEBUG = -g

# フラグ設定
CPPFLAGS = $(shell pkg-config libpng --cflags)
LDFLAGS = $(shell pkg-config libpng --libs)

# includeパス設定(-I)
INCLUDE = -I./include -I/opt/homebrew/Cellar/libpng/1.6.37/include

# ライブラリパス設定(-l)
LIBRARY0 =

# 優先ライブラリパス設定(-L)
LIBRARY = -L/opt/homebrew/Cellar/jpeg/9e/lib -L/opt/homebrew/Cellar/libpng/1.6.37/lib

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

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

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

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

$(OBJDIR)/libpng_test.o: $(SRCDIR)/libpng_test.cpp
	$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<

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

# コンパイル&ビルド
all: clean $(TARGET)

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

[C++] 20 Exiv2 Exifデータの取得

画像ファイルのinfoデータを取得する方法について調べていたらExifデータを取得するExiv2というライブラリを見つけたので試してみました。

Homebrewからインストールも可能ですが、今回は公式サイトからtar.gzファイルをDLしました。

最初はエラー出力から自力でフラグを探っていきましたが結局うまくいかず、CPPFLAGSやLDFLAGSをコマンドで取得しMakefileを作成しました。

公式サイトにあるexifprint.cppというソースコードでテストし、Exif全データを出力させることができました。

なおPCで作成した画像ファイルのinfoデータはこのライブラリでは読み取れません。あくまで写真のメタデータ取得用です。

# コンパイラ設定他
COMPILER = clang++
DEBUG = -g

# フラグ設定
CPPFLAGS = $(shell pkg-config exiv2 --cflags )
LDFLAGS = $(shell pkg-config exiv2 --libs )

# includeパス設定
INCLUDE =

# linkパス設定
LINK =

# ライブラリパス設定
LIBRARY =

# 実行ファイル設定
TARGET = exifprint
TARGETDIR = ../bin

# ソースコードパス
SRCROOT = .

# oファイルの出力ディレクトリ
OBJROOT = ../obj

# ソースディレクトリ
SRCDIR = ../src

# オブジェクトファイル
OBJECT = $(OBJROOT)/$(TARGET).o

# cppファイルからoファイル作成
$(OBJROOT)/%.o: $(SRCROOT)/%.cpp
	$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<

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

# コンパイル&ビルド
all: clean $(TARGET)

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

作成した実行ファイルでExif全データを出力するコマンド

exifprint [画像ファイルパス]
Exif.Image.Make                              0x010f Ascii      18  NIKON CORPORATION
Exif.Image.Model                             0x0110 Ascii      11  NIKON D500
Exif.Image.Orientation                       0x0112 Short       1  1
Exif.Image.XResolution                       0x011a Rational    1  300/1
Exif.Image.YResolution                       0x011b Rational    1  300/1
Exif.Image.ResolutionUnit                    0x0128 Short       1  2
Exif.Image.Software                          0x0131 Ascii      10  Ver.1.20 

exifprint.cppソースコード

[C++] 19 FLTK:setTextとgetTextに相当する機能

FLTKでJava・SwingのsetTextとgetTextに相当する機能を探すのに少々時間がかかりました。

Swing : setText FLTK : value(const char *)
Swing : getText FLTK : value()

value関数が両方の機能を担っています。Fl_Inputの文字列を全削除する時はvalue(“”)と書きます。value()では消去ではなくコピーするので要注意です。こういった仕組みは軽量高速化のために余計な関数を設定しないという思想の表れと理解しました。

一方、Qtは速さを犠牲にしてsetTextやgetTextを導入しています。JavaやPythonからプログラミングの世界に入った方々はQtに親しみを覚え、FLTKには違和感しかないでしょう。

私もvalueの機能を知った時は脱力しましたが、一貫した設計思想に感銘を受けました。

ところで、新興言語のRustがFLTKを積極的に活用しているようでそちらに興味が向きはじめました。

C++版が完成したら次は学習を兼ねたRust版作成を課題候補にしておきます。

<関連する関数>

void inspect(void){
    output_line->insert("inspect\n");
    const char *path = input_line->value();
    output_line->insert(path);
    output_line->insert("\n");
}
void resize(void){
    output_line->insert("resize\n");
}
void icns(void){
    output_line->insert("icns\n");
}

void execute_cb(Fl_Widget*, void*) {
    onoff_inspect = inspect_rbtn->value(); 
    onoff_resize = resize_rbtn->value();
    onoff_icns = icns_rbtn->value();

    if (onoff_inspect == 1){
        inspect();
    } else if (onoff_resize == 1){
        resize();
    } else {
        icns();
    }
}
void clear_cb(Fl_Widget*, void*) {
    input_line->value("");
}

[C++] 18 FLTK:ドラッグ&ドロップ

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

一つの山場と予想されたドラッグ&ドロップですが、これについては有益なネット情報がありましたので拝借しました。

この40行のコードを書こうと思ったら今の私なら半日仕事です。とても助かりました。参考コードでauto型の存在を初めて知りました(C++11から採用)。Javaもvar型で型推論できますが、使ったことはありませんでした。

Fl_InputとセットになったFL_Box継承のBoxクラスは他のウィジェットが見えるように invisible(FL_NO_BOX)にするなど私なりに少し工夫を入れています。

ここまででappファイルのサイズはたったの942KBです。この分だと最終的には2,3MB以内に収まる感じがします。PyQt6版の100分の1です。

期待通りの爆速軽量アプリに仕上がりそうです。

<関連する関数とクラス>
class Box : Fl_Box {
  Fl_Input* input_line;
  public:
    Box(int, int, int, int, Fl_Input*);
  private:
    auto handle(int) -> int override;
};

Box::Box(int x, int y, int width, int height, Fl_Input* input) : Fl_Box(FL_NO_BOX, x, y, width, height, "") {
   this->input_line = input;
}

auto Box::handle(int event) -> int {
   switch (event) {
     case FL_DND_DRAG:
     case FL_DND_ENTER:
     case FL_DND_RELEASE:
     return 1;

     case FL_PASTE:
       input_line->value(Fl::event_text());
     return 1;

     default:
     return Fl_Box::handle(event);
  }
}

<mainクラスの一部>
input_line = new Fl_Input(50,10,220,25,"");

Box *box = new Box(0, 0, 360,220, input_line);

参考サイト

[C++] 17 FLTK:バージョン1.3.8のビルド&インストール

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8(24/2/13現在 1.3.9)]

HomebrewからApple Silicon用ビルド済みバイナリが配布されていますが、自分でビルドしたかったのでGitHubにあるバージョン1.4.0のREADME.macOS.mdを読みながら試してみました。

1.4.0ではビルドはできたもののインストールに失敗、安定版の1.3.8ではうまくいきました。

これでFLTKを自分の好きなように改変することも可能になりました。

<FLTK 1.3.8のインストール方法>
事前にCMakeなど必要なツールをインストールしておく。

1.公式サイトからバージョン1.3.8のtar.gzファイルをダウンロードする。

2.ファイルを解凍してfltkフォルダ内にディレクトリを作成し、cmakeコマンドを実行する。

mkdir build
cd build
mkdir Makefile
cd Makefile
cmake -G "Unix Makefiles" \
    -D OPTION_USE_SYSTEM_LIBJPEG=Off \
    -D OPTION_USE_SYSTEM_ZLIB=Off \
    -D OPTION_USE_SYSTEM_LIBPNG=Off \
    -D OPTION_USE_THREADS=ON \
    ../..

2024/2/13 オプション追加:
-D OPTION_USE_THREADS=ONによりマルチスレッドサポートを有効にする。Fl::lockなどが使えるようになる。

3.ビルドする。

make

4.デモアプリを起動する(省略可)。

open bin/test/demo.app

5.インストールする。[/usr/local/bin]

sudo make install