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:
- PicturePreviewPanel: Image preview panel, base class for the control
- BackgroundWidget: Shadow window, a subclass of PicturePreviewPanel
- CutShape: Base class for interactive graphics, implements drag and resize functions
- CutRound: Circular clipping, inherits from CutShape, implements the paintInit interface
- 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:
- 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 }
- 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