1. Pillow绘图基础从安装到第一个图形如果你正在寻找一个简单易用的Python图像处理工具Pillow绝对是你的不二选择。作为PILPython Imaging Library的友好分支Pillow继承了PIL的强大功能同时完美支持Python 3.x版本。我刚开始接触图像处理时就被它的简洁API所吸引——用几行代码就能完成复杂的图像操作。安装Pillow只需要一条简单的pip命令pip install Pillow安装完成后让我们先来创建一个简单的白色画布。这个画布将成为我们后续绘图的基础from PIL import Image, ImageDraw # 创建一个300x300的白色背景画布 canvas Image.new(RGB, (300, 300), white) draw ImageDraw.Draw(canvas)现在我们有了一个空白的画布和一个绘图对象。让我们在上面画一个红色的矩形作为公司Logo的背景框# 绘制一个红色矩形左上角坐标(50,50)右下角坐标(250,250) draw.rectangle([(50, 50), (250, 250)], fillred, outlineblack, width2)你可能注意到了rectangle方法的第一个参数是一个包含两个坐标点的列表分别代表矩形的左上角和右下角位置。fill参数设置填充颜色outline设置边框颜色width控制边框粗细。这种直观的参数设计让Pillow用起来特别顺手。2. 绘制专业水印背景从基础图形到组合效果水印的背景设计直接影响最终效果的专业程度。通过组合不同的基础图形我们可以创造出各种精美的水印背景。让我分享几个我在实际项目中常用的技巧。首先是圆形水印背景非常适合作为Logo的容器# 绘制一个蓝色圆形通过椭圆的外接正方形实现 draw.ellipse([(100, 100), (200, 200)], fillblue, outlinewhite, width3)这里有个小技巧当ellipse方法的外接矩形是正方形时画出来的就是完美的圆形。我经常用这个特性来创建统一的圆形标识。对于需要更复杂背景的情况可以尝试组合多种图形。比如下面这个例子创建了一个带有装饰边框的水印背景# 外层大矩形 draw.rectangle([(40, 40), (260, 260)], outlinegray, width1) # 内层小矩形 draw.rectangle([(60, 60), (240, 240)], outlinegray, width1) # 对角线装饰线 draw.line([(40, 40), (260, 260)], filllightgray, width1) draw.line([(260, 40), (40, 260)], filllightgray, width1)在实际项目中我还会根据公司品牌色调整颜色方案。比如如果主色是深蓝色#1E3A8A可以这样设置brand_blue (30, 58, 138) # RGB值对应的#1E3A8A draw.rectangle([(50, 50), (250, 250)], fillbrand_blue, outlinewhite, width2)3. 文字水印全攻略从基础到高级排版文字是水印的核心内容Pillow的文字绘制功能虽然基础但足够强大。让我们从最基本的文字绘制开始。首先我们需要准备字体文件。这是很多新手容易忽略的一步——没有字体文件Pillow无法正确渲染文字。我推荐使用SimHei.ttf黑体作为中文字体因为它清晰易读且免费可用。from PIL import ImageFont # 加载字体文件假设字体文件放在同目录下的fonts文件夹中 try: font ImageFont.truetype(fonts/SimHei.ttf, 24) except IOError: print(字体文件加载失败请检查路径) font ImageFont.load_default() # 使用默认字体作为后备方案绘制基础文字很简单# 在(100,150)位置绘制黑色文字 draw.text((100, 150), 版权所有, fillblack, fontfont)但实际项目中我们经常需要处理更复杂的排版需求。比如让文字在水印背景中居中显示。这需要计算文字的实际占用空间text 公司机密 # 获取文字的边界框 text_bbox draw.textbbox((0, 0), text, fontfont) text_width text_bbox[2] - text_bbox[0] text_height text_bbox[3] - text_bbox[1] # 计算居中位置 x (300 - text_width) / 2 y (300 - text_height) / 2 draw.text((x, y), text, fillwhite, fontfont)对于多行文字我们需要手动处理行间距。下面是一个添加版权信息的例子lines [公司名称, © 2023 All Rights Reserved] y_position 180 line_height 30 # 预设行高 for line in lines: text_bbox draw.textbbox((0, 0), line, fontfont) text_width text_bbox[2] - text_bbox[0] x (300 - text_width) / 2 draw.text((x, y_position), line, fillwhite, fontfont) y_position line_height4. 动态内容处理自动添加日期和序列号静态水印已经能满足基本需求但如果能加入动态信息水印的价值会大大提升。Pillow可以轻松实现这一点。添加当前日期是很常见的需求from datetime import datetime current_date datetime.now().strftime(%Y-%m-%d %H:%M:%S) draw.text((50, 270), f生成时间: {current_date}, fillgray, fontfont)对于需要批量处理的图片添加序列号特别有用。比如给每张图片加上唯一的编号serial_number SN-2023-001 draw.text((180, 270), serial_number, fillgray, fontfont)在实际项目中我通常会把这些动态信息放在图片的角落并使用半透明效果避免干扰主要内容from PIL import ImageColor # 创建半透明灰色 (最后一位是alpha通道0-255) semi_transparent ImageColor.getrgb(rgba(100, 100, 100, 150)) draw.text((10, 270), fID: {serial_number}, fillsemi_transparent, fontfont)5. 透明度与混合模式专业水印的秘诀专业的水印往往需要与背景图片和谐共存这就需要掌握透明度和混合模式的技巧。首先我们需要创建一个带有透明通道的图像RGBA模式# 创建一个带有透明通道的画布 transparent_canvas Image.new(RGBA, (300, 300), (255, 255, 255, 0)) draw ImageDraw.Draw(transparent_canvas)现在我们可以绘制半透明的水印了# 半透明红色矩形 (alpha128) draw.rectangle([(50, 50), (250, 250)], fill(255, 0, 0, 128), outline(0, 0, 0, 255))将水印叠加到现有图片上的正确方法是使用alpha_composite# 假设background是已经加载的背景图片 watermarked Image.alpha_composite(background.convert(RGBA), transparent_canvas)在实际项目中我经常使用这种技术为公司图片添加不显眼但又清晰可见的水印。通过调整透明度可以平衡版权保护和视觉体验。6. 批量处理实战自动化水印添加系统单张图片处理已经够用了但真正的威力在于批量处理。让我们构建一个完整的批量水印添加系统。首先我们需要一个处理单张图片的函数def add_watermark(image_path, output_path, watermark_text): try: with Image.open(image_path).convert(RGBA) as base_image: # 创建一个透明层用于水印 watermark Image.new(RGBA, base_image.size, (255, 255, 255, 0)) draw ImageDraw.Draw(watermark) # 计算水印位置右下角 font ImageFont.truetype(fonts/SimHei.ttf, 30) text_bbox draw.textbbox((0, 0), watermark_text, fontfont) text_width text_bbox[2] - text_bbox[0] text_height text_bbox[3] - text_bbox[1] margin 20 # 边距 x base_image.width - text_width - margin y base_image.height - text_height - margin # 绘制半透明背景 draw.rectangle( [(x-10, y-10), (xtext_width10, ytext_height10)], fill(0, 0, 0, 128) ) # 绘制文字 draw.text((x, y), watermark_text, fill(255, 255, 255, 200), fontfont) # 合并图片 watermarked Image.alpha_composite(base_image, watermark) watermarked.convert(RGB).save(output_path) except Exception as e: print(f处理 {image_path} 时出错: {str(e)})然后我们可以用这个函数处理整个文件夹的图片import os def batch_watermark(input_folder, output_folder, watermark_text): if not os.path.exists(output_folder): os.makedirs(output_folder) for filename in os.listdir(input_folder): if filename.lower().endswith((.png, .jpg, .jpeg)): input_path os.path.join(input_folder, filename) output_path os.path.join(output_folder, filename) add_watermark(input_path, output_path, watermark_text) print(f已处理: {filename})这个批处理系统在我的工作中大显身手特别是当需要处理数百张产品图片时。你可以根据需要扩展它比如添加日期、序列号或者根据图片内容调整水印位置。7. 性能优化与常见问题解决当处理大量或大尺寸图片时性能会成为问题。以下是我在实践中总结的几个优化技巧。首先是图片尺寸的问题。如果只是添加水印通常不需要原始分辨率with Image.open(image_path) as img: # 如果图片宽度超过2000px先缩小尺寸 if img.width 2000: new_height int(img.height * (2000 / img.width)) img img.resize((2000, new_height), Image.LANCZOS)字体加载也是一个性能瓶颈。不要在每次绘制时都加载字体应该全局缓存font_cache {} def get_font(size): if size not in font_cache: try: font_cache[size] ImageFont.truetype(fonts/SimHei.ttf, size) except: font_cache[size] ImageFont.load_default() return font_cache[size]常见的中文乱码问题通常是因为字体文件未正确加载。确保字体文件路径正确字体文件有读取权限使用支持中文的字体另一个常见问题是水印位置计算错误。我建议先绘制一个临时矩形标记预期位置# 调试水印位置 draw.rectangle([(x, y), (xtext_width, ytext_height)], outlinered)内存管理也很重要特别是处理大量图片时。确保使用with语句自动关闭文件或者手动调用close()方法。8. 创意扩展超越基础水印掌握了基础技巧后Pillow还能实现更多创意效果。让我们探索几种进阶应用。倾斜水印能增加专业感也更难被去除# 创建一个透明层 watermark Image.new(RGBA, (400, 200), (255, 255, 255, 0)) draw ImageDraw.Draw(watermark) draw.text((50, 50), 内部资料, fill(255, 255, 255, 180), fontfont) # 旋转45度 watermark watermark.rotate(45, expandTrue, resampleImage.BICUBIC, fillcolor(0, 0, 0, 0))平铺水印适合保护敏感文档def tile_watermark(base_image, text, font, opacity60): watermark Image.new(RGBA, (200, 200), (255, 255, 255, 0)) draw ImageDraw.Draw(watermark) draw.text((10, 10), text, fill(255, 0, 0, opacity), fontfont) # 创建平铺图案 tiled Image.new(RGBA, base_image.size) for x in range(0, base_image.width, watermark.width): for y in range(0, base_image.height, watermark.height): tiled.paste(watermark, (x, y)) return Image.alpha_composite(base_image, tiled)最后分享一个我最近为客户做的动态二维码水印。它将版权信息编码成二维码嵌入图片角落import qrcode def add_qr_watermark(image, qr_data, size150, margin20): # 生成二维码 qr qrcode.QRCode( version1, error_correctionqrcode.constants.ERROR_CORRECT_L, box_size10, border1, ) qr.add_data(qr_data) qr.make(fitTrue) qr_img qr.make_image(fill_colorblack, back_colorwhite).convert(RGBA) # 调整大小 qr_img qr_img.resize((size, size), Image.LANCZOS) # 创建透明层 qr_watermark Image.new(RGBA, image.size, (255, 255, 255, 0)) position (image.width - size - margin, image.height - size - margin) qr_watermark.paste(qr_img, position) return Image.alpha_composite(image, qr_watermark)