标题:Python 实现 ESC/P 串行数据解析并生成 BMP 图像教程
发布时间 - 2026-01-09 00:00:00 点击率:次本文详解如何用 python 解析 esc/p 协议中的点阵图像指令(如 \x1b* 或 \x1bk),将原始串行字节流转换为标准黑白 bmp 图像,适用于无打印机场景下的嵌入式设备图像捕获与存档。
ESC/P(Epson Standard Code for Printers)是一种广泛用于针式/点阵打印机的控制协议,其图像打印指令(如 ESC * 和 ESC K)以紧凑的位图格式传输逐列点阵数据。尽管现代应用中已少用物理打印机,但部分测试设备(如罗德与施瓦茨 CMS52 频谱监测仪)、工业终端或旧式 POS 设备仍通过串口输出 ESC/P 图像流。本文提供一套轻量、可扩展的 Python 解析方案,无需第三方 ESC/P 专用库,仅依赖标准库与 Pillow(PIL)即可完成从原始字节到 BMP 文件的端到端转换。
核心原理:ESC/P 图像指令结构
ESC/P 中最常用的位图指令有两种常见变体:
*`ESC m nL nH [data]**(m=0表示 8-bit 单色模式): 指令起始为\x1b\x2a(即ESC *),后跟模式字节m,再紧接两个字节nL(低字节)和nH(高字节)表示列数N = nH
ESC K nL nH [data](常见于 R&S CMS52 等设备):
起始为 \x1b\x4b,省略模式字节,直接以 nL+nH 指定列数,后续 N 字节为图像数据。
每字节代表一列(column)的 8 行像素(MSB 对应第 0 行,即顶部),bit 值为 1 表示该位置“有墨点”。因此,解析时需对每个字节执行 位展开(bit unpacking),并按行优先顺序重组像素矩阵。
完整可运行解析函数(兼容双指令)
以下代码统一支持 ESC * 与 ESC K 指令,并修复了原方案中宽高误置、指令偏移错误及边界处理缺陷:
from PIL import Image
import struct
import io
def parse_escp_to_bmp(data: bytes) -> bytes:
"""
将 ESC/P 串行字节流(含 ESC * 或 ESC K 图像指令)解析为 BMP 二进制数据。
支持两种主流格式:
- ESC * 0 nL nH [N bytes] → 起始索引 +3 处读取列数
- ESC K nL nH [N bytes] → 起始索引 +2 处读取列数
返回:BMP 格式字节流(可用于保存或传输)
"""
image_rows = [] # 存储所有行(每行是 list[int],元素为 0/1)
pos = 0
while pos < len(data):
# 查找 ESC * (\x1b\x2a) 或 ESC K (\x1b\x4b)
star_pos = data.find(b'\x1b\x2a', pos)
k_pos = data.find(b'\x1b\x4b', pos)
# 优先匹配更靠前的指令
if star_pos == -1 and k_pos == -1:
break
elif star_pos == -1:
cmd_start = k_pos
mode_offset = 0 # ESC K 无模式字节
col_bytes_offset = 2 # nL/nH 紧跟在 \x1b\x4b 后
elif k_pos == -1:
cmd_start = star_pos
mode_offset = 1 # ESC * 后第 1 字节为模式(通常为 0)
col_bytes_offset = 3 # nL/nH 在模式字节之后
else:
cmd_start = min(star_pos, k_pos)
mode_offset = 1 if cmd_start == star_pos else 0
col_bytes_offset = 3 if cmd_start == star_pos else 2
# 提取列数(nL + nH,大端)
col_bytes_start = cmd_start + col_bytes_offset
if col_bytes_start + 2 > len(data):
break
nL, nH = data[col_bytes_start:col_bytes_start + 2]
num_columns = (nH << 8) | nL
# 提取图像数据(num_columns 字节)
data_start = col_bytes_start + 2
data_end = data_start + num_columns
if data_end > len(data):
break
column_bytes = data[data_start:data_end]
# 将每列字节展开为 8 行像素(从顶行 MSB 开始 → 行索引 0~7)
for bit_pos in range(7, -1, -1): # 从 bit7(最高位)到 bit0(最低位)
row = []
for b in column_bytes:
row.append((b >> bit_pos) & 1)
image_rows.append(row)
# 更新搜索起始位置:跳过当前指令 + 数据 + 可能的校验/终止符(+2 是保守跳过 CR/LF 等)
pos = data_end + 2
if not image_rows:
raise ValueError("No valid ESC/P image data found in input stream.")
# 注意:image_rows 是 [行][列] 结构 → width = 列数,height = 行数
height = len(image_rows)
width = len(image_rows[0]) if height > 0 else 0
# 创建 1-bit 黑白图像('1' 模式),注意 PIL 的尺寸参数是 (width, height)
img = Image.new('1', (width, height), color=1) # 白底(1 = white, 0 = black)
pixels = [pixel for row in image_rows for pixel in row]
img.putdata(pixels)
# 输出为 BMP 字节流
buf = io.BytesIO()
img.save(buf, format='BMP')
return buf.getvalue()
# 使用示例
if __name__ == "__main__":
# 读取原始串口捕获文件(二进制)
with open("ESCP.bin", "rb") as f:
raw = f.read()
try:
bmp_data = parse_escp_to_bmp(raw)
with open("output.bmp", "wb") as f:
f.write(bmp_data)
print(f"✅ BMP saved successfully: {len(bmp_data)} bytes")
except Exception as e:
print(f"❌ Parsing failed: {e}")关键注意事项与优化建议
- ✅ 指令兼容性:本实现自动识别 ESC * 与 ESC K,适配 Epson 标准设备与 R&S CMS52 等非标设备;若遇其他指令(如 ESC L),可依协议扩展分支逻辑。
- ✅ 内存安全:严格校验字节边界(data_end
- ✅ 图像方向正确性:原问题中 Image.new('1', (height, width)) 是典型错误——PIL 要求 (width, height),且 ESC/P 的“列”对应图像宽度,“行”对应高度,必须严格区分。
- ⚠️ 多图像支持:当前版本按顺序提取所有图像块并纵向拼接(适合连续打印帧)。如需分离多张独立图像,可在每次解析后清空 image_rows 并分别保存。
- ⚙️ 性能提示:对超长数据流(>1MB),建议改用生成器分块处理,避免内存峰值;高频实时串口接收时,推荐结合 pyserial 的 read_until() 捕获完整指令块。
- ? 协议延伸:ESC/P 还支持压缩(如 ESC * m 1)、多密度(n=1/2/3 表示 8/24/32 行/列)、灰度(需 ESC .)等,进阶需求可参考 Epson ESC/P Reference。
通过本方案,开发者可快速构建 ESC/P 图像采集网关,将老旧设备输出转化为可编程处理的数字图像资源,为自动化测试、日志归档或 UI 模拟提供坚实基础。
# python
# cms
# app
# 打印机
# 字节
# ai
# stream
# 标准库
# elif
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
如何正确选择百度移动适配建站域名?
Laravel表单请求验证类怎么用_Laravel Form Request分离验证逻辑教程
Laravel怎么实现验证码(Captcha)功能
Laravel怎么连接多个数据库_Laravel多数据库连接配置
如何在景安云服务器上绑定域名并配置虚拟主机?
Python正则表达式进阶教程_复杂匹配与分组替换解析
如何为不同团队 ID 动态生成多个非值班状态按钮
Laravel如何配置中间件Middleware_Laravel自定义中间件拦截请求与权限校验【步骤】
Laravel如何处理和验证JSON类型的数据库字段
Laravel如何集成Inertia.js与Vue/React?(安装配置)
微信小程序 input输入框控件详解及实例(多种示例)
Laravel怎么实现模型属性转换Casting_Laravel自动将JSON字段转为数组【技巧】
网站建设整体流程解析,建站其实很容易!
使用豆包 AI 辅助进行简单网页 HTML 结构设计
微信小程序制作网站有哪些,微信小程序需要做网站吗?
java ZXing生成二维码及条码实例分享
Android仿QQ列表左滑删除操作
Midjourney怎样加参数调细节_Midjourney参数调整技巧【指南】
公司门户网站制作流程,华为官网怎么做?
开心动漫网站制作软件下载,十分开心动画为何停播?
悟空浏览器如何设置小说背景色_悟空浏览器背景色设置【方法】
在线ppt制作网站有哪些软件,如何把网页的内容做成ppt?
Mybatis 中的insertOrUpdate操作
Laravel如何优化应用性能?(缓存和优化命令)
如何将凡科建站内容保存为本地文件?
HTML透明颜色代码怎么让图片透明_给img元素加透明色的技巧【方法】
Laravel如何使用模型观察者?(Observer代码示例)
Python自然语言搜索引擎项目教程_倒排索引查询优化案例
百度浏览器ai对话怎么关 百度浏览器ai聊天窗口隐藏
Python并发异常传播_错误处理解析【教程】
Android GridView 滑动条设置一直显示状态(推荐)
Python结构化数据采集_字段抽取解析【教程】
香港服务器网站测试全流程:性能评估、SEO加载与移动适配优化
Laravel的辅助函数有哪些_Laravel常用Helpers函数提高开发效率
谷歌Google入口永久地址_Google搜索引擎官网首页永久入口
百度输入法全感官ai怎么关 百度输入法全感官皮肤关闭
Laravel如何配置任务调度?(Cron Job示例)
Win11怎么开启自动HDR画质_Windows11显示设置HDR选项
如何在服务器上三步完成建站并提升流量?
Internet Explorer官网直接进入 IE浏览器在线体验版网址
如何用西部建站助手快速创建专业网站?
香港服务器如何优化才能显著提升网站加载速度?
Laravel如何清理系统缓存命令_Laravel清除路由配置及视图缓存的方法【总结】
北京专业网站制作设计师招聘,北京白云观官方网站?
Laravel集合Collection怎么用_Laravel集合常用函数详解
网站制作大概多少钱一个,做一个平台网站大概多少钱?
活动邀请函制作网站有哪些,活动邀请函文案?
利用JavaScript实现拖拽改变元素大小
Laravel Debugbar怎么安装_Laravel调试工具栏配置指南
javascript中数组(Array)对象和字符串(String)对象的常用方法总结


# 注意:image_rows 是 [行][列] 结构 → width = 列数,height = 行数
height = len(image_rows)
width = len(image_rows[0]) if height > 0 else 0
# 创建 1-bit 黑白图像('1' 模式),注意 PIL 的尺寸参数是 (width, height)
img = Image.new('1', (width, height), color=1) # 白底(1 = white, 0 = black)
pixels = [pixel for row in image_rows for pixel in row]
img.putdata(pixels)
# 输出为 BMP 字节流
buf = io.BytesIO()
img.save(buf, format='BMP')
return buf.getvalue()
# 使用示例
if __name__ == "__main__":
# 读取原始串口捕获文件(二进制)
with open("ESCP.bin", "rb") as f:
raw = f.read()
try:
bmp_data = parse_escp_to_bmp(raw)
with open("output.bmp", "wb") as f:
f.write(bmp_data)
print(f"✅ BMP saved successfully: {len(bmp_data)} bytes")
except Exception as e:
print(f"❌ Parsing failed: {e}")