import os from PIL import Image 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 # 使用 thumbnail 进行等比缩放,确保不超过最大尺寸 img.thumbnail((MAX_WIDTH, MAX_HEIGHT), Image.Resampling.LANCZOS) target_width, target_height = img.size print(f"图片缩放后尺寸: {target_width}x{target_height}") # 转为二值图 # 1. 先转灰度 img = img.convert('L') # 2. 二值化 (使用默认的抖动算法) img = img.convert('1') # 构造 BITMAP 数据 width_bytes = (target_width + 7) // 8 data = bytearray() # 遍历像素生成数据 # TSPL BITMAP 数据: 1=Black(Print), 0=White(No Print) # PIL '1' mode: 0=Black, 255=White for y in range(target_height): row_bytes = bytearray(width_bytes) for x in range(target_width): pixel = img.getpixel((x, y)) # 逻辑修正: # 我们希望 黑色像素(0) -> 打印(1) # 白色像素(255) -> 不打印(0) should_print = False if pixel == 0: # Black 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) # 计算居中偏移 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" )