需要明白的是 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