基于i.MX6UL平台,使用嵌入式QT实现电容屏的多点触控,前提是开发板的电容触摸屏驱动已经支持多点触控,并且驱动程序能通过事件方式向应用程序上报触控数据。关于电容触摸屏多点触控驱动程序的介绍,不在本章节描述之列。本章节重在实现多点触控的QT应用程序。
嵌入式QT电容屏多点触控应用程序,是基于qTUIO库和mtdev库来实现的。qTUIO是国外一位开发者编写的第三方库,这个第三方库在本地UDP套接字上实现了一个基于TUIO协议的监听器,然后把监听到的事件转发到QT的内部事件系统。mtdev是一个独立的库,它可以将来自内核的MT事件转换为时隙类型的B协议,这些MT事件可能来自内核中所有的MT设备。
总的来说,mtdev库可以把内核上报的多点触摸事件,通过mtdev2tuio后台应用程序(后面会讲到这个应用程序的移植),转换为TUIO协议,然后QT应用程序可以使用qTUIO库对内核上报的触摸事件进行处理。
以下是多点触控应用程序(涂鸦画板)运行时的界面:
界面描述:
(1)手写板界面,可以用多个手指同时涂鸦,最多支持5点同时触摸。
(2)Options里面有clear screen按钮,可以进行清屏。
(1) 在i.MX6UL开发板上移植 qTUIO 库,使其支持嵌入式QT多点触摸应用程序开发。
(2) 基于 qTUIO 库,编写一个QT多点触控应用程序。(手写板应用程序)
在i.MX6UL开发板上移植qTUIO库,需要依赖以下的第三方库:liblo,mtdev,mtdev2tuio,qTUIO。
通过以下网址,下载以上的第三方依赖库:
https://github.com/x29a/qTUIO
https://github.com/olivopaolo/mtdev2tuio
http://bitmath.org/code/mtdev/
http://liblo.sourceforge.net/
如果不能访问以上网址,也可以使用作者下载好的文件:
链接:https://pan.baidu.com/s/17l6LonZuh8U7axVRQ4TV-w 提取码:gvo7
(1)下载完成后,把下载好的文件,通过FileZilla工具上传到ubuntu系统,作者上传到ubuntu的/opt/work/tools/qt_multitouch/目录,以上依赖库的交叉编译也是基于该目录进行,把下载好的文件进行解压,并创建好各个依赖库的安装目录,如下图所示。
以上依赖库的交叉编译顺序是:liblo --> mtdev --> mtdev2tuio --> qTUIO。交叉编译完的文件,统一存放在 imx6ul-rootfs目录,方便移植到开发板。
(2)在ubuntu系统的命令行终端,执行以下命令,配置交叉编译环境。
#export CC=/opt/EmbedSky/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
#export CXX=/opt/EmbedSky/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++
(3)先交叉编译liblo库,执行以下命令进行交叉编译:
#cd /opt/work/tools/qt_multitouch/liblo-0.26
#export SKIP_RMDIR_CHECK=yes
#./configure --prefix=/opt/work/tools/qt_multitouch/liblo_install --host=arm
#make clean
#make
#make install
交叉编译成功后,会在 liblo_install 目录下生成bin、include、lib目录,如下图所示:
执行以下命令,把生成的三个目录都复制到imx6ul-rootfs文件夹:
#cp /opt/work/tools/qt_multitouch/liblo_install/* /opt/work/tools/qt_multitouch/imx6ul-rootfs/ -a
(4)然后,再交叉编译mtdev库,执行以下命令进行交叉编译:
#cd /opt/work/tools/qt_multitouch/mtdev-1.1.3
#./configure --prefix=/opt/work/tools/qt_multitouch/mtdev_install --host=arm
#make clean
#make
#make install
交叉编译成功后,会在 mtdev_install 目录下生成 bin、include、lib 目录,如下图所示:
执行以下命令,把生成的三个目录都复制到imx6ul-rootfs文件夹:
#cp /opt/work/tools/qt_multitouch/mtdev_install/* /opt/work/tools/qt_multitouch/imx6ul-rootfs/ -a
(5)接着,再交叉编译 mtdev2tuio-master 后台程序,mtdev2tuio 后台程序是连接mtdev与qTUIO的桥梁,在运行基于qTUIO库编写的应用程序前,需要先运行mtdev2tuio,与以上两个依赖库不同,mtdev2tuio-master源码里面已经有Makefile文件,交叉编译过程可以基于此Makefile进行,修改 /opt/work/tools/qt_multitouch/mtdev2tuio-master/Makefile 内容,如下图所示:
修改完Makefile文件后,执行以下命令,进行交叉编译:
#cd /opt/work/tools/qt_multitouch/mtdev2tuio-master
#make clean
#make
交叉编译完成后,会在源码目录下生成 mtdev2tuio 应用程序,如下图所示:
执行以下命令,把mtdev2tuio应用程序复制到 imx6ul-rootfs的bin目录:
#cd /opt/work/tools/qt_multitouch/mtdev2tuio-master/
#cp ./mtdev2tuio /opt/work/tools/qt_multitouch/imx6ul-rootfs/bin -a
(6)最后,交叉编译qTUIO库。qTUIO库是一个QT工程,其工程的.pro文件以及源码目录是:/opt/work/tools/qt_multitouch/qTUIO-master/src,可以使用qmake工具进行生成Makefile文件,再交叉编译。执行以下命令,进行交叉编译。
#cd /opt/work/tools/qt_multitouch/qTUIO-master
#rm -rf /opt/work/tools/qt_multitouch/qTUIO-master/lib/*
#cd src/
#/opt/EmbedSky/TQIMX6UL/TQ_COREB/imx6ul_rootfs/opt/qt-4.8.7/bin/qmake
#make clean
#make
交叉编译成功后,会在/opt/work/tools/qt_multitouch/qTUIO-master/lib文件夹下生成qTUIO的动态链接库,如下图所示:
执行以下命令,把这些动态链接库,复制到imx6ul-rootfs的lib目录:
#cd /opt/work/tools/qt_multitouch/qTUIO-master/
#cp ./lib/* /opt/work/tools/qt_multitouch/imx6ul-rootfs/lib -a
(7)完成以上工作后,imx6ul-rootfs目录下各个文件夹里面的内容分别如下图所示:
imx6ul-rootfs/bin目录的内容
imx6ul-rootfs/include目录的内容
imx6ul-rootfs/lib目录的内容
(8)最后,把imx6ul-rootfs/bin目录的文件复制到i.MX6UL开发板的/bin目录,把imx6ul-rootfs/include目录的文件复制到i.MX6UL开发板的/include目录,把imx6ul-rootfs/lib目录的文件复制到i.MX6UL开发板的/usr/lib目录,复制完成后,如下图所示:
(9)至此,qTUIO库已经全部移植到i.MX6UL开发板,开发板已经支持运行基于qTUIO库编写的多点触摸应用程序,为了方便应用程序运行前不用手动运行mtdev2tuio后台程序,我们把该后台程序设置为开机自启动,修改 /etc/embedsky_env文件,如下图所示:
/bin/mtdev2tuio /dev/input/cap_ts & 表示监控 /dev/input/cap_ts节点,该节点是电容触摸屏的设备节点,内核里面的的电容触摸事件都是通过该节点进行上报,应用程序通过该节点捕获电容触摸事件,然后进行处理。
(1)先用Qt Creator构建一个工程,命名为:010_multi_touch,关于如何构建工程,请参考“第一个嵌入式QT应用程序”的具体内容。与以往的程序不同,此次构建的工程,基类选择QMainWindow类。
(2)在编写代码前,我们需要把上一步骤移植好的qTUIO动态库和头文件复制到工程中,复制完成后,如下图所示:
其中,3rdparty目录直接从 /opt/work/tools/qt_multitouch/qTUIO-master/src 复制,并删除目录里面所有的非 .h 文件,只保留 .h 头文件。
(3)在工程目录上,右键 --> 添加库,如下图所示:
(4)选择添加“外部库”,点击下一步,在弹出的对话框中,平台选择Linux,库文件选择刚刚添加进工程的libqTUIO.so,然后点击下一步,如下图所示:
(5)动态链接库添加完成后,010_multi_touch.pro文件如下图所示:
(6)为了方便在界面上进行涂鸦绘画,我们创建一个ScribbleArea类,这个类继承于QWidget类,包含了清屏函数,以及重构的绘画事件函数,窗口大小调整函数,系统事件捕获函数,等等。类的具体内容如下图所示:
class ScribbleArea : public QWidget
{
Q_OBJECT
public:
ScribbleArea(QWidget *parent = 0);
public slots:
void clearImage(); //清屏函数
protected:
void paintEvent(QPaintEvent *event); //绘画事件函数
void resizeEvent(QResizeEvent *event); //窗口大小调整的事件函数
bool event(QEvent *event); //系统事件处理函数
private:
void resizeImage(QImage *image, const QSize &newSize); //重新调整窗口大小
QList myPenColors; //画笔颜色
QImage image; //窗口图像
};
(7)在ScribbleArea类的构造函数中,我们设置该类对象可以捕获触摸事件,并初始化设置画笔的颜色,该类的构造函数如下图所示:
ScribbleArea::ScribbleArea(QWidget *parent) : QWidget(parent)
{
setAttribute(Qt::WA_AcceptTouchEvents); //接收touch事件
setAttribute(Qt::WA_StaticContents); //设置静态的窗口部件
myPenColors
<< QColor("green")
<< QColor("purple")
<< QColor("red")
<< QColor("blue")
<< QColor("yellow")
<< QColor("pink")
<< QColor("orange")
<< QColor("brown")
<< QColor("grey")
<< QColor("black");
}
(8)当系统有触摸事件上报时,应用程序会调用bool ScribbleArea::event(QEvent *event)函数,实际上,这个被重构的event函数不仅仅只在触摸事件发生时被调用,对于一般的系统事件,这个函数也会被调用,在这个event函数里面,我们只处理QEvent::TouchBegin,QEvent::TouchUpdate,QEvent::TouchEnd事件,其他时间忽略不进行处理。event函数的内容如下图所示:
//系统事件处理函数
bool ScribbleArea::event(QEvent *event)
{
switch (event->type())
{
case QEvent::TouchBegin: //触摸开始事件
case QEvent::TouchUpdate: //触摸更新事件
case QEvent::TouchEnd: //触摸结束事件
{
//获取触摸事件中所有的触摸点列表
QList touchPoints = static_cast(event)->touchPoints();
foreach (const QTouchEvent::TouchPoint &touchPoint, touchPoints)
{
switch (touchPoint.state())
{
case Qt::TouchPointStationary:
// don't do anything if this touch point hasn't moved
continue;
default:
{
QRectF rect = touchPoint.rect(); //获取触摸点的矩形区域
if (rect.isEmpty()) //如果矩形区域空白
{
qreal diameter = qreal(20);
rect.setSize(QSizeF(diameter, diameter)); //重置触摸点的大小
rect.setX(touchPoint.pos().x() - 0); //重置触摸点的x,y位置
rect.setY(touchPoint.pos().y() - 0);
}
QPainter painter(&image);
painter.setPen(Qt::NoPen); //不使用画笔
painter.setBrush(myPenColors.at(touchPoint.id() % myPenColors.count())); //使用画刷
painter.drawEllipse(rect); //根据给定的矩形区域,绘制一个椭圆点
painter.end(); //绘制完成
int rad = 2;
update(rect.toRect().adjusted(-rad,-rad, +rad, +rad)); //更新绘制的区域
}
break;
}
}
break;
}
default:
return QWidget::event(event);
}
return true;
}
在该函数里面,只处理三种触摸事件,先获取触摸事件里面的所有触摸点,然后根据该触摸点的坐标和矩形大小,绘制椭圆点,然后调用update函数更新绘制的区域,update函数被调用的时候,void ScribbleArea::paintEvent(QPaintEvent *event)事件函数就会被触发。
(9)图形会在void ScribbleArea::paintEvent(QPaintEvent *event)事件函数里面绘制,函数的内容如下图所示:
//paintEvent重绘事件,在调用QWidget::update()函数后被触发
void ScribbleArea::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
const QRect rect = event->rect(); //构造一个矩形
painter.drawImage(rect.topLeft(), image, rect); //重新绘制这个矩形
}
(10)在MainWindow的窗体构造函数里实例化一个ScribbleArea对象,并把该对象设置为MainWindow的中心窗体,然后绘制一个菜单栏,并重置窗体的大小。代码如下图所示:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
scribbleArea = new ScribbleArea; //实例化一个scribbleArea对象
setCentralWidget(scribbleArea); //设置中心窗体
createActions(); //设置清屏按钮
createMenus(); //创建菜单栏
setWindowTitle(tr("Finger Paint")); //修改窗体title
resize(800, 480); //重置窗体大小
}
(11)在应用程序的入口int main(int argc, char *argv[])函数里面,使用QTuio类实例化一个对象qTUIO,该对象引用了MainWindow类对象进行实例化,然后运行qTUIO.run()函数,这样,qTUIO就可以监听系统的多点触摸事件,当系统有多点触摸事件上报时,qTUIO会把这些事件转换为Qt内部可以处理的系统事件,方便Qt应用程序进行处理,main函数的内容如下图所示:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow window;
window.show();
QTuio qTUIO(&window);
qTUIO.run();
return app.exec();
}
(12)至此,整个多点触摸应用程序已经开发完成,编译下载到开发板,应用程序运行的现象如下图所示。
关于qTUIO库的实现机制和实现原理,作者没有进行太多深入的研究和分析,网上对于该库的讲解资料也比较少。根据移植过程的分析,大概可以推断系统的MultiTouch事件会被mtdev库捕获,然后这些事件会通过mtdev2tuio通过TUIO协议转发到qTUIO库,最后qTUIO库把这些多点触摸事件转换为Qt系统内部可处理的事件。qTUIO库的源代码里提供了这个库的使用例程,例程源码在 qTUIO-master/examples/ 目录里面,感兴趣的开发者可以进行移植测试。如对qTUIO库有比较深入的研究,或者发现以上的移植过程有纰漏,欢迎联系作者并指正,谢谢。