134 lines
4.4 KiB
Python
134 lines
4.4 KiB
Python
import os
|
||
import uuid
|
||
from PIL import Image, ImageOps
|
||
from typing import Tuple, Optional
|
||
from config import settings
|
||
|
||
class ImageProcessor:
|
||
def __init__(self):
|
||
self.width = settings.ink_width
|
||
self.height = settings.ink_height
|
||
self.upload_dir = settings.upload_dir
|
||
self.processed_dir = settings.processed_dir
|
||
|
||
# 确保目录存在
|
||
os.makedirs(self.upload_dir, exist_ok=True)
|
||
os.makedirs(self.processed_dir, exist_ok=True)
|
||
|
||
def process_image(self, image_path: str, grayscale: bool = True, dither: bool = True) -> str:
|
||
"""
|
||
处理上传的图片,使其适配墨水屏显示
|
||
|
||
Args:
|
||
image_path: 原始图片路径
|
||
grayscale: 是否转换为灰度图
|
||
dither: 是否使用抖动算法
|
||
|
||
Returns:
|
||
处理后图片的相对路径
|
||
"""
|
||
try:
|
||
# 打开原始图片
|
||
img = Image.open(image_path)
|
||
|
||
# 转换为RGB模式(处理RGBA等格式)
|
||
if img.mode != 'RGB':
|
||
img = img.convert('RGB')
|
||
|
||
# 自动旋转(基于EXIF信息)
|
||
img = ImageOps.exif_transpose(img)
|
||
|
||
# 计算缩放比例,保持宽高比
|
||
img_ratio = img.width / img.height
|
||
target_ratio = self.width / self.height
|
||
|
||
if img_ratio > target_ratio:
|
||
# 图片较宽,以高度为准
|
||
new_height = self.height
|
||
new_width = int(self.height * img_ratio)
|
||
else:
|
||
# 图片较高,以宽度为准
|
||
new_width = self.width
|
||
new_height = int(self.width / img_ratio)
|
||
|
||
# 缩放图片
|
||
img = img.resize((new_width, new_height), Image.LANCZOS)
|
||
|
||
# 居中裁剪到目标尺寸
|
||
left = (new_width - self.width) // 2
|
||
top = (new_height - self.height) // 2
|
||
right = left + self.width
|
||
bottom = top + self.height
|
||
img = img.crop((left, top, right, bottom))
|
||
|
||
# 转换为灰度图
|
||
if grayscale:
|
||
img = img.convert('L')
|
||
|
||
# 生成处理后的文件名
|
||
filename = f"{uuid.uuid4()}.bmp"
|
||
processed_path = os.path.join(self.processed_dir, filename)
|
||
|
||
# 保存为BMP格式(墨水屏易解析)
|
||
if grayscale:
|
||
# 黑白图片,使用抖动算法
|
||
if dither:
|
||
img.convert('1').save(processed_path)
|
||
else:
|
||
img.convert('1', dither=Image.NONE).save(processed_path)
|
||
else:
|
||
# 彩色图片,转换为RGB模式
|
||
img.save(processed_path)
|
||
|
||
# 返回相对路径
|
||
return os.path.relpath(processed_path)
|
||
|
||
except Exception as e:
|
||
raise Exception(f"图片处理失败: {str(e)}")
|
||
|
||
def save_upload(self, file_data: bytes, filename: str) -> str:
|
||
"""
|
||
保存上传的原始文件
|
||
|
||
Args:
|
||
file_data: 文件二进制数据
|
||
filename: 原始文件名
|
||
|
||
Returns:
|
||
保存后的文件路径
|
||
"""
|
||
# 生成唯一文件名
|
||
file_ext = os.path.splitext(filename)[1]
|
||
unique_filename = f"{uuid.uuid4()}{file_ext}"
|
||
upload_path = os.path.join(self.upload_dir, unique_filename)
|
||
|
||
# 保存文件
|
||
with open(upload_path, "wb") as f:
|
||
f.write(file_data)
|
||
|
||
return upload_path
|
||
|
||
def get_image_info(self, image_path: str) -> dict:
|
||
"""
|
||
获取图片信息
|
||
|
||
Args:
|
||
image_path: 图片路径
|
||
|
||
Returns:
|
||
图片信息字典
|
||
"""
|
||
try:
|
||
with Image.open(image_path) as img:
|
||
return {
|
||
"width": img.width,
|
||
"height": img.height,
|
||
"mode": img.mode,
|
||
"format": img.format,
|
||
"size_bytes": os.path.getsize(image_path)
|
||
}
|
||
except Exception as e:
|
||
raise Exception(f"获取图片信息失败: {str(e)}")
|
||
|
||
# 全局图片处理器实例
|
||
image_processor = ImageProcessor() |