使用QGraphicsScene
不会改善事情 - 它是QWidget
之上的附加层。你在原始表演之后,所以你不应该使用它。
你可以实现一个QTextDocument
为您的内存缓冲区/文件的可见部分中的视图模型,但您滚动新鲜QTextDocument
每次画会不会比直接在QWidget
绘制的东西更快。
使用QStaticText
朝着正确的方向迈出了一步,但不足:渲染QStaticText
仍然需要栅格化字形的形状。你可以做得更好,并缓存你想渲染的每个QChar, QColor
组合的像素图:这将比光栅化字符轮廓要快得多,无论是否使用QStaticText
。
不是绘制单个字符,而是从缓存中绘制像素图。 This commit演示了这种方法。字符绘制方法是:
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
具有白色背景和透明形状。 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);
}
而不是存储在给定颜色的完全预渲染的字符,你也可以只把阿尔法遮掩和复合他们 - 要求:
- 从透明背景上的预先呈现的白色字形开始。
- 在
CompositionMode_SourceOut
中填充带背景的字形直方图:背景将为该字符本身留下一个空洞。
- 在
CompositionMode_DestinationOver
前面填充字形矩形:前景将填充该孔。
- 在小部件上绘制复合材料。
事实证明这是相当快的,如果您希望这样做(我把它作为练习读者),复合材料的渲染是完全可并行化的。
注意:预渲染的字形可以使用颜色与alpha的进一步预乘,看起来不那么厚。
另一种具有卓越性能的方法是使用GPU模拟文本模式显示。将预渲染的字形轮廓存储在纹理中,将要呈现的字形索引和颜色存储在数组中,并使用OpenGL和两个着色器进行渲染。 This example可能是实施这种方法的起点。
下面是一个使用CPU渲染的完整示例。
我们先从CP437字符集:
// 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();
}
};
我们充分利用现代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();
}
有助于了解代码现在是什么?你在使用QTextEdit吗?您是否使用QTextCursor插入文本?你只是在绘制QGraphicsScene? – GabeWeiss
您是否建立了基线在QTextEdit或QGraphicsScene中甚至在QML中绘制文本? – rubenvb
用相关信息更新了帖子。我无法真正使用'QTextEdit',因为它似乎管理着自己的缓冲区。我需要有自己的缓冲区,因为我需要我的应用程序来允许管理(复制/粘贴)大量数据区域(如10 TB)而不会有任何延迟。 – antonone