Tomorrow.Comes.Today

python 2D图像处理cairo设置MASK


话说前几天看到这篇文章整个人都被燃了一下啊啊啊!

就是这哥们儿,放上来膜拜一下:

man-live-inside-computer

咳咳,我的意思是,以前做过一套安卓图标Sailfish化的仓库,P图辛苦的一比,索性今天写个脚本自己来好了。

关于Python使用cairo进行图像处理。

目标

将一个png图像裁剪,改变形状即可:

source -> target

闪亮亮的Sailfish风格!

(请将第一张图另存为source.png

安装

pycairo免了吧,新版连setup.py都给弄丢了,cairocffi才是未来

pip install cairocffi

使用的时候只需要

import cairocffi as cairo

即可

基本

官方文档这篇教程都给我够了,前者我找不到怎么开始,后者我干嘛要知道GTK的破事儿?!!

一路一路来,先建个py文件跟source.png放在一起,写个

import cairocffi as cairo

Surface

Surface就是一个画板,没有画板就没法画画。cairo创建Surface大致有两种,一种直接载入一个图片:

source = cairo.ImageSurface.create_from_png('source.png')

另外一种是创建一个全新的,什么都没有:

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 144, 144)

mask

有些许图像知识的话应该很好懂。mask也是一张图像,但它不是用来显示的。

一般来说mask就一张筛子,白色部分是筛洞,一张图像放在mask这个筛子上,于白色重叠的部分就变成了透明的(因为颜色漏下去了嘛)。最好mask的尺寸跟给定尺寸一致。

mask

不过这里我们不用这种筛子。我们用更简单的方式,就是让透明的部分变成透明的(听着好怪)

新筛子(另存为mask.png):

mask

说过了,mask也是图像,所以我们要把它载入为Surface对象

mask = cairo.ImageSurface.create_from_png('mask.png')

Context

好了,画板(Surface)是有了,问题是cairo画画并不能直接在Surface上画,画花了怎么整?

所以在画上去之前,要获取一个叫上下文设备(Context)的东西,它负责记录你所有的操作,然后等你告诉它把这些修改画上去。

所以我们先把source画到surface上面去:

surface_context = cairo.Context(surface)  # 获取surface的上下文
surface_context.set_source_surface(source)  # 把source放倒surface上面去
surface_context.paint()  # 这一步告诉context把修改应用到surface上

保存

Surface对象可以保存为文件……这不是废话么,不然我们画半天给谁看? .write_to_png可以将图像保存下来(注:可以传入pythonfile-like对象)

surface.write_to_png('surface_with_source.png')

好了。目前我们的脚本看起来像这样:

import cairocffi as cairo
source = cairo.ImageSurface.create_from_png('source.png')
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 144, 144)
mask = cairo.ImageSurface.create_from_png('mask.png')

surface_context = cairo.Context(surface)  # 获取surface的上下文
surface_context.set_source_surface(source)  # 把source放倒surface上面去
surface_context.paint()  # 这一步告诉context把修改应用到surface上

surface.write_to_png('surface_with_source.png')

目前已经得到的图片:

surface_with_source

嗯没有变化。因为我们只是把一张图原样画到另一个画板上而已……

绘制操作

之前说过,图像和mask都是一个Surface对象,怎么控制让mask起到筛子的作用呢?直接画上去不就黑乎乎一坨了嘛?

实际上绘制之前我们是可以设置怎么绘制的。默认就是安静地画上去,而mask的要求就是,有颜色的区域留下,透明区域滚粗!

Context对象的.set_operator方法可以做到:

surface_context.set_operator(cairo.OPERATOR_DEST_IN)  # 修改绘制方式
surface_context.set_source_surface(mask)  # 扣图
surface_context.paint()  # 绘制修改

surface.write_to_png('surface_with_mask.png')

看效果:

surface_with_mask

差不多。在我把边角弄好之前,先说说画线。

绘制

这里有个麻烦的地方:我得准备一大堆不同的mask,形状尺寸各不相同,简直要命……

所以自动生成比啥都好。

路径

cairoContext总有一个激活路径,路径从初始点开始,通过move_to(移动到新点) line_to(移动到新点并画线)等方法画图。然后通过fill填充或stroke描线。

根据这个写下我们绘制的脚本模板:

import cairocffi as cairo

width = 192
height = 192
center_x = width / 2
center_y = height / 2

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
context = cairo.Context(surface)
# 这里制作mask
context.fill()

surface.write_to_png('generate_mask.png')

画线与描边

line_to可以连线。一个小示例:

context.set_line_width(5)  # 笔画粗细
context.set_source_rgb(255, 0, 0)  # 设置颜色
context.move_to(0, center_y)  # 移动点
context.line_to(0, 0)  # 绘制
context.line_to(center_x, 0)
context.line_to(center_x, center_y)
context.line_to(0, center_y)
context.stroke()  # 画线,不是fill

就得到了这货:

generate_mask

左上角一个红框框

图形与填充

我们要做的是mask啊,需要色块……

基本上就是arc画圆弧,高级点的像curve_to画贝塞尔曲线什么的真是够了(PhoneShop钢笔工具啊)。

注意arc角度的起始点是右边中间(其实就是0度啦)

来着手画出来(完整示例):

import cairocffi as cairo
from math import pi

width = 192
height = 192
center_x = width / 2
center_y = height / 2
radius  width / 2

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
context = cairo.Context(surface)
context.move_to(0, center_y)  # 移动点
# 左上角圆弧
context.arc(center_x, center_y, radius, 1 * pi, 1.5 * pi)
# 右上角方框
context.line_to(width, 0)
context.line_to(width, center_y)
# 右下角圆弧
context.arc(center_x, center_y, radius, 0, 0.5 * pi)
# 左下角方框
context.line_to(0, width)
context.line_to(0, center_y)
# 填充
context.fill()

surface.write_to_png('generate_mask.png')

这样就得到之前一模一样的mask了

mask

图像叠图像叠图像

至此所有需要的东西都准备好了。接下来,我想把边边那个尖尖也填上绿色。大致思路就是:

  • 获取原图尺寸
  • 按照原图尺寸创建两个形状,一个作为mask抠图,一个作为填充背景
  • 将原图修边为我们想要的形状
  • 将修好的原图覆盖在填充背景上

完整代码就这样:

import cairocffi as cairo
from math import pi

bg_color = (0.3, 1, 0.4)  # 绿色背景. 奇葩的是它是用百分比表示的

source = cairo.ImageSurface.create_from_png('source.png')
width = source.get_width()  # 原图宽
height = width  # 高度跟宽度一致。不然图标就丑了
center_x = width / 2
center_y = height / 2
radius = width / 2  # 半径

# 我们要操作的surface
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, width)
# 我们要制作的mask
mask = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, width)
# 我们要制作的背景形状
background = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, width)

bg_context = cairo.Context(background)
bg_context.set_source_rgb(*bg_color)  # 背景形状填充绿色

# 修改各自形状,填充颜色
for each_context in (cairo.Context(mask), bg_context):
    each_context.move_to(0, center_y)  # 移动点
    # 左上角圆弧
    each_context.arc(center_x, center_y, radius, 1 * pi, 1.5 * pi)
    # 右上角方框
    each_context.line_to(width, 0)
    each_context.line_to(width, center_y)
    # 右下角圆弧
    each_context.arc(center_x, center_y, radius, 0, 0.5 * pi)
    # 左下角方框
    each_context.line_to(0, width)
    each_context.line_to(0, center_y)
    # 填充
    each_context.fill()
    # each_context.paint()

surface_context = cairo.Context(surface)  # 获取surface的上下文
surface_context.set_source_surface(source)  # 把source放倒surface上面去
surface_context.paint()  # 这一步告诉context把修改应用到surface上


surface_context.set_operator(cairo.OPERATOR_DEST_IN)  # 修改绘制方式
surface_context.set_source_surface(mask)  # 扣图
surface_context.paint()  # 绘制修改

# 接下来,把抠好的surface画到背景上去
bg_context.set_source_surface(surface)
bg_context.paint()

background.write_to_png('target.png')

target-fill.png

以上

这里有一个我的小工具,可以完整地帮我做转换: TylerTemp-jollaicon


TylerTemp

TylerTemp is a Phthon programmer. You can find me here:      

If you like my work, you can buy me a cup of beer so I can make it better!   |   Flattr this

回到顶部