Files
V2_micropython/convert_img.py
jeremygan2021 ea0594bf88
All checks were successful
Deploy WebSocket Server / deploy (push) Successful in 20s
printer
2026-03-05 20:25:32 +08:00

241 lines
8.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
# 遍历像素生成数据
# 修正逻辑:用户反馈打印出来是负片(黑底白字),说明原有的判断反了。
# 我们现在采用完全相反的逻辑。
# 之前的逻辑: if pixel == 0: should_print = True (导致背景被打印)
# 现在的逻辑: if pixel != 0: should_print = True (即 0不打印255打印)
# 这样背景(通常是0或255看PIL怎么处理)会被反转。
# 如果原图是白底(255)黑字(0)PIL convert('1') 后背景通常是 255字是 0。
# 如果用 if pixel != 0: print -> 背景打(黑),字不打(白)。这还是负片。
# 如果之前的逻辑 (pixel==0 -> print) 导致了负片,说明背景走了 print 分支,说明背景是 0。
# 这意味着 PIL 读进来的图背景是 0。
# 所以如果要背景不打我们应该if pixel == 0: no_print.
# 所以 if pixel != 0: print.
# 让我们再加一道保险ImageOps.invert
# 但 ImageOps.invert 不支持 '1' 模式,要先转 'L' 再 invert 再转 '1' 比较好。
# 不过既然我们是在 pixel 级别操作,我们可以直接在判断里改。
for y in range(target_height):
row_bytes = bytearray(width_bytes)
for x in range(target_width):
pixel = img.getpixel((x, y))
should_print = False
# 尝试修复:如果 pixel != 0 (即白色/非黑),则打印?
# 不,根据推导,背景如果是 0那我们要背景不打印所以 if pixel != 0: print.
# 让我们试试这个逻辑。
if pixel != 0:
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 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("=== 开始打印图片 ===")
# 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)
chunk_size = 128
for i in range(0, len(img_data), chunk_size):
uart.write(img_data[i : i + chunk_size])
time.sleep(0.005)
uart.write(b'\\r\\n')
# 5. 打印
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"
)