需要明白的是 Qt 实现图片等显示需要一个所谓的 QPaintDevice,我们通过 QPaintEngine 在上面作图,而 QPainter 实现比较高层次的作图动作。对于一些特殊的应用,比如需要渲染 OpenGL 场景,原始的 QPaintEngine 并不适用,注意一般说来 QPaintEngine 是一个 static 对象,我们看看相关一些定义。首先了解一下下面这一段代码,来自 qglobal.h
#define Q_GLOBAL_STATIC(TYPE, NAME) \
static TYPE *NAME() \
{ \
static TYPE this_##NAME; \
static QGlobalStatic<TYPE > global_##NAME(&this_##NAME); \
return global_##NAME.pointer; \
}
template <typename T>
class QGlobalStatic
{
public:
T *pointer;
inline QGlobalStatic(T *p) : pointer(p) { }
inline ~QGlobalStatic() { pointer = 0; }
};可见这定义了一个函数,该函数内有一个 static 对象,叫 this_NAME,然后通过模板类产生一个指向该对象的指针。我们在 qwidget.h 里面看到Q_GLOBAL_STATIC(QX11PaintEngine, qt_widget_paintengine),在 qgl.cpp 中发现
Q_GLOBAL_STATIC(QGL2PaintEngineEx, qt_gl_engine) Q_GLOBAL_STATIC(QOpenGLPaintEngine, qt_gl_engine)因此,实际上我们平时在 Linux 下使用的 Engine 是 qt_widget_paintengine(QX11PaintEngine),而有两种 OpenGL 的 Engine。
那么使用不同的 Engine 获益主要在于某些 engine 可以通过特殊的硬件,如显卡,对特别的应用实现高速的渲染。因此,普通的 QWidget 如果想具有 OpenGL 的渲染能力需要将其 paint engine 换成 qt_gl_engine,这是通过构造函数里面
QGLWidget::QGLWidget(QWidget *parent, const QGLWidget* shareWidget, Qt::WindowFlags f)
: QWidget(*(new QGLWidgetPrivate), parent, f | Qt::MSWindowsOwnDC)
{
Q_D(QGLWidget);
setAttribute(Qt::WA_PaintOnScreen);
setAttribute(Qt::WA_NoSystemBackground);
setAutoFillBackground(true); // for compatibility
d->init(new QGLContext(QGLFormat::defaultFormat(), this), shareWidget);
}实现的,这里 QGLContext 在该 QPainterDevice 上建立了 OpenGL 的 context,这是 enable OpenGL 的重要步骤,这个实现也是通过 QGLContextPrivate 的 init() 方法实现的。
有了这些底层的准备工作之后,我们后面还需要做很多事情。虽然 QWidget 提供了一个基本的舞台,但是并没有很多专门性的用途,因此,很多我们使用的 component 都是从 QWidget 里面继承的,比如 QLabel,它本身是一个可以显示 image 的 component。我们可以借鉴它的代码,
void QLabel::setPixmap(const QPixmap &pixmap)
{
Q_D(QLabel);
if (!d->pixmap || d->pixmap->cacheKey() != pixmap.cacheKey()) {
d->clearContents();
d->pixmap = new QPixmap(pixmap);
}
if (d->pixmap->depth() == 1 && !d->pixmap->mask())
d->pixmap->setMask(*((QBitmap *)d->pixmap));
d->updateLabel();
}注意这里使用了一个 QLablePrivate 类作为 QPixmap 的存储,最后调用 QLabelPrivate::updateLabel() 实际上是重画这个 QWidget。那么如何重画呢,其实这时候需要响应所谓的 QPaintEvent,这会调用 QWidget::paintEvent(),我们后面来看看所谓的 event 到底是什么。因此实现一个自己需要的样子的 widget 就只需要把这个 protected 虚函数 overriden 即可。
那么,对于比如说 QGLWidget 而言,如果需要渲染场景、改变场景,是否应该对应到对应到 paint event 呢?我们注意到该 widget 声明中,
protected:
virtual void initializeGL();
virtual void resizeGL(int w, int h);
virtual void paintGL();
void paintEvent(QPaintEvent*);我们来看看是怎么回事,在实现文件中,void QGLWidget::paintEvent(QPaintEvent *)
{
if (updatesEnabled()) {
glDraw();
updateOverlayGL();
}
}可见直接调用 OpenGL 函数重绘场景,而另外三个函数提供给需要自己创建自己的 widget 的用户继承该类进行 overridden。因此,我们得出一个基本的事情,就是当我们收到 paint event 时,正常情况是调用 paint engine 重绘,一旦我们需要改变一个 QWidget 对其 paintEvent() 进行重载,QGLWidget 提供的另外三个函数只是为了方便实现一些简单的功能留下来的“钩子”,比如 initializeGL 将在 QGLWidget::glInit() 中调用,初始化 GL 环境后创建初始场景,QGLWidget::glDraw() 中调用 paintGL(),因此响应 paint event 的时候我们可以通过 override paintGL() 更改场景。那么实现 OpenGL 动画的关键就是通过几个参数确定状态,然后在 paintGL() 里面将场景更新,而在一个 QTimer 中依照一定的时间间隔更新参数。
现在我们实现一个简单的显示图片的的 widget,根据前面的知识,我们创建如下的 qmake 配置文件,
TEMPLATE = app
CONFIG += qt debug_and_release
DEPENDPATH += .
INCLUDEPATH += .
TARGET = test_imagewidget
SOURCES = main.cpp imagewidget.cpp
HEADERS = imagewidget.hpp
debug {
DESTDIR = debug
}
release {
DESTDIR = release
}
DESTDIR_TARGET = $$DESTDIR首先我们看看我们的 ImageWidget 类,#ifndef IMAGEWIDGET_HPP
#define IMAGEWIDGET_HPP
#include <QWidget>
#include <QImage>
class ImageWidget : public QWidget {
Q_OBJECT
QImage *img ;
protected:
void paintEvent( QPaintEvent * ) ;
public:
ImageWidget( QWidget* = 0 ) ;
void setImage( QImage * ) ;
const QImage *getImage() const ;
} ;
#endif我们通过一个 QImage 的指针用于作图,其实用 QPixmap 可能更好。下面是实现的代码,根据以上分析,我们修改 QWidget::paintEvent() 实现显示功能,#include "imagewidget.hpp"
#include <QPainter>
ImageWidget::ImageWidget( QWidget* pa ) : QWidget::QWidget( pa )
{
img = NULL ;
}
void
ImageWidget::setImage( QImage *image )
{
img = image ;
}
const QImage*
ImageWidget::getImage() const
{
return img ;
}
void
ImageWidget::paintEvent( QPaintEvent * )
{
if( img != NULL ) {
QPainter p( this ) ;
p.drawImage( QPoint( 0, 0 ), img -> scaled( size() ) ) ;
}
}
值得注意的是如果我们考虑实现 seam carving 这种东西,我们可能需要对 resizeEvent 进行 override。最后下面是主调文件,#include <QApplication>
#include <QImage>
#include "imagewidget.hpp"
int
main( int argc, char *argv[] )
{
QApplication app( argc, argv ) ;
QImage img ;
if( argc == 2 )
img.load( argv[1] ) ;
else
return 1 ;
ImageWidget widget ;
widget.setImage( &img ) ;
widget.show() ;
return app.exec() ;
}
那么很显然,如果我们有一个图片序列,可以用 QTimer 调用 ImageWidget::setImage() 设置图片,然后就可以获得我们需要的结果了。因此从一个高层角度来看,我们去实现一个 image buffer,然后通过 QTimer 从中获得需要的图片,另一个线程更新该 buffer 即可。但是事实上 video 是这样的么?
No comments:
Post a Comment