0

0

标题:Python 实现 ESC/P 串行数据解析并生成 BMP 图像教程

心靈之曲

心靈之曲

发布时间:2026-01-09 13:53:08

|

892人浏览过

|

来源于php中文网

原创

标题:Python 实现 ESC/P 串行数据解析并生成 BMP 图像教程

本文详解如何用 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),并按行优先顺序重组像素矩阵。

雪鸮AI
雪鸮AI

高效便捷的智能绘图辅助工具,一键生成高质量效果图。

下载

立即学习Python免费学习笔记(深入)”;

完整可运行解析函数(兼容双指令)

以下代码统一支持 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开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

741

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

634

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

755

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

617

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1259

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

547

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

577

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

705

2023.08.11

c++主流开发框架汇总
c++主流开发框架汇总

本专题整合了c++开发框架推荐,阅读专题下面的文章了解更多详细内容。

3

2026.01.09

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 0.6万人学习

Django 教程
Django 教程

共28课时 | 2.9万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号