Qt4简单截图功能的实现

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_OBJECT
	
public:

	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对象用到中间文件,效率低;
  • 三原色全部乘以倍数的手法导致程序效率不高。在一些配置较差电脑上,点开程序可能要等一段时间才能开始截图,这可能导致用户无法准确截取一些视频动画;
  • 单机鼠标右键退出以后,返回桌面也会产生鼠标右键点击的效果,这个问题我认为还是窗口没有顶置造成的。
posted on 2022-01-12 21:23  lazycat7706  阅读(422)  评论(0)    收藏  举报