qt模仿qq截圖

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章