基于QT实现的图元拖曳、定点滚轮旋转和缩放

基于QT实现的图元拖

包含可视化锚点的演示

一、概述
在算法模块方面,实现了直线和多边形的 DDA、Bresenham 算法;实现了中点圆和中点椭圆算法;实现了图元平移、缩放、旋转和两种裁剪算法;实现了 n 阶贝塞尔曲线和三次均匀 B 样条算法。

在文件输入接口方面,实现了一个命令行程序,支持解析固定格式的字符串命令。在用户交互接口方面,提供基于鼠标点击的直线、多边形、椭圆、曲线的绘制和实时渲染;实现了基于链表遍历的图元捕获,提供基于鼠标拖曳的图元移动操作;提供基于可视化锚点及鼠标滚轮的图元旋转、图元缩放操作.

二、算法重述
2.1 DDA 算法
DDA 算法,即数值差分分析算法,直接利用直线 x 或 y 方向增量 △x 或 △y,在直线投影较长的坐标轴上,以单位增量对线段离散取样,确定另一个坐标轴上最靠近线段路径的对应整数值。实际实现时,采用增量法确定这个整数值,另一个坐标轴上的增量应满足的要求是,符号使起始点具备向结束点移动的趋势,模长等于当前坐标轴投影和较长坐标轴投影的比值。

2.2 Bresenham 算法
Bresenham 算法利用了光栅扫描时,线段离散取样位置的有限性,只有两个可能的位置符合采样要求,于是设计整型参量来表示两个侯选位置和理想位置的偏移量,通过检测这个整型参量的符号,在侯选位置里二选一。

算法推导如下:

对斜率 0<m<1 的情况,yk+1=mxk+1+b,比较 yk+1 和 yk、yk+1 的偏移,d1 = y - yk = m(xk + 1) + b - yk, d2 = yk+1 - m(xk + 1) - b,有 Δx(d1 - d2) = 2m(xk + 1) - 2yk + 2b - 1,设置决策参数 pk=Δx(d1 - d2), pk 大于 0 意味着 yk+1 比 yk 更接近理想位置。

计算 pk+1 和 pk 的差,可知, pk 大于 0 取高像素 yk+1 时的增量为 2Δx-2Δy,pk 小于 0 取低像素 yk 时的增量为 2Δy。

代码实现如下:

从实现上看,Bresenham 算法不需要对浮点数取整,不存在 DDA 算法因取整造成的整体偏差。

在性能方面,因为现在的 CPU 性能挺好,很难看出 DDA 和 Bresenham 算法在用户体验方面的差异,在 Qt 应用的主线程中分别运行 DDA 和 Bresenham 算法来绘制直线和多边形,并且调用 update 函数立即渲染,肉眼无法察觉鼠标快速拖曳时,窗体画面的延时。

2.3 中点圆和中点椭圆算法
中点圆算法
决策参数和增量的推导类似 Bresenham 算法,推导如下:定义圆函数:

fcircle(x, y) = x^2 + y^2 – r^2

圆边界上的点(x, y)满足 fcircle(x, y) = 0

任意点(x, y)与圆周的相对位置关系可由对圆函数符号的检测来决定:

若 fcircle(x, y) < 0,(x, y)位于圆边界内;
若 fcircle(x, y) = 0,(x, y)位于圆边界内;
若 fcircle(x, y) > 0,(x, y)位于圆边界外。
第 k 个决策参数是圆函数在两候选像素中点处求值,

pk = fcircle(xk+1, (yk+1 + yk) / 2) 其中 yk+1= yk-1 所以 pk = fcircle(xk+1, yk – 1/2)

pk < 0,中点在圆周边界内,选择像素位置(xk+1, yk);

pk > 0,中点位于圆周边界外,选择像素位置(xk+1, yk-1);

pk 符号决定两候选像素中点位置(yk+2 + yk+1) / 2 的取值,

若 pk < 0,(yk+2 + yk+1) / 2 = yk - 0.5,即 pk+1 = fcircle(xk+2, yk - 0.5);

若 pk > 0,(yk+2 + yk+1) / 2 = yk - 1.5,即 pk+1 = fcircle(xk+2, yk - 1.5)。

只需要计算八分之一圆弧,另外七个圆弧通过对称、对映操作得到坐标。 

代码实现:

中点椭圆算法

椭圆的对称性比圆要弱一些,决策参数和增量在圆周斜率在过 1 时要进行调整,采用计算四分之一圆周,对称、对映出另外四分之三圆周的方案。另外,在每次步进之后,都要重新计算斜率,来判断是否更换决策参数和增量。代码实现:

2.4 图元编辑算法
图元平移
二维平面上的图元平移可通过二维向量的加减运算来描述,对于控制点(x0,y0),平移(x,y)即意味着平移到(x0+x,y0+y)。编程的时候需注意,椭圆的实轴和虚轴长度不是控制点,不能参与平移计算。

图元旋转
对于将控制点缓冲中的点逆时针绕(x,y)旋转角度制 r 的变化,可以通过以下函数描述: const double pi = 3.1415926; double cosr(cos(r * pi / 180.0)), sinr(sin(r * pi / 180.0)); for (auto& i : ctrlbuffer) { int x0 = i.x(), y0 = i.y();

CohenSutherland 裁剪算法

对目标点做四个方向九个区域的编码测试,用四个比特位表达目标点在九个区域中的哪一个,然后计算射线和目标点靠近的边框的交点,替换目标点,直到两端的目标点落在边框内,或都不可能落在边框内,结束算法。编码的策略如下:

这里的 m 表示两个端点构成的直线的斜率,这个斜率可能不存在。为了方便编写代码,我计算 m 的方法是:

double m = (q.y() - p.y()) / (q.x() - p.x() + 0.000000000001);

为了避免整形舍入的误差,计算交点时使用 round 函数来避免完全的向下舍入。

设有 n 个控制点,对于[0,1]中的每一个参数 t,需要做(n-1)次线段的 t 比例分割,第 i 次分割会产生(n-i) 个中间型值点,第(n-1)次分割可以得到 1 个型值点,这个点就是需要的最终型值点。

算法举例如下:对于 4 个控制点,迭代 3 次获得一个最终型值点:

通过 div 参数来控制参数 t 的步长,避免曲线过长(控制点过多)时,步长太小导致的出现折线的问题。

编程的过程中需要注意,必须使用浮点数做中间运算,否则迭代的过程中,整型变量会发生连续舍入,使得部分曲线呈现阶梯状的特点。

三次均匀 B 样条
使用 de Boor-Cox 算法,对于 k 次的 B 样条基函数,构造一个递推的公式,由 0 次多项式的递推构造 1 次的, 1 次的递推构造 2 次……递推公式如下:

一阶的多项式涉及一个区间两个节点,K 阶的 Bi,k 涉及 k 个区间 k+1 个节点。

代码实现如下:

对于公式中 U 的取值,只要保证基函数系数的分子分母数量级一致即可,所以这里直接用区间段的索引给 U 赋值。迭代中进行额外的判断,避免两端处,(0,0)被加入型值点序列。

三、应用设计
以 Qt 为编程框架,C++ 为编程语言,程序分为三个模块:图形学算法、命令行交互和手绘板交互。

图形学算法方面,将所有图元生成算法以静态成员函数的形式封装在 Proc 类中,在这些函数里实现上述算法,采用面向过程的风格,公共接口设计如下:

 命令行交互方面,在 class Cli 中解析命令,调用 Proc 提供的算法,公共 API 设计如下:

手绘板交互方面,通过在手绘面板 class ScribbleArea 中拦截四种鼠标事件,完成用户输入的获取,调用 Proc 类提供的图元生成算法,将结果实时渲染到窗体上。

GUI 的涂鸦功能的实现细节在此不再赘述,下面介绍图元编辑的实现。

对于图元编辑的 GUI 交互,采用捕捉被点击图元的方法,为当前所有可见图元构造矩形框,存储在一个链表中,在 mouseMoveEvent 中捕捉满足 QRect::contains(QPoint)的鼠标点,对符合要求的图元的矩形框做特殊标注,意味着鼠标捕获了目标图元。

考察 Qt 使用的图形视图框架,内部通过 BSP 树实现鼠标和图元的快速对应。事实上,当二维空间中的图元数量达到一定数量级,像我目前这样遍历链表而用 Rect.contains(Point)的做法捕获图元是极为缓慢的。 GUI 框架大多通过树形结构比如 BSP 树、4 叉树来从坐标索引图元。对于画板,考虑到可能的交互强度,使用链表遍历来查找图元,延时是完全可以接受的。

在拥有了从鼠标点击索引图元的实现以后,对于图元平移,记录图元的原始位置和当前鼠标位置,每一次鼠标移动,先把图元移回初始位置,再渲染到当前鼠标位置,从用户界面观察,相当于自己在拖曳图元。

对于缩放和旋转,大作业的要求和 word 文档、ppt 等软件提供的交互逻辑有所出路,要求围绕固定点做缩放和旋转,所以我设计了基于可视化锚点的交互逻辑,点击功能按钮后,会要求用户放下一个图钉形状的锚点,接下来用户点击图元,实现图元指定,转动鼠标滚轮,通过滚轮的前进和后退,映射到缩放比例(>1 或 <1)和旋转角度(顺时针或逆时针)。提供基于鼠标滚轮的缩放和旋转,需要解决的问题有精度问题,因为图元控制点是用整型数记录的,连续对一个图元做几十上百次浮点精度的变形,控制点的相对位置会发生扭曲,累计误差不可接受。为了解决这个缺陷(根据大作业要求,控制点坐标用整数表示),我采用先把图元恢复到初始位置,再重新渲染的方式,来消除对同一个图元连续操作时的误差累计。

点击链接加我为QQ好友:https://qm.qq.com/cgi-bin/qm/qr?k=abS_id-jDIpS9cL1pnuRdQUEMh_ugfF4&noverify=0&personal_qrcode_source=3

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值