Qt:可视化大型二维数组的最有效方法是什么?
Qt: what is the most efficient way to vizualize a large 2D array?
我正在移植一个使用 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 的图形项目要慢得多。我的下一个想法是创建某种视口,这将消除使用图形项目的需要。它可能是一些 canvas (QImage),每次更新数组时我都会清除并绘制它。但是你推荐什么?
2次次尝试
在第 1st 次尝试并不令人满意之后,我遵循了 :
的提示
I mean that drawing a character is very expensive operation. So I suggest that you draw each possible character (I assume there are not many of them) to some small image in memory. And than do bitblt (if you do not understand some word, then google for it), i.e. copy blocks of bytes to the final QImage. It will be much faster than painting texts.
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();
}
输出:
我用全屏window(2560×1280)做了同样的测试。性能仍然具有可比性。 (截取的GIF动图太大,这里无法上传。)
与V.K的提示相反,我使用了QPixmap
。 QImage
也可以用 QPainter
修改。还有一个 QPainter::drawImage() 可用。
1st 尝试
我的第一次尝试是在 QAbstractScrollArea
派生的 class 的 paintEvent()
中打印字符。因此,我小心地跳过了视图区域之外的所有行和列。乍一看,性能似乎还不错,但在全屏 window 大小的情况下,该方法显示出弱点。拖动滚动条时,输出明显滞后。
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();
}
输出:
我正在移植一个使用 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 的图形项目要慢得多。我的下一个想法是创建某种视口,这将消除使用图形项目的需要。它可能是一些 canvas (QImage),每次更新数组时我都会清除并绘制它。但是你推荐什么?
2次次尝试
在第 1st 次尝试并不令人满意之后,我遵循了
I mean that drawing a character is very expensive operation. So I suggest that you draw each possible character (I assume there are not many of them) to some small image in memory. And than do bitblt (if you do not understand some word, then google for it), i.e. copy blocks of bytes to the final QImage. It will be much faster than painting texts.
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();
}
输出:
我用全屏window(2560×1280)做了同样的测试。性能仍然具有可比性。 (截取的GIF动图太大,这里无法上传。)
与V.K的提示相反,我使用了QPixmap
。 QImage
也可以用 QPainter
修改。还有一个 QPainter::drawImage() 可用。
1st 尝试
我的第一次尝试是在 QAbstractScrollArea
派生的 class 的 paintEvent()
中打印字符。因此,我小心地跳过了视图区域之外的所有行和列。乍一看,性能似乎还不错,但在全屏 window 大小的情况下,该方法显示出弱点。拖动滚动条时,输出明显滞后。
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();
}
输出: