logo

Python PIL批量添加文字水印全攻略:从基础到进阶

作者:搬砖的石头2025.10.10 17:03浏览量:1

简介:本文详细介绍如何使用Python PIL库批量为图片添加文字水印,涵盖基础操作、进阶技巧及性能优化,适合开发者及企业用户快速实现自动化水印处理。

PIL如何批量给图片添加文字水印?

一、PIL库基础与水印原理

Python Imaging Library(PIL)是Python生态中最经典的图像处理库之一,其分支Pillow提供了更完善的维护和扩展功能。文字水印的本质是通过在图片指定位置绘制半透明文字,实现版权标识或品牌宣传的目的。

1.1 PIL核心组件

  • Image:基础图像对象,支持加载、保存和转换图像格式
  • ImageDraw:提供2D绘图功能,包括文字、几何图形绘制
  • ImageFont:控制文字字体、大小和样式

1.2 水印关键参数

  • 文字内容:可动态配置(如添加时间戳)
  • 位置坐标:绝对定位或相对定位
  • 透明度:通过RGBA颜色通道的Alpha值控制
  • 字体样式:影响水印可读性和美观度

二、基础批量处理实现

2.1 单张图片水印添加

  1. from PIL import Image, ImageDraw, ImageFont
  2. def add_text_watermark(input_path, output_path, text, position, font_size=30, opacity=0.5):
  3. # 加载图片
  4. img = Image.open(input_path).convert("RGBA")
  5. # 创建透明图层用于水印
  6. txt = Image.new("RGBA", img.size, (255, 255, 255, 0))
  7. draw = ImageDraw.Draw(txt)
  8. # 加载字体(需系统存在或指定路径)
  9. try:
  10. font = ImageFont.truetype("arial.ttf", font_size)
  11. except:
  12. font = ImageFont.load_default()
  13. # 计算文字尺寸
  14. text_width, text_height = draw.textsize(text, font=font)
  15. # 计算居中位置(示例为右下角定位)
  16. x, y = position if position else (img.width - text_width - 10, img.height - text_height - 10)
  17. # 绘制半透明文字
  18. draw.text((x, y), text, font=font, fill=(255, 255, 255, int(255 * opacity)))
  19. # 合并图层
  20. out = Image.alpha_composite(img, txt)
  21. out.convert("RGB").save(output_path)
  22. # 使用示例
  23. add_text_watermark("input.jpg", "output.jpg", "Sample Watermark", (10, 10))

2.2 批量处理框架

  1. import os
  2. from glob import glob
  3. def batch_watermark(input_dir, output_dir, text, **kwargs):
  4. # 创建输出目录
  5. os.makedirs(output_dir, exist_ok=True)
  6. # 获取所有图片文件
  7. image_paths = glob(f"{input_dir}/*.jpg") + glob(f"{input_dir}/*.png")
  8. for input_path in image_paths:
  9. # 构造输出路径
  10. filename = os.path.basename(input_path)
  11. output_path = os.path.join(output_dir, filename)
  12. # 添加水印
  13. add_text_watermark(input_path, output_path, text, **kwargs)
  14. print(f"Processed: {filename}")
  15. # 使用示例
  16. batch_watermark("input_images", "output_images", "© 2023 MyCompany")

三、进阶优化技巧

3.1 动态水印内容

  1. import datetime
  2. def get_dynamic_text(base_text):
  3. now = datetime.datetime.now()
  4. return f"{base_text} | {now.strftime('%Y-%m-%d')}"
  5. # 修改调用方式
  6. dynamic_text = get_dynamic_text("Batch Process")
  7. add_text_watermark("input.jpg", "output.jpg", dynamic_text)

3.2 多位置水印

  1. def add_multi_position_watermark(img_path, output_path, text, positions, **kwargs):
  2. img = Image.open(img_path).convert("RGBA")
  3. txt = Image.new("RGBA", img.size, (255, 255, 255, 0))
  4. draw = ImageDraw.Draw(txt)
  5. try:
  6. font = ImageFont.truetype("arial.ttf", kwargs.get("font_size", 30))
  7. except:
  8. font = ImageFont.load_default()
  9. for pos in positions:
  10. draw.text(pos, text, font=font, fill=(255, 255, 255, int(255 * kwargs.get("opacity", 0.5))))
  11. out = Image.alpha_composite(img, txt)
  12. out.convert("RGB").save(output_path)
  13. # 使用示例(四角水印)
  14. positions = [(10,10), (10,390), (390,10), (390,390)] # 假设400x400图片
  15. add_multi_position_watermark("input.jpg", "output.jpg", "WATERMARK", positions)

3.3 性能优化策略

  1. 字体缓存:重复使用字体对象避免频繁加载
    ```python
    font_cache = {}

def get_cached_font(font_path, size):
key = (font_path, size)
if key not in font_cache:
try:
font_cache[key] = ImageFont.truetype(font_path, size)
except:
font_cache[key] = ImageFont.load_default()
return font_cache[key]

  1. 2. **多线程处理**:使用`concurrent.futures`加速批量处理
  2. ```python
  3. from concurrent.futures import ThreadPoolExecutor
  4. def parallel_batch(input_dir, output_dir, text, workers=4):
  5. os.makedirs(output_dir, exist_ok=True)
  6. image_paths = glob(f"{input_dir}/*.jpg")
  7. def process_single(input_path):
  8. output_path = os.path.join(output_dir, os.path.basename(input_path))
  9. add_text_watermark(input_path, output_path, text)
  10. return output_path
  11. with ThreadPoolExecutor(max_workers=workers) as executor:
  12. results = list(executor.map(process_single, image_paths))
  13. return results

四、常见问题解决方案

4.1 中文乱码问题

  1. # 使用支持中文的字体文件
  2. def add_chinese_watermark(input_path, output_path, text):
  3. try:
  4. font = ImageFont.truetype("simhei.ttf", 40) # 黑体
  5. except:
  6. font = ImageFont.load_default()
  7. img = Image.open(input_path).convert("RGBA")
  8. txt = Image.new("RGBA", img.size, (255,255,255,0))
  9. draw = ImageDraw.Draw(txt)
  10. draw.text((10,10), text, font=font, fill=(255,255,255,128))
  11. out = Image.alpha_composite(img, txt)
  12. out.convert("RGB").save(output_path)

4.2 水印位置计算

  1. def calculate_position(img_width, img_height, text_width, text_height, align="center"):
  2. if align == "center":
  3. return (img_width//2 - text_width//2, img_height//2 - text_height//2)
  4. elif align == "top_right":
  5. return (img_width - text_width - 10, 10)
  6. # 可扩展其他对齐方式

五、企业级应用建议

  1. 配置化管理:将水印参数(文字、位置、透明度)存储在JSON/YAML配置文件中
  2. 日志系统:记录处理过程和异常信息
  3. 异常处理:添加图片加载失败、字体缺失等异常捕获
  4. 格式支持:扩展支持WEBP、TIFF等更多格式
  5. 质量保证:添加MD5校验确保处理前后图片完整性

六、完整企业级实现示例

  1. import json
  2. import logging
  3. from pathlib import Path
  4. from PIL import Image, ImageDraw, ImageFont
  5. from concurrent.futures import ThreadPoolExecutor
  6. # 配置日志
  7. logging.basicConfig(
  8. level=logging.INFO,
  9. format='%(asctime)s - %(levelname)s - %(message)s',
  10. handlers=[logging.FileHandler('watermark.log'), logging.StreamHandler()]
  11. )
  12. class WatermarkProcessor:
  13. def __init__(self, config_path):
  14. with open(config_path) as f:
  15. self.config = json.load(f)
  16. self.font_cache = {}
  17. def get_font(self, size):
  18. font_path = self.config.get("font_path", "arial.ttf")
  19. key = (font_path, size)
  20. if key not in self.font_cache:
  21. try:
  22. self.font_cache[key] = ImageFont.truetype(font_path, size)
  23. except:
  24. self.font_cache[key] = ImageFont.load_default()
  25. logging.warning(f"Fallback to default font for size {size}")
  26. return self.font_cache[key]
  27. def process_image(self, input_path, output_path):
  28. try:
  29. img = Image.open(input_path).convert("RGBA")
  30. txt = Image.new("RGBA", img.size, (255,255,255,0))
  31. draw = ImageDraw.Draw(txt)
  32. font = self.get_font(self.config["font_size"])
  33. text = self.config["text"]
  34. text_width, text_height = draw.textsize(text, font=font)
  35. # 位置计算
  36. x, y = self.config["position"]
  37. if x == "center":
  38. x = (img.width - text_width) // 2
  39. if y == "center":
  40. y = (img.height - text_height) // 2
  41. draw.text((x, y), text, font=font,
  42. fill=(255,255,255, int(255 * self.config["opacity"])))
  43. out = Image.alpha_composite(img, txt)
  44. out.convert("RGB").save(output_path, quality=95)
  45. logging.info(f"Successfully processed: {input_path}")
  46. return True
  47. except Exception as e:
  48. logging.error(f"Error processing {input_path}: {str(e)}")
  49. return False
  50. def batch_process(self, input_dir, output_dir):
  51. input_dir = Path(input_dir)
  52. output_dir = Path(output_dir)
  53. output_dir.mkdir(exist_ok=True)
  54. image_files = list(input_dir.glob("*." + self.config.get("extension", "jpg")))
  55. image_files += list(input_dir.glob("*." + self.config.get("extension2", "png")))
  56. with ThreadPoolExecutor(max_workers=self.config.get("workers", 4)) as executor:
  57. futures = []
  58. for img_file in image_files:
  59. output_path = output_dir / img_file.name
  60. futures.append(executor.submit(self.process_image, str(img_file), str(output_path)))
  61. success_count = sum(f.result() for f in futures)
  62. logging.info(f"Batch processing completed. Success: {success_count}/{len(image_files)}")
  63. # 配置文件示例 (config.json)
  64. """
  65. {
  66. "text": "© 2023 Company",
  67. "font_size": 36,
  68. "opacity": 0.7,
  69. "position": ["center", "bottom"], # 或具体坐标如 [100, 100]
  70. "workers": 8,
  71. "extension": "jpg",
  72. "extension2": "png"
  73. }
  74. """
  75. # 使用示例
  76. if __name__ == "__main__":
  77. processor = WatermarkProcessor("config.json")
  78. processor.batch_process("input_images", "output_images")

七、总结与最佳实践

  1. 字体管理:始终处理字体加载异常,提供备用方案
  2. 内存优化:批量处理大图片时注意内存使用,可分批处理
  3. 格式一致性:确保输出格式与输入格式兼容,避免质量损失
  4. 测试验证:先在小批量图片上测试效果,再大规模应用
  5. 自动化集成:可将脚本集成到CI/CD流程中,实现自动化水印添加

通过以上方法,开发者可以高效、稳定地实现图片批量水印添加功能,满足从个人到企业级的不同需求。实际开发中,建议根据具体场景调整参数,并添加完善的错误处理和日志记录机制。

相关文章推荐

发表评论

活动