TCAX 字幕特效制作工具官方论坛 | ASS | TCAS | Python | Aegisub | Lua

 找回密码
 加入社区
查看: 1667|回复: 5

[特效算法] 拆分字形 [附工程] [复制链接]

Moderator

Effect Researcher.

Rank: 5Rank: 5

发表于 2015-9-10 21:48:09 |显示全部楼层
本帖最后由 面麻 于 2015-9-10 22:48 编辑

当初我在一个MAD作品里看到的效果,就是拆分字形,想用ASS来实现一下。
一共2个方法,都是灾厄前辈想的,代码也是他写的。
我在其中也学到了很多,很感谢灾厄。

可能代码里面有一些东西涉及到高层次的Python语法,尽量看吧。
注释也很多,如果还有问题,直接跟帖。
完整工程在下面,可以Prase一下看看。

两种方法可能还有点小bug,各有优劣。

test_split_font_temp.zip (17.56 KB, 下载次数: 281)

第一种方法,是拆分矢量字体的绘图代码,先通过 'm' 字符判断大致拆分开,然后再考虑内外框分别是顺时针和逆时针的情况,下面的代码是以逆时针为内框。
需要说明的是,不同字体,内外框的绕行方向是不确定的,所以如果出现大片字形不正确,可以修改函数check_clockwise()的clockwise值。
其实,这个判断是比较复杂的,尤其是凹多边形很难判断,如果出现个别字形不正确,只能手动判断和修改绘图代码。
  1. from tcaxPy import *
  2. from util.gdiFont import *

  3. import re

  4. def tcaxPy_Init():
  5.     global font   # Font和GdiFont效果差不多
  6.     global fontsize
  7.     global GdiFont
  8.     fontsize = GetVal(val_FontSize)
  9.     font = InitFont(GetVal(val_FontFileName), GetVal(val_FaceID), fontsize, GetVal(val_Spacing), GetVal(val_SpaceScale), 0xFFFFFF, 0, 0)
  10.     GdiFont = gfInitFont(GetVal(val_FontFaceName), fontsize, GetVal(val_Spacing), GetVal(val_SpaceScale), 0, False)   #GDIfont

  11. def tcaxPy_User():
  12.     pass


  13. def tcaxPy_Fin():
  14.     FinFont(font)

  15. def checkacw(str):          # 判斷內框

  16.     Ppath = ppath(str)
  17.    
  18.     if Ppath[0]==Ppath[-1]:        #字體矢量有重複最後坐標點的習慣 貌似重複的基本是外框 不過總有bug的地方 總之取首尾不同的 扔掉最後重複
  19.         Ppath.pop()
  20.     return check_clockwise(Ppath)   # 设为内框

  21. #構建一個本項和後項結合的list 比方說poly為[(1,1),(2,2),(3,3),(4,4)] 則新list為[((1,1),(2,2)),((2,2),(3,3)),((3,3),(4,4)),((4,4),(1,1))]
  22. def segments(poly):       #網上照搬
  23.     return zip(poly, poly[1:] + [poly[0]])     

  24. def check_clockwise(poly):      #判斷順時針 網上搬 算法之前判斷用的極值求叉積法 總有凹多邊形不準確 這個遍歷所有點算叉積 最後以總和的正負來判斷
  25.     clockwise = False
  26.     if (sum(x0*y1 - x1*y0 for ((x0, y0), (x1, y1)) in segments(poly))) < 0:   # 叉積为負值是逆時針
  27.         clockwise = not clockwise
  28.     return clockwise


  29. def ppath(str):   # 從str 將路徑儲存為 [(1,1),(2,2),(3,3),(4,4)] 這種格式 b 標籤捨棄中間點 做直線考慮
  30.     if str[0] != 'm':
  31.         str = 'm'+ str
  32.     sb = re.split('m|l|b ',str)     # re是一个正则表达式Module,这里的split其实就是字符串的split函数的翻版,只是可以对分隔符用正则
  33.     ppath = []
  34.     for s in sb:
  35.         b = s.split()
  36.         if len(b) > 1:
  37.             if b[-1]=='c':
  38.                 ppath.append((int(b[-3]),int(b[-2])))
  39.             else:
  40.                 ppath.append((int(b[-2]),int(b[-1])))
  41.     ppath.pop()   # 所有路徑 首尾都相同 捨棄掉最後重複
  42.     return ppath

  43. def polycmp(poly1,poly2):    #判斷多邊形是否在另一個多邊形內
  44.     result = True
  45.     if poly2[-1]==poly2[0]:
  46.         poly2.pop()
  47.     for p1 in poly1:
  48.         x1,y1= p1
  49.         if not pointinpoly(x1,y1,poly2):  #遍歷多邊形所有點是否在多邊形內
  50.             result = False
  51.     return result

  52. def pointinpoly(x,y,poly): # 判斷一個點是否在多邊形內 網上搬

  53.     n = len(poly)
  54.     inside = False

  55.     p1x,p1y = poly[0]
  56.     for i in range(n+1):
  57.         p2x,p2y = poly[i % n]
  58.         if y > min(p1y,p2y):
  59.             if y <= max(p1y,p2y):
  60.                 if x <= max(p1x,p2x):
  61.                     if p1y != p2y:
  62.                         xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
  63.                     if p1x == p2x or x <= xints:
  64.                         inside = not inside
  65.         p1x,p1y = p2x,p2y

  66.     return inside


  67. def tcaxPy_Main(_i, _j, _n, _start, _end, _elapk, _k, _x, _y, _a, _txt):

  68.     ASS_BUF  = []        # used for saving ASS FX lines

  69.     #############################
  70.     # TODO: write your codes here #

  71.     dx = _x - int(_a / 2 + 0.5)
  72.     dy = _y - int(fontsize / 2 + 0.5)

  73.     outline = gfGetOutline(GdiFont, _txt, dx,dy)

  74.     draw = outline.split('m')
  75.     num = len(draw)
  76.     tcaxLog(draw)


  77.     acdl = []   #用來儲存內框矢量
  78.     cdl = []    #儲存外框矢量
  79.     if _i>-1 :
  80.         for i in range(num):     #遍歷所有m標籤矢量 判斷內外框 并進行儲存
  81.             if draw[i] == '':
  82.                 continue

  83.             if checkacw(draw[i]):     
  84.                 acd = 'm'+draw[i]
  85.                 acdl.append(acd)
  86.             else:
  87.                 cd = 'm'+draw[i]
  88.                 cdl.append(cd)   
  89.    
  90.     cdlcopy = cdl[:]   #複製一遍list
  91.     for acd in acdl:   
  92.         index = -1
  93.         for i in range(len(cdlcopy)):
  94.             if polycmp(ppath(acd),ppath(cdlcopy[i])):     #遍歷所有內框外框 判斷內框是否在外框範圍內
  95.                 if index == -1:
  96.                     index = i
  97.                 else:                                    
  98.                     if polycmp(ppath(cdlcopy[i]),ppath(cdlcopy[index])):   #若存在多個可匹配的範圍 取最小的框 如"回"字
  99.                         index = i
  100.         if index != -1:
  101.             cdl[index] += acd    #將內框矢量 加在外框矢量之後
  102.         else:
  103.             cdl.append(acd)  #若沒找到匹配的外框 則作為外框 這個有可能是順時針逆時針的判斷問題 也有可能是字體設計的問題 總之是保險

  104.     for cd in cdl:   #繪製
  105.         posX = randint(-_a, _a) + randint(-10, 10)
  106.         posY = randint(-fontsize , fontsize ) + randint(-10, 10)
  107.         EFT = an(7) + move(posX, posY, 0, 0, 0, 500) + p(4)
  108.         ass_main(ASS_BUF, SubL(_start, _end), EFT, cd)

  109.     #############################

  110.     return (ASS_BUF, None)
复制代码
第二种方法,是用粒子,这里面用到了判断连通性的Labeling算法,网络上搜索 'Labeling' 就可以找到相关资料,比如:http://blog.csdn.net/icvpr/article/details/10259577
具体的算法不做解释,但还是需要自己查看一下才能看懂下面的代码。
  1. from tcaxPy import *

  2. def tcaxPy_Init():
  3.     global font
  4.     global fontsize

  5.     fontsize = GetVal(val_FontSize)
  6.     font = InitFont(GetVal(val_FontFileName), GetVal(val_FaceID), fontsize, GetVal(val_Spacing), GetVal(val_SpaceScale), 0xFFFFFF, 0, 0)


  7. def tcaxPy_User():
  8.     pass


  9. def tcaxPy_Fin():
  10.     FinFont(font)

  11. def GetPixelPointFromPix(PIX, InitPosX, InitPosY):      # 将方形PIX[1][0]xPIX[1][1]范围的pixel扫描成一个list,保存位置、RGBA、label信息
  12.     pixel = []
  13.     label = 0
  14.     for h in range(PIX[1][1]):
  15.         posY = InitPosY + h
  16.         for w in range(PIX[1][0]):
  17.             posX = InitPosX + w
  18.             idx = 4 * (h * PIX[1][0] + w)
  19.             pixR = PIX[2][idx + 0]
  20.             pixG = PIX[2][idx + 1]
  21.             pixB = PIX[2][idx + 2]
  22.             pixA = PIX[2][idx + 3]
  23.             pixel.append([(posX, posY), (pixR, pixG, pixB, pixA), label])
  24.    
  25.     return pixel

  26. def DeleteRepeat(labelset):     # 删除list中的重复元素

  27.     labelset2 = []

  28.     [labelset2.append(label) for label in labelset if not label in labelset2]

  29.     return labelset2      


  30. def UnionTwoLists(list1, list2):     # 合并2个list,默认它们含有相同元素
  31.     for elem in list1:
  32.         if elem in list2:
  33.             continue
  34.         else:
  35.             list2.append(elem)

  36.     return list2


  37. def UnionLabelSet(labelset):        # 整理labelset,合并
  38.     labelset = sorted(labelset, key=lambda k: k[0]) # 排序
  39.     labelset = DeleteRepeat(labelset) # 刪除重複

  40.     labelset2 = []


  41.     for label in labelset:
  42.         if labelset2 == []:               #第一個 label 直接添加
  43.             labelset2.append(label)

  44.         else:
  45.             flag = 0
  46.             for i in range(len(labelset2)):   #遍歷之前的label找交集 若存在交集 向對應位置 添加未存在的元素
  47.                 #tcaxLog(label2)
  48.                 if (label[0] not in labelset2[i]) and (label[1] in labelset2[i]):
  49.                     flag = 1
  50.                     labelset2[i].append(label[0])
  51.                 elif (label[0] in labelset2[i]) and (label[1] not in labelset2[i]):
  52.                     flag = 1
  53.                     labelset2[i].append(label[1])
  54.                 elif (label[0] in labelset2[i]) and (label[1] in labelset2[i]):
  55.                     flag = 1
  56.             if flag == 0:   # 若不存在交集 則另存
  57.                 labelset2.append(label)

  58.     #tcaxLog(labelset2)           
  59.     return labelset2
  60.    

  61. def tcaxPy_Main(_i, _j, _n, _start, _end, _elapk, _k, _x, _y, _a, _txt):

  62.     ASS_BUF  = []        # used for saving ASS FX lines

  63.     #############################
  64.     # TODO: write your codes here #

  65.     PIX = TextPix(font, _txt)

  66.     InitPosX = _x - int(_a / 2 + 0.5) + PIX[0][0]
  67.     InitPosY = _y - int(fontsize / 2 + 0.5) + PIX[0][1]

  68.     pixel = GetPixelPointFromPix(PIX, InitPosX, InitPosY)

  69.     num = len(pixel)
  70.     label = 0
  71.     labelset = []

  72.     # 1-pass
  73.     for i in range(num):
  74.         if pixel[i][1][3] != 0:     # 如果是有效像素
  75.             if i == 0:              # 如果是第1个像素,没有左面和上面的相邻像素,需要单独考虑
  76.                 label += 1
  77.                 pixel[i][2] = label
  78.             elif i % PIX[1][0] == 0:                # 如果是第1列像素,没有左面的相邻像素,需要单独考虑
  79.                 uppixelindex = i - PIX[1][0]        # 取上面的相邻像素index
  80.                 if pixel[uppixelindex][2] != 0:      
  81.                     pixel[i][2] = pixel[uppixelindex][2]    # 如果上面的相邻像素是有效像素,就把上面像素的label赋给当前像素
  82.                 else:                                       # 如果上面的相邻像素不是有效像素,就把label加1,然后赋给当前像素
  83.                     label += 1
  84.                     pixel[i][2] = label
  85.             elif i <= PIX[1][0] - 1:        # 如果是第1行像素,没有上面的相邻像素,需要单独考虑
  86.                 leftpixelindex = i - 1
  87.                 if pixel[leftpixelindex][2] != 0:
  88.                     pixel[i][2] = pixel[leftpixelindex][2]
  89.                 else:
  90.                     label += 1
  91.                     pixel[i][2] = label
  92.             else:                           # 同时考虑上面和左面的相邻像素是否是有效像素
  93.                 uppixelindex = i - PIX[1][0]
  94.                 leftpixelindex = i - 1
  95.                 if pixel[leftpixelindex][2] != 0 and pixel[uppixelindex][2] != 0:                   # 如果上面和左面的相邻像素都是有效像素,
  96.                     pixel[i][2] = min(pixel[leftpixelindex][2], pixel[uppixelindex][2])             # 就把它们的label的最小值赋给当前像素
  97.                     if pixel[leftpixelindex][2] != pixel[uppixelindex][2]:                          # 如果它们的label不相等,
  98.                         labelset.append(sorted([pixel[leftpixelindex][2], pixel[uppixelindex][2]])) # 就把它们的label组成一个list加入labelset
  99.                 elif pixel[leftpixelindex][2] != 0 and pixel[uppixelindex][2] == 0:                 # 如果上面的相邻像素不是有效像素,
  100.                     pixel[i][2] = pixel[leftpixelindex][2]                                          # 就把左面像素的label赋给相邻像素
  101.                 elif pixel[leftpixelindex][2] == 0 and pixel[uppixelindex][2] != 0:                 # 如果左面的相邻像素不是有效像素,
  102.                     pixel[i][2] = pixel[uppixelindex][2]                                            # 就把上面像素的label赋给相邻像素
  103.                 else:                                                                               # 如果如果上面和左面的相邻像素都不是有效像素,
  104.                     label += 1                                                                      # 把label加1,赋给当前像素
  105.                     pixel[i][2] = label
  106.     #tcaxLog(pixel)                                       
  107.     labelset = UnionLabelSet(labelset)       

  108.     #print(labelset)                       
  109.       
  110.     # 2-pass    #這個地方寫法冗長 比較亂 沒仔細想

  111.     for i in range(len(pixel)):   #按labelset 修改相同 label
  112.         if pixel[i][2] != 0:
  113.             for j in range(len(labelset)):
  114.                 if pixel[i][2] in labelset[j]:                                            
  115.                     pixel[i][2] = min(labelset[j])
  116.                  
  117.     #tcaxLog(min(labelset[j]))
  118.     rndx = []
  119.     rndy = []
  120.     cor = []
  121.     labelbuf = []

  122.     for i in range(len(pixel)):   # 將不連續的label修改成最小連續數列

  123.         label = pixel[i][2]
  124.         if label != 0:
  125.             if label not in labelbuf:
  126.                 labelbuf.append(label)
  127.             pixel[i][2] = labelbuf.index(label) +1


  128.     for i in range(len(labelbuf)):  # 按照label個數生成隨機位置和顏色list
  129.         rndx.append(randint(-50, 50))
  130.         rndy.append(randint(-100, 100))
  131.         cor.append(RandRGB())

  132.     #tcaxLog(sorted(labelbuf))

  133.     #if _i == 0 and _j == 2:
  134.     #    tcaxLog(pixel)
  135.   
  136.     for i in range(len(pixel)):   #繪製  
  137.         label = pixel[i][2]

  138.         if label != 0:

  139.             EFT = move(pixel[i][0][0] + rndx[label-1], pixel[i][0][1] + rndy[label-1], pixel[i][0][0], pixel[i][0][1], 0, 500) + alpha(255 - pixel[i][1][3]) + color1(cor[label-1])
  140.             ass_main(ASS_BUF, SubL(_start, _end, 0, Pix_Style), EFT, DrawPoint())


  141.     #############################

  142.     return (ASS_BUF, None)
复制代码
5

查看全部评分

Rank: 4

发表于 2015-9-11 08:29:32 |显示全部楼层
好东西 新人回覆

Administrator

TCAX Dev.

Rank: 7Rank: 7Rank: 7

发表于 2015-9-12 22:55:57 |显示全部楼层
给你32个赞.

Rank: 4

发表于 2015-10-6 12:54:08 |显示全部楼层
赞一个!

Rank: 4

发表于 2016-5-1 20:23:58 |显示全部楼层
话说,怎么使用这些代码?

Rank: 4

发表于 2018-2-11 21:53:42 |显示全部楼层
谢谢楼主分享
您需要登录后才可以回帖 登录 | 加入社区

GitHub|TCAX 主页

GMT+8, 2018-12-10 19:59

Powered by Discuz! X2

© 2001-2011 Comsenz Inc.

回顶部
RealH