Files
ESP32_GDEY042T81_server/tool/image_converter.py
jeremygan2021 22aa782648 二进制图片
2025-11-26 17:29:16 +08:00

313 lines
12 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.
#!/usr/bin/env python3
"""
图片转换工具 - 将多种格式的图片转换为适用于墨水屏显示的二进制点阵数据
支持转换为黑色背景白色文本或白色背景黑色文本的格式
"""
import os
import sys
from PIL import Image, ImageOps, ImageDraw, ImageFont
import argparse
import math
def convert_image_to_epaper(input_path, output_path, width=400, height=300, invert=False, rotate=False, dither=True):
"""
将图片转换为墨水屏二进制格式
参数:
input_path: 输入图片路径
output_path: 输出文件路径
width: 目标宽度(默认400)
height: 目标高度(默认300)
invert: 是否反转颜色(默认False黑色背景白色文本)
rotate: 是否旋转90度(默认False)
dither: 是否使用抖动算法(默认True)
"""
try:
# 打开图片
img = Image.open(input_path)
# 转换为RGB模式
if img.mode != 'RGB':
img = img.convert('RGB')
# 调整大小,保持宽高比
img_ratio = img.width / img.height
target_ratio = width / height
if img_ratio > target_ratio:
# 图片较宽,以宽度为准
new_width = width
new_height = int(width / img_ratio)
else:
# 图片较高,以高度为准
new_height = height
new_width = int(height * img_ratio)
img = img.resize((new_width, new_height), Image.LANCZOS)
# 创建目标大小的黑色背景
result = Image.new('RGB', (width, height), (0, 0, 0) if not invert else (255, 255, 255))
# 计算居中位置
x_offset = (width - new_width) // 2
y_offset = (height - new_height) // 2
# 将图片粘贴到中心
result.paste(img, (x_offset, y_offset))
# 转换为灰度
result = result.convert('L')
# 转换为1位黑白图像
if dither:
result = result.convert('1', dither=Image.FLOYDSTEINBERG)
else:
# 使用阈值128进行二值化
result = result.point(lambda x: 0 if x < 128 else 255, '1')
# 如果需要反转颜色
if invert:
result = ImageOps.invert(result)
# 如果需要旋转90度
if rotate:
result = result.rotate(90, expand=True)
# 如果旋转后尺寸不匹配,需要重新调整
if result.size != (width, height):
result = result.resize((width, height), Image.LANCZOS)
# 转换为字节数组
width_bytes = (width + 7) // 8 # 每行需要的字节数
total_bytes = width_bytes * height
# 创建字节数组
byte_array = bytearray(total_bytes)
# 将像素数据转换为字节数组
for y in range(height):
for x in range(width):
# 获取像素值 (0或255)
pixel = 0 if result.getpixel((x, y)) == 0 else 1
# 计算字节位置和位位置
byte_index = y * width_bytes + x // 8
bit_position = 7 - (x % 8) # 最高位在前
# 设置位
if pixel:
byte_array[byte_index] |= (1 << bit_position)
# 生成Python代码
var_name = os.path.splitext(os.path.basename(output_path))[0]
with open(output_path, 'w', encoding='utf-8') as f:
f.write(f"# Converted from {input_path}\n")
f.write(f"# Size: {width}x{height}\n")
f.write(f"# Inverted: {invert}, Rotated: {rotate}\n")
f.write(f"{var_name} = bytearray(b'")
# 将字节数组格式化为十六进制字符串
for i, byte in enumerate(byte_array):
if i > 0 and i % 16 == 0:
f.write("'\n b'")
f.write(f"\\x{byte:02X}")
f.write("')\n")
print(f"转换完成: {input_path} -> {output_path}")
print(f"输出尺寸: {width}x{height}")
print(f"总字节数: {total_bytes}")
return True
except Exception as e:
print(f"转换失败: {e}")
return False
def create_text_image(text, output_path, width=400, height=300, font_size=24, invert=False, rotate=False):
"""
创建文本图像并转换为墨水屏二进制格式
参数:
text: 要显示的文本
output_path: 输出文件路径
width: 目标宽度(默认400)
height: 目标高度(默认300)
font_size: 字体大小(默认24)
invert: 是否反转颜色(默认False黑色背景白色文本)
rotate: 是否旋转90度(默认False)
"""
try:
# 创建图像
result = Image.new('RGB', (width, height), (0, 0, 0) if not invert else (255, 255, 255))
draw = ImageDraw.Draw(result)
# 尝试加载字体
try:
# 尝试使用系统字体
font = ImageFont.truetype("arial.ttf", font_size)
except:
try:
# 尝试使用项目中的字体
font = ImageFont.truetype("GB2312-12.fon", font_size)
except:
# 使用默认字体
font = ImageFont.load_default()
# 计算文本位置
text_width, text_height = draw.textsize(text, font=font)
x = (width - text_width) // 2
y = (height - text_height) // 2
# 绘制文本
text_color = (255, 255, 255) if not invert else (0, 0, 0)
draw.text((x, y), text, font=font, fill=text_color)
# 转换为灰度
result = result.convert('L')
# 转换为1位黑白图像
result = result.convert('1')
# 如果需要旋转90度
if rotate:
result = result.rotate(90, expand=True)
# 如果旋转后尺寸不匹配,需要重新调整
if result.size != (width, height):
result = result.resize((width, height), Image.LANCZOS)
# 转换为字节数组
width_bytes = (width + 7) // 8 # 每行需要的字节数
total_bytes = width_bytes * height
# 创建字节数组
byte_array = bytearray(total_bytes)
# 将像素数据转换为字节数组
for y in range(height):
for x in range(width):
# 获取像素值 (0或255)
pixel = 0 if result.getpixel((x, y)) == 0 else 1
# 计算字节位置和位位置
byte_index = y * width_bytes + x // 8
bit_position = 7 - (x % 8) # 最高位在前
# 设置位
if pixel:
byte_array[byte_index] |= (1 << bit_position)
# 生成Python代码
var_name = os.path.splitext(os.path.basename(output_path))[0]
with open(output_path, 'w', encoding='utf-8') as f:
f.write(f"# Text image: {text}\n")
f.write(f"# Size: {width}x{height}\n")
f.write(f"# Font size: {font_size}\n")
f.write(f"# Inverted: {invert}, Rotated: {rotate}\n")
f.write(f"{var_name} = bytearray(b'")
# 将字节数组格式化为十六进制字符串
for i, byte in enumerate(byte_array):
if i > 0 and i % 16 == 0:
f.write("'\n b'")
f.write(f"\\x{byte:02X}")
f.write("')\n")
print(f"文本图像创建完成: {output_path}")
print(f"文本: {text}")
print(f"输出尺寸: {width}x{height}")
print(f"总字节数: {total_bytes}")
return True
except Exception as e:
print(f"创建失败: {e}")
return False
def main():
parser = argparse.ArgumentParser(description='将图片转换为墨水屏二进制格式')
subparsers = parser.add_subparsers(dest='command', help='子命令')
# 图片转换命令
img_parser = subparsers.add_parser('image', help='转换图片文件')
img_parser.add_argument('input', help='输入图片路径')
img_parser.add_argument('output', help='输出文件路径')
img_parser.add_argument('--width', type=int, default=400, help='目标宽度(默认400)')
img_parser.add_argument('--height', type=int, default=300, help='目标高度(默认300)')
img_parser.add_argument('--invert', action='store_true', help='反转颜色(白色背景黑色文本)')
img_parser.add_argument('--rotate', action='store_true', help='旋转90度')
img_parser.add_argument('--no-dither', action='store_true', help='不使用抖动算法')
# 文本创建命令
text_parser = subparsers.add_parser('text', help='创建文本图像')
text_parser.add_argument('text', help='要显示的文本')
text_parser.add_argument('output', help='输出文件路径')
text_parser.add_argument('--width', type=int, default=400, help='目标宽度(默认400)')
text_parser.add_argument('--height', type=int, default=300, help='目标高度(默认300)')
text_parser.add_argument('--font-size', type=int, default=24, help='字体大小(默认24)')
text_parser.add_argument('--invert', action='store_true', help='反转颜色(白色背景黑色文本)')
text_parser.add_argument('--rotate', action='store_true', help='旋转90度')
# 批量转换命令
batch_parser = subparsers.add_parser('batch', help='批量转换图片')
batch_parser.add_argument('input_dir', help='输入目录')
batch_parser.add_argument('output_dir', help='输出目录')
batch_parser.add_argument('--width', type=int, default=400, help='目标宽度(默认400)')
batch_parser.add_argument('--height', type=int, default=300, help='目标高度(默认300)')
batch_parser.add_argument('--invert', action='store_true', help='反转颜色(白色背景黑色文本)')
batch_parser.add_argument('--rotate', action='store_true', help='旋转90度')
batch_parser.add_argument('--no-dither', action='store_true', help='不使用抖动算法')
args = parser.parse_args()
if args.command == 'image':
convert_image_to_epaper(
args.input,
args.output,
args.width,
args.height,
args.invert,
args.rotate,
not args.no_dither
)
elif args.command == 'text':
create_text_image(
args.text,
args.output,
args.width,
args.height,
args.font_size,
args.invert,
args.rotate
)
elif args.command == 'batch':
# 确保输出目录存在
os.makedirs(args.output_dir, exist_ok=True)
# 支持的图片格式
supported_formats = ('.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff')
# 遍历输入目录
for filename in os.listdir(args.input_dir):
if filename.lower().endswith(supported_formats):
input_path = os.path.join(args.input_dir, filename)
output_filename = os.path.splitext(filename)[0] + '.py'
output_path = os.path.join(args.output_dir, output_filename)
convert_image_to_epaper(
input_path,
output_path,
args.width,
args.height,
args.invert,
args.rotate,
not args.no_dither
)
else:
parser.print_help()
if __name__ == '__main__':
main()