天天看点

qt实现头像上传功能

文章目录

    • 一、概要
    • 二、详情
想必大家都使用过qt的自定义头像功能吧,那么图1应该不会陌生,本片文章我就是要模拟一个这样的功能,虽然没有这么强大的效果,但是能够满足一定的需求。
qt实现头像上传功能

首先在讲解功能之前,我先给出一片文章,QT实现的类似QQ的头像选择框,这篇文章也是讲解头像上传功能的,而我自己的代码是从这个demo中优化而来,不仅对代码进行了重构,而且处理了快速拖动时,边框消失的问题。使用事件和双缓冲来尽量减少重新绘制的几率。接下里我会一步一步进行讲解,怎么实现图片自定义截取功能。

一、概要

首选,我给出4个类,并给出他们的解释

1、PicturePreviewPanel:图标展示框,控件基类

2、BackgroundWidget:阴影窗口,是PicturePreviewPanel子类

3、CutShape:交互图形基类,实现了拖拽和放大缩小功能

4、CutRound:圆形剪贴,父类为CutShape,实现父类的paintInit接口重绘

5、CutRectangle:矩形剪贴,父类为CutShape,实现父类的paintInit接口重绘

二、详情

理解了上述5个类之后,接下来我分别讲解下类的头文件和重要的实现接口,其中自定义图形类也可以自己在扩充

头文件代码如下,有部分注释,代码应该不难理解,开发过程中只需要声明PicturePreviewPanel类,并调用期LoadPicture接口就可以加载图片,并进行图片剪切

#include <QWidget>

class QLabel;

enum ShapeType
{
    Rect,//矩形
    Round,//圆形
};

//剪贴图基类 实现了基本的交互功能,并绘制了部分图案,主要的团绘制在子类实现,通过实现paintInit接口
class CutShape : public QWidget
{
public:
    CutShape(QWidget * parent = nullptr);
    ~CutShape();

public:
    QPainterPath CutRegion();

protected:
    //QWidget
    virtual void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE;
    virtual void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE;
    virtual void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE;
    virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE;
    virtual void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;

    virtual bool paintInit(QPaintEvent *, QPaintDevice *) = 0;
    virtual QPainterPath Region(){ return QPainterPath(); };

protected:
    ShapeType m_Type;
    bool m_MouseDown = false;
    bool m_IsMoving = false;
    bool m_IsFirst = true;
    int border = 5;

private:
    QRect getResizeGem(QRect oldgeo, QPoint mousePoint, bool & ignore);

private:
    bool m_EnableRepaint = true;
    bool m_Left = false;
    bool m_Right = false;
    bool m_Bottom = false;
    bool m_Top = false;
    QPoint m_startPoint;
    QPoint m_old_pos;
    QLabel * m_Label;
    QPixmap m_BufferPix;
};

class CutRectangle : public CutShape
{
public:
    CutRectangle(QWidget * parent = nullptr);
    ~CutRectangle();

protected:
    //CutShape
    virtual bool paintInit(QPaintEvent *, QPaintDevice *) Q_DECL_OVERRIDE;
    virtual QPainterPath Region() Q_DECL_OVERRIDE;
};

class CutRound : public CutShape
{
public:
    CutRound(QWidget * parent = nullptr);
    ~CutRound();

protected:
    //CutShape
    virtual bool paintInit(QPaintEvent *, QPaintDevice *) Q_DECL_OVERRIDE;
    virtual QPainterPath Region() Q_DECL_OVERRIDE;

private:
};

class BackgroundWidget : public QWidget
{
public:
    BackgroundWidget(QWidget * parent = nullptr, ShapeType type = Round);
    ~BackgroundWidget();

public:
    void PictureLoadFinished();

protected:
    virtual void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;

private:
    ShapeType m_Type;
    CutShape * m_CutShape = nullptr;
};

class PicturePreviewPanel : public QWidget
{
    Q_OBJECT

public:
    PicturePreviewPanel(QWidget * parent = nullptr);
    ~PicturePreviewPanel();

public:
    void LoadPicture(const QString & filepath);//加载图片

protected:
    virtual bool eventFilter(QObject *, QEvent *) Q_DECL_OVERRIDE;

private:
    void InitializeUI();
    void LoadPicture_p();

private:
    QString m_PicturePath;
    QLabel * m_PictureContainer = nullptr;
    BackgroundWidget * m_BgWidget = nullptr;
};
           

上述头文件,如果觉得麻烦了也可以不具体细看接口,只要理解了标题1下边的5个类的作用就可以了。下边我分别说下交互图形类、背景色类和显示图片类(即控件基类)这三者直接的一些关系,具体如下:

1、CutShape剪贴图基类 实现了基本的交互功能,并绘制了部分图案,主要的绘制在子类实现,通过实现paintInit接口,该接口在剪切图街基类重新绘制时调用,代码如下:

void CutShape::paintEvent(QPaintEvent * event)
{
    QPainter paint;
    if (m_EnableRepaint && paintInit(event, &m_BufferPix))
    {
        m_EnableRepaint = false;
        qDebug() << "event->type()" << event->type();
        m_BufferPix = QPixmap(size());
        m_BufferPix.fill(Qt::transparent);

        paint.begin(&m_BufferPix);
        QPen pen0;
        pen0.setColor(QColor(54, 158, 254, 120));
        pen0.setWidth(2);
        paint.setPen(pen0);
        paint.drawRect(border, border, width() - border * 2, width() - border * 2);

        QPen pen;
        QVector<qreal> dashes;
        qreal space = 3;
        dashes << 5 << space << 5 << space;
        pen.setDashPattern(dashes);
        pen.setColor(Qt::white);

        paint.setPen(pen);
        int x_pos = (int)width() / 3.0;
        int y_pos = (int)height() / 3.0;
        paint.drawLine(x_pos, border, x_pos, height() - 2 * border);
        paint.drawLine(2 * x_pos, border, 2 * x_pos, height() - 2 * border);
        paint.drawLine(border, y_pos, width() - 2 * border, y_pos);
        paint.drawLine(border, 2 * y_pos, width() - 2 * border, 2 * y_pos);
        paint.end();
    }
    paint.begin(this);
    paint.drawPixmap(rect(), m_BufferPix);
    paint.end();
}
           

上边是主要的绘制过程,关于剪切图形的交互功能,我给出具体的大姨妈,就不仔细讲解功能了,代码有些长,如下:

void CutShape::mousePressEvent(QMouseEvent * event)
{
    m_startPoint = event->pos();
    m_MouseDown = event->button() == Qt::LeftButton;
}

void CutShape::mouseMoveEvent(QMouseEvent * event)
{
    QPoint dragPoint = event->pos();
    if (!parentWidget()->rect().contains(mapToParent(dragPoint)))
    {
        return;
    }
    int x = event->x();
    int y = event->y();
    if (m_MouseDown)
    {
        if (m_Left == false && m_Right == false
            && m_Bottom == false && m_Top == false)
        {
            QPoint p = QPoint((pos().x() + dragPoint.x() - m_startPoint.x()), (pos().y() + dragPoint.y() - m_startPoint.y()));
            QPoint dragEndge = p;
            dragEndge.setX(dragEndge.x() + rect().width());
            dragEndge.setY(dragEndge.y() + rect().height());
            p.setX(p.x() < 0 ? 0 : p.x());
            p.setX(dragEndge.x() > parentWidget()->width() ? parentWidget()->width() - rect().width() : p.x());
            p.setY(p.y() < 0 ? 0 : p.y());
            p.setY(dragEndge.y() > parentWidget()->height() ? parentWidget()->height() - rect().height() : p.y());
            move(p);
        }
        else
        {
            bool ignore = false;
            QRect g = getResizeGem(geometry(), dragPoint, ignore);
            if (parentWidget()->rect().contains(g))
                setGeometry(g);
            if (ignore == false)
            {
                m_startPoint = QPoint(!m_Right ? m_startPoint.x() : event->x(), !m_Bottom ? m_startPoint.y() : event->y());
            }
        }
    }
    else
    {
        QRect r = rect();
        m_Left = qAbs(x - r.left()) < 5;
        m_Right = qAbs(x - r.right()) < 5;
        m_Bottom = qAbs(y - r.bottom()) < 5;
        m_Top = qAbs(y - r.top()) < 5;
        bool lorr = m_Left | m_Right;
        bool torb = m_Top | m_Bottom;
        if (lorr && torb)
        {
            if ((m_Left && m_Top) || (m_Right && m_Bottom))
            {
                setCursor(Qt::SizeFDiagCursor);
            }
            else
                setCursor(Qt::SizeBDiagCursor);
        }
        else if (lorr)
            setCursor(Qt::SizeHorCursor);
        else if (torb)
            setCursor(Qt::SizeVerCursor);
        else
        {
            setCursor(Qt::SizeAllCursor);
            m_Bottom = m_Left = m_Right = m_Top = false;
        }
    }
}

void CutShape::mouseReleaseEvent(QMouseEvent * event)
{
    m_MouseDown = false;
}

void CutShape::resizeEvent(QResizeEvent *event)
{
    m_EnableRepaint = true;
    update();

    QWidget::resizeEvent(event);
}

QRect CutShape::getResizeGem(QRect oldgeo, QPoint mousePoint, bool & ignore)
{
    QRect g = oldgeo;
    bool lorr = m_Left | m_Right;
    bool torb = m_Top | m_Bottom;
    int dx = mousePoint.x() - m_startPoint.x();
    int dy = mousePoint.y() - m_startPoint.y();
    ignore = false;
    if (lorr && torb)
    {
        int maxLen = qMax(qAbs(dx), qAbs(dy));
        if (m_Left && m_Top && dx*dy > 0)
        {
            g.setLeft(dx > 0 ? g.left() + maxLen : g.left() - maxLen);
            g.setTop(dy > 0 ? g.top() + maxLen : g.top() - maxLen);
        }
        else if (m_Right && m_Top && dx * dy < 0)
        {
            g.setRight(dx > 0 ? g.right() + maxLen : g.right() - maxLen);
            g.setTop(dy > 0 ? g.top() + maxLen : g.top() - maxLen);
        }
        else if (m_Right && m_Bottom)
        {
            if (dx * dy > 0)
            {
                g.setRight(dx > 0 ? g.right() + maxLen : g.right() - maxLen);
                g.setBottom(dy > 0 ? g.bottom() + maxLen : g.bottom() - maxLen);
            }
            else if (dx == 0 && dy != 0
                /*|| dx != 0 && dy == 0*/)
            {
                ignore = true;
            }            
        }
        else if (m_Left && m_Bottom && dx*dy < 0)
        {
            g.setLeft(dx > 0 ? g.left() + maxLen : g.left() - maxLen);
            g.setBottom(dy > 0 ? g.bottom() + maxLen : g.bottom() - maxLen);
        }
        
        return g;
    }
    else if (lorr)
    {
        if (m_Left)
            g.setLeft(g.left() + dx);
        if (m_Right)
            g.setRight(g.right() + dx);
        int len = g.width() - oldgeo.width();
        int intHight = (int)len / 2.0;

        g.setTop(g.top() - intHight);
        g.setBottom(g.bottom() + len - intHight);
    }
    else if (torb)
    {
        if (m_Bottom)
            g.setBottom(g.bottom() + dy);
        if (m_Top)
            g.setTop(g.top() + dy);
        int dheigt = g.height() - oldgeo.height();
        int intWidth = (int)dheigt / 2.0;

        g.setLeft(g.left() - intWidth);
        g.setRight(g.right() + dheigt - intWidth);
    }
    else
    {
        ignore = true;
    }
    return g;
}
           

2、BackgroundWidget背景色窗口,该窗口作为剪切窗口的父类窗口,但是没有布局,目的就是可以让剪切窗口自由的移动,并保持在背景色窗口之上,当背景色窗口重新绘制的时候,只绘制除剪切窗口以外的部分。这样就实现了剪切窗口是透明的但是其余部分都是半透明的,代码如下:

void BackgroundWidget::paintEvent(QPaintEvent *)
{
    QPainterPath painterPath;
    QPainterPath p;
    p.addRect(x(), y(), rect().width(), rect().height());
    if (m_CutShape)
    {
        painterPath.addPath(m_CutShape->CutRegion().translated(m_CutShape->pos()));
    }
    QPainterPath drawPath = p.subtracted(painterPath);

    QPainter paint(this);
    paint.setOpacity(0.5);
    paint.fillPath(drawPath, QBrush(Qt::black));
}
           

3、PicturePreviewPanel图片上传控件基类,当图片加载后,需要重置背景色窗口的大小,以便覆盖到图片之上,代码如下:

bool PicturePreviewPanel::eventFilter(QObject * watched, QEvent * event)
{
    if (watched == m_PictureContainer)
    {
        if (event->type() == QEvent::Resize)
        {
            LoadPicture_p();
            if (m_BgWidget)
            {
                m_BgWidget->resize(m_PictureContainer->size());
            }
        }
    }
    return QWidget::eventFilter(watched, event);
}
           

之所以需要重新加载图片是,放置图片因为拖拉而失真,图片的加载的加载代码如下:

void PicturePreviewPanel::LoadPicture_p()
{
    QPixmap picture;
    picture.load(m_PicturePath);
    if (!picture.isNull())
    {
        picture = picture.scaled(m_PictureContainer->width(), m_PictureContainer->height());
        m_PictureContainer->setPixmap(picture);
        m_BgWidget->PictureLoadFinished();
    }
}
           

好了,有了上边的代码之后,这个图片上传空间的基本交互功能就完成了,点击保存截取图片,可以使用剪贴图基类的CutRegion方法获取剪贴的路径,并保存成指定图片格式。效果如图2所示

图2 效果展示

qt实现头像上传功能

实例代码下载

如果您觉得文章不错,不妨给个 打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!

qt实现头像上传功能
qt实现头像上传功能

很重要–转载声明

  1. 本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords
  2. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。

继续阅读