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

来源:互联网 发布:大数据傻瓜式 编辑:程序博客网 时间:2024/05/20 03:40

    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).