2018年11月3日土曜日

QML での HiDPI 画像保存と Image への読み込み方法


表示状態のビジュアルアイテムに対して Item.grabToImage() を呼び出して表示内容を画像ファイルに保存できます。二つの仮引数があり第一仮引数にはコールバック関数を以下のように指定して画像ファイルを保存します
visualItem.grabToImage(function(result) {
                           if (!result.saveToFile("imageFileName.png")) {
                               // 保存できなかったときの処理
                           }                                         
                       });
コールバック関数の第一仮引数の result の型は ItemGrabResult で、そのオブジェクトの saveToFile() 関数を呼び出してファイルを保存します。保存に成功すれば true が返り、失敗すれば false が返ります。

コールバック関数の第二仮引数には保存する画像ファイルのサイズを指定します。省略するとビジュアルオブジェクトのサイズが使われます。HiDPI の場合には描画内容を縮小して画像ファイルに保存されるため、細い線が欠けたりテキストが粗く表示されてしまいます。コールバック関数の第二仮引数にビジュアルオブジェクトのサイズを Screen.devicePixelRatio 倍したサイズを指定すると HiDPI での表示内容がそのまま画像ファイルに保存されます。Screen.devicePixelRatio の値が 2 の場合には以下のようになります。
visualItem.grabToImage(function(result) {
                           if (!result.saveToFile("imageFIleName@2x.png") {
                               // 保存できなかったときの処理
                           }
                       }, Qt.size(2 * visualItem.width, 2 * visualItem.height));
保存ファイル名に @2x を付加しているのは、以降の説明のように Image で HiDPI 表示するためです。

ビジュアルアイテムの表示内容を HiDPI でファイルに保存するサンプルコードです。
grabtoimage.qml:
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.12

ApplicationWindow {
    visible: true

    minimumWidth: 300
    minimumHeight: 300

    Rectangle {
        id: yellowRect

        width: 200
        height: 200

        anchors.centerIn: parent

        color: "yellow"
        border {
            color: "grey"
            width: 1/Screen.devicePixelRatio
            pixelAligned: Screen.devicePixelRatio > 1 ? false : true
        }

        Text {
            id: messageText

            anchors.centerIn: parent

            text: "Yellow"
            font.pointSize: 20
        }

    }

    MouseArea {
        anchors.fill: yellowRect

        onClicked: {
            if (Screen.devicePixelRatio > 1) {
                yellowRect.grabToImage(function(result) {
                                           result.saveToFile(`YellowRectangle@${Screen.devicePixelRatio}x.png`);
                                       }, Qt.size(Screen.devicePixelRatio * yellowRect.width, Screen.devicePixelRatio * yellowRect.height));
            } else {
                yellowRect.grabToImage(function(result) {
                                           result.saveToFile("YellowRectangle.png");
                                       });
            }
        }
    }
}
Rectangle の Qt のドキュメントに記載されているプロパティーの説明には間違いがあり、実際のプロパティーは以下のようになっています。
Rectangle QML タイプのプロパティー
    color: color = "#ffffffff"
    gradient: Gradient = null
    border: QQuickPen(this) readonly constant
        width: real = 1.0
        color: color = "#ff000000"
        pixelAligned: bool = true
        radius: real = 0.0
Rectangle QML タイプのドキュメントには border.width の型が int と記載されていますが実際の型は real です。Rectangle には浮動小数点数値が渡って来て、小数点以下を四捨五入された値を使っています。例えば、0.5 を指定すると 1.0 が縁の細さになります。もしこのプロパティーの型が int ならば小数点以下を切り捨てた値が Rectangle に渡されますが、そうはならずに前述のように処理されます。border.pixelAligned は Rectangle QML タイプのキュメントに記載されていないプロパティーです。border.pixelAligned を false に設定すると Screen.devicePixelRatio の値が 2 の HiDPI ディスプレイでは border.width に 0.5 の場合に、0.5 ピクセルの細さで縁が描画されます。

HiDPI での確認のために Text も表示しています。

Image QML タイプで HiDPI 画像を表示するには以下のように画像ファイルを用意します。前述のサンプルコードでもこのファイル名命名規則に従ってます。
imageFileName.png     Screen.devicePixelRatio が 1 用、通常サイズ
imageFileName@2x.png             〃              2 用、サイズ 2 倍 
imageFileName@3x.png             〃              3 用、サイズ 3 倍
@2x の画像ファイルは表示サイズの二倍サイズで、@3x の画像ファイルは表示サイズの三倍サイズで画像ファイルを作成しておきます。Image の source プロパティーに通常サイズの画像ファイル名を指定しておけば、自動的に実行時の Screen.devicePixelRatio に対応する画像ファイルを読み込み、HiDPI で表示します。
Image {
    source: "YellowRectangle.png"
}
以下のように環境変数を設定すると HiDPI 自動ファイル切り替えを行わないようにできます。スクリーンが HiDPI の場合でも通常サイズの画像が使われるので当然ながら表示は粗くなります。
QT_HIGHDPI_DISABLE_2X_IMAGE_LOADING=1
Qt の Scalability などのドキュメントに、このファイル名命名規則は、iOS と macOS での方法だと記載されています。実際には Image が判断して処理をしているので、Linux や Android、Windows でも同様に扱われます。また、ファイルシステム上のファイルではなく、Qt のリソースに入れた画像ファイルでも同様に動作します。

いくつか制約があります。
  • ビジュアルアイテムが表示状態の時に Item.grabToImage() で画像保存できます。
  • ApplicationWindow の contentItem に設定されているのは Item を継承したビジュアルタイプですが Window が直接そのインスタンスを生成し、QQmlEngine と QQmlContext が紐づけられていないため Item.grabToImage() を使用できません。ApplicationWindow や Window のウィンドウ領域全体に対して Item.grabToImage() を使うにはルートアイテムを contentItem と同じ大きさにします。 
  • QQuickImageProvider で生成した画像は Image で HiDPI 表示できません。Image が QQuickImageProvider の場合には HiDPI 表示できるようにまだ実装されていないためです。
  • ネットワークアクセスした画像は Image で HiDPI 表示できません。

2018年7月13日金曜日

Qt で扱える画像フォーマット

QImage と QPixmap で扱える画像フォーマットはQt Image Formats で説明されています。しかし、内容が現状と合わなくなっています。

Qt 5.6 以降で QImage と QPixmap が扱える画像フォーマットは以降のようになっています。

標準のロードフォーマット

BMP CUR DDS*4 GIF HEIC*2 HEIF*2 ICNS ICO JP2*1 JPEG JPG MNG*5 PBM PGM PNG PPM SVG*3 SVGZ*3 TGA TIF TIFF WBMP WEBP XBM XPM

標準のセーブフォーマット

BMP CUR DDS*4 HEIC*2 HEIF*2 ICNS ICO JP2*1 JPEG JPG MNG*5 PBM PGM PNG PPM TIF TIFF WBMP WEBP XBM XPM
*1 Qt 5.7 以上、Windows と Linux はバイナリープラグインなし
*2 Qt 5.11 以上、macOS と iOS のみ。ただし Qt 5.7〜5.10 では libqmacjp2 プラグイン経由で読めてしまうが書き込みはできない。Android と Windows 10 も サポートしているが Qt は未対応。
*3 iOS と Android はなし。
*4 DDS は Qt 5.7 のみ。セキュリティー上の理由から Qt 5.8 でバイナリープラグインなし。
*5 MNG はセキュリティー上の理由から Qt 5.6 でバイナリープラグインなし。

SVG は QSvgGenerator で生成しファイルに保存できます。

JPEG と JPG、TIFF と TIF は画像ファイルサフィックスの違いです。

Qt の画像フォーマットの扱いは GUI アプリケーションとして使うには充分です。しかし、画像フォーマット変換などの画像処理には適さない場合があることに注意が必要です。例えば TIFF 16bit の精度は 8bit になり、BMP の一部の形式は扱えず、セーブ時に画像フォーマットのすべてのオプション指定はできません。

Qt 5.12 で QImage::Format_RGBA64 と QImage::Format_RGBA64_Premultiplied が追加され 16-16-16-16 で使えます。

2018年7月11日水曜日

外部 JavaScript のモジュール化

外部 JavaScript をモジュール化するとファイルパスではなくモジュールとしてインポートできます。
${HOME}/src/qt/examples/qml/javascript/module/
 +-- lib/
 |   +-- jsmodule/
 |   |   +-- qmldir
 |   |   +-- utils.js
 +-- use-jsmodule.qml
モジュール化する外部 JavaScript ファイルを上記のように配置して、qmldir ファイルに以下のように記述します。
qmldir:

Utils 1.0 utils.js
Utils は外部 JavaScript ファイル内の関数や変数、定数を参照するときに指定するプリフィック名で、1.0 はモジュールのバージョン番号 (メジャー.マイナー) です。
utils.js:

.pragma library

function colorName(color) {
    function formatElement(e) {
        return ("00" + Math.round(255*e).toString(16)).slice(-2);
    }

    const r = formatElement(color.r);
    const g = formatElement(color.g);
    const b = formatElement(color.b);
    const a = formatElement(color.a);

    return `#${a}${r}${g}${b}`
}

// Typical mono space font on each platform.
// const monoFont = function() { // Qt 5.11
var monoFont = function() {      // Qt 5.12 regression
    if (Qt.platform.os == "linux") {
        return Qt.font({ family: "DejaVuSansMono" });
    } else if (Qt.platform.os == "windows") {
        return Qt.font({ family: "Lucida Console" });
    } else if (Qt.platform.os == "osx") {
        return Qt.font({ family: "Osaka", styleName: "Regular-Mono" });
    } else {
        return Qt.font({ family: "Lucida Console" });
    }
}();

// Returns a gray for the specified color. Same as qGray() in Qt C++.
function grayFor(color) {
    return ((255*color.r*11 + 255*color.g*16 + 255*color.b*5)/32)/255;
}

// Retruns a contrast gray color for background to clearly display text.
function textColorFor(aBackgroundColor) {
    return grayFor(aBackgroundColor) > 0.5 ? "black" : "white";
}
上記のような外部 JavaScript ファイルを用意したときに、以下のようにしてモジュール化した JavaScript を使えます。
use-jsmodule.qml:

import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.12
import lib.jsmodule 1.0

ApplicationWindow {
    visible: true

    minimumWidth: 300
    minimumHeight: 300

    title: "JavaScript Module"

    Rectangle {
        width: 200
        height: 200
        anchors.centerIn: parent

        radius: 5
        color: Qt.rgba(Math.random(), Math.random(), Math.random())

        Text {
            anchors.centerIn: parent

            text: Utils.colorName(parent.color)
            font {
                family: Utils.monoFont
                pointSize: 32
            }
            color: Utils.textColorFor(parent.color)
        }
    }
}
JavaScript モジュールの import 文には、インポートパス以下の外部 JavaScript ファイルのパスをピリオドで区切って指定します。1.0 は qmldir に記述したバージョン番号です。実行するにはインポートパスを指定して以下のようにします。
$ qml -I ${HOME}/src/qt/examples/qml/javascript/module use-jsmodule.qml

参考情報

Module Definition qmldir Files

2018年5月19日土曜日

underscore.js を QML で使う方法


Underscore.js から Development Version をダウンロードし GitHub - diro/qml_underscorejs: Underscore.js for QML を参考にして以下の修正をします。QML では JavaScript のグローバル変数を追加できなくしているので Qt グローバルオブジェクトに JavaScript プロパティー _ を追加して underscore.js の機能を使うようにしています。
underscore.js 修正差分:

--- original/underscore.js       2018-06-10 11:36:50.000000000 +0900
+++ scripts/underscore.js     2018-06-10 14:06:35.000000000 +0900
@@ -1,9 +1,10 @@
+.pragma library
 //     Underscore.js 1.9.1
 //     http://underscorejs.org
 //     (c) 2009-2018 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
 //     Underscore may be freely distributed under the MIT license.

-(function() {
+var init = function() {

   // Baseline setup
   // --------------
@@ -56,7 +57,12 @@
     }
     exports._ = _;
   } else {
+    if (typeof Qt !== 'undefined') {
+      Qt._ = _;
+    }
+    else {
     root._ = _;
+    }
   }

   // Current version.
@@ -1689,4 +1695,8 @@
       return _;
     });
   }
-}());
+}
+
+if (typeof Qt !== 'undefined') {
+  init();
+}
メイン QML で修正した underscore.js をインポートし、ルートオブジェクトで var 型のプロパティー _ を定義して Qt._ で初期化すればアプリケーション全体で通常の underscore.js と同じ使い方ができます。
now.qml:
import QtQml 2.8
import "underscore.js" as Underscore

QtObject {
    readonly property var _: Qt._

    Component.onCompleted: {
        const times = _.chain(_.times(10000, _.now));
        print(times.first(), times.last());

        Qt.quit();
    }
}
実行結果です。
$ qml now.qml
qml: 1526710520747 1526710520748
$

2018年4月12日木曜日

Ubuntu 17.10 でのデバッグ出力

Ubuntu 17.10 では以下の設定が追加されて qDebug() や consol.debug()、consol.log() などでメッセージが表示されなくなってしまっています。
/etc/xdg/QtProject/qtlogging.ini:

[Rules]
*.debug=false
このフィルター記述は Qt 内部で出力されるデバッグメッセージの表示抑制をしたもののようです。しかし、ユーザーレベルのデバッグメッセージも表示抑制対象になっ てしまいます。ホームディレクトリーの以下の場所に qtlogging.ini を作成して、Qt の内部で出力されるメッセージだけを抑制するようにフィルターを記述すればデバッグメッセージが表示されるようになります。
$HOME/.config/QtProject/qtlogging.ini

[Rules]
qt.*.debug=false

注意


Ubuntu 18.04 で /etc/xdg/QtProject/qtlogging.ini が削除されています。

参考情報

QLoggingCategory Class

2018年4月8日日曜日

macOS ファイルオープンイベント

Finder でアプリケーションアイコンにファイルをドロップするとアプリケーションが起動されてファイルを開くプログラムです。PNG と XPM をドロップでき、アプリケーションウィンドウにも画像ファイルをドロップできるようにしています。

application.h:

#pragma once

#include <QApplication>

class Application : public QApplication
{
    Q_OBJECT

public:
    Application(int& argc, char** argv);

signals:
    void requestedFile(const QString& fileName);

protected:
    bool event(QEvent* event) override;
};
QApplication をサブクラス化し event() を再実装して QEvent::FileOpen イベントを受け取れるようにします。
application.cpp:

#include "application.h"
#include <QFileOpenEvent>

Application::Application(int& argc, char** argv)
    : QApplication(argc, argv)
{
}

bool Application::event(QEvent* event)
{
    if (event->type() == QEvent::FileOpen) {
        emit requestedFile(static_cast<QFileOpenEvent*>(event)->file());
        return true;
    }

    return QApplication::event(event);
}
QEvent::FileOpen イベントを受け取ったならば、QFileOpenEvent::file() でファイル名が分かります。 そのファイル名を実引数にして requestedFile() シグナルを送信します。
droparea.h:

#pragma once

#include <QWidget>

class QDragEnterEvent;
class QDropEvent;

class DropArea : public QWidget
{
    Q_OBJECT

public:
    explicit DropArea(QWidget* parent = 0);

public slots:
    void appendImage(const QString& fileName);

protected:
    void dragEnterEvent(QDragEnterEvent* event) override;
    void dropEvent(QDropEvent* event) override;

public:
    QSize sizeHint() const override;

private:
    QSize totalImageSize;
    int numberOfImages;
};
アプリケーションアイコンにドロップした画像ファイルを表示するウィジェットです。このウィジェットに画像ファイルをドロップできるようにもしています。
droparea.cpp:

#include "droparea.h"
#include "application.h"
#include "pixmapview.h"
#include <QApplication>
#include <QDragEnterEvent>
#include <QMimeData>
#include <QLabel>
#include <QLayout>

DropArea::DropArea(QWidget* parent)
    : QWidget(parent)
{
    numberOfImages = 0;

    setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);

    setAcceptDrops(true);

    const auto topLayout = new QVBoxLayout(this);
    topLayout->addStretch();
    topLayout->setSpacing(8);

    QObject::connect(qobject_cast<Application*>(qApp), &Application::requestedFile, this, &DropArea::appendImage);

    setAutoFillBackground(true);
    QPalette background = palette();
    background.setColor(QPalette::Window, Qt::white);
    setPalette(background);
}

void DropArea::appendImage(const QString& fileName)
{
    const QPixmap pixmap(fileName);
    if (!pixmap.isNull()) {
        auto pixmapView = new PixmapView(pixmap, fileName);

        numberOfImages += 1;
        totalImageSize.rwidth() = qMax(totalImageSize.width(), pixmapView->width());
        totalImageSize.rheight() += pixmapView->height();

        auto topLayout = qobject_cast<QVBoxLayout*>(layout());
        Q_ASSERT(topLayout);

        int left, top, right, bottom;
        topLayout->getContentsMargins(&left, &top, &right, &bottom);
        const int newWidth = totalImageSize.width() + left + right;
        const int newHeight = qMax(totalImageSize.height() + top + bottom + topLayout->spacing()*(numberOfImages - 1), height());
        setFixedSize(newWidth, newHeight);

        topLayout->insertWidget(topLayout->count() - 1, pixmapView);
    }
}

void DropArea::dragEnterEvent(QDragEnterEvent* event)
{
    if (event->mimeData()->hasUrls()) {
        event->accept();
    }
}

void DropArea::dropEvent(QDropEvent* event)
{
    for (const auto& url : event->mimeData()->urls()) {
        appendImage(url.path());
    }
    event->acceptProposedAction();
}

QSize DropArea::sizeHint() const
{
    return QSize(300, 300);
}
Application::requestedFile() シグナルを appendImage() スロットに接続して、アプリケーションアイコンにファイルがドロップされたときに画像追加がされるようにしています。

ドロップされたファイルで QPixmap のインスタンス生成が成功したならば画像ファイルと考えます。失敗したならば何もしません。画像ファイルがドロップされたときにレイアウトの最後のスペーサーの前に画像を表示する PixmapView インスタンスを追加しています。レイアウトの最後のスペーサーは addStretch() で追加しているのでできる限り広がるため画像が上部に詰められてレイアウトされます。
pixmapview.h:

#pragma once

#include <QWidget>

class PixmapView : public QWidget
{
    Q_OBJECT

public:
    PixmapView(const QPixmap& pixmap, const QString& path, QWidget* parent = 0);
};
画像ファイル一つを表示するウィジェットです。
pixmapview.cpp:

#include "pixmapview.h"
#include <QLabel>
#include <QLayout>
#include <QDebug>

PixmapView::PixmapView(const QPixmap& pixmap, const QString& path, QWidget* parent)
    : QWidget(parent)
{
    const auto topLayout = new QVBoxLayout(this);

    const auto pixmapLabel = new QLabel;
    pixmapLabel->setPixmap(pixmap);
    topLayout->addWidget(pixmapLabel);

    topLayout->addWidget(new QLabel(path));
    setFixedSize(sizeHint());

    setAutoFillBackground(true);
    QPalette background = palette();
    background.setColor(QPalette::Window, "whitesmoke");
    setPalette(background);
}
画像の下にドロップされた画像のファイル名も表示しています。
harness.h:

#pragma once

#include <QWidget>

class Harness : public QWidget
{
    Q_OBJECT

public:
    explicit Harness(QWidget* parent = 0);
};
画像を表示する DropArea をスクロールさせて表示するウィジェットです。
harness.cpp:

#include "harness.h"
#include "droparea.h"
#include <QScrollArea>
#include <QLayout>

Harness::Harness(QWidget* parent)
    : QWidget(parent)
{
    const auto topLayout = new QVBoxLayout(this);

    const auto dropArea = new DropArea(this);

    const auto scrollArea = new QScrollArea(this);
    scrollArea->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
    scrollArea->resize(300, 300);
    scrollArea->setWidget(dropArea);

    topLayout->addWidget(scrollArea);
}
スクロール領域がウィンドウ全体を占めるようにしています。
main.cpp:

#include "application.h"
#include "harness.h"

int main(int argc, char** argv)
{
    Application app(argc, argv);

    Harness harness;
    harness.show();

    return app.exec();
}
main() では Harness のインスタンスを作って表示しているだけです。
Info.plist

<!DOCTYPE plist PUBLIC
    "-//Apple Computer//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeExtensions</key>
            <array>
                <string>xpm</string>
            </array>
            <key>CFBundleTypeMIMETypes</key>
            <array>
                <string>image/x-xpm</string>
            </array>
            <key>CFBundleTypeName</key>
            <string>X PixMap File</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSIsAppleDefaultForType</key>
            <true/>
        </dict>
        <dict>
            <key>CFBundleTypeExtensions</key>
            <array>
                <string>png</string>
            </array>
            <key>CFBundleTypeMIMETypes</key>
            <array>
                <string>image/png</string>
            </array>
            <key>CFBundleTypeName</key>
            <string>Portable Network Graphics File</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSIsAppleDefaultForType</key>
            <true/>
        </dict>
    </array>
    <key>CFBundleIconFile</key>
    <string>appfileopen.icns</string>
    <key>CFBundleExecutable</key>
    <string>appleevents</string>
    <key>CFBundleIdentifier</key>
    <string>jp.co.reversedomainname.fileopenevent</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleExecutable</key>
    <string>AppFileOpen</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>1.0</string>
    <key>CSResourcesFileMapped</key>
    <true/>
    <key>NSPrincipalClass</key>
    <string>NSApplication</string>
    <key>NSHighResolutionCapable</key>
    <string>True</string>
</dict>
</plist>
PNG と XPM をドロップするとアプリケーションが起動されるように設定しています。アプリケーションアイコン appfileopen.icns も設定しています。
appfileopen.pro:

!macos {
    error(Only for macOS)
}

QT += widgets

TARGET = AppFileOpen

TEMPLATE = app
SOURCES += main.cpp \
    pixmapview.cpp \
    application.cpp \
    droparea.cpp \
    harness.cpp

HEADERS += \
    pixmapview.h \
    application.h \
    droparea.h \
    harness.h

QMAKE_INFO_PLIST=Info.plist
ICON=icon/appfileopen.icns
QMAKE_INFO_PLIST に Info.plist を指定し、ICON に appfileopen.icns を指定して、ビルドするとアプリケーションバンドルの適切な場所にコピーされるようにしています。



左上のアプリケーションアイコンに PNG か XPM をドロップするとアプリケーションが起動し画像ファイルが表示されます。起動後にドロップすると画像ファイルが追加されます。


アプリケーションウィンドウに画像ファイルをドロップすると画像ファイルが追加して表示されます。Qt が扱える画像はすべて受け付けるようにしています。画像ファイル以外もドロップできますが無視して何もしません。

参考情報

QFileOpenEvent Class

for ベンチマーク

QStringList の for と foreach、range for でのベンチマークです。
forbench.cpp:

#include 
#include 

class ForBenchmark : public QObject
{
    Q_OBJECT

public:
    ForBenchmark() : sizeOfList(1000) {}

    enum class IterationType {
        IndexAt,
        IndexValue,
        IndexMutable,
        Java,
        JavaMutable,
        Stl,
        StlConst,
        Qtforeach,
        QtForeachConst,
        QtForeachConstRef,
#if __cplusplus >= 201103L || (_MSC_VER >= 1700)
        RangeFor,
        RangeForConst,
        RangeForConstRef,
        RangeForAuto,
        RangeForConstAuto,
        RangeForConstAutoRef,
        RangeForAutoRvalue,
        AutoIterator,
        AutoConstIterator,
#endif
    };

private slots:
    void forBenchmark();
    void forBenchmark_data();

private:
    QStringList list;
    const int sizeOfList;
};

Q_DECLARE_METATYPE(ForBenchmark::IterationType)

void ForBenchmark::forBenchmark_data()
{
    QTest::addColumn("type");

    QTest::newRow("Index At") << IterationType::IndexAt;
    QTest::newRow("Index Value") << IterationType::IndexValue;
    QTest::newRow("Index Mutable") << IterationType::IndexMutable;
    QTest::newRow("Java mutable iterator") << IterationType::JavaMutable;
    QTest::newRow("Java iterator") << IterationType::Java;
    QTest::newRow("STL iterator") << IterationType::Stl;
    QTest::newRow("STL const iterator") << IterationType::StlConst;
    QTest::newRow("Qt foreach") << IterationType::Qtforeach;
    QTest::newRow("Qt foreach, const") << IterationType::QtForeachConst;
    QTest::newRow("Qt foreach, const&") << IterationType::QtForeachConstRef;
#if __cplusplus >= 201103L || (_MSC_VER >= 1700)
    QTest::newRow("Range for") << IterationType::RangeFor;
    QTest::newRow("Range for, const") << IterationType::RangeForConst;
    QTest::newRow("Range for, const&") << IterationType::RangeForConstRef;
    QTest::newRow("Range for, auto") << IterationType::RangeForAuto;
    QTest::newRow("Range for, const auto") << IterationType::RangeForConstAuto;
    QTest::newRow("Range for, const auto&") << IterationType::RangeForConstAutoRef;
    QTest::newRow("Range for, auto&&") <<  IterationType::RangeForAutoRvalue;
    QTest::newRow("auto iterator") << IterationType::AutoIterator;
    QTest::newRow("auto const iterator") << IterationType::AutoConstIterator;
#endif

    for (int n = 0; n < sizeOfList; n++) {
        list << QString::number(n);
    }
}

void ForBenchmark::forBenchmark()
{
    QFETCH(IterationType, type);

    int sum = 0;

    switch (type) {
    case IterationType::IndexAt:
        QBENCHMARK {
            for (int i = 0; i < sizeOfList; ++i) {
                sum += list.at(i).size();
            }
        }
        break;
    case IterationType::IndexValue:
        QBENCHMARK {
            for (int i = 0; i < sizeOfList; ++i) {
                sum += list.value(i).size();
            }
        }
        break;
    case IterationType::IndexMutable:
        QBENCHMARK {
            for (int i = 0; i < sizeOfList; ++i) {
                sum += list[i].size();
            }
        }
        break;
    case IterationType::Java: {
        QBENCHMARK {
            QStringListIterator it(list);
            while (it.hasNext())
                sum += it.next().size();
        }
        break;
    }
    case IterationType::JavaMutable: {
        QBENCHMARK {
            QMutableStringListIterator it(list);
            while (it.hasNext())
                sum += it.next().size();
        }
        break;
    }
    case IterationType::Stl: {
        QBENCHMARK {
            const QStringList::iterator end = list.end();
            for (QStringList::iterator it = list.begin(); it != end; ++it) {
                sum += (*it).size();
            }
        }
        break;
    }
    case IterationType::StlConst: {
        QBENCHMARK {
            const QStringList::const_iterator end = list.constEnd();
            for (QStringList::const_iterator it = list.constBegin(); it != end; ++it) {
                sum += (*it).size();
            }
        }
        break;
    }
    case IterationType::Qtforeach:
        QBENCHMARK {
            foreach (QString string, list) {
                sum += string.size();
            }
        }
        break;
    case IterationType::QtForeachConst:
        QBENCHMARK {
            foreach (const QString string, list) {
                sum += string.size();
            }
        }
        break;
    case IterationType::QtForeachConstRef:
        QBENCHMARK {
            foreach (const QString& string, list) {
                sum += string.size();
            }
        }
        break;
#if __cplusplus >= 201103L || (_MSC_VER >= 1700)
    case IterationType::RangeFor:
        QBENCHMARK {
            for (QString string : list) {
                sum += string.size();
            }
        }
        break;
    case IterationType::RangeForConst:
        QBENCHMARK {
            for (const QString string : list) {
                sum += string.size();
            }
        }
        break;
    case IterationType::RangeForConstRef:
        QBENCHMARK {
            for (const QString& string : list) {
                sum += string.size();
            }
        }
        break;
    case IterationType::RangeForAuto:
        QBENCHMARK {
            for (auto string : list) {
                sum += string.size();
            }
        }
        break;
    case IterationType::RangeForConstAuto:
        QBENCHMARK {
            for (const auto string : list) {
                sum += string.size();
            }
        }
        break;
    case IterationType::RangeForConstAutoRef:
        QBENCHMARK {
            for (const auto& string : list) {
                sum += string.size();
            }
        }
        break;
    case IterationType::RangeForAutoRvalue:
        QBENCHMARK {
            for (auto&& it : list) {
                sum += it.size();
            }
        }

        break;
    case IterationType::AutoIterator:
        QBENCHMARK {
            auto end = list.end();
            for (auto it = list.begin(); it != end; ++it) {
                sum += (*it).size();
            }
        }
        break;
    case IterationType::AutoConstIterator: {
        QBENCHMARK {
            auto end = list.constEnd();
            for (auto it = list.constBegin(); it != end; ++it) {
                sum += (*it).size();
            }
        }
    }
        break;
#endif
    }
}

QTEST_MAIN(ForBenchmark)

#include "forbench.moc"
forbench.pro:

QT+=testlib
QT -= gui
CONFIG += release
CONFIG -= app_bundle

TEMPLATE = app
TARGET = forbench

DEPENDPATH += .
INCLUDEPATH += .

SOURCES += forbench.cpp
macOS での実行結果です。
$ ./forbench
********* Start testing of ForBenchmark *********
Config: Using QtTest library 5.10.1, Qt 5.10.1 (x86_64-little_endian-lp64 shared (dynamic) release build; by Clang 8.1.0 (clang-802.0.42) (Apple))
PASS   : ForBenchmark::initTestCase()
PASS   : ForBenchmark::forBenchmark(Index At)
RESULT : ForBenchmark::forBenchmark():"Index At":
     0.000041 msecs per iteration (total: 87, iterations: 2097152)
PASS   : ForBenchmark::forBenchmark(Index Value)
RESULT : ForBenchmark::forBenchmark():"Index Value":
     0.013 msecs per iteration (total: 54, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Index Mutable)
RESULT : ForBenchmark::forBenchmark():"Index Mutable":
     0.00049 msecs per iteration (total: 65, iterations: 131072)
PASS   : ForBenchmark::forBenchmark(Java mutable iterator)
RESULT : ForBenchmark::forBenchmark():"Java mutable iterator":
     0.0131 msecs per iteration (total: 431, iterations: 32768)
PASS   : ForBenchmark::forBenchmark(Java iterator)
RESULT : ForBenchmark::forBenchmark():"Java iterator":
     0.0131 msecs per iteration (total: 215, iterations: 16384)
PASS   : ForBenchmark::forBenchmark(STL iterator)
RESULT : ForBenchmark::forBenchmark():"STL iterator":
     0.0132 msecs per iteration (total: 217, iterations: 16384)
PASS   : ForBenchmark::forBenchmark(STL const iterator)
RESULT : ForBenchmark::forBenchmark():"STL const iterator":
     0.0130 msecs per iteration (total: 107, iterations: 8192)
PASS   : ForBenchmark::forBenchmark(Qt foreach)
RESULT : ForBenchmark::forBenchmark():"Qt foreach":
     0.012 msecs per iteration (total: 53, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Qt foreach, const)
RESULT : ForBenchmark::forBenchmark():"Qt foreach, const":
     0.012 msecs per iteration (total: 53, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Qt foreach, const&)
RESULT : ForBenchmark::forBenchmark():"Qt foreach, const&":
     0.000046 msecs per iteration (total: 97, iterations: 2097152)
PASS   : ForBenchmark::forBenchmark(Range for)
RESULT : ForBenchmark::forBenchmark():"Range for":
     0.012 msecs per iteration (total: 51, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Range for, const)
RESULT : ForBenchmark::forBenchmark():"Range for, const":
     0.012 msecs per iteration (total: 51, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Range for, const&)
RESULT : ForBenchmark::forBenchmark():"Range for, const&":
     0.000042 msecs per iteration (total: 90, iterations: 2097152)
PASS   : ForBenchmark::forBenchmark(Range for, auto)
RESULT : ForBenchmark::forBenchmark():"Range for, auto":
     0.012 msecs per iteration (total: 52, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Range for, const auto)
RESULT : ForBenchmark::forBenchmark():"Range for, const auto":
     0.012 msecs per iteration (total: 52, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Range for, const auto&)
RESULT : ForBenchmark::forBenchmark():"Range for, const auto&":
     0.000042 msecs per iteration (total: 89, iterations: 2097152)
PASS   : ForBenchmark::forBenchmark(Range for, auto&&)
RESULT : ForBenchmark::forBenchmark():"Range for, auto&&":
     0.000042 msecs per iteration (total: 90, iterations: 2097152)
PASS   : ForBenchmark::forBenchmark(auto iterator)
RESULT : ForBenchmark::forBenchmark():"auto iterator":
     0.000041 msecs per iteration (total: 87, iterations: 2097152)
PASS   : ForBenchmark::forBenchmark(auto const iterator)
RESULT : ForBenchmark::forBenchmark():"auto const iterator":
     0.000041 msecs per iteration (total: 88, iterations: 2097152)
PASS   : ForBenchmark::cleanupTestCase()
Totals: 21 passed, 0 failed, 0 skipped, 0 blacklisted, 6451ms
********* Finished testing of ForBenchmark *********
$
Ubuntu での実行結果です。
$ ./forbench 
********* Start testing of ForBenchmark *********
Config: Using QtTest library 5.11.0, Qt 5.11.0 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 5.3.1 20160406 (Red Hat 5.3.1-6))
PASS   : ForBenchmark::initTestCase()
PASS   : ForBenchmark::forBenchmark(Index At)
RESULT : ForBenchmark::forBenchmark():"Index At":
     0.0000026 msecs per iteration (total: 88, iterations: 33554432)
PASS   : ForBenchmark::forBenchmark(Index Value)
RESULT : ForBenchmark::forBenchmark():"Index Value":
     0.012 msecs per iteration (total: 53, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Index Mutable)
RESULT : ForBenchmark::forBenchmark():"Index Mutable":
     0.00073 msecs per iteration (total: 96, iterations: 131072)
PASS   : ForBenchmark::forBenchmark(Java mutable iterator)
RESULT : ForBenchmark::forBenchmark():"Java mutable iterator":
     0.0000033 msecs per iteration (total: 56, iterations: 16777216)
PASS   : ForBenchmark::forBenchmark(Java iterator)
RESULT : ForBenchmark::forBenchmark():"Java iterator":
     0.000016 msecs per iteration (total: 68, iterations: 4194304)
PASS   : ForBenchmark::forBenchmark(STL iterator)
RESULT : ForBenchmark::forBenchmark():"STL iterator":
     0.0000033 msecs per iteration (total: 56, iterations: 16777216)
PASS   : ForBenchmark::forBenchmark(STL const iterator)
RESULT : ForBenchmark::forBenchmark():"STL const iterator":
     0.0000028 msecs per iteration (total: 97, iterations: 33554432)
PASS   : ForBenchmark::forBenchmark(Qt foreach)
RESULT : ForBenchmark::forBenchmark():"Qt foreach":
     0.012 msecs per iteration (total: 52, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Qt foreach, const)
RESULT : ForBenchmark::forBenchmark():"Qt foreach, const":
     0.012 msecs per iteration (total: 53, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Qt foreach, const&)
RESULT : ForBenchmark::forBenchmark():"Qt foreach, const&":
     0.000015 msecs per iteration (total: 67, iterations: 4194304)
PASS   : ForBenchmark::forBenchmark(Range for)
RESULT : ForBenchmark::forBenchmark():"Range for":
     0.012 msecs per iteration (total: 52, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Range for, const)
RESULT : ForBenchmark::forBenchmark():"Range for, const":
     0.012 msecs per iteration (total: 52, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Range for, const&)
RESULT : ForBenchmark::forBenchmark():"Range for, const&":
     0.0000033 msecs per iteration (total: 57, iterations: 16777216)
PASS   : ForBenchmark::forBenchmark(Range for, auto)
RESULT : ForBenchmark::forBenchmark():"Range for, auto":
     0.013 msecs per iteration (total: 54, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Range for, const auto)
RESULT : ForBenchmark::forBenchmark():"Range for, const auto":
     0.012 msecs per iteration (total: 52, iterations: 4096)
PASS   : ForBenchmark::forBenchmark(Range for, const auto&)
RESULT : ForBenchmark::forBenchmark():"Range for, const auto&":
     0.0000035 msecs per iteration (total: 60, iterations: 16777216)
PASS   : ForBenchmark::forBenchmark(Range for, auto&&)
RESULT : ForBenchmark::forBenchmark():"Range for, auto&&":
     0.0000038 msecs per iteration (total: 64, iterations: 16777216)
PASS   : ForBenchmark::forBenchmark(auto iterator)
RESULT : ForBenchmark::forBenchmark():"auto iterator":
     0.0000041 msecs per iteration (total: 69, iterations: 16777216)
PASS   : ForBenchmark::forBenchmark(auto const iterator)
RESULT : ForBenchmark::forBenchmark():"auto const iterator":
     0.0000026 msecs per iteration (total: 89, iterations: 33554432)
PASS   : ForBenchmark::cleanupTestCase()
Totals: 21 passed, 0 failed, 0 skipped, 0 blacklisted, 5033ms
********* Finished testing of ForBenchmark *********
$


参考情報

Writing a Benchmark

名前付き色名

R、G、B を 16 進数一文字とするとき QColor と QML の color で扱える名前付き色名は以下のようになります。
  1. #RGB
  2. #RRGGBB
  3. #AARRGGBB
  4. #RRRGGGBBB
  5. #RRRRGGGGBBBB
  6. SVG 色名と transparent
4 と 5 の場合には各色要素の前 2 文字のみが参照され残りの文字は無視されるだけで色精度が上がる訳ではありません。以下のように記述してもエラーにはならず正しく色が使えます。
QColor("#7F_7F_7F_")
QColor("#80_80_80_")
QColor("#7F__7F__7F__")
QColor("#80__80__80__")

color: "#ff__00__00__" 
つまり、上の記述は下の記述とまったく同一です。
QColor("#7F7F7F")
QColor("#808080")
QColor("#7F7F7F")
QColor("#808080")

color: "#ff0000" 

参考情報

QColor Class
color QML Basic Type

SVG 色名と X11 色名

SVG 色名と X11 色名の一覧を表示して比較するプログラムです。
x11rgbtext.h:

#pragma once

#include <QRegularExpression>
#include <QStringList>

class X11RGBText
{
private:
    X11RGBText();
    virtual ~X11RGBText() = default;

    void init();

public:
    X11RGBText(const X11RGBText&) = delete;
    X11RGBText& operator=(const X11RGBText&) = delete;
    X11RGBText(X11RGBText&&) = delete;
    X11RGBText& operator=(X11RGBText&&) = delete;

    static X11RGBText& instance();

    QStringList names() const;
    QColor color(const QString& name) const;

private:
    QStringList colorNames;
    QHash<QString, QColor> x11rgbTextHash;
};
names() は X11 色名のリストを返し、color() は指定した色名の QColor を返します。
x11rgbtext.cpp:

#include "x11rgbtext.h"
#include <QTextStream>
#include <QScrollArea>
#include <QRegularExpression>
#include <QFile>

X11RGBText::X11RGBText()
{
    init();
}

X11RGBText& X11RGBText::instance() {
    static X11RGBText x11rgbText;
    return x11rgbText;
}

QStringList X11RGBText::names() const {
    return colorNames;
}

QColor X11RGBText::color(const QString& name) const
{
    return *x11rgbTextHash.find(name);
}

void X11RGBText::init()
{
    const QRegularExpression leadingWhiteSpacesRegularExpression = QRegularExpression("^\\s+");
    const QRegularExpression startWithDigitRegularExpression = QRegularExpression("^[0-9]");

    for (const QString& rgbTextFileName : { "/etc/X11/rgb.txt",
                                            "/usr/lib/X11/rgb.txt",
                                            "/usr/share/X11/rgb.txt",
                                            "/opt/X11/share/X11/rgb.txt",
                                            "/usr/share/emacs/22.1/etc/rgb.txt",
                                            "/opt/local/share/netpbm/rgb.txt" }) {
        QFile rgbTextFile(rgbTextFileName);
        if (rgbTextFile.open(QIODevice::ReadOnly)) {
            QTextStream rgbTextInputStream(&rgbTextFile);
            while (!rgbTextInputStream.atEnd()) {
                QString line = rgbTextInputStream.readLine();
                line = line.replace(leadingWhiteSpacesRegularExpression, "");
                if (!line.contains(startWithDigitRegularExpression)) {
                    continue;
                }
                QTextStream lineStream(&line);
                int r, g, b;
                QString colorName;
                lineStream >> r >> g >> b;
                colorName = lineStream.readAll().trimmed();
                colorNames.append(colorName);
                x11rgbTextHash.insert(colorName, QColor(r, g, b));
            }
            break;
        }
    }
}
rgb.txt ファイルは以降のような形式で記述されています。init() では色を定義している行を探して、色名を QStringList 型の colorNames に入れ、 色名をキーにして色を求めるために QHash<QString, QColor> 型の x11rgbTextHash に登録しています。
/etc/X11/rgb.txt:
! $Xorg: rgb.txt,v 1.3 2000/08/17 19:54:00 cpqbld Exp $
255 250 250             snow
248 248 255             ghost white
248 248 255             GhostWhite
245 245 245             white smoke
245 245 245             WhiteSmoke
...
/opt/local/share/netpbm/rgb.txt:
# The colors in this color dictionary are from the following sources, in
# order.  Some color names are defined multiple times, and sometimes
# with different colors.  Many colors have multiple names.
...
  0   0   0 Black
255 255 255 White
255   0   0 Red
  0 255   0 Green
  0   0 255 Blue
  0 255 255 Cyan
...
colorstack.h:

#pragma once

#include <QWidget>

class ColorStack : public QWidget
{
    Q_OBJECT

public:
    explicit ColorStack(const QStringList& colors, QWidget* parent = 0);
    bool eventFilter(QObject* watched, QEvent* event) override;
};
色名の一覧を表示するウィジェットです。
colorstack.cpp:

#include "colorstack.h"
#include "x11rgbtext.h"
#include <QLayout>
#include <QLabel>
#include <QToolTip>
#include <QHelpEvent>

ColorStack::ColorStack(const QStringList& colorNames, QWidget* parent)
    : QWidget(parent)
{
    const auto topLayout = new QVBoxLayout(this);
    topLayout->setSpacing(0);
    topLayout->setMargin(0);

    for (const QString& colorName : qAsConst(colorNames)) {
        QColor color(colorName);

        auto colorLabel = new QLabel(colorName);
        colorLabel->setAutoFillBackground(true);
        QPalette colorLabelPalette = colorLabel->palette();
        if (color.isValid()) {
            colorLabelPalette.setColor(QPalette::Window, color);
        } else {
            color = X11RGBText::instance().color(colorName);
            colorLabelPalette.setColor(QPalette::Window, color);
        }

        // Choose a proper text color for the background color.
        int gray = 0;
        if (color.isValid()) {
            if (colorName.compare("transparent", Qt::CaseInsensitive) == 0) {  // Qt extension to SVG color names
                gray = qGray(colorLabel->palette().color(QPalette::Window).rgb());
            } else {
                gray = qGray(color.rgb());
            }
        }
        if (double(gray) > (255.0 / 2.0)) {
            colorLabelPalette.setColor(QPalette::WindowText, "Black");
        } else {
            colorLabelPalette.setColor(QPalette::WindowText, "White");
        }

        colorLabel->setPalette(colorLabelPalette);
        colorLabel->installEventFilter(this);
        topLayout->addWidget(colorLabel);
    }
}

bool ColorStack::eventFilter(QObject* watched, QEvent* event)
{
    if (event->type() == QEvent::ToolTip) {
        const QHelpEvent* const helpEvent = static_cast<QHelpEvent*>(event);
        QLabel* const label = qobject_cast<QLabel*>(watched);
        if (label) {
            const QColor color = label->palette().color(QPalette::Window);
            QToolTip::showText(helpEvent->globalPos(), color.name());
        }
    }
    return false;
}
イベントフィルターでは表示されている色名のラベルの上にマウスカーソルが来たときに 16 進数で色名を表示しています。
harness.h:

#pragma once

#include "colorstack.h"
#include "x11rgbtext.h"
#include <QWidget>

class Harness : public QWidget
{
    Q_OBJECT

public:
    Harness();
};
SVG 色名の一覧と X11 色名の一覧を横に並べて表示するウィジェットです。
harness.cpp:

#include "harness.h"
#include "x11rgbtext.h"
#include <QLayout>
#include <QLabel>
#include <QScrollArea>

Harness::Harness()
    : QWidget(0)
{
    auto topLayout = new QHBoxLayout(this);

    auto addColorStack = [&](const QString& title, ColorStack* const colorStack) {
        auto colorStackLayout = new QVBoxLayout;

        colorStackLayout->addWidget(new QLabel(title));

        const auto scrollArea = new QScrollArea;
        scrollArea->setWidgetResizable(true);
        scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        scrollArea->setWidget(colorStack);
        colorStackLayout->addWidget(scrollArea);

        topLayout->addLayout(colorStackLayout);
    };

    addColorStack("SVG name", new ColorStack(QColor::colorNames()));
    addColorStack("rgb.txt", new ColorStack(X11RGBText::instance().names()));
}

main.cpp:

#include "harness.h"
#include <QApplication>

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    Harness harness;
    harness.show();

    return app.exec();
}

macOS と Ubuntu で実行した結果です。

macOS での実行結果
Ubuntu での実行結果

参考情報

QColor Class
ウェブカラー

2018年4月7日土曜日

QPixmapColorizeFilter

Graphics Effects の内部クラス QPixmapColorizeFilter を使って画像に色を付けられます。ただし Graphics Effects は設計上の欠陥のために廃止されています。
harness.h:

#pragma once

#include <QWidget>
#include <private/qpixmapfilter_p.h>

class Harness : public QWidget
{
    Q_OBJECT

public:
    explicit Harness(const QString& fileName, const QColor& colorizingColor = QColor(7+50, 142, 17+50));

protected:
    void paintEvent(QPaintEvent* event) override;

private:
    struct {
        QString fileName;
        QPixmapColorizeFilter* colorizingFilter;
        QPixmap pixmap;
        QColor colorizingColor;
    } d;
};

harness.cpp:

#include "harness.h"
#include <QPainter>
#include <QDebug>

Harness::Harness(const QString& fileName, const QColor& colorizingColor)
    : QWidget(0)
{
    d.fileName = fileName;
    d.colorizingColor = colorizingColor;
    d.colorizingFilter = new QPixmapColorizeFilter(this);
    if (!d.pixmap.load(fileName)) {
        qDebug() << "Invalid image file " << fileName << endl;
        return;
    }
    resize(d.pixmap.size());
}

void Harness::paintEvent(QPaintEvent*)
{
    QPainter painter(this);
    d.colorizingFilter->setColor(d.colorizingColor);
    d.colorizingFilter->draw(&painter, QPointF(0.0, 0.0), d.pixmap);
}
QPixmapColorizeFilter::draw() の呼び出し方は以下のようになっています。
void draw(QPainter* painter, const QPointF& dest, const QPixmap& src, const QRectF& srcRect = QRectF()) const;
painter描画先 QPainter。
dest色を付けた画像の描画位置。
src元画像。
srcRect元画像の部分矩形領域。空ならば全領域。指定した画像領域を切り出し色付して描画する。
main.cpp:

#include "harness.h"
#include <QApplication>
#include <QDebug>

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    switch (app.arguments().count()) {
    case 2:
    case 3:
        break;
    default:
        qWarning() << "Usage: colorise image [color]";
        exit(-1);
    }

    const QString fileName = app.arguments().at(1);

    QScopedPointer harness([&]() {
        if (app.arguments().count() == 2) {
            return new Harness(fileName);
        } else {
            const QColor colorizingColor(app.arguments().at(2));
            return new Harness(fileName, colorizingColor);
        }
    }());

    harness.data()->show();

    return app.exec();
}
コマンドラインで画像ファイルと色を指定して動かします。色を指定しない場合にはデフォルトの緑色になります。
colorize.pro:

QT += widgets widgets-private

SOURCES += main.cpp \
    harness.cpp

HEADERS += \
    harness.h
プライベートクラスを使っているので widgets-private の指定が必要です。
赤で色付
デフォルトの緑で色付
青で色付

参考情報

Qt Modules Maturity Level

2018年4月6日金曜日

MouseArea のマスク

Item の containmentMask にマスクオブジェクトを設定すると MouseArea のイベントをマスクできます。マスクオブジェクトとは以下を実装した QObject を継承したオブジェクトです。
Q_INVOKABLE QObject::contains(const QPoint& point)
QObject を継承したマスクオブジェクトは以下のようにして作成します。
main.cpp:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QLineF>

class CircleMask : public QObject
{
    Q_OBJECT

    Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)

public:
    explicit CircleMask(QObject* parent = 0) : QObject(parent) {}

    ~CircleMask() {}

    qreal radius() const {
        return d.radius;
    }

    void setRadius(qreal radius)
    {
        if (d.radius == radius) {
            return;
        }
        d.radius = radius;
        emit radiusChanged();
    }

    Q_INVOKABLE bool contains(const QPointF& point) const
    {
        const QPointF center(d.radius, d.radius);
        const QLineF line(center, point);
        return line.length() <= d.radius;
    }

signals:
    void radiusChanged();

private:
    struct {
        qreal radius;
    } d;
};

int main(int argc, char** argv)
{
    QGuiApplication app(argc, argv);

    qmlRegisterType("Mask", 1, 0, "CircleMask");

    QQmlApplicationEngine engine;
    engine.load(QUrl("qrc:/main.qml"));

    return app.exec();
}

#include "main.moc"
contains() では point が半径 radius の円の内側かどうかを判定しています。
mail.qml:

import QtQuick 2.11
import QtQuick.Window 2.10
import Mask 1.0

Window {
    id: window

    visible: true

    minimumWidth: 300
    minimumHeight: 300

    color: "white"

    Rectangle {
        id: circle

        width: window.width/2; height: width
        anchors.centerIn: parent

        radius: width/2
        color: "red"
        border.width: circleMouseArea.containsMouse ? 4 : 0
        border.color: "firebrick"

        MouseArea {
            id: circleMouseArea

            anchors.fill: parent

            hoverEnabled: true
            containmentMask: CircleMask {
                radius: circle.radius
            }

            onPressed: {
                parent.color = "green";
            }

            onReleased: {
                parent.color = "red";
            }
        }
    }
}
MouseArea の containmentMask に半径を設定した CircleMask オブジェクトを設定すれば円の内側だけでマウスが反応するようになります。

円の内側だけでマウスが反応する

Qt Quick Shapes の Shape をオブジェクトマスクに使えます。containsMode を Shape.FillContains に設定すると内側だけに反応するようになります。
shapedmousearea.qml:

import QtQuick 2.11
import QtQuick.Shapes 1.11
import QtQuick.Window 2.10

Window {
    id: window

    visible: true

    minimumWidth: 300
    minimumHeight: 300

    color: "white"

    Shape {
        id: circle

        readonly property real radius: 60

        anchors.fill: parent

        layer.enabled: true
        layer.smooth: true
        layer.textureSize: Qt.size(2*width, 2*height)
        vendorExtensionsEnabled: false

        containsMode: Shape.FillContains

        ShapePath {
            id: halfArch

            strokeWidth: -1
            fillColor: "green"

            startX: circle.width/2 - circle.radius
            startY: circle.height/2 - circle.radius

            PathArc {
                id: halfPathArc

                x: circle.width/2 + circle.radius
                y: circle.height/2 + circle.radius
                radiusX: circle.radius
                radiusY: circle.radius
            }
        }
    }

    MouseArea {
        id: circleMouseArea

        anchors.fill: circle

        containmentMask: circle

        onPressed: {
            halfArch.fillColor = "red";
        }

        onReleased: {
            halfArch.fillColor = "green";
        }
    }
}
こちらも MouseArea の containmentMask に Shape オブジェクトをマスクとして設定します。こうすると半円の内側だけでマウスに反応します。Qt Quick Shapes を使えば不定形図形の描画とマスクに同じ Shape オブジェクトを使えます。

半円の内側だけでマウスに反応する


参考情報

containmentMask

Trolltech Colors

Qt の有名な 2 色を使ってみます。
Trolltech Green RGB(141, 192, 48) [CMYK 40/0/100/0]
Trolltech Purple RGB(131, 124, 172) [CMYK 39/39/0/0]
QML での実装です。
trolltechcolors.qml:

import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.Layouts 1.3

Window {
    id: window

    visible: true

    readonly property color trolltechGreen: Qt.rgba(141/255, 192/255, 48/255)
    readonly property color trolltechPurple: Qt.rgba(131/255, 124/255, 172/255)

    minimumWidth: topLayout.implicitWidth
    minimumHeight: topLayout.implicitHeight

    ColumnLayout {
        id: topLayout

        anchors.fill: parent
        spacing: 0

        Repeater {
            id: trolltechColors

            model: [ { "name": "Trolltech Green", "color": trolltechGreen },
                     { "name": "Trolltech Purple", "color": trolltechPurple } ]

            Rectangle {
                width: 2*fontMetrics.boundingRect(name.text).width
                height: 3*fontMetrics.height
                Layout.fillWidth: true
                Layout.fillHeight: true

                color: modelData.color

                Text {
                    id: name

                    anchors.centerIn: parent

                    text: modelData.name
                    color: "white"

                    FontMetrics {
                        id: fontMetrics

                        font: name.font
                    }
                }
            }
        }
    }
}
FontMetrics は Text の子オブジェクトですが Item を継承していないので parent プロパティーはありません。従って、Text の font を参照するには id 修飾が必要です。

C++ での実装です。
main.cpp:

#include <QApplication>
#include <QLabel>
#include <QPalette>
#include <QFontMetricsF>
#include <QLayout>

class TrolltechColors : public QWidget
{
    Q_OBJECT

public:
    TrolltechColors();

private:
    QLabel* makeColorLabel(const QString& text, const QColor& color) const;
};

TrolltechColors::TrolltechColors()
    : QWidget(0)
{
    const auto topLayout = new QVBoxLayout(this);
    topLayout->setMargin(0);
    topLayout->setSpacing(0);
    topLayout->addWidget(makeColorLabel("Trolltech Green", QColor(141, 192, 48)));
    topLayout->addWidget(makeColorLabel("Trolltech Purple", QColor(131, 124, 172)));
}

QLabel* TrolltechColors::makeColorLabel(const QString& text, const QColor& color) const
{
    const auto label = new QLabel(text);
    label->setAutoFillBackground(true);

    QPalette labelPalette = label->palette();
    labelPalette.setColor(QPalette::Window, color);
    labelPalette.setColor(QPalette::WindowText, "White");
    label->setPalette(labelPalette);

    const QFontMetricsF fontMetricsF(label->font());

    label->setMinimumWidth(2*fontMetricsF.width(label->text()));
    label->setMinimumHeight(3*fontMetricsF.height());

    label->setAlignment(Qt::AlignCenter);

    return label;
}

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    TrolltechColors trolltechColors;
    trolltechColors.show();

    app.exec();
}

#include "main.moc"
QLabel::fontMetrics() は QFontMetrics を返すので QML が使っている QFontMetricsF よりも幅や高さが大きくなります。そこで QFontMetricsF を使って同じサイズになるようにしています。

実行結果は左から順に QML で renderType にデフォルトの Text.QtRendering と Text.NativeRendering、QLabel で QFontMetricsF と QLabel::fontMetrics() です。QML の Text.NativeRendering は QLable と同じようにテキスト描画されます。QML の Text.QtRendering ではこれらと違いテキストが細くなっています。しかし、FontMetrics で求めるサイズは renderType に依存しません。

QML
renderType: Text.QtRendering
QML
renderType: Text.NativeRendering
QLabel
QFontMetricsF
QLabel
fontMetrics()

索引

索引