printer
All checks were successful
Deploy WebSocket Server / deploy (push) Successful in 20s

This commit is contained in:
jeremygan2021
2026-03-05 20:25:32 +08:00
parent d1c2ea91ad
commit ea0594bf88
2 changed files with 243 additions and 2 deletions

240
convert_img.py Normal file
View File

@@ -0,0 +1,240 @@
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"
)

View File

@@ -535,7 +535,7 @@ def optimize_prompt(asr_text, progress_callback=None):
关键要求: 关键要求:
1. 风格必须是:简单的黑白线稿、简笔画、图标风格 (Line art, Sketch, Icon style)。 1. 风格必须是:简单的黑白线稿、简笔画、图标风格 (Line art, Sketch, Icon style)。
2. 画面必须清晰、线条粗壮,适合低分辨率热敏打印机打印。 2. 画面必须清晰、线条粗壮,适合低分辨率热敏打印机打印。
3. 绝对不要有复杂的阴影、渐变、彩色描述。 3. 绝对不要有复杂的阴影、渐变、黑白线条描述。
4. 背景必须是纯白 (White background)。 4. 背景必须是纯白 (White background)。
5. 提示词内容请使用英文描述,因为绘图模型对英文理解更好,但在描述中强调 "black and white line art", "simple lines", "vector style" 5. 提示词内容请使用英文描述,因为绘图模型对英文理解更好,但在描述中强调 "black and white line art", "simple lines", "vector style"
6. 尺寸比例遵循宽48mm:高30mm (约 1.6:1)。 6. 尺寸比例遵循宽48mm:高30mm (约 1.6:1)。
@@ -615,7 +615,8 @@ def generate_image(prompt, progress_callback=None, retry_count=0, max_retries=2)
response = ImageSynthesis.call( response = ImageSynthesis.call(
model='wanx2.0-t2i-turbo', model='wanx2.0-t2i-turbo',
prompt=prompt prompt=prompt,
size='1280*720'
) )
if response.status_code == 200: if response.status_code == 200: