2016-11-07 12 views
2

私はたくさんのテキストを表示するアプリケーションを書いています。それは言葉や文章ではなく、CP437文字セットで表示されるバイナリデータです。現在の形式:Qt5でたくさんの独立した文字を描く最も良い方法は?

Screenshot of my current application

は、私はそれらの文字を描くといえ問題を抱えています。私は後で私は別の色を適用したいので、1つずつ文字を描画する必要があります。これらの文字は透明な背景も持つ必要があります。後で、背景と異なる色のセクションや範囲を描画したい(いくつかの基準に基づいてこれらの文字をグループ化するため)。

アプリケーションは複数の開いたファイルを同時にサポートしますが、複数のファイルが開いている場合、高速i7で図面が目立つようになります。

Qt5でこの種のデータを表示するにはどうすればよいでしょうか?文字をビットマップにプリレンダリングしてそこから開始するか、通常のQt関数を使ってテキストを描くことで、実際にたくさんの文字を描くことは可能でしょうか?

編集:私はQPainterを使用して、paintEventに描くん通常QFrameウィジェットを使用しています。これは間違ったアプローチですか?私はQGraphicsSceneのいくつかのドキュメントを読んできました。これは、ウィジェットが描画するオブジェクトをウィジェットが何らかの制御を必要とする状況で使用するのが最もよいことを思い出しました。私は、私が描くものを何もコントロールする必要はありません。私はそれを描く必要があり、それだけです。私は特定の文字を参照しませんの後に私はそれを描くでしょう。

ウィジェットが2000行を持っているので、私は全体のコードを貼り付けませんが、現在、私の描画アプローチは、このようなものです:

  • まず、256個のエントリを持つテーブル(cache)を作成し、イテレータを置きます各エントリに対してi変数、
  • にカウンタ、入力ストリーム内の各バイトのために、描画機能では、
  • その後、i変数から取られたASCIIコードによって識別文字についての情報を描画含まQStaticTextオブジェクトを作成します(すなわち、ファイルから)、描画tデータはcacheテーブルのQStaticTextを使用しています。したがって、ASCII文字0x7Aを描画するには、インデックス0x7aからcacheテーブルのQStaticTextを検索し、このQStaticTextオブジェクトをQPainterオブジェクトにフィードします。

私はまた、別のアプローチを試して1回のQPainter::drawText呼び出しで行全体をレンダリングし、確かにそれは速かったが、私は別の色で各文字を着色する可能性を失ってしまったました。私はこの可能性を持ちたいと思います。

+0

コードは現在何がわかりましたか?あなたはQTextEditを使用していますか?テキストを挿入するのにQTextCursorを使用していますか?あなたはQGraphicsSceneを使っていますか? – GabeWeiss

+0

ベースラインを設定しましたか? QTextEditまたはQGraphicsScene、またはQMLでテキストを描画しますか? – rubenvb

+0

関連情報とともに投稿を更新しました。私は本当に 'QTextEdit'を使うことはできません。なぜなら、それはそれ自身のバッファを管理しているようです。膨大なデータ(10テラバイトなど)を遅延なく管理(コピー/貼り付け)できるようにするためには自分のアプリケーションが必要なので、私自身のバッファを用意する必要があります。 – antonone

答えて

5

QGraphicsSceneを使用しても改善されません。これはQWidgetの上に追加されたレイヤーです。生の演奏が終わったので、それを使ってはいけません。

はあなたのメモリ・バッファ/ファイルの可視部分のためのviewmodelとしてQTextDocumentを実装することができますが、あなたはスクロール新鮮QTextDocumentたびに塗装しても、速くQWidgetに直接物事を描画するよりもではないでしょう。

QStaticTextを使用することは、正しい方向のステップですが、不十分です。レンダリングQStaticTextでは、依然としてグリフのシェイプのラスタライズが必要です。レンダリングしたいそれぞれのQChar, QColorの組み合わせのピクスマップをよりよくキャッシュしてキャッシュすることができます。QStaticTextを使用するかどうかにかかわらず、キャラクタのアウトラインをラスタライズするよりもはるかに高速です。

個々の文字を描画する代わりに、キャッシュからピックスマップを描画します。 This commitはこのアプローチを実証しています。文字描画方法は次のとおりです。

各(文字、前景、背景)タプルをキャッシュすることもできます。悲しいかな、前景と背景の組み合わせがたくさんある場合、これはすぐに手に入らなくなります。

背景がすべて同じ色(白など)の場合は、文字のネガティブマスクを保存することをお勧めします。glyphは白い背景と透明な形をしています。 This commitはこのアプローチを実証しています。

void drawChar(const QPointF & pos, QChar ch, QColor color, QPainter & p) { 
    auto & glyph = m_glyphs[ch]; 
    if (glyph.isNull()) { 
     glyph = QImage{m_glyphRect.size().toSize(), QImage::Format_ARGB32_Premultiplied}; 
     glyph.fill(Qt::white); 
     QPainter p{&glyph}; 
     p.setCompositionMode(QPainter::CompositionMode_DestinationOut); 
     p.setFont(m_font); 
     p.drawText(m_glyphPos, {ch}); 
    } 
    auto rect = m_glyphRect; 
    rect.moveTo(pos); 
    p.fillRect(rect, color); 
    p.drawImage(pos, glyph); 
} 

の代わりに与えられた色の完全プリレンダリング文字を格納し、あなただけのアルファマスクと複合して上に格納することができ:グリフの矩形はその後、白いマスクが上に適用され、グリフの色で満たされています-demand:

  1. 透明な背景にあらかじめレンダリングされた白いグリフから始めます。
  2. CompositionMode_SourceOutに背景のグリフ矩形を塗りつぶします。背景には、文字自体の穴が残っています。
  3. フォアグラウンドでグリフの矩形を塗りつぶします。CompositionMode_DestinationOver:フォアグラウンドで穴が埋められます。
  4. ウィジェットにコンポジットを描画します。

これは、かなり高速であることが判明しています。そうしたい場合は、コンポジットのレンダリングを完全に並列化できます(読者の練習として残します)。

注:プリレンダリングされたグリフは、アルファとの色のあらかじめ乗算を使用して、より薄く表示することができます。

優れたパフォーマンスを備えたさらに別のアプローチは、GPUを使用してテキストモードの表示をエミュレートすることです。プリレンダリングされたグリフのアウトラインをテクスチャに格納し、レンダリングするグリフのインデックスと色を配列に格納し、OpenGLと2つのシェーダを使用してレンダリングします。 This exampleは、このようなアプローチを実装するための出発点かもしれません。

完全な例として、CPUレンダリングを使用します。

// https://github.com/KubaO/stackoverflown/tree/master/questions/hex-widget-40458515 
#include <QtWidgets> 
#include <algorithm> 
#include <cmath> 

const QString & CP437() { 
    static auto const set = QStringLiteral(
       " ☺☻♥♦♣♠•◘○◙♂♀♪♫☼▶◀↕‼¶§▬↨↑↓→←∟↔▲▼" 
       "␣!\"#$%&'()*+,-./:;<=>?" 
       "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" 
       "`abcdefghijklmnopqrstuvwxyz{|}~ " 
       "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒ" 
       "áíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐" 
       "└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀" 
       "αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ "); 
    return set; 
} 

HexViewウィジェットはQAbstractScrollAreaから派生し、データのメモリマップされたチャンクを可視化:

class HexView : public QAbstractScrollArea { 
    const int m_addressChars = 8; 
    const qreal m_dataMargin = 4.; 
    const char * m_data; 
    size_t m_size; 
    size_t m_start = 0; 
    QRectF m_glyphRect{0.,0.,1.,1.}; 
    QPointF m_glyphPos; 
    int m_chars, m_lines; 
    QMap<QChar, QImage> m_glyphs; 
    QFont m_font{"Monaco"}; 
    qreal xStep() const { return m_glyphRect.width(); } 
    qreal yStep() const { return m_glyphRect.height(); } 
    static QChar decode(char ch) { return CP437()[(uchar)ch]; } 
    void drawChar(const QPointF & pos, QChar ch, QColor fg, QColor bg, QPainter & p) { 
     auto & glyph = m_glyphs[ch]; 
     if (glyph.isNull()) { 
      glyph = QImage{m_glyphRect.size().toSize(), QImage::Format_ARGB32_Premultiplied}; 
      glyph.fill(Qt::transparent); 
      QPainter p{&glyph}; 
      p.setPen(Qt::white); 
      p.setFont(m_font); 
      p.drawText(m_glyphPos, {ch}); 
     } 
     QImage composite = glyph; 
     { 
      QPainter p{&composite}; 
      p.setCompositionMode(QPainter::CompositionMode_SourceOut); 
      p.fillRect(composite.rect(), bg); 
      p.setCompositionMode(QPainter::CompositionMode_DestinationOver); 
      p.fillRect(composite.rect(), fg); 
     } 
     auto rect = m_glyphRect; 
     rect.moveTo(pos); 
     p.drawImage(pos, composite); 
    } 
    void initData() { 
     qreal width = viewport()->width() - m_addressChars*xStep() - m_dataMargin; 
     m_chars = (width > 0.) ? width/xStep() : 0.; 
     m_lines = viewport()->height()/yStep(); 
     if (m_chars && m_lines) { 
      verticalScrollBar()->setRange(0, m_size/m_chars); 
      verticalScrollBar()->setValue(m_start/m_chars); 
     } else { 
      verticalScrollBar()->setRange(0, 0); 
     } 
    } 
    void paintEvent(QPaintEvent *) override { 
     QPainter p{viewport()}; 
     QPointF pos; 
     QPointF step{xStep(), 0.}; 
     auto dividerX = m_addressChars*xStep() + m_dataMargin/2.; 
     p.drawLine(dividerX, 0, dividerX, viewport()->height()); 
     int offset = 0; 
     while (offset < m_chars*m_lines && m_start + offset < m_size) { 
      auto rawAddress = QString::number(m_start + offset, 16); 
      auto address = QString{m_addressChars-rawAddress.size(), ' '} + rawAddress; 
      for (auto c : address) { 
       drawChar(pos, c, Qt::black, Qt::white, p); 
       pos += step; 
      } 
      pos += QPointF{m_dataMargin, 0.}; 
      auto bytes = std::min(m_size - offset, (size_t)m_chars); 
      for (int n = bytes; n; n--) { 
       drawChar(pos, decode(m_data[m_start + offset++]), Qt::red, Qt::white, p); 
       pos += step; 
      } 
      pos = QPointF{0., pos.y() + yStep()}; 
     } 
    } 
    void resizeEvent(QResizeEvent *) override { 
     initData(); 
    } 
    void scrollContentsBy(int, int) override { 
     m_start = verticalScrollBar()->value() * (size_t)m_chars; 
     viewport()->update(); 
    } 
public: 
    HexView(QWidget * parent = nullptr) : HexView(nullptr, 0, parent) {} 
    HexView(const char * data, size_t size, QWidget * parent = nullptr) : 
     QAbstractScrollArea{parent}, m_data(data), m_size(size) 
    { 
     auto fm = QFontMetrics(m_font); 
     for (int i = 0x20; i < 0xE0; ++i) 
      m_glyphRect = m_glyphRect.united(fm.boundingRect(CP437()[i])); 
     m_glyphPos = {-m_glyphRect.left(), -m_glyphRect.top()}; 
     initData(); 
    } 
    void setData(const char * data, size_t size) { 
     if (data == m_data && size == m_size) return; 
     m_data = data; 
     m_size = size; 
     m_start = 0; 
     initData(); 
     viewport()->update(); 
    } 
}; 

私たちは、現代の活用私たちは、CP437文字セットで始まる

enter image description here

64ビットシステムとメモリマップは、ウィジェットによって可視化されるソースファイルをマップします。テスト目的のために、文字セットのビューも利用できます。

int main(int argc, char ** argv) { 
    QApplication app{argc, argv}; 
    QFile file{app.applicationFilePath()}; 
    if (!file.open(QIODevice::ReadOnly)) return 1; 
    const char * const map = (char*)file.map(0, file.size(), QFile::MapPrivateOption); 
    if (!map) return 2; 

    QWidget ui; 
    QGridLayout layout{&ui}; 
    HexView view; 
    QRadioButton exe{"Executable"}; 
    QRadioButton charset{"Character Set"}; 
    layout.addWidget(&view, 0, 0, 1, 3); 
    layout.addWidget(&exe, 1, 0); 
    layout.addWidget(&charset, 1, 1); 
    QObject::connect(&exe, &QPushButton::clicked, [&]{ 
     view.setData(map, (size_t)file.size()); 
    }); 
    QObject::connect(&charset, &QPushButton::clicked, [&]{ 
     static QByteArray data; 
     if (data.isNull()) { 
      data.resize(256); 
      for (int i = 0; i < data.size(); ++i) data[i] = (char)i; 
     } 
     view.setData(data.constData(), (size_t)data.size()); 
    }); 
    charset.click(); 
    ui.show(); 
    return app.exec(); 
} 
+0

非常に有用な情報で完璧な答え。ありがとう! – antonone

2

私が時々使用する1つの解決策は、プリレンダリングされた行のキャッシュを保持することです。私は通常、画面に表示される行の約2倍のエントリの二重リンクLRUリストを使用します。レンダリングにラインが使用されるたびに、リストの先頭に移動されます。私は新しい行を作成する必要があり、現在のキャッシュ数が限界を超えているとき、私はリストの最後の項目を再利用します。

個々の行の最終結果を保存することで、ほとんどの場合、ほとんどの行が1つのフレームから次のフレームに変化することはありません(スクロール時を含む)。

複雑さが増すと、コンテンツを変更するときに行を無効にする必要があります。

+0

これは良い最適化のように思えますが、正しく理解していれば、ビューを1行ずつスクロールするときに最適です。 PageDown/Up(画面全体を一度にスクロール)、またはスクロールバーを使用して大きなファイルをランダムにスクロールするときにもスピードアップしたいと思います(サイズが6 GB)。それでも、私はあなたが後で提案した最適化を使用するでしょう! – antonone

+0

@antonone:特別なケースをコーディングせずに、通常のケース(スムーズにスクロールしたり、小さな変更を加える、エリアを選択するなど)の場合、すべてが高速に動作します。私の経験では、wiewの内容が完全に変わった場合、20fpsのような「遅い」フレームレート(例:キー繰り返しレート以下)も大きな問題ではありません。私がこれを実装したのは、複雑なシンタックスを持つIDEで、キーボードを移動したり、テキストを選択したり、入力したりするときに、キーボードよりも遅くなることはまったく受け入れられません。 – 6502

関連する問題