Qt4简单截图功能的实现

来源:互联网 发布:巨人网络offer 编辑:程序博客网 时间:2024/06/01 17:15

1、概述

       截图是一个非常常见的功能。但是Windows自带的截图功能我们发现并不好用。所以很多时候我们打开QQ就是为了做一个截图。QQ的截图功能还是非常强大的,今天,我将用Qt4手工做一个简单的截图。这个截图比不上QQ的截图,只实现了基本功能。没有复杂的操作,供大家学习、参考。

2、 需求描述

       最终成品是一个可执行的exe文件,运行平台是Windows操作系统(经过我的测试,在Windows7、10下均可以完美运行)。
       打开软件后,屏幕变暗,进入“自由模式”,在这个模式下,鼠标可以自由移动。自由模式可以做以下的事情:

  • 自由模式上面显示一个图片表示当前位于自由模式下,鼠标左键单击图片,图片会消失。
  • 单击鼠标左键,开始抓图,进入“截屏模式”,上面的图片自动消失。在这个模式下,鼠标按住拖动会产生一个橡皮框。表示要截图的区域。橡皮框左上角会产生一个QLabel指示这个橡皮框的大小(像素)。
  • 松开鼠标后,程序会询问用户是否要保存图片,如果用户选择“yes”,那么会弹出一个保存文件的对话框,图片只能保存为png格式的。程序这时候会抓取橡皮框区域的图片,保存并且退出。如果用户选择“no”,橡皮框和QLabel均消失。
  • 在自由模式下双击鼠标左键自动截取全屏。
  • 在自由模式下单击鼠标右键不截图,直接退出程序。

3、软件截图

自由模式:
这里写图片描述
截图模式:
这里写图片描述
询问用户:
这里写图片描述

3、 开发环境

       本软件采用Qt 4.8.7 开发,操作系统为Windows10 64位。没有使用QtDesigner、QtCreator。
       编译环境为mingw32 4.8.2

4、 分析

注意:下面只分析和截图功能有关的代码,很多细枝末节(如图片提示、文件保存)代码不再解释。
       要想实现这个程序,思路其实很简单。我们只要获取当前整个桌面的图片,把这个图片设置为主窗口的背景图片。然后让主窗口充斥整个桌面。
       获取整个桌面并不难,Qt是有直接提供实现的。我们看QPixmap的如下成员函数:

QPixmap grabWindow ( WId window, int x = 0, int y = 0, int width = -1, int height = -1 )

       关心第一个参数,它表示要截取的Window ID。这里我们要截取整个桌面,使用QApplication::desktop()->winId()即可。后面的参数不需要关心,默认它就是截取整个窗口的。
       我们只需要把主窗口的背景设置为这个图片,大小设置为图片的大小。还需要把主窗口的状态栏隐藏,实现也很简单,利用setWindowFlags()设置Qt::FramelessWindowHint即可。
       获取整个桌面的QPixmap以后,我们需要两份副本。一份用来变暗并显示在主窗口上,另外一份是原本,用于稍后真正的截图。
       一个懊恼的问题是我们如何把图片变暗。我找了很多方法,这里采用一个简单的:把图片的三原色全部乘以一个倍数。注意,QPixmap并没有获取三原色的方法,所以我只好把QPixmap转换为QImage。这里我使用了一个中间文件temp.png。效率略低,有更好方法的小伙伴欢迎评论。

    whole_window.save("temp.png");    bg = QImage("temp.png");    QFile *temp = new QFile("temp.png");    temp->remove();

       whole_window表示用grabWindow抓取的QPixmap,bg是一个QImage类对象,它们都是主窗口类的成员。
       接着,使用以下手段使QImage变暗:

    int red,green,blue;    for(int i = x; i < width; i++){        for(int j = y; j < height; j++){            //三原色全部乘以一个倍数,实现屏幕的变暗            red = qRed(bg.pixel(i, j)) * bright ;            green = qGreen(bg.pixel(i, j)) * bright;            blue = qBlue(bg.pixel(i, j)) * bright;            bg.setPixel( i, j, qRgb(red, green, blue));        }    }

       然后我们重写paintEvent事件把bg设置为主窗口的背景图片:

void screenShot::paintEvent(QPaintEvent *event){   QPainter painter(this);   //把bg设置为整个窗口的背景图片   painter.drawImage(0, 0, bg);}

       这就实现了点开程序,整个桌面变暗的假象了。
       接下来,整个程序的关键来了:在用户点击鼠标左键的时候,我们需要开始抓图。这需要我们重写主窗口的鼠标事件:

void mousePressEvent(QMouseEvent *e);void mouseMoveEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); void mouseDoubleClickEvent(QMouseEvent *event);

       四个事件分别对应于鼠标移按下、移动、松开、双击。
       我们重点关心鼠标按下。
       在鼠标按下的时候,我们需要创建一个橡皮框显示抓图的区域。这使用到一个叫做QRubberBand的类。Qt的帮助文档是这样介绍这个类的:

The QRubberBand class provides a rectangle or line that can indicate a selection or a boundary.
You can create a QRubberBand whenever you need to render a rubber band around a given area (or to represent a single line), then call setGeometry(), move() or resize() to position and size it. A common pattern is to do this in conjunction with mouse events.

       文档说,这个类可以表示一块区域。我们可以通过resize改变大小,move改变框的位置。
       那么,把它设置为主窗口的一个成员,当鼠标单击时,实例化橡皮框。鼠标移动时,改变橡皮框的大小。(具体大小需要计算)
       为了实现截图,我们需要记录鼠标按下和松开的位置(计算出截图区域的长宽)。在主窗口的成员中保存两个QPoint对象start和end即可。
       这需要我们取得鼠标的位置,很简单,通过QMouseEvent的pos()即可完成。注意它返回的是QPoint类对象。
       在鼠标左键按下时,记录鼠标的初始位置:

start = e->pos();

       鼠标移动时,我们需要实时改变橡皮框的大小,实现如下:

//记录鼠标移动的位置end = e->pos();//计算框的大小int width = abs(end.x() - start.x());int height = abs(end.y() - start.y());//计算框的位置int x = start.x() < end.x() ? start.x() : end.x();int y = start.y() < end.y() ? start.y() : end.y();

       在鼠标松开以后,我们需要记录鼠标点击和松开的位置,计算出要抓取屏幕的大小。然后开始截屏,截屏完成后,退出程序:

//取得鼠标松开的位置end = e->pos();//隐藏框rubber->hide();//调用截屏函数this->grapScreen();//关闭this->close();

       在grapScreen()成员函数中,我们需要实现真正的截图,这里使用QPixmap的copy函数取得桌面原本图片(注意,不是变暗的bg)的一个子图片。copy函数的原型如下:

QPixmap QPixmap::copy ( int x, int y, int width, int height ) const

       只要提供初始位置(x,y),图片大小(width,height),我们就可以获取要抓取的图片。初始位置就是start或者end坐标。大小我们可以利用start和end计算得出:

int width = abs(start.x() - end.x());int height = abs(start.y() - end.y());int x = start.x() < end.x() ? start.x() : end.x();int y = start.y() < end.y() ? start.y() : end.y();

接着调用QPixmap的save即可保存图片。
以上就是截图核心功能的一个解释,还有很多细节我没有解释,大家看代码自行理解。

5、 代码实现

  • screenShot.h 主窗口类声明
#ifndef _SCREEN_SHOT_H_#define _SCREEN_SHOT_H_#include <QWidget>#include <QRubberBand>#include <QLabel>#include <QScreen>#include <QPoint>#include <QString>#include <QPalette>#include <QFileDialog>#include <QDesktopServices>#include <QPixmap>#include <QImage>class screenShot : public QWidget{    Q_OBJECTpublic:    screenShot(QWidget *parent = 0);    void mouseMoveEvent(QMouseEvent *e);     void mousePressEvent(QMouseEvent *e);    void mouseReleaseEvent(QMouseEvent *e);     void mouseDoubleClickEvent(QMouseEvent *event);     void keyPressEvent(QKeyEvent *e);    void setLabel();    void changeLight(int x, int y, int width, int height, double bright);    void paintEvent(QPaintEvent *event);    void show();    void grapScreen();    ~screenShot();private:    QRubberBand *rubber;     QLabel *label;     QPoint start;     QPoint end;     QPixmap whole_window;    QPixmap image;     QImage *info;    QLabel *info_label;    QImage bg;    int s_height;    int s_width;     QPushButton *button;};#endif //screenShot.h
  • screenShot.cpp 主窗口类实现
#include "screenShot.h"#include <QtGui>screenShot::screenShot(QWidget *parent)    :QWidget(parent),    rubber(NULL),    label(new QLabel("")),    start(QPoint(0, 0)),    end(QPoint(0, 0)),    whole_window(QPixmap::grabWindow(QApplication::desktop()->winId())),    image(QPixmap()),    info(new QImage("infoa.png")),    info_label(new QLabel("")),    s_height(0),    s_width(0),    button(new QPushButton()){    //此语句解决中文乱码情况    QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));    //此处把QPixmap转换为QImage    //因为只有QImage才可以设置三原色    whole_window.save("temp.png");    bg = QImage("temp.png");    QFile *temp = new QFile("temp.png");    temp->remove();    changeLight(0, 0, bg.width(), bg.height(), 0.6); //把图片变暗    //获取桌面尺寸    QDesktopWidget *d_widget = QApplication::desktop();    QRect d_rect = d_widget->screenGeometry();    s_width = d_rect.width();    s_height = d_rect.height();    //设置窗口大小为桌面尺寸    this->resize(s_width, s_height);    //这个按钮是用户第一次打开程序的提示    button->setIcon(QIcon("infob.png"));    button->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);    button->setIconSize(QSize(211, 123));    button->setGeometry(s_width / 2, 0, 211, 123);    button->show();    //用户点击按钮后,按钮消失    connect(button, SIGNAL(clicked()), button, SLOT(close()));    //窗口和坐标信息置于整个屏幕顶端    label->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);    this->setWindowFlags(Qt::FramelessWindowHint);}void screenShot::changeLight(int x, int y, int width, int height, double bright){    int red,green,blue;    for(int i = x; i < width; i++){        for(int j = y; j < height; j++){            //三原色全部乘以一个倍数,实现屏幕的变暗            red = qRed(bg.pixel(i, j)) * bright ;            green = qGreen(bg.pixel(i, j)) * bright;            blue = qBlue(bg.pixel(i, j)) * bright;            bg.setPixel( i, j, qRgb(red, green, blue));        }    }}void screenShot::mousePressEvent(QMouseEvent *e){    if(e->button() == Qt::LeftButton){        //初始化提示信息框        info_label->setPixmap(QPixmap::fromImage(*info));        info_label->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);        info_label->move(QPoint(e->pos().x() + 30, e->pos().y() + 30));        info_label->show();        button->close();        if(!rubber){            //初始化框            rubber = new QRubberBand(QRubberBand::Line, this);        }        //显示框        rubber->show();        //记录初始鼠标位置        start = e->pos();        //将框放置于起始位置,且大小为0x0        rubber->setGeometry(start.x(), start.y(), 0, 0);        //调用此函数,显示坐标        setLabel();    } else if(e->button() == Qt::RightButton) { //鼠标右键点击,退出程序        button->close();        info_label->close();        this->close();    }}void screenShot::mouseDoubleClickEvent(QMouseEvent *event){    // 此处应该截取全屏    if(event->button() == Qt::LeftButton){        button->close();        QString fileName = QFileDialog::getSaveFileName(this,                                        tr("保存图片"),                                        ".",                                        tr("Image Files(*.PNG)")); //此处只支持png格式的图片        whole_window.save(fileName);        info_label->close();        this->close();    }}void screenShot::mouseMoveEvent(QMouseEvent *e){    if(rubber){        info_label->move(QPoint(e->pos().x() + 30, e->pos().y() + 30));        //记录鼠标移动的位置        end = e->pos();        //计算框的大小        int width = abs(end.x() - start.x());        int height = abs(end.y() - start.y());        //计算框的位置        int x = start.x() < end.x() ? start.x() : end.x();        int y = start.y() < end.y() ? start.y() : end.y();        //更新框的位置        rubber->setGeometry(x, y, width, height);        //changeLight(x, y, width, height, 1.25);        //改变坐标显示        setLabel();    }}void screenShot::mouseReleaseEvent(QMouseEvent *e){    if(rubber){        info_label->hide();        label->hide();        QMessageBox::StandardButton res                = QMessageBox::question(this, "询问", "您要保存这个截图吗?",                 QMessageBox::Yes | QMessageBox::No);        if(res == QMessageBox::Yes){            //取得鼠标松开的位置            end = e->pos();            //隐藏框            rubber->hide();            //调用截屏函数            this->grapScreen();            //关闭            this->close();        } else {            //用户不保存图片,返回自由模式            button->show();             rubber->close();            info_label->hide();            label->hide();        }    }}void screenShot::grapScreen(){    int width = abs(start.x() - end.x());    int height = abs(start.y() - end.y());    int x = start.x() < end.x() ? start.x() : end.x();    int y = start.y() < end.y() ? start.y() : end.y();    //截取指定大小的图片    image = whole_window.copy(x, y, width, height);    QString fileName = QFileDialog::getSaveFileName(this,                                        tr("保存图片"),                                         ".",                                        tr("Image Files(*.PNG)"));    //保存图片    bool flag = image.save(fileName);    if(flag){        QMessageBox::information(this, "信息", "图片保存成功!");    }}void screenShot::setLabel(){    int width = abs(start.x() - end.x());    int height = abs(start.y() - end.y());    int x = start.x() < end.x() ? start.x() : end.x();    int y = start.y() < end.y() ? start.y() : end.y();    //设置标签内容    QString str = QString(" %1 x %2        ").arg(width).arg(height);    label->setText(str);    //重定位标签的位置    QRect rect(label->contentsRect());    label->move(QPoint(x, y - rect.height()));    label->show();}void screenShot::keyPressEvent(QKeyEvent *event){    this->close(); }screenShot::~screenShot(){    //...}void screenShot::show(){    QWidget::show();}void screenShot::paintEvent(QPaintEvent *event){   QPainter painter(this);   //把bg设置为整个窗口的背景图片   painter.drawImage(0, 0, bg);}
  • main.cpp
#include "screenShot.h"#include <QApplication>#include <QCoreApplication>#include <QTextCodec>#include <windows.h>int main(int argc, char *argv[]){    QTextCodec *codec = QTextCodec::codecForName("UTF-8");    QTextCodec::setCodecForTr(codec);    QTextCodec::setCodecForLocale(codec);    QTextCodec::setCodecForCStrings(codec);    QApplication a(argc, argv);    screenShot *ss = new screenShot();    ss->show();    return a.exec();}

以上代码仅作参考!

6、问题

本程序尚存在如下问题:

  • 图片只能保存png格式的,这是硬伤;
  • 截图并没有做到顶置(为了显示提示信息),这导致一些顶置的程序无法截进去,如输入法等;
  • 把QPixmap转换为QImage对象用到中间文件,效率低;
  • 三原色全部乘以倍数的手法导致程序效率不高。在一些配置较差电脑上,点开程序可能要等一段时间才能开始截图,这可能导致用户无法准确截取一些视频动画;
  • 单机鼠标右键退出以后,返回桌面也会产生鼠标右键点击的效果,这个问题我认为还是窗口没有顶置造成的。
原创粉丝点击