action
All checks were successful
Deploy WebSocket Server / deploy (push) Successful in 4s

This commit is contained in:
jeremygan2021
2026-03-05 21:09:37 +08:00
parent a784c88c60
commit 1b2c55afc7
5 changed files with 73 additions and 360 deletions

View File

@@ -1,226 +1,81 @@
import os
from PIL import Image, ImageOps
def image_to_tspl_commands(image_path):
"""
读取图片并转换为 TSPL 打印指令 (bytes)
包含: SIZE, GAP, CLS, BITMAP, PRINT
"""
if not os.path.exists(image_path):
print(f"错误: 找不到图片 {image_path}")
return None
try:
img = Image.open(image_path)
except Exception as e:
print(f"无法打开图片: {e}")
return None
# 处理透明背景
if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):
alpha = img.convert('RGBA').split()[-1]
bg = Image.new("RGBA", img.size, (255, 255, 255, 255))
bg.paste(img, mask=alpha)
img = bg
# 目标尺寸: 48mm x 30mm @ 203dpi
# 宽度: 48 * 8 = 384 dots
# 高度: 30 * 8 = 240 dots
MAX_WIDTH = 384
MAX_HEIGHT = 240
# 1. 强制裁剪/缩放以填满 (Aspect Fill / Cover)
# 计算目标比例
target_ratio = MAX_WIDTH / MAX_HEIGHT
img_ratio = img.width / img.height
if img_ratio > target_ratio:
# 图片更宽,以高度为基准缩放,然后裁剪宽度
new_height = MAX_HEIGHT
new_width = int(new_height * img_ratio)
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
# 居中裁剪
left = (new_width - MAX_WIDTH) // 2
img = img.crop((left, 0, left + MAX_WIDTH, MAX_HEIGHT))
else:
# 图片更高,以宽度为基准缩放,然后裁剪高度
new_width = MAX_WIDTH
new_height = int(new_width / img_ratio)
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
# 居中裁剪
top = (new_height - MAX_HEIGHT) // 2
img = img.crop((0, top, MAX_WIDTH, top + MAX_HEIGHT))
target_width, target_height = img.size
print(f"图片处理后尺寸 (Cover模式): {target_width}x{target_height}")
# 转为二值图
# 1. 先转灰度
img = img.convert('L')
# 2. 二值化 (使用默认的抖动算法)
img = img.convert('1')
# 构造 BITMAP 数据
width_bytes = (target_width + 7) // 8
data = bytearray()
# 遍历像素生成数据
for y in range(target_height):
row_bytes = bytearray(width_bytes)
for x in range(target_width):
pixel = img.getpixel((x, y))
# 逻辑修正:
# 我们希望 黑色像素(0) -> 打印(1)
# 白色像素(255) -> 不打印(0)
# 使用 < 128 判定,增加容错性,防止像素值偏移
should_print = False
if pixel < 128: # Black or Dark Gray
should_print = True
if should_print:
byte_index = x // 8
bit_index = 7 - (x % 8)
row_bytes[byte_index] |= (1 << bit_index)
data.extend(row_bytes)
# 计算居中偏移 (理论上 Cover 模式下偏移为 0)
x_offset = (MAX_WIDTH - target_width) // 2
y_offset = (MAX_HEIGHT - target_height) // 2
# 生成指令
cmds = bytearray()
# 1. 初始化
# CLS
cmds.extend(b"CLS\r\n")
# SIZE 48 mm, 30 mm
cmds.extend(b"SIZE 48 mm, 30 mm\r\n")
# GAP 2 mm, 0 mm
cmds.extend(b"GAP 2 mm, 0 mm\r\n")
# HOME
cmds.extend(b"HOME\r\n")
# 2. BITMAP
# BITMAP x, y, width_bytes, height, mode, data
header = f"BITMAP {x_offset},{y_offset},{width_bytes},{target_height},0,".encode('utf-8')
cmds.extend(header)
cmds.extend(data)
cmds.extend(b"\r\n") # BITMAP data 后面通常不需要回车,但有些文档建议加? 不binary data后紧跟下一个指令
# TSPL protocol says: BITMAP ... data ... CR LF is NOT required after data, but next command must start.
# Usually it's safer to just send data.
# 3. PRINT
cmds.extend(b"PRINT 1\r\n")
return cmds
def generate_micropython_printer_script(image_path, output_py_path):
"""
保留此函数以兼容现有 workflow但内部使用新的逻辑
"""
cmds = image_to_tspl_commands(image_path)
if not cmds:
return
# 提取 BITMAP 数据部分用于生成脚本 (因为脚本里是用 uart.write 分段发送的)
# 为了简单,我们重新解析一下 cmds 或者直接重写这部分逻辑
# 但为了脚本的可读性,还是像之前一样生成
# 重新执行一遍核心逻辑来获取参数 (为了生成漂亮的 python 代码)
if not os.path.exists(image_path): return
img = Image.open(image_path)
if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):
alpha = img.convert('RGBA').split()[-1]
bg = Image.new("RGBA", img.size, (255, 255, 255, 255))
bg.paste(img, mask=alpha)
img = bg
MAX_WIDTH = 384
MAX_HEIGHT = 240
img.thumbnail((MAX_WIDTH, MAX_HEIGHT), Image.Resampling.LANCZOS)
target_width, target_height = img.size
img = img.convert('L').convert('1')
width_bytes = (target_width + 7) // 8
data = bytearray()
for y in range(target_height):
row_bytes = bytearray(width_bytes)
for x in range(target_width):
if img.getpixel((x, y)) == 0:
byte_index = x // 8
bit_index = 7 - (x % 8)
row_bytes[byte_index] |= (1 << bit_index)
data.extend(row_bytes)
x_offset = (MAX_WIDTH - target_width) // 2
y_offset = (MAX_HEIGHT - target_height) // 2
hex_data = data.hex()
script_content = f'''from machine import UART
import time
from machine import UART
from config import ttl_tx, ttl_rx
from printer_driver import TsplPrinter
import ubinascii
# ==============================================================================
# 图片打印脚本 (自动生成)
# ==============================================================================
# 图片来源: {image_path}
# ==============================================================================
# 1. 初始化
uart = UART(1, baudrate=115200, tx=ttl_tx, rx=ttl_rx)
printer = TsplPrinter(uart)
def print_image():
print("=== 开始打印图片 ===")
def print_bitmap(printer, data, width, height, x_offset=0, y_offset=0):
"""
发送位图数据到打印机
:param printer: TsplPrinter 对象
:param data: 位图数据 (bytes), 每一位代表一个像素 (1=print/黑, 0=no print/白)
:param width: 图像宽度 (dots)
:param height: 图像高度 (dots)
:param x_offset: X轴偏移
:param y_offset: Y轴偏移
"""
width_bytes = (width + 7) // 8
# 2. 设置标签
printer.cls()
printer.size(48, 30)
printer.gap(2, 0)
# 3. 准备图片数据
img_hex = "{hex_data}"
img_data = ubinascii.unhexlify(img_hex)
# 4. 发送 BITMAP 指令
print(f"正在发送图片数据 ({{len(img_data)}} bytes)...")
# BITMAP X, Y, width_bytes, height, mode, data
# 居中打印
cmd = f"BITMAP {x_offset},{y_offset},{width_bytes},{target_height},0,".encode('utf-8')
uart.write(cmd)
# TSPL BITMAP 指令
# BITMAP x, y, width_bytes, height, mode, data
# mode=0: 正常模式
header = f"BITMAP {x_offset},{y_offset},{width_bytes},{height},0,".encode('utf-8')
printer.uart.write(header)
# 分段发送数据,防止串口缓冲区溢出
chunk_size = 128
for i in range(0, len(img_data), chunk_size):
uart.write(img_data[i : i + chunk_size])
for i in range(0, len(data), chunk_size):
printer.uart.write(data[i : i + chunk_size])
# 简单的流控,防止发送太快
time.sleep(0.005)
uart.write(b'\\r\\n')
printer.uart.write(b'\r\n')
def print_raw_image_file(file_path, width, height):
"""
直接打印存储在文件系统中的原始位图数据 (.bin)
该文件应包含预处理好的二进制像素数据 (1 bit per pixel)
# 5. 打印
:param file_path: 文件路径
:param width: 图片宽度 (dots)
:param height: 图片高度 (dots)
"""
try:
with open(file_path, 'rb') as f:
data = f.read()
except OSError:
print(f"错误: 无法打开文件 {file_path}")
return
# 初始化打印机
uart = UART(1, baudrate=115200, tx=ttl_tx, rx=ttl_rx)
printer = TsplPrinter(uart)
print("=== 开始打印图片 ===")
# 基础设置
printer.cls()
printer.size(48, 30) # 默认 48x30mm
printer.gap(2, 0)
# 计算居中位置 (假设标签纸最大宽度 384 dots, 高度 240 dots)
MAX_WIDTH = 384
MAX_HEIGHT = 240
x_offset = (MAX_WIDTH - width) // 2
y_offset = (MAX_HEIGHT - height) // 2
if x_offset < 0: x_offset = 0
if y_offset < 0: y_offset = 0
print(f"正在发送图片数据 ({len(data)} bytes)...")
print_bitmap(printer, data, width, height, x_offset, y_offset)
# 打印出纸
printer.print_out(1)
print("打印完成")
# 示例用法
if __name__ == "__main__":
try:
print_image()
except Exception as e:
print(f"错误: {{e}}")
'''
with open(output_py_path, 'w', encoding='utf-8') as f:
f.write(script_content)
print(f"成功生成 MicroPython 脚本: {output_py_path}")
if __name__ == "__main__":
generate_micropython_printer_script(
"/Users/jeremygan/Desktop/python_dev/V2_micropython/test_image.png",
"/Users/jeremygan/Desktop/python_dev/V2_micropython/printer_image_print.py"
)
# 假设有一个预处理好的 384x240 的二进制文件
# print_raw_image_file("image_data.bin", 384, 240)
pass