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() # 遍历像素生成数据 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 # 修正逻辑:用户反馈打印出来是负片(黑底白字),说明原有的判断反了。 # 我们现在采用完全相反的逻辑。 # 之前的逻辑: if pixel != 0: should_print = True (导致背景被打印) # 现在的逻辑: if pixel == 0: should_print = True (即 0(黑)打印,255(白)不打印) 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" )