当前位置: 首页 > article >正文

使用Python实现矢量路径的压缩、解压与可视化

引言

在图形设计和Web开发中,矢量路径数据的高效存储与传输至关重要。本文将通过一个Python示例,展示如何将复杂的矢量路径命令序列压缩为JSON格式,再将其解压还原,并通过matplotlib进行可视化。这一过程可应用于字体设计、矢量图形编辑或Web应用中的路径数据传输。


核心功能概述

1. 路径命令解析

  • 输入:包含moveTolineToqCurveTo(二次贝塞尔曲线)、closePath命令的路径数据。
  • 输出:转换为matplotlib.path.Path对象,用于绘制矢量图形。

2. 路径数据压缩

  • 将路径命令序列转换为紧凑的JSON格式,便于存储或传输。
  • 示例:moveTo((100, 177)){"M":[100,177]}

3. 路径数据解压

  • 将JSON格式还原为原始路径命令序列,确保数据完整性。

4. 可视化

  • 使用matplotlib渲染路径,验证压缩/解压过程的正确性。

代码实现详解

1. 路径命令解析(parse_commands函数)

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':# 处理二次贝塞尔曲线(每段需要两个控制点和一个终点)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])  # 闭合路径回到起点return codes, vertices
关键点:
  • 二次贝塞尔曲线qCurveTo命令需两个控制点和一个终点,通过Path.CURVE3实现。
  • 闭合路径CLOSEPOLY命令自动连接最后一个点到起点。

2. 路径数据压缩(compress_path_to_json函数)

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:# 将坐标元组展平为一维列表(如 [(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. 路径数据解压(decompress_json_to_path函数)

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, ()))  # 闭合路径无参数else:# 将一维列表转换为坐标元组(如 [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函数)

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)  # 根据数据范围调整坐标轴ax.set_ylim(-30, 220)plt.gca().set_aspect('equal')plt.show()

完整代码与运行结果

示例数据

data = [('moveTo', ((100, 177),)),('lineTo', ((107, 169),)),('qCurveTo', ((116, 172), (127, 172))),# ... 其他路径命令(如闭合路径、复杂曲线)
]

执行流程

# 压缩数据
compressed_json = compress_path_to_json(data)
print("压缩后的JSON:", compressed_json)# 解压数据
decompressed = decompress_json_to_path(compressed_json)
print("解压后的路径数据:", decompressed)# 可视化
show_ttf(decompressed)

结果展示

1. 压缩后的JSON片段

[{"M":[100,177]},{"L":[107,169]},{"Q":[116,172,127,172]},{"Z":[]}
]

2. 解压后的路径数据

[('moveTo', ((100, 177),)),('lineTo', ((107, 169),)),('qCurveTo', ((116, 172), (127, 172))),('closePath', ())
]

技术要点总结

  1. 路径命令映射

    • MmoveTo:移动到起点
    • LlineTo:绘制直线
    • QqCurveTo:二次贝塞尔曲线
    • ZclosePath:闭合路径
  2. JSON压缩策略

    • 将坐标元组展平为一维列表,减少冗余。
    • 闭合路径(Z)的参数为空列表。
  3. matplotlib路径渲染

    • 使用Path对象和PathPatch实现复杂曲线的绘制。
    • CURVE3命令需成对使用,适配二次贝塞尔曲线的参数。

应用场景

  • Web开发:将矢量路径数据嵌入SVG或Canvas元素。
  • 字体设计:存储和传输字体轮廓路径。
  • 数据可视化:动态生成并传输图表路径数据。

import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches# 解析输入数据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 segmentfor i in range(0, len(params) - 1, 2):  # Ensure we don't go out of boundscontrol_point = params[i]end_point = params[i + 1]codes.extend([Path.CURVE3, Path.CURVE3])  # Two CURVE3 commands for the quad Beziervertices.extend([control_point, end_point])elif command == 'closePath':codes.append(Path.CLOSEPOLY)vertices.append(vertices[0])  # Closing back to the start pointreturn codes, verticesdef 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 extentax.set_ylim(-30, 220)  # Adjust these limits based on your data's extentplt.gca().set_aspect('equal', adjustable='box')  # Keep aspect ratio equalplt.show()import jsondef 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无需坐标else:# 确保params[0]是坐标点列表(即使只有一个点)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,参数为空decompressed.append((original_cmd, ()))else:# 将points列表转换为坐标点元组的元组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 decompressedcompressed_json = compress_path_to_json(data)# 解压
decompressed = decompress_json_to_path(compressed_json)

相关文章:

使用Python实现矢量路径的压缩、解压与可视化

引言 在图形设计和Web开发中&#xff0c;矢量路径数据的高效存储与传输至关重要。本文将通过一个Python示例&#xff0c;展示如何将复杂的矢量路径命令序列压缩为JSON格式&#xff0c;再将其解压还原&#xff0c;并通过matplotlib进行可视化。这一过程可应用于字体设计、矢量图…...

达梦数据库迁移问题总结

更多技术博客&#xff0c;请关注微信公众号&#xff1a;运维之美 问题一、DTS工具运行乱码 开启图形化 [rootlocalhost ~]# xhost #如果命令不存在执行sudo yum install xorg-x11-server-utils xhost: unable to open display "" [rootlocalhost ~]# su - dmd…...

OpenHarmony荷兰研习会回顾 | 仓颉语言赋能原生应用开发实践

近日&#xff0c;由全球顶级学术峰会EuroSys/ASPLOS和OpenHarmony社区在荷兰鹿特丹合办的操作系统深度研习会圆满收官&#xff0c;本次研习会以"架构探秘-开发实践-创新实验"三位一体的进阶模式&#xff0c;为全球开发者构建了沉浸式技术探索平台。其中&#xff0c;由…...

【远程工具】0 std::process::Command 介绍

std::process::Command 是 Rust 标准库中用于创建和配置子进程的主要类型。它允许你启动新的进程、设置其参数和环境变量、重定向输入/输出等。 基本用法 use std::process::Command;let output Command::new("echo").arg("Hello, world!").output().ex…...

【JAVA】JVM 堆内存“缓冲空间”的压缩机制及调整方法

1. 缓冲空间是否可压缩&#xff1f; 是的&#xff0c;JVM 会在满足条件时自动收缩堆内存&#xff0c;将未使用的缓冲空间释放回操作系统。但需满足以下条件&#xff1a; GC 触发堆收缩&#xff1a;某些垃圾回收器&#xff08;如 G1、Serial、Parallel&#xff09;在 Full GC …...

RV1126 人脸识别门禁系统解决方案

1. 方案简介 本方案为类人脸门禁机的产品级解决方案,已为用户构建一个带调度框架的UI应用工程;准备好我司的easyeai-api链接调用;准备好UI的开发环境。具备低模块耦合度的特点。其目的在于方便用户快速拓展自定义的业务功能模块,以及快速更换UI皮肤。 2. 快速上手 2.1 开…...

matlab内置的git软件版本管理功能

1、matlab多人协作开发比普通的嵌入式软件开发困难很多 用过matlab的人都知道,版本管理对于matlab来说真的很费劲,今天介绍的这个工具也不是说它就解决了这个痛点,只是让它变得简单一点。版本管理肯定是不可或缺的,干就完了 2、操作说明 如图所示,源代码管理,选项罗列的…...

【问题排查】SQLite安装失败

启动 Django 自带的开发服务器 python manage.py runserver出现如下报错&#xff1a; [rootiZ2zedudtf2cwzi9argky2Z myproject]# python manage.py runserver Watching for file changes with StatReloader Performing system checks...System check identified no issues (…...

Express中间件(Middleware)详解:从零开始掌握(2)

1. 请求耗时中间件的增强版 问题&#xff1a;原版只能记录到控制台&#xff0c;如何记录到文件&#xff1f; 改进点&#xff1a; 使用process.hrtime()是什么&#xff1f;获取更高精度的时间支持将日志写入文件记录更多信息(IP地址、状态码)工厂函数模式使中间件可配置 con…...

《前端面试题之 CSS篇(第一集)》

目录 1、CSS的盒模型2、CSS选择器及其优先级3、隐藏元素的方法有那些4、px、em、rem的区别及使用场景5、重排、重绘有什么区别6、水平垂直居中的实现7、CSS中可继承与不可继承属性有哪些8、Sass、Less 是什么&#xff1f;为什么要使用他们&#xff1f;9、CSS预处理器/后处理器是…...

MySQL部分总结

mysql学习笔记&#xff0c;如有不足还请指出&#xff0c;谢谢。 外连接&#xff0c;内连接&#xff0c;全连接 外连接&#xff1a;左外、右外 内连接&#xff1a;自己和自己连接 全连接&#xff1a;左外连接右外链接 mysql unique字段 unique可以在数据库层面避免插入相同…...

2025第十六届蓝桥杯PythonB组部分题解

一、攻击次数 题目描述 小蓝操控三个英雄攻击敌人&#xff0c;敌人初始血量2025&#xff1a; 第一个英雄每回合固定攻击5点第二个英雄奇数回合攻击15点&#xff0c;偶数回合攻击2点第三个英雄根据回合数除以3的余数攻击&#xff1a;余1攻2点&#xff0c;余2攻10点&#xff0…...

RocketMQ 中的 MessageStore 组件:消息存储的核心枢纽

引言 在现代分布式系统中&#xff0c;消息队列扮演着至关重要的角色&#xff0c;它能够实现系统间的异步通信、解耦服务以及削峰填谷等功能。RocketMQ 作为一款高性能、高可靠的分布式消息队列&#xff0c;在众多企业级应用中得到了广泛的应用。而在 RocketMQ 的架构体系里&am…...

Linux Kernel 2

地址空间&#xff08;Address Space&#xff09; 一、物理地址空间&#xff08;Physical Address Space&#xff09; 物理地址空间 是指 RAM 和设备内存 在系统内存总线上所呈现的地址布局。 举例&#xff1a;在典型的 32 32 32 位 Intel 架构中&#xff0c; RAM&#xff08…...

AndroidTV D贝桌面-v3.2.5-[支持文件传输]

AndroidTV D贝桌面 链接&#xff1a;https://pan.xunlei.com/s/VONXSBtgn8S_BsZxzjH_mHlAA1?pwdzet2# AndroidTV D贝桌面-v3.2.5[支持文件传输] 第一次使用的话&#xff0c;壁纸默认去掉的&#xff0c;不需要按遥控器上键&#xff0c;自己更换壁纸即可...

线性方程组的解法

文章目录 线性方程组的解法认识一些基本的矩阵函数MATLAB 实现机电工程学院教学函数构造1.高斯消元法2.列主元消去法3. L U LU LU分解法 线性方程组的解法 看到以下线性方程组的一般形式&#xff1a;设有以下的 n n n阶线性方程组&#xff1a; A x b \mathbf{Ax}\mathbf{b} A…...

轻量级锁是什么?轻在哪里?重量级锁是什么?重在哪里?

轻量级锁 vs 重量级锁&#xff1a;核心区别与设计哲学 在JVM的锁优化体系中&#xff0c;轻量级锁和重量级锁是两种不同竞争强度下的解决方案。它们的核心差异体现在 资源消耗、适用场景和实现机制 上。以下是详细对比&#xff1a; 一、轻量级锁&#xff08;Thin Lock&#xff…...

Python赋能量子计算:算法创新与应用拓展

量子计算与Python结合的算法开发与应用研究 摘要 量子计算作为计算机科学的前沿技术,凭借其独特的计算能力在解决复杂问题方面展现出巨大潜力。Python作为一种高效、灵活的编程语言,为量子计算算法的开发提供了强大的支持。本文从研究学者的视角,系统探讨了量子计算与Pytho…...

Java学习笔记(多线程):ReentrantLock 源码分析

本文是自己的学习笔记&#xff0c;主要参考资料如下 JavaSE文档 1、AQS 概述1.1、锁的原理1.2、任务队列1.2.1、结点的状态变化 1.3、加锁和解锁的简单流程 2、ReentrantLock2.1、加锁源码分析2.1.1、tryAcquire()的具体实现2.1.2、acquirQueued()的具体实现2.1.3、tryLock的具…...

【LeetCode 热题100】二叉树构造题精讲:前序 + 中序建树 有序数组构造 BST(力扣105 / 108)(Go语言版)

&#x1f331; 二叉树构造题精讲&#xff1a;前序 中序建树 & 有序数组构造 BST 本文围绕二叉树的两类构造类题目展开解析&#xff1a; 从前序与中序遍历序列构造二叉树 将有序数组转换为二叉搜索树 我们将从「已知遍历构造树」和「平衡构造 BST」两个角度&#xff0c;拆…...

【软考系统架构设计师】系统配置与性能评价知识点

1、 常见的性能指标 主频外频*倍频 主频1/CPU时钟周期 CPI&#xff08;Clock Per Instruction&#xff09;平均每条指令的平均时间周期数 IPC&#xff08;Instruction Per Clock&#xff09;每时钟周期运行指令数 MIPS百万条指令每秒 MFLOPS百万个浮点操作每秒 字长影响运算的…...

【android bluetooth 协议分析 01】【HCI 层介绍 1】【hci_packets.pdl 介绍】

在 AOSP 的蓝牙协议栈 (Gabeldorsche) 中&#xff0c;hci_packets.pdl 是一个 协议描述语言文件&#xff0c;用于定义 HCI (Host Controller Interface) 层的数据包结构和通信协议。以下是详细解析&#xff1a; 1. 文件作用 system/gd/hci/hci_packets.pdl 协议自动化生成&…...

低资源需求的大模型训练项目---调研0.5B大语言模型

一、主流0.5B大语言模型及性能对比 1. Qwen系列&#xff08;阿里&#xff09; • Qwen2.5-0.5B&#xff1a;阿里2024年9月开源的通义千问系列最小尺寸模型&#xff0c;支持32K上下文长度和8K生成长度。在中文场景下表现优异&#xff0c;指令跟踪、JSON结构化输出能力突出&…...

Spring Boot 中集成 Disruptor_高性能事件处理框架

1. 引言 1.1 什么是 Disruptor Disruptor 是一个高性能的事件处理框架,广泛应用于金融交易系统、日志记录、消息队列等领域。它通过无锁机制和环形缓冲区(Ring Buffer)实现高效的事件处理,具有极低的延迟和高吞吐量的特点。 1.2 为什么使用 Disruptor 高性能:通过无锁机…...

解锁Midjourney创作潜能:超详细提示词(Prompts)分类指南

AI生图自由&#xff01;就来 ChatTools (https://chat.chattools.cn)&#xff0c;畅享Midjourney免费无限绘画。同时体验GPT-4o、Claude 3.7 Sonnet、DeepSeek等强大模型。 为了帮助大家更好地驾驭Midjourney&#xff0c;我们精心整理并分类了大量常用且效果出众的提示词。无论…...

Vue3.5 + Vite6.x 项目的完整 Stylelint 配置方案,支持 .vue/.html 内联样式、Less/SCSS/CSS 等多种文件类

Vue3.5 Vite6.x 项目的完整 Stylelint 配置方案&#xff0c;支持 .vue/.html 内联样式、Less/SCSS/CSS 等多种文件类型 一、完整依赖安装 npm install --save-dev stylelint stylelint-config-standard postcss-html # 解析 Vue/HTML 文件中的样式postcss-scss …...

大模型分布式推理和量化部署

一、小常识 1、计算大模型占用多少显存 对于一个7B(70亿)参数的模型,每个参数使用16位浮点数(等于 2个 Byte)表示,则模型的权重大小约为: 7010^9 parameters2 Bytes/parameter=14GB 70亿个参数每个参数占用2个字节=14GB 所以我们需要大于14GB的显存。注意14GB单纯是大…...

Ubuntu 下通过 Docker 部署 WordPress 服务器

最近想恢复写私人博客的习惯&#xff0c;准备搭建一个wordpress。 在这篇博客中&#xff0c;我将记录如何在 Ubuntu 环境下通过 Docker 部署一个 WordPress 服务器。WordPress 是一个流行的内容管理系统&#xff08;CMS&#xff09;&#xff0c;它让用户能够轻松地创建和管理网…...

【ROS】分布式通信架构

【ROS】分布式通信架构 前言环境要求主机设置&#xff08;Master&#xff09;从机设置&#xff08;Slave&#xff09;主机与从机通信测试本文示例启动ROS智能车激光雷达节点本地计算机配置与订阅 前言 在使用 ROS 时&#xff0c;我们常常会遇到某些设备计算能力不足的情况。例…...

零基础HTML·笔记(持续更新…)

基础认知 HTML标签的结构 <strong>文字变粗</strong> &#xff1c;开始标签&#xff1e;内容&#xff1c;结束标签&#xff1e; 结构说明&#xff1a; 标签由<、>、1、英文单词或字母组成。并且把标签中<>包括起来的英文单词或字母称为标签名。常…...