Image Module - class Image

Note

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

Methods

convert

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.convert, http://effbot.org/imagingbook/image.htm#tag-Image.Image.convert

convert(mode)

srcimg03.jpg srcimg03

from PIL import Image

img = Image.open('data/srcimg03.jpg')
img.convert("L").save(
    "result/im_convert_m_L_01.jpg")
img.convert("1").save(
    "result/im_convert_m_1_01.jpg")

im_convert_m_L_01.jpg im.convert_m.res1

im_convert_m_1_01.jpg im.convert_m.res2

from PIL import Image

# create RGBA image
img = Image.open('data/srcimg03.jpg')
r, g, b = img.split()
a = Image.new("L", r.size, color=127)
withalpha = Image.merge("RGBA", (r, b, g, a))
withalpha.save("result/im_convert_m_in_01.png")

# convert from RGBA to RGB
withalpha.convert("RGB").save(
    "result/im_convert_m_RGB_01.jpg")

im_convert_m_in_01.png im.convert_m.res3

im_convert_m_RGB_01.jpg im.convert_m.res4

convert(“P”, **options)

srcimg04.jpg srcimg04

from PIL import Image

img = Image.open('data/srcimg04.jpg')
img = img.convert(
    "P",
    dither=Image.FLOYDSTEINBERG,  # default
    palette=Image.WEB  # default
    #, colors=216  # standard 216-color "web palette"
    )
img.save(
    "result/im_convert_P_01.png")

im.convert_P.res1

from PIL import Image

img = Image.open('data/srcimg04.jpg')
img = img.convert(
    "P",
    dither=Image.NONE,  # disable dithering
    palette=Image.WEB  # default
    #, colors=216  # standard 216-color "web palette"
    )
img.save(
    "result/im_convert_P_02.png")

im.convert_P.res2

from PIL import Image

img = Image.open('data/srcimg04.jpg')
# When palette is ADAPTIVE, the parameter dither makes no sense (ignored).
img = img.convert(
    "P",
    palette=Image.ADAPTIVE,  # an optimized palette
    colors=32
    )
img.save(
    "result/im_convert_P_03.png")

im.convert_P.res3

convert(mode, matrix)

srcimg03.jpg srcimg03

from PIL import Image

img = Image.open('data/srcimg03.jpg')
# rgb2xyz
mat = (
    0.412453, 0.357580, 0.180423, 0,
    0.212671, 0.715160, 0.072169, 0,
    0.019334, 0.119193, 0.950227, 0 )
#
img.convert("RGB", mat).save(
    "result/im_convert_mm_01.jpg")

im.convert_mm.res1

copy

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.copy, http://effbot.org/imagingbook/image.htm#tag-Image.Image.copy

>>> from PIL import Image
>>>
>>> srcimg = Image.new("L", (2, 2), 128)
>>> cloned = srcimg.copy()
>>> cloned.putdata((0, 64, 128, 192))
>>>
>>> srcimg.tobytes("raw")  # not changed
b'\x80\x80\x80\x80'
>>> cloned.tobytes("raw")
b'\x00@\x80\xc0'

crop

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.crop, http://effbot.org/imagingbook/image.htm#tag-Image.Image.crop

srcimg11.png srcimg11

from PIL import Image

img = Image.open('data/srcimg11.png')
bbox = (
    img.size[0] // 4,  # left
    img.size[1] // 4,  # top
    img.size[0] // 4 * 3,  # right
    img.size[1] // 4 * 3   # bottom
    )
img.crop(bbox).save("result/im_crop_01.jpg")

im.crop.res1

draft

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.draft, http://effbot.org/imagingbook/image.htm#tag-Image.Image.draft

The official documents says: “For example, you can use this method to convert a color JPEG to greyscale while loading it”:

>>> from PIL import Image
>>>
>>> img = Image.open("data/srcimg01.jpg")
>>> img
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=670x445 at 0x144A048>
>>> img.decoderconfig, img.mode, img.size, img.tile
((), 'RGB', (670, 445), [('jpeg', (0, 0, 670, 445), 0, ('RGB', ''))])
>>> img.draft("L", (img.width // 4, img.height // 4))
<PIL.JpegImagePlugin.JpegImageFile image mode=L size=168x112 at 0x144A048>
>>> img.decoderconfig, img.mode, img.size, img.tile
((4, 0), 'L', (168, 112), [('jpeg', (0, 0, 168, 112), 0, ('L', ''))])
>>> img.show()

but normally you should not depend on this method very much, because:

This method is not implemented for most images. It is currently implemented only for JPEG and PCD images.

To convert mode, simply use convert(mode), and to resize image, simply use resize.

getbands

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.getbands, http://effbot.org/imagingbook/image.htm#tag-Image.Image.getbands

>>> from glob import iglob
>>> from PIL import Image
>>>
>>> for fn in iglob("data/*.[jp][pn]g"):
...     img = Image.open(fn)
...     print("{}: {}".format(fn, img.getbands()))
...
data/mask_circle_01.jpg: ('L',)
- snip -
data/srcimg08.jpg: ('R', 'G', 'B')
data/srcimg09.png: ('R', 'G', 'B', 'A')
data/srcimg10.png: ('R', 'G', 'B')
- snip -

getbbox

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.getbbox, http://effbot.org/imagingbook/image.htm#tag-Image.Image.getbbox

>>> from glob import iglob
>>> from PIL import Image
>>>
>>> for fn in iglob("data/*.[jp][pn]g"):
...     img = Image.open(fn)
...     print("{}: {}".format(fn, img.getbbox()))
...
data/mask_circle_01.jpg: (0, 0, 256, 256)
- snip -
data/srcimg16.jpg: (0, 0, 675, 500)
data/srcimg17.jpg: (0, 0, 967, 750)

getcolors, getpalette

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.getcolors, http://effbot.org/imagingbook/image.htm#tag-Image.Image.getcolors, https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.getpalette

getcolors and getpalette with “P” mode:

>>> from PIL import Image
>>> img = Image.open('data/srcimg06.jpg')
>>> img = img.convert("P", palette=Image.ADAPTIVE, colors=16)
>>> #
>>> img.width * img.height
280350
>>> # pixel values are represented by indexes:
>>> data = list(img.getdata())
>>> len(data)  # == img.width * img.height, that is, these are not [(r, g, b), ...]
280350
>>> data[50:70]
[7, 7, 7, 7, 7, 7, 7, 7, 13, 7, 7, 7, 7, 7, 7, 13, 13, 13, 7, 7]
>>> # getcolors returns unsorted list of (count, color) tuples.
>>> # If the maxcolors value is exceeded, the method stops counting and returns None.
>>> clrs = img.getcolors()
>>> len(clrs)
16
>>> clrs.sort(key=lambda x: x[1])  # sort by color
>>>
>>> # show up colors and palette
>>> p_nzero = img.getpalette()[:len(clrs) * 3]
>>> for v in [(clrs[i // 3], (p_nzero[i], p_nzero[i + 1], p_nzero[i + 2]))
...           for i in range(0, len(p_nzero), 3)]:
...     print(v)
...
((24270, 0), (227, 230, 229))
((8860, 1), (212, 210, 208))
((24091, 2), (190, 201, 214))
((25088, 3), (167, 188, 206))
((15363, 4), (199, 157, 148))
((6508, 5), (143, 163, 172))
((4955, 6), (118, 150, 170))
((27667, 7), (88, 140, 199))
((20889, 8), (224, 101, 150))
((23184, 9), (136, 102, 92))
((16386, 10), (188, 50, 112))
((16948, 11), (133, 34, 70))
((17653, 12), (82, 106, 93))
((11588, 13), (44, 99, 157))
((20856, 14), (51, 63, 49))
((16044, 15), (25, 21, 13))

getcolors and getpalette with “L” mode:

>>> from PIL import Image
>>> img = Image.open('data/srcimg06.jpg').convert("L")
>>> # getcolors returns unsorted list of (count, color) tuples.
>>> # If the maxcolors value is exceeded, the method stops counting and returns None.
>>> clrs = img.getcolors()
>>> len(clrs)
256
>>> print("\n".join(map(str, clrs)))
(826, 0)
(537, 1)
(450, 2)
- snip -
(106, 253)
(62, 254)
(1, 255)
>>> img.getpalette() is None
True

getdata

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.getdata, http://effbot.org/imagingbook/image.htm#tag-Image.Image.getdata

>>> from PIL import Image
>>> img = Image.open('data/srcimg06.jpg')
>>> grn = img.getdata(band=1)  # get green
>>> upth, lwth = 0, 0
>>> for c in grn:  # grn is a sequence-like object.
...     if c < 128:
...         lwth += 1
...     else:
...         upth += 1
...
>>> print("upper={}, lower={}".format(upth, lwth))
upper=144495, lower=135855
>>> # pixel data to numpy array
>>> import numpy as np
>>> from PIL import Image
>>> img = Image.open('data/srcimg06.jpg')
>>> red = np.array(img.getdata(band=0))  # get red
>>> grn = np.array(img.getdata(band=1))  # get green
>>> blu = np.array(img.getdata(band=2))  # get blur
>>>
>>> red
array([ 14,  25,  28, ...,  96, 118, 124])
>>> red[red < 50] = 0
>>> red
array([  0,   0,   0, ...,  96, 118, 124])
>>> # pixel data to numpy array
>>> import numpy as np
>>> from PIL import Image
>>> img = Image.open('data/srcimg06.jpg')
>>> rawpb1 = np.array(img.getdata(), dtype=np.uint8).reshape((img.height, img.width, 3))
>>> rawpb1.shape
(450, 623, 3)
>>> rawpb1
array([[[ 14,  74, 160],
        [ 25,  85, 171],
        [ 28,  90, 175],
        ...,
        [  6,  52, 130],
        [ 10,  53, 131],
        [  7,  49, 125]],

       --- snip ---

       [[151,  67,  82],
        [102,  32,  43],
        [146,  95, 100],
        ...,
        [ 96,  92,  55],
        [118, 112,  78],
        [124, 120,  85]]], dtype=uint8)
>>>
>>> # you can use numpy.asarray:
>>> rawpb2 = np.asarray(img)
>>> rawpb2.shape
(450, 623, 3)
>>> rawpb2
array([[[ 14,  74, 160],
        [ 25,  85, 171],
        [ 28,  90, 175],
        ...,
        [  6,  52, 130],
        [ 10,  53, 131],
        [  7,  49, 125]],

       --- snip ---

       [[151,  67,  82],
        [102,  32,  43],
        [146,  95, 100],
        ...,
        [ 96,  92,  55],
        [118, 112,  78],
        [124, 120,  85]]], dtype=uint8)
>>>
>>> # identical?
>>> np.all(rawpb1 == rawpb2)
True
>>> # pixel data to numpy array
>>> import numpy as np
>>> from PIL import Image
>>> img = Image.open('data/srcimg06.jpg').convert("L")  # as grayscale
>>> gray = np.array(img.getdata())
>>>
>>> gray
array([ 65,  76,  81, ...,  88, 109, 117])
>>> gray[gray < 70] = 0
>>> gray
array([  0,  76,  81, ...,  88, 109, 117])

See also: putdata.

getextrema

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.getextrema, http://effbot.org/imagingbook/image.htm#tag-Image.Image.getextrema

>>> from PIL import Image
>>> img = Image.open('data/mask_grad_01.jpg')  # L (i.e., single band)
>>> img.getextrema()
(0, 255)
>>> img = Image.open('data/srcimg02.jpg')  # RBG (i.e., multiple bands)
>>> img.getextrema()
((0, 255), (0, 236), (0, 245))
>>> img = Image.open('data/srcimg04.jpg')  # RBG
>>> img.getextrema()
((3, 255), (0, 255), (0, 255))
>>> img = Image.open('data/srcimg06.jpg')  # RBG
>>> img.getextrema()
((0, 255), (0, 255), (0, 255))

getpixel

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.getpixel, http://effbot.org/imagingbook/image.htm#tag-Image.Image.getpixel

>>> from PIL import Image
>>> img1 = Image.open('data/mask_grad_01.jpg')  # L
>>> img1.getpixel((10, 20))
20
>>> img2 = Image.open('data/srcimg06.jpg')  # RBG
>>> img2.getpixel((10, 20))
(127, 144, 198)

histogram

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.histogram, http://effbot.org/imagingbook/image.htm#tag-Image.Image.histogram

srcimg03.jpg srcimg03

>>> from PIL import Image
>>> fn = 'data/srcimg03.jpg'
>>> simg = Image.open(fn)
>>> r, g, b = simg.split()
>>> len(r.histogram())
256
>>> r.histogram()
[2402, 236, 214, 258, 234, ...(snip)..., 16366]

To visualize this list, for example:

#
# PIL histogram to graph with matplotlib (https://matplotlib.org)
#
from PIL import Image
import matplotlib.pyplot as plt
#
fn = 'data/srcimg03.jpg'
simg = Image.open(fn)
r, g, b = simg.split()

bins = list(range(256))
plt.plot(bins, r.histogram(), 'r')
plt.plot(bins, g.histogram(), 'g')
plt.plot(bins, b.histogram(), 'b')
plt.xlabel('Pixel value')
plt.ylabel('Frequency')
plt.title(fn)
plt.grid(True)
plt.savefig("result/im_histogram_01.jpg")

im.histogram.res1

offset

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.offset, http://effbot.org/imagingbook/image.htm#tag-Image.Image.offset

This method that have been marked as deprecated for many releases have been removed at Pillow 3.0.0 (actually is still defined but raise NotImplementedError). Use ImageChops.offset() instead.

paste

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.paste, http://effbot.org/imagingbook/image.htm#tag-Image.Image.paste

srcimg12.jpg srcimg12

srcimg13.jpg srcimg13

from PIL import Image

img1 = Image.open('data/srcimg12.jpg')
img2 = Image.open('data/srcimg13.jpg')
img2 = img2.resize((img2.size[0] // 4, img2.size[1] // 4))

# paste at (0, 0)
res = img1.copy()
res.paste(img2, box=(0, 0, img2.size[0], img2.size[1]))
res.save("result/im_paste_01.jpg")

# paste at (50, 50)
res = img1.copy()
res.paste(img2, box=(50, 50, img2.size[0] + 50, img2.size[1] + 50))
res.save("result/im_paste_02.jpg")

# paste at (50, 50) with mask
res = img1.copy()
mask = Image.new("L", img2.size, 128)
res.paste(
    img2, box=(50, 50, img2.size[0] + 50, img2.size[1] + 50), mask=mask)
res.save("result/im_paste_03.jpg")

# paste at (50, 50) with mask (circle)
res = img1.copy()
mask = Image.open('data/mask_circle_01.jpg')
res.paste(
    img2, box=(50, 50, img2.size[0] + 50, img2.size[1] + 50),
    mask=mask.resize(img2.size))
res.save("result/im_paste_04.jpg")

(0, 0) im.paste.res1

(50, 50) im.paste.res2

(50, 50) with mask im.paste.res3

(50, 50) with mask (circle) im.paste.res4

point

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.point, http://effbot.org/imagingbook/image.htm#tag-Image.Image.point

srcimg04.jpg srcimg04

from itertools import chain
from PIL import Image
#
simg = Image.open('data/srcimg04.jpg')

# --- lookup table by function
#
dimg = simg.point(lambda i: i if i < 220 else 255)
dimg.save("result/im_point_01.jpg")

# --- lookup table by list
#

# all bands share same lookup table
lut = [i if i < 220 else 255
       for i in range(256)] * len(simg.getbands())
dimg = simg.point(lut)
dimg.save("result/im_point_02.jpg")

# each band has individual lookup table
lut = list(chain.from_iterable((
    (i if i < 220 else 255
     for i in range(256)),  # R
    (tuple(range(256))),  # G
    (tuple(range(256))),  # B
    )))

dimg = simg.point(lut)
dimg.save("result/im_point_03.jpg")
im_point_01.jpg im.point.res1
im_point_02.jpg im.point.res2
im_point_03.jpg im.point.res3

putalpha

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.putalpha, http://effbot.org/imagingbook/image.htm#tag-Image.Image.putalpha

srcimg12.jpg srcimg12

from PIL import Image

img = Image.open('data/srcimg12.jpg')
img.putalpha(127)  # with color value
img.save(
    "result/im_putalpha_01.png")

im.putalpha.res1

import numpy as np
from PIL import Image

img = Image.open('data/srcimg12.jpg')
r = np.array(img.getdata(0))
g = np.array(img.getdata(1))
b = np.array(img.getdata(2))
a = np.ones(r.shape) * 255
a[np.logical_and(r < 50, r < 50, g < 50)] = 0
alpha = Image.new("L", img.size)
alpha.putdata(a.flatten())

# alpha cab be an "L" or "1" image having the same size as this image
img.putalpha(alpha)
img.save(
    "result/im_putalpha_02.png")

im.putalpha.res2

putdata

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.putdata, http://effbot.org/imagingbook/image.htm#tag-Image.Image.putdata

# from pure python list data
from PIL import Image

img = Image.new("L", (64, 64))  # single band
graddata = list(range(0, 256, 4)) * 64
img.putdata(graddata)
img.save(
    "result/im_putdata_01.jpg")

im.putdata.res1

# from pure python list data
from PIL import Image

img = Image.new("RGB", (64, 64))  # multiple bands
gr_r = [0] * 64 * 64
gr_g = list(range(0, 256, 4)) * 64
gr_b = list(range(0, 256, 4)) * 64
img.putdata(list(zip(gr_r, gr_g, gr_b)))
img.save(
    "result/im_putdata_02.jpg")

im.putdata.res2

# from numpy array
import numpy as np
from PIL import Image

img = Image.new("L", (64, 64))  # single band
graddata = np.ndarray((64, 64))
for i in range(64):
    graddata[:, i] = i * 4
img.putdata(graddata.flatten())
img.save(
    "result/im_putdata_03.jpg")

im.putdata.res3

# from numpy array
import numpy as np
from PIL import Image

img = Image.new("RGB", (64, 64))  # multiple bands
graddata = np.zeros((3, 64, 64), dtype=np.int32)
for i in range(64):
    graddata[1:, :, i] = i * 4
img.putdata(
    list(
        zip(graddata[0].flatten(),
            graddata[1].flatten(),
            graddata[2].flatten())))
img.save(
    "result/im_putdata_04.jpg")

im.putdata.res4

>>> # from bytes
>>> from PIL import Image
>>>
>>> rawpixelbytes = b'\xa0\xfe\xfe\xa0'
>>>
>>> # with frombytes
>>> img1 = Image.frombytes("L", (2, 2), rawpixelbytes)
>>>
>>> # putdata after new
>>> img2 = Image.new("L", (2, 2))
>>> img2.putdata(rawpixelbytes)
>>>
>>> # identical?
>>> img1.tobytes("raw"), img2.tobytes("raw"), img1.tobytes("raw") == img2.tobytes("raw")
(b'\xa0\xfe\xfe\xa0', b'\xa0\xfe\xfe\xa0', True)
>>>
>>> # putdata after new with scale=0.5
>>> hex(0xa0 // 2), chr(0xa0 // 2), hex(0xfe // 2)
('0x50', 'P', '0x7f')
>>> img3 = Image.new("L", (2, 2))
>>> img3.putdata(rawpixelbytes, scale=1/2.)
>>> img3.tobytes("raw")
b'P\x7f\x7fP'
>>>
>>> # putdata after new with offset=-0x10
>>> hex(0xa0 - 0x10), hex(0xfe - 0x10)
('0x90', '0xee')
>>> img4 = Image.new("L", (2, 2))
>>> img4.putdata(rawpixelbytes, offset=-0x10)
>>> img4.tobytes("raw")
b'\x90\xee\xee\x90'

See also: frombytes, frombuffer, fromarray.

putpalette, remap_palette

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.putpalette, https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.remap_palette

Note

See also: getcolors, getpalette.

remap_palette will change both pixel value and palette (in other words, indexes as pixel values will follow new palette table):

>>> import os
>>> from PIL import Image
>>>
>>> img = Image.open('data/srcimg06.jpg')
>>> img = img.convert("P", palette=Image.ADAPTIVE, colors=16)
>>> img.save('result/p_adaptive16_orig.png')
>>> os.stat('result/p_adaptive16_orig.png').st_size
76348
>>> # pixel values are represented by indexes:
>>> data = list(img.getdata())
>>> data[50:70]
[7, 7, 7, 7, 7, 7, 7, 7, 13, 7, 7, 7, 7, 7, 7, 13, 13, 13, 7, 7]
>>> clrs = img.getcolors()
>>> len(clrs)
16
>>>
>>> # show up colors and palette
>>> srcpalette = img.getpalette()
>>> p_nzero = srcpalette[:len(clrs) * 3]
>>> clrs.sort(key=lambda x: x[1])  # sort by color, for showing
>>> for v in [(clrs[i // 3], (p_nzero[i], p_nzero[i + 1], p_nzero[i + 2]))
...           for i in range(0, len(p_nzero), 3)]:
...     print(v)
...
((24270, 0), (227, 230, 229))
((8860, 1), (212, 210, 208))
((24091, 2), (190, 201, 214))
((25088, 3), (167, 188, 206))
((15363, 4), (199, 157, 148))
((6508, 5), (143, 163, 172))
((4955, 6), (118, 150, 170))
((27667, 7), (88, 140, 199))
((20889, 8), (224, 101, 150))
((23184, 9), (136, 102, 92))
((16386, 10), (188, 50, 112))
((16948, 11), (133, 34, 70))
((17653, 12), (82, 106, 93))
((11588, 13), (44, 99, 157))
((20856, 14), (51, 63, 49))
((16044, 15), (25, 21, 13))
>>> # build new palette ordered by occurrence, and create new image with it
>>> clrs.sort(key=lambda x: -x[0])  # sort by occurrence
>>> img = img.remap_palette([c for oc, c in clrs])
>>> img.save('result/p_adaptive16_remapped.png')
>>> os.stat('result/p_adaptive16_remapped.png').st_size
72098
>>>
>>> # --- show up again for new image
>>> data = list(img.getdata())
>>> data[50:70]
[0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 12, 12, 12, 0, 0]
>>> clrs = img.getcolors()
>>> p_nzero = img.getpalette()[:len(clrs) * 3]
>>> clrs.sort(key=lambda x: x[1])  # sort by color, for showing
>>> for v in [(clrs[i // 3], (p_nzero[i], p_nzero[i + 1], p_nzero[i + 2]))
...           for i in range(0, len(p_nzero), 3)]:
...     print(v)
...
((27667, 0), (88, 140, 199))
((25088, 1), (167, 188, 206))
((24270, 2), (227, 230, 229))
((24091, 3), (190, 201, 214))
((23184, 4), (136, 102, 92))
((20889, 5), (224, 101, 150))
((20856, 6), (51, 63, 49))
((17653, 7), (82, 106, 93))
((16948, 8), (133, 34, 70))
((16386, 9), (188, 50, 112))
((16044, 10), (25, 21, 13))
((15363, 11), (199, 157, 148))
((11588, 12), (44, 99, 157))
((8860, 13), (212, 210, 208))
((6508, 14), (143, 163, 172))
((4955, 15), (118, 150, 170))
srcimg06_100
original
p_adaptive16_orig
convert(“P”)
p_adaptive16_remap
remap_palette

Meanwhile, putpalette replace only the colors of the palette:

>>> from PIL import Image
>>>
>>> img = Image.open('data/srcimg06.jpg')
>>> img = img.convert("P", palette=Image.ADAPTIVE, colors=16)
>>> data = list(img.getdata())
>>> data[50:70]
[7, 7, 7, 7, 7, 7, 7, 7, 13, 7, 7, 7, 7, 7, 7, 13, 13, 13, 7, 7]
>>> clrs = img.getcolors()
>>> len(clrs)
16
>>>
>>> # sort the colors of the palette ordered by occurrence
>>> # (Of course, this is nonsence. Just an example.)
>>> srcpalette = img.getpalette()
>>> newpalette = []
>>> for oc, c in sorted(clrs, key=lambda x: -x[0]):
...     newpalette.extend([srcpalette[c * 3], srcpalette[c * 3 + 1], srcpalette[c * 3 + 2]])
...
>>> newpalette.extend((768 - len(newpalette)) * [0])
>>>
>>> # result image is no longer the same photo.
>>> img.putpalette(newpalette)
>>> data = list(img.getdata())
>>> data[50:70]  # not changed
[7, 7, 7, 7, 7, 7, 7, 7, 13, 7, 7, 7, 7, 7, 7, 13, 13, 13, 7, 7]
>>> img.save('result/p_adaptive16_newpalette.png')
srcimg06_100
original
p_adaptive16_orig
convert(“P”)
p_adaptive16_newpalette
putpalette

putpixel

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.putpixel, http://effbot.org/imagingbook/image.htm#tag-Image.Image.putpixel

Note

This method is relatively slow. For more extensive changes, use paste() or the ImageDraw module instead. And also, if you want to put data as a whole image, see putdata().

For single band:

>>> from PIL import Image
>>>
>>> img = Image.new("L", (4, 4))  # single band
>>> for y in range(4):
...     for x in range(4):
...         img.putpixel((x, y), y * 64)
...
>>> list(img.getdata())
[0, 0, 0, 0, 64, 64, 64, 64, 128, 128, 128, 128, 192, 192, 192, 192]

You can also use PixelAccess (but fairly slow, too):

>>> from PIL import Image
>>>
>>> img = Image.new("L", (4, 4))  # single band
>>> px = img.load()
>>> for y in range(4):
...     for x in range(4):
...         px[(x, y)] = y * 64
...
>>> list(img.getdata())
[0, 0, 0, 0, 64, 64, 64, 64, 128, 128, 128, 128, 192, 192, 192, 192]

For multiple bands:

>>> from PIL import Image
>>>
>>> img = Image.new("RGB", (4, 4))  # multiple bands
>>> for y in range(4):
...     for x in range(4):
...         img.putpixel(
...             (x, y),
...             ((y + 1) * 64, y * 64, 255 - (y * 64)))
...
>>> print("\n".join(list(map(str, img.getdata()))))
(64, 0, 255)
(64, 0, 255)
(64, 0, 255)
(64, 0, 255)
(128, 64, 191)
(128, 64, 191)
(128, 64, 191)
(128, 64, 191)
(192, 128, 127)
(192, 128, 127)
(192, 128, 127)
(192, 128, 127)
(255, 192, 63)
(255, 192, 63)
(255, 192, 63)
(255, 192, 63)

You can also use PixelAccess (but fairly slow, too):

>>> from PIL import Image
>>>
>>> img = Image.new("RGB", (4, 4))  # multiple bands
>>> px = img.load()
>>> for y in range(4):
...     for x in range(4):
...         px[(x, y)] = ((y + 1) * 64, y * 64, 255 - (y * 64))
...
>>> print("\n".join(list(map(str, img.getdata()))))
(64, 0, 255)
(64, 0, 255)
(64, 0, 255)
(64, 0, 255)
(128, 64, 191)
(128, 64, 191)
(128, 64, 191)
(128, 64, 191)
(192, 128, 127)
(192, 128, 127)
(192, 128, 127)
(192, 128, 127)
(255, 192, 63)
(255, 192, 63)
(255, 192, 63)
(255, 192, 63)

quantize

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.quantize, http://effbot.org/imagingbook/image.htm#tag-Image.Image.quantize

Note

This method of original PIL is deprecated because this is identical to convert(“P”, **options), but in the case of Pillow, these are not the same each other. Pillow version is for color quantization, as its name claims so that. (Strictly speaking, convert(“P”, **options) will also do color quantization, so the differences are those scope. For example, you can’t select quantizer method if you are using convert(“P”, **options).)

Note

It seems that the argument method can be:

, but official documents (which is the same as the docstring of python source code) and C source code comment doesn’t say so. I can’t say for sure.

The argument kmeans seems that it is for k-means clustering, and I can’t say for sure about this, too. kmeans is likely used only by mode=0(median cut), 1(maximum coverage), and it seems k-means logic is applied after processing “median cut” or “maximum coverage” while changes <= (kmeans - 1). Sorry, I can’t understand at all about these. At least, zero kmeans value is no effect, larger kmeans increase the times of call of k-means logic. And I can’t sure these spec is frozen or not.

>>> from PIL import Image
>>>
>>> img = Image.open('data/srcimg06.jpg')
>>> dimg = img.quantize(
...     colors=32,
...     method=0,  # median cut
...     kmeans=5)
>>> len(dimg.getcolors())
32
>>> dimg.save("result/im_quantize_32_0_5.png")
>>> dimg = img.quantize(
...     colors=32,
...     method=1,  # maximum coverage
...     kmeans=5)
>>> len(dimg.getcolors())
32
>>> dimg.save("result/im_quantize_32_1_5.png")
>>> dimg = img.quantize(
...     colors=32,
...     method=2)  # octree
>>> len(dimg.getcolors())
32
>>> dimg.save("result/im_quantize_32_2.png")
srcimg06_100
original
im_quantize_32_0_5
32, 0, 5
im_quantize_32_1_5
32, 1, 5
im_quantize_32_2
32, 2

resize

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.resize, http://effbot.org/imagingbook/image.htm#tag-Image.Image.resize

About resample, see Filters.

from PIL import Image

img = Image.open('data/srcimg13.jpg')
# * "resize" returns new image.
# * "resize" streches image without considering aspect ratio.
img = img.resize((img.width // 4, img.height * 2))

rotate

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.rotate, http://effbot.org/imagingbook/image.htm#tag-Image.Image.rotate

About resample, see Filters.

srcimg14.jpg srcimg14 (size=(912, 684))

from PIL import Image
#
simg = Image.open('data/srcimg14.jpg')
# angle, resample, expand
#   resample can be one of Image.NEAREST (default),
#   Image.BILINEAR, Image.BICUBIC
dimg = simg.rotate(-10)
dimg.save("result/im_rotate_r-10.jpg")
#
dimg = simg.rotate(-10, expand=True)
dimg.save("result/im_rotate_r-10_expand.jpg")

rotate=-10 => result size=(912, 684) im.rotate.res1

rotate=-10, expand=True => result size=(1017, 833) im.rotate.res2

# collaborate with putalpha
from PIL import Image
#
simg = Image.open('data/srcimg14.jpg')
dimg = simg.rotate(-10)
mask = Image.new("L", simg.size, 255)
mask = mask.rotate(-10)
dimg.putalpha(mask)
dimg.save("result/im_rotate_r-10m.png")
#
dimg = simg.rotate(-10, expand=True)
mask = Image.new("L", simg.size, 255)
mask = mask.rotate(-10, expand=True)
dimg.putalpha(mask)
dimg.save("result/im_rotate_r-10m_expand.png")

rotate=-10 => result size=(912, 684) im.rotate.res3

rotate=-10, expand=True => result size=(1017, 833) im.rotate.res4

save

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.save, http://effbot.org/imagingbook/image.htm#tag-Image.Image.save

Saves this image under the given filename:

from PIL import Image

img = Image.new("RGB", (16, 16))
img.save("out.jpg")

If the filename extension is not a standard, you can pass format argument explicitly:

from PIL import Image

img = Image.new("RGB", (16, 16))
img.save("out.thumbnail", "JPEG")

Possible formats are described in Image file formats. Each format writer sometimes takes additional keyword arguments:

from PIL import Image

img = Image.new("RGB", (16, 16))
img.save("out.thumbnail", "JPEG", quality=75)

Those specifications are also described in Image file formats.

You can use a file object instead of a filename:

from PIL import Image

img = Image.new("RGB", (16, 16))
with open("out.thumbnail", "wb") as fo:
    img.save(fo, "JPEG", quality=75)

The file object must implement the seek, tell, and write methods, and be opened in binary mode. In other words, you can also use file-like object like io.BytesIO:

from io import BytesIO
import base64
from PIL import Image

img = Image.new("L", (256, 256))
img.putdata([i // 256 for i in range(256*256)])

# You can use a file object instead of a filename.
dst = BytesIO()
# In this case, you must always specify the format
img.save(dst, "JPEG")

# In case of using BytesIO, you can get the written data
# by getvalue() method:
b64ed = base64.standard_b64encode(dst.getvalue())
encoded_image = b"data:image/jpeg;base64," + b64ed

with open("result/encoded_image_01.html", "wb") as fo:
    fo.write(b'<html><body><img src="')
    fo.write(encoded_image)
    fo.write(b'"/></body>\n')

seek, tell

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.seek, https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.tell, http://effbot.org/imagingbook/image.htm#tag-Image.Image.seek, http://effbot.org/imagingbook/image.htm#tag-Image.Image.tell

ImageChops_blend_02.gif: see blend.

>>> from PIL import Image
>>> # ImageChops_blend_02.gif has 33 frames.
>>> img = Image.open("result/ImageChops_blend_02.gif")
>>> img.tell()
0
>>> img.seek(img.tell() + 1)
>>> img.tell()
1
>>> img.show()
>>> # convert RGB before save as jpeg to avoid "IOError: cannot write mode P as JPEG"
>>> img.convert("RGB").save("frame1.jpg")
>>> for i in range(31): img.seek(img.tell() + 1)
...
>>> img.seek(img.tell() + 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\Python27\lib\site-packages\PIL\GifImagePlugin.py", line 132, in seek
    raise EOFError("no more images in GIF file")
EOFError: no more images in GIF file

See also: ImageSequence.

show

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.show, http://effbot.org/imagingbook/image.htm#tag-Image.Image.show

Displays this image. This method is mainly intended for debugging purposes.

>>> from PIL import Image, ImageDraw, ImageChops
>>>
>>> dia = 128
>>> circle = Image.new("L", (dia * 4, dia * 4), 0)
>>> dctx = ImageDraw.Draw(circle)
>>> dctx.ellipse([dia, dia, dia * 3, dia * 3], fill=255)
>>> del dctx
>>>
>>> offset = dia // 2
>>> r = ImageChops.offset(circle, offset, offset)
>>> #r.show()
>>> g = ImageChops.offset(circle, -offset, offset)
>>> #g.show()
>>> b = ImageChops.offset(circle, 0, -offset)
>>> #b.show()
>>>
>>> dimg = Image.merge("RGB", (r, g, b))
>>> #dimg.show()
>>> mask = Image.eval(dimg.convert("L"), lambda p: 255 if p > 0 else 0)
>>> #mask.show()
>>> dimg.putalpha(mask)
>>> dimg.show()

If you use Windows, and you encounter the trouble like:

Windows Photo Viewer can’t open this picture because either the picture is deleted, or it’s in a location that isn’t available.

or in Japanese edition:

この画像が削除されたか、利用出来ない場所にあるため、Windows フォトビューワーでこの画像を開けません。

, you can replace the viewer as you like with inserting the following codes in your debuggee code:

“without cleanup temporary” version
from PIL import ImageShow

class _WindowsViewer_NoDelTemp(ImageShow.Viewer):
    format = "BMP"

    def get_command(self, file, **options):
        # defined as the following in original WindowsViewer
        # (of PIL version '1.1.7', Pillow version = '4.1.1'):
        #
        #    return ('start "Pillow" /WAIT "%s" '
        #            '&& ping -n 2 127.0.0.1 >NUL '
        #            '&& del /f "%s"' % (file, file))
        return ("""start "Pillow" /WAIT "%s" """ % (file, ))
        # you have to cleanup temporary files manually...

ImageShow.register(_WindowsViewer_NoDelTemp, order=-1)
“simply change the retry count of ping” version
from PIL import ImageShow

class _WindowsViewer2(ImageShow.Viewer):
    format = "BMP"

    def get_command(self, file, **options):
        return ('start "Pillow" /WAIT "%s" '
                '&& ping -n 5 127.0.0.1 >NUL '  # "-n 2" -> "-n 5"
                '&& del /f "%s"' % (file, file))

ImageShow.register(_WindowsViewer2, order=-1)
“too much but without ‘start’” version
from PIL import ImageShow

class _WindowsViewer3(ImageShow.Viewer):
    format = "BMP"
    def get_command(self, file, **options):
        import os, subprocess, atexit, win32process
        atexit.register(
            subprocess.Popen,
            ["python",
             "-c",
             "import time, os ; time.sleep(5); os.remove('%s')" % \
                 file.replace(os.sep, "/")],
            creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | \
                win32process.DETACHED_PROCESS,
            close_fds=True)
        return ("""start "Pillow" /WAIT "%s" """ % (file, ))

ImageShow.register(_WindowsViewer3, order=-1)

split

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.split, http://effbot.org/imagingbook/image.htm#tag-Image.Image.split

srcimg24.jpg srcimg24

from PIL import Image
#
img = Image.open('data/srcimg24.jpg')
red, grn, blu = img.split()
red.save("result/im_split_01_rL.jpg")
grn.save("result/im_split_01_gL.jpg")
blu.save("result/im_split_01_bL.jpg")
blk = Image.new("L", red.size, "black")

Image.merge("RGB", (red, blk, blk)).save(
    "result/im_split_01_r.jpg")
Image.merge("RGB", (blk, grn, blk)).save(
    "result/im_split_01_g.jpg")
Image.merge("RGB", (blk, blk, blu)).save(
    "result/im_split_01_b.jpg")

im_split_01_rL.jpg im.split.res1

im_split_01_gL.jpg im.split.res2

im_split_01_bL.jpg im.split.res3

im_split_01_r.jpg im.split.res4

im_split_01_g.jpg im.split.res5

im_split_01_b.jpg im.split.res6

thumbnail

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.thumbnail, http://effbot.org/imagingbook/image.htm#tag-Image.Image.thumbnail

About resample, see Filters.

# -*- coding: utf-8 -*-
#
# very simple script for creating thumbnails.
#
import argparse
import os
from glob import iglob
from PIL import Image

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("-i", "--input_dir", default=".")
    parser.add_argument("-d", "--dest_dir", default=".")
    parser.add_argument("-s", "--size", type=int, default=128)
    parser.add_argument(
        "-e", "--extentension",
        choices=["thumbnail", "jpg", "jpeg"],
        default="jpg")
    args = parser.parse_args()
    size = (args.size, args.size)

    for pat in ("*.jpg", "*.jpeg", "*.png"):
        for fpath in iglob(os.path.join(args.input_dir, pat)):
            fn = os.path.basename(fpath)
            bn, ext = os.path.splitext(fn)
            #
            img = Image.open(fpath)

            # NOTE:
            #  * Aspect ratio is preserved.
            #  * This function modifies Image object in place.
            #  * The parameter 'resample' differs between PIL
            #    (or pillow) versions.
            img.thumbnail(size)
            #

            if not os.path.exists(args.dest_dir):
                os.mkdir(args.dest_dir)
            img.save(
                os.path.join(
                    args.dest_dir,
                    "{}.{}".format(bn, args.extentension)),
                "JPEG")

tobitmap

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.tobitmap, http://effbot.org/imagingbook/image.htm#tag-Image.Image.tobitmap

>>> from io import BytesIO
>>> from PIL import Image
>>>
>>> img = Image.frombytes("L", (16, 16), b'''\
... \xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
... \xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\
... \xff\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\xff\
... \xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\xff\
... \xff\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\xff\
... \xff\x00\x00\x00\x00\xff\x00\x00\x00\x00\xff\x00\x00\x00\x00\xff\
... \xff\x00\x00\x00\x00\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\xff\
... \xff\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\xff\
... \xff\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\xff\
... \xff\x00\x00\x00\x00\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\xff\
... \xff\x00\x00\x00\x00\xff\x00\x00\x00\x00\xff\x00\x00\x00\x00\xff\
... \xff\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\xff\
... \xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\xff\
... \xff\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\xff\
... \xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\
... \xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff''')
>>>
>>> xbm = img.convert("1").tobitmap("fubar")  # "xbm" format (X11 bitmap)
>>> print(xbm.decode('ascii'))
#define fubar_width 16
#define fubar_height 16
static char fubar_bits[] = {
0xff,0xff,0x03,0xc0,0x05,0xa0,0x09,0x90,0x11,0x88,0x21,0x84,0x41,0x82,0x81,
0x81,0x81,0x81,0x41,0x82,0x21,0x84,0x11,0x88,0x09,0x90,0x05,0xa0,0x03,0xc0,
0xff,0xff
};
>>>
>>> # xbm can be read by XbmImagePlugin
>>> dimg = Image.open(BytesIO(xbm))
>>> _TAB = [" ", "O"]
>>> _NLT = ["\n", ""]
>>> print("".join([
...             (_TAB[v > 0] + _NLT[bool((i + 1) % dimg.width)])
...             for i, v in enumerate(dimg.getdata())]))
OOOOOOOOOOOOOOOO
OO            OO
O O          O O
O  O        O  O
O   O      O   O
O    O    O    O
O     O  O     O
O      OO      O
O      OO      O
O     O  O     O
O    O    O    O
O   O      O   O
O  O        O  O
O O          O O
OO            OO
OOOOOOOOOOOOOOOO

tobytes

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.tobytes, http://effbot.org/imagingbook/image.htm#tag-Image.Image.tobytes

Note

Basically, you should regard this method as just for use by plugin authors, or just for debugging, or testing purpose. This method just returns the raw image data from the internal storage. See save, or getdata, they might be what you want.

>>> from PIL import Image
>>>
>>> img = Image.frombytes("L", (2, 2), b'\xff\x00\xff\x00')
>>> mode = "L"
>>> img = img.convert(mode)
>>>
>>> img.tobytes("hex", mode)
b'ff00ff00'
>>> img.tobytes("eps", mode)
b'ff00ff00'
>>> img.tobytes("xbm", mode)
b'0x01,0x01\n'
>>>
>>> img.tobytes("raw", mode)
b'\xff\x00\xff\x00'
>>>
>>> img.tobytes("gif", mode)
b'\x07\x00\xff\x01\xf8\x07  '
>>> img.tobytes("jpeg", mode)
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xdb\x00C\x00\x08\x06\x06\x07\x06\x05\x08\x07\x07\x07\t\t\x08\n\x0c\x14\r\x0c\x0b\x0b\x0c\x19\x12\x13\x0f\x14\x1d\x1a\x1f\x1e\x1d\x1a\x1c\x1c $.\' ",#\x1c\x1c(7),01444\x1f\'9=82<.342\xff\xc0\x00\x0b\x08\x00\x02\x00\x02\x01\x01\x11\x00\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00\x00\x01}\x01\x02\x03\x00\x04\x11\x05\x12!1A\x06\x13Qa\x07"q\x142\x81\x91\xa1\x08#B\xb1\xc1\x15R\xd1\xf0$3br\x82\t\n\x16\x17\x18\x19\x1a%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x08\x01\x01\x00\x00?\x00\xf1\xff\x00\x1d\xff\x00\xc9C\xf1/\xfd\x85n\xbf\xf4kW\xff\xd9'
>>> img.tobytes("pcx", mode)
b'\xc1\xff\x00\xc1\xff\x00'
>>> img.tobytes("zip", mode)
b'x\x9cc\xf8\xcf\xc0\xc4\xc0\x00\x00\x05\x07\x01\x02'

tostring

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.tostring, http://effbot.org/imagingbook/image.htm#tag-Image.Image.tostring

This method that have been marked as deprecated for many releases have been removed at Pillow 3.0.0 (actually is still defined but raise NotImplementedError). Use tobytes() instead. (But also note that original PIL has no tobytes but has only tostring.)

transform

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.transform, http://effbot.org/imagingbook/image.htm#tag-Image.Image.transform

About resample, see Filters.

transform(size, EXTENT, data)

srcimg16.jpg srcimg16

from PIL import Image

simg = Image.open('data/srcimg16.jpg')

#
x0, y0 = 10, 0
x1, y1 = 10 + simg.width // 4, simg.height // 3
dimg = simg.transform(
    simg.size,  # expand to original size
    method=Image.EXTENT,
    data=[x0, y0, x1, y1])
dimg.save(
    "result/im_transform_EXTENT_01.jpg")

#
x0, y0 = 10, 0
x1, y1 = 10 + simg.width // 4, simg.height // 3
dimg = simg.transform(
    (x1 - x0, y1 - y0),  # to cropped size
    method=Image.EXTENT,
    data=[x0, y0, x1, y1])
dimg.save(
    "result/im_transform_EXTENT_02.jpg")
expand to original size im.transform.EXTENT.res1
to cropped size im.transform.EXTENT.res2

transform(size, AFFINE, data)

srcimg15.jpg srcimg15

import math
from PIL import Image

simg = Image.open('data/srcimg15.jpg')

#
# shear
#
cx, cy = 0.1, 0
dimg = simg.transform(
    simg.size,
    method=Image.AFFINE,
    data=[1, cx, 0,
          cy, 1, 0,])
dimg.save(
    "result/im_transform_AFFINE__shear1.jpg")
#
cx, cy = 0, 0.1
dimg = simg.transform(
    simg.size,
    method=Image.AFFINE,
    data=[1, cx, 0,
          cy, 1, 0,])
dimg.save(
    "result/im_transform_AFFINE__shear2.jpg")

#
# rotate
#
theta = math.radians(-5)
dimg = simg.transform(
    simg.size,
    method=Image.AFFINE,
    data=[math.cos(theta), math.sin(theta), 0,
          -math.sin(theta), math.cos(theta), 0,])
dimg.save(
    "result/im_transform_AFFINE__rotate.jpg")
shear1 im.transform.AFFINE.res1
shear2 im.transform.AFFINE.res2
rotate im.transform.AFFINE.res3

transform(size, QUAD, data)

srcimg17.jpg srcimg17

# quadrilateral warp.  data specifies the four corners
# given as NW, SW, SE, and NE.
from itertools import chain
from PIL import Image

simg = Image.open('data/srcimg17.jpg')

_nw = (0, 190)
_sw = (140, simg.height - 150)
_se = (simg.width - 160, simg.height - 70)
_ne = (simg.width, 0)
dimg = simg.transform(
    (simg.width, simg.height),
    method=Image.QUAD,
    data=list(chain.from_iterable((_nw, _sw, _se, _ne))))
dimg.save(
    "result/im_transform_QUAD_01.jpg")

im.transform.QUAD.res1

transform(size, MESH, data)

srcimg17.jpg srcimg17

from PIL import Image

simg = Image.open('data/srcimg17.jpg')

(w, h) = simg.size
dimg = simg.transform(
    simg.size,
    method=Image.MESH,
    # data is a list of target rectangles
    # and corresponding source quadrilaterals.
    data=[(
            # target rectangle (1)
            (0, 0, w // 2, h // 2),
            # corresponding source quadrilateral (1)
            # (NW, SW, SE, and NE. see method=QUAD)
            (0, 0, 0, h, w, h, w - 100, 0)
           ),
          (
            # target rectangle (2)
            (w // 2, h // 2, w, h),
            # corresponding source quadrilateral (2)
            # (NW, SW, SE, and NE. see method=QUAD)
            (0, 0, 0, h, w, h, w - 100, 0)
            ),
          ]
    )
dimg.save(
    "result/im_transform_MESH_01.jpg")

im.transform.MESH.res1

transform(size, PERSPECTIVE, data)

Data is a 8-tuple (a, b, c, d, e, f, g, h) which contains the coefficients for a perspective transform.

\[\begin{split}\begin{pmatrix} x' \\ y' \end{pmatrix} = \begin{pmatrix} (a x + b y + c)/(g x + h y + 1) \\ (d x + e y + f)/(g x + h y + 1) \end{pmatrix}\end{split}\]

But how do we get these coefficients? Don’t worry, because above equation can be solved if we know four relationships between \(\begin{pmatrix} x' \\ y' \end{pmatrix}\) and \(\begin{pmatrix} x \\ y \end{pmatrix}\):

\[ \begin{align}\begin{aligned}\begin{split}\begin{cases} x_{1} a + y_{1} b + 1c + 0d + 0e + 0f - x_{1} x'_{1} g - x'_{1} y_{1} h = x'_{1} \\ 0a + 0b + 0c + x_{1} d + y_{1} e + 1f - x_{1} y'_{1} g - y_{1} y'_{1} h = y'_{1} \end{cases}\end{split}\\\begin{split}\begin{cases} x_{2} a + y_{2} b + 1c + 0d + 0e + 0f - x_{2} x'_{2} g - x'_{2} y_{2} h = x'_{2} \\ 0a + 0b + 0c + x_{2} d + y_{2} e + 1f - x_{2} y'_{2} g - y_{2} y'_{2} h = y'_{2} \end{cases}\end{split}\\\begin{split}\begin{cases} x_{3} a + y_{3} b + 1c + 0d + 0e + 0f - x_{3} x'_{3} g - x'_{3} y_{3} h = x'_{3} \\ 0a + 0b + 0c + x_{3} d + y_{3} e + 1f - x_{3} y'_{3} g - y_{3} y'_{3} h = y'_{3} \end{cases}\end{split}\\\begin{split}\begin{cases} x_{4} a + y_{4} b + 1c + 0d + 0e + 0f - x_{4} x'_{4} g - x'_{4} y_{4} h = x'_{4} \\ 0a + 0b + 0c + x_{4} d + y_{4} e + 1f - x_{4} y'_{4} g - y_{4} y'_{4} h = y'_{4} \end{cases}\end{split}\end{aligned}\end{align} \]

That is:

\[\begin{split}\begin{pmatrix} x_{1} & y_{1} & 1 & 0 & 0 & 0 & -x_{1} x'_{1} & -x'_{1} y_{1} \\ 0 & 0 & 0 & x_{1} & y_{1} & 1 & -x_{1} y'_{1} & -y_{1} y'_{1} \\ x_{2} & y_{2} & 1 & 0 & 0 & 0 & -x_{2} x'_{2} & -x'_{2} y_{2} \\ 0 & 0 & 0 & x_{2} & y_{2} & 1 & -x_{2} y'_{2} & -y_{2} y'_{2} \\ x_{3} & y_{3} & 1 & 0 & 0 & 0 & -x_{3} x'_{3} & -x'_{3} y_{3} \\ 0 & 0 & 0 & x_{3} & y_{3} & 1 & -x_{3} y'_{3} & -y_{3} y'_{3} \\ x_{4} & y_{4} & 1 & 0 & 0 & 0 & -x_{4} x'_{4} & -x'_{4} y_{4} \\ 0 & 0 & 0 & x_{4} & y_{4} & 1 & -x_{4} y'_{4} & -y_{4} y'_{4} \end{pmatrix} \begin{pmatrix} a \\ b \\ c \\ d \\ e \\ f \\ g \\ h \end{pmatrix} = \begin{pmatrix} x'_{1} \\ y'_{1} \\ x'_{2} \\ y'_{2} \\ x'_{3} \\ y'_{3} \\ x'_{4} \\ y'_{4} \end{pmatrix}\end{split}\]

srcimg17.jpg srcimg17

import numpy as np
from numpy import linalg
from PIL import Image

def _create_coeff(
    xyA1, xyA2, xyA3, xyA4,
    xyB1, xyB2, xyB3, xyB4):

    A = np.array([
            [xyA1[0], xyA1[1], 1, 0, 0, 0, -xyB1[0] * xyA1[0], -xyB1[0] * xyA1[1]],
            [0, 0, 0, xyA1[0], xyA1[1], 1, -xyB1[1] * xyA1[0], -xyB1[1] * xyA1[1]],
            [xyA2[0], xyA2[1], 1, 0, 0, 0, -xyB2[0] * xyA2[0], -xyB2[0] * xyA2[1]],
            [0, 0, 0, xyA2[0], xyA2[1], 1, -xyB2[1] * xyA2[0], -xyB2[1] * xyA2[1]],
            [xyA3[0], xyA3[1], 1, 0, 0, 0, -xyB3[0] * xyA3[0], -xyB3[0] * xyA3[1]],
            [0, 0, 0, xyA3[0], xyA3[1], 1, -xyB3[1] * xyA3[0], -xyB3[1] * xyA3[1]],
            [xyA4[0], xyA4[1], 1, 0, 0, 0, -xyB4[0] * xyA4[0], -xyB4[0] * xyA4[1]],
            [0, 0, 0, xyA4[0], xyA4[1], 1, -xyB4[1] * xyA4[0], -xyB4[1] * xyA4[1]],
            ], dtype=np.float32)
    B = np.array([
            xyB1[0],
            xyB1[1],
            xyB2[0],
            xyB2[1],
            xyB3[0],
            xyB3[1],
            xyB4[0],
            xyB4[1],
            ], dtype=np.float32)
    return linalg.solve(A, B)

#
simg = Image.open('data/srcimg17.jpg')
coeff = _create_coeff(
    (0, 0),
    (simg.width, 0),
    (simg.width, simg.height),
    (0, simg.height),
    # =>
    (-500, 0),
    (simg.width + 500, 0),
    (simg.width, simg.height),
    (0, simg.height),
    )
dimg = simg.transform(
    (simg.width, simg.height),
    method=Image.PERSPECTIVE,
    data=coeff)
dimg.save(
    "result/im_transform_PERSPECTIVE_01.jpg")

im.transform.PERSPECTIVE.res1

transpose

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.transpose, http://effbot.org/imagingbook/image.htm#tag-Image.Image.transpose

srcimg14.jpg srcimg14

from PIL import Image
#
img = Image.open('data/srcimg14.jpg')
img.transpose(Image.FLIP_LEFT_RIGHT).save(
    "result/im_transpose_FLIP_LEFT_RIGHT.jpg")
img.transpose(Image.FLIP_TOP_BOTTOM).save(
    "result/im_transpose_FLIP_TOP_BOTTOM.jpg")
img.transpose(Image.ROTATE_90).save(
    "result/im_transpose_ROTATE_90.jpg")
img.transpose(Image.ROTATE_180).save(
    "result/im_transpose_ROTATE_180.jpg")
img.transpose(Image.ROTATE_270).save(
    "result/im_transpose_ROTATE_270.jpg")
img.transpose(Image.TRANSPOSE).save(
    "result/im_transpose_TRANSPOSE.jpg")

FLIP_LEFT_RIGHT im.transpose.res1

FLIP_TOP_BOTTOM im.transpose.res2

ROTATE_90 im.transpose.res3

ROTATE_180 im.transpose.res4

ROTATE_270 im.transpose.res5

TRANSPOSE im.transpose.res6

verify

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.verify, http://effbot.org/imagingbook/image.htm#tag-Image.Image.verify

>>> from io import BytesIO
>>> from PIL import Image
>>>
>>> simg = Image.open("data/srcimg09.png")
>>> dimg = BytesIO()
>>> simg.save(dimg, "PNG")
>>>
>>> # make dimg be malformed
>>> dimgrawbytes = dimg.getvalue()
>>> dimg = BytesIO(dimgrawbytes[:len(dimgrawbytes) // 2])
>>> # open brokened
>>> brokenimg = Image.open(dimg)  # => success
>>> # call verify
>>> brokenimg.verify()
Traceback (most recent call last):
  File "c:\Python35\lib\site-packages\PIL\PngImagePlugin.py", line 144, in crc
    crc2 = i16(self.fp.read(2)), i16(self.fp.read(2))
  File "c:\Python35\lib\site-packages\PIL\_binary.py", line 70, in i16be
    return unpack(">H", c[o:o+2])[0]
struct.error: unpack requires a bytes object of length 2

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "c:\Python35\lib\site-packages\PIL\PngImagePlugin.py", line 570, in verify
    self.png.verify()
  File "c:\Python35\lib\site-packages\PIL\PngImagePlugin.py", line 172, in verify
    self.crc(cid, ImageFile._safe_read(self.fp, length))
  File "c:\Python35\lib\site-packages\PIL\PngImagePlugin.py", line 150, in crc
    % cid)
  File "<string>", line None
SyntaxError: broken PNG file (incomplete checksum in b'IDAT')

Note

If you need to load the image after using this method, you must reopen the image file.

Attributes

doc

https://pillow.readthedocs.io/en/latest/reference/Image.html#attributes, http://effbot.org/imagingbook/image.htm#attributes

>>> from PIL import Image
>>> img1 = Image.open("data/srcimg01.jpg")
>>> img1.format, img1.mode, img1.size
('JPEG', 'RGB', (670, 445))
>>> img1.width, img1.height  # == img1.size
(670, 445)
>>> img1.info
{'jfif_version': (1, 1), 'jfif': 257, 'jfif_unit': 0, 'jfif_density': (1, 1)}
>>> img2 = Image.open("data/srcimg09.png")
>>> img2.format, img2.mode, img2.size
('PNG', 'RGBA', (540, 432))
>>> img2.width, img2.height  # == img2.size
(540, 432)
>>> img2.info
{}
>>> img2.palette
>>> img2.convert("P", palette=Image.WEB).palette
<PIL.ImagePalette.ImagePalette object at 0x0000000002F430F0>