二进制图片
This commit is contained in:
313
tool/image_converter.py
Normal file
313
tool/image_converter.py
Normal file
@@ -0,0 +1,313 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user