好久没写博客了。最近在做图片水印功能,本来觉得难度不大,照着 Pillow 文档随便搞搞就行了。没想到设计师验收的时候发现一堆坑。也许是学艺不精,没有仔细研究过 Pillow,只是随便看看文档做的,可能是我用的姿势不对才导致一堆坑。
基本需求是:
- 前面一个品牌 logo
- 后面一个 @用户名,用思源黑体常规字重的字体
- 有投影效果
- 整体大小和图片宽度成比例
首先去 Pillow 文档里找了个 example
在网上查一些 blog 也都是这么写的,于是就照着写了一个基本的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
from PIL import Image, ImageDraw, ImageFont
SourceHanSans_font = '~/SourceHanSansCN-Regular.otf'
STANDARD_SIDE_MARGIN = 12 STANDARD_BOTTOM_MARGIN = 10 SHADOW_OFFSET = 1
im = Image.open('test.jpg').convert("RGBA") username = u'@用户名'
im_width, im_height = im.size
scale = im_width * 1.0 / 450
font = ImageFont.truetype(SourceHanSans_font, size=int(round(13 * scale))) text = username text_width, text_height = font.getsize(text) text_x = int(round(im_width - STANDARD_SIDE_MARGIN * scale - text_width)) text_y = int(round(im_height - STANDARD_BOTTOM_MARGIN * scale - text_height))
txt = Image.new('RGBA', im.size, (0, 0, 0, 0)) draw = ImageDraw.Draw(txt)
draw.text((text_x + int(round(SHADOW_OFFSET * scale)), text_y + int(round(SHADOW_OFFSET * scale))), text, font=font, fill=(0, 0, 0, 77)) draw.text((text_x, text_y), text, font=font, fill=(255, 255, 255, 255)) r = Image.alpha_composite(combined, txt) draw = ImageDraw.Draw(r)
r.save("result.jpg", format="JPEG", quality=95)
|
看起来还可以啊,计算好文字的长宽,先绘制投影。投影就是一个黑色带透明度的同样的文字,(x,y) 坐标分别向右和下增加若干像素。绘制完之后,再在原来的 (x,y) 位置绘制白色文字就行了。文字都绘制到一张空白的透明图上,然后把两个图层贴一块就行了。
搞出来发现,怎么文字不是按照我说的 (x,y) 放置,y 坐标偏移了若干像素呢?
然后网上查了一圈发现,字体这个东西,原来是有个 offset 的概念的。绘制的时候把 offset 自己减去就行了。晕。改一把。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
from PIL import Image, ImageDraw, ImageFont
SourceHanSans_font = '~/SourceHanSansCN-Regular.otf'
STANDARD_SIDE_MARGIN = 12 STANDARD_BOTTOM_MARGIN = 10 SHADOW_OFFSET = 1
im = Image.open('test.jpg').convert("RGBA") username = u'@用户名'
im_width, im_height = im.size
scale = im_width * 1.0 / 450
font = ImageFont.truetype(SourceHanSans_font, size=int(round(13 * scale))) text = username text_width, text_height = font.getsize(text) text_x_offset, text_y_offset = font.getoffset(text) text_x = int(round(im_width - STANDARD_SIDE_MARGIN * scale - text_width)) text_y = int(round(im_height - STANDARD_BOTTOM_MARGIN * scale - text_height))
txt = Image.new('RGBA', im.size, (0, 0, 0, 0)) draw = ImageDraw.Draw(txt)
draw.text((text_x - text_x_offset + int(round(SHADOW_OFFSET * scale)), text_y - text_y_offset + int(round(SHADOW_OFFSET * scale))), text, font=font, fill=(0, 0, 0, 77)) draw.text((text_x - text_x_offset, text_y - text_y_offset), text, font=font, fill=(255, 255, 255, 255)) r = Image.alpha_composite(combined, txt) draw = ImageDraw.Draw(r)
r.save("result.jpg", format="JPEG", quality=95)
|
然后就开心的给设计师验收了。
然后就坑爹了。
在图片大的时候,比如 1080P 这么大的图,看着效果确实还行。但是在小图的时候图片中文字周围有不明的黑色锯齿。
例如这样
原因不明。然后就网上搜抗锯齿的方案。stackoverflow 上看到一些方法,例如这里。就是把图片先放大 2 倍,等比放大绘制完成之后,再缩小到 1/2,也就是原图大小,并在 pillow 里的 resize
方法添加 filter=Image.ANTIALIAS
。
确实有不少效果,黑色锯齿少了很多,但是还是有,并且因为缩小,绘制出来的文字画质有损失。不能接受。
查到最后发现,原来直接在原图上绘制就不会有黑色锯齿。之前都是先生成一张透明的图层,在这个上面绘制完成之后,把原图和透明图合并。不知道为什么,在透明图上绘制白色的文字就会有黑色锯齿,在尺寸小的时候。于是就改了一下思路,全部直接在原图上进行绘制,不用透明图了。
但是又发现,绘制出来的问题,没有透明度了。崩溃。不知道为什么。就又改了一下思路,把需要透明度的黑色阴影在透明图上绘制,和原图贴合之后,再在原图上绘制不需要透明度的白色文字。这样就解决了问题。晕死。
其实同时还有另一个问题,就是品牌 logo,其实就是「知乎」两个字。设计师给我的是一个 svg 图,方便等比放大。但是搜了一圈,发现 Python 没有什么特别方便的,操作 svg 的库,不是依赖太大,就是用起来很麻烦,没什么人维护。就让设计师换了个思路,把 svg 图转成 ttf 字体文件,到时候一起用字体绘制多方便。