Files
V2_micropython/convert_img.py
jeremygan2021 c66f80d0eb
All checks were successful
Deploy WebSocket Server / deploy (push) Successful in 4s
printer
2026-03-05 19:59:56 +08:00

258 lines
10 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
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"
)