仿寫抖音旋轉漢字時鍾


      一直對抖音上的各種人臉處理算法很感興趣,個別的我覺得目前的水平我能寫個簡單的實現方式,但是涉及復雜的,還是太菜了。但是之前在抖音上看到了一個用網頁寫的旋轉漢字時鍾的視頻,感覺很好玩,而且我覺着寫出來完全沒問題,就用Pygame 和之前實現的旋轉立方體的底層代碼實現了一下。原視頻地址找不到了,只有個模糊的印象,所以就跟着感覺走了。最終實現的東西,需要運算量較大,而且速度很慢。所以我若是有時間了,我會移植為C或者C++語言再看看。因為我的代碼都是由之前的C語言改來的,所以改回去不會很難,只有麻煩不麻煩。

      另外,本文及實現的內容純粹是為了好玩而完全自己編寫的,且原視頻中介紹的是用其他代碼實現的,而且我也沒有記錄原作者是誰,無法進行感謝。但不論怎樣,若是有侵權的地方望告知,會立刻進行處理。

    先上圖:

        

也能調整大小(非動態),只要修改文件中的一個全局變量就可以修改大小了:

    上一篇的 pygame實現旋轉立方體 里面已經實現了點陣字庫的讀取和顯示,當時只是寫了個單個字符的,現在拓展一下,支持多個字符的就可以了。因為主要用到中文字符,也就沒添加英文字符的支持。其實很簡單,原理相通的。

    多字符的實現無非就是計算多字符對應點陣的坐標,然后進行投影計算就可以了。

    然后根據圓的坐標方程得出圓上某點與角度的關系,可以得到此角度下字符應該顯示在什么位置,然后將旋轉后的字符在此位置顯示,就好了。

    當知道圓的半徑和某點的角度值后,就能得到坐標關系式,如下:

   已知 圓心:(x0,y0)      半徑:r   角度:a0    求圓上任意一點坐標:(x1,y1),可得公式如下:

          x1=x0+r*cos(a0 * 3.14/180)
          y1=y0+r*sin(a0 * 3.14/180)

     然后將字符串沿Z軸旋轉(當前角度a0 減去 90度)的度數就能夠將字符旋轉到a0的角度,符合當前角度。選擇適當的半徑,就能夠將字符調整到合適的位置,然后輸出顯示就可以了。

    中文字符串16x16點陣的顯示:在Transform3D.py 文件中

 1 def Show3D16x16Char(font,ax,ay,az,x,y,Z_Size,frontcolor,backcolor,model=1.5,fontmultiple=1.0):
 2     '''**********************************************************/
 3     |**函數: Show3D16x16Font
 4     |**功能:顯示3D的16x16字符,為從漢字庫讀取的字符數據,支持數千個漢字
 5     |        本函數需要 binascii  庫的支持,不支持此庫的環境無法運行本函數
 6     |**說明:font:欲顯示的漢字,目前只支持一個字符的顯示,只支持漢字的顯示
 7     |        sx,sy,sz :角度值
 8     |        x,y: 欲顯示的坐標位置
 9     |        Z_Size:距旋轉軸的距離
10     |        frontcolor,backcolor:顏色,前景色和背景色
11     |        model:顯示模式,只有模式為1時才填充背景色,否則只填充前景色
12     |                     
13     |**作者: wcc  執念執戰
14     |**時間:2019-6-3
15     |********************************************************'''
16     length = len(font)
17     if length == 0:#字符數要大於一個
18         return 
19     if length == 1: #只支持一個字符的顯示
20         text = font
21     else:
22         text = font[0]
23   
24        
25     
26     m=0
27     i=0
28     k=0
29     j=0
30     XO=0
31     YO=0
32     #fontmultiple=1.0 #放大倍數,放到外面統一調整
33     
34     
35     gMAT=[[0.0 for i in range(4)]  for n in range(4)]
36     Point0=zuobiaostruct()
37     Point1=zuobiaostruct()
38     PointDis=zuobiaostruct()
39     
40     gMAT=structure_3D()                        #//構建單位矩陣
41     gMAT=Translate3D(gMAT,-8,-8,-8);         #//平移變換矩陣
42     gMAT=Scale_3D(gMAT,fontmultiple,fontmultiple,fontmultiple);                #//比例變換矩陣
43     gMAT=Rotate_3D(gMAT,ax,ay,az);            #//旋轉變換矩陣
44     
45     for m in range(length): #理論上就是將單個的字符延長為多個字符,最重要的就是坐標的確定和計算
46         
47         text=font[m]
48         gb2312 = text.encode('gb2312')
49         hex_str = binascii.b2a_hex(gb2312)
50         result = str(hex_str,encoding = 'utf-8' )  #換算出漢字對應的字符地址
51         if eval('0x' + result[:2]) <128:
52             print("請輸入中文")  #目前只支持gb2312中文字符,英文字符沒加。原理一樣,可以取模保存起來
53             return
54         else:
55             
56             area = eval('0x' + result[:2]) - 0xA0
57             index = eval('0x' + result[2:]) - 0xA0 #換算為16進制地址
58             offset = (94 * (area - 1)+ (index - 1))*32  #得出具體地址
59             font_rect = None
60             with open("D:/Mystudy/Python/pyGame/HZK16","rb") as f: #16x16字符集的地址,從中讀取出一個字符的點陣數據
61                 f.seek(offset)
62                 font_rect = f.read(32)#得到32個點陣數據
63             f.close()
64         
65       
66         #gMAT=Translate3D(gMAT,8,8,8);             #//平移變換矩陣       x:調節距離中心點的位置,相當於下面Point0.z
67         
68         
69         
70         for i in range(16):
71             for k in range(8):
72                 temp = 0x01 << k
73                 for j in range(2):
74                     data=font_rect[i*2+j]  #取出數據
75                     if data & temp == temp:
76                         
77                         Point0.x=16-(k+(1-j)*8)+m*16+m*2 #第m個字符的當前點的坐標
78                         '''
79                             每個字符16個像素,第m個字符共m*16個像素,每兩個字符間的間距設為2,則m*16+m*2,前面的時實現當前點陣坐標的計算
80                         '''
81                         Point0.y=i #(i*8)+k
82                         Point0.z=Z_Size        #//此參數能夠改變字符距離旋轉軸中心的距離
83                         
84                         Point1=vector_matrix_MULTIPLY(Point0,gMAT)#//矢量與矩陣相乘
85                         PointDis=PerProject(Point1,XO,YO)       #//映射投影
86                         Gui_Point(PointDis.x+x,PointDis.y+y,frontcolor)
87                     else:
88                         if model ==1: #模式為1 時才會繪制底色
89                             Point0.x=16-(k+(1-j)*8)+m*16+m*2
90                             Point0.y=i #(i*8)+k
91                             Point0.z=Z_Size        #//此參數能夠改變字符距離旋轉軸中心的距離
92                             
93                             Point1=vector_matrix_MULTIPLY(Point0,gMAT)#//矢量與矩陣相乘
94                             PointDis=PerProject(Point1,XO,YO)       #//映射投影
95                             Gui_Point(PointDis.x+x,PointDis.y+y,backcolor)
96      

 

 

    下面是實現旋轉時鍾的代碼:

  1 # -*- coding: utf-8 -*-
  2 """
  3 Created on Sat Jun 29 15:22:12 2019
  4 
  5 @author: Administrator
  6 """
  7 
  8 from Transform3D import *
  9 
 10 
 11 import pygame 
 12 import time 
 13 import math
 14 
 15 fontmultiple=0.8   #字符倍數比例函數,修改次數據可以實現整體的放大和縮小,建議此數值在0.8-1.3 之間,看起來比較合適
 16 
 17 SCREEN_X_MAX = int(800*fontmultiple) #屏幕的寬和高
 18 SCREEN_Y_MAX = int(800*fontmultiple)
 19 
 20 BLACK=(0,0,0)
 21 WHITE=(255,255,255)
 22 RED=(255,0,0)
 23 GREEN=(0,255,0)
 24 BLUE=(0,0,255)
 25 
 26 ForeColor = RED #前景色和背景色
 27 BackColor = BLACK    
 28 
 29 
 30 pygame.init()
 31 screen = pygame.display.set_mode((SCREEN_X_MAX,SCREEN_Y_MAX))
 32 
 33 
 34 #myfont=pygame.font.Font(None,1)
 35 #textImage=myfont.render("test",True,WHITE)
 36 
 37 Week_zw=("","","","","","","") #周的中文
 38 Day_zw=("","","","","","","","","","","") #可用於所有需要0-10的中文的地方
 39 Mouth_day=[31,28,31,30,31,30,31,31,30,31,30,31] #月份時長表
 40 
 41 
 42 
 43 '''
 44 圓: (x-a)^2+(y-b)^2=r^2
 45     a,b:圓心坐標
 46     r:半徑
 47     
 48     圓心:(x0,y0)
 49     半徑:r
 50     角度:a0
 51     圓上任意一點:(x1,y1)
 52     x1=x0+r*cos(a0 * 3.14/180)
 53     y1=y0+r*sin(a0 * 3.14/180)
 54 '''
 55 def Show_Year(x0,y0,r,angel):
 56     '''
 57     顯示年
 58     '''
 59     
 60     timenow=time.localtime(time.time())
 61     yearstr=Day_zw[(int)(timenow[0]/1000)]+Day_zw[int((timenow[0]%1000)/100)]+Day_zw[int((timenow[0]%100)/10)]+Day_zw[int((timenow[0]%10))]+""
 62     Show3D16x16Char(yearstr,0,0,angel-90,x0,y0,1,ForeColor,BackColor,0,fontmultiple)
 63 def Show_Week(x0,y0,r,agl):
 64     '''
 65     顯示周
 66     '''
 67     timenow=time.localtime(time.time()) #獲取時間
 68     for i in range(7):
 69         angel=360/7
 70        
 71         if i < timenow[6]:
 72             angel=agl-angel*(timenow[6]-i)
 73             ForeColor=RED
 74         elif i >timenow[6]:
 75             angel=agl+angel*(i-timenow[6])
 76             ForeColor=RED
 77         else :
 78             angel=agl  #當前周設為90度,即在水平方向上
 79             ForeColor=WHITE
 80             
 81         x1=x0 + r * math.sin(angel * 3.14/180) #由角度計算出當前應在的坐標點
 82         y1=y0 + r * math.cos(angel * 3.14/180)
 83         Show3D16x16Char(""+Week_zw[i],0,0,angel-90,(int)(x1),(int)(y1),1,ForeColor,BackColor,0,fontmultiple)#輸出旋轉后的字符串
 84 
 85 def Show_Month(x0,y0,r,agl):
 86     '''
 87     顯示月份
 88     '''
 89     timenow=time.localtime(time.time())#獲取時間
 90     for i in range(1,13): #一年12個月
 91         angel=360/12
 92        
 93         if i < timenow[1]:
 94             angel=agl-angel*(timenow[1]-i) #其他月份相應得到角度推算
 95             ForeColor=RED
 96         elif i >timenow[1]:
 97             angel=agl+angel*(i-timenow[1])
 98             ForeColor=RED
 99         else :
100             angel=agl  #當前月份設為90度,即在水平方向上
101             ForeColor=WHITE
102         x1=x0 + r * math.sin(angel * 3.14/180) #由角度計算出當前應在的坐標點
103         y1=y0 + r * math.cos(angel * 3.14/180)
104         if i>10:
105             monthstr=Day_zw[10]+Day_zw[i%10]+""  #得出要顯示的字符串
106         else:
107             monthstr = Day_zw[i]+""
108         Show3D16x16Char(monthstr,0,0,angel-90,(int)(x1),(int)(y1),1,ForeColor,BackColor,0,fontmultiple)#顯示字符串
109 
110 def Show_Day(x0,y0,r,agl):
111     '''
112     顯示日期
113     '''
114     timenow=time.localtime(time.time()) #獲取時間
115     if (timenow[0]%4==0 and timenow[0]%100 !=0) or (timenow[0]%400 == 0):
116         Mouth_day[1]=29 #閏年,補全二月的時長表
117     else:
118         Mouth_day[1]=28
119         
120         #Mouth_day 為月份的時長表
121     for i in range(1,Mouth_day[timenow[1]-1]+1):
122         angel=360/Mouth_day[timenow[1]-1]
123         
124         if i < timenow[2]:
125             angel=agl-angel*(timenow[2]-i)#其他日期相應得到角度推算
126             ForeColor=RED
127         elif i >timenow[2]:
128             angel=agl+angel*(i-timenow[2])
129             ForeColor=RED
130         else :
131             angel=agl  #當前日期設為90度,即在水平方向上
132             ForeColor=WHITE
133         x1=x0 + r * math.sin(angel * 3.14/180)#由角度計算出當前應在的坐標點
134         y1=y0 + r * math.cos(angel * 3.14/180)
135         if i>20:
136             if i%10 !=0:
137                 daystr= Day_zw[(int)(i/10) ]+ Day_zw[10] +Day_zw[i%10]+""#得出要顯示的字符串
138             else:
139                 daystr= Day_zw[(int)(i/10) ]+ Day_zw[10]+""
140         elif i>10:
141             daystr= Day_zw[10]+Day_zw[i%10]+""
142         else:
143             daystr=Day_zw[i]+""
144         Show3D16x16Char(daystr,0,0,angel-90,(int)(x1),(int)(y1),1,ForeColor,BackColor,0,fontmultiple)#顯示字符串
145         
146 
147 def Show_Hour(x0,y0,r,agl):
148     '''
149     顯示時
150     '''
151     
152     timenow=time.localtime(time.time())#獲取時間
153 
154     for i in range(0,24):
155         angel=360/24
156         
157         if i < timenow[3]:
158             angel=agl-angel*(timenow[3]-i)#其他時間相應得到角度推算
159             ForeColor=RED
160         elif i >timenow[3]:
161             angel=agl+angel*(i-timenow[3])
162             ForeColor=RED
163         else :
164             angel=agl  #當前時間設為90度,即在水平方向上
165             ForeColor=WHITE
166         x1=x0 + r * math.sin(angel * 3.14/180)
167         y1=y0 + r * math.cos(angel * 3.14/180)
168         if i>20:
169             if i%10 !=0:
170                 daystr= Day_zw[(int)(i/10) ]+ Day_zw[10] +Day_zw[i%10]+""  #得到要顯示的字符串
171             else:
172                 daystr= Day_zw[(int)(i/10) ]+ Day_zw[10]+""
173         elif i>10:
174             daystr= Day_zw[10]+Day_zw[i%10]+""
175         else:
176             daystr=Day_zw[i]+""
177         Show3D16x16Char(daystr,0,0,angel-90,(int)(x1),(int)(y1),1,ForeColor,BackColor,0,fontmultiple) #顯示角度計算后的字符串
178         
179 def Show_Min(x0,y0,r,agl):
180     '''
181     顯示分
182     '''
183     
184     timenow=time.localtime(time.time())
185 
186     for i in range(0,60):
187         angel = 360/60
188         
189         if i < timenow[4]:
190             angel=agl-angel*(timenow[4]-i)-(360/60/60 * timenow[5] ) #將秒數也代入角度計算中就可以得到更精確的角度偏移,而且每秒鍾都會移動,好看
191             ForeColor=RED
192         elif i > timenow[4]:
193             angel = agl + angel * (i-timenow[4]) - (360/60/60 * timenow[5] )
194             ForeColor = RED
195         else :
196             angel = agl - (360/60/60 * timenow[5] )  #當前日期設為90度-秒鍾的角度,實現動態顯示
197             ForeColor = WHITE
198         x1 = x0 + r * math.sin(angel * 3.14/180) #由角度計算出當前應在的坐標點
199         y1 = y0 + r * math.cos(angel * 3.14/180)
200         if i>=20:
201             if i%10 !=0:
202                 daystr = Day_zw[(int)(i/10) ]+ Day_zw[10] +Day_zw[i%10]+"" #得到要顯示的字符串
203             else:
204                 daystr = Day_zw[(int)(i/10) ]+ Day_zw[10]+""
205         elif i>10:
206             daystr = Day_zw[10]+Day_zw[i%10]+""
207         else:
208             daystr = Day_zw[i]+""
209         Show3D16x16Char(daystr,0,0,angel-90,(int)(x1),(int)(y1),1,ForeColor,BackColor,0,fontmultiple)#顯示角度計算后的字符串
210 
211 
212 i=0
213 while True:
214     for event in pygame.event.get():
215         if event.type in (QUIT,KEYDOWN):
216             pygame.quit()
217             sys.exit()
218             
219     i+=1  #開始的動畫,調整大小可以調整速度,因為沒優化,所以顯示速度很慢,數據大一些動畫能盡早結束
220     if i>90:
221         i=90
222         
223     Show_Year(SCREEN_X_MAX/2-40*fontmultiple,SCREEN_Y_MAX/2,32,90) #顯示年
224     Show_Week(SCREEN_X_MAX/2,SCREEN_Y_MAX/2,65*fontmultiple,180-i) #顯示周
225     Show_Month(SCREEN_X_MAX/2,SCREEN_Y_MAX/2,110*fontmultiple,i)    #顯示月份
226     Show_Day(SCREEN_X_MAX/2,SCREEN_Y_MAX/2,170*fontmultiple,180-i)  #顯示日期
227     Show_Hour(SCREEN_X_MAX/2,SCREEN_Y_MAX/2,245*fontmultiple,i)     #顯示時
228     Show_Min(SCREEN_X_MAX/2,SCREEN_Y_MAX/2,325*fontmultiple,180-i)  #顯示分
229     
230     pygame.draw.circle(screen,ForeColor,((int)(SCREEN_X_MAX/2),(int)(SCREEN_Y_MAX/2)),int(55*fontmultiple),1) #顯示幾個圓
231     pygame.draw.circle(screen,ForeColor,((int)(SCREEN_X_MAX/2),(int)(SCREEN_Y_MAX/2)),int(100*fontmultiple),1)
232     pygame.draw.circle(screen,ForeColor,((int)(SCREEN_X_MAX/2),(int)(SCREEN_Y_MAX/2)),int(160*fontmultiple),1)
233     pygame.draw.circle(screen,ForeColor,((int)(SCREEN_X_MAX/2),(int)(SCREEN_Y_MAX/2)),int(235*fontmultiple),1)
234     pygame.draw.circle(screen,ForeColor,((int)(SCREEN_X_MAX/2),(int)(SCREEN_Y_MAX/2)),int(315*fontmultiple),1)
235     
236     pygame.display.update()
237     screen.fill(0)  #屏幕清零
238     #time.sleep(30/1000)
View Code

   其他調用的代碼都在 Transform3D.py文件中,可在我的另一篇博客《python+基本3D顯示》中下載,然后將上面的字符串顯示代碼加進去,就可以了。

 --------------------------------------------2019-10-26補充----------------------------------

時鍾界面的速度很慢(尤其是剛開始的旋轉那兒,很卡的,才剛能到幾幀),流暢度不夠。猜測是因為每個漢字的點陣都要打開文件讀取一次的原因(也有可能是python本身效率低)。

若是想加快,建議的方法如下:

  1:先試試將字體庫讀取如緩存,然后調用,不要每個點陣都要open一次文件

  2:將計算函數使用numpy庫替換和優化

  若是還不行,可以使用C/C++改寫底層的點陣讀取和計算,但是這么做不如直接使用C/C++寫完整的代碼了......

------------------------------------------------------------------------------------------------------

 

    

本文水平有限,內容很多詞語由於知識水平問題不嚴謹或很離譜,但主要作為記錄作用,能理解就好了,希望以后的自己和路過的大神對必要的錯誤提出批評與指點,對可笑的錯誤不要嘲笑,指出來我會改正的。 

 另外,轉載使用請注明作者和出處,不要刪除文檔中的關於作者的注釋。                                                                                                

                                                                                             隨夢,隨心,隨願,恆執念,為夢執戰,執戰蒼天!    ------------------執念執戰

 

      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM