本系列所有文章可以在这里查看[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://box.kancloud.cn/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://box.kancloud.cn/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://box.kancloud.cn/2016-01-18_569cbd0b2f317.jpg) 由 ~~~ x + flip * dx * len / m_radius, y + flip * dy * len / m_radius ~~~ 可知当 flip 为正数时,越靠近滤镜中心的部分 x,y增量越大,表现为中心部分向外扩张得更厉害,并且保证坐标增量不至于超出滤镜范围,从而形成凸透镜的效果。 反之,flip为负数时,越靠近中心部分的x,y减去的值更大,从而使图形向中心收缩,形成凹透镜的效果。 主要就是这些啦~