ImageDraw Module

Note

All source images in this document are derived from https://www.pexels.com (CC0 License).

class Draw

doc

https://pillow.readthedocs.io/en/latest/reference/ImageDraw.html, http://effbot.org/imagingbook/imagedraw.htm

Note

The graphics interface uses the same coordinate system as PIL itself, with (0, 0) in the upper left corner.

Warning

Almost all of methods takes xy argument for sequence of points, and official documents says like this:

xy - Sequence of either 2-tuples like [(x, y), (x, y), ...] or numeric values like [x, y, x, y, ...].

But it seems passing [x, y, x, y, ...] does not work, at least PIL 1.1.7 of Pillow 2.9.0 on CPython 2.7. (Furthermore, note that this is NOT [[x, y], [x, y], ...]. Passing [[x, y], [x, y], ...] causes exception.)

arc, chord, ellipse, pieslice, rectangle

import math
from PIL import Image, ImageDraw

#
w, h = 120, 90
bbox = [(10, 10), (w - 10, h - 10)]

# ----------------------------------------------------------
# rectangle
img = Image.new("RGB", (w, h), "#f9f9f9")  # create new Image
dctx = ImageDraw.Draw(img)  # create drawing context
dctx.rectangle(bbox, fill="#ddddff", outline="blue")
del dctx  # destroy drawing context
img.save("result/ImageDraw_rectangle_01.jpg")

# ----------------------------------------------------------
# ellipse
img = Image.new("RGB", (w, h), "#f9f9f9")  # create new Image
dctx = ImageDraw.Draw(img)  # create drawing context
dctx.ellipse(bbox, fill="#ddddff", outline="blue")
del dctx  # destroy drawing context
img.save("result/ImageDraw_ellipse_01.jpg")

# ----------------------------------------------------------
# arc (end=130)
img = Image.new("RGB", (w, h), "#f9f9f9")  # create new Image
dctx = ImageDraw.Draw(img)  # create drawing context
dctx.arc(bbox, start=20, end=130, fill="blue")
del dctx  # destroy drawing context
img.save("result/ImageDraw_arc_01.jpg")

# chord (end=130)
img = Image.new("RGB", (w, h), "#f9f9f9")  # create new Image
dctx = ImageDraw.Draw(img)  # create drawing context
dctx.chord(bbox, start=20, end=130, fill="#ddddff", outline="blue")
del dctx  # destroy drawing context
img.save("result/ImageDraw_chord_01.jpg")

# pieslice (end=130)
img = Image.new("RGB", (w, h), "#f9f9f9")  # create new Image
dctx = ImageDraw.Draw(img)  # create drawing context
dctx.pieslice(bbox, start=20, end=130, fill="#ddddff", outline="blue")
del dctx  # destroy drawing context
img.save("result/ImageDraw_pieslice_01.jpg")

# ----------------------------------------------------------
# arc (end=300)
img = Image.new("RGB", (w, h), "#f9f9f9")  # create new Image
dctx = ImageDraw.Draw(img)  # create drawing context
dctx.arc(bbox, start=20, end=300, fill="blue")
del dctx  # destroy drawing context
img.save("result/ImageDraw_arc_02.jpg")

# chord (end=300)
img = Image.new("RGB", (w, h), "#f9f9f9")  # create new Image
dctx = ImageDraw.Draw(img)  # create drawing context
dctx.chord(bbox, start=20, end=300, fill="#ddddff", outline="blue")
del dctx  # destroy drawing context
img.save("result/ImageDraw_chord_02.jpg")

# pieslice (end=300)
img = Image.new("RGB", (w, h), "#f9f9f9")  # create new Image
dctx = ImageDraw.Draw(img)  # create drawing context
dctx.pieslice(bbox, start=20, end=300, fill="#ddddff", outline="blue")
del dctx  # destroy drawing context
img.save("result/ImageDraw_pieslice_02.jpg")
ImageDraw_arc_etc.res00
rectangle

ImageDraw_arc_etc.res02
arc(start=20, end=130)

ImageDraw_arc_etc.res03
chord(start=20, end=130)

ImageDraw_arc_etc.res04
pieslice(start=20, end=130)

ImageDraw_arc_etc.res01
ellipse

ImageDraw_arc_etc.res05
arc(start=20, end=300)

ImageDraw_arc_etc.res06
chord(start=20, end=300)

ImageDraw_arc_etc.res07
pieslice(start=20, end=300)

bitmap

srcimg07.jpg srcimg07

from PIL import Image, ImageDraw
from PIL import ImageColor

img = Image.open("data/srcimg07.jpg")  # load base image
dctx = ImageDraw.Draw(img)  # create drawing context
bmsz = (img.width // 16 - 10, img.height // 16 - 10)
#
# NOTE: ImageColor.colormap is undocumented attribute.
colors = list(ImageColor.colormap.keys())
for y in range(16):
    for x in range(16):
        bm = Image.new("L", bmsz)
        dctx_inner = ImageDraw.Draw(bm)
        dctx_inner.ellipse(
            [(0, 0), bm.size],
            fill=y * 16 + x  # (y * 16 + x) varies in range(0, 256)
            )
        del dctx_inner

        pos = [
            ((bmsz[0] + 10) * x + 10,
             (bmsz[1] + 10) * y + 10)]
        dctx.bitmap(
            pos,
            # pixel values of bm is used as mask to fill.
            bm,
            fill=colors[(y * 16 + x) % len(colors)])
#
del dctx  # destroy drawing context
img.save("result/ImageDraw_bitmap_01.png")

ImageDraw_bitmap.res01

line, polygon

import math
from PIL import Image, ImageDraw
from PIL import ImagePath  # for calculating bounding box

#
ptc = 7
xy = [
    ((math.cos(th) + 1) * 120,
     (math.sin(th) + 1) * 90)
    for th in [i * (2 * math.pi) / ptc for i in range(ptc)]
    ]  # not closed (i.e., end != start)

bbox = ImagePath.Path(xy).getbbox()  # calculate bounding box
size = list(map(int, map(math.ceil, bbox[2:])))

# demo 1 - polygon with outline
img = Image.new("RGB", size, "#f9f9f9")  # create new Image
dctx = ImageDraw.Draw(img)  # create drawing context
dctx.polygon(xy, fill="#eeeeff", outline="blue")  # draw polygon with outline
del dctx  # destroy drawing context
img.save("result/ImageDraw_polygon_line_01.jpg")

# demo 2 - polygon and line with the same xy
img = Image.new("RGB", size, "#f9f9f9")  # create new Image
dctx = ImageDraw.Draw(img)  # create drawing context
dctx.polygon(xy, fill="#eeeeff")  # draw polygon without outline
dctx.line(xy, fill="blue", width=5)  # draw line
del dctx  # destroy drawing context
img.save("result/ImageDraw_polygon_line_02.jpg")
ImageDraw_polygon_line.res01
polygon with outline

ImageDraw_polygon_line.res02
polygon and line with the same xy

point

from PIL import Image, ImageDraw

# drawing single point on large canvas is not visible for human's eye,
# so this demo use very small canvas and later resize it.

img = Image.new("RGB", (16, 16), "#f9f9f9")  # create new Image
dctx = ImageDraw.Draw(img)  # create drawing context
dctx.point([(2, 3)], fill="blue")  # draw points
dctx.point([(5, 8)], fill="red")  # draw points
del dctx  # destroy drawing context
img.resize((128, 128)).save("result/ImageDraw_point_01.jpg")

draw single point ImageDraw_point.res01

import math
from PIL import Image, ImageDraw
from PIL import ImagePath  # for calculating bounding box

#
xy = [
    (i, (math.sin(i / 64. * math.pi) + 1) * 128)
    for i in range(256)
    ]

bbox = ImagePath.Path(xy).getbbox()  # calculate bounding box
size = list(map(int, map(math.ceil, bbox[2:])))

img = Image.new("RGB", size, "#f9f9f9")  # create new Image
dctx = ImageDraw.Draw(img)  # create drawing context
dctx.point(xy, fill="blue")  # draw points
del dctx  # destroy drawing context
img.save("result/ImageDraw_point_02.jpg")

draw points ImageDraw_point.res02

text, textsize

srcimg12.jpg srcimg12

# -*- coding: utf-8 -*-
from PIL import Image, ImageDraw, ImageFont

# get a font
#   This example is for Windows (7, etc.).
#   If you use Unix-like system, fonts are found at
#   for example "/usr/share/fonts".
#fnt = ImageFont.truetype('c:/Windows/Fonts/msmincho.ttc', 30)
fnt = ImageFont.truetype('msmincho.ttc', 30)

img = Image.open("data/srcimg12.jpg")  # open base image
dctx = ImageDraw.Draw(img)  # create drawing context

# text to draw
txt = u"東京タワーと三縁山増上寺"  # Tokyo tower and San'en-zan Zōjō-ji

# calculate text size
txtsz = dctx.textsize(txt, fnt)

# draw text
dctx.text(
    # draw text at near (right, top)
    (img.width - txtsz[0] - 20, 20),
    txt,
    font=fnt,
    fill="#eeeeff"
    )

del dctx  # destroy drawing context

img.save("result/ImageDraw_text_01.jpg")

ImageDraw_text.res01

# -*- coding: utf-8 -*-
# simple banner.
#
from __future__ import unicode_literals
from PIL import Image, ImageDraw, ImageFont

fnt = ImageFont.truetype('CENTURY.TTF', 64)
txt = "Readability counts."
img = Image.new("L", (1, 1))  # temp to calculate size
draw = ImageDraw.Draw(img)
img = img.resize(draw.textsize(txt, fnt))  # re-create
draw = ImageDraw.Draw(img)
draw.text((0, 0), txt, font=fnt, fill=255)
img = img.rotate(-90, expand=True)
# values varies 0 to 255 => 0 or 1 => mapping to char => join
_TAB = ["  ", "##"]
_NLT = ["\n", ""]
print("".join([
            (_TAB[v > 0] + _NLT[bool((i + 1) % img.width)])
            for i, v in enumerate(img.getdata())]))

-> result

# -*- coding: utf-8 -*-
# simple "text to xpm" converter
#
from __future__ import unicode_literals

import sys
from PIL import Image, ImageDraw, ImageFont


if __name__ == '__main__':
    fnt = ImageFont.truetype('cour.ttf', 24)
    txt = sys.argv[1]
    name = sys.argv[1].lower()  # FIXME: more safely
    img = Image.new("L", (1, 1))  # temp to calculate size
    draw = ImageDraw.Draw(img)
    img = img.resize(draw.textsize(txt, fnt))  # re-create
    draw = ImageDraw.Draw(img)
    draw.text((0, 0), txt, font=fnt, fill=255)
    # values varies 0 to 255 => 0 to 2 => mapping to char => join
    _PAL = [(".", "#ffffff"), ("o", "#7f7f7f"), ("#", "#000000")]
    _NLT = ["\n", ""]
    palette = "\n".join(['"{} c {}",'.format(ch, cl) for ch, cl in _PAL])
    databody = "".join([
        (_PAL[int(v / 127)][0] + _NLT[bool((i + 1) % img.width)])
        for i, v in enumerate(img.getdata())]).strip().split("\n")
    databody = "\n".join(['"{}",'.format(d) for d in databody])
    print("""\
/* XPM */
static char * {}_xpm[] = {{
"{:d} {:d} {:d} {:d}",
{}
{}
}};
""".format(name, img.width, img.height, len(_PAL), 1, palette, databody))

-> result

# -*- coding: utf-8 -*-
from PIL import Image, ImageDraw, ImageFont

# get a font
#   This example is for Windows (7, etc.).
#   If you use Unix-like system, fonts are found at
#   for example "/usr/share/fonts".
#fnt = ImageFont.truetype('c:/Windows/Fonts/msmincho.ttc', 30)
fnt = ImageFont.truetype('msmincho.ttc', 30)

img = Image.open("data/srcimg12.jpg")  # open base image

txt = u"東京タワーと三縁山増上寺"  # Tokyo tower and San'en-zan Zōjō-ji
tmpdctx = ImageDraw.Draw(img)  # create drawing context
txtsz = tmpdctx.textsize(txt, fnt)  # calculate text size
del tmpdctx

# create image for blending
osd = Image.new("RGB", (txtsz[0] + 10, txtsz[1] + 10), "skyblue")
dctx = ImageDraw.Draw(osd)  # create drawing context
dctx.text((5, 5), txt, font=fnt, fill="black")  # draw text to osd
del dctx  # destroy drawing context

# blend osd image
img.paste(
    osd,
    box=(20, 420, osd.size[0] + 20, osd.size[1] + 420),
    mask=Image.new("L", osd.size, 192))

img.save("result/ImageDraw_text_03.jpg")

ImageDraw_text.res03

multiline_text, multiline_textsize

srcimg12.jpg srcimg12

# -*- coding: utf-8 -*-
from PIL import Image, ImageDraw, ImageFont

# get a font
#   This example is for Windows (7, etc.).
#   If you use Unix-like system, fonts are found at
#   for example "/usr/share/fonts".
#fnt = ImageFont.truetype('c:/Windows/Fonts/msmincho.ttc', 30)
fnt = ImageFont.truetype('msmincho.ttc', 30)

img = Image.open("data/srcimg12.jpg")  # open base image
dctx = ImageDraw.Draw(img)  # create drawing context

# multiline text to draw
txt = u"""\
東京タワーと三縁山増上寺
(Tokyo tower and San'en-zan Zōjō-ji)"""

# calculate text size
spacing = 2
txtsz = dctx.multiline_textsize(txt, fnt, spacing=spacing)

# draw text
dctx.multiline_text(
    # draw text at near (left, bottom)
    (20, img.height - txtsz[1] - 20),
    txt,
    font=fnt,
    fill="#eeeeff",
    spacing=spacing,
    align="center"
    )

del dctx  # destroy drawing context

img.save("result/ImageDraw_multiline_text_01.jpg")

ImageDraw_multiline_text.res01

# -*- coding: utf-8 -*-
from PIL import Image, ImageDraw, ImageFont
from PIL import ImageFilter

# get a font
fnt = ImageFont.truetype('msmincho.ttc', 300)

img = Image.open("data/srcimg12.jpg")  # open base image

# text to draw
txt = u"""東京タワーと
三縁山増上寺"""  # Tokyo tower and San'en-zan Zōjō-ji

# calculate text size
dctx = ImageDraw.Draw(img)  # create drawing context (of img)
txtsz = dctx.multiline_textsize(txt, fnt)
del dctx

# draw text to mask
mask = Image.new("L", (txtsz[0], txtsz[1]), 64)
dctx = ImageDraw.Draw(mask)  # create drawing context (of mask)
dctx.multiline_text(
    (0, 0),
    txt,
    font=fnt,
    fill=255  # full opacity
    )

del dctx  # destroy drawing context

# add some effect
for i in range(10):
    mask = mask.filter(ImageFilter.BLUR)

# putmask to base image
img.putalpha(mask.resize(img.size))

img.save("result/ImageDraw_multiline_text_02.png")

ImageDraw_multiline_text.res02