如何计算canvas Path2D矢量路径对象的包围盒

概要

Path2D 底层是Skia的SkPath对象实现的,你可以直接使用Skia的wasm版本canvaskit或者如果只需要处理路径相关的只需要SkPath wasm的版本PathKit.这些库功能非常强大,但也很大,同时是wasm版本,源码是c++。比如我之前使用Pathkit Wasm版本,为了使用底层更多的方法需要经常编译。回到正题其实计算Path2D的包围盒不是很难,只需要把常用的方法转成折线和贝塞尔曲线.
Path2D
有这些路径命令方法:
moveTo,lineTo,quadrationBezierTo,bezierCurveT,closePath,ellipse,roundRect,arc,arcTo,rect

看上去有很多方法,像arcTo arc ellipse roundRect这些椭圆弧和圆、椭圆都可以贝塞尔曲线近似。所以我们只需要计算贝塞尔曲线的包围盒就行了。

ellipse

如果实现像Path2D 类似的ellipse方法。
首先要实现一个从端点坐标转换到椭圆中心坐标的方法。
w3c官网有对这个转换公式的描述。path2d内部实现也是参照这个来的

大概如下

	function EndPointToCenter(x1: number, y1: number, x2: number, y2: number,rx: number, ry: number, xAxisRotateAngle: number, fa: boolean, fs: boolean){
   
   
	}
	// 椭圆弧绘制
 	function ellipseArc(x1: number, y1: number, x2: number, y2: number,
        _rx: number, _ry: number, xAxisRotation: number,
        largeArcFlag: number, sweepFlag: number) {
   
   
        const{
   
   cx,cy,rx,ry,theta1,deltaTheta}=EndpointToCenter(x1,y1,x2,y2,_rx,_ry,xAxisRotation,!!largeArcFlag,!!sweepFlag)
 	// 计算圆弧需要几段曲线近似
   // 将弧划分为若干段,每段弧跨度不超过 PI/2
    const segments = Math.ceil(Math.abs(deltaTheta / (Math.PI / 2)));
    const delta = deltaTheta / segments;
    const curves: number[][] = [];
    const k=4/3*Math.tan(delta/4) // 控制点的延伸长度
    for (let i = 0; i < segments; i++) {
   
   
        const thetaStart = theta1 + i * delta;
        const thetaEnd = thetaStart + delta;
        // 根据thetaStart和thetaEnd椭圆的单位弧,得到起始点和终点
        // 圆的标准参数方程:theta(cos(theta),sin(theta))
        // 然后再找到对应的切线向量(圆的切向量其实就是旋转90度),再乘k
        // 最后再旋转到xRotationAxis轴对齐
        // 因为是单位圆,所以最后要缩放到rx,ry半径上
        // 最后再加上cx,cy中心坐标
        curves.push(arcSegmentToCubic(
            cx, cy, rx, ry, angle,
            thetaStart, thetaEnd
        ));
    }
    return curves;

svg path data 中的A命令可以直接用ellipseArc方法来绘制.
有了ellipseArc 函数后,svg 的路径数据可以全部无障碍转换过来了,因为svg path命令只有A,M,L,A,Q,C,其它都是与这几个相关的。比如下面这个tiger,我可以完美还原
在这里插入图片描述
像SkPath有ConicTo方法,这个方法以后也会加入到Path2D中,这个方法其实就是一个有理的二次贝塞曲线,可以用两段二次贝塞曲线,完美近似。有兴趣的可以自己去看。

下面开始实现ellipse、arc、arcTo方法了

ellipse方法

方法参数:

```ellipse(
        cx: number, cy: number, rx: number, ry: number,
        rotation: number, startAngle: number, endAngle: number,
        anticlockwise: boolean = false
    )

ellipse 绘制有两种方法,

  1. 和ellipseArc方法实现差不多,只它不需要再计算中心坐标了,所以可以省略endPointToCenter方法,直接接执行for循环的逻辑
  2. 另一种就是复用已写的ellipseArc方法。另外增加一个从中心到端点的坐标转换的方法centerToEndPoint.公式在w3c官网 。实现就是计算起始与终止的角度差,大于等于360就调用ellipseArc两次,每个画一半,小于360的就调一次就行了。其实一次也能画出完整椭圆,只要就是无限趋向360度.

Arc 方法

方法参数:

cx: number, cy: number, radius: number, startAngle: number, endAngle: number,
        anticlockwise: boolean = false

这个直接调用ellipse方法就行了,只是rx,ry都是相同

ArcTo 方法

方法参数:

arcTo(x1: number, y1: number, x2: number, y2: number, radius: number) 

这个方法也是利用ellipseArc方法.先计算它们是否共线,如果共线就画个直线就行。如果没有,就求它们的

// A(x0,y0),B(x1,y1),C(x2,y2).

let ba=normalize(A-B);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值