中村@アールテクニカ
アールテクニカの中村です。
Qtによるマルチプラットフォーム開発の3回目です。
前回はQMLによるGUIアプリの新規作成まで行ないました。
今回はそのプロジェクトを元にしてC++によるネイティブコードの威力を活かすべく簡単な画像処理アプリを作成してみましょう。
QMLとC++との連携方法
前回はGUIをQMLで記述したので、QMLとC++を連携させる必要があります。何をどう連携させるかによっていくつか方法がありますが、今回作成する画像処理アプリに必要な以下の2つの方法を解説します。
QMLからC++のクラスを参照する方法
QMLから参照可能なクラスを作成するには、QObjectを継承する必要があります。
また、QMLから呼び出すメソッドには、Q_INVOKABLEというマクロをつける必要があります。
以下に、簡単なサンプルを提示します。
QMLとMyClassオブジェクトのバインディング
次に、MyClassのオブジェクトをQMLから参照できるようにバインディングします。
main.cppの以下の箇所でMyClassをバインディングします。
QMLからは、setContextPropertyの第一引数で指定した、"myclass"でMyClassオブジェクトを参照出来ます。
QQmlApplicationEngine engine; MyClass myclass(NULL); engine.rootContext()->setContextProperty("myclass", &myclass); engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QMLからMyClassのオブジェクトを参照する方法
ボタンが押されたら、myclassのgetText()メソッドに引数を渡して呼び出します。
main.qml
MainForm { id: mainform anchors.fill: parent button1.onClicked:messageDialog.show(qsTr(myclass.getText(5))) (略) }
QMLのImageエレメントの画像データをC++で作成する方法
QMLのImageエレメントはHTMLのimgタグのようにsourceプロパティで画像のURLを指定しますが、このURLにはQQuickImageProviderの派生クラスをソースとするように指定も出来ます。
MyImageProviderクラスの作成
画像データが必要になった際にrequestImage()メソッドが呼ばれ、このメソッドが返したQImageオブジェクトがImageエレメントの画像データとして使用されます。
class MyImageProvider : public QQuickImageProvider { public: MyImageProvider() :QQuickImageProvider(QQuickImageProvider::Image){} QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) { QImage image(requestedSize, QImage::Format::Format_RGBA8888); // ここでimageに対し処理を行う return image; } };
QMLにImageProviderをセットする
以下のコードでQMLのGUIエディタでImageエレメントのsourceプロパティに"image://imageprovider/xxx"を設定します。
この設定によりMyImageProvider::requestImage()が返すQImageが画像データとして使用されます。xxxの部分はidでMyImageProviderを複数のImageエレメントのソースに設定した場合の識別用に使用します。
main.cppの以下の箇所に、engine.addImageProvider(…)でMyImageProviderを追加します。
QQmlApplicationEngine engine; engine.addImageProvider(QLatin1String("imageprovider"), new MyImageProvider); engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
画像フィルターアプリの作成
以上のQML - C++連携の仕組みを使用して画像フィルターアプリを作成してみます。
フィルター処理は、せっかくネイティブコードで実装するので比較的処理の重いブラー(ぼかし)フィルターとします。
主な仕様は以下の通りとします。
- ブラーフィルター処理部分をC++で実装する
- ブラー半径をテキストフィールドで入力する
- 「フィルター実行」ボタンを押してフィルター処理を開始する
- 画像はアプリ埋め込みの画像を使用する
Filterクラス
まず、QMLから参照可能かつQMLの画像ソースとなるようにQobjectとQQuickImageProviderを継承したFilterクラスを作成します。
setKernelRadius()メソッドでフィルターの半径をセットします。
requestImage()メソッドではソース画像を読み込みブラーフィルターをかけた画像を返します。
フィルター処理部分では、フィルター半径に応じてコンボリューション行列を作成します。
今回は行列に0か1のみの値を使用し、1が円形に並ぶ単純なブラー効果とします。
半径2(5x5行列)のコンボリューション行列
0 | 0 | 1 | 0 | 0 |
0 | 1 | 1 | 1 | 0 |
1 | 1 | 1 | 1 | 1 |
0 | 1 | 1 | 1 | 0 |
0 | 0 | 1 | 0 | 0 |
QML
次に、main.qmlとMainform.ui.qmlをそれぞれ以下の様に編集します。
上記QMLをGUIエディタで表示すると以下のようになります。
左側に元画像、右側にフィルター適用後の画像を表示します。
半径を入力し「フィルター実行」ボタンを押すと右側にフィルター処理された画像が表示されます。
main.cpp
次に、main.cppでFilterクラスのオブジェクトをnewし、QMLにバインディングします。
gist.github.com
画像データの準備
最後に、テスト用画像をプロジェクトに追加します。
今回は以下のサイトからダウンロードさせて頂いた画像を使用しています。
http://www.ess.ic.kanagawa-it.ac.jp/app_images_j.html
Parrots.bmpをPNGに変換して、Parrots.pngとして使用しています。
画像サイズは256x256ピクセルです。
実行結果
ソースコードの編集が終わったら早速実行してみましょう。
Android版の実行結果
起動直後
フィルター半径=5で実行した結果
フィルター半径=30で実行した結果
Windows、AndroidともにC++でコーディングしたフィルター処理化正常に動作しているのが確認できました。しかし、フィルター処理はさほど最適化していないためフィルター半径が大きくなるとかなり時間がかかってしまいます。
フィルター半径=30で処理時間を計測した結果が以下になります。
OS | Windows 10 | Android 4.4 (Nexus7 2013) |
---|---|---|
CPU | Core i5 4670 3.4HHz | Snapdragon 1.5GHz |
処理時間 | 5.0秒 | 28.5秒 |
Androidではで30秒近くかかってしまうのでとても実用レベルとはいえません。実用レベルにするには、マルチスレッド化も含めた最適化が必須でしょう。
まとめ
Qtによるマルチプラットフォーム開発と題して3回にわたり記事を書いてきましたが、簡単なアプリを作成できましたのでここでいったん完結としたいと思います。
QMLを使えばそれほど苦労なくマルチプラットフォーム対応のGUIが作成できる反面、C++コーディングのメリットを活かすためにやはり最適化対応が必要になることがわかりました。
機会があれば、フィルター処理などの重い処理の最適化手法なども調べていきたいと思います。