SoFunction
Updated on 2025-04-27

Use Python to implement compression, decompression and visualization of vector paths

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:IncludemoveTolineToqCurveTo(Quadratic Bezier curve),closePathThe path data of the command.
  • Output: Convert toObject, 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

  • usematplotlibRender 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 curveqCurveToThe command requires two control points and one end point, throughPath.CURVE3accomplish.
  • Closed pathCLOSEPOLYThe 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

  1. 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
  2. JSON compression strategy

    • Flatten the coordinate tuple into a one-dimensional list to reduce redundancy.
    • Closed path(Z) is an empty list of parameters.
  3. matplotlib path rendering

    • usePathObjects andPathPatchImplement the drawing of complex curves.
    • CURVE3The commands must be used in pairs and adapted to the parameters of the quadratic Bezier curve.

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!