×
Namespaces

Variants
Actions

利用OpenGL特性抓取QML屏幕并实现其动态效果

From Nokia Developer Wiki
Jump to: navigation, search
Article Metadata

代码示例
文章
renlin 在 06 Mar 2012 创建
最后由 hamishwillee 在 26 Jul 2012 编辑

Contents

Introduction

深度解析QML的实现原理一文中,作者阐述了QML最终如何通过OpenGL呈现内容的,根据其实现原理,本文将提供一种简单的抓取屏幕并实现其动画效果的方法。因为要加载到窗口中的内容都是从FrameBuffer中读取的,在FrameBuffer中存取了每个像素点的值,QGLWidget提供了grabFrameBuffer()方法来获取FrameBuffer中的内容并以QImage的形式返回,我们实现一个QDeclarativeItem的子类,让其来管理这个QImage并通过Paint()方法将其呈现,并在QML中使用这个QDeclarativeItem的子类,达到动画效果。 在屏幕是动态或者屏幕中有大量的元素时,获取整个屏幕在瞬间的图像是困难的,有的开发者在使用QPixMap::GrabWindow()方法时,抓取静态的屏幕没问题,而在播放视频或者动画时就不能正确捕捉到屏幕,所以这里推荐使用本文提供的方法。通过这种方法可以减少内存消耗,提高程序的运行效率。

实现

QML实现

用QML实现一个列表和一个按钮,用一幅图片做背景。

import QtQuick 1.1
import com.nokia.symbian 1.1
Rectangle {
id: mainPage
width:400
height:600
Image{
id:bg
source:"bg.jpg"
width:parent.width
height: parent.height
}
Rectangle{
id:button
color:"yellow"
anchors.top:parent.top
width:parent.width
height:50
Text{
anchors.centerIn: parent
text:"click me"
}
MouseArea{
anchors.fill: parent
onClicked: {
console.log("button clicked")
}
}
}
ListView{
id:contactView
width:parent.width
height:parent.height-button.height
spacing: 10
anchors.top:button.bottom
anchors.topMargin: 20
anchors.left:parent.left
anchors.leftMargin: 20
model:listModel
delegate:myDelegate
}
ListModel{
id:listModel
ListElement{ name:"Andrew";number:"13899003000"}
ListElement{ name:"Henry";number:"13899000300"}
ListElement{ name:"LyLi";number:"13899000400"}
ListElement{ name:"Eidewa";number:"13899000500"}
ListElement{ name:"Antony";number:"13899006000"}
ListElement{ name:"Lee";number:"13899000070"}
 
}
Component{
id:myDelegate
Rectangle{
width:320
height:60
color:"lightblue"
radius: 10
Text{
font.pixelSize: 20
anchors.centerIn: parent
text: name + ": " + number
color:"white"
}
}
}
}

C++实现

实现一个QGLWidget的子类,以便我们能通过它的实例来获得屏幕的内容。

myglwidget.h
----------------------------------------------------
#include <QGLWidget>
class MyGLWidget : public QGLWidget
{
public:
static MyGLWidget* instance();
void initializeGL();
void paintGL();
void resizeGL(int w, int h);
private:
MyGLWidget();
static MyGLWidget * m_self;
};
myglwidget.cpp
----------------------------------------------------------
#include "myglwidget.h"
 
MyGLWidget* MyGLWidget::m_self=NULL;
MyGLWidget::MyGLWidget()
{
}
 
MyGLWidget * MyGLWidget::instance()
{
if(m_self==NULL)
{
m_self=new MyGLWidget();
 
}
return m_self;
}
 
void MyGLWidget::initializeGL()
{
QGLWidget::initializeGL();
}
void MyGLWidget::resizeGL(int w, int h)
{
QGLWidget::resizeGL(w,h);
 
}
void MyGLWidget::paintGL()
{
 
QGLWidget::paintGL();
}

需要实现一个QDeclarativeItem的子类来获取并呈现抓取的屏幕内容。

#include <QGLWidget>
#include <QImage>
#include <QDeclarativeItem>
class MyScreenShot : public QDeclarativeItem
{
Q_OBJECT
explicit MyScreenShot();
void componentComplete();
Q_INVOKABLE void imageCapture(/*QDeclarativeItem* item*/);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *);
private:
QImage m_capture;
// float x;
// float y;
};
 
myscreenshot.cpp
-----------------------------------------------------------------------------------------------------
#include "myscreenshot.h"
#include <QApplication>
#include <QDebug>
#include "myglwidget.h"
MyScreenShot::MyScreenShot()
{
setFlag(QGraphicsItem::ItemHasNoContents,false);
}
 
void MyScreenShot::imageCapture(/*QDeclarativeItem* item*/)
{
MyGLWidget* glw = MyGLWidget::instance();
glw->makeCurrent();
 
m_capture= glw->grabFrameBuffer();//返回的QImage
}
 
void MyScreenShot::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
painter->drawImage(0,0,m_capture);//画出抓取的屏幕
}
 
void MyScreenShot::componentComplete()
{
imageCapture();
}

在QML中调用使用MyScreenShot,需要注册这个类型,可以参考使用C++创建新的QML类型

 qmlRegisterType<MyScreenShot>("com.digia",1,0,"ScreenShot");

要使QML的内容通过OPenGL来呈现,我们需要在main函数中定义呈现方式为opengl,设置viewport为MyGLWidget

  QApplication::setGraphicsSystem("opengl");
......
viewer.setViewport(MyGLWidget::instance());

通过Loader来动态加载ScreenShot,在QML中动态加载组件可以提高程序的运行性能,可以参考Wiki使用QML loader 元素动态创建加载QML组件以提高程序性能了解更详细的内容。

    Loader {
id:screenShotLoader
}
Component{
id:screenShot
ScreenShot{
id:screen
width:400
height:600
}
}

当按钮被点击时抓取屏幕,这时通过设置opacit属性来隐藏原来的组件:

onClicked: {
contactView.opacity = 0
button.opacity =0
screenShotLoader.sourceComponent = screenShot
animation.start() //开始动画效果
console.log("button clicked")
}

这里实现了屏幕向中间缩小的动画

 SequentialAnimation{
id:animation
NumberAnimation { target: screenShotLoader; property: "scale"; to: 0; duration: 2000 }
NumberAnimation { target: screenShotLoader; property: "opacity"; to: 0; duration: 2000 }

如图所示:
OpenGLScreenshot1.png OpenGLScreenShot2.png

抓取部分组件

有时我们需要抓取屏幕中某部分的内容来进行处理,这时要将QML中的组件对象传递给C++部分,这时的imageCapture方法需要参数QDeclarativeItem,即:

<void MyScreenShot::imageCapture(QDeclarativeItem* item)
{
MyGLWidget* glw = MyGLWidget::instance();
glw->makeCurrent();
 
m_capture= glw->grabFrameBuffer();
x = item->x();
y= item->y();
QPainter p;
p.drawImage(0,0,m_capture,x,y); //截取所需组件的部分
}

在QML中传递所需组件的id,不再通过Loader加载,而是直接通过imageCapture函数抓取并使用QPainter画出内容:

   screen.imageCapture(contactView)

源码下载

File:OpenGLScreenShot.zip File:Screnshot video.zip 通过N9测试,通过播放视频测试。

相关链接

Add categories below. Remove Category:Draft when the page is complete or near complete

This page was last modified on 26 July 2012, at 06:47.
209 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×