c++ - Qt:可视化大型二维数组的最有效方法是什么?
问题描述
我正在移植一个项目,该项目使用 Curses 来可视化一个大型(例如 5000x5000)二维字符数组。问题是,它必须是一个高性能项目,其中数组不断更新,但在当前状态下,无论我如何优化后端,输出到 stdout 都是一个瓶颈。该项目将受益于使用 Qt 可以提供的更快和面向对象的方法。我试过的:
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
QGraphicsView view(&scene);
double totalY = 0;
for (size_t i = 0; i < 5000; ++i) {
double totalX = 0;
for (size_t j = 0; j < 5000; ++j) {
// Add an arbitrary char
QGraphicsSimpleTextItem *simpleText = scene.addSimpleText(QChar('.'));
simpleText->setPos(totalX, totalY);
// Add cell's width and height respectively
totalX += 10;
}
totalY += 10;
}
view.show();
return a.exec();
}
但事实证明,创建 5000x5000 图形项目要慢得多。我的下一个想法是创建某种视口,从而消除使用图形项目的需要。它可能是一些画布(QImage),每次更新数组时我都会清除并绘制它。但是你推荐什么?
解决方案
第二次尝试
在第一次尝试并不令人满意之后,我按照VK的提示进行操作:
我的意思是绘制一个字符是非常昂贵的操作。所以我建议你将每个可能的字符(我假设它们不多)绘制到内存中的一些小图像上。而不是bitblt(如果你不明白一些词,然后谷歌它),即复制字节块到最终的QImage。这将比绘画文本快得多。
testQLargeCharTable2.cc
:
#include <cassert>
#include <algorithm>
#include <random>
#include <vector>
#include <QtWidgets>
template <typename Value>
class MatrixT {
private:
size_t _nCols;
std::vector<Value> _values;
public:
MatrixT(size_t nRows, size_t nCols, Value value = Value()):
_nCols((assert(nCols > 0), nCols)), _values(nRows * nCols, value)
{ }
size_t rows() const { return _values.size() / _nCols; }
size_t cols() const { return _nCols; }
Value* operator[](size_t i)
{
assert(i < rows());
return &_values[i * _nCols];
}
const Value* operator[](size_t i) const
{
assert(i < rows());
return &_values[i * _nCols];
}
};
using CharTable = MatrixT<char>;
class CharTableView: public QAbstractScrollArea {
private:
const CharTable* pTbl = nullptr;
using CharCache = std::map<char, QPixmap>;
int wCell;
int hCell;
CharCache cacheChars;
public:
CharTableView(QWidget* pQParent = nullptr);
virtual ~CharTableView() = default;
CharTableView(const CharTableView&) = delete;
CharTableView& operator=(const CharTableView&) = delete;
void set(CharTable* pTbl)
{
this->pTbl = pTbl;
updateScrollBars();
update();
}
protected:
virtual void resizeEvent(QResizeEvent* pQEvent) override;
virtual void paintEvent(QPaintEvent* pQEvent) override;
private:
void updateScrollBars();
const QPixmap& getCharPixmap(char c);
};
void CharTableView::resizeEvent(QResizeEvent* pQEvent)
{
updateScrollBars();
}
void CharTableView::paintEvent(QPaintEvent* pQEvent)
{
if (!pTbl) return;
const int xView = horizontalScrollBar()
? horizontalScrollBar()->value() : 0;
const int yView = verticalScrollBar()
? verticalScrollBar()->value() : 0;
const int wView = viewport()->width();
const int hView = viewport()->height();
const int iRow0 = yView / hCell;
const int iCol0 = xView / wCell;
const int iRowN = std::min((int)pTbl->rows(), (yView + hView) / hCell + 1);
const int iColN = std::min((int)pTbl->cols(), (xView + wView) / wCell + 1);
QPainter qPainter(viewport());
for (int iRow = iRow0; iRow < iRowN; ++iRow) {
const char*const row = (*pTbl)[iRow];
const int yCell = iRow * hCell - yView;
for (int iCol = iCol0; iCol < iColN; ++iCol) {
const int xCell = iCol * wCell - xView;
const QPixmap& qPixmap = getCharPixmap(row[iCol]);
qPainter.drawPixmap(
QRect(xCell, yCell, wCell, hCell),
qPixmap);
}
}
}
CharTableView::CharTableView(QWidget* pQWidget):
QAbstractScrollArea(pQWidget)
{
QFontMetrics qFontMetrics(viewport()->font());
wCell = 2 * qFontMetrics.averageCharWidth();
hCell = qFontMetrics.height();
}
void CharTableView::updateScrollBars()
{
const int w = (int)(pTbl ? pTbl->cols() : 0) * wCell;
const int h = (int)(pTbl ? pTbl->rows() : 0) * hCell;
const QSize sizeView = viewport()->size();
QScrollBar*const pQScrBarH = horizontalScrollBar();
pQScrBarH->setRange(0, w > sizeView.width() ? w - sizeView.width() : 0);
pQScrBarH->setPageStep(sizeView.width());
QScrollBar*const pQScrBarV = verticalScrollBar();
pQScrBarV->setRange(0, h > sizeView.height() ? h - sizeView.height() : 0);
pQScrBarV->setPageStep(sizeView.height());
}
const QPixmap& CharTableView::getCharPixmap(char c)
{
const CharCache::iterator iter = cacheChars.find(c);
if (iter != cacheChars.end()) return iter->second;
QPixmap& qPixmap = cacheChars[c] = QPixmap(wCell, hCell);
qPixmap.fill(QColor(0, 0, 0, 0));
{ QPainter qPainter(&qPixmap);
qPainter.drawText(
QRect(0, 0, wCell, hCell),
Qt::AlignCenter,
QString(QChar(c)));
}
return qPixmap;
}
int main(int argc, char** argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
const size_t n = 10;
QApplication app(argc, argv);
// setup data
const char chars[]
= "0123456789()[]{}/&%$!'+#?="
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CharTable tbl(5000, 5000);
std::random_device rd;
std::mt19937 rng(rd()); // seed the generator
std::uniform_int_distribution<size_t> distr(0, std::size(chars) - 1);
for (size_t i = 0; i < tbl.rows(); ++i) {
char*const row = tbl[i];
for (size_t j = 0; j < tbl.cols(); ++j) {
row[j] = chars[distr(rng)];
}
}
// setup GUI
CharTableView qCharTableView;
qCharTableView.setWindowTitle("Large Character Table View - 2nd Attempt");
qCharTableView.resize(1024, 768);
qCharTableView.set(&tbl);
qCharTableView.show();
// runtime loop
return app.exec();
}
输出:
我用全屏窗口(2560×1280)做了同样的测试。性能仍然是可比的。(截取的 GIF 动画太大,无法在此处上传。)
与 VK 的提示相反,我使用了QPixmap
. QImage
也可以用 a 修改QPainter
。还有一个QPainter::drawImage()可用。
第一次尝试
我的第一次尝试是在paintEvent()
从QAbstractScrollArea
. 因此,我小心地跳过了视图区域之外的所有行和列。乍一看,性能似乎没有那么糟糕,但在全屏窗口大小的情况下,该方法显示出弱点。在拖动滚动条时,输出明显落后。
testQLargeCharTable1.cc
:
#include <cassert>
#include <algorithm>
#include <random>
#include <vector>
#include <QtWidgets>
template <typename Value>
class MatrixT {
private:
size_t _nCols;
std::vector<Value> _values;
public:
MatrixT(size_t nRows, size_t nCols, Value value = Value()):
_nCols((assert(nCols > 0), nCols)), _values(nRows * nCols, value)
{ }
size_t rows() const { return _values.size() / _nCols; }
size_t cols() const { return _nCols; }
Value* operator[](size_t i)
{
assert(i < rows());
return &_values[i * _nCols];
}
const Value* operator[](size_t i) const
{
assert(i < rows());
return &_values[i * _nCols];
}
};
using CharTable = MatrixT<char>;
class CharTableView: public QAbstractScrollArea {
private:
const CharTable* pTbl = nullptr;
int wCell;
int hCell;
public:
CharTableView(QWidget* pQParent = nullptr);
virtual ~CharTableView() = default;
CharTableView(const CharTableView&) = delete;
CharTableView& operator=(const CharTableView&) = delete;
void set(CharTable* pTbl)
{
this->pTbl = pTbl;
updateScrollBars();
update();
}
protected:
virtual void resizeEvent(QResizeEvent* pQEvent) override;
virtual void paintEvent(QPaintEvent* pQEvent) override;
private:
void updateScrollBars();
};
void CharTableView::resizeEvent(QResizeEvent* pQEvent)
{
updateScrollBars();
}
void CharTableView::paintEvent(QPaintEvent* pQEvent)
{
if (!pTbl) return;
const int xView = horizontalScrollBar()
? horizontalScrollBar()->value() : 0;
const int yView = verticalScrollBar()
? verticalScrollBar()->value() : 0;
const int wView = viewport()->width();
const int hView = viewport()->height();
const int iRow0 = yView / hCell;
const int iCol0 = xView / wCell;
const int iRowN = std::min((int)pTbl->rows(), (yView + hView) / hCell + 1);
const int iColN = std::min((int)pTbl->cols(), (xView + wView) / wCell + 1);
QPainter qPainter(viewport());
for (int iRow = iRow0; iRow < iRowN; ++iRow) {
const char*const row = (*pTbl)[iRow];
const int yCell = iRow * hCell - yView;
const int yC = yCell + hCell / 2;
for (int iCol = iCol0; iCol < iColN; ++iCol) {
const int xCell = iCol * wCell - xView;
const int xC = xCell + wCell / 2;
qPainter.drawText(
QRect(xCell, yCell, wCell, hCell),
Qt::AlignCenter,
QString(QChar(row[iCol])));
}
}
}
CharTableView::CharTableView(QWidget* pQWidget):
QAbstractScrollArea(pQWidget)
{
QFontMetrics qFontMetrics(viewport()->font());
wCell = 2 * qFontMetrics.averageCharWidth();
hCell = qFontMetrics.height();
}
void CharTableView::updateScrollBars()
{
const int w = (int)pTbl->cols() * wCell;
const int h = (int)pTbl->rows() * hCell;
const QSize sizeView = viewport()->size();
QScrollBar*const pQScrBarH = horizontalScrollBar();
pQScrBarH->setRange(0, w - sizeView.width());
pQScrBarH->setPageStep(sizeView.width());
QScrollBar*const pQScrBarV = verticalScrollBar();
pQScrBarV->setRange(0, h - sizeView.height());
pQScrBarV->setPageStep(sizeView.height());
}
int main(int argc, char** argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
const size_t n = 10;
QApplication app(argc, argv);
// setup data
const char chars[]
= "0123456789()[]{}/&%$!'+#?="
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CharTable tbl(5000, 5000);
std::random_device rd;
std::mt19937 rng(rd()); // seed the generator
std::uniform_int_distribution<size_t> distr(0, std::size(chars) - 1);
for (size_t i = 0; i < tbl.rows(); ++i) {
char*const row = tbl[i];
for (size_t j = 0; j < tbl.cols(); ++j) {
row[j] = chars[distr(rng)];
}
}
// setup GUI
CharTableView qCharTableView;
qCharTableView.setWindowTitle("Large Character Table View - 1st Attempt");
qCharTableView.resize(640, 480);
qCharTableView.set(&tbl);
qCharTableView.show();
// runtime loop
return app.exec();
}
输出:
推荐阅读
- sql - SQL 日期时间 2019 年 1 月 25 日下午 4:06:45
- c# - 如何在 Unity 2d 中制作推进器?
- tsql - 在一个表的列中分解 csv 以插入新表
- git - 为什么git没有在分支更改中隐藏空文件夹?
- c# - 从其他脚本 Unity 访问脚本
- java - 通过处理与 Arduino 通信的问题
- python - 使用 python-markdown-math 渲染降价的问题
- php - PHP; 通过浏览器与 CLI 访问时,cURL 的行为不同
- java - Spring Boot 应用程序无法启动,并出现错误“应用程序需要一个 bean,但找到了 2 个”
- css - 将共享的 sass 变量导入每个 vue 组件是一种好习惯吗?