CS巴别塔(1)

如何在午休时间写一个QT程序以便能够挣到同事的五毛

Posted in Uncategorized by Kenny Yuan on 2010/04/28
作者的话:本文有两个目的:1、Zhuangbility;2、教你如何变成五毛;3、展示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;
}

这个函数有点长,但思路却很简单:
1、先创建一个小图像,宽高与选区相同
2、取得源图像的数据,复制到小图像中
3、返回小图像

有了这个辅助函数之后,想保存文件,就可以这样写:


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)
Advertisements
Tagged with: , ,

Qt的Unit Test Framework简介

Posted in Uncategorized by Kenny Yuan on 2009/06/18

在国内的编程界,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

Tagged with: