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" )