Files
imgwatermark/watermark.py
T

185 lines
6.3 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.
import os
from PIL import Image, ImageDraw, ImageFont
from config import SUPPORTED_INPUT_EXTENSIONS, SUPPORTED_OUTPUT_EXTENSIONS, STATS_FILE
def load_font(size=16):
"""根据系统加载合适的字体文件。"""
candidates = [
r"C:\Windows\Fonts\msyh.ttc",
r"C:\Windows\Fonts\simhei.ttf",
r"C:\Windows\Fonts\simsun.ttc",
r"C:\Windows\Fonts\arial.ttf",
]
for path in candidates:
if os.path.exists(path):
return ImageFont.truetype(path, size)
return ImageFont.load_default()
def _normalize_path(path):
"""规范化路径,统一使用正斜杠,解析相对路径。"""
# 统一将反斜杠转为正斜杠
path = path.replace("\\", "/")
# 规范化相对路径
path = os.path.normpath(path)
return path
def _strip_quotes(s):
"""去掉首尾的引号。"""
s = s.strip()
if len(s) >= 2 and s[0] == s[-1] and s[0] in ('"', "'"):
return s[1:-1]
return s
def _parse_output_spec(output_spec, input_path):
"""
解析输出规格,返回 (output_path, output_ext)。
支持格式:
- ".png" / "png" -> 使用原目录,转换格式
- "./output.png" -> 输出到当前目录
- "../output.png" -> 输出到上级目录
- "/absolute/path.png" -> 绝对路径
- "output.png" -> 输出到原目录(重命名)
"""
if not output_spec:
# 无格式指定,保持原格式和原目录
input_dir = os.path.dirname(os.path.abspath(input_path))
base_name = os.path.basename(input_path)
name_no_ext = os.path.splitext(base_name)[0]
_, ext = os.path.splitext(input_path)
return os.path.join(input_dir, f"{name_no_ext}_watermarked{ext.lower()}"), ext.lower()
output_spec = _strip_quotes(output_spec.strip())
output_spec = _normalize_path(output_spec)
# 分离目录和文件名
dir_part = os.path.dirname(output_spec)
file_part = os.path.basename(output_spec)
# 分离文件名和扩展名
name_no_ext, ext = os.path.splitext(file_part)
# 判断是否是纯格式(如 .jpg、jpg)vs 有文件名(如 result.jpg)
is_just_format = False
if not name_no_ext:
# 没有文件名部分,只有扩展名
is_just_format = True
elif name_no_ext == file_part and len(name_no_ext) <= 5 and name_no_ext.isalpha():
# 没有点,整个是字母文件名(如 jpg)
is_just_format = True
elif file_part.startswith('.') and len(file_part) <= 5 and file_part[1:].isalpha():
# 以点开头且其余为字母(如 .jpg)
is_just_format = True
# 如果没有扩展名,使用原图片的扩展名
if not ext:
_, orig_ext = os.path.splitext(input_path)
ext = orig_ext.lower() if orig_ext else ".png"
# 如果只有文件名没有扩展名,给它加上正确的扩展名
if is_just_format:
ext = '.' + name_no_ext.lstrip('.') if name_no_ext else ext
name_no_ext = ''
else:
ext = ext.lower()
if ext not in SUPPORTED_OUTPUT_EXTENSIONS:
raise ValueError(f"不支持的输出格式: {ext},支持的格式: {', '.join(sorted(SUPPORTED_OUTPUT_EXTENSIONS))}")
if is_just_format:
# 纯格式转换,使用原目录和原文件名加后缀
input_dir = os.path.dirname(os.path.abspath(input_path))
base_name = os.path.basename(input_path)
name_no_ext = os.path.splitext(base_name)[0]
output_path = os.path.join(input_dir, f"{name_no_ext}_watermarked{ext}")
elif dir_part:
# 有路径,使用指定路径
output_path = os.path.join(dir_part, file_part)
else:
# 只有文件名,没有路径,使用原目录
input_dir = os.path.dirname(os.path.abspath(input_path))
output_path = os.path.join(input_dir, file_part)
return _normalize_path(output_path), ext
def watermark_image_tiled(input_path, text, output_path=None, output_ext=None):
"""
给图片添加平铺水印(小字+全屏,类似身份证水印效果)。
Args:
input_path: 输入图片路径
text: 水印文字内容
output_path: 输出路径,默认在同目录生成 _watermarked 后缀文件
output_ext: 指定输出格式(如 ".png"),None 则保持原格式
Returns:
最终保存的绝对路径
"""
img = Image.open(input_path).convert("RGBA")
# 创建平铺水印图层
overlay = Image.new("RGBA", img.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(overlay)
# 小字字体
font = load_font(16)
# 计算水印间距(根据图片尺寸自适应)
spacing_x = max(80, img.width // 4)
spacing_y = max(60, img.height // 3)
# 绘制平铺水印
for y in range(-20, img.height + 20, spacing_y):
for x in range(-20, img.width + 20, spacing_x):
# 半透明文字(白色,透明度 60/255)
draw.text((x, y), text, fill=(255, 255, 255, 60), font=font)
# 合成水印
result = Image.alpha_composite(img, overlay)
result = result.convert("RGB")
# 确定输出格式和路径
if output_ext is None:
_, output_ext = os.path.splitext(input_path)
output_ext = output_ext.lower()
else:
output_ext = output_ext.lower()
if output_path is None:
input_dir = os.path.dirname(os.path.abspath(input_path))
base_name = os.path.basename(input_path)
name_no_ext = os.path.splitext(base_name)[0]
output_path = os.path.join(input_dir, f"{name_no_ext}_watermarked{output_ext}")
elif not output_path.endswith(output_ext):
output_path = output_path + output_ext
# 保存
save_kwargs = {}
if output_ext in (".jpg", ".jpeg"):
save_kwargs["quality"] = 95
save_kwargs["optimize"] = True
elif output_ext == ".png":
save_kwargs["optimize"] = True
result.save(output_path, **save_kwargs)
return os.path.abspath(output_path)
def increment_count():
"""已处理图片计数 +1,返回新总数。"""
count = 0
if os.path.exists(STATS_FILE):
with open(STATS_FILE, "r", encoding="utf-8") as f:
try:
count = int(f.read().strip())
except ValueError:
count = 0
count += 1
with open(STATS_FILE, "w", encoding="utf-8") as f:
f.write(str(count))
return count