[C++] 239 FLTK : Fl_Boxに高さ不足の画像を表示した際の座標 アスペクト比

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 3.8.1, NO IDE]

Fl_Boxに高さ不足の画像を表示した際、画像の左上を原点(0, 0)とするための調整が少しややこしかったので書き留めておきます。

横幅が一致していて高さが足りない画像の場合はその縦位置は適当に置かれているわけではなく、残った空白がアスペクト比で配分されていました。幾何学の世界ですね。

今回はアスペクト比が4:3なので、縦の空白は上部:下部 = 3:4になっています。これにより画像の左上Y座標をゼロにするための調整値が算出できます。

Fl_PNG_Image *png = new Fl_PNG_Image((paths[0]).c_str());
anaW = wInput -> value();
anaH = hInput -> value();

aspect = stof(string(anaW))/stof(string(anaH));

if (aspect > 4/3){
    newH = stof(string(anaH)) * 480/stof(string(anaW));
    cout << "newH: " << newH << endl;
    png_copy = png -> copy(480,round(newH));

    // Y調整値算出
    adjY = round((360 - newH) * 3/7);

} else {
    newW = stof(string(anaW)) * 360/stof(string(anaH));
    cout << "newW: " << newW << endl;
    png_copy = png -> copy(round(newW),360);
}

showBox -> image(png_copy);
showBox -> redraw();

[C++] 238 FLTK : アスペクト比固定、小数点以下の扱い

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 3.8.1, NO IDE]

動画編集アプリの左下に画像をコマ送りで表示させるのですが、アスペクト比を元動画のままにして、経過時間を小数点第3位までとしました。

floatは有効桁数が7桁なので小数点以下で四捨五入しても、1.570000のような表示になります。そこでfloatを文字列に変換し、無限ループを利用して末尾が0である限り削除し続けるようにしました。

ただこの方法では小数点以下の桁数を固定できないのがデメリットです。Pythonであれば簡単に桁数を揃えられそうですが、C++は面倒なので最小限の手立てに留めておきます。

void showCB(Fl_Widget*, void*) {
    paths = getFilePath("/Volumes/DATA_m1/VideoEditor/images", "png");
    // スライダーの数値を取得
    slider -> bounds(0, (double)(paths.size() -1));

    showData(fpsFile);
    fpsChar = fpsInput -> value();
    if (fpsChar == NULL){
        return;
    }

    string fpsStr = string(fpsChar);
    sec = frameNum/stof(fpsStr);
    sec2 = round(sec*1000)/1000;
    sec2_str = to_string(sec2);

    while(1){
        if (sec2_str.back() == '0' or sec2_str == "0."){
            sec2_str.pop_back();
        } else {
            break;
        }
    }
    imageSec -> value(sec2_str.c_str());

    imageNumStr = to_string(frameNum +1) + "/" + to_string(paths.size());
    imageNum -> value(imageNumStr.c_str());

    Fl_PNG_Image *png = new Fl_PNG_Image((paths[0]).c_str());
    anaW = wInput -> value();
    anaH = hInput -> value();

    aspect = stof(string(anaW))/stof(string(anaH));

    if (aspect > 4/3){
        newH = stof(string(anaH)) * 480/stof(string(anaW));
        cout << "newH: " << newH << endl;
        png_copy = png -> copy(480,round(newH));
    } else {
        newW = stof(string(anaW)) * 360/stof(string(anaH));
        cout << "newW: " << newW << endl;
        png_copy = png -> copy(round(newW),360);
    }

    showBox -> image(png_copy);
    showBox -> redraw();
}

[C++] 237 FLTK : マウスをドラッグして四角形を描く その3 フレーム描画 Fl_Box

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 3.8.1, NO IDE]

FLTKのfl_frame関数でフレームを描くと残像が重なって使えなかったのですが、最新のフレームだけ表示すると正常になりました。

また前のコードでは拡げたエリアを縮小することはできませんでした。このコードでは伸縮自在です。

ただし複数のフレームを描くと直前のフレームが消えてしまうので、これを残す方法を考える必要があります。あくまで見た目の問題であって、フレームの座標自体は保存できるようになっています。

課題はありますが、かなり前進したと言えるでしょう。

#pragma once
#include <FLstd.h>
#include "cppstd.h"

class BoxXY : public Fl_Box {
    Fl_Input* inputFrame1;
    Fl_Input* inputFrame2;
    Fl_Input* inputFrame3;
    Fl_Input* inputFrame4;

    public:
        BoxXY(int x, int y, int width_input, int height_input, Fl_Input* input1,Fl_Input* input2, Fl_Input* input3,Fl_Input* input4);
        void draw();
    private:
        int handle(int);
};
#include "BoxXY.h"

extern Fl_Box *show_box;
int xx1, yy1, xx2, yy2, xx0, yy0;

BoxXY::BoxXY(int x, int y, int width_input, int height_input, Fl_Input* input1,Fl_Input* input2, Fl_Input* input3,Fl_Input* input4) : Fl_Box(FL_FLAT_BOX, x, y, width_input, height_input, "") 
{
    this->inputFrame1 = input1;
    this->inputFrame2 = input2;
    this->inputFrame3 = input3;
    this->inputFrame4 = input4;
}

void BoxXY::draw(){
    // 線の色(第1引数)はAからXの24段階グレースケール4値(top, left, bottom, rightの順)
    // 灰色線
    fl_frame("LLLL",xx1+65, yy1+190, xx0-xx1, yy0-yy1);
    // 白線
    // fl_frame("XXXX",xx1+65, yy1+190, xx0-xx1, yy0-yy1);
    // 黒線
    // fl_frame("AAAA",xx1+65, yy1+190, xx0-xx1, yy0-yy1);
}

int BoxXY::handle(int event){
    switch (event) {
        case FL_PUSH:{
            xx1 = Fl::event_x() - 65;
            yy1 = Fl::event_y() - 190;

            inputFrame1->value(to_string(xx1).c_str());
            inputFrame1->textsize(12);

            inputFrame2->value(to_string(yy1).c_str());
            inputFrame2->textsize(12);

            return 1;
        }
        case FL_RELEASE:{
            xx2 = Fl::event_x() - 65;
            yy2 = Fl::event_y() - 190;

            inputFrame3->value(to_string(xx2).c_str());
            inputFrame3->textsize(12);

            inputFrame4->value(to_string(yy2).c_str());
            inputFrame4->textsize(12);

            return 1;
        }
        case FL_DRAG:{
            xx0 = Fl::event_x() - 65;
            yy0 = Fl::event_y() - 190;

            inputFrame3->value(to_string(xx0).c_str());
            inputFrame3->textsize(12);

            inputFrame4->value(to_string(yy0).c_str());
            inputFrame4->textsize(12);

            show_box -> redraw(); // この行を追加
            this->redraw();

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

[C++] 236 FLTK : マウスをドラッグして四角形を描く その2 図形の削除 Fl_Box

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 3.8.1, NO IDE]

前回の続きです。

fl_draw_box関数で描いた図形はキャンバスに相当するFl_Boxをredrawすると削除できます。

四角形ではなくフレームを描く方法も試しましたが、残像が何個も重なるため実用には至りませんでした。

あとは図形を透過させれば出来上がりです。

clearBtn = new Fl_Button(565,190,50,30,"矩形\nクリア");
clearBtn->color(fl_rgb_color(112,128,144));
clearBtn->labelcolor(fl_rgb_color(208,207,207));
clearBtn->labelsize(12);
clearBtn->callback(clearCB);

--------------------------------------------------
void clearCB(Fl_Widget*, void*) {
    x1_input -> value(0);
    y1_input -> value(0);
    x2_input -> value(0);
    y2_input -> value(0);

    show_box -> redraw();
}

[C++] 235 FLTK : マウスをドラッグして四角形を描く その1 Fl_Box

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 3.8.1, NO IDE]

動画にモザイクをかける範囲を指定する際、同時に四角形の枠が表示されるようにすると作業しやすくなります。

今のところ四角形自体は描けていますが、透明度が設定できないため描画した範囲の内容が隠れて全く見えません。

Fl_GL_Windowではアルファ値を設定できるようなのでそのうちテストしたいです。

記事とは関係ないですが、今日からMacOS Montereyを再度試しています。現時点の最新版はVentura 13.2です。

#pragma once
#include <FLstd.h>
#include "cppstd.h"
#include "Box.h"
#include <Line.h>

class BoxXY : public Fl_Box {
    Fl_Input* inputA1;
    Fl_Input* inputA2;
    Fl_Input* inputA3;
    Fl_Input* inputA4;

    public:
        BoxXY(int x, int y, int width_input, int height_input, Fl_Input* input1,Fl_Input* input2, Fl_Input* input3,Fl_Input* input4);
        void draw();
    private:
        int handle(int);
};
#include "BoxXY.h"

int xx1, yy1, xx2, yy2, xx0, yy0;

BoxXY::BoxXY(int x, int y, int width_input, int height_input, Fl_Input* input1,Fl_Input* input2, Fl_Input* input3,Fl_Input* input4) : Fl_Box(FL_FLAT_BOX, x, y, width_input, height_input, "") 
{
    this->inputA1 = input1;
    this->inputA2 = input2;
    this->inputA3 = input3;
    this->inputA4 = input4;
}

void BoxXY::draw(){
    fl_rectf(xx1+65, yy1+190, xx0-xx1, yy0-yy1, fl_rgb_color(255,250,240));

    // または
    // fl_draw_box(FL_FLAT_BOX, xx1+65, yy1+190, xx0-xx1, yy0-yy1, fl_rgb_color(255,250,240));

    // 以下の方法では残像が出る
    // fl_frame("XXXX",xx1+65, yy1+190, xx0-xx1, yy0-yy1);
    
}

int BoxXY::handle(int event){
    switch (event) {
        case FL_PUSH:{
            xx1 = Fl::event_x() - 65;
            yy1 = Fl::event_y() - 190;

            inputA1->value(to_string(xx1).c_str());
            inputA1->textsize(12);

            inputA2->value(to_string(yy1).c_str());
            inputA2->textsize(12);

            return 1;
        }
        case FL_RELEASE:{
            xx2 = Fl::event_x() - 65;
            yy2 = Fl::event_y() - 190;

            inputA3->value(to_string(xx2).c_str());
            inputA3->textsize(12);

            inputA4->value(to_string(yy2).c_str());
            inputA4->textsize(12);

            return 1;
        }
        case FL_DRAG:{
            xx0 = Fl::event_x() - 65;
            yy0 = Fl::event_y() - 190;

            inputA3->value(to_string(xx0).c_str());
            inputA3->textsize(12);

            inputA4->value(to_string(yy0).c_str());
            inputA4->textsize(12);

            this->redraw();

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

[C++] 234 FLTK : vector不具合の回避

[M1 Mac, Big Sur 11.7.2, clang 13.0.0, FLTK 3.8.1, NO IDE]

ビデオ編集アプリがvector<string>の要素取得で不具合を起こしました。アプリの実行ファイルでは問題なく動作し、appファイルでおかしくなります。

以前よりこのアプリに限らずvectorがらみの不具合が頻発しており、仕方ないのでvectorを作成せず各要素を直接変数に代入しました。

今回はswitch文を使いましたが、array<string, 5>への代入&取得でも上手くいくと思います。

MacOSのレポートによるとlibc++.1.dylibあたりに問題があるようです。深追いはやめておきます。

このアプリで音声なし動画の分割、結合、複数箇所モザイク作成ができます。Final Cut Proは持っていますが、サクッと編集できるツールとして作りました。サイズは1.6MBでffmpegやffprobeファイルは含みません。

FFmpegのライセンスはLGPLですから、動的リンクであればソースコード非開示で頒布可能のようです。

FLTKではGUIの野暮ったさが拭えないので、頒布するならwxWidgetsで作り直しでしょうか。

いずれにせよこのアプリのGUIはまだまだ手入れが必要です。

string width_str, height_str, fps_str, time_str, frames_str;

void showData(string file){
    std::ifstream ifFile;
    string buffer;

    ifFile.open(file, std::ios::in);
    std::getline(ifFile, buffer);
    cout << "buffer: " << buffer << endl;

    // bufferを先頭からカンマで分割していく
    int first = 0;
    int last = buffer.find_first_of(",");

    int count = 0;
    while (first < buffer.size()) {
        string subStr(buffer, first, last - first);
        
        switch (count){
            case 0:
                width_str = subStr;
                break;
            case 1:
                height_str = subStr;
                break;
            case 2:
                fps_str = subStr;
                break;
            case 3:
                time_str = subStr;
                break;
            case 4:
                frames_str = subStr;
                break;
            default:
                break;
        }
        first = last + 1;
        last = buffer.find_first_of(",", first);
 
        if (last == string::npos) {
            last = buffer.size();
        }
        count += 1;
    }

<以下略>
MacOSレポート

[C++] 233 FLTK : ビデオ編集アプリの改良

[M1 Mac, Big Sur 11.7.2, clang 13.0.0, FLTK 3.8.1, NO IDE]

昨年2022年9月に作成したビデオツール統合アプリから、ビデオ編集機能(音声なし)を取り出して改良を試みます。

コードの内容を大分忘れていて思い出すのに少々時間がかかりました。

分離したアプリをMakefileで作成できるようになったので、これから改良に着手します。

[C++] 232 FLTK : ハイパーリンクの代替手段検討

[M1 Mac, Big Sur 11.7.2, clang 13.0.0, FLTK 3.8.1, NO IDE]

ダイアログ内のテキストから外部サイトへハイパーリンクさせたかったのですが難易度が高いため、Menuからの選択でコールバック関数によりリンク先がブラウザで開くようにしました。

Fl_Menu_ItemでHelpメニューを設定すると自動的にMacOSのHelp内を検索する窓が出現しました。これは不要なのでHelpをHELPに一時的に変更しています。検索窓が出ない方法が分かり次第、Helpに戻すつもりです。

今になって思い浮かんだのですが、テキスト部分をFl_Buttonにて枠なしで作成し前後に文字列をくっ付ければハイパーリンク風にできなくもないですね。

Helpの場合
HELPの場合
static Fl_Menu_Item	items[] = {
        { "★", 0, 0, 0, FL_SUBMENU },
        { "読込", 0, loadFavList, 0, 0 },
        { "保存", 0, 0, 0, FL_SUBMENU },
        { "名前を付けて保存", 0, saveFavList, 0, 0 },
        { "自動保存", 0, saveFavListAuto, 0, 0 },
        { 0 },
        { "消去", 0, deleteFavList, 0, 0 },
        { 0 },
        { "履歴", 0, 0, 0, FL_SUBMENU },
        { "消去", 0, deleteHistory, 0, 0 },
        { 0 },
        { "HELP", 0, 0, 0, FL_SUBMENU },
        { "Website", 0, openWebsite, 0, 0 },
        { 0 },
    #ifdef DEV
        { "開発", 0, 0, 0, FL_SUBMENU },
        { "開発モード", 0, showSubWindow, 0, 0 },
        { 0 },
        { 0 }
    #else
        { 0 },
    #endif
    };
void openWebsite(Fl_Widget*, void*){
    string cmd = "open \"リンク先のURL\"";
    system(cmd.c_str());
}

[C++] 231 wxWidgets : 外部サイトへのハイパーリンク

[M1 Mac, Big Sur 11.7.2, clang 13.0.0, wxWidgets 3.2.0, NO IDE]

ちょうどいいサンプルをGitHubに見つけ、早速活用させていただきました。見にくいですが青字部分がリンクになっています。

wxWidgetsに対する興味が増してきたので自分のFLTKアプリを移植しようか考えたものの、wxButtonの色設定ができないのであきらめました。OSのLook&Feelに依存している分、自由度は低いようです。

となるとFLTKにHyperLink機能実装となりますが、Fl_Widgetよりさらに上の概念を構築する必要がありハードルはかなり高そうです。

#include <wx/debug.h>
#include <wx/app.h>
#include <wx/frame.h>
#include <wx/hyperlink.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/stattext.h>

namespace Examples {
  class Frame : public wxFrame {
  public:
    Frame() : wxFrame(nullptr, wxID_ANY, "HyperlinkCtrl example") {
      panel->SetSizer(boxSizerLinks);
      boxSizerLinks->Add(staticText1, 0, wxSTRETCH_NOT, 0);
      boxSizerLinks->Add(hyperlinkCtrl1, 0, wxSTRETCH_NOT, 0);
      boxSizerLinks->Add(staticText2, 0, wxSTRETCH_NOT, 0);
      boxSizerLinks->Add(hyperlinkCtrl2, 0, wxSTRETCH_NOT, 0);
    }
    
  private:
    wxPanel* panel = new wxPanel(this);
    wxBoxSizer* boxSizerLinks = new wxBoxSizer(wxVERTICAL);
    wxStaticText* staticText1 = new wxStaticText(panel, wxID_ANY, " Googleへのリンク");
    wxHyperlinkCtrl* hyperlinkCtrl1 = new wxHyperlinkCtrl(panel, wxID_ANY, "Google", "https://www.google.com/");
    wxStaticText* staticText2 = new wxStaticText(panel, wxID_ANY, " Yahoo!へのリンク");
    wxHyperlinkCtrl* hyperlinkCtrl2 = new wxHyperlinkCtrl(panel, wxID_ANY, "Yahoo!", "https://www.yahoo.co.jp/");
  };

  class Application : public wxApp {
    bool OnInit() override {
      (new Frame())->Show();
      return true;
    }
  };
}

wxIMPLEMENT_APP(Examples::Application);

参考サイト

[C++] 230 ウィジェットツールキットwxWidgetsを試す Makefile

[M1 Mac, Big Sur 11.7.2, clang 13.0.0, wxWidgets 3.2.0, NO IDE]

FLTKにはハイパーリンク機能がないことが判明したため、wxWidgetsからの機能移植を模索しています。

wxWidgets導入自体は今年8月に済ませていたので、参考サイトから簡単なコードを拝借してMakefileを自作しビルドしてみました。

GitHubのソースからビルドしたwxWidgetsが不完全であることが分かり、Homebrew版に切り替えました。ただHomebrew版はdylibしかなく、静的ライブラリは別途ビルドする必要があるようです。

マクロが多くて初心者泣かせのサンプルコードでしたが、まあそのうち慣れるでしょう。

FLTKに比べてかなり多機能なのにこれもまたクセが強すぎるためか、ユーザー数はさほど多くないようです。

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

# フラグ
CPPFLAGS = $(shell wx-config --inplace --cxxflags) -std=c++17
LDFLAGS = $(shell wx-config --inplace  --libs) -lc++

# includeパス(-I)
INCLUDE = -I./include -I/Volumes/DATA_m1/code/cpp/mylib/include \
-I/opt/homebrew/Cellar/wxwidgets/3.2.0_1/include/wx-3.2

# ライブラリ(-l)
LIBRARY0 =

# ライブラリパス(-L)
LIBRARY = -L/opt/homebrew/Cellar/wxwidgets/3.2.0_1/lib

# ソースファイル
SRCDIR = ./src
SRCS = $(shell find $(SRCDIR) -type f)

# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(addprefix $(OBJDIR), $(patsubst ./src/%.cpp,/%.o,$(SRCS)))

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

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

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

# 全ソース強制コンパイル
.PHONY:all
all: clean $(TARGET)

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

参考サイト