我正在编写一个小地图编辑器(带有矩形图块),我需要一种方法来绘制大量图像或一个大图像。应用程序很简单:用鼠标在空白屏幕上绘制图像,完成后可以保存。图块由小图片组成。
我尝试了几种显示瓷砖的解决方案:
QGraphicsItem
(直到你有一个
1000x1000地图)QPixmap
上(这意味着一个非常大的图像。例如:1000x100的地图,每个图块的大小为32x32意味着QPixmap
的大小为32000x32000。这是QPainter
的问题。) 当前的解决方案:遍历宽度& TileLayer
的高度,并使用painter->drawPixmap()
绘制每个单个图块。我paint()
的{{1}}方法如下所示:
TileLayer
适用于100x100甚至1000x100瓷砖的小地图。但不适用于1000x1000。整个应用程序开始滞后,这当然是因为我有一个非常昂贵的for循环。为了使我的工具有用,我需要能够制作至少 1000x1000瓦片地图而不会有滞后。有谁知道我能做什么?我应该如何表示瓷砖?
我更改了以下内容:只绘制超过小地图窗口大小的地图,并为每个图块绘制单个像素。这是我现在的渲染功能:
void TileLayerGraphicsItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option,QWidget* /*widget*/)
{
painter->setClipRect(option->exposedRect);
int m_width=m_layer->getSize().width();
int m_height=m_layer->getSize().height();
for(int i=0;i<m_width;i++)
{
for(int j=0;j<(m_height);j++)
{
Tile* thetile=m_layer->getTile(i,j);
if(thetile==NULL)continue;
const QRectF target(thetile->getLayerPos().x()*thetile->getSize().width(),thetile->getLayerPos().y()*thetile->getSize().height(),thetile->getSize().width(),thetile->getSize().height());
const QRectF source(0, 0, thetile->getSize().width(), thetile->getSize().height());
painter->drawImage(target,*thetile->getImage(),source);
}
}}
}
这种方式不正确,但速度非常快。问题是我的 lerp (线性插值)函数,以获取正确的图块来绘制像素。 当我迭代小地图像素时,有没有人有更好的解决方案来获得正确的图块?目前我在0和tilemap的最大尺寸之间使用线性插值,但它无法正常工作。
void RectangleRenderer::renderMinimapImage(QPainter* painter, TileMap* map,QSize windowSize)
{
for(int i=0;i<map->getLayers().size();i++)
{
TileLayer* currLayer=map->getLayers().at(i);
//if the layer is small draw it completly
if(windowSize.width()>currLayer->getSize().width()&&windowSize.height()>currLayer->getSize().height())
{
...
}
else // This is the part where the map is so big that only some pixels are drawn!
{
painter->fillRect(0,0,windowSize.width(),windowSize.height(),QBrush(QColor(map->MapColor)));
for(float i=0;i<windowSize.width();i++)
for(float j=0;j<windowSize.height();j++)
{
float tX=i/windowSize.width();
float tY=j/windowSize.height();
float pX=lerp(i,currLayer->getSize().width(),tX);
float pY=lerp(j,currLayer->getSize().height(),tY);
Tile* thetile=currLayer->getTile((int)pX,(int)pY);
if(thetile==NULL)continue;
QRgb pixelcolor=thetile->getImage()->toImage().pixel(thetile->getSize().width()/2,thetile->getSize().height()/2);
QPen pen;
pen.setColor(QColor::fromRgb(pixelcolor));
painter->setPen(pen);
painter->drawPoint(i,j);
}
}
}
尝试对示例代码进行反向工程,但仍然无法正常运行。
我再次使用线性插值尝试(更新1)。当我看到代码时,我看到了错误:
//currLayer->getSize() returns how many tiles are in the map
// currLayer->getTileSize() returns how big each tile is (32 pixels width for example)
int raw_width = currLayer->getSize().width()*currLayer->getTileSize().width();
int raw_height = currLayer->getSize().height()*currLayer->getTileSize().height();
int desired_width = windowSize.width();
int desired_height = windowSize.height();
int calculated_width = 0;
int calculated_height = 0;
// if dealing with a one dimensional image buffer, this ensures
// the rows come out clean, and you don't lose a pixel occasionally
desired_width -= desired_width%2;
// http://qt-project.org/doc/qt-5/qt.html#AspectRatioMode-enum
// Qt::KeepAspectRatio, and the offset can be used for centering
qreal ratio_x = (qreal)desired_width / raw_width;
qreal ratio_y = (qreal)desired_height / raw_height;
qreal floating_factor = 1;
QPointF offset;
if(ratio_x < ratio_y)
{
floating_factor = ratio_x;
calculated_height = raw_height*ratio_x;
calculated_width = desired_width;
offset = QPointF(0, (qreal)(desired_height - calculated_height)/2);
}
else
{
floating_factor = ratio_y;
calculated_width = raw_width*ratio_y;
calculated_height = desired_height;
offset = QPointF((qreal)(desired_width - calculated_width)/2,0);
}
for (int r = 0; r < calculated_height; r++)
{
for (int c = 0; c < calculated_width; c++)
{
//trying to do the following: use your code to get the desired pixel. Then divide that number by the size of the tile to get the correct pixel
Tile* thetile=currLayer->getTile((int)((r * floating_factor)*raw_width)/currLayer->getTileSize().width(),(int)(((c * floating_factor)*raw_height)/currLayer->getTileSize().height()));
if(thetile==NULL)continue;
QRgb pixelcolor=thetile->getImage()->toImage().pixel(thetile->getSize().width()/2,thetile->getSize().height()/2);
QPen pen;
pen.setColor(QColor::fromRgb(pixelcolor));
painter->setPen(pen);
painter->drawPoint(r,c);
}
}
应该是:
float pX=lerp(i,currLayer->getSize().width(),tX);
float pY=lerp(j,currLayer->getSize().height(),tY);
那就是它。现在它有效。
答案 0 :(得分:1)
这显示了如何正确地完成它。您可以使用一定级别的细节(lod)变量来确定如何根据缩放比例绘制屏幕上当前可见的元素。
http://qt-project.org/doc/qt-5/qtwidgets-graphicsview-chip-example.html
也不要遍历所有可见的元素,但只能浏览那些已经改变的元素,以及那些只有当前可见的元素。
您使用的下一个选项是其他一些手动缓存,因此您不必经常重复迭代O(n ^ 2)。
如果您无法针对QGraphicsView / QGraphicsScene优化它......那么OpenGL可能就是您可能想要研究的内容。它可以直接在显卡上进行大量的绘图和缓存,因此您不必担心它。
更新: 在工作线程上推送对QImage的更改可以让您缓存和更新缓存,同时让程序的其余部分保持响应,然后使用排队连接返回GUI线程以将QImage绘制为Pixmap。 / p>
如果你问得好,QGraphicsView会告诉你哪些瓷砖是可见的:
http://qt-project.org/doc/qt-5/qgraphicsview.html#items-5
更新2: http://qt-project.org/doc/qt-5/qtwidgets-graphicsview-chip-chip-cpp.html
您可能需要调整项目允许的缩小范围以测试此功能...
在哪里
const qreal lod = option->levelOfDetailFromTransform(painter->worldTransform());
if (lod < 0.2) {
if (lod < 0.125) {
painter->fillRect(QRectF(0, 0, 110, 70), fillColor);
return;
}
QBrush b = painter->brush();
painter->setBrush(fillColor);
painter->drawRect(13, 13, 97, 57);
painter->setBrush(b);
return;
}
添加如下内容:
if(lod < 0.05)
{
// using some sort of row/col value to know which ones to not draw...
// This below would only draw 1/3 of the rows and 1/3 of the column
// speeding up the redraw by about 9x.
if(row%3 != 0 || col%3 != 0)
return;// don't do any painting, return
}
更新3:
抽取示例:
// How to decimate an image to any size, properly
// aka fast scaling
int raw_width = 1000;
int raw_height = 1000;
int desired_width = 300;
int desired_height = 200;
int calculated_width = 0;
int calculated_height = 0;
// if dealing with a one dimensional image buffer, this ensures
// the rows come out clean, and you don't lose a pixel occasionally
desired_width -= desired_width%2;
// http://qt-project.org/doc/qt-5/qt.html#AspectRatioMode-enum
// Qt::KeepAspectRatio, and the offset can be used for centering
qreal ratio_x = (qreal)desired_width / raw_width();
qreal ratio_y = (qreal)desired_height / raw_height();
qreal floating_factor = 1;
QPointF offset;
if(ratio_x < ratio_y)
{
floating_factor = ratio_x;
calculated_height = raw_height*ratio_x;
calculated_width = desired_width;
offset = QPointF(0, (qreal)(desired_height - calculated_height)/2);
}
else
{
floating_factor = ratio_y;
calculated_width = raw_width*ratio_y;
calculated_height = desired_height;
offset = QPointF((qreal)(desired_width - calculated_width)/2);
}
for (int r = 0; r < calculated_height; r++)
{
for (int c = 0; c < calculated_width; c++)
{
pixel[r][c] = raw_pixel[(int)(r * floating_factor)*raw_width][(int)(c * floating_factor)];
}
}
希望有所帮助。