在Qt5中绘制许多独立字符的最佳方法是? [英] A best way to draw a lot of independent characters in Qt5?

查看:131
本文介绍了在Qt5中绘制许多独立字符的最佳方法是?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个显示很多文本的应用程序。它不是单词和句子,而是以CP437字符集显示的二进制数据。当前格式:





我正在虽然绘制这些字符是一个问题。我需要一个一个地绘制每个字符,因为稍后我想应用不同的颜色。这些字符也应该具有透明的背景,因为稍后我想在背景中绘制具有不同颜色的部分和范围(以根据某些条件将这些字符分组)。



该应用程序同时支持多个打开的文件,但是当打开多个文件时,该图形在快速i7上开始可见,因此它可能写得很差。



在Qt5中绘制此类数据的最佳方法是什么?我应该只是将字符预渲染到位图并从那里开始,还是实际上可以通过使用常规Qt函数绘制文本来绘制很多字符?



编辑:我使用的是普通的 QFrame 小部件,该小部件确实使用<$ c在 paintEvent 中进行绘制$ c> QPainter 。这是错误的做法吗?我已经阅读了 QGraphicsScene 上的一些文档,我记得其中最适合在小部件需要对其绘制的对象进行控制的情况下使用。我不需要对绘画内容进行任何控制;我只需要绘制即可,仅此而已。在绘制后,我将不再引用任何特定字符。



该小部件有2000行,因此我不会粘贴完整的代码,但目前我的绘制方法是这样的:




  • 首先,创建一个表(缓存)(包含256个条目),将迭代器计数器放入 i 变量,

  • 为每个条目创建一个 QStaticText 对象,该对象包含有关由 i 变量

  • 稍后,在绘图功能中,对于输入流中的每个字节(即从文件中),使用 QStaticText 缓存中绘制数据表。因此,要绘制ASCII字符 0x7A ,我将从索引 0x7a <中查找 QStaticText / code>在缓存表中,并将此 QStaticText 对象提供给 QPainter 对象。



我也在尝试另一种方法,将整行显示为一个 QPainter :: drawText 调用,的确更快,但是我已经失去了用不同颜色为每个字符着色的可能性。我想有这种可能性。

解决方案

使用 QGraphicsScene 不会改善情况-它是 QWidget 之上的另一层。您追求原始性能,因此不应该使用它。



您可以实现 QTextDocument 作为内存缓冲区/文件的可见部分的视图模型,但是每次滚动绘制新鲜的 QTextDocument 都不会比直接在<$ c上绘制东西要快$ c> QWidget



使用 QStaticText 是朝正确方向迈出的一步,但是还不够:渲染 QStaticText 仍然需要字形形状的栅格化。您可以做得更好,并缓存要渲染的每个 QChar,QColor 组合的像素图:无论使用 QStaticText 。



然后从缓存中绘制像素图,而不是绘制单个字符。



我们从后备存储视图开始,用于生成 QImage s



在2013年的iMac上,这段代码在大约8毫秒内重新绘制了全屏小部件。

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

结构BackingStoreView {
QImage * dst = {};
uchar * data = {};
const QWidget * widget = {};
显式BackingStoreView(const QWidget * widget){
if(!widget ||!widget-> window())return;
dst = dynamic_cast< QImage *>(widget-> window()-> backingStore()-> paintDevice());
if(!dst || dst-> depth()%8)返回;
自动byteDepth = dst-> depth()/ 8;
auto pos = widget-> mapTo(widget-> window(),{});
data = const_cast< uchar *>(dst-> constScanLine(pos.y())+ byteDepth * pos.x());
this-> widget =小部件;
}
//查看给定小部件的后备存储区的视图
QImage getView()const {
if(!data)return {};
QImage ret(数据,小部件->宽度(),小部件->高度(),dst-> bytesPerLine(),dst-> format());
ret.setDevicePixelRatio(widget-> devicePixelRatio());
回程;
}
//给定的图像正是该视图吗?
bool isAView(const QImage& img)const {
return data&& img.bits()==数据&& img.depth()== dst-> depth()
&& img.width()==小部件-> width()&& img.height()==小部件-> height()
&& img.bytesPerLine()== dst-> bytesPerLine()&& img.format()== dst-> format();
}
};

然后,CP437字符集:

 静态自动常量CP437 = QStringLiteral(
☺☻♥♦♣♠•◘○◙♂♀♪♫☼▶◀↕!¶§▬↨↑↓→←∟ ↔▲▼
␣!\#$%&'()*,-。/ 0123456789:;< => ;?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ [\\ ] ^ _
ʻabcdefghijklmnopqrstuvwxyz {|}〜
ÇüéâäàåçêëèèïîîÄÄÅÉæÆôöûûÿÜÜ££₧ƒ
áíóúñѪº¿⌐¼¼¡░ ╣║╗╝╜╛┐
└┴┬├─┼╞╟╚╔影院╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀
αßΓπΣσµτΦθΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■);

HexView 小部件从 QAbstractScrollArea 派生并可视化内存映射的数据块:

  class HexView:public QAbstractScrollArea {
Q_OBJECT
QImage const m_nullImage;
const int m_addressChars = 8;
const int m_dataMargin = 4;
const char * m_data = {};
size_t m_dataSize = 0;
size_t m_dataStart = 0;
QSize m_glyphSize;
QPointF m_glyphPos;
int m_charsPerLine,m_lines;
QMap< QChar,QImage> m_glyphs;
QFont m_font { Monaco};
QFontMetricsF m_fm {m_font};
struct DrawUnit {QPoint pos; const QImage *字形; QColor fg,bg; };
QFutureSynchronizer< void> m_sync;
QVector< DrawUnit> m_chunks;
QVector< QImage> m_stores;
使用chunk_it = QVector< DrawUnit> :: const_iterator;
使用store_it = QVector< QImage> :: const_iterator;

静态内联QChar解码(char ch){return CP437 [uchar(ch)]; }
inline int xStep()const {return m_glyphSize.width(); }
inline yyStep()const {return m_glyphSize.height(); }
void initData(){
int const width = viewport()-> width()-m_addressChars * xStep()-m_dataMargin;
m_charsPerLine =(宽度> 0)? width / xStep():0;
m_lines = viewport()-> height()/ yStep();
if(m_charsPerLine&& m_lines){
verticalScrollBar()-> setRange(0,m_dataSize / m_charsPerLine);
verticalScrollBar()-> setValue(m_dataStart / m_charsPerLine);
}否则{
verticalScrollBar()-> setRange(0,0);
}
}
const QImage& glyph(QChar ch){
auto& glyph = m_glyphs [ch];
if(glyph.isNull()){
QPointF范围= m_fm.boundingRect(ch).translated(m_glyphPos).bottomRight();
字形= QImage(m_glyphSize,QImage :: Format_ARGB32_Premultiplied);
glyph.fill(Qt :: transparent);
QPainter p {& glyph};
p.setPen(Qt :: white);
p.setFont(m_font);
p.translate(m_glyphPos);
p.scale(std :: min(1.0,(m_glyphSize.width()-1)/extent.x()),
std :: min(1.0,(m_glyphSize.height()- 1)/extent.y()));
p.drawText(QPointF {},{ch});
}
返回字形;
}

并行渲染是在类方法中完成的-它们不会修改状态除了访问只读数据并将其呈现到后备存储中外,还包括窗口小部件的外观。每个线程都作用于存储区中的隔离行。

 静态void drawChar(const DrawUnit& u,QPainter& p) {
const QRect rect(u.pos,u.glyph-> size());
p.setCompositionMode(QPainter :: CompositionMode_Source);
p.drawImage(u.pos,* u.glyph);
p.setCompositionMode(QPainter :: CompositionMode_SourceOut);
p.fillRect(rect,u.bg);
p.setCompositionMode(QPainter :: CompositionMode_DestinationOver);
p.fillRect(rect,ufg);
}
静态QFuture< void> SubmitChunks(chunk_it开始,chunk_it结束,store_it商店){
返回QtConcurrent :: run([begin,end,store] {
QPainter p(const_cast< QImage *>(& * store))) ;
for(auto it = begin; it!= end; it ++)
drawChar(* it,p);
});
}

此方法在线程之间分配工作块:

  int processChunks(){
m_stores.resize(QThread :: idealThreadCount());
BackingStoreView view(viewport());
if(!view.isAView(m_stores.last()))
std :: generate(m_stores.begin(),m_stores.end(),[& view] {return view.getView( );});
std :: ptrdiff_t jobSize = std :: max(128,(m_chunks.size()/ m_stores.size())+ 1);
auto const cend = m_chunks.cend();
int refY = 0;
自动存储= m_stores.cbegin();
for(auto it = m_chunks.cbegin(); it!= cend;){
auto end = it + std :: min(cend-it,jobSize);
而(end!= cend&&(end-> pos.y()== refY ||(refY = end-> pos.y(),false)))
结束++; //打破行边界上的块
m_sync.addFuture(submitChunks(it,end,store));
it =结束;
store ++;
}
m_sync.waitForFinished();
m_sync.clearFutures();
m_chunks.clear();
退货商店-m_stores.cbegin();
}

实施的其余部分没有争议:

 受保护:
void paintEvent(QPaintEvent * ev)覆盖{
QElapsedTimer时间;
time.start();
QPainter p {viewport()};
QPoint pos;
QPoint const step {xStep(),0};
自动除法器X = m_addressChars * xStep()+ m_dataMargin / 2 .;
p.drawLine(dividerX,0,dividerX,viewport()-> height());
int偏移量= 0;
QRect rRect = ev-> rect();
p.end();
while(offset< m_charsPerLine * m_lines&& m_dataStart + offset< m_dataSize){
const auto address = QString :: number(m_dataStart + offset,16);
pos + = step *(m_addressChars-address.size());
for(auto c:address){
if(QRect(pos,m_glyphSize).intersects(rRect))
m_chunks.push_back({pos,& glyph(c),Qt: :black,Qt :: white});
pos + =步骤;
}
pos + = {m_dataMargin,0};
自动字节= std :: min(m_dataSize-偏移量,(size_t)m_charsPerLine);
for(int n = bytes; n; n--){
if(QRect(pos,m_glyphSize).intersects(rRect))
m_chunks.push_back({pos,& glyph (decode(m_data [m_dataStart + offset])),Qt :: red,Qt :: white});
pos + =步骤;
偏移++;
}
pos = {0,pos.y()+ yStep()};
}
个int作业= processChunks();
newStatus(QStringLiteral(%1ms n =%2)。arg(time.nsecsElapsed()/ 1e6).arg(jobs));
}
void resizeEvent(QResizeEvent *)覆盖{
initData();
}
void scrollContentsBy(int,int dy)覆盖{
m_dataStart = verticalScrollBar()-> value()*(size_t)m_charsPerLine;
viewport()-> scroll(0,dy * m_glyphSize.height(),viewport()-> rect());
}
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_dataSize(size)
{
QRectF glyphRectF {0。,0.,1.,1.};
for(int i = 0x20; i< 0xE0; ++ i)
glyphRectF = glyphRectF.united(m_fm.boundingRect(CP437 [i]));
m_glyphPos = -glyphRectF.topLeft();
m_glyphSize = QSize(std :: ceil(glyphRectF.width()),std :: ceil(glyphRectF.height()));
initData();
}
void setData(const char * data,size_t size){
if(data == m_data&& size == m_dataSize)返回;
m_data =数据;
m_dataSize =大小;
m_dataStart = 0;
initData();
viewport()-> update();
}
Q_SIGNAL void newStatus(const QString&);
};

我们利用现代的64位系统并对源文件进行内存映射,以使小部件可视化。出于测试目的,还提供了字符集视图:

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

QWidget ui;
QGridLayout布局{& ui};
HexView视图;
QRadioButton exe { Executable};
QRadioButton charset { Character Set};
QLabel状态;
layout.addWidget(& view,0,0,1,4);
layout.addWidget(& exe,1,0);
layout.addWidget(& charset,1,1);
layout.addWidget(& status,1,2,1,2);
QObject :: connect(& exe,& QPushButton :: clicked,[&] {
view.setData(map,(size_t)file.size());
} );
QObject :: connect(&charset,& QPushButton :: clicked,[&] {
静态std :: array< char,256>数据;
std :: iota( data.begin(),data.end(),char(0));
view.setData(data.data(),data.size());
});
QObject :: connect(& view,& HexView :: newStatus,& status,& QLabel :: setText);
charset.click();
ui.resize(1000,800);
ui.show();
返回app.exec();
}

#include main.moc


I'm writing an application that displays a lot of text. It's not words and sentences though, it's binary data displayed in CP437 charset. Current form:

I'm having a problem though with drawing those characters. I need to draw each character one by one, because later I would like to apply different coloring. Those characters should have a transparent background as well, because later I would like to draw sections and ranges with different colors in the background (to group those characters based on some criteria).

The application supports multiple opened files at the same time, but when there are multiple files opened, the drawing starts to be noticeable on fast i7, so it's probably badly written.

What would be the best approach to draw this kind of data in Qt5? Should I just prerender characters to a bitmap and start from there, or it actually is possible to draw lots of characters by using normal Qt functions to draw text?

Edit: I'm using a normal QFrame widget that does drawing in paintEvent, using QPainter. Is this a wrong approach? I've read some docs on QGraphicsScene, from which I've remembered that it's best used in situations where a widget needs to have some control on the objects it draws. I don't need any control on what I draw; I just need to draw it and that's all. I won't reference any particular character after I'll draw it.

The widget has 2000 lines, so I won't paste the whole code, but currently my drawing approach is like this:

  • First, create a table (cache) with 256 entries, put the iterator counter to i variable,
  • For each entry, create a QStaticText object that contains drawing information about a character identified by ASCII code taken from i variable,
  • Later, in the drawing function, for each byte in the input stream (i.e. from the file), draw the data using QStaticText from the cache table. So, to draw ASCII character 0x7A, I'll look up QStaticText from index 0x7a in cache table, and feed this QStaticText object into the QPainter object.

I was also experimenting with a different approach, rendering the whole line in one QPainter::drawText call, and indeed it was faster, but I've lost possibility of coloring each character with different color. I would like to have this possibility.

解决方案

The use of a QGraphicsScene wouldn't improve things - it's an additional layer on top of a QWidget. You're after raw performance, so you shouldn't be using it.

You could implement a QTextDocument as a viewmodel for the visible section of your memory buffer/file, but painting the fresh QTextDocument each time you scroll wouldn't be any faster than drawing things directly on a QWidget.

Using QStaticText is a step in the right direction, but insufficient: rendering QStaticText still requires the rasterization of the glyph's shape. You can do better and cache the pixmap of each QChar, QColor combination that you wish to render: this will be much faster than rasterizing character outlines, whether using QStaticText or not.

Instead of drawing individual characters, you then draw pixmaps from the cache. This commit demonstrates this approach. The character drawing method is:

void drawChar(const QPointF & pos, QChar ch, QColor color, QPainter & p) {
    auto & glyph = m_cache[{ch, color}];
    if (glyph.isNull()) {
        glyph = QPixmap{m_glyphRect.size().toSize()};
        glyph.fill(Qt::white);
        QPainter p{&glyph};
        p.setPen(color);
        p.setFont(m_font);
        p.drawText(m_glyphPos, {ch});
    }
    p.drawPixmap(pos, glyph);
}

You could also cache each (character,foreground,background) tuple. Alas, this gets quickly out of hand when there are many foreground/background combinations.

If all of your backgrounds are of the same color (e.g. white), you'd wish to store a negative mask of the character: the glyph has a white background and a transparent shape. This commit demonstrates this approach. The glyph rectangle is filled with glyph color, then a white mask is applied on top:

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);
}

Instead of storing a fully pre-rendered character of a given color, you could store just the alpha mask and composite them on-demand:

  1. Start with a pre-rendered white glyph on a transparent background (CompositionMode_Source).
  2. Fill the glyph rect with background in CompositionMode_SourceOut: the background will remain with a hole for the character itself.
  3. Fill the glyph rect with foreground in CompositionMode_DestinationOver: the foreground will fill the hole.
  4. (Optional) Draw the composite on the widget, if you're not painting on the widget already.

This turns out to be reasonably fast, and the rendering is fully parallelizable - see the example below.

Note: The pre-rendered glyph could use further premultiplication of the color with alpha to appear less thick.

Yet another approach, with excellent performance, would be to emulate a text-mode display using the GPU. Store the pre-rendered glyph outlines in a texture, store the glyph indices and colors to be rendered in an array, and use OpenGL and two shaders to do the rendering. This example might be a starting point to implement such an approach.

A complete example, using CPU rendering across multiple threads, follows.

We start with the backing store view, used to produce QImages that are views into the backing store for a given widget, and can be used to parallelize painting.

On a 2013 iMac, this code repaints the full-screen widget in about 8ms.

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

struct BackingStoreView {
    QImage *dst = {};
    uchar *data = {};
    const QWidget *widget = {};
    explicit BackingStoreView(const QWidget *widget) {
        if (!widget || !widget->window()) return;
        dst = dynamic_cast<QImage*>(widget->window()->backingStore()->paintDevice());
        if (!dst || dst->depth() % 8) return;
        auto byteDepth = dst->depth()/8;
        auto pos = widget->mapTo(widget->window(), {});
        data = const_cast<uchar*>(dst->constScanLine(pos.y()) + byteDepth * pos.x());
        this->widget = widget;
    }
    // A view onto the backing store of a given widget
    QImage getView() const {
        if (!data) return {};
        QImage ret(data, widget->width(), widget->height(), dst->bytesPerLine(), dst->format());
        ret.setDevicePixelRatio(widget->devicePixelRatio());
        return ret;
    }
    // Is a given image exactly this view?
    bool isAView(const QImage &img) const {
        return data && img.bits() == data && img.depth() == dst->depth()
                && img.width() == widget->width() && img.height() == widget->height()
                && img.bytesPerLine() == dst->bytesPerLine() && img.format() == dst->format();
    }
};

Then, the CP437 character set:

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

The HexView widget derives from QAbstractScrollArea and visualizes a memory-mapped chunk of data:

class HexView : public QAbstractScrollArea {
    Q_OBJECT
    QImage const m_nullImage;
    const int m_addressChars = 8;
    const int m_dataMargin = 4;
    const char * m_data = {};
    size_t m_dataSize = 0;
    size_t m_dataStart = 0;
    QSize m_glyphSize;
    QPointF m_glyphPos;
    int m_charsPerLine, m_lines;
    QMap<QChar, QImage> m_glyphs;
    QFont m_font{"Monaco"};
    QFontMetricsF m_fm{m_font};
    struct DrawUnit { QPoint pos; const QImage *glyph; QColor fg, bg; };
    QFutureSynchronizer<void> m_sync;
    QVector<DrawUnit> m_chunks;
    QVector<QImage> m_stores;
    using chunk_it = QVector<DrawUnit>::const_iterator;
    using store_it = QVector<QImage>::const_iterator;

    static inline QChar decode(char ch) { return CP437[uchar(ch)]; }
    inline int xStep() const { return m_glyphSize.width(); }
    inline int yStep() const { return m_glyphSize.height(); }
    void initData() {
        int const width = viewport()->width() - m_addressChars*xStep() - m_dataMargin;
        m_charsPerLine = (width > 0) ? width/xStep() : 0;
        m_lines = viewport()->height()/yStep();
        if (m_charsPerLine && m_lines) {
            verticalScrollBar()->setRange(0, m_dataSize/m_charsPerLine);
            verticalScrollBar()->setValue(m_dataStart/m_charsPerLine);
        } else {
            verticalScrollBar()->setRange(0, 0);
        }
    }
    const QImage &glyph(QChar ch) {
        auto &glyph = m_glyphs[ch];
        if (glyph.isNull()) {
            QPointF extent = m_fm.boundingRect(ch).translated(m_glyphPos).bottomRight();
            glyph = QImage(m_glyphSize, QImage::Format_ARGB32_Premultiplied);
            glyph.fill(Qt::transparent);
            QPainter p{&glyph};
            p.setPen(Qt::white);
            p.setFont(m_font);
            p.translate(m_glyphPos);
            p.scale(std::min(1.0, (m_glyphSize.width()-1)/extent.x()),
                    std::min(1.0, (m_glyphSize.height()-1)/extent.y()));
            p.drawText(QPointF{}, {ch});
        }
        return glyph;
    }

The parallelized rendering is done in class methods - they don't modify the state of the widget, other than accessing read-only data, and rendering into the backing store. The threads each act on isolated lines in the store.

    static void drawChar(const DrawUnit & u, QPainter &p) {
        const QRect rect(u.pos, u.glyph->size());
        p.setCompositionMode(QPainter::CompositionMode_Source);
        p.drawImage(u.pos, *u.glyph);
        p.setCompositionMode(QPainter::CompositionMode_SourceOut);
        p.fillRect(rect, u.bg);
        p.setCompositionMode(QPainter::CompositionMode_DestinationOver);
        p.fillRect(rect, u.fg);
    }
    static QFuture<void> submitChunks(chunk_it begin, chunk_it end, store_it store) {
        return QtConcurrent::run([begin, end, store]{
            QPainter p(const_cast<QImage*>(&*store));
            for (auto it = begin; it != end; it++)
                drawChar(*it, p);
        });
    }

This method distributes the chunks of work between threads:

    int processChunks() {
        m_stores.resize(QThread::idealThreadCount());
        BackingStoreView view(viewport());
        if (!view.isAView(m_stores.last()))
            std::generate(m_stores.begin(), m_stores.end(), [&view]{ return view.getView(); });
        std::ptrdiff_t jobSize = std::max(128, (m_chunks.size() / m_stores.size())+1);
        auto const cend = m_chunks.cend();
        int refY = 0;
        auto store = m_stores.cbegin();
        for (auto it = m_chunks.cbegin(); it != cend;) {
            auto end = it + std::min(cend-it, jobSize);
            while (end != cend && (end->pos.y() == refY || (refY = end->pos.y(), false)))
                end++; // break chunks across line boundaries
            m_sync.addFuture(submitChunks(it, end, store));
            it = end;
            store++;
        }
        m_sync.waitForFinished();
        m_sync.clearFutures();
        m_chunks.clear();
        return store - m_stores.cbegin();
    }

The remainder of the implementation is uncontroversial:

protected:
    void paintEvent(QPaintEvent *ev) override {
        QElapsedTimer time;
        time.start();
        QPainter p{viewport()};
        QPoint pos;
        QPoint const step{xStep(), 0};
        auto dividerX = m_addressChars*xStep() + m_dataMargin/2.;
        p.drawLine(dividerX, 0, dividerX, viewport()->height());
        int offset = 0;
        QRect rRect = ev->rect();
        p.end();
        while (offset < m_charsPerLine*m_lines && m_dataStart + offset < m_dataSize) {
            const auto address = QString::number(m_dataStart + offset, 16);
            pos += step * (m_addressChars - address.size());
            for (auto c : address) {
                if (QRect(pos, m_glyphSize).intersects(rRect))
                    m_chunks.push_back({pos, &glyph(c), Qt::black, Qt::white});
                pos += step;
            }
            pos += {m_dataMargin, 0};
            auto bytes = std::min(m_dataSize - offset, (size_t)m_charsPerLine);
            for (int n = bytes; n; n--) {
                if (QRect(pos, m_glyphSize).intersects(rRect))
                    m_chunks.push_back({pos, &glyph(decode(m_data[m_dataStart + offset])), Qt::red, Qt::white});
                pos += step;
                offset ++;
            }
            pos = {0, pos.y() + yStep()};
        }
        int jobs = processChunks();
        newStatus(QStringLiteral("%1ms n=%2").arg(time.nsecsElapsed()/1e6).arg(jobs));
    }
    void resizeEvent(QResizeEvent *) override {
        initData();
    }
    void scrollContentsBy(int, int dy) override {
        m_dataStart = verticalScrollBar()->value() * (size_t)m_charsPerLine;
        viewport()->scroll(0, dy * m_glyphSize.height(), viewport()->rect());
    }
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_dataSize(size)
    {
        QRectF glyphRectF{0., 0., 1., 1.};
        for (int i = 0x20; i < 0xE0; ++i)
            glyphRectF = glyphRectF.united(m_fm.boundingRect(CP437[i]));
        m_glyphPos = -glyphRectF.topLeft();
        m_glyphSize = QSize(std::ceil(glyphRectF.width()), std::ceil(glyphRectF.height()));
        initData();
    }
    void setData(const char * data, size_t size) {
        if (data == m_data && size == m_dataSize) return;
        m_data = data;
        m_dataSize = size;
        m_dataStart = 0;
        initData();
        viewport()->update();
    }
    Q_SIGNAL void newStatus(const QString &);
};

We leverage modern 64-bit systems and memory-map the source file to be visualized by the widget. For test purposes, a view of the character set is also available:

int main(int argc, char ** argv) {
    QApplication app{argc, argv};
    QFile file{app.applicationFilePath()};
    if (!file.open(QIODevice::ReadOnly)) return 1;
    auto *const map = (const 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"};
    QLabel status;
    layout.addWidget(&view, 0, 0, 1, 4);
    layout.addWidget(&exe, 1, 0);
    layout.addWidget(&charset, 1, 1);
    layout.addWidget(&status, 1, 2, 1, 2);
    QObject::connect(&exe, &QPushButton::clicked, [&]{
        view.setData(map, (size_t)file.size());
    });
    QObject::connect(&charset, &QPushButton::clicked, [&]{
        static std::array<char, 256> data;
        std::iota(data.begin(), data.end(), char(0));
        view.setData(data.data(), data.size());
    });
    QObject::connect(&view, &HexView::newStatus, &status, &QLabel::setText);
    charset.click();
    ui.resize(1000, 800);
    ui.show();
    return app.exec();
}

#include "main.moc"

这篇关于在Qt5中绘制许多独立字符的最佳方法是?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆