操作图像
如果你有一台数码相机,或者只是将照片从手机上传到 Facebook,你可能随时都会偶然遇到数字图像文件。你可能知道如何使用基本的图形软件,如 Microsoft Paint 或 Paintbrush,甚至更高级的应用程序,如 Adobe Photoshop。但是,如果需要编辑大量的图像,手工编辑可能是漫长、枯燥的工作。
请用 Python
。Pillow
是一个第三方 Python
模块,用于处理图像文件。该模块包含一些函数,可以很容易地裁剪图像、调整图像大小,以及编辑图像的内容。可以像 Microsoft Paint 或 Adobe Photoshop 一样处理图像,有了这种能力,Python
可以轻松地自动编辑成千上万的图像。
用 Pillow
操作图像
处理 Image
数据类型
Image对象有一些有用的属性,提供了加载的图像文件的基本信息:它的宽度和高度、文件名和图像格式(如 JPEG
、 GIF
或PNG)。
from PIL import Image
cat_img = Image.open('zophie.png')
# 获取图片的宽高
print(cat_img.size)
width, height = cat_img.size
print(width, height)
# 获取图片的名字
print(cat_img.filename)
# 获取图片的格式
print(cat_img.format)
# 获取图片的描述信息
print(cat_img.format_description)
# 保存图片
cat_img.save('zophie.jpg')
从 Zophie.png 得到一个 Image
对象并保存在cat_img中后,我们可以看到该对象的 size
属性是一个元组,包含该图像的宽度和高度的像素数。我们可以将元组中的值赋给 width
和 height 变量,以便分别访问宽度和高度。filename 属性描述了原始文件的名称。format 和 format_description 属性是字符串,描述了原始文件的图像格式(format_description
比较详细)。
最后,调用 save() 方法,传入 'zophie.jpg’,将新图像以文件名 zophie.jpg 保存到硬盘上。Pillow 看到文件扩展名是 jpg ,就自动使用 JPEG
图像格式来保存图像。现在硬盘上应该有两个图像,zophie.png 和 zophie.jpg。虽然这些文件都基于相同的图像,但它们不一样,因为格式不同。
Pillow 还提供了 Image.new() 函数,它返回一个 Image 对象。这很像 Image.open(),不过 Image.new() 返回的对象表示空白的图像。Image.new() 的参数如下:
- 字符串 'RGBA',将颜色模式设置为 RGBA(还有其他模式,但本书没有涉及)。
- 大小,是两个整数元组,作为新图像的宽度和高度。
- 图像开始采用的背景颜色,是一个表示
RGBA
值的四整数元组。你可以用ImageColor.getcolor()
函数的返回值作为这个参数。另外,Image.new()
也支持传入标准颜色名称的字符串。
from PIL import Image
# 100像素宽、200像素高,带有紫色背景
img = Image.new('RGBA', (100, 200), 'purple')
img.save('purpleImage.png')
# 未指定背景颜色默认为透明
img2 = Image.new('RGBA', (20, 20))
img2.save('transparentImage.png')
这里,我们创建了一个 Image
对象,它有 100 像素宽、200 像素高,带有紫色背景。然后,该图像存入文件 purpleImage.png 中。我们再次调用 Image.new() ,创建另一个 Image
对象,这次传入(20, 20)作为大小,没有指定背景色。 如果未指定颜色参数,默认的颜色是不可见的黑色(0,0,0,0),因此第二个图像具有透明背景,我们将这个 20×20 的透明正方形存入 transparentImage.png。
图像基础
为了处理图像,你需要了解计算机如何处理图像中的顔色和坐标的基本知识,以及如何在 Pillow
中处理颜色和坐标。但在继续探讨之前,先要安装 pillow
模块。安装第三方模块请见附录 A
。
颜色和 RGBA
值
计算机程序通常将图像中的颜色表示为 RGBA
值。 RGBA
值是一组数字,指定顔色中的红、绿、蓝和alpha(透明度)的值。这些值是从0(根本没有)到255(最高)的整数。这些 RGBA
值分配给单个像素,像素是计算机屏幕上能显示一种顔色的最小点(你可以想到,屏幕上有几百万像素)。像素的 RGB
设置准确地告诉它应该显示哪种颜色的色彩。图像也有一个 alpha
值,用于生成 RGBA
值。如果图像显示在屏幕上,遮住了背景图像或桌面墙纸, alpha
值决定了“透过”这个图像的象素,你可以看到多少背景。
在 Pillow
中, RGBA
值表示为四个整数值的元组。例如,红色表示为(255,0,0,255)。这种颜色中红的值为最大,没有绿和蓝,并且 alpha
值最大,这意味着它完全不透明。绿色表示为(0,255,0,255),蓝色是(0,0,255,255)。白色是各种颜色的组合,即(255,255,255,255),而黑色没有任何颜色,是(0,0,0,255)。
如果颜色的 alpha
值为0,不论 RGB
值是什么,该颜色是不可见的。毕竟,不可见的红色看起来就像不可见的黑色一样。
Pillow使用了 HTML
使用的标准颜色名称。下面列出了一些标准颜色的名称和值。
标准颜色名称及其
RGB
值
名称 | RGBA值 | 名称 | RGBA值 |
---|---|---|---|
White | (255, 255, 255,255) | Red | (255, 0, 0,255) |
Green | (0, 128, 0,255) | Blue | (0, 0, 255,255) |
Gray | (128, 128, 128,255) | Yellow | (255, 255, 0,255) |
Black | (0, 0, 0, 255) | Purple | (128, 0, 128,255) |
Pillow 提供 ImageColor.getcolor() 函数,所以你不必记住想用的顔色的 RGBA
值。该函数接受一个颜色名称字符串作为第一个参数,字符串 'RGBA' 作为第二个参数,返回一个 RGBA
元组。
要了解该函数的工作方式,就在交互式环境中输入以下代码:
from PIL import ImageColor
print(ImageColor.getcolor('red', 'RGBA'))
print(ImageColor.getcolor('RED', 'RGBA'))
print(ImageColor.getcolor('Black', 'RGBA'))
# 巧克力色
print(ImageColor.getcolor('chocolate', 'RGBA'))
# 矢车菊蓝
print(ImageColor.getcolor('CornflowerBlue', 'RGBA'))
首先,你需要从 PIL
导入 ImageColor 模块(不是从 Pillow ,稍后你就会明白为什么)。传递给 ImageColor.getcolor() 的颜色名称字符串是不区分大小写的,所以传入 'red' 和传入 'RED' 将得到同样的 RGBA
元组。还可以传递更多的不常见的顔色名称,如 'chocolate' 和 'Cornflower Blue'。
Pillow 支持大量的颜色名称,从 'aliceblue' 到 'whitesmoke' 。超过 100 种标准颜色名称的完整列表。
坐标和 Box
元组
图像像素用 x
和 y
坐标指定,分别指定像素在图像中的水平和垂直位置。原点是位于图像左上角的像素,用符号(0,0)指定。第一个0表示 x
坐标,它以原点处为 0,从左至右增加。第二个 0 表示 y
坐标,它以原点处为 0,从上至下增加。这值得重复一下:y 坐标向下走增加,你可能还记得数学课上使用的 y
坐标,与此相反。下图坐标系统的工作方式。
27×26 的图像的 x
和 y
坐标,某种古老的数据存储装置
CMYK 和 RGB
着色
上小学时你学过,混合红、黄、蓝三种顔料可以得到其他颜色。例如,可以混合蓝色和黄色,得到绿色顔料。这就是所谓的减色模型,它适用于染料、油墨和颜料。这就是为什么彩色打印机有的 CMYK
墨盒:青色(蓝色)、品红色(红色)、黄色和黑色墨水可以混合在一起,形成任何颜色。
然而,光的物理使用所谓的加色模型。如果组合光(例如由计算机屏幕发出的光),红、绿和蓝光可以组合形成其他颜色。这就是为什么在计算机程序中使用 RGB
值表示颜色。
许多 Pillow
函数和方法需要一个矩形元组参数。这意味着 Pillow
需要一个四个整坐标的元组,表示图像中的一个矩形区域。四个整数按顺序分别是:
- 左:该矩形的最左边的
x
坐标。 - 顶:该矩形的顶边的
y
坐标。 - 右:该矩形的最右边右面一个像素的
x
坐标。此整数必须比左边整数大。 - 底:该矩形的底边下面一个像素的
y
坐标。此整数必须比顶边整数大。
注意,该矩形包括左和顶坐标,直到但不包括右和底坐标。例如,矩形元组(3, 1, 9, 6)表示图中黑色矩形的所有像素。
既然知道了 Pillow 中颜色和坐标的工作方式,就让我们用 Pillow 来处理图像。
将图像文件 Zophie.png
放在当前工作目录中,你就可以将 Zophie
的图像加载到 Python
中,像这样:
from PIL import Image
cat_img = Image.open('zophie.png')
要加载图像,就从 Pillow
导入 Image
模块,并调用 Image.open()
,传入图像的文件名。然后,可以将加载图像保存在 CatIm
这样的变量中。 Pillow
的模块名称是 PIL
,这保持与老模块 Python Imaging Library
向后兼容,这就是为什么必须 from PIL import Image
,而不是 from Pillow import Image
。由于 Pillow
的创建者设计 Pillow
模块的方式,你必须使用 from PIL import Image
形式的 import
语句,而不是简单地 import PIL
。
Image.open()
函数的返回值是 Image
对象数据类型,它是 Pillow
将图像表示为 Python
值的方法。可以调用 Image.open()
,传入文件名字符串,从一个图像文件(任何格式)加载一个 Image
对象。通过 save()
方法,对 Image
对象的所有更改都可以保存到图像文件中(也是任何格式)。所有的旋转、调整大小、裁剪、绘画和其他图像操作,都通过这个 Image
对象上的方法调用来完成。
为了让本章的例子更简短,我假定你已导入了 Pillow
的 Image
模块,并将 Zophie
的图像保存在变量 catIm
中。要确保 zophie.png
文件在当前工作目录中,让 Image.open()
函数能找到它。否则,必须在 Image.open()
的字符串参数中指定完整的绝对路径。
裁剪图片
裁剪图像是指在图像内选择一个矩形区域,并删除矩形之外的一切。Image
对象的 crop()
方法接受一个矩形元组,返回一个 Image
对象,表示裁剪后的图像。裁剪不是在原图上发生的,也就是说,原始的 Image
对象原封不动,crop()
方法返回一个新的 Image
对象。请记住,矩形元组(这里就是要裁剪的区域)包括左列和顶行的像素,直至但不包括右列和底行的像素。
在交互式环境中输入以下代码:
# 剪裁图片
cat_img = Image.open('zophie.png')
cropped_img = cat_img.crop((335, 345, 565, 560))
cropped_img.save('cropped_cat_img.png')
这得到一个新的 Image
对象,是剪裁后的图像,保存在 croppedIm
中,然后调用 croppedIm
的 save()
,将裁剪后的图像存入 cropped.png
。新文件 cropped.png
从原始图像创建。
新图像只有原始图像剪裁后的部分
复制和粘贴图像到其他图像
copy()方法返回一个新的 Image
对象,它和原来的 Image
对象具有一样的图像。如果需要修改图像,同时也希望保持原有的版本不变,这非常有用。
# 复制图片
cat_copy_img = cat_img.copy()
catIm和 catCopyIm
变量包含了两个独立的 Image
对象,它们的图像相同。既然 catCopyIm
中保存了一个 Image
对象,你可以随意修改 catCopyIm
,将它存入一个新的文件名,而 zophie.png
没有改变。例如,让我们尝试用 paste()
方法修改 catCopyIm
。
paste()方法在 Image
对象调用,将另一个图像粘贴在它上面。我们继续交互式环境的例子,将一个较小的图像粘贴到 catCopyIm
。
# 粘贴图片
face_img = cat_img.crop((335, 345, 565, 560))
cat_copy_img.paste(face_img, (0, 0))
cat_copy_img.paste(face_img, (400, 500))
cat_copy_img.save('pasted.png')
首先我们向 crop()
传入一个矩形元组,指定 zophie.png
中的一个矩形区域,包含 Zophie
的脸。这将创建一个 Image
对象,表示 230×215 的剪裁区域,保存在 faceIm
中。现在,我们可以将 faceIm
粘贴到 catCopyIm
。paste()
方法有两个参数:一个“源” Image 对象,一个包含 x
和 y
坐标的元组,指明源 Image
对象粘贴到主 Image
对象时左上角的位置。这里,我们在 catCopyIm
上两次调用 paste()
,第一次传入(0, 0),第二次传入(400, 500)。这将 faceIm
两次粘贴到 catCopyIm
:一次 faceIm
的左上角在(0, 0),一次 faceIm
的左上角在(400, 500)。最后,我们将修改后的 catCopyIm
存入 pasted.png
。 pasted.png
如图所示。
注意
尽管名称是 copy() 和 paste(),但
Pillow
中的方法不使用计算机的剪贴板。
请注意,paste()
方法在原图上修改它的 Image
对象,它不会返回粘贴后图像的 Image
对象。如果想调用 paste()
,但还要保持原始图像的未修改版本,就需要先复制图像,然后在副本上调用 paste()
。
案例:将脸铺满整个图片
假定要用 Zophie
的头平铺整个图像,如图所示。可以用两个 for
循环来实现这个效果。继续交互式环境的例子,输入以下代码:
# 平铺到整个页面
cat_copy_img_two = cat_img.copy()
# 获取 猫 图片的宽高
cat_img_width, cat_img_height = cat_img.size
# 获取脸图片的宽高
face_img_width, face_img_height = face_img.size
# 获取可以放置头像的宽度
for left in range(0, cat_img_width, face_img_width):
# 获取所有可以放置头像的高度
for top in range(0, cat_img_height, face_img_height):
print(left, top)
# 复制头像到指定位置
cat_copy_img_two.paste(face_img, (left, top))
cat_copy_img_two.save('pasted2.png')
这里,我们将 catIm
的高度的宽度保存在cat_img_width和cat_img_height中。我们得到了cat_img的副本,并保存在cat_copy_img_two。既然有了一个副本可以粘贴,我们就开始循环,将face_img到cat_copy_img_two。外层 for
循环的 left
变量从0开始,增量是face_img_width(即230)。内层 for
循环的 top
变量从0开始,增量是face_img_height(即215)。这些嵌套的 for
循环生成了 left
和 top
的值,将face_img图像按照网格粘贴到 Image
对象cat_copy_img_two,如图所示。为了看到嵌套循环的工作,我们打印了 left
和 top
。粘贴完成后,我们将修改后的cat_copy_img_two保存到pasted2.png。
调整图像大小
resize()方法在 Image
对象上调用,返回指定宽度和高度的一个新 Image
对象。它接受两个整数的元组作为参数,表示返回图像的新高度和宽度。
cat_img_copy = cat_img.copy()
width, height = cat_img_copy.size
# 将大小改成之前的四分之一
reset_size_img = cat_img_copy.resize((int(width / 4), int(height / 4)))
reset_size_img.save('reset_size_img.png')
# 高度增加 300 像素
reset_height_img = cat_img_copy.resize((width, height + 300))
reset_height_img.save('reset_height.png')
这里,我们将cat_img_copy.size元组中的两个值赋给变量 width
和 height
。使用 width
和 height
,而不是cat_img_copy.size[0]和cat_img_copy.size[1],让接下来的代码更易读。
第一个 resize()
调用传入int(width / 2)作为新宽度,int(height / 2)作为新高度,所以resize() 返回的 Image
对象具有原始图像的一半长度和宽度,是原始图像大小的四分之一。resize() 方法的元组参数中只允许整数,这就是为什么需要用 int()
调用对两个除以2的值取整。
这个大小调整保持了相同比例的宽度和高度。但传入resize() 的新宽度和高度不必与原始图像成比例。 svelteIm
变量保存了一个 Image
对象,宽度与原始图像相同,但高度增加了300像素,让 Zophie
显得更苗条。
请注意, resize()
方法不会在原图上修改 Image
对象,而是返回一个新的 Image
对象。
粘贴透明像素
通常透明像素像白色像素一样粘贴。如果要粘贴图像有透明像素,就将该图像作为第三个参数传入,这样就不会粘贴一个不透明的矩形。这个第三参数是“遮罩”Image对象。遮罩是一个 Image
对象,其中 alpha
值是有效的,但红、绿、蓝值将被忽略。遮罩告诉paste() 函数哪些像素应该复制,哪些应该保持透明。遮罩的高级用法超出了本书的范围,但如果你想粘贴有透明像素的图像,就再传入该 Image
对象作为第三个参数。
旋转和翻转图像
图像可以用 rotate()
方法旋转,该方法返回旋转后的新 Image
对象,并保持原始 Image
对象不变。rotate() 的参数是一个整数或浮点数,表示图像逆时针旋转的度数。
cat_img.rotate(90).save('rotated90.png')
cat_img.rotate(180).save('rotated180.png')
cat_img.rotate(270).save('rotated270.png')
注意,可以连续调用方法,对 rotate()
返回的 Image
对象直接调用 save()
。第一个 rotate()
和save() 调用得到一个逆时针旋转90度的新 Image
对象,并将旋转后的图像存入rotated90.png。第二和第三个调用做的事情一样,但旋转了180度和270度。结果如图所示。
注意,当图像旋转90度或270度时,宽度和高度会变化。如果旋转其他角度,图像的原始尺寸会保持。在 Windows
上,使用黑色的背景来填补旋转造成的缝隙,如图17-8所示。在OS X上,使用透明的像素来填补缝隙。 rotate()
方法有一个可选的 expand
关键字参数,如果设置为 True
,就会放大图像的尺寸,以适应整个旋转后的新图像。
cat_img.rotate(6).save('rotated6.png')
cat_img.rotate(6, expand=True).save('rotated6_expanded.png')
第一次调用将图像旋转6度,并存入rotate.png(参见左边的图像)。第二次调用将图像旋转6度, expand
设置为 True
,并存入rotate6_expanded.png(参见的右侧的图像)。
利用 transpose()
方法,还可以得到图像的“镜像翻转”。必须向 transpose()
方法传入Image.FLIP_LEFT_RIGHT或Image.FLIP_TOP_BOTTOM。
cat_img.transpose(Image.FLIP_LEFT_RIGHT).save('horizontal_flip.png')
cat_img.transpose(Image.FLIP_TOP_BOTTOM).save('vertical_flip.png')
像 rotate()
一样, transpose()
会创建一个新 Image
对象。这里我们传入Image.FLIP_ LEFT_RIGHT,让图像水平翻转,然后存入horizontal_flip.png。要垂直翻转图像,传入Image.FLIP_TOP_BOTTOM,并存入vertical_flip.png。
原始图像(左),水平翻转(中),垂直翻转(右)
更改单个像素
单个像素的颜色可以通过 getpixel()
和 putpixel()
方法取得和设置。它们都接受一个元组,表示像素的 x
和 y
坐标。putpixel()
方法还接受一个元组,作为该像素的颜色。这个顔色参数是四整数 RGBA
元组或三整数 RGB
元组
from PIL import Image
img = Image.new('RGBA', (100, 100))
print(img.getpixel((0, 0)))
# 遍历图片上半部分,设置为浅灰色
for x in range(100):
for y in range(50):
img.putpixel((x, y), (210, 210, 210))
from PIL import ImageColor
# 设置下半部分为深灰色
for x in range(100):
for y in range(50, 100):
img.putpixel((x, y), ImageColor.getcolor('darkgray', 'RGBA'))
print(img.getpixel((0, 0)))
print(img.getpixel((0, 50)))
img.save('putPixel.png')
先创建一个新图像,这是一个100×100的透明正方形。对一些坐标调用 getPixel()
将返回(0,0,0,0),因为图像是透明的。要给图像中的像素上色,我们可以使用嵌套的 for
循环,遍历图像上半部分的所有像素,用 putpixel()
设置每个像素的顔色。这里我们向 putpixel()
传入 RGB
元组(210,210,210),即浅灰色。
假定我们希望图像下半部分是暗灰色,但不知道深灰色的 RGB
元组。 putpixel()
方法不接受 'darkgray'
这样的标准颜色名称,所以必须使用 ImageColor.getcolor()
来获得 'darkgray'
的颜色元组。循环遍历图像的下半部分像素,向 putpixel()
传入 ImageColor.getcolor()
的返回值,你现在应该得到一个图像,上半部分是浅灰色,下半部分是深灰色,可以对一些坐标调用 getPixel()
,确认指定像素的颜色符合你的期望。最后,将图像存入 putPixel.png
。
当然,在图像上一次绘制一个像素不是很方便。如果需要绘制形状,就使用本章稍后介绍的 ImageDraw
函数。
项目:批量添加水印
假设你有一项无聊的工作,要调整数千张图片的大小,并在每张图片的角上增加一个小徽标水印。使用基本的图形程序,如 Paintbrush
或 Paint
,完成这项工作需要很长时间。像 Photoshop
这样神奇的应用程序可以批量处理,但这个软件要花几百美元。让我们写一个脚本来完成工作。
是要添加到每个图像右下角的标识:带有白色边框的黑猫图标,图像的其余部分是透明的。
总的来说,程序应该完成下面的事:
- 载入徽标图像。
- 循环遍历工作目标中的所有
.png
和.jpg
文件。 - 检查图片是否宽于或高于 300 像素。
- 如果是,将宽度或高度中较大的一个减小为 300 像素,并按比例缩小的另一维度。
- 在角上粘贴徽标图像。
- 将改变的图像存入另一个文件夹。
这意味着代码需要做到以下几点:
- 打开
catlogo.png
文件作为Image
对象。 - 循环遍历
os.listdir('.')
返回的字符串。 - 通过
size
属性取得图像的宽度和高度。 - 计算调整后图像的新高度和宽度。
- 调用
resize()
方法来调整图像大小。 - 调用
paste()
方法来粘贴徽标。 - 调用
save()
方法保存更改,使用原来的文件名。
import os
from PIL import Image
SQUARE_FIT_SIZE = 300
LOGO_FILENAME = 'catlogo.png'
logo_img = Image.open(LOGO_FILENAME)
work_dir = 'withLogo'
if not os.path.exists(work_dir):
os.makedirs(work_dir)
# TODO: 循环遍历工作目录中的所有文件
for filename in os.listdir('.'):
# 忽略不是图片的文件以及水印自己本身
if not (filename.endswith('.png') or filename.endswith('.jpg')) or filename == LOGO_FILENAME:
continue
# TODO: 读取需要添加水印的图片
# filename = 'zophie.jpg'
cat_img = Image.open(filename)
cat_img_width, cat_img_height = cat_img.size
# TODO: 调整水印大小。
min_side = cat_img_width if cat_img_width > cat_img_height else cat_img_height
logo_img_copy = logo_img.copy()
logo_img_copy = logo_img_copy.resize((int(min_side / 4), int(min_side / 4)))
# TODO: 添加水印。
new_logo_width, new_logo_height = logo_img_copy.size
cat_img.paste(logo_img_copy, (cat_img_width - new_logo_width, cat_img_height - new_logo_height), logo_img_copy)
# TODO: 保存修改
cat_img.save(os.path.join(work_dir, filename))
在图像上绘画
如果需要在图像上画线、矩形、圆形或其他简单形状,就用 Pillow
的 ImageDraw
模块。
>>> from PIL import Image, ImageDraw
>>> im = Image.new('RGBA', (200, 200), 'white')
>>> draw = ImageDraw.Draw(im)
首先,我们导入 Image
和 ImageDraw
。然后,创建一个新的图像,在这个例子中,是200×200的白色图像,将这个 Image
对象保存在 Im
中。我们将该 Image
对象传入ImageDraw.Draw() 函数,得到一个 ImageDraw
对象。这个对象有一些方法,可以在 Image
对象上绘制形状和文字。将 ImageDraw
对象保存在变量 draw
中,这样就能在接下来的例子中方便地使用它。
绘制形状
下面的 ImageDraw
方法在图像上绘制各种形状。这些方法的 fill
和 outline
参数是可选的,如果未指定,默认为白色。
点
point(xy, fill)方法绘制单个像素。 xy
参数表示要画的点的列表。该列表可以是 x
和 y
坐标的元组的列表,例如 [(x, y), (x, y), ...]
,或是没有元组的 x
和 y
坐标的列表,例如 [x1, y1, x2, y2, ...]
。fill 参数是点的颜色,要么是一个 RGBA
元组,要么是颜色名称的字符串,如 'red'
。fill
参数是可选的。
线
line(xy, fill, width)方法绘制一条线或一系列的线。 xy
要么是一个元组的列表,例如[(x, y), (x, y), ...],要么是一个整数列表,例如[x1, y1, x2, y2, ...]。每个点都是正在绘制的线上的一个连接点。可选的 fill
参数是线的颜色,是一个 RGBA
元组或颜色名称。可选的 width
参数是线的宽度,如果未指定,缺省值为1。
矩形
rectangle(xy, fill, outline)方法绘制一个矩形。 xy
参数是一个矩形元组,形式为(left, top, right, bottom) 。 left
和 top
值指定了矩形左上角的 x
和 y
坐标, right
和 bottom
指定了矩形的右下角。可选的 fill
参数是颜色,将填充该矩形的内部。可选的 outline
参数是矩形轮廓的颜色。
椭圆
ellipse(xy, fill, outline)方法绘制一个椭圆。如果椭圆的宽度和高度一样,该方法将绘制一个圆。 xy
参数是一个矩形元组(left, top, right, bottom),它表示正好包含该椭圆的矩形。可选的 fill
参数是椭圆内的颜色,可选的 outline
参数是椭圆轮廓的颜色。
多边形
polygon(xy, fill, outline)方法绘制任意的多边形。 xy
参数是一个元组列表,例如[(x, y), (x, y), ...],或者是一个整数列表,例如[x1, y1, x2, y2, ...],表示多边形边的连接点。最后一对坐标将自动连接到第一对坐标。可选的 fill
参数是多边形内部的颜色,可选的 outline
参数是多边形轮廓的颜色。
绘制示例
from PIL import Image, ImageDraw
im = Image.new('RGBA', (200, 200), 'white')
draw = ImageDraw.Draw(im)
# 绘制线条
draw.line([(0, 0), (199, 0), (199, 199), (0, 199), (0, 0)], fill='black')
# 绘制矩形
draw.rectangle((20, 30, 60, 60), fill='blue')
# 绘制椭圆
draw.ellipse((120, 30, 160, 60), fill='red')
# 绘制多边形
draw.polygon(((57, 87), (79, 62), (94, 85), (120, 90), (103, 113)), fill='brown')
for i in range(100, 200, 10):
draw.line([(i, 0), (200, i - 100)], fill='green')
# im.save('drawing.png')
im.show()
为200×200的白色图像生成 Image
对象后,将它传入ImageDraw.Draw() ,获得 ImageDraw
对象,将它保存在 draw
中,可以对 draw
调用绘图方法。这里,我们在图像边缘画上窄的黑色轮廓;一个蓝色的矩形,左上角在(20, 30),右下角在(60, 60);一个红色的椭圆,由(120, 30)到(160, 60)的矩形来定义;一个棕色的多边形,有五个顶点,以及一些绿线的图案,用 for
循环绘制。得到的 drawing.png
文件如图所示。
ImageDraw对象还有另外几个绘制形状的方法。完整的文档在http://pillow.readthedocs.org/en/latest/reference/ImageDraw.html 。
绘制文本
ImageDraw对象还有 text()
方法,用于在图像上绘制文本。 text()
方法有4个参数:xy、 text
、 fill
和 font
。
- xy参数是两个整数的元组,指定文本区域的左上角。
- text参数是想写入的文本字符串。
- 可选参数
fill
是文本的颜色。 - 可选参数
font
是一个ImageFont
对象,用于设置文本的字体和大小。下一节中更详细地介绍了这个参数。
因为通常很难预先知道一块文本在给定的字体下的大小,所以 ImageDraw
模块也提供了textsize() 方法。它的第一个参数是要测量的文本字符串,第二个参数是可选的 ImageFont
对象。textsize() 方法返回一个两整数元组,表示如果以指定的字体写入图像,文本的宽度和高度。可以利用这个宽度和高度,帮助你精确计算文本放在图像上的位置。
text()的前三个参数非常简单。在用 text()
向图像绘制文本之前,让我们来看看可选的第四个参数,即 ImageFont
对象。
text()和 textsize()
都接受可选的 ImageFont
对象,作为最后一个参数。要创建这种对象,先执行以下命令:
>>> from PIL import ImageFont
既然已经导入 Pillow
的 ImageFont
模块,就可以调用ImageFont.truetype() 函数,它有两个参数。第一个参数是字符串,表示字体的 TrueType
文件,这是硬盘上实际的字体文件。 TrueType
字体文件具有 .TTF
文件扩展名,通常可以在以下文件夹中找到:
- 在
Windows
上:C:\Windows\Fonts。 - 在OS X上:/Library/Fonts and /System/Library/Fonts。
- 在
Linux
上:/usr/share/fonts/truetype。
实际上并不需要输入这些路径作为 TrueType
字体文件的字符串的一部分,因为 Python
知道自动在这些目录中搜索字体。如果无法找到指定的字体, Python
会显示错误。
ImageFont.truetype()的第二个参数是一个整数,表示字体大小的点数(而不是像素)。请记住, Pillow
创建的 PNG
图像默认是每英寸72像素,一点是1/72英寸。
import os
from PIL import Image, ImageDraw, ImageFont
# 创建一张新的图片用于绘制
im = Image.new('RGBA', (200, 200), 'white')
draw = ImageDraw.Draw(im)
# 直接使用默认字体绘制内容
draw.text((20, 150), 'Hello', fill='purple')
# 选择使用的字体文件,设置字体的大小
fontsFolder = 'FONT_FOLDER' # e.g. ‘/Library/Fonts’
arialFont = ImageFont.truetype(os.path.join(fontsFolder, 'arial.ttf'), 32)
draw.text((100, 150), 'Howdy', fill='gray', font=arialFont)
# im.save('text.png')
im.show()
导入 Image
、 ImageDraw
、 ImageFont
和 os
后,我们生成一个 Image
对象,是新的200×200白色图像,并通过这个 Image
对象得到一个 ImageDraw
对象。我们使用text() 在(20, 150)以紫色绘制 Hello
。在这次 text()
调用中,我们没有传入可选的第四个参数,所以这段文本的字体和大小没有定制。
要设置字体和大小,我们首先将文件夹名称(如/Library/Fonts)保存在 fontsFolder
中。然后调用 ImageFont.truetype()
,传入我们想要的字体的 .TTF
文件,之后是表示字体大小的整数。将 ImageFont.truetype()
返回的 Font
对象保存在 arialFont
这样的变量中,然后将该变量传入 text()
,作为最后的关键字参数。行的 text()
调用绘制了 Howdy
,采用灰色、32点 Arial
字体。
得到的 text.png
文件如图所示。