Skip to content

操作图像

如果你有一台数码相机,或者只是将照片从手机上传到 Facebook,你可能随时都会偶然遇到数字图像文件。你可能知道如何使用基本的图形软件,如 Microsoft Paint 或 Paintbrush,甚至更高级的应用程序,如 Adobe Photoshop。但是,如果需要编辑大量的图像,手工编辑可能是漫长、枯燥的工作。

请用 PythonPillow 是一个第三方 Python 模块,用于处理图像文件。该模块包含一些函数,可以很容易地裁剪图像、调整图像大小,以及编辑图像的内容。可以像 Microsoft Paint 或 Adobe Photoshop 一样处理图像,有了这种能力,Python 可以轻松地自动编辑成千上万的图像。

Pillow 操作图像

处理 Image 数据类型

Image对象有一些有用的属性,提供了加载的图像文件的基本信息:它的宽度和高度、文件名和图像格式(如 JPEGGIF 或PNG)。

python
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() 也支持传入标准颜色名称的字符串。
python
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 元组。

要了解该函数的工作方式,就在交互式环境中输入以下代码:

python
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 元组

图像像素用 xy 坐标指定,分别指定像素在图像中的水平和垂直位置。原点是位于图像左上角的像素,用符号(0,0)指定。第一个0表示 x 坐标,它以原点处为 0,从左至右增加。第二个 0 表示 y 坐标,它以原点处为 0,从上至下增加。这值得重复一下:y 坐标向下走增加,你可能还记得数学课上使用的 y 坐标,与此相反。下图坐标系统的工作方式。

27×26 的图像的 xy 坐标,某种古老的数据存储装置

CMYK 和 RGB 着色

上小学时你学过,混合红、黄、蓝三种顔料可以得到其他颜色。例如,可以混合蓝色和黄色,得到绿色顔料。这就是所谓的减色模型,它适用于染料、油墨和颜料。这就是为什么彩色打印机有的 CMYK 墨盒:青色(蓝色)、品红色(红色)、黄色和黑色墨水可以混合在一起,形成任何颜色。

然而,光的物理使用所谓的加色模型。如果组合光(例如由计算机屏幕发出的光),红、绿和蓝光可以组合形成其他颜色。这就是为什么在计算机程序中使用 RGB 值表示颜色。

许多 Pillow 函数和方法需要一个矩形元组参数。这意味着 Pillow 需要一个四个整坐标的元组,表示图像中的一个矩形区域。四个整数按顺序分别是:

  • 左:该矩形的最左边的 x 坐标。
  • 顶:该矩形的顶边的 y 坐标。
  • 右:该矩形的最右边右面一个像素的 x 坐标。此整数必须比左边整数大。
  • 底:该矩形的底边下面一个像素的 y 坐标。此整数必须比顶边整数大。

注意,该矩形包括左和顶坐标,直到但不包括右和底坐标。例如,矩形元组(3, 1, 9, 6)表示图中黑色矩形的所有像素。

既然知道了 Pillow 中颜色和坐标的工作方式,就让我们用 Pillow 来处理图像。

将图像文件 Zophie.png 放在当前工作目录中,你就可以将 Zophie 的图像加载到 Python 中,像这样:

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 对象上的方法调用来完成。

为了让本章的例子更简短,我假定你已导入了 PillowImage 模块,并将 Zophie 的图像保存在变量 catIm 中。要确保 zophie.png 文件在当前工作目录中,让 Image.open() 函数能找到它。否则,必须在 Image.open() 的字符串参数中指定完整的绝对路径。

裁剪图片

裁剪图像是指在图像内选择一个矩形区域,并删除矩形之外的一切。Image 对象的 crop() 方法接受一个矩形元组,返回一个 Image 对象,表示裁剪后的图像。裁剪不是在原图上发生的,也就是说,原始的 Image 对象原封不动,crop() 方法返回一个新的 Image 对象。请记住,矩形元组(这里就是要裁剪的区域)包括左列和顶行的像素,直至但不包括右列和底行的像素。

在交互式环境中输入以下代码:

python
# 剪裁图片
cat_img = Image.open('zophie.png')
cropped_img = cat_img.crop((335, 345, 565, 560))
cropped_img.save('cropped_cat_img.png')

这得到一个新的 Image 对象,是剪裁后的图像,保存在 croppedIm 中,然后调用 croppedImsave() ,将裁剪后的图像存入 cropped.png。新文件 cropped.png 从原始图像创建。

新图像只有原始图像剪裁后的部分

复制和粘贴图像到其他图像

copy()方法返回一个新的 Image 对象,它和原来的 Image 对象具有一样的图像。如果需要修改图像,同时也希望保持原有的版本不变,这非常有用。

python
# 复制图片
cat_copy_img = cat_img.copy()

catIm和 catCopyIm 变量包含了两个独立的 Image 对象,它们的图像相同。既然 catCopyIm 中保存了一个 Image 对象,你可以随意修改 catCopyIm ,将它存入一个新的文件名,而 zophie.png 没有改变。例如,让我们尝试用 paste() 方法修改 catCopyIm

paste()方法在 Image 对象调用,将另一个图像粘贴在它上面。我们继续交互式环境的例子,将一个较小的图像粘贴到 catCopyIm

python
# 粘贴图片
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 粘贴到 catCopyImpaste() 方法有两个参数:一个“源” Image 对象,一个包含 xy 坐标的元组,指明源 Image 对象粘贴到主 Image 对象时左上角的位置。这里,我们在 catCopyIm 上两次调用 paste() ,第一次传入(0, 0),第二次传入(400, 500)。这将 faceIm 两次粘贴到 catCopyIm:一次 faceIm 的左上角在(0, 0),一次 faceIm 的左上角在(400, 500)。最后,我们将修改后的 catCopyIm 存入 pasted.pngpasted.png 如图所示。

注意

尽管名称是 copy() 和 paste(),但 Pillow 中的方法不使用计算机的剪贴板。

请注意,paste() 方法在原图上修改它的 Image 对象,它不会返回粘贴后图像的 Image 对象。如果想调用 paste() ,但还要保持原始图像的未修改版本,就需要先复制图像,然后在副本上调用 paste()

案例:将脸铺满整个图片

假定要用 Zophie 的头平铺整个图像,如图所示。可以用两个 for 循环来实现这个效果。继续交互式环境的例子,输入以下代码:

python
# 平铺到整个页面
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 循环生成了 lefttop 的值,将face_img图像按照网格粘贴到 Image 对象cat_copy_img_two,如图所示。为了看到嵌套循环的工作,我们打印了 lefttop 。粘贴完成后,我们将修改后的cat_copy_img_two保存到pasted2.png。

调整图像大小

resize()方法在 Image 对象上调用,返回指定宽度和高度的一个新 Image 对象。它接受两个整数的元组作为参数,表示返回图像的新高度和宽度。

python
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元组中的两个值赋给变量 widthheight 。使用 widthheight ,而不是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。

python
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() 方法取得和设置。它们都接受一个元组,表示像素的 xy 坐标。putpixel() 方法还接受一个元组,作为该像素的颜色。这个顔色参数是四整数 RGBA 元组或三整数 RGB 元组

python
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

..16-0403yds排版16-0403yds排版LinksLinks17-10.tif

当然,在图像上一次绘制一个像素不是很方便。如果需要绘制形状,就使用本章稍后介绍的 ImageDraw 函数。

项目:批量添加水印

假设你有一项无聊的工作,要调整数千张图片的大小,并在每张图片的角上增加一个小徽标水印。使用基本的图形程序,如 PaintbrushPaint ,完成这项工作需要很长时间。像 Photoshop 这样神奇的应用程序可以批量处理,但这个软件要花几百美元。让我们写一个脚本来完成工作。

是要添加到每个图像右下角的标识:带有白色边框的黑猫图标,图像的其余部分是透明的。

总的来说,程序应该完成下面的事:

  • 载入徽标图像。
  • 循环遍历工作目标中的所有 .png.jpg 文件。
  • 检查图片是否宽于或高于 300 像素。
  • 如果是,将宽度或高度中较大的一个减小为 300 像素,并按比例缩小的另一维度。
  • 在角上粘贴徽标图像。
  • 将改变的图像存入另一个文件夹。

这意味着代码需要做到以下几点:

  • 打开 catlogo.png 文件作为 Image 对象。
  • 循环遍历 os.listdir('.') 返回的字符串。
  • 通过 size 属性取得图像的宽度和高度。
  • 计算调整后图像的新高度和宽度。
  • 调用 resize() 方法来调整图像大小。
  • 调用 paste() 方法来粘贴徽标。
  • 调用 save() 方法保存更改,使用原来的文件名。
python
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))

在图像上绘画

如果需要在图像上画线、矩形、圆形或其他简单形状,就用 PillowImageDraw 模块。

>>> from PIL import Image, ImageDraw
>>> im = Image.new('RGBA', (200, 200), 'white')
>>> draw = ImageDraw.Draw(im)

首先,我们导入 ImageImageDraw 。然后,创建一个新的图像,在这个例子中,是200×200的白色图像,将这个 Image 对象保存在 Im 中。我们将该 Image 对象传入ImageDraw.Draw() 函数,得到一个 ImageDraw 对象。这个对象有一些方法,可以在 Image 对象上绘制形状和文字。将 ImageDraw 对象保存在变量 draw 中,这样就能在接下来的例子中方便地使用它。

绘制形状

下面的 ImageDraw 方法在图像上绘制各种形状。这些方法的 filloutline 参数是可选的,如果未指定,默认为白色。

point(xy, fill)方法绘制单个像素。 xy 参数表示要画的点的列表。该列表可以是 xy 坐标的元组的列表,例如 [(x, y), (x, y), ...],或是没有元组的 xy 坐标的列表,例如 [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) 。 lefttop 值指定了矩形左上角的 xy 坐标, rightbottom 指定了矩形的右下角。可选的 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 参数是多边形轮廓的颜色。

绘制示例

python
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 文件如图所示。

..16-0403yds排版16-0403yds排版LinksLinks17-14.tif

ImageDraw对象还有另外几个绘制形状的方法。完整的文档在http://pillow.readthedocs.org/en/latest/reference/ImageDraw.html

绘制文本

ImageDraw对象还有 text() 方法,用于在图像上绘制文本。 text() 方法有4个参数:xy、 textfillfont

  • xy参数是两个整数的元组,指定文本区域的左上角。
  • text参数是想写入的文本字符串。
  • 可选参数 fill 是文本的颜色。
  • 可选参数 font 是一个 ImageFont 对象,用于设置文本的字体和大小。下一节中更详细地介绍了这个参数。

因为通常很难预先知道一块文本在给定的字体下的大小,所以 ImageDraw 模块也提供了textsize() 方法。它的第一个参数是要测量的文本字符串,第二个参数是可选的 ImageFont 对象。textsize() 方法返回一个两整数元组,表示如果以指定的字体写入图像,文本的宽度和高度。可以利用这个宽度和高度,帮助你精确计算文本放在图像上的位置。

text()的前三个参数非常简单。在用 text() 向图像绘制文本之前,让我们来看看可选的第四个参数,即 ImageFont 对象。

text()和 textsize() 都接受可选的 ImageFont 对象,作为最后一个参数。要创建这种对象,先执行以下命令:

>>> from PIL import ImageFont

既然已经导入 PillowImageFont 模块,就可以调用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()

导入 ImageImageDrawImageFontos 后,我们生成一个 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 文件如图所示。

..16-0403yds排版16-0403yds排版LinksLinks17-15.tif