一直想做個模仿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截圖的功能我沒有實現,只是模仿了一小部分