简介:本文详细介绍如何使用Python的PIL库批量为图片添加文字水印,涵盖从基础操作到高级技巧的完整流程,并提供可复用的代码示例。
Python Imaging Library(PIL)作为图像处理领域的核心工具,其Pillow分支提供了完善的图像操作接口。在批量添加文字水印的场景中,PIL通过ImageDraw模块实现文本渲染,结合ImageFont控制字体样式,形成可复用的水印添加机制。
from PIL import Image, ImageDraw, ImageFontimport osdef add_text_watermark(input_dir, output_dir, text, font_path='arial.ttf',font_size=30, color=(255,255,255,128), position=(10,10)):"""批量添加文字水印:param input_dir: 输入目录:param output_dir: 输出目录:param text: 水印文本:param font_path: 字体路径:param font_size: 字体大小:param color: RGBA颜色值(A为透明度):param position: 文本位置(左上角坐标)"""if not os.path.exists(output_dir):os.makedirs(output_dir)font = ImageFont.truetype(font_path, font_size)for filename in os.listdir(input_dir):if filename.lower().endswith(('.png', '.jpg', '.jpeg')):try:img_path = os.path.join(input_dir, filename)img = Image.open(img_path).convert('RGBA')# 创建透明背景图层用于水印watermark = Image.new('RGBA', img.size, (0,0,0,0))draw = ImageDraw.Draw(watermark)draw.text(position, text, font=font, fill=color)# 合成水印层result = Image.alpha_composite(img, watermark)result.save(os.path.join(output_dir, filename), 'PNG')except Exception as e:print(f"处理 {filename} 失败: {str(e)}")
针对不同尺寸图片,可采用相对坐标计算:
def calculate_position(img_width, img_height, text_width, text_height,h_align='left', v_align='top', padding=10):"""动态计算文本位置:param h_align: 水平对齐方式(left/center/right):param v_align: 垂直对齐方式(top/middle/bottom):param padding: 边距"""positions = {'left': padding,'center': (img_width - text_width) // 2,'right': img_width - text_width - padding}v_positions = {'top': padding,'middle': (img_height - text_height) // 2,'bottom': img_height - text_height - padding}x = positions.get(h_align, padding)y = v_positions.get(v_align, padding)return x, y
def add_multiline_watermark(img, text, font, max_width, color, position):"""添加自动换行的多行文本:param max_width: 最大行宽(像素)"""draw = ImageDraw.Draw(img)lines = []current_line = []current_width = 0for word in text.split():word_width, _ = draw.textsize(word, font=font)if current_width + word_width > max_width and current_line:lines.append(' '.join(current_line))current_line = [word]current_width = word_widthelse:current_line.append(word)current_width += word_width + draw.textsize(' ', font=font)[0]if current_line:lines.append(' '.join(current_line))y_position = position[1]for line in lines:text_width, text_height = draw.textsize(line, font=font)draw.text((position[0], y_position), line, font=font, fill=color)y_position += text_height
字体缓存:避免重复加载字体文件
font_cache = {}def get_cached_font(font_path, font_size):key = (font_path, font_size)if key not in font_cache:font_cache[key] = ImageFont.truetype(font_path, font_size)return font_cache[key]
内存管理:
with语句处理图像文件def process_image(args):
# 解包参数return add_text_watermark_single(*args)
def batch_process_parallel(input_output_pairs, max_workers=4):
with ThreadPoolExecutor(max_workers=max_workers) as executor:
executor.map(process_image, input_output_pairs)
### 3.2 高级水印效果1. **倾斜水印**:```pythondef add_diagonal_watermark(img, text, font, color, angle=-45, spacing=100):draw = ImageDraw.Draw(img)text_width, text_height = draw.textsize(text, font=font)img_width, img_height = img.sizefor y in range(0, img_height + text_height, spacing):for x in range(0, img_width + text_width, spacing):# 计算旋转后的位置rad = angle * 3.14159 / 180new_x = x * cos(rad) - y * sin(rad)new_y = x * sin(rad) + y * cos(rad)draw.text((new_x, new_y), text, font=font, fill=color)
半透明渐变水印:
def add_gradient_watermark(img, text, font, color_start, color_end, steps=10):draw = ImageDraw.Draw(img)text_width, text_height = draw.textsize(text, font=font)img_width, img_height = img.sizefor i in range(steps):alpha = int(255 * i / steps)color = (color_start[0] + (color_end[0]-color_start[0])*i/steps,color_start[1] + (color_end[1]-color_start[1])*i/steps,color_start[2] + (color_end[2]-color_start[2])*i/steps,alpha)x = (img_width - text_width) // 2y = (img_height - text_height) // 2 + i*(text_height//steps)draw.text((x, y), text, font=font, fill=color)
文件类型过滤:
ALLOWED_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.bmp', '.webp'}def is_valid_image(filename):return any(filename.lower().endswith(ext) for ext in ALLOWED_EXTENSIONS)
异常处理增强:
try:# 图像处理代码except IOError as e:print(f"文件操作错误: {str(e)}")except ValueError as e:print(f"参数错误: {str(e)}")except Exception as e:print(f"未知错误: {str(e)}")
import loggingdef setup_logging(log_file='watermark.log'):logging.basicConfig(filename=log_file,level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s')# 使用示例logging.info(f"开始处理图片: {filename}")logging.error(f"处理失败: {str(e)}", exc_info=True)
from PIL import Image, ImageDraw, ImageFontimport osimport loggingfrom math import cos, sindef setup_logging():logging.basicConfig(filename='watermark.log',level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s')def get_cached_font(font_path, font_size, font_cache):key = (font_path, font_size)if key not in font_cache:try:font_cache[key] = ImageFont.truetype(font_path, font_size)except IOError:# 回退到默认字体font_cache[key] = ImageFont.load_default()logging.warning(f"无法加载字体 {font_path}, 使用默认字体")return font_cache[key]def add_text_watermark_advanced(input_path, output_path, text,font_path='arial.ttf', font_size=30,color=(255,255,255,128), position='bottom_right',angle=0, font_cache=None):try:img = Image.open(input_path).convert('RGBA')width, height = img.sizeif font_cache is None:font_cache = {}font = get_cached_font(font_path, font_size, font_cache)# 计算文本尺寸draw = ImageDraw.Draw(img)text_width, text_height = draw.textsize(text, font=font)# 计算位置if position == 'top_left':x, y = 10, 10elif position == 'top_right':x = width - text_width - 10y = 10elif position == 'bottom_left':x = 10y = height - text_height - 10elif position == 'bottom_right':x = width - text_width - 10y = height - text_height - 10elif position == 'center':x = (width - text_width) // 2y = (height - text_height) // 2else: # 默认左上角x, y = 10, 10# 创建水印层watermark = Image.new('RGBA', img.size, (0,0,0,0))draw_wm = ImageDraw.Draw(watermark)# 应用旋转if angle != 0:watermark = watermark.rotate(angle, expand=1)# 旋转后需要重新计算位置(简化处理)x, y = 10, 10draw_wm.text((x, y), text, font=font, fill=color)# 合成图像result = Image.alpha_composite(img, watermark)result.save(output_path, 'PNG')logging.info(f"成功处理: {input_path} -> {output_path}")return Trueexcept Exception as e:logging.error(f"处理 {input_path} 失败: {str(e)}", exc_info=True)return Falsedef batch_process(input_dir, output_dir, text, **kwargs):setup_logging()if not os.path.exists(output_dir):os.makedirs(output_dir)success_count = 0font_cache = {}for filename in os.listdir(input_dir):if is_valid_image(filename):input_path = os.path.join(input_dir, filename)output_path = os.path.join(output_dir, filename)if add_text_watermark_advanced(input_path, output_path, text,font_cache=font_cache, **kwargs):success_count += 1logging.info(f"处理完成。总文件数: {len(os.listdir(input_dir))}, 成功: {success_count}")return success_count# 使用示例if __name__ == "__main__":batch_process(input_dir='input_images',output_dir='output_images',text='Sample Watermark',font_path='arial.ttf',font_size=36,color=(255,255,255,150),position='bottom_right',angle=-15)
本方案通过PIL库实现了高效的批量文字水印添加功能,覆盖了从基础实现到高级优化的完整技术栈。实际应用中,可根据具体需求进行以下扩展:
对于企业级应用,建议将本方案封装为微服务,通过REST API提供水印处理能力,并集成到现有的媒体处理管道中。同时应考虑添加水印模板管理、批量任务调度等企业级功能。