一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 14 天,点击查看活动详情。
📒博客首页:何名取 的个人主页 - 文章 - 掘金 (juejin.cn)
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
❤️期待一起交流!
🙏作者水平很有限,如果发现错误,求告知,多谢!
🌺有问题可私信交流!!!
QML Canvas类型
前言
前面在学习使用QML粒子系统时制作了一个动态的艾尔登法环,将法环上加上了火焰粒子。QML粒子系统,点燃🔥 艾尔登法环 - 掘金 (juejin.cn)在文章中我使用了遮罩的方式来发射粒子,遮罩使用的是一张处理过的透明背景图片。当时我观察到法环的形状并不复杂,主要部分可以分解为三个小圆和一个大圆。
//! [0]
anchors.fill: parent
shape: MaskShape {
source: "qrc:/ring.png"
}
//! [0]
Canvas2D可以让我们像画画一样绘制图形图案。因此,我们可以使用今天的主角QML Canvas来绘制出法环图形。
QML Canvas简介
Canvas项允许绘制直线和曲线、简单和复杂的形状、图形和参考的图形图像。它还可以添加文本、颜色、阴影、渐变和模式,并进行低级别像素操作。Canvas输出可以保存为图像文件或序列化到URL。
QML Canvas类型与HTML5大致相同,移植了现有的HTML5 Canvas API,不同之处在于一些事件的处理方式和增加了一些Qt特有的信号。比如将HTML中的事件处理转为了鼠标事件MouseArea item。
属性列表
属性 | 类型 | 描述 |
---|---|---|
available | bool | 该属性用于设置Canvs是否可用,只有为true时后续的操作才有效。 |
canvasSize | size | 画布大小。默认情况下,画布大小与当前Canvas的width和height相同. |
context | object | 保存活动的绘图上下文。如果画布已经准备好,并且成功调用了getContext(),或者contextType属性已经设置了支持的上下文类型,那么这个属性将包含当前的绘图上下文,否则为空。 |
contextType | string | 保存绘图上下文的类型,对于Context2D,该值将为"2d" |
renderStrategy | enumeration | 保存当前画布渲染策略,取值有以下几种: Canvas.Immediate - 在UI主线程中立即执行图形命令(默认) Canvas.Threaded - 当前要绘制的图形延迟到专用的线程里执行(不在UI主线程中立即执行) Canvas.Cooperative - 当前要绘制的图形延迟到应用程序的全局渲染线程 |
renderTarget | enumeration | 渲染目标.取值有以下几种: Canvas.Image -渲染到内存中的图像缓冲区(默认) Canvas.FramebufferObject -渲染到OpenGL帧缓冲区 |
信号列表
信号 | 描述 |
---|---|
imageLoaded() | 加载图像时发出此信号。 |
paint(rect region) | 当需要渲染区域时,会发出此信号。我们可以在该信号接收器里写入要绘制的内容。 |
painted() | 在执行所有上下文绘制命令并渲染Canvas(onPaint之后)后发出。 |
我们在进行绘制时主要使用的就是与paint相应的处理函数onPaint来实现图形图案。
函数列表
函数 | 描述 |
---|---|
cancelRequestAnimationFrame(handle) | 取消请求动画框架(句柄) |
object getContext(contextId, ... args) | 返回图形上下文,如果没有可用的上下文,则返回null。在第一次调用getContext之后,后续再次调用时都将返回相同的上下文对象 |
isImageError(image) | 如果图像加载失败,则返回true,否则返回false。 |
isImageLoaded(image) | 如果图像已成功加载并准备好使用,则返回true。 |
isImageLoading(image) | 如果图像当前正在加载,则返回true。 |
loadImage(image) | 异步加载图像。一旦图像准备好,将发出imageLoaded()信号。加载的图像可以使用unloadImage()方法卸载。 |
markDirty(area) | 将给定区域标记为等待刷新的区域(只会刷新一次),只要该此区域为可见区,画布渲染器将重新绘制它。这将触发requestPaint信号。 |
int requestAnimationFrame(callback) | 动画计时器,它会自动根据电脑配置来计算刷新帧率,一般与屏幕刷新频率一致,它的返回值是一个计数值,可以获取当前调用了callback多少次 |
requestPaint() | 请求重新绘制整个可见区域,将会重新发出paint信号 |
bool save(filename) | 将当前画布内容保存到图像文件文件名中。保存的图像格式由文件名的后缀自动决定。成功时返回true,比如save("1.png") |
string toDataURL(mimeType) | 返回画布中图像的数据URL。mimeType参数默认为“image/png”。 |
unloadImage(image) | 卸载图像后,除非再次加载图像(loadImage()、Context2D::createImageData()和Context2D::drawImage()),否则画布上下文将无法绘制图像 |
本节中我们主要使用toDataURL函数来将绘制出的法环导入到粒子系统中作为发射器的遮罩使用。
绘制法环
设计思路
前言中已经提到,法环的形状是由三个小圆和一个大圆构成。将大圆放置在画布正中,以大圆的圆心作为坐标原点。找出大圆的内心三角形的位置,它的三个边的中点就是三个小圆的圆心。小圆的直径则为这个三角形的边长。三个小圆呈“品”字护持在大圆上。
绘制大圆
ctx.beginPath();
ctx.arc(ox0, oy0, r, 0, 2 * Math.PI);
ctx.stroke();
将大圆的圆心设置为坐标原点,也就是画布的中心ox0和oy0,半径为r。
绘制小圆
绘制小圆时需要根据大圆的位置和半径来计算小圆的位置和半径。
上方小圆
var oy2 = r * Math.cos(Math.PI/3);
var r2 = r * Math.sin(Math.PI/3);
ctx.beginPath();
ctx.arc(ox0, oy0 - oy2, r2, 0, 2 * Math.PI);
ctx.stroke();
右侧小圆
var o1o3 = r * Math.sin(Math.PI/6);
ox3 = o1o3 * Math.cos(Math.PI/6);
oy3 = o1o3 * Math.sin(Math.PI/6);
ctx.beginPath();
ctx.arc(ox0 + ox3, oy0 + oy3, r2, 0, 2 * Math.PI);
ctx.stroke();
左侧小圆
ctx.beginPath();
ctx.arc(ox0 - ox3, oy0 + oy3, r2, 0, 2 * Math.PI);
ctx.stroke();
绘制上下圆弧
绘制完成圆环后,则需要将上下圆弧部分进行绘制。圆弧的绘制比较简单,上面是一段小圆弧,中间一条直线连接,下面一段大圆弧。
ctx.beginPath();
ctx.arc(ox0, oy0 - 4*r, 2*r, Math.PI/3, Math.PI - Math.PI/3);
ctx.moveTo(ox0, oy0 - 2*r);
ctx.lineTo(ox0, oy0 + 1.5*r);
ctx.stroke();
ctx.beginPath();
ctx.arc(ox0, oy0 - 2*r, 3.5*r, Math.PI/3, Math.PI - Math.PI/3);
ctx.stroke();
效果展示
点燃法环
加上粒子效果
既然法环已经绘制好了,那么就赶紧加入粒子系统来点燃它吧。
在这里为了显示出与之前的图片遮罩不同,加入了鼠标点击后再发射火焰的处理。在点击鼠标之前,canvas只对法环进行绘制,点击鼠标后,toDataURL()函数将绘制的图形的URL导入发射器中,开启粒子系统发射粒子。
鼠标处理
MouseArea {
anchors.fill: parent
onClicked: {
ringImage = canvas.toDataURL()
particles.running = true
}
}
在此处使用了toDataURL()函数,将绘制的法环以URL进行返回。
发射器遮罩
//! [0]
anchors.fill: parent
shape: MaskShape {
source: ringImage
}
//! [0]
}
这里的遮罩不再使用之前找到的图片素材,而是以上面canva绘制出法环图形作为遮罩。
效果展示
点击下面的链接可以获取完整的代码。有兴趣的小伙伴可以持续关注。
感谢❤️💛💚💙💜💗