如何在午休时间写一个QT程序以便能够挣到同事的五毛
在30分钟内用QT写一个抓屏程序
(注:Qt这个词应该读成Cute,而不是Queue-Tee)
※ ※ ※ ※ 叮咚、叮咚、叮咚——00:29:59 叮咚、叮咚、叮咚——00:29:58 叮咚、叮咚、叮咚——00:29:57 ※ ※ ※ ※
此截图程序可以实现以下功能:
+跨平台运行
+可以截取全屏幕或者其中一部分
+鼠标选中的区域为高亮,未选中的区域亮度减半
+截屏后可以反复选择,直至满意为止
+保存文件到桌面并退出
+复制图像到剪切板并退出
※ ※ ※ ※ 叮咚、叮咚、叮咚——00:25:59 叮咚、叮咚、叮咚——00:25:58 叮咚、叮咚、叮咚——00:25:57 ※ ※ ※ ※
整个程序主要有三个关键点:(1)抓屏到内存;(2)随着鼠标选取,加亮显示内存中的图片;(3)将图片的一部分保存到剪切板或者文件。
在QT中,这三个问题很好解决,问题(1)可以直接调用QT函数:
QPixmap px = QPixmap::grabWindow(QApplication::desktop()->winId())
※ ※ ※ ※
叮咚、叮咚、叮咚——00:24:59
叮咚、叮咚、叮咚——00:24:58
叮咚、叮咚、叮咚——00:24:57
※ ※ ※ ※
问题(2)也不难,只需要提前做一点点工作,在抓图后保存两个图片,一个是正常的,另一个是变暗之后的:
screen_ = px.toImage();
将刚刚截屏得到的图像转换成设备无关的内存表示
darkScreen_ = screen_;
在内存中对RGB减半处理,使图像变暗,先计算每行的字节数:
int bytesPerLine = darkScreen_.width() * darkScreen_.depth() / 8;
然后处理每个行
for ( int i = 0 ; i < darkScreen_.height() ; ++ i )
{
uchar* lineBuf = darkScreen_.scanLine(i);
scanLine函数可以取得指定行的起始指针,不必再自己计算了。取得行指针后对每像素RGB值进行操作
for ( int x = 0 ; x < bytesPerLine ; ++ x )
lineBuf[x] /= 2;
}
※ ※ ※ ※ 叮咚、叮咚、叮咚——00:22:59 叮咚、叮咚、叮咚——00:22:58 叮咚、叮咚、叮咚——00:22:57 ※ ※ ※ ※
void Widget::paintEvent(QPaintEvent *)
{
声明绘图对象,目标为窗口
QPainter painter(this);
全屏绘制暗图像:
painter.drawImage(0, 0, darkScreen_);
在选定的区域绘制亮图像:
painter.drawImage(currentRect_, screen_, currentRect_, Qt::AutoColor);
}
currentRect_是在鼠标事件中保存的当前选择区域,它的计算也很简单,在鼠标的移动消息处理器中:
void Widget::mouseMoveEvent(QMouseEvent * e)
{
if ( dragging_ )
end_ = e->globalPos();
保存旧区域,用于后面的区域合并
QRect oldRect = currentRect_;
设置新区域
currentRect_.setTopLeft(start_);
currentRect_.setBottomRight(end_);
调用normalized函数解决负宽度或者负高度问题
currentRect_ = currentRect_.normalized();
QRect::united()函数用来求两个rect合并后的rect,可以省去许多手工计算。
且QT的repaint()函数也不需要关心背景擦除问题,QT4以上都是自动double buffer
repaint(oldRect.united(currentRect_));
}
至此,问题(2)处理完毕。
※ ※ ※ ※ 叮咚、叮咚、叮咚——00:18:59 叮咚、叮咚、叮咚——00:18:58 叮咚、叮咚、叮咚——00:18:57 ※ ※ ※ ※
为处理问题(3),需要得到指定图像的指定区域,并且一个图像对象来表示。将这个功能编写成以下辅助函数:
static QImage clipImage(const QImage& srcImage, QRect rect)
{
QImage image(rect.size(), QImage::Format_RGB32);
const QImage* target = &srcImage;
QImage targetImg;
if ( srcImage.depth() != 32 )
{
targetImg = srcImage.convertToFormat(QImage::Format_RGB32);
target = &targetImg;
}
int bytesPerPixel = image.depth() / 8;
for ( int i = 0 ; i < image.height() ; ++ i )
{
uchar * line = image.scanLine(i);
const uchar * srcLine = target->scanLine(rect.top() + i);
memcpy(line,
srcLine + rect.left() * bytesPerPixel,
rect.width() * bytesPerPixel);
}
return image;
}
有了这个辅助函数之后,想保存文件,就可以这样写:
QImage img(clipImage(screen_, currentRect_)); img.save(pathName, "png");
QApplication::clipboard()->clear(); QApplication::clipboard()->setImage(clipImage(screen_, currentRect_), QClipboard::Clipboard); QImage(QApplication::clipboard()->image(QClipboard::Clipboard)); // Retrieve data from clipboard to make it owns the data ※ ※ ※ ※ 叮咚、叮咚、叮咚——00:10:59 叮咚、叮咚、叮咚——00:10:58 叮咚、叮咚、叮咚——00:10:57 ※ ※ ※ ※
再往下就是处理一些鼠标和键盘事件,比如在双击时复制到剪切板并退出,在按Ctrl+S时保存到文件并退出,按ESC时直接退出,等等
最终完成,编译,修改typo并运行,发现速度还可以,故取消profiling和性能调优计划,直接和DLL一起打包。至此任务完成,领取五毛工资。
※ ※ ※ ※ 叮咚、叮咚、叮咚——00:00:09 叮咚、叮咚、叮咚——00:00:08 叮咚、叮咚、叮咚——00:00:07 叮咚、叮咚、叮咚 叮咚、叮咚、叮咚——00:00:05 叮咚、叮咚、叮咚 叮咚、叮咚、叮咚——00:00:03 叮咚、叮咚、叮咚——00:00:02 叮咚、叮咚、叮咚——00:00:01 叮咚、叮咚、叮咚——00:00:00 (EOF)
Qt的Unit Test Framework简介
在国内的编程界,QT常常是不入程序员的法眼的,但平时能够听到的批评却有很多——大部分是对着MOC的。 其实QT有许多的好东西,比如在内部使用BSP管理数百万级别绘图对象的Graphics View Framework (这里说的BSP就是游戏编程中用的那个BSP)。除管理海量对象、3维变形、动画管理等功能之外,Graphics View Framework还能基于OpenGL渲染,而不用编写任何gl-开头的函数。
本文要介绍的是QT中的轻量级Unit Test Framework。既然要介绍的是代码框架,我就少喷方块字,直接先上一碟appetizer先:
代码
sample.cpp的内容:
01 #include <QtTest/QtTest>
02 class My1stUT : public QObject
03 {
04 Q_OBJECT
05 private slots:
06 void initTestCase()
07 {
08 }
09 void cleanupTestCase()
10 {
11 }
12 void testAdd()
13 {
14 QVERIFY(1 + 1 == 2);
15 QBENCHMARK
16 {
17 QString (100 * 1024 * 1024, '6');
18 QString (100 * 1024 * 1024, '4');
19 }
20 }
21 };
22 QTEST_MAIN(My1stUT)
23 #include "sample.moc"
上 面的代码构成了一个完整的单元测试。其中,slots是一个扩展关键字,Q_OBJECT用来声明一些专有的macro,#include用来包含头文件 和MOC的结果,QTEST_MAIN用来生成程序的主入口函数:main(int argc, char** argv)。
程序的主要 部分是三个member functions:initTestCase, cleanupTestCase和testAdd。其中,前两个名字是固定的,被测试系统默认调用的函数:initTestCase函数是第一个执行的测 试函数,用来为测试准备环境,如果没有需要,这个函数可以不写。cleanupTestCase的作用是在退出测试前清理资源,如果用不上,这个函数也可 以不写。
testAdd是实际的用户编写的测试函数,可以写任意多个函数,每个函数的名字也可以任意取(不一定要以test-开头)。这 些函数都会被当成测试用例,在最终生成的可执行程序中运行。与某些C++的测试框架不同,在这里我们不需要去任何地方注册这些测试用例。另外,这些函数的 名字可以在命令行中作为参数,用来手工指定运行哪一个测试。
testAdd中的程序行就是要编写的单元测试了,可以使用QCOMPARE和QVERIFY进行测试断言,还可以用QBENCHMARK测试性能。
编译
编译的过程也很简单,新建一个文件夹,比如名叫test,将sample文件放入,然后运行
1. qmake -project “QT += testlib”
2. qmake
3. make
即可得到可执行程序test.exe(此程序的名字取决于文件夹的名字)
运行
在命令行敲入test.exe -silent,得到如下结果:
Testing My1stUT
RESULT : My1stUT::testAdd():
1,141 msec per iteration (total: 1141, iterations: 1)
Totals: 3 passed, 0 failed, 0 skipped
大功告成!最后介绍一下test.exe的参数(这些参数都是QTEST_MAIN这个macro自动生成的,不用手工编写任何代码)
Usage: test.exe [options] [testfunction[:testdata]]…
By default, all testfunctions will be run.
options:
-functions : Returns a list of current testfunctions
-xml : Outputs results as XML document
-lightxml : Outputs results as stream of XML tags
-o filename: Writes all output into a file
-silent : Only outputs warnings and failures
-v1 : Print enter messages for each testfunction
-v2 : Also print out each QVERIFY/QCOMPARE/QTEST
-vs : Print every signal emitted
-eventdelay ms : Set default delay for mouse and keyboard simulation to ms milliseconds
-keydelay ms : Set default delay for keyboard simulation to ms milliseconds
-mousedelay ms : Set default delay for mouse simulation to ms milliseconds
-keyevent-verbose : Turn on verbose messages for keyboard simulation
-maxwarnings n : Sets the maximum amount of messages to output.
0 means unlimited, default: 2000
Benchmark related options:
-tickcounter : Use CPU tick counters to time benchmarks
-eventcounter : Counts events received during benchmarks
-minimumvalue n : Sets the minimum acceptable measurement value
-iterations n : Sets the number of accumulation iterations.
-median n : Sets the number of median iterations.
-vb : Print out verbose benchmarking information.
-help : This help
附录
没有提及的其它主要特性
1、在命令行给出测试数据
2、编程方式提供的GUI键鼠事件测试(非屏幕录制方式)
3、线程安全:框架产生的错误消息是线程安全的
最后还要说一句:QT的正确读法是cute,不要读成queue-tee
1 comment