如何解决QPixmap(大型绘图工作)的性能问题?

时间:2014-08-20 19:31:47

标签: c++ performance qt qpixmap

我正在编写一个小地图编辑器(带有矩形图块),我需要一种方法来绘制大量图像或一个大图像。应用程序很简单:用鼠标在空白屏幕上绘制图像,完成后可以保存。图块由小图片组成。

我尝试了几种显示瓷砖的解决方案:

  1. 每个磁贴都有自己的QGraphicsItem(直到你有一个 1000x1000地图)
  2. 每个图块都绘制在一个大的QPixmap上(这意味着一个非常大的图像。例如:1000x100的地图,每个图块的大小为32x32意味着QPixmap的大小为32000x32000。这是QPainter的问题。)
  3. 当前的解决方案:遍历宽度& TileLayer的高度,并使用painter->drawPixmap()绘制每个单个图块。我paint()的{​​{1}}方法如下所示:

    TileLayer
  4. 适用于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的最大尺寸之间使用线性插值,但它无法正常工作。

    更新2

    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);
                }
        }
    }
    

    尝试对示例代码进行反向工程,但仍然无法正常运行。

    更新3

    我再次使用线性插值尝试(更新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);
    

    那就是它。现在它有效。

1 个答案:

答案 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)];
      }
 }

希望有所帮助。