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

来源:互联网 发布:钢笔推荐 知乎 编辑:程序博客网 时间:2024/03/28 20:16

作者的话:本文有两个目的: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)