qml+QQuickPaintedItem笛卡尔坐标和屏幕坐标的转换
专业图形软件必备功能用qml、QQuickPaintedItem、QTransform实现,屏幕坐标和笛卡尔坐标之间自由转换。
目录
0. 最终效果
坐标转换
1. 为什么是qml+QQuickPaintedItem
用过多年的WPF,MFC, 了解OpenGL和Qt Widget,ImGUI, 要做中规中矩的图形软件(效率工具),OpenGL过于复杂,新一点的OpenGL依赖shader编程,不是一般人能轻易驾驭的。MFC也是老黄历了,开发效率低,而且是微软阵营。抛开跨平台不说,WPF应该是功能最为强大的一款,上手简单,效果炫酷,xaml所见即所得无有出其右者。ImGUI是实时架构,多用在游戏界面中,开发理念差别较大。
挑来拣去,qml+QQuickPaintedItem在开发效率、实现效果、运行效率上看是一个不错的组合。
2. 屏幕坐标和笛卡尔坐标
屏幕坐标的原点在左上角,y轴向下,x轴向右。在几何数学上我们一般用笛卡尔坐标,原点在屏幕中央,x轴朝右,y轴朝上。除了原点和轴朝向的不同,这两个坐标的单位也不同,屏幕坐标一般以像素为单位,这个和显示器硬件相关的;在笛卡尔坐标系中,我们用到的是逻辑坐标,比如规定:
- x轴坐标范围为-3到3, y轴范围为-3到3。
笛卡尔坐标系,来自wikipedia
在屏幕坐标中,一个QQuickPaintedItem的绘制区域假设为:
- 宽500个像素,高500个像素
屏幕坐标上点(0,0)对应的笛卡尔坐标系上的点(-3,3),在一个图形软件中,图形对象的坐标一般以笛卡尔坐标系作为参考,避免受到显示硬件的影响,这是因为如果采用屏幕坐标,在某个分辨率高的显示器上创建的图形对象在另外一台分辨率低的显示器上就会不正常,采用笛卡尔坐标就不会有这个问题。
有些同学可能要问了,仅仅是采用笛卡尔坐标系就能解决硬件依赖的问题么?毕竟我们什么也没有做啊。这就引入了坐标映射的概念了,用鼠标绘制通常需要进行两次映射,第一次我们得到鼠标事件的位置,这个是屏幕坐标,我们要把这个坐标转换为逻辑坐标,然后图形的变形转换等操作都以逻辑坐标为基础进行操作;第二次是在绘制的时候,我们用QPainter进行绘制,QPainter把绘制指令传递到显卡,并最终到达显示器上依然用的是屏幕坐标(当然中间省略了不少细节),需要我们把逻辑坐标转换为屏幕坐标。
3. QPainter的转换矩阵
我们首先介绍第二次映射,QTransform已经提供了相关的API让我们设置转换矩阵,这个API就是setMatrix函数,目的是设置转换矩阵的参数,官方文档有介绍,有兴趣的自行查看。QTransform这个类功能强大,能进行平移、缩放、错切(shear)等功能,在本文中,需要进行下述转换:
- 缩放,(-3,3)比(500,500)小很多,需要放大
- 平移,笛卡尔原点在中间,屏幕原点在左上,需要平移
- y轴方向颠倒一下
我们已经知道需要哪些转换了,具体如何设置呢?QTransform的是个3x3的矩阵,总共有9个参数,实际上需要设置的参数为5个:m11,m22,m31,m32,m33。m11是x轴的缩放参数;m22是y轴的缩放参数;m31是x轴的平移参数;m32是y轴的平移参数;m33是单位矩阵的值,设为1就行;另外还有一个y轴方向颠倒,我们直接把m22设置好了,前面加个负号就行了。参看下图:
其余四个参数设置为0.0即可。由于所有的绘制操作都要依赖这个矩阵,我们创建一个QTransform类型的成员变量,保存起来,在paint函数中调用setWorldTransform即可:
说了这么多,具体的参数还是举个例子吧,笛卡尔x,y轴范围(-3,3)转换为屏幕x,y范围(0,500)为例,m11为x轴放大系数,值为(500 / 6),m22为-(500 / 6),前面符号是因为两个坐标系下面y轴方向是相反的;m31为x轴平移系数,相当于把笛卡尔原点x坐标0移到视口最左边,距离为500除以2,为250。m32也是250:
4. 鼠标位置转换为笛卡尔坐标
上面第三节写的是绘制的坐标转换,其实还有个鼠标位置的转换,相当于上面的逆转换,即,鼠标的x坐标缩小(500/6.0)倍,y缩小-(500/6.0)倍,水平方向平移-3, 垂直方向平移3即可。这些操作在QQuickPaintedItem派生类的鼠标回调函数开始处进行即可,如mousePressEvent,mouseMoveEvent等等。
专业图形软件一般在界面实时显示鼠标位置,这个通过qml即可以实现,首先导出QQuickPaintedItem派生类到qml中,假设在qml中类型为DrawingArea,我们定义一个MouseArea,在onMouseXChanged和onMouseYChanged事件处理函数中获取鼠标位置(当然要进行转换),然后定义两个TextInput(id分别为xpos和ypos),分别展示x和y鼠标位置。
实时展示的TextInput如下所示:
效果见文章开始处的视频。
更多推荐
所有评论(0)