一直想做个模仿qq截图的程序,今天完成了部分,下面是效果图:
这里面截图使用了QScreen封装好了的函数,下面是函数的签名:
QPixmap QScreen::grabWindow(WId window, int x = 0, int y = 0, int width = -1, int height = -1)
上面的参数比较容易理解,下面介绍一下像QQ那样截取任意部分的思路,首先先利用这个函数截取全屏,然后设置成为一个QWidget的背景,然后重写这个QWidget累的mouseMoveEvent()、mousePressEvent()、mouseReleaseEvent()函数,记住用户拖动的两个点的坐标,分别是左上角和右下角的坐标,里面QPixmap的copy函数截取那个全屏的pixmap对象的指定部分,QPixmap的copy函数的函数签名是:
QPixmap QPixmap::copy(const QRect & rectangle = QRect()) const
然后直接调用QPixmap的save函数就可以将截取到的任意部分保存到文件了,所以程序的主要任务是实现想QQ那样拖动鼠标出现选区的效果,下面开始上代码了,首先定义一个普通的QWidget,上面放置一个按钮,然后连接这个按钮的信号和槽,槽函数里面的定义:
void Widget::on_grabBtn_clicked()
{
//截取全屏
QScreen *scrPix = QGuiApplication::primaryScreen();
QPixmap pixmap = scrPix->grabWindow(0);
if( screen != NULL) {
screen = new Screen(this);
}
screen->setPixmap(pixmap);
screen->show();
}
这里面做了两件事,第一件事是截取全屏,第二件事是创建Screen对象,并将截屏函数得到的全屏QPixmap对象传进去,然后调用Screen::show()方法,让它显示出来,这里的Screen类是自定义的类,就是我前面说的用来显示全屏截图的QWidget类,我们看看Screen类的定义:
class Screen : public QWidget {
Q_OBJECT
public :
explicit Screen(QWidget *parent);
//重写鼠标点击事件
virtual void mousePressEvent(QMouseEvent *e);
//重写鼠标释放事件
virtual void mouseReleaseEvent(QMouseEvent *e);
//重写鼠标移动事件
virtual void mouseMoveEvent(QMouseEvent *e);
//重写鼠标绘制事件
virtual void paintEvent(QPaintEvent *e);
//pixmap的setter方法
void setPixmap(QPixmap pixmap);
//根据传过来坐标得到线性渐变对象,并设置颜色
QLinearGradient getLinearGadient(int x, int y, int width, int height);
//根据坐标绘制渐变
void paintGradient(int x1, int y1, int width, int height, QPainter &painter);
//绘制边框
void paintBorder(QPoint ltPoint, QPoint rbPoint, QPainter &painter);
//绘制边框上的四个可拉伸的小正方形
void paintStretchRect(QPoint ltPoint, QPoint rbPoint, QPainter &painter);
//保存截取到的图片
void savePixmap();
//判断左上角和右下角之间的坐标是否能够用来选取
bool isGrab(QPoint &p1, QPoint &p2);
//判断左下角与右上角之间的坐标
bool isGrabLeftBottom(QPoint &p, QPoint &p2);
//判断点是否在矩形里面
bool pointInRect(const QPoint &, const QRectF &);
//判断是否可以移动选取
bool isMove(QPoint <Point, QPoint &rbPoint, int moveX, int moveY);
//鼠标移动类型
enum MoveType {
AREAGRAB, AREAMOVE, AREALEFTTOP, AREALEFTBOTTOM,
AREARIGHTTOP, AREARIGHTBOTTOM
};
private :
QPixmap pixmap; //保存全屏
//选区的坐标
QPoint ltPoint; //左上角的坐标
QPoint rbPoint; //右下角坐标
QPoint tempPoint; //临时右下角坐标
QPixmap savedPixmap; //待保存的图片
//选区的边框的上面的四个小矩形
QRectF ltRect; //左上角矩形
QRectF lbRect; //左下角矩形
QRectF rtRect; //右上角矩形
QRectF rbRect; //右下角矩形
//整个选取
QRectF pixmapRect; //整个待截图的矩形
MoveType moveType; //鼠标移动的类型
bool grab; //判断当前的点击是不是第一次截图时候的点击,后面的都是修改时候的点击
//这里记录的是选取移动的时候的坐标
QPoint clickPoint; //记录每个点击的click坐标
QPoint oldPoint; //上一个移动的点的坐标
int desktopWidth; //桌面的宽度
int desktopHeight; //桌面的高度
};
上面都有注释,我后面慢慢来介绍这些函数,首先就是重写父类的四个函数,mousePressEvent()、mouseMoveEvent()、mouseReleaseEvent()、paintEvent()、这四个函数是最重要的核心部分,先来介绍paintEvent()函数吧,这个函数完成整个截图过程中的绘制:
void Screen::paintEvent(QPaintEvent *e) {
QPainter painter(this);
//将全屏画上去
painter.drawPixmap(0, 0, pixmap);
if( ltPoint == tempPoint || tempPoint == rbPoint && rbPoint == QPoint(0, 0)) {
paintGradient(0, 0, rect().width(), rect().height(), painter);
return;
}
//矩形上方的渐变
paintGradient(0, 0, rect().width(), ltPoint.y(), painter);
//矩形左边的渐变
paintGradient(0, ltPoint.y(), ltPoint.x(), rect().height(), painter);
//矩形正下方的渐变
paintGradient(ltPoint.x(), tempPoint.y(), tempPoint.x() - ltPoint.x(), rect().height(), painter);
//矩形右边的渐变
paintGradient(tempPoint.x(), ltPoint.y(), rect().width(), rect().height(), painter);
//保存画笔的状态
painter.save();
//绘制矩形边界
paintBorder(ltPoint, tempPoint, painter);
painter.restore();
//画四个顶点的矩形
paintStretchRect(ltPoint, tempPoint, painter);
}
首先将得到的全屏QPixmap对象绘制上去坐背景,然后绘制选取之外的渐变也就是paintGradient()函数的作用:
QLinearGradient Screen::getLinearGadient(int x, int y, int width, int height) {
QLinearGradient grad(x, y, width, height);
grad.setColorAt(0.0, QColor(0, 0, 0, 100));
return grad;
}
//根据坐标绘制渐变
void Screen::paintGradient(int x1, int y1, int width, int height, QPainter &painter) {
QLinearGradient grad(x1, y1, width, height);
grad.setColorAt(0.0, QColor(0, 0, 0, 100));
painter.fillRect(x1, y1, width, height, grad);
}
这个函数根据传过来的坐标绘制渐变,也就是除选取之外的蒙版效果,上面的ltPoint和tempPoint我在Screen这个类的定义的地方有注释,上面说了这个是干什么的,这样将选取之外的地方绘制一层蒙版效果这样选区的样子就做出来,然后就是绘制边框,这个蓝色边框的绘制:
void Screen::paintBorder(QPoint ltPoint, QPoint rbPoint, QPainter &painter) {
QPen pen;
//设置画笔颜色
pen.setColor(Qt::blue);
//设置画笔宽度
pen.setWidth(1);
painter.setPen(pen);
//画左边界
painter.drawLine(ltPoint.x(), ltPoint.y(), ltPoint.x(), rbPoint.y());
//画上边界
painter.drawLine(ltPoint.x(), ltPoint.y(), rbPoint.x(), ltPoint.y());
//画右边界
painter.drawLine(rbPoint.x(), ltPoint.y(), rbPoint.x(), rbPoint.y());
//画下边界
painter.drawLine(ltPoint.x(), rbPoint.y(), rbPoint.x(), rbPoint.y());
}
最然后就是绘制选区上面四个角的小矩形,这个矩形有一定大小,当鼠标移动到小矩形上面就会变成响应的样式,下面是绘制矩形函数:
void Screen::paintStretchRect(QPoint ltPoint, QPoint rbPoint, QPainter &painter) {
//每个矩形算6的长度
//左上角
ltRect.setX(ltPoint.x() - 3);
ltRect.setY(ltPoint.y() - 3);
ltRect.setWidth(6);
ltRect.setHeight(6);
//右上角
rtRect.setX(rbPoint.x() - 3);
rtRect.setY(ltPoint.y() - 3);
rtRect.setWidth(6);
rtRect.setHeight(6);
//左下角
lbRect.setX(ltPoint.x() - 3);
lbRect.setY(rbPoint.y() - 3);
lbRect.setWidth(6);
lbRect.setHeight(6);
//右下角
rbRect.setX(rbPoint.x() - 3);
rbRect.setY(rbPoint.y() - 3);
rbRect.setWidth(6);
rbRect.setHeight(6);
//绘制用于拉伸的矩形
QBrush brush(Qt::blue);
painter.fillRect(ltRect, brush);
painter.fillRect(lbRect, brush);
painter.fillRect(rtRect, brush);
painter.fillRect(rbRect, brush);
}
这样绘制就完成了,上面的ltPoint和tempPoint的取值在mousePressEvent()和mouseMoveEvent()函数里面得到,分别是选区左上角的坐标和右下角的坐标,mousePressEvent()函数:
void Screen::mousePressEvent(QMouseEvent *e) {
//判断左键点击事件
if( e->button() & Qt::LeftButton && grab) {
ltPoint = e->pos();
repaint();
qDebug() << "ltPoint:" << ltPoint;
}
//记录此时点击的坐标,主要用作移动选区
clickPoint = e->pos();
oldPoint = clickPoint;
}
从上面看到当第一次点击的时候这个grab就是true,这个时候记录左上角的坐标,现在要计算右下角的坐标,根据mouseMoveEvent()获取到移动的坐标点赋值给tempPoint就行了:
void Screen::mouseMoveEvent(QMouseEvent *e) {
QPoint p = e->pos();
//临时的point
QPoint m_tempPoint;
//左键点击移动事件
if( e->buttons() & Qt::LeftButton) {
switch(moveType) {
case AREAGRAB:
//如果移动后的坐标大于矩形左上角的坐标
if(isGrab(ltPoint, p)) {
tempPoint = p;
repaint();
}
// qDebug() << "截图";
break;
case AREALEFTBOTTOM:
// qDebug() << "左下角";
//封装右上角的坐标
m_tempPoint.setX(tempPoint.x());
m_tempPoint.setY(ltPoint.y());
if(isGrabLeftBottom(p, m_tempPoint)) {
ltPoint.setX(p.x());
tempPoint.setY(p.y());
}
break;
case AREALEFTTOP:
// qDebug() << "左上角";
if( isGrab(p, tempPoint)) {
ltPoint = p;
}
break;
case AREARIGHTBOTTOM:
// qDebug() << "右下角";
if(isGrab(ltPoint, p)) {
tempPoint = p;
}
break;
case AREARIGHTTOP:
// qDebug() << "右上角";
//封装左下角的坐标
m_tempPoint.setX(ltPoint.x());
m_tempPoint.setY(tempPoint.y());
if(isGrabLeftBottom(m_tempPoint, p)) {
ltPoint.setY(p.y());
tempPoint.setX(p.x());
}
break;
case AREAMOVE: {
// qDebug() << "区域中间";
//记录此时移动的距离
int moveX = p.x() - oldPoint.x();
int moveY = p.y() - oldPoint.y();
//整个选取移动的时候两个点的坐标都要移动
if(isMove(ltPoint, tempPoint, moveX, moveY)) {
ltPoint.setX(ltPoint.x() + moveX); //左上角的x坐标
ltPoint.setY(ltPoint.y() + moveY); //左上角的y坐标
tempPoint.setX(tempPoint.x() + moveX); //右下角的x坐标
tempPoint.setY(tempPoint.y() + moveY); //右下角的y坐标
//将此时的移动坐标记录
oldPoint = p;
}
}
break;
default:
// qDebug() << "moveType:" << moveType << "\tAREAGRAB:" << AREAGRAB;
break;
}
//鼠标未点击的时候移动
} else {
//根据鼠标移动的位置该表鼠标的样式
if(pointInRect(p, ltRect)) {
setCursor(Qt::SizeFDiagCursor);
moveType = AREALEFTTOP;
} else if(pointInRect(p, rtRect)) {
setCursor(Qt::SizeBDiagCursor);
moveType = AREARIGHTTOP;
} else if(pointInRect(p, lbRect)) {
setCursor(Qt::SizeBDiagCursor);
moveType = AREALEFTBOTTOM;
} else if(pointInRect(p, rbRect)) {
setCursor(Qt::SizeFDiagCursor);
moveType = AREARIGHTBOTTOM;
} else if(pointInRect(p, pixmapRect)) {
setCursor(Qt::SizeAllCursor);
moveType = AREAMOVE;
} else {
setCursor(Qt::ArrowCursor);
moveType = AREAGRAB;
}
}
//移动之后重绘
repaint();
}
上面AREAMOVE、AREAGRAB等等是Screen类里面定义的枚举常量用来表示移动的类型,分别对应:截图类型(也就是第一次点击之后拖出一个选区)、选区移动、选区左上角拖动、选区左下角拖动、选区右上角拖动、选区右下角拖动,setCursor()函数是用来该表鼠标的样式
isGrab()函数用来比较右下角和左上角的坐标:
/**
* @brief Screen::isGrab
* 当p2的坐标大于p1是返回true,否则返回false
* @param p1
* @param p2
* @return
*/
bool Screen::isGrab(QPoint &p1, QPoint &p2) {
if( p2.x() > p1.x() && p2.y() > p1.y())
return true;
return false;
}
isGrabLeftBottom()函数是用来判断左下角和右上角的坐标:
/**
* @brief Screen::isGrabLeftBottom
* 当作p的x坐标小于p2的x坐标切p的y坐标大于p2的y坐标
* @param p 左下角
* @param p2 右上角
* @return
*/
bool Screen::isGrabLeftBottom(QPoint &p, QPoint &p2) {
if( p.x() < p2.x() && p.y() > p2.y())
return true;
return false;
}
这两个函数是用来限制选区的不合理缩小,例如右下角到了左上角上面,这个是不合理的情况,通过这两个函数可以限制
isMove()函数是用来限制选区的不正常移动,不能让选区到屏幕内部去,限制选区在当前屏幕上面:
/**
* @brief Screen::isMove
* 判断是不是可以移动
* @param ltPoint 左上角的坐标
* @param rbPoint 右下角的坐标
* @param moveX 移动的x坐标
* @param moveY 移动的y坐标
* @return
*/
bool Screen::isMove(QPoint <Point, QPoint &rbPoint, int moveX, int moveY) {
if( (ltPoint.x() + moveX) < 0 || (ltPoint.y() + moveY) < 0
|| (rbPoint.x() + moveX) >= desktopWidth || (rbPoint.y() + moveY)
>= desktopHeight) {
return false;
}
return true;
}
还有一个截取的图片保存,当鼠标释放的时候保存图片:
void Screen::mouseReleaseEvent(QMouseEvent *e) {
//左键释放事件
if( e->button() & Qt::LeftButton) {
// rbPoint = e->pos();
rbPoint = tempPoint;
//释放鼠标的时候截取选中的部分的图片
QPixmap pix = pixmap.copy(ltPoint.x(), ltPoint.y(), rbPoint.x() - ltPoint.x(), rbPoint.y() - ltPoint.y());
savedPixmap = pix;
//保存图片
savePixmap();
pixmapRect.setX(ltPoint.x());
pixmapRect.setY(ltPoint.y());
pixmapRect.setWidth(rbPoint.x() - ltPoint.x());
pixmapRect.setHeight(rbPoint.y() - ltPoint.y());
grab = false;
//初始化桌面的宽高
desktopWidth = pixmap.width();
desktopHeight = pixmap.height();
}
}
savePixmap()函数就是用来保存图片到文件:
//保存图片到文件
void Screen::savePixmap() {
//生成图片名称
QString picName = "小万截图";
QTime time;
//获取当前系统时间,用做伪随机数的种子
time = QTime::currentTime();
qsrand(time.msec() + time.second() * 1000);
//随机字符串
QString randStr;
randStr.setNum(qrand());
picName.append(randStr);
picName.append(".jpg");
qDebug() << "picName:" << picName << "qrand:" << qrand();
savedPixmap.save(picName, "JPG");
}
好了,模仿qq截图的程序就到这里为止了,还有好多qq截图的功能我没有实现,只是模仿了一小部分