kniost

谁怕,一蓑烟雨任平生

0%

Python使用Pillow(PIL)库生成圆形图片的方法和优化

以下 PIL 和 Pillow 两个库只出现 Pillow, 但 PIL 具有相同功能

最近遇到了在一张海报中贴上圆形头像的需求,贴图可以使用Pillow直接的Image.paste()方法,但是要实现圆形的话,也有许多方法,下面把我想到的和搜集到的方法一一说明。

基本的思路就是:让圆形外的像素透明

一个直接的方法是:图像首先转成’RGBA’,获取 size 找到中心点,然后取半径 r,遍历所有像素并对圆外的像素的 alpha 赋值为 0。但是这样写起来费劲,可以使用更好的方法来做。

1. 使用Image.putalpha修改 alpha 层

这个方法中我们使用替换图像 alpha 层的方法实现除圆形外的区域透明,这种方法在我们只需要一个圆形图案时使用比较好

先来看官方文档

Image.putalpha(alpha)[source]
Adds or replaces the alpha layer in this image. If the image does not have an alpha layer, it’s converted to “LA” or “RGBA”. The new layer must be either “L” or “1”.

这个方法添加或者替换图像的 alpha 层,如果本来没有 alpha 层将自动转为有 alpha 层的图像,需要一个模式为'L'(灰度)或者'1'(黑白)的图像

上代码:

1
2
3
4
5
6
7
8
9
10
# 首先获取一个alpha图像:
# 假设经过前处理后已经有 w == h
w, h = img.size
# 研究源码后发现,如果使用'1'模式的图像,内部也会转换成'L',所以直接用'L'即可
alpha_layer = Image.new('L', (w, w), 0)
draw = ImageDraw.Draw(alpha_layer)
draw.ellipse((0, 0, w, w), fill=255)

# 接着替换图像的alpha层
img.putalpha(alpha_layer)

实现效果:
putalpha-2018122

2. 使用 Image.paste蒙版粘贴圆形图片

如果我们要在一张图片上盖上一个圆形头像,就需要用到 paste 方法,paste 方法有一个可选参数是 mask,我们看官方文档:

If a mask is given, this method updates only the regions indicated by the mask. You can use either “1”, “L” or “RGBA” images (in the latter case, the alpha band is used as mask). Where the mask is 255, the given image is copied as is. Where the mask is 0, the current value is preserved. Intermediate values will mix the two images together, including their alpha channels if they have them.

意思是我们同样需要一个圆形蒙版,圆形部分透明度是 255,其他部分透明度为 0,但是这个mask可以使用一个'RGBA'模式的图像。

上代码:

1
2
3
4
5
6
7
8
9
# 首先获取一个alpha图像:
# 假设经过前处理后已经有 w == h
w, h = img_paste.size
alpha_layer = Image.new('L', (w, w), 0)
draw = ImageDraw.Draw(alpha_layer)
draw.ellipse((0, 0, w, w), fill=255)

# 接着直接使用其作为模板粘贴即可
img_origin.paste(image_paste, (0, 0), alpha_layer)

效果如下:

paste_with_mask-2018122

3. 锯齿问题和解决方案

使用透明度替换方法时很容易发现直接使用原始大小的 alpha 层会出现多边形现象,也就是圆的周围会出现锯齿,目前想到的解决方案就是:先绘制一个 scale 倍大的圆,然后使用resize方法将其缩小,使用ANTIALIAS(即LANCZOS)采样器

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 首先获取一个alpha图像:
scale = 3
# 假设经过前处理后已经有 w == h
w, h = img_paste.size
# 使用新的半径构建alpha层
r = w * scale
alpha_layer = Image.new('L', (r, r), 0)
draw = ImageDraw.Draw(alpha_layer)
draw.ellipse((0, 0, r, r), fill=255)
# 使用ANTIALIAS采样器缩小图像
alpha_layer = alpha_layer.resize((w, w), Image.ANTIALIAS)

# 接着直接使用其作为模板粘贴即可
img_origin.paste(image_paste, (0, 0), alpha_layer)

效果如下:

scale为1,3,5的对比图

上面为scale分别为 1,3,5 时的对比图,可以明显发现边缘已经基本没有锯齿感了。