introduction
In graphic design and web development, efficient storage and transmission of vector path data is crucial. This article will use a Python example to show how to compress a complex vector path command sequence into JSON format, then decompress and restore it, and visualize it through matplotlib. This process can be applied to font design, vector graphics editing, or path data transmission in web applications.
Core Function Overview
1. Path command analysis
-
enter:Include
moveTo
、lineTo
、qCurveTo
(Quadratic Bezier curve),closePath
The path data of the command. -
Output: Convert to
Object, used to draw vector graphics.
2. Path data compression
- Converts a sequence of path commands to a compact JSON format for easy storage or transmission.
- Example:
moveTo((100, 177))
→{"M":[100,177]}
。
3. Path data decompression
- Restore JSON format to the original path command sequence to ensure data integrity.
4. Visualization
- use
matplotlib
Render the path and verify the correctness of the compression/decompression process.
Detailed explanation of code implementation
1. Path command parse (parse_commands function)
def parse_commands(data): codes = [] vertices = [] for cmd, params in data: if cmd == 'moveTo': () (params[0]) elif cmd == 'lineTo': () (params[0]) elif cmd == 'qCurveTo': # Processing quadratic Bezier curves (each section requires two control points and one end point) for i in range(0, len(params), 2): control = params[i] end = params[i+1] if i+1 < len(params) else params[-1] ([Path.CURVE3, Path.CURVE3]) ([control, end]) elif cmd == 'closePath': () (vertices[0]) # Close the path back to the starting point return codes, vertices
Key points:
-
Quadratic Bezier curve:
qCurveTo
The command requires two control points and one end point, throughPath.CURVE3
accomplish. -
Closed path:
CLOSEPOLY
The command automatically connects the last point to the starting point.
2. Path data compression (compress_path_to_json function)
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': ({cmd_short: []}) else: # Flatten the coordinate tuple into a one-dimensional list (such as [(x,y), (a,b)] → [x,y,a,b]) for coord in params: (list(coord)) ({cmd_short: points}) return (compressed, separators=(',', ':'))
Sample output:
[{"M":[100,177]},{"L":[107,169]},{"Q":[116,172,127,172]},...]
3. Decompression of path data (decompress_json_to_path function)
def decompress_json_to_path(compressed_json): command_map = {'M': 'moveTo', 'L': 'lineTo', 'Q': 'qCurveTo', 'Z': 'closePath'} data = (compressed_json) decompressed = [] for item in data: cmd_short = next(iter(item)) points = item[cmd_short] cmd = command_map[cmd_short] if not points: ((cmd, ())) # Closed path without parameters else: # Convert a one-dimensional list to a coordinate tuple (such as [x,y,a,b] → [(x,y), (a,b)]) coords = [] for i in range(0, len(points), 2): ((points[i], points[i+1])) ((cmd, tuple(coords))) return decompressed
4. Visual rendering (show_ttf function)
def show_ttf(data): codes, vertices = parse_commands(data) path = Path(vertices, codes) fig, ax = () patch = (path, facecolor='orange', lw=2) ax.add_patch(patch) ax.set_xlim(0, 250) # Adjust the coordinate axis according to the data range ax.set_ylim(-30, 220) ().set_aspect('equal') ()
Complete code and run results
Sample data
data = [ ('moveTo', ((100, 177),)), ('lineTo', ((107, 169),)), ('qCurveTo', ((116, 172), (127, 172))), # ... Other path commands (such as closed paths, complex curves)]
Execution process
# Compress datacompressed_json = compress_path_to_json(data) print("Compressed JSON:", compressed_json) # Unzip the datadecompressed = decompress_json_to_path(compressed_json) print("Decompressed path data:", decompressed) # Visualizationshow_ttf(decompressed)
Results Display
1. Compressed JSON fragments
[ {"M":[100,177]}, {"L":[107,169]}, {"Q":[116,172,127,172]}, {"Z":[]} ]
2. Decompressed path data
[ ('moveTo', ((100, 177),)), ('lineTo', ((107, 169),)), ('qCurveTo', ((116, 172), (127, 172))), ('closePath', ()) ]
Summary of technical points
-
Path command mapping:
-
M
→moveTo
: Move to the starting point -
L
→lineTo
: Draw a straight line -
Q
→qCurveTo
: Quadratic Bezier Curve -
Z
→closePath
: Closed path
-
-
JSON compression strategy:
- Flatten the coordinate tuple into a one-dimensional list to reduce redundancy.
- Closed path(
Z
) is an empty list of parameters.
-
matplotlib path rendering:
- use
Path
Objects andPathPatch
Implement the drawing of complex curves. -
CURVE3
The commands must be used in pairs and adapted to the parameters of the quadratic Bezier curve.
- use
Application scenarios
- Web Development: Embed vector path data into SVG or Canvas elements.
- Font design: Store and transfer font outline paths.
- Data visualization: Dynamically generate and transmit chart path data.
import as plt from import Path import as patches # parse input data def parse_commands(data): codes = [] vertices = [] for command, params in data: if command == 'moveTo': () (params[0]) elif command == 'lineTo': () (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] ([Path.CURVE3, Path.CURVE3]) # Two CURVE3 commands for the quad Bezier ([control_point, end_point]) elif command == 'closePath': () (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 = () patch = (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 ().set_aspect('equal', adjustable='box') # Keep aspect ratio equal () 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 no coordinates required else: # Make sure params[0] is a list of coordinate points (even if there is only one point) for param in params: points += list(param) ({ command_type: points }) return (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 = (compressed_json) decompressed = [] for item in data: cmd_char = next(iter(item)) # Get command characters points = item[cmd_char] original_cmd = command_map[cmd_char] if not points: # closePath, parameter is empty ((original_cmd, ())) else: # Convert points list to tuple of coordinate point tuple tuples = [] for i in range(0, len(points), 2): x = points[i] y = points[i + 1] ((x, y)) params = tuple(tuples) ((original_cmd, params)) return decompressed compressed_json = compress_path_to_json(data) # Uncompressiondecompressed = decompress_json_to_path(compressed_json)
The above is the detailed content of using Python to implement compression, decompression and visualization of vector paths. For more information about decompression and visualization of Python vector paths, please pay attention to my other related articles!