我正在编写一个显示大量文本的应用程序。这不是单词和句子,而是显示在CP437字符集中的二进制数据。目前的表格:

我在画那些人物的时候遇到了问题。我需要一个一个地画每个字符,因为以后我想要应用不同的颜色。这些字符也应该有一个透明的背景,因为稍后我想在背景中绘制不同颜色的部分和范围(根据某些标准对这些字符进行分组)。
应用程序同时支持多个已打开的文件,但是当有多个文件打开时,快速i7上的绘图就开始引人注目,因此它可能编写得很糟糕。
在Qt5中绘制这类数据的最佳方法是什么?我应该先将字符预写到位图中,然后从那里开始,还是可以通过使用普通的Qt函数来绘制文本来绘制大量字符?
编辑:我使用的是一个普通的QFrame小部件,它使用QPainter在paintEvent中绘图。这是错误的做法吗?我读过一些关于QGraphicsScene的文档,从这些文档中我记得,在小部件需要对其绘制的对象有一些控制的情况下,最好使用它。我不需要对我画的东西有任何控制,我只需要画它,仅此而已。在我画完它之后,我不会引用任何特定的字符。
这个小部件有2000行代码,所以我不会粘贴整个代码,但是目前我的绘图方法如下:
cache),将迭代器计数器放置到i变量中,QStaticText对象,该对象包含有关从i变量中提取的ASCII代码标识的字符的绘图信息,QStaticText从cache表中绘制数据。因此,要绘制ASCII字符0x7A,我将从cache表中的索引0x7a中查找cache,并将这个QStaticText对象输入QPainter对象。我还在尝试一种不同的方法,在一个QPainter::drawText调用中呈现整行,这确实更快,但我已经失去了用不同颜色着色每个字符的可能性。我希望有这种可能性。
发布于 2016-11-07 23:18:04
QGraphicsScene的使用不会改善任何事情--它是QWidget之上的一个附加层。你追求的是原始的表现,所以你不应该使用它。
您可以将QTextDocument实现为内存缓冲区/文件的可见部分的视图模型,但是每次滚动时绘制新的QTextDocument不会比直接在QWidget上绘制东西更快。
使用QStaticText是朝着正确方向迈出的一步,但还不够:渲染QStaticText仍然需要字形的栅格化。您可以做得更好,并缓存您希望呈现的每个QChar, QColor组合的像素映射:无论是否使用QStaticText,这将比栅格化字符轮廓要快得多。
然后,您将从缓存中绘制像素映射,而不是绘制单个字符。此承诺演示了这种方法。字符绘制方法是:
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);
}您还可以缓存每个元组(字符、前景、背景)元组。唉,当有许多前景/背景组合时,这很快就失控了。
如果你所有的背景都是相同的颜色(例如白色),你会想要存储一个负面的面具的字符:glyph有一个白色的背景和透明的形状。此承诺演示了这种方法。字形矩形填充字形颜色,然后在顶部涂上白色掩膜:
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);
}与其存储给定颜色的完全预先呈现的字符,不如只存储alpha掩码并按需组合它们:
CompositionMode_Source)上预先呈现的白色字形开始.CompositionMode_SourceOut中用背景填充字形,背景将保留字符本身的一个洞。CompositionMode_DestinationOver中用前景填充字体:前景将填补这个洞。这是合理的快速,呈现是完全并行的-参见下面的例子。
注意:预先渲染的字形可以使用与alpha的颜色的进一步的预乘,以显示较少的厚。
另一种性能优异的方法是使用GPU模拟文本模式的显示。将预先呈现的字形轮廓存储在纹理中,存储要在数组中呈现的符号索引和颜色,并使用OpenGL和两个着色器进行渲染。这个例子可能是实现这种方法的起点。
下面是一个完整的示例,使用跨多个线程的CPU呈现。

我们从备份存储视图开始,用于生成QImage,这些视图是给定小部件的后备存储中的视图,可以用于并行绘制。
在2013年的iMac上,这段代码在大约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();
}
};然后,CP437字符集:
static auto const 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 *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;
}并行化呈现是在类方法中完成的-它们不会修改小部件的状态,只会访问只读数据并呈现到备份存储中。每个线程都作用于存储区中的孤立行。
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);
});
}此方法在线程之间分配工作块:
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();
}其余的执行工作是没有争议的:
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 &);
};我们利用现代64位系统和内存映射源文件来可视化小部件。出于测试目的,还可以获得字符集的视图:
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"发布于 2016-11-07 07:09:42
我有时使用的一个解决方案是保存一个预渲染行的缓存。我通常使用双链接LRU列表的条目,大约两倍的行,可以在屏幕上看到。每次使用一行来呈现时,都会移到列表的前面;当我需要创建一个新行时,当当前缓存计数超过限制时,我会重用列表中的最后一个条目。
通过存储单个行的最终结果,您可以非常快地重新绘制显示,在许多情况下,大多数线条不会从一个帧更改到另一个帧(包括滚动时)。
增加的复杂性也被合理地限制在更改内容时必须使行无效。
https://stackoverflow.com/questions/40458515
复制相似问题