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

http://blog.163.com/pz124578@126/blog/static/23522694201282643611633/

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
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 
#include
#include
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
#include
#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("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 }

如图所示:
 

抓取部分组件

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

{
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测试,通过播放视频测试。


你可能感兴趣的:(qml)