使用Python實(shí)現(xiàn)矢量路徑的壓縮、解壓與可視化
引言
在圖形設(shè)計(jì)和Web開發(fā)中,矢量路徑數(shù)據(jù)的高效存儲(chǔ)與傳輸至關(guān)重要。本文將通過一個(gè)Python示例,展示如何將復(fù)雜的矢量路徑命令序列壓縮為JSON格式,再將其解壓還原,并通過matplotlib進(jìn)行可視化。這一過程可應(yīng)用于字體設(shè)計(jì)、矢量圖形編輯或Web應(yīng)用中的路徑數(shù)據(jù)傳輸。
核心功能概述
1. 路徑命令解析
- 輸入:包含
moveTo、lineTo、qCurveTo(二次貝塞爾曲線)、closePath命令的路徑數(shù)據(jù)。 - 輸出:轉(zhuǎn)換為
matplotlib.path.Path對(duì)象,用于繪制矢量圖形。
2. 路徑數(shù)據(jù)壓縮
- 將路徑命令序列轉(zhuǎn)換為緊湊的JSON格式,便于存儲(chǔ)或傳輸。
- 示例:
moveTo((100, 177))→{"M":[100,177]}。
3. 路徑數(shù)據(jù)解壓
- 將JSON格式還原為原始路徑命令序列,確保數(shù)據(jù)完整性。
4. 可視化
- 使用
matplotlib渲染路徑,驗(yàn)證壓縮/解壓過程的正確性。
代碼實(shí)現(xiàn)詳解
1. 路徑命令解析(parse_commands函數(shù))
def parse_commands(data):
codes = []
vertices = []
for cmd, params in data:
if cmd == 'moveTo':
codes.append(Path.MOVETO)
vertices.append(params[0])
elif cmd == 'lineTo':
codes.append(Path.LINETO)
vertices.append(params[0])
elif cmd == 'qCurveTo':
# 處理二次貝塞爾曲線(每段需要兩個(gè)控制點(diǎn)和一個(gè)終點(diǎn))
for i in range(0, len(params), 2):
control = params[i]
end = params[i+1] if i+1 < len(params) else params[-1]
codes.extend([Path.CURVE3, Path.CURVE3])
vertices.extend([control, end])
elif cmd == 'closePath':
codes.append(Path.CLOSEPOLY)
vertices.append(vertices[0]) # 閉合路徑回到起點(diǎn)
return codes, vertices
關(guān)鍵點(diǎn):
- 二次貝塞爾曲線:
qCurveTo命令需兩個(gè)控制點(diǎn)和一個(gè)終點(diǎn),通過Path.CURVE3實(shí)現(xiàn)。 - 閉合路徑:
CLOSEPOLY命令自動(dòng)連接最后一個(gè)點(diǎn)到起點(diǎn)。
2. 路徑數(shù)據(jù)壓縮(compress_path_to_json函數(shù))
def compress_path_to_json(data):
command_map = {'moveTo': 'M', 'lineTo': 'L', 'qCurveTo': 'Q', 'closePath': 'Z'}
compressed = []
for cmd, params in data:
cmd_short = command_map[cmd]
points = []
if cmd == 'closePath':
compressed.append({cmd_short: []})
else:
# 將坐標(biāo)元組展平為一維列表(如 [(x,y), (a,b)] → [x,y,a,b])
for coord in params:
points.extend(list(coord))
compressed.append({cmd_short: points})
return json.dumps(compressed, separators=(',', ':'))
示例輸出:
[{"M":[100,177]},{"L":[107,169]},{"Q":[116,172,127,172]},...]
3. 路徑數(shù)據(jù)解壓(decompress_json_to_path函數(shù))
def decompress_json_to_path(compressed_json):
command_map = {'M': 'moveTo', 'L': 'lineTo', 'Q': 'qCurveTo', 'Z': 'closePath'}
data = json.loads(compressed_json)
decompressed = []
for item in data:
cmd_short = next(iter(item))
points = item[cmd_short]
cmd = command_map[cmd_short]
if not points:
decompressed.append((cmd, ())) # 閉合路徑無參數(shù)
else:
# 將一維列表轉(zhuǎn)換為坐標(biāo)元組(如 [x,y,a,b] → [(x,y), (a,b)])
coords = []
for i in range(0, len(points), 2):
coords.append((points[i], points[i+1]))
decompressed.append((cmd, tuple(coords)))
return decompressed
4. 可視化渲染(show_ttf函數(shù))
def show_ttf(data):
codes, vertices = parse_commands(data)
path = Path(vertices, codes)
fig, ax = plt.subplots()
patch = patches.PathPatch(path, facecolor='orange', lw=2)
ax.add_patch(patch)
ax.set_xlim(0, 250) # 根據(jù)數(shù)據(jù)范圍調(diào)整坐標(biāo)軸
ax.set_ylim(-30, 220)
plt.gca().set_aspect('equal')
plt.show()
完整代碼與運(yùn)行結(jié)果
示例數(shù)據(jù)
data = [
('moveTo', ((100, 177),)),
('lineTo', ((107, 169),)),
('qCurveTo', ((116, 172), (127, 172))),
# ... 其他路徑命令(如閉合路徑、復(fù)雜曲線)
]
執(zhí)行流程
# 壓縮數(shù)據(jù)
compressed_json = compress_path_to_json(data)
print("壓縮后的JSON:", compressed_json)
# 解壓數(shù)據(jù)
decompressed = decompress_json_to_path(compressed_json)
print("解壓后的路徑數(shù)據(jù):", decompressed)
# 可視化
show_ttf(decompressed)
結(jié)果展示
1. 壓縮后的JSON片段
[
{"M":[100,177]},
{"L":[107,169]},
{"Q":[116,172,127,172]},
{"Z":[]}
]
2. 解壓后的路徑數(shù)據(jù)
[
('moveTo', ((100, 177),)),
('lineTo', ((107, 169),)),
('qCurveTo', ((116, 172), (127, 172))),
('closePath', ())
]
技術(shù)要點(diǎn)總結(jié)
路徑命令映射:
M→moveTo:移動(dòng)到起點(diǎn)L→lineTo:繪制直線Q→qCurveTo:二次貝塞爾曲線Z→closePath:閉合路徑
JSON壓縮策略:
- 將坐標(biāo)元組展平為一維列表,減少冗余。
- 閉合路徑(
Z)的參數(shù)為空列表。
matplotlib路徑渲染:
- 使用
Path對(duì)象和PathPatch實(shí)現(xiàn)復(fù)雜曲線的繪制。 CURVE3命令需成對(duì)使用,適配二次貝塞爾曲線的參數(shù)。
- 使用
應(yīng)用場(chǎng)景
- Web開發(fā):將矢量路徑數(shù)據(jù)嵌入SVG或Canvas元素。
- 字體設(shè)計(jì):存儲(chǔ)和傳輸字體輪廓路徑。
- 數(shù)據(jù)可視化:動(dòng)態(tài)生成并傳輸圖表路徑數(shù)據(jù)。
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
# 解析輸入數(shù)據(jù)
def parse_commands(data):
codes = []
vertices = []
for command, params in data:
if command == 'moveTo':
codes.append(Path.MOVETO)
vertices.append(params[0])
elif command == 'lineTo':
codes.append(Path.LINETO)
vertices.append(params[0])
elif command == 'qCurveTo':
# Check if there are enough points to form a quadratic Bezier curve segment
for i in range(0, len(params) - 1, 2): # Ensure we don't go out of bounds
control_point = params[i]
end_point = params[i + 1]
codes.extend([Path.CURVE3, Path.CURVE3]) # Two CURVE3 commands for the quad Bezier
vertices.extend([control_point, end_point])
elif command == 'closePath':
codes.append(Path.CLOSEPOLY)
vertices.append(vertices[0]) # Closing back to the start point
return codes, vertices
def show_ttf():
codes, vertices = parse_commands(data)
path = Path(vertices, codes)
fig, ax = plt.subplots()
patch = patches.PathPatch(path, facecolor='orange', lw=2)
ax.add_patch(patch)
ax.set_xlim(0, 250) # Adjust these limits based on your data's extent
ax.set_ylim(-30, 220) # Adjust these limits based on your data's extent
plt.gca().set_aspect('equal', adjustable='box') # Keep aspect ratio equal
plt.show()
import json
def compress_path_to_json(data):
command_map = {
'moveTo': 'M',
'lineTo': 'L',
'qCurveTo': 'Q',
'closePath': 'Z'
}
compressed = []
for cmd, params in data:
command_type = command_map[cmd]
points = []
if cmd == 'closePath':
pass # closePath無需坐標(biāo)
else:
# 確保params[0]是坐標(biāo)點(diǎn)列表(即使只有一個(gè)點(diǎn))
for param in params:
points += list(param)
compressed.append({
command_type: points
})
return json.dumps(compressed, separators=(',', ':'))
data = [('moveTo', ((100, 177),)), ('lineTo', ((107, 169),)), ('qCurveTo', ((116, 172), (127, 172))),
('lineTo', ((240, 172),)), ('lineTo', ((224, 190),)), ('lineTo', ((212, 177),)), ('lineTo', ((175, 177),)),
('qCurveTo', ((183, 186), (176, 200), (154, 210))), ('lineTo', ((152, 207),)),
('qCurveTo', ((164, 190), (166, 177))), ('closePath', ()), ('moveTo', ((204, 143),)), ('lineTo', ((211, 148),)),
('lineTo', ((198, 162),)), ('lineTo', ((189, 152),)), ('lineTo', ((143, 152),)), ('lineTo', ((128, 160),)),
('qCurveTo', ((129, 149), (129, 116), (128, 102))), ('lineTo', ((142, 106),)), ('lineTo', ((142, 114),)),
('lineTo', ((191, 114),)), ('lineTo', ((191, 105),)), ('lineTo', ((205, 111),)),
('qCurveTo', ((204, 119), (204, 135), (204, 143))), ('closePath', ()), ('moveTo', ((142, 147),)),
('lineTo', ((191, 147),)), ('lineTo', ((191, 119),)), ('lineTo', ((142, 119),)), ('closePath', ()),
('moveTo', ((119, 87),)), ('lineTo', ((218, 87),)), ('lineTo', ((218, 6),)),
('qCurveTo', ((218, -3), (210, -5), (181, -3))), ('lineTo', ((181, -8),)),
('qCurveTo', ((212, -13), (212, -26))), ('qCurveTo', ((221, -22), (231, -12), (231, 2))),
('lineTo', ((231, 80),)), ('lineTo', ((240, 87),)), ('lineTo', ((224, 102),)), ('lineTo', ((216, 92),)),
('lineTo', ((119, 92),)), ('lineTo', ((105, 100),)), ('qCurveTo', ((106, 84), (106, 5), (105, -26))),
('lineTo', ((119, -18),)), ('closePath', ()), ('moveTo', ((196, 58),)), ('lineTo', ((203, 63),)),
('lineTo', ((188, 76),)), ('lineTo', ((182, 67),)), ('lineTo', ((151, 67),)), ('lineTo', ((137, 76),)),
('qCurveTo', ((138, 59), (138, 30), (137, 5))), ('lineTo', ((150, 11),)), ('lineTo', ((150, 21),)),
('lineTo', ((184, 21),)), ('lineTo', ((184, 10),)), ('lineTo', ((197, 16),)),
('qCurveTo', ((196, 27), (196, 48), (196, 58))), ('closePath', ()), ('moveTo', ((150, 62),)),
('lineTo', ((184, 62),)), ('lineTo', ((184, 26),)), ('lineTo', ((150, 26),)), ('closePath', ()),
('moveTo', ((36, 63),)), ('qCurveTo', ((66, 100), (94, 148))), ('lineTo', ((103, 152),)),
('lineTo', ((83, 163),)), ('qCurveTo', ((74, 138), (66, 125))), ('lineTo', ((30, 123),)),
('qCurveTo', ((50, 154), (71, 193))), ('lineTo', ((82, 197),)), ('lineTo', ((59, 209),)),
('qCurveTo', ((51, 178), (23, 124), (14, 124))), ('lineTo', ((25, 106),)),
('qCurveTo', ((31, 111), (50, 117), (63, 119))), ('qCurveTo', ((44, 87), (24, 63), (18, 62))),
('lineTo', ((28, 44),)), ('qCurveTo', ((39, 51), (68, 60), (98, 66))), ('lineTo', ((97, 70),)),
('qCurveTo', ((67, 66), (36, 63))), ('closePath', ()), ('moveTo', ((11, 14),)), ('lineTo', ((21, -4),)),
('qCurveTo', ((30, 4), (65, 20), (95, 30))), ('lineTo', ((94, 34),)),
('qCurveTo', ((72, 28), (25, 16), (11, 14))), ('closePath', ())]
def decompress_json_to_path(compressed_json):
command_map = {
'M': 'moveTo',
'L': 'lineTo',
'Q': 'qCurveTo',
'Z': 'closePath'
}
data = json.loads(compressed_json)
decompressed = []
for item in data:
cmd_char = next(iter(item)) # 獲取命令字符
points = item[cmd_char]
original_cmd = command_map[cmd_char]
if not points:
# closePath,參數(shù)為空
decompressed.append((original_cmd, ()))
else:
# 將points列表轉(zhuǎn)換為坐標(biāo)點(diǎn)元組的元組
tuples = []
for i in range(0, len(points), 2):
x = points[i]
y = points[i + 1]
tuples.append((x, y))
params = tuple(tuples)
decompressed.append((original_cmd, params))
return decompressed
compressed_json = compress_path_to_json(data)
# 解壓
decompressed = decompress_json_to_path(compressed_json)
以上就是使用Python實(shí)現(xiàn)矢量路徑的壓縮、解壓與可視化的詳細(xì)內(nèi)容,更多關(guān)于Python矢量路徑解壓縮與可視化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python獲取指定時(shí)間差的時(shí)間實(shí)例詳解
這篇文章主要介紹了python獲取指定時(shí)間差的時(shí)間實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04
Python實(shí)現(xiàn)指定數(shù)組下標(biāo)值正序與倒序排序算法功能舉例
在程序中,經(jīng)常需要按數(shù)組倒序或反序重新排列數(shù)組,下面這篇文章主要給大家介紹了關(guān)于Python實(shí)現(xiàn)指定數(shù)組下標(biāo)值正序與倒序排序算法功能的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02
python中的內(nèi)置函數(shù)max()和min()及mas()函數(shù)的高級(jí)用法
這篇文章主要介紹了python中的內(nèi)置函數(shù)max()和min()的相關(guān)知識(shí)及python中內(nèi)置函數(shù)max()的高級(jí)用法,需要的朋友可以參考下2018-03-03
python3.6+django2.0開發(fā)一套學(xué)員管理系統(tǒng)
本篇文章給大家詳細(xì)講述了python3.6+django2.0開發(fā)一套學(xué)員管理系統(tǒng)的全部過程以及源碼分享,有興趣的朋友參考下。2018-03-03
圖解Python中淺拷貝copy()和深拷貝deepcopy()的區(qū)別
這篇文章主要介紹了Python中淺拷貝copy()和深拷貝deepcopy()的區(qū)別,淺拷貝和深拷貝想必大家在學(xué)習(xí)中遇到很多次,這也是面試中常常被問到的問題,本文就帶你詳細(xì)了解一下2023-05-05
Python實(shí)現(xiàn)給qq郵箱發(fā)送郵件的方法
這篇文章主要介紹了Python實(shí)現(xiàn)給qq郵箱發(fā)送郵件的方法,涉及Python郵件發(fā)送的相關(guān)技巧,需要的朋友可以參考下2015-05-05

