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截图的功能我没有实现,只是模仿了一小部分

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值