想必大家都使用过qt的自定义头像功能吧,那么图1应该不会陌生,本片文章我就是要模拟一个这样的功能,虽然没有这么强大的效果,但是能够满足一定的需求。
图1 qq上传图片
首先在讲解功能之前,我先给出一片文章,QT实现的类似QQ的头像选择框,这篇文章也是讲解头像上传功能的,而我自己的代码是从这个demo中优化而来,不仅对代码进行了重构,而且处理了快速拖动时,边框消失的问题。使用事件和双缓冲来尽量减少重新绘制的几率。接下里我会一步一步进行讲解,怎么实现图片自定义截取功能。
一、概要
首选,我给出4个类,并给出他们的解释
1、PicturePreviewPanel:图标展示框,控件基类
2、BackgroundWidget:阴影窗口,是PicturePreviewPanel子类
3、CutShape:交互图形基类,实现了拖拽和放大缩小功能
4、CutRound:圆形剪贴,父类为CutShape,实现父类的paintInit接口重绘
5、CutRectangle:矩形剪贴,父类为CutShape,实现父类的paintInit接口重绘
二、详情
理解了上述5个类之后,接下来我分别讲解下类的头文件和重要的实现接口,其中自定义图形类也可以自己在扩充
头文件代码如下,有部分注释,代码应该不难理解,开发过程中只需要声明PicturePreviewPanel类,并调用期LoadPicture接口就可以加载图片,并进行图片剪切
1 #include <QWidget>
2
3 class QLabel;
4
5 enum ShapeType
6 {
7 Rect,//矩形
8 Round,//圆形
9 };
10
11 //剪贴图基类 实现了基本的交互功能,并绘制了部分图案,主要的团绘制在子类实现,通过实现paintInit接口
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);//加载图片
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 };
上述头文件,如果觉得麻烦了也可以不具体细看接口,只要理解了标题1下边的5个类的作用就可以了。下边我分别说下交互图形类、背景色类和显示图片类(即控件基类)这三者直接的一些关系,具体如下:
1、CutShape剪贴图基类 实现了基本的交互功能,并绘制了部分图案,主要的绘制在子类实现,通过实现paintInit接口,该接口在剪切图街基类重新绘制时调用,代码如下:
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 }
上边是主要的绘制过程,关于剪切图形的交互功能,我给出具体的大姨妈,就不仔细讲解功能了,代码有些长,如下:
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 }
2、BackgroundWidget背景色窗口,该窗口作为剪切窗口的父类窗口,但是没有布局,目的就是可以让剪切窗口*的移动,并保持在背景色窗口之上,当背景色窗口重新绘制的时候,只绘制除剪切窗口以外的部分。这样就实现了剪切窗口是透明的但是其余部分都是半透明的,代码如下:
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 }
3、PicturePreviewPanel图片上传控件基类,当图片加载后,需要重置背景色窗口的大小,以便覆盖到图片之上,代码如下:
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 }
之所以需要重新加载图片是,放置图片因为拖拉而失真,图片的加载的加载代码如下:
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 }
好了,有了上边的代码之后,这个图片上传空间的基本交互功能就完成了,点击保存截取图片,可以使用剪贴图基类的CutRegion方法获取剪贴的路径,并保存成指定图片格式。效果如图2所示
图2 效果展示
实例代码下载:http://download.csdn.net/detail/qq_30392343/9581238
http://www.cnblogs.com/swarmbees/p/5688885.html