37——Vector Deformation
最后更新于:2022-04-01 07:22:29
本系列所有文章可以在这里查看[http://blog.csdn.net/cloud_castle/article/category/2123873](http://blog.csdn.net/cloud_castle/article/category/2123873)
接上文[](http://blog.csdn.net/cloud_castle/article/details/43672509)[Qt5官方demo解析集36——Wiggly Example](http://blog.csdn.net/cloud_castle/article/details/44018679)
在 Qt 中设计GUI界面,经常需要考虑不同尺寸,不同分辨率下面的情况,因此我们经常需要准备几套图片素材来应付不同的场景。不过好在,我们还可以使用矢量绘图和矢量图形。
今天这个例子基于 QPainterPath 绘制了文本字符的矢量路径,并可以在一个"滤镜"范围内进行变形处理,效果萌萌哒~下面就来看看吧:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-18_569cbd0aee1db.jpg)
这个例子看起来代码量较多,我们关心的核心处理也就是几行,经过抽丝剥茧,去掉布局按钮定时器之类的逻辑,其实这也就是个小demo啦。
还是先从main.cpp来看:
~~~
#include "pathdeform.h"
#include <QApplication>
int main(int argc, char **argv)
{
Q_INIT_RESOURCE(deform);
QApplication app(argc, argv);
bool smallScreen = QApplication::arguments().contains("-small-screen");
PathDeformWidget deformWidget(0, smallScreen);
QStyle *arthurStyle = new ArthurStyle();
deformWidget.setStyle(arthurStyle);
QList<QWidget *> widgets = deformWidget.findChildren<QWidget *>();
foreach (QWidget *w, widgets)
w->setStyle(arthurStyle);
if (smallScreen)
deformWidget.showFullScreen();
else
deformWidget.show();
#ifdef QT_KEYPAD_NAVIGATION
QApplication::setNavigationMode(Qt::NavigationModeCursorAuto);
#endif
return app.exec();
}
~~~
在上一个例子中我们提到了这个 smallScreen,这里就不多提啦,注意将这个参数传递给我们的窗口,以做不同的处理。
接着它将窗口风格设置为 “ArthurStyle”,这个类是在 shared 子工程中定义的,继承自 QCustomStyle,之所以放在shared 中是因为有好几个 demo 是使用它的。如果大家一直在做 Qt 开发,不妨留心建立下自己的“Qt库”,把可扩展性和通用性考虑进去之后,就可以在下一个项目中复用一些东西啦。
好,不扯远了,如果屏蔽掉这几行 setStyle 语句后,可以看到这个 control 面板几乎不忍直视。。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-18_569cbd0b1bd35.jpg)
不看 shared 子工程,我们就剩 pathdeform.h 和 pathdeform.cpp 俩文件了,不过里面定义了三个类:
PathDeformWidget、PathDeformControls、PathDeformRenderer,分别是主窗口,控件窗体和渲染窗体
可以看到,控件窗体 PathDeformControls 针对 smallScreen 参数设计了两套布局
layoutForDesktop()
layoutForSmallScreen()
以应对不同的场景。
接下来,绘制“滤镜”并保存在 QImage 或 QPixmap 中,代码就不帖啦,
然后使用 QPainterPath 的 addText 将用户输入的字符添加到绘制路径中:
~~~
bool do_quick = true;
for (int i=0; i<text.size(); ++i) {
if (text.at(i).unicode() >= 0x4ff && text.at(i).unicode() <= 0x1e00) {
do_quick = false;
break;
}
}
if (do_quick) {
for (int i=0; i<text.size(); ++i) {
QPainterPath path;
path.addText(advance, f, text.mid(i, 1));
m_pathBounds |= path.boundingRect();
m_paths << path;
advance += QPointF(fm.width(text.mid(i, 1)), 0);
}
} else {
QPainterPath path;
path.addText(advance, f, text);
m_pathBounds |= path.boundingRect();
m_paths << path;
}
~~~
有意思的是,针对字符的不同编码,这里使用了两种不同的添加方式:
Unicode编码大于 0X4FF 且 小于 0X1E00 的字符,直接将整个文本添加到路径中去;
否则,使用循环语句添加该字符串中每个字符。
根据 do_quick 命名似乎是第二种方式要更快速一些,不过暂时没有找到相关的证明-.-
函数lensDeform()用来返回一个经过变形的QPainterPath,并由 painter 绘制出来:
~~~
QPainterPath PathDeformRenderer::lensDeform(const QPainterPath &source, const QPointF &offset)
{
QPainterPath path;
path.addPath(source);
qreal flip = m_intensity / qreal(100);
for (int i=0; i<path.elementCount(); ++i) {
const QPainterPath::Element &e = path.elementAt(i);
qreal x = e.x + offset.x();
qreal y = e.y + offset.y();
qreal dx = x - m_pos.x();
qreal dy = y - m_pos.y();
qreal len = m_radius - qSqrt(dx * dx + dy * dy);
if (len > 0) {
path.setElementPositionAt(i,
x + flip * dx * len / m_radius,
y + flip * dy * len / m_radius);
} else {
path.setElementPositionAt(i, x, y);
}
}
return path;
}
~~~
这里通过 len 判断 QPainterPath 的元素是否在 “滤镜”内,len值越大,离“滤镜”中心点越近。
一个在圆内的点各参数可以表示为下面这张图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-18_569cbd0b2f317.jpg)
由
~~~
x + flip * dx * len / m_radius,
y + flip * dy * len / m_radius
~~~
可知当 flip 为正数时,越靠近滤镜中心的部分 x,y增量越大,表现为中心部分向外扩张得更厉害,并且保证坐标增量不至于超出滤镜范围,从而形成凸透镜的效果。
反之,flip为负数时,越靠近中心部分的x,y减去的值更大,从而使图形向中心收缩,形成凹透镜的效果。
主要就是这些啦~