Qt中利用GraphicsView实现可部分擦除的Item的思绪解析
可部分擦除Item的抽象概念解析数学意义上来说,一条线其实并没有宽度的概念,它是由无数的点连接而成的。而面积,是由无数的线所构成的。
在现实中却并非如此,无论你用什么笔,点都会具有面积。这就意味着我们的线也有面积。这就意味着,我们的白板上的笔所留下来的陈迹也需要“模仿”这一特性。
比如说,我们画矩形的时候,矩形的边并非是由一条线构成,而是另一些有着面积的矩形构成的,只不外这个矩形的宽非常窄。就像下面如许:
https://img2024.cnblogs.com/blog/3397714/202409/3397714-20240903162653436-1045060926.png
只是我平常没有注意到,下意识地以为本身所连的一条线,真的就是数学意义上的线。因此,我们使用Qt中有关连线的接口,所画出来的线Line,也不是我们所想要的“线”。我们所想要的线,应该是Qt所称的填充地区的东西!
我们所要完成的笔画,其实就是一种填充地区,这个填充地区模仿了现实中的笔画。比如,我们想要在程序中画一条直线,它并不是一条直线,而是颠末看起来像是直线的填充地区,就像下面这张图所示:
https://img2024.cnblogs.com/blog/3397714/202409/3397714-20240903162710816-598192550.png
因为其宽度足够小,所以人在感官上就以为这是一条直线,但现实上不是,它是一个有面积的填充地区。
如许抽象的好处是什么?
假如,我们将Qt中所提供的线,当做我们所想要模仿的现实中的“线”,情况怎么样?
Qt中的线,完满是按照数学中的界说来进行处理的。
如许做的话,假如我们有一条Qt中的线line,和一个橡皮擦所覆盖的地区S。我们规定:与橡皮擦S碰撞的全部线的部分,都必须被擦除。
line与S确实发生了碰撞,然后我们试图使用S(用QPainterPath表示)的intersected(line)来获取碰撞的地区,你什么也不会得到,因为Qt中碰撞产生的基础是碰撞的双方必须具有面积,而line没有面积,因此它不会和任何东西产生碰撞!这就与我们现实中的抽象产生了辩论,在我们的认知中line和S它们确实发生了碰撞。
这个问题产生的根本缘故原由就在于,数学意义上的“线”它没有面积,它本不应该有任何现实意义的呈现方式。但是Qt为了体现出线是一条线,它在渲染的时候给线添加了宽度,让我们擅自以为Qt所渲染的线,其实和现实中的线一样是有宽度的,但其实并非如此。
Qt中渲染的线的宽度并非是线本身的概念,而是渲染它的QPen所赋予的。
可部分擦除的Item的活动逻辑抽象
正是因为Qt中的线无法模仿现实中的线,所以我们必须要自界说本身的线用来模仿现实中的笔所画的线。
以矩形为例,我们的矩形,并不是由Qt中的线概念形成的矩形,也就是QRect并不能表示我们想要的矩形(QRect以为由矩形围起来的地区都是矩形本身)。
https://img2024.cnblogs.com/blog/3397714/202409/3397714-20240903162920582-298958705.png
我们的矩形仅包括模仿的线的填充地区本身。也就是这张图的赤色地区。
https://img2024.cnblogs.com/blog/3397714/202409/3397714-20240903162934917-1520489481.png
这两者是完全不同的概念,举个例子:对于QRect来说,S(橡皮擦)只要和矩形内部有相交地区,那么就可以被当做出现了碰撞;然而,对于我们的矩形来说,S可以和赤色框内部的地区相交,且不以为这是碰撞。这对我们简化了我们处理橡皮擦碰撞的处理逻辑,你不消再担心橡皮擦有没有真正碰撞到矩形的边框了(这个边框的宽度会随着QPen而变更,且无法通过Qt中给定的碰撞接口来获取)。
那么我们的矩形在碰到擦除的时候该怎么处理呢?假设矩形名为myRect(用QPainterPath表示),橡皮擦地区为为S(用QPainterPath表示),你就可以直接调用
remain = myRect.subtracted(S);就可以将剩余的矩形轨迹获取出来存放到remain中。剩余的矩形地区可能是什么样的呢?可以是如许的:
https://img2024.cnblogs.com/blog/3397714/202409/3397714-20240903162950086-2029173016.png
https://img2024.cnblogs.com/blog/3397714/202409/3397714-20240903163005304-876814221.png
擦除之后就不再是矩形了,那用什么表示呢?
我想出了如许一种抽象:
其实不论是矩形,还是圆形,都是使用笔所画出来的填充地区,擦除之后依旧可以以为是一个笔画。而矩形只是使用特殊的笔画顺序所生成的一个整体而已。同时,这个整体在初始化时因碰撞而连接,在初始化完成之后不会因为碰撞而连接(这个初始化阶段类比于我们下笔到笔脱离纸的过程),当我们再次下笔,那么新形成的笔画不应该和原来在纸上的笔画有任何形式的接洽。
同时,笔画在形成后不会因新笔画的碰撞而连接,但却有可能因为与橡皮擦碰撞而分离。橡皮擦的碰撞不肯定导致笔画的分离,比如:
https://img2024.cnblogs.com/blog/3397714/202409/3397714-20240903163023818-1669553481.png
但有的时候,擦除会使得一个笔画分离,比如:
https://img2024.cnblogs.com/blog/3397714/202409/3397714-20240903163036672-1649530117.png
如许的情况下,一个笔画就被分成了两个笔画,我们应该将这原本是一个笔画的部分分成两个笔画。如许做的技术难度也不大,就是使用QPainterPath::toFillPolygons()将返回的填充地区都封装成一个新笔画,然后本身再消失就行了。如许笔画之间的关系就被分离,每个笔画可以被单独移动、擦除。
解决方案探索过程
[*]利用Qt中的线来表示白板中的线。
[*]试图制造有宽度的点对象,将多个点Item组合形成一个Item。即,我们将笔划切割成一个极小的构成部分,以实现精确擦除功能,但是如许做的时候绘图的逻辑复杂,且处理的计算复杂度高。而且这个极小的构成部分的管理逻辑不好把控。用点构成线的粒度不好把控,按照数学逻辑来说需要界说无数个点才能形成一条线,而且点和点之间可能会存在渲染辩论(特别是渲染激光笔的时候的辩论,外围的阴影效果非常不好实现)。
[*]将笔画抽象成填充地区,利用Qt提供的处理填充地区的接口实现精确擦除功能,让可以将单个笔画看做整体来处理(而不是由极多的小部分构成的),降低了计算的复杂度。但绘图逻辑仍然需要计划,但相较于第2种,复杂度是降低了的。
我们还考虑到打消操纵的实现(Memento计划模式):
[*]对于第2种来说,我们可以记录多出来什么item,少了什么item都可以当做是Scene/Item状态改变的一部分,通过管理这些状态可以实现打消操纵。但是由于原子粒度是一个对象,它本身占据空间来构成一个大Item,所以必然需要巨量的这些Item,这即需要存储的控件,又需要处理的时间。
[*]对于第3中来说,我们也是通过记录Scene中Item的状态改变。
[*]在空间方面,我们利用Qt的接口处理不同的填充地区,假如我们添加一个笔画,那就只是添加了一个填充地区Item,而并非由非常多的Item构成的一条笔画;而假如我们擦除笔划,那么造成的笔画分离也不会产出太多了填充地区,这对空间是极大的减负。
[*]而在处理速度方面,一方面我们处理一个笔画不再需要处理笔画里大量的点Item,只是进行填充地区间的运算;另一方面,由于单个笔画Item对象得到了简化,所以处理速度得到加快
从原理上来说,第2种方案和第3种方案实现打消的原理是一样的。但第2种方案由于要处理笔画Item本身的复杂性导致其时间和空间复杂度都高,而第3种方案通过优化笔画的实现,减小了时间和空间的复杂度。
经验教训总结
一切都是需求分析错误导致的灾难
在我为了实现画曲线效果的时候,我确实详细学习过QPainterPath的每一个接口的功能。
在我实验QPainterPath的时候,我发现它规定只有填充地区之间才可以或许进行一系列的相交等操纵,它无法对直线与直线之间,直线与填充地区之间进行相交操纵。于是我以为,无法通过QPainterPath的接口完成擦除功能,因为它没有办法检测线与擦除地区之间的相交。
后来,我实践了第二种方法,发现如许完成的笔画线及其复杂,时间和空间复杂度都极其高。于是,从性能角度来看,我以为这种方式实现可擦除的笔画Item不可取。
这对我打击很大,花了2、3天找出的解决方案没有得到应有的成果,我暂时没有想到第3种方案,但提出第2中方法的改进方案:
[*]通过缩小点Item的粒度来减少性能损耗。
[*]通过优化算法来优化性能。
我考虑到画曲线的优化可能会运用到贝赛尔曲线去优化算法,所以用QPainterPath去看看怎样运用贝塞尔曲线。
机缘巧合之下,我重新试了试用直线和填充地区相交的实验,发现依然行不通,只有填充地区之间才能相交。那时的我已经通过实验第2种解决方案明确了一件事:用来构成线的点存在宽度,那么这条线肯定也是有宽度的!线具有填充地区!但现实情况确是:线和填充地区之间却不可以或许相交,这是为什么?
而探索这个问题的答案,得出的结论就是上面的第3种解决方案。
发现一个细小的差别,然后探索缘故原由,最终得出一个答案:因为Qt的线是数学意义上的线,无法与任何填充地区相交,而我抱负中的线却有与填充地区相交的本领,因此我抱负中的线并非数学意义上的线!一开始没故意识到这一点是因为我以为白板软件想要的线是一种数学意义上的线,但现实上它不是一种数学意义上的线!
归根接地,是现实天下到软件天下的概念映射没有真正意义上的完成,所以导致一开始没有思绪。后面通过实践发现,白板软件的线跟数学意义上的线是有差别的,但被Qt渲染的时候掩盖了(能画出来阐明有面积,但Qt的底层处理中线并没有面积,它的呈现状态和它底层的情况有差别!),让我忽略了一个点:数学上的线不应该有面积,因此又误导我以为Qt中的线有面积(但现实上Qt中的线没有面积)。
总结一开始没有想到好的解决方案的缘故原由:
[*]以为白板软件中线没有面积(需求分析错误)。
[*]Qt中渲染的线状态和现实底层处理线的逻辑存在矛盾,但我没有深入研究“线不能和填充地区相交”如许现象的缘故原由。这里其实是需求分析不够彻底甚至是错误导致的,因为没有对白板软件的线进行明确的界说和需求分析,所以不知道白板软件想要的线是什么就擅自使命它是数学意义上的线,而Qt中对线不能与填充地区相交是符合数学界说的,于是我没有以为该现象错误(不符合需求),因为我没故意识到本身认知的线和白板软件需要线是完全不同的两个东西。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]