【Aegisub】真正正确的ASSDraw3填充规则

可能绝大部分的人并不清楚ASSDraw3绘图的填充规则,最多也就知道所谓的“反向掏空”了。但是实际上出现相反的路径也不一定会有掏空。很多人不知道自交图形的填充情况,也不知道带有同向包含图形的填充情况。因为我没见过有人正确地讲过ASSDraw3的填充规则,所以我还是专门写一篇专栏来介绍一下。
ASSDraw3的填充规则实际上和svg的non-zero规则完全一样,知道svg的人可能会懂non-zero规则,但是如果不知道也没关系,因为我接下来就是要讲non-zero规则
现在我举一些各自有特点、有代表的例子。
首先,对于填充效果来说,未封闭的路径肯定是按照封闭路径来填充的,因为不然的话,未封闭的路径得填充成什么样?所以,如图1.01和图1.02,一个是未封闭的一个是封闭的,但填充结果一样。

图1.01:m 0 0 l 28 1 l 26 18 l -2 17 图1.02:m 0 0 l 28 1 l 26 18 l -2 17 l 0 0

图1.01和图1.02的路径本身是有区别的,图1.01和图1.02的路径可以画成图1.03和图1.04这样:

图1.03就对应图1.01的路径,图1.04就对应图1.02的路径

所以第一点就是所有未封闭路径的绘图都会按照封闭路径的绘图来填充
好,那么接下来看图1.05,讲一下为什么会填充成这样:

为什么图1.05会出现掏空呢?现在先将图1.05的路径画出来(未封闭的绘图现在就一律画成封闭的了),并且随便作一条水平线L,咱们来观察一下处于这一条水平线位置的填充情况,如图1.06

现在很清楚的看到,此时状况是绘图由两个m构成,第一个m路径顺时针第二个m逆时针,然后在水平线L这个位置,点A到点B之间是填充的,点B到点C之间是未填充的,点C与点D之间是填充的,那么为什么处于水平线L这个位置的填充情况是这样的?就是因为绘图填充规则是non-zero规则:要知道水平线L处的填充情况,就让水平线L与绘图求交点(遍历每条路径即可求得交点),然后此时水平线和绘图有4个交点,将交点按照x从小到大的顺序排序,得到4个点A、B、C、D。然后开始交点计数(计数当然从0开始计了),第一个交点A,A所处路径向上(当然从坐标系来看是向下的,因为y的方向是反的,但这不重要),向上则计数变量cnt就加1,所以cnt=cnt+1=1,那么non-zero规则就来了,cnt只要不为0则该交点到下一个交点之间就是填充的!所以,点A到点B之间填充。那么继续,现在到B点了,B点处的路径方向向下,则cnt减1,即cnt=cnt-1=0,欸,B点处的cnt等于0了,那么由于是non-zero规则,所以只要计数变量cnt只要为0,则该交点到下一个交点之间就不填充!所以,点B到点C之间不填充。那么现在继续到了C点,此时C点所处路径方向向上,cnt加1即cnt=cnt+1=1,所以cnt≠0,所以点C到点D之间是填充的。这样,我们就用non-zero(非零)规则分析出了在水平线L这个位置的填充情况,当然显然,仅仅是这一条水平线不足以代表整个绘图的填充情况,而是仅仅代表这个水平线所处的位置的填充情况!
所以现在再分析一下另一条水平线L处的填充情况,如图1.07

好,直接分析。现在水平线L和绘图有两个交点,将交点按照x从小到大排序,得到点A和点B然后在A点处的路径方向向上,cnt=cnt+1=0+1=1,则点A到下一个交点B之间填充。当然B处的路径向下,cnt=cnt-1=1-1=0,所以B的右边不填充。现在显然,在这一条水平线L所处位置的填充情况就和刚刚不一样了。所以,现在很明白了,一个绘图整体的填充结果就可以由从上到下的水平扫描线得到!咱们现在得到了两条水平线所处位置的填充情况,那整个绘图的填充情况当然就可以由从上到下的一条条扫描线得到。
那么知道了non-zero规则(非零规则)以后,就可以看看自交图形是怎么填充的了,比如下图1.08是一个有自交的绘图

图1.08的绘图代码是:m 0 0 l 28 1 l 28 22 l -2 23 l 0 0 m 20 4 l 7 4 l 21 18 l 5 17
显然该绘图的第二个m的路径没有封闭,但是这无所谓,因为咱们在填充的时候会直接当做封闭路径来进行填充。那么现在就标记好路径信息,然后来看看如图1.09中水平线L所处位置的填充情况吧

现在水平线L和绘图求交点,得到交点按x从小到大排序,有交点A、B、C、D。计数变量cnt初始值等于0,然后开始到第一个交点A,A所在路径是P4P1,该路径方向向上,所以计数变量cnt=cnt+1=0+1=1,非零啦非零啦,所以A到下一个交点B之间填充。继续,到B了,点B所处路径P8P5同样是方向向上的,所以,cnt=cnt+1=1+1=2,非零啦非零啦,所以B到下一个交点C之间是填充的。继续,到了点C了,点C所处的路径P6P7的方向向下,所以计数变量cnt=cnt-1=2-1=1,非零啦非零啦,所以C到下一个交点D之间是填充的。这样,这一条水平线L所处位置的填充情况就分析清楚了。
那么,咱们再来看看另一条水平线L它的所处位置的填充情况是什么样的,如图1.10

现在水平线L和绘图求得4个交点A、B、C、D。首先,A点处的路径P4P1方向向上,计数变量cnt=cnt+1=1,所以cnt非零,所以A到下一个交点B之间填充。然后B点处路径P6P7方向向下,计数变量cnt=cnt-1=1-1=0,所以cnt为零,所以B点到下一个交点C之间不填充。然后C点的路径P8P5方向向上,cnt=cnt+1,所以cnt非零,所以C到下一个交点D之间填充。这样,我们就分析出了现在这一条水平线位置的填充情况,那么整个绘图的填充情况就还是像前面说的,用从上到下的水平扫描线就可以得到了。可见,对于有自交的图形来说,它的填充结果咱们用non-zero非零规则,就能迅速判断,因为什么,就是因为ASSDraw3的填充规则实际上和svg的non-zero规则完全一样啊。
再来看最后一个例子,一起分析一下图1.11的填充情况

图1.11的绘图代码为:m 0 0 l 28 1 l 28 22 l -2 23 l 0 0 m 4 3 l 21 4 l 24 18 l 2 20 m 17 7 l 7 8 l 7 16 l 19 16
还是一样的,未封闭的绘图一律“自动”封闭了。然后画出路径信息,咱们来看看如图1.12的水平线L所处的位置的填充情况

显然,这样很清楚,该绘图总共有3个m构成。第一个m的路径方向是顺时针的,第二个m的路径方向也是顺时针的,第三个m的路径方向是逆时针的。现在水平线L和绘图求交点,并将求得的交点按x的升序排序,得到6个交点A、B、C、D、E、F。首先,点A处路径方向向上,所以cnt=cnt+1=1,所以AB之间填充。然后B点时路径方向向上,所以cnt=cnt+1=2,所以BC之间填充。然后C处路径方向向下,所以cnt=cnt-1=1,所以CD之间填充。然后D点处的路径方向向上,所以cnt=cnt+1=2,所以DE之间填充。然后E时的路径方向向下,所以cnt=cnt-1=1,所以EF之间填充。这样,这一条水平线所处位置的填充情况就迅速地分析出来了。显然,这个绘图里的m有路径相反的情况,但是,却没有出现掏空,这就如本文一开始所说,出现相反的路径也不一定会有掏空。所以ASSDraw3绘图的填充情况并不是简单的一句“反向掏空”就能完全概况的。因为,因为啊,ASSDraw3的填充规则就和svg的non-zero规则完全一样呀
那么非零填充规则应该大概讲清楚了,下面画几个图,大家自己练习一下,想想这些图会填充成什么样。
根据所画路径判断绘图填充情况:
题目1,如图1.13

题目2,如图1.14

最后还是总结一下,non-zero规则:一、封闭所有未封闭的绘图。二、从上到下用水平线扫描。三、求水平线和绘图的交点并根据交点处的路径方向计数,计数变量非零则填充,为0则不填充。