Implementing Avatar Upload Functionality in Qt

Many users have encountered the avatar upload feature in Qt. Figure 1 should be familiar to most. This article demonstrates how to simulate a similar function, although it may not offer all the advanced features, it can fulfill basic requirements.

Figure 1: QQ image upload

Before diving into the functionality, I recommend reading a article on implementing a QQ-like avatar selection box using Qt. This article explains the avatar upload process, and my code is optimized from that demo. It includes code refactoring and fixes issues such as the border disappearing during fast dragging. Events and double buffering are used to minimize redrawing. Next, I will explain step by step how to implement custom image cropping.

I. Overveiw
First, I present four classes with their explanations:

  1. PicturePreviewPanel: Image preview panel, base class for the control
  2. BackgroundWidget: Shadow window, a subclass of PicturePreviewPanel
  3. CutShape: Base class for interactive graphics, implements drag and resize functions
  4. CutRound: Circular clipping, inherits from CutShape, implements the paintInit interface
  5. CutRectangle: Rectangular clipping, inherits from CutShape, implements the paintInit interface

II. Details
After understanding the five classes mentioned above, I will now explain each class's header file and key implementation interfaces. Custom graphic classes can be extended as needed.

The header file is as follows, with some comments. The code is straightforward and easy to understand. During development, only the PicturePreviewPanel class needs to be declared, and the LoadPicture interface can be called to load the image and perform image cropping.

  1 #include <QWidget>
  2 
  3 class QLabel;
  4 
  5 enum ShapeType
  6 {
  7     Rect,
  8     Round,
  9 };
 10 
 11 // Base class for clipping graphics, implements basic interaction functions and draws parts of the pattern. Main drawing is implemented in subclasses through the paintInit interface.
 12 class CutShape : public QWidget
 13 {
 14 public:
 15     CutShape(QWidget * parent = nullptr);
 16     ~CutShape();
 17 
 18 public:
 19     QPainterPath CutRegion();
 20 
 21 protected:
 22     // QWidget
 23     virtual void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE;
 24     virtual void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE;
 25     virtual void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE;
 26     virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE;
 27     virtual void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
 28 
 29     virtual bool paintInit(QPaintEvent *, QPaintDevice *) = 0;
 30     virtual QPainterPath Region(){ return QPainterPath(); };
 31 
 32 protected:
 33     ShapeType m_Type;
 34     bool m_MouseDown = false;
 35     bool m_IsMoving = false;
 36     bool m_IsFirst = true;
 37     int border = 5;
 38 
 39 private:
 40     QRect getResizeGem(QRect oldgeo, QPoint mousePoint, bool & ignore);
 41 
 42 private:
 43     bool m_EnableRepaint = true;
 44     bool m_Left = false;
 45     bool m_Right = false;
 46     bool m_Bottom = false;
 47     bool m_Top = false;
 48     QPoint m_startPoint;
 49     QPoint m_old_pos;
 50     QLabel * m_Label;
 51     QPixmap m_BufferPix;
 52 };
 53 
 54 class CutRectangle : public CutShape
 55 {
 56 public:
 57     CutRectangle(QWidget * parent = nullptr);
 58     ~CutRectangle();
 59 
 60 protected:
 61     // CutShape
 62     virtual bool paintInit(QPaintEvent *, QPaintDevice *) Q_DECL_OVERRIDE;
 63     virtual QPainterPath Region() Q_DECL_OVERRIDE;
 64 };
 65 
 66 class CutRound : public CutShape
 67 {
 68 public:
 69     CutRound(QWidget * parent = nullptr);
 70     ~CutRound();
 71 
 72 protected:
 73     // CutShape
 74     virtual bool paintInit(QPaintEvent *, QPaintDevice *) Q_DECL_OVERRIDE;
 75     virtual QPainterPath Region() Q_DECL_OVERRIDE;
 76 
 77 private:
 78 };
 79 
 80 class BackgroundWidget : public QWidget
 81 {
 82 public:
 83     BackgroundWidget(QWidget * parent = nullptr, ShapeType type = Round);
 84     ~BackgroundWidget();
 85 
 86 public:
 87     void PictureLoadFinished();
 88 
 89 protected:
 90     virtual void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
 91 
 92 private:
 93     ShapeType m_Type;
 94     CutShape * m_CutShape = nullptr;
 95 };
 96 
 97 class PicturePreviewPanel : public QWidget
 98 {
 99     Q_OBJECT
100 
101 public:
102     PicturePreviewPanel(QWidget * parent = nullptr);
103     ~PicturePreviewPanel();
104 
105 public:
106     void LoadPicture(const QString & filepath); // Load image
107 
108 protected:
109     virtual bool eventFilter(QObject *, QEvent *) Q_DECL_OVERRIDE;
110 
111 private:
112     void InitializeUI();
113     void LoadPicture_p();
114 
115 private:
116     QString m_PicturePath;
117     QLabel * m_PictureContainer = nullptr;
118     BackgroundWidget * m_BgWidget = nullptr;
119 };

View Code If you find this too detailed, you can skip the interface specifics and just understand the roles of the five classes mentioned earlier. Next, I will explain the relationships between the interactive graphics class, background class, and image display class (the base class for the control), as follows:

  1. CutShape clipping base class implements basic interaction functions and draws some patterns. Main drawing is done in subclasses by implementing the paintInit interface. This interface is called when the clipping shape is redrawn. Here is the code:
 1 void CutShape::paintEvent(QPaintEvent * event)
 2 {
 3     QPainter paint;
 4     if (m_EnableRepaint && paintInit(event, &m_BufferPix))
 5     {
 6         m_EnableRepaint = false;
 7         qDebug() << "event->type()" << event->type();
 8         m_BufferPix = QPixmap(size());
 9         m_BufferPix.fill(Qt::transparent);
10 
11         paint.begin(&m_BufferPix);
12         QPen pen0;
13         pen0.setColor(QColor(54, 158, 254, 120));
14         pen0.setWidth(2);
15         paint.setPen(pen0);
16         paint.drawRect(border, border, width() - border * 2, width() - border * 2);
17 
18         QPen pen;
19         QVector<qreal> dashes;
20         qreal space = 3;
21         dashes << 5 << space << 5 << space;
22         pen.setDashPattern(dashes);
23         pen.setColor(Qt::white);
24 
25         paint.setPen(pen);
26         int x_pos = (int)width() / 3.0;
27         int y_pos = (int)height() / 3.0;
28         paint.drawLine(x_pos, border, x_pos, height() - 2 * border);
29         paint.drawLine(2 * x_pos, border, 2 * x_pos, height() - 2 * border);
30         paint.drawLine(border, y_pos, width() - 2 * border, y_pos);
31         paint.drawLine(border, 2 * y_pos, width() - 2 * border, 2 * y_pos);
32         paint.end();
33     }
34     paint.begin(this);
35     paint.drawPixmap(rect(), m_BufferPix);
36     paint.end();
37 }

This is the main drawing process. For the interaction functions of the clipping graphics, I provide the specific implementation without going into detail. The code is lengthy, as shown below:

  1 void CutShape::mousePressEvent(QMouseEvent * event)
  2 {
  3     m_startPoint = event->pos();
  4     m_MouseDown = event->button() == Qt::LeftButton;
  5 }
  6 
  7 void CutShape::mouseMoveEvent(QMouseEvent * event)
  8 {
  9     QPoint dragPoint = event->pos();
 10     if (!parentWidget()->rect().contains(mapToParent(dragPoint)))
 11     {
 12         return;
 13     }
 14     int x = event->x();
 15     int y = event->y();
 16     if (m_MouseDown)
 17     {
 18         if (m_Left == false && m_Right == false
 19             && m_Bottom == false && m_Top == false)
 20         {
 21             QPoint p = QPoint((pos().x() + dragPoint.x() - m_startPoint.x()), (pos().y() + dragPoint.y() - m_startPoint.y()));
 22             QPoint dragEndge = p;
 23             dragEndge.setX(dragEndge.x() + rect().width());
 24             dragEndge.setY(dragEndge.y() + rect().height());
 25             p.setX(p.x() < 0 ? 0 : p.x());
 26             p.setX(dragEndge.x() > parentWidget()->width() ? parentWidget()->width() - rect().width() : p.x());
 27             p.setY(p.y() < 0 ? 0 : p.y());
 28             p.setY(dragEndge.y() > parentWidget()->height() ? parentWidget()->height() - rect().height() : p.y());
 29             move(p);
 30         }
 31         else
 32         {
 33             bool ignore = false;
 34             QRect g = getResizeGem(geometry(), dragPoint, ignore);
 35             if (parentWidget()->rect().contains(g))
 36                 setGeometry(g);
 37             if (ignore == false)
 38             {
 39                 m_startPoint = QPoint(!m_Right ? m_startPoint.x() : event->x(), !m_Bottom ? m_startPoint.y() : event->y());
 40             }
 41         }
 42     }
 43     else
 44     {
 45         QRect r = rect();
 46         m_Left = qAbs(x - r.left()) < 5;
 47         m_Right = qAbs(x - r.right()) < 5;
 48         m_Bottom = qAbs(y - r.bottom()) < 5;
 49         m_Top = qAbs(y - r.top()) < 5;
 50         bool lorr = m_Left | m_Right;
 51         bool torb = m_Top | m_Bottom;
 52         if (lorr && torb)
 53         {
 54             if ((m_Left && m_Top) || (m_Right && m_Bottom))
 55             {
 56                 setCursor(Qt::SizeFDiagCursor);
 57             }
 58             else
 59                 setCursor(Qt::SizeBDiagCursor);
 60         }
 61         else if (lorr)
 62             setCursor(Qt::SizeHorCursor);
 63         else if (torb)
 64             setCursor(Qt::SizeVerCursor);
 65         else
 66         {
 67             setCursor(Qt::SizeAllCursor);
 68             m_Bottom = m_Left = m_Right = m_Top = false;
 69         }
 70     }
 71 }
 72 
 73 void CutShape::mouseReleaseEvent(QMouseEvent * event)
 74 {
 75     m_MouseDown = false;
 76 }
 77 
 78 void CutShape::resizeEvent(QResizeEvent *event)
 79 {
 80     m_EnableRepaint = true;
 81     update();
 82 
 83     QWidget::resizeEvent(event);
 84 }
 85 
 86 QRect CutShape::getResizeGem(QRect oldgeo, QPoint mousePoint, bool & ignore)
 87 {
 88     QRect g = oldgeo;
 89     bool lorr = m_Left | m_Right;
 90     bool torb = m_Top | m_Bottom;
 91     int dx = mousePoint.x() - m_startPoint.x();
 92     int dy = mousePoint.y() - m_startPoint.y();
 93     ignore = false;
 94     if (lorr && torb)
 95     {
 96         int maxLen = qMax(qAbs(dx), qAbs(dy));
 97         if (m_Left && m_Top && dx*dy > 0)
 98         {
 99             g.setLeft(dx > 0 ? g.left() + maxLen : g.left() - maxLen);
100             g.setTop(dy > 0 ? g.top() + maxLen : g.top() - maxLen);
101         }
102         else if (m_Right && m_Top && dx * dy < 0)
103         {
104             g.setRight(dx > 0 ? g.right() + maxLen : g.right() - maxLen);
105             g.setTop(dy > 0 ? g.top() + maxLen : g.top() - maxLen);
106         }
107         else if (m_Right && m_Bottom)
108         {
109             if (dx * dy > 0)
110             {
111                 g.setRight(dx > 0 ? g.right() + maxLen : g.right() - maxLen);
112                 g.setBottom(dy > 0 ? g.bottom() + maxLen : g.bottom() - maxLen);
113             }
114             else if (dx == 0 && dy != 0
115                 /*|| dx != 0 && dy == 0*/)
116             {
117                 ignore = true;
118             }            
119         }
120         else if (m_Left && m_Bottom && dx*dy < 0)
121         {
122             g.setLeft(dx > 0 ? g.left() + maxLen : g.left() - maxLen);
123             g.setBottom(dy > 0 ? g.bottom() + maxLen : g.bottom() - maxLen);
124         }
125         
126         return g;
127     }
128     else if (lorr)
129     {
130         if (m_Left)
131             g.setLeft(g.left() + dx);
132         if (m_Right)
133             g.setRight(g.right() + dx);
134         int len = g.width() - oldgeo.width();
135         int intHight = (int)len / 2.0;
136 
137         g.setTop(g.top() - intHight);
138         g.setBottom(g.bottom() + len - intHight);
139     }
140     else if (torb)
141     {
142         if (m_Bottom)
143             g.setBottom(g.bottom() + dy);
144         if (m_Top)
145             g.setTop(g.top() + dy);
146         int dheigt = g.height() - oldgeo.height();
147         int intWidth = (int)dheigt / 2.0;
148 
149         g.setLeft(g.left() - intWidth);
150         g.setRight(g.right() + dheigt - intWidth);
151     }
152     else
153     {
154         ignore = true;
155     }
156     return g;
157 }

View Code2. BackgroundWidget background window, which acts as the parent window for the clipping window but has no layout. Its purpose is to allow the clipping window to move freely while staying on top of the background window. When the background window is repainted, only the part outside the clipping window is drawn. This makes the clipping window transparent while the rest is semi-transparent. The code is as follows:

 1 void BackgroundWidget::paintEvent(QPaintEvent *)
 2 {
 3     QPainterPath painterPath;
 4     QPainterPath p;
 5     p.addRect(x(), y(), rect().width(), rect().height());
 6     if (m_CutShape)
 7     {
 8         painterPath.addPath(m_CutShape->CutRegion().translated(m_CutShape->pos()));
 9     }
10     QPainterPath drawPath = p.subtracted(painterPath);
11 
12     QPainter paint(this);
13     paint.setOpacity(0.5);
14     paint.fillPath(drawPath, QBrush(Qt::black));
15 }

  1. PicturePreviewPanel image upload base class. After loading the image, the size of the background window needs to be reset to cover the image. The code is as follows:
 1 bool PicturePreviewPanel::eventFilter(QObject * watched, QEvent * event)
 2 {
 3     if (watched == m_PictureContainer)
 4     {
 5         if (event->type() == QEvent::Resize)
 6         {
 7             LoadPicture_p();
 8             if (m_BgWidget)
 9             {
10                 m_BgWidget->resize(m_PictureContainer->size());
11             }
12         }
13     }
14     return QWidget::eventFilter(watched, event);
15 }

The reason for reloading the image is to prevent distortion due to dragging. The image loading code is as follows:

 1 void PicturePreviewPanel::LoadPicture_p()
 2 {
 3     QPixmap picture;
 4     picture.load(m_PicturePath);
 5     if (!picture.isNull())
 6     {
 7         picture = picture.scaled(m_PictureContainer->width(), m_PictureContainer->height());
 8         m_PictureContainer->setPixmap(picture);
 9         m_BgWidget->PictureLoadFinished();
10     }
11 }

With the above code, the basic interaction functionality of the image upload control is complete. To save the cropped image, the CutRegion method of the clipping base class can be used to get the clipped path and save it in the desired format. The result is shown in Figure 2.

Figure 2: Result display

Sample code download: http://download.csdn.net/detail/qq_30392343/9581238

Tags: Qt C++ gui Image Processing Widget

Posted on Wed, 15 Jul 2026 16:29:41 +0000 by jkurrle