1. 简介
在日常图像处理中,为图片添加水印是一项常见任务。有多种方法和工具可供选择,而今天我们将专注于使用Python语言结合PIL库批量添加水印。
需要注意的是,所选用的图片格式不应为JPG或JPEG,因为这两种格式的图片不支持透明度设置。
2. PIL库概述
- PIL是Python的图像处理库,支持多种文件格式。
- PIL提供强大的图像和图形处理功能,包括缩放、裁剪、叠加以及添加线条、文字等操作。
- 安装PIL库可使用以下命令:
pip install Pillow
3. PIL库中涉及的类
模块或类 | 说明 |
---|---|
image模块 | 用于图像处理 |
ImageDraw | 2D图像对象 |
ImageFont | 字体存储 |
ImageEnhance | 图像增强 |
4. 实现原理
本文的主要目标是批量为某个文件夹下的图片添加水印,实现原理如下:
- 设置水印内容;
- 使用Image对象的open()方法打开原始图片;
- 使用Image对象的new()方法创建存储水印图片的对象;
- 使用ImageDraw.Draw对象的text()方法绘制水印文字;
- 使用ImageEnhance中Brightness的enhance()方法设置水印透明度。
5. 实现过程
5.1 原始图片
设定原始图片的存储目录,例如:
F:\python_study\image\image01
5.2 导入相关模块
导入所需的PIL模块或类:
from PIL imort Image, ImageDraw, ImageFont, ImageEnhance
import os
5.3 初始化数据
通过用户手动输入相关信息,如图片存储路径、水印文字、水印位置、水印透明度等:
class WatermarkText():
def __init__(self):
super(WatermarkText, self).__init__()
self.image_path = input('图片路径:')
self.watermark_text = input('水印文字:')
self.position_flag = int(input('水印位置(1:左上角,2:左下角,3:右上角,4:右下角,5:居中):'))
self.opacity = float(input('水印透明度(0—1之间的1位小数):'))
5.4 水印字体设置
选择系统字体库中的字体:
self.font = ImageFont.truetype("cambriab.ttf", size=35)
5.5 打开原始图片并创建存储对象
打开原始图片并转换为RGBA:
image = Image.open(img).convert('RGBA')
创建绘制对象:
new_img = Image.new('RGBA', image.size, (255, 255, 255, 0))
image_draw = ImageDraw.Draw(new_img)
5.6 计算图片和水印的大小
计算图片大小:
w, h = image.size
计算文字大小:
w1 = self.font.getsize(self.watermark_text)[0]
h1 = self.font.getsize(self.watermark_text)[1]
5.7 选择性设置水印文字
通过if语句实现:
if self.position_flag == 1: # 左上角
location = (0, 0)
elif self.position_flag == 2: # 左下角
location = (0, h - h1)
elif self.position_flag == 3: # 右上角
location = (w - w1, 0)
elif self.position_flag == 4: # 右下角
location = (w - w1, h - h1)
elif self.position_flag == 5: # 居中
location = (h/2, h/2)
5.8 绘制文字并设置透明度
绘制文字:
image_draw.text(location, self.watermark_text, font=self.font, fill="blue")
设置透明度:
transparent = new_img.split()[3]
transparent = ImageEnhance.Brightness(transparent).enhance(self.opacity)
new_img.putalpha(transparent)
Image.alpha_composite(image, new_img).save(img)
5.9 遍历获取图片文件并调用绘制方法
watermark_text = WatermarkText()
try:
file_list = os.listdir(watermark_text.image_path)
for i in range(0, len(file_list)):
filepath = os.path.join(watermark_text.image_path, file_list[i])
if os.path.isfile(filepath):
filetype = os.path.splitext(filepath)[1]
if filetype == '.png':
watermark_text.add_text_watermark(filepath)
else:
print("图片格式有误,请使用png格式图片")
print('批量添加水印完成')
except:
print('输入的文件路径有误,请检查~~')
6. 完整源码
from PIL import
Image, ImageDraw, ImageFont, ImageEnhance
import os
class WatermarkText():
def __init__(self):
super(WatermarkText, self).__init__()
self.image_path = input('图片路径:')
self.watermark_text = input('水印文字:')
self.position_flag = int(input('水印位置(1:左上角,2:左下角,3:右上角,4:右下角,5:居中):'))
self.opacity = float(input('水印透明度(0—1之间的1位小数):'))
# 设置字体
self.font = ImageFont.truetype("cambriab.ttf", size=35)
# 文字水印
def add_text_watermark(self, img):
global location
image = Image.open(img).convert('RGBA')
new_img = Image.new('RGBA', image.size, (255, 255, 255, 0))
image_draw = ImageDraw.Draw(new_img)
w, h = image.size # 图片大小
w1 = self.font.getsize(self.watermark_text)[0] # 字体宽度
h1 = self.font.getsize(self.watermark_text)[1] # 字体高度
# 设置水印文字位置
if self.position_flag == 1: # 左上角
location = (0, 0)
elif self.position_flag == 2: # 左下角
location = (0, h - h1)
elif self.position_flag == 3: # 右上角
location = (w - w1, 0)
elif self.position_flag == 4: # 右下角
location = (w - w1, h - h1)
elif self.position_flag == 5: # 居中
location = (h/2, h/2)
# 绘制文字
image_draw.text(location, self.watermark_text, font=self.font, fill="blue")
# 设置透明度
transparent = new_img.split()[3]
transparent = ImageEnhance.Brightness(transparent).enhance(self.opacity)
new_img.putalpha(transparent)
Image.alpha_composite(image, new_img).save(img)
if __name__ == "__main__":
watermark_text = WatermarkText()
try:
file_list = os.listdir(watermark_text.image_path)
for i in range(0, len(file_list)):
filepath = os.path.join(watermark_text.image_path, file_list[i])
if os.path.isfile(filepath):
filetype = os.path.splitext(filepath)[1]
if filetype == '.png':
watermark_text.add_text_watermark(filepath)
else:
print("图片格式有误,请使用png格式图片")
print('批量添加水印完成')
except:
print('输入的文件路径有误,请检查~~')
7. 效果展示
运行过程:
D:\Python37\python.exe F:/python_study/python_project/watermark_text.py
图片路径:F:\python_study\image\image01
水印文字:
水印位置(1:左上角,2:左下角,3:右上角,4:右下角,5:居中):1
水印透明度(0—1之间的1位小数):0.5
F:/python_study/python_project/watermark_text.py:32: DeprecationWarning: getsize is deprecated and will be removed in Pillow 10 (2023-07-01). Use getbbox or getlength instead.
w1 = self.font.getsize(self.watermark_text)[0] # 获取字体宽度
F:/python_study/python_project/watermark_text.py:33: DeprecationWarning: getsize is deprecated and will be removed in Pillow 10 (2023-07-01). Use getbbox or getlength instead.
h1 = self.font.getsize(self.watermark_text)[1] # 获取字体高度
批量添加水印完成
8. 改进与建议
8.1 参数输入方式优化
在初始化数据的部分,我们可以考虑通过命令行参数或配置文件的方式输入相关信息,以提高用户体验。例如使用argparse
库来解析命令行参数。
import argparse
class WatermarkText():
def __init__(self):
parser = argparse.ArgumentParser(description='Add watermark to images.')
parser.add_argument('--image_path', type=str, help='Path to the image directory.')
parser.add_argument('--watermark_text', type=str, help='Text for watermark.')
parser.add_argument('--position_flag', type=int, help='Position flag for watermark (1: top-left, 2: bottom-left, 3: top-right, 4: bottom-right, 5: center).')
parser.add_argument('--opacity', type=float, help='Opacity for watermark (0-1 with 1 decimal place).')
args = parser.parse_args()
self.image_path = args.image_path or input('Image path: ')
self.watermark_text = args.watermark_text or input('Watermark text: ')
self.position_flag = args.position_flag or int(input('Watermark position (1: top-left, 2: bottom-left, 3: top-right, 4: bottom-right, 5: center): '))
self.opacity = args.opacity or float(input('Watermark opacity (0-1 with 1 decimal place): '))
8.2 异常处理改进
在处理异常的部分,我们可以更具体地捕获异常类型,并提供更友好的提示信息。
try:
# existing code...
except FileNotFoundError:
print('Error: The specified image directory does not exist.')
except PermissionError:
print('Error: Permission denied to access the specified image directory.')
except Exception as e:
print(f'An unexpected error occurred: {e}')
8.3 代码结构优化
可以考虑将一些功能模块化,提高代码的可读性和维护性。例如,将文字水印的添加功能独立成一个方法。
class WatermarkText():
# existing code...
def add_text_watermark(self, img):
# existing code...
8.4 日志记录
考虑在程序中添加日志记录,记录关键步骤和出错信息,以便于排查问题。
import logging
logging.basicConfig(level=logging.INFO)
class WatermarkText():
# existing code...
def add_text_watermark(self, img):
try:
# existing code...
logging.info(f'Successfully added watermark to {img}')
except Exception as e:
logging.error(f'Error adding watermark to {img}: {e}')
8.5 扩展功能
在程序中可以考虑添加更多功能,比如支持不同的水印颜色、字体大小等选项,以使程序更加灵活。
这些改进和建议将有助于提高程序的稳定性、易用性和可维护性。
当然,我们将继续改进和完善你的代码。在这一部分,我们会考虑一些进一步的优化和改进。
9. 优化图片格式检查
在处理图片文件时,可以优化检查图片格式的方式。使用os.path.splitext
得到的文件扩展名可能包含大写字母,为了确保匹配,可以将文件扩展名转换为小写。
if filetype.lower() == '.png':
watermark_text.add_text_watermark(filepath)
else:
print("Error: Image format is not supported. Please use PNG format.")
10. 增加用户交互性
可以考虑在程序中增加更多用户交互性,比如在成功添加水印后询问用户是否继续添加水印。
while True:
try:
# existing code...
print('Watermark added successfully.')
another = input('Do you want to add watermark to another image? (yes/no): ').lower()
if another != 'yes':
break
except Exception as e:
logging.error(f'Error: {e}')
这样,用户可以选择是否继续添加水印,提高程序的交互性。
11. 多线程处理
如果你需要处理大量图片,可以考虑使用多线程来加速处理过程。这可以通过concurrent.futures
模块实现。
from concurrent.futures import ThreadPoolExecutor
# existing code...
if __name__ == "__main__":
watermark_text = WatermarkText()
try:
file_list = os.listdir(watermark_text.image_path)
with ThreadPoolExecutor() as executor:
executor.map(watermark_text.add_text_watermark, [os.path.join(watermark_text.image_path, file) for file in file_list])
print('Batch watermarking completed.')
except Exception as e:
logging.error(f'Error: {e}')
这将允许同时处理多个图片,提高处理速度。
12. 其他优化建议
- 考虑支持更多图片格式,而不仅限于PNG。你可以使用Pillow库中的
Image.register_open()
方法注册其他格式的图片打开器。 - 如果水印文字较长,可以考虑自动调整文字大小,以适应图片。