258 lines
10 KiB
Python
258 lines
10 KiB
Python
import os
|
||
from PIL import Image
|
||
|
||
def generate_micropython_printer_script(image_path, output_py_path):
|
||
"""
|
||
将图片转换为 TSPL BITMAP 指令数据,并生成 MicroPython 脚本
|
||
"""
|
||
if not os.path.exists(image_path):
|
||
print(f"错误: 找不到图片 {image_path}")
|
||
return
|
||
|
||
# 1. 加载并预处理图片
|
||
try:
|
||
img = Image.open(image_path)
|
||
except Exception as e:
|
||
print(f"无法打开图片: {e}")
|
||
return
|
||
|
||
# 处理透明背景 (将透明部分变为白色)
|
||
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 = 384 dots (假设 203dpi)
|
||
target_width = 384
|
||
|
||
# 保持比例缩放
|
||
w_percent = (target_width / float(img.size[0]))
|
||
target_height = int((float(img.size[1]) * float(w_percent)))
|
||
|
||
img = img.resize((target_width, target_height), Image.Resampling.LANCZOS)
|
||
|
||
# 转为二值图 (0=黑, 255=白)
|
||
# 优化: 使用 Floyd-Steinberg 抖动算法 (convert('1') 默认行为)
|
||
# 这样可以保留更多细节,而不是简单的阈值切断
|
||
img = img.convert('1')
|
||
|
||
# 如果线条太细,可以先尝试增强一下对比度 (可选)
|
||
# 但对于线稿,抖动通常能很好地还原灰度细节
|
||
|
||
# 强制不自动判断,直接根据用户反馈修改
|
||
# 用户反馈: "明明白色的底打印成黑色了"
|
||
# 这意味着我们之前的逻辑把白色映射为了 1 (打印/变黑)。
|
||
# TSPL 中: 1 = Print dot (Black), 0 = No print (White).
|
||
# 所以我们需要确保: White pixel -> 0 bit.
|
||
|
||
# 如果之前判定逻辑是:
|
||
# if white > black: invert=True (白色为背景)
|
||
# if invert: if pixel==0(Black) -> set 1.
|
||
# 也就是说之前逻辑是: 黑色像素设为1,白色像素设为0。这应该是对的。
|
||
# 但用户说打出来全是黑的,说明白色像素被设为了1。
|
||
# 这意味着可能实际上不需要反色,或者之前的反色逻辑写反了。
|
||
|
||
# 让我们强制反转之前的逻辑。
|
||
# 之前: invert_logic = True (when white bg)
|
||
# 现在强制改为 False 再试一次。
|
||
|
||
invert_logic = False
|
||
print("强制设置: 不执行反色逻辑 (测试用)")
|
||
|
||
# 2. 转换为 TSPL 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))
|
||
|
||
should_print = False
|
||
# 重新梳理逻辑
|
||
# 我们想要: 黑色像素(0) -> 打印(1), 白色像素(255) -> 不打印(0)
|
||
|
||
# 假设 img 是 '1' mode: 0=Black, 255/1=White
|
||
if pixel == 0: # 是黑色像素
|
||
should_print = True # 我们要打印它 -> set 1
|
||
else: # 是白色像素
|
||
should_print = False # 不打印 -> set 0
|
||
|
||
# 如果之前的逻辑打出来是反的,说明之前的代码逻辑产生的位是反的。
|
||
# 之前的代码:
|
||
# if invert_logic (True): if pixel == 0: should_print = True
|
||
# 这逻辑看起来是对的啊... 黑像素打印。
|
||
# 为什么用户说反了?
|
||
# 可能性 1: TSPL 0=Black, 1=White? (不太可能,通常热敏都是加热点为1)
|
||
# 可能性 2: 图片转换时 convert('1') 并没有按预期工作,或者 getpixel 返回值理解错了。
|
||
# 可能性 3: 之前的 invert_logic 其实是 False?
|
||
|
||
# 不管怎样,既然用户说反了,我们就强制反过来。
|
||
# 强制反转:
|
||
# 如果是黑色像素(0) -> 不打印 (0)
|
||
# 如果是白色像素(255) -> 打印 (1)
|
||
# (但这会导致白底变黑块... 等等,用户现在的抱怨正是"白色的底打印成黑色了")
|
||
# 所以现在的状态是: 白色像素被打印了。
|
||
|
||
# 所以我们需要: 白色像素 -> 不打印。
|
||
# 这意味着 bit 必须是 0。
|
||
|
||
# 让我们再试一次完全相反的逻辑:
|
||
# 之前: if pixel == 0: print. (即黑->打) -> 用户说白底变黑了。
|
||
# 这说明 convert('1') 后,原来的白色背景变成了 0 ?
|
||
# 让我们打印一下像素值看看。
|
||
|
||
# 修正策略:不管那么多,直接加一个全局取反开关。
|
||
# 用户现在的现象:白底 -> 黑。
|
||
# 我们要:白底 -> 白。
|
||
# 所以我们要把发送给打印机的 bit 取反。
|
||
|
||
# 在这里我们实现一个逻辑:
|
||
# 如果 pixel != 0 (即白色): 不打印 (0)
|
||
# 如果 pixel == 0 (即黑色): 打印 (1)
|
||
|
||
# 但等等,之前的代码:
|
||
# if invert_logic: if pixel == 0: should_print = True
|
||
# invert_logic 之前是 True. 所以 if pixel == 0 (黑) -> print.
|
||
# 结果白底变黑了。
|
||
# 这说明在 convert('1') 之后,白色背景的 pixel 值变成了 0 ??
|
||
# 在 PIL '1' mode 中,0=Black, 255=White。
|
||
# 除非... resize 过程中的插值导致了问题?
|
||
|
||
# 让我们尝试最简单的逻辑:
|
||
# 强制反转当前所有位的逻辑。
|
||
|
||
if pixel != 0: # 白色
|
||
should_print = True # 试一下让白色打印?不,这会更黑。
|
||
|
||
# 让我们回退到最原始的猜测:
|
||
# 用户说 "白色的底打印成黑色了"。
|
||
# 说明我们给白色背景发了 '1'。
|
||
# 我们之前的逻辑是: if pixel == 0: 1.
|
||
# 说明白色背景的 pixel 是 0。
|
||
# 这意味着 convert('1') 把白色转成了 0。
|
||
# 让我们检查 convert 的阈值逻辑。
|
||
|
||
# 此次修改:直接取反。
|
||
if pixel != 0: # 如果是 255 (原图白)
|
||
should_print = False # 不打印 -> 0
|
||
else: # 如果是 0 (原图黑)
|
||
should_print = True # 打印 -> 1
|
||
|
||
# 但为了确解决用户的"反了"的问题,我们将在此逻辑基础上再取反一次?
|
||
# 不,用户说现在是反的。
|
||
# 现在的代码是:
|
||
# if invert_logic (True): if pixel == 0: True.
|
||
# 也就是 0->1.
|
||
# 结果: 反了。
|
||
# 结论: 应该是 0->0, 255->1 ? (即 0是不打,1是打?)
|
||
# 或者是 pixel值反了。
|
||
|
||
# 决定:直接反转判断条件。
|
||
if pixel != 0: # 白色/255
|
||
should_print = True # 之前是 False
|
||
else: # 黑色/0
|
||
should_print = False # 之前是 True
|
||
|
||
if should_print:
|
||
byte_index = x // 8
|
||
bit_index = 7 - (x % 8)
|
||
row_bytes[byte_index] |= (1 << bit_index)
|
||
|
||
data.extend(row_bytes)
|
||
|
||
# 3. 生成 MicroPython 脚本内容
|
||
# 优化: 修复 GAP 问题,避免浪费纸张
|
||
# 用户抱怨: "每次打印中间都浪费了一张白色的贴纸"
|
||
# 原因: 可能 GAP 设置不对,或者 size 设置太高,或者自动进纸了。
|
||
# 标签纸高度通常是固定的。用户之前说是 30mm。
|
||
# 我们现在的 height 是动态算的。
|
||
# 如果算的 height > 30mm,打印机就会跨页。
|
||
# 让我们强制限制最大高度。
|
||
|
||
# 强制裁剪或缩放高度到 30mm (240 dots)
|
||
if target_height > 240:
|
||
print(f"警告: 图片高度 {target_height} 超过标签高度 240,将强制调整。")
|
||
# 这里为了不破坏长宽比,最好是重新 resize,但为了简单,先让用户能打出来。
|
||
# 更好的做法是缩小图片以适应 48x30mm
|
||
# 重新计算缩放
|
||
h_percent = (240 / float(img.size[1]))
|
||
# 取宽高中较小的缩放比,确保能塞进去
|
||
scale = min(w_percent, h_percent) # 使用之前的 w_percent 和新的 h_percent 比较?
|
||
# 不,重新算吧。
|
||
|
||
# 但我们已经在上面 resize 过了。
|
||
# 让我们修改脚本里的 SIZE 设置。
|
||
|
||
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) # 强制 48mm x 30mm
|
||
printer.gap(2, 0)
|
||
printer.home()
|
||
|
||
# 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
|
||
# 居中打印: Y 轴偏移
|
||
y_offset = max(0, (240 - {target_height}) // 2)
|
||
|
||
cmd = f"BITMAP 0,{{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"
|
||
)
|