翻译:OPTIMIZING WITH QPIXMAPCACHE(使用QPIXMAPCACHE进行优化

转载 2012年03月26日 10:15:59

    By Mark Summerfield(原文请见:http://doc.qt.nokia.com/qq/qq12-qpixmapcache.html)

    TroyCheng新博客:

    http://troychengspace.appspot.com/2010/07/7/linux16.html

    Widget在反复绘制图像的时候常常导致程序不响应,针对这个问题,这篇文章主要介绍一下如何使用缓存机制来加速应用程序。

    [下载源代码]

    QPixmapCache 类提供了一个全局的QPixmap缓存,它的API由一组用于插入、查找和删除的静态函数组成,这些函数所使用的QString类型的key由用户定义。(还有Key类型文章里面没有提)

    我们通过两个示例来看看如何通过QPixmapCache来进行优化。

    具有较少状态的Widgets

    举一个简单的例子,对于一个具有较少状态的Widget,其中每一种状态又有自己的一套外观。例如, QRatioButton有两种状态,开和关,每种状态在第一次使用的时候都被存在缓存中。之后,无论程序使用了多少个ratio button,使用的都是在缓存中的外观副本,而不会再有重复的painting发生。


    Qt所使用的这种方法可以很容易的使用到自定义的Widget中。我们可以通过下面的这个简单的红绿灯的例子来进行说明。

      class Lights : public QWidget
        {
            Q_OBJECT
        
        public:
            Lights(QWidget *parent)
                : QWidget(parent), m_color(Qt::red),
                  m_diameter(80)
            {}
        
            QColor color() const { return m_color; }
            int diameter() const { return m_diameter; }
            QSize sizeHint() const
                { return QSize(m_diameter, 3 * m_diameter); }
        
        public slots:
            void setDiameter(int diameter);
        
        signals:
            void changed(QColor color);
        
        protected:
            void mousePressEvent(QMouseEvent *event);
            void paintEvent(QPaintEvent *event);
        
        private:
            QPixmap generatePixmap();
        
            QColor m_color;
            int m_diameter;
        };

    除了generagePixmap()函数外定义过程没有什么特别的地方。generatePixmap函数稍后介绍。

        void Lights::setDiameter(int diameter)
        {
            m_diameter = diameter;
            update();
            updateGeometry();
        }

    当diameter改变的时候,我们调用update函数去schedule一个paint event然后调用updateGeometry ( )来告诉任意一个layout manager这个widget的size hint已经发生了变化

        void Lights::mousePressEvent(QMouseEvent *event)
        {
            if (event->y() < m_diameter) {
                m_color = Qt::red;
            } else if (event->y() < 2 * m_diameter) {
                m_color = Qt::yellow;
            } else {
                m_color = Qt::green;
            }
        
            emit changed(m_color);
            update();
        }

    为完整性我们添加了一个鼠标点击事件,当用户点击Widget上的第一个图标时,我们将它的颜色改为红色,对黄色和绿色采取类似的操作。然后调用paint函数来调度一个paint事件。

        void Lights::paintEvent(QPaintEvent *)
        {
            QString key = QString("lights:%1:%2")
                                  .arg(m_color.name())
                                  .arg(m_diameter);
            QPixmap pixmap;
        
            if (!QPixmapCache::find(key, pixmap)) {
                pixmap = generatePixmap();
                QPixmapCache::insert(key, pixmap);
            }
            bitBlt(this, 0, 0, &pixmap);
        }

    在paint事件中,我们首先生成每一个外观所需要的key。在这个例子中,外观依赖两种因素:需要点亮哪种颜色以及Widget的直径。有这两个因素之后我们就可以创建一个空的pixmap。

    QPixmapCache:: find() 函数用过指定的key来查找pixmap,如果找到了匹配的缓存图片那么该函数会返回true并将缓存图片copy给第二个参数pixmap (一个非const引用),否则返回false并且忽略第二个参数。如果不需要在缓存中查找pixmap(例如第一次使用这种颜色和直径的组合),我们将会生成所需的pixmap并将它插入到Qt的全局缓存空间中去。在这两种情况中,用作外观的pixmap最终都会绘制到widget的表面上。

        QPixmap Lights::generatePixmap()
        {
            int w = m_diameter;
            int h = 3 * m_diameter;
        
            QPixmap pixmap(w, h);
            QPainter painter(&pixmap, this);
        
            painter.setBrush(darkGray);
            painter.drawRect(0, 0, w, h);
        
            painter.setBrush(
                    m_color == Qt::red ? Qt::red
                                       : Qt::lightGray);
            painter.drawEllipse(0, 0, w, w);
        
            painter.setBrush(
                    m_color == Qt::yellow ? Qt::yellow
                                          : Qt::lightGray);
            painter.drawEllipse(0, w, w, w);
        
            painter.setBrush(
                    m_color == Qt::green ? Qt::green
                                         : Qt::lightGray);
            painter.drawEllipse(0, 2 * w, w, w);
        
            return pixmap;
        }

    最终,这是绘制widget外观的代码。首先创建一个有正确大小的pixmap然后创建一个painter用来绘制pixmap。接着,在pixmap的表面绘制一个暗灰色的矩形框,然后,设置brush并且画一个适当大小的圆圈。

    通过使用QPixmapCache,我们确保无论有多少个Light类,在缓存未满之前,我们都只需要对每种颜色和直径的组合绘制一次

    计算开销较大的Painting操作

    对于一些自定义的Widget,通常它的状态无法确定,例如,一个图片Widget,如果会与用户绘制的每一个图片都做缓存的话,可能会得不偿失,因为用户可能不会经常去改变它的图片或者不会绘制同样的图片。

    但是,对于数据不改变的情况,缓存也会有一定好处,例如如果Widget被遮挡然后需要重新呈现的时候,或者是在该Widget之上另需绘制其它图片的时候(例如绘制一个选择方框)。


    我们来看一个简单的例子,在一个Graph widget上画一系列点,它的定义和Light类很想,因此我们只关注一些必要的函数,从paint event handler函数开始:

        void Graph::paintEvent(QPaintEvent *)
        {
            if (m_width <= 0 || m_height <= 0)
                return;
        
            QPixmap pixmap;
        
            if (!QPixmapCache::find(key(), pixmap)) {
                pixmap = generatePixmap();
                QPixmapCache::insert(key(), pixmap);
            }
            bitBlt(this, 0, 0, &pixmap);
        }

    Paint event handler几乎和Light类的一样,除了产生key的函数是由另一个单独的函数提供。

        QString Graph::key() const
        {
            QString result;
            result.sprintf("%p", static_cast<const void *>(this));
            return result;
        }

    我们生成的key仅标示Graph实例。将整个Graph对象转换为字符串用作key就有点太impractical了。

        QPixmap Graph::generatePixmap()
        {
            QPixmap pixmap(m_width, m_height);
            pixmap.fill(this, 0, 0);
        
            QPainter painter(&pixmap, this);
            painter.drawPolyline(m_points);
            return pixmap;
        }

    上述代码首先创建了一个pixmap,这次使用填充的方式来初始化。然后以polygonal line的方式画些点。这个在Lights例子中的处理方式一样,最不同的地方在于setData函数。

        void Graph::setData(const QPointArray &points,
                            int width, int height)
        {
            m_points = points;
            m_width = width;
            m_height = height;
            QPixmapCache::remove(key());
            update();
        }

    当数据发生改变的时候,删除缓存的appearance然后产生一个paint event。当paint event发生的时候,使用key在缓存中查找不到副本,程序就会重新绘制apperance。因此,当用户创建graph的时候,这个graph会被缓存,当用户改变数据的时候会产生一个新的图并被缓存下来,原来的图会被丢弃掉。

    在缓存中自始至终只有Graph widget的一个实例,因此,除非当用户重新绘图的时候,其它的类似隐藏然后显示等等状态都不会造成重新绘制图像,也就避免了不需要的画图的开销。

    另一个可选的方案是使用一个QPixmap对象作为Graph对象的成员,用于缓存Pixmap(参见 C++ GUI programming with Qt 3 中的Double Buffering一节)。但是这种方案的缺点也非常明显,如果application创建了许多实例,这种方式会很快耗尽pixmap的内存空间。使用全局的QPixmapCache要安全一些,because QPixmapCache enforces an upper limit on the cumulative size of stored items (1 MB by default).


相关文章推荐

9. Optimizing and satisficing metrics 优化指标和满足指标(《MACHINE LEARNING YEARNING》翻译)

优化指标和满足指标这是组合多个评估指标的另一种方法。假设你同时关心算法的准确率和运行时间。你需要在下面三个分类器中进行选择: 这里如果将准确率和运行时间组合为单个评估指标会看起来不太自然,例如:...

Optimizing Latent Dirichlet Allocation with Variational Inference

Optimizing Latent Dirichlet Allocation with Variational InferenceIn the last article, we discussed h...

Optimizing Code with GCC

现在的编译器越来越聪明,功能越来越强,从简单的函数内联,到复杂的寄存器分析,一系列代码革命使程序运行得越来越快。大多数时候,更快比更小重要,因为磁盘空间和内存都变得便宜了。但是在嵌入式系统里,更小和更...

Optimizing DLL Load Time Performance(优化 DLL 加载时间性能)

Optimizing DLL Load Time PerformanceMatt PietrekDownload the code for this article: UnderTheHood0500...
  • gxj1680
  • gxj1680
  • 2011年02月14日 16:42
  • 1142

Tips on Optimizing SQL Server Composite Indexes(SQL server 复合索引优化提示)

SQL server 复合索引优化提示

Unity3D优化之Optimizing the Size of the Built iOS Player

Optimizing the Size of the Built iOS Player  优化内置 iOS Player的尺寸   The two main ways of reducing th...

Uniy3D优化之Optimizing Graphics Performance (一)

Optimizing Graphics Performance 优化图形性能 Desktop 桌面游戏   Good performance is critical to the s...

optimizing C++ C++优化-英文版

  • 2011年12月22日 12:09
  • 879KB
  • 下载

[中英文对照]android Designing for TV(一) ------ Optimizing Layouts for TV 优化电视布局

When your application is running on a television set, you should assume that the user is sitting abo...

优化图形性能 Optimizing Graphics Performance

Good performance is critical to the success of many games. Below are some simple guidelines for maxi...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:翻译:OPTIMIZING WITH QPIXMAPCACHE(使用QPIXMAPCACHE进行优化
举报原因:
原因补充:

(最多只允许输入30个字)