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

索引

索引