Merge pull request #8246 from python-pillow/tutorial-images
BIN
docs/handbook/animated_hopper.gif
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
docs/handbook/contrasted_hopper.jpg
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
docs/handbook/cropped_hopper.webp
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
docs/handbook/enhanced_hopper.webp
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
docs/handbook/flip_left_right_hopper.webp
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
docs/handbook/flip_top_bottom_hopper.webp
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
docs/handbook/hopper_ps.webp
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
docs/handbook/masked_hopper.webp
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
docs/handbook/merged_hopper.webp
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
docs/handbook/pasted_hopper.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
docs/handbook/rebanded_hopper.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
docs/handbook/rolled_hopper.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
docs/handbook/rotated_hopper_180.webp
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
docs/handbook/rotated_hopper_270.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
docs/handbook/rotated_hopper_90.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
docs/handbook/show_hopper.webp
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
docs/handbook/thumbnail_hopper.jpg
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
docs/handbook/transformed_hopper.webp
Normal file
After Width: | Height: | Size: 3.0 KiB |
|
@ -37,6 +37,9 @@ example, let’s display the image we just loaded::
|
||||||
|
|
||||||
>>> im.show()
|
>>> im.show()
|
||||||
|
|
||||||
|
.. image:: show_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The standard version of :py:meth:`~PIL.Image.Image.show` is not very
|
The standard version of :py:meth:`~PIL.Image.Image.show` is not very
|
||||||
|
@ -79,6 +82,9 @@ Convert files to JPEG
|
||||||
except OSError:
|
except OSError:
|
||||||
print("cannot convert", infile)
|
print("cannot convert", infile)
|
||||||
|
|
||||||
|
.. image:: ../../Tests/images/hopper.jpg
|
||||||
|
:align: center
|
||||||
|
|
||||||
A second argument can be supplied to the :py:meth:`~PIL.Image.Image.save`
|
A second argument can be supplied to the :py:meth:`~PIL.Image.Image.save`
|
||||||
method which explicitly specifies a file format. If you use a non-standard
|
method which explicitly specifies a file format. If you use a non-standard
|
||||||
extension, you must always specify the format this way:
|
extension, you must always specify the format this way:
|
||||||
|
@ -103,6 +109,9 @@ Create JPEG thumbnails
|
||||||
except OSError:
|
except OSError:
|
||||||
print("cannot create thumbnail for", infile)
|
print("cannot create thumbnail for", infile)
|
||||||
|
|
||||||
|
.. image:: thumbnail_hopper.jpg
|
||||||
|
:align: center
|
||||||
|
|
||||||
It is important to note that the library doesn’t decode or load the raster data
|
It is important to note that the library doesn’t decode or load the raster data
|
||||||
unless it really has to. When you open a file, the file header is read to
|
unless it really has to. When you open a file, the file header is read to
|
||||||
determine the file format and extract things like mode, size, and other
|
determine the file format and extract things like mode, size, and other
|
||||||
|
@ -140,16 +149,19 @@ Copying a subrectangle from an image
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
box = (100, 100, 400, 400)
|
box = (0, 0, 64, 64)
|
||||||
region = im.crop(box)
|
region = im.crop(box)
|
||||||
|
|
||||||
The region is defined by a 4-tuple, where coordinates are (left, upper, right,
|
The region is defined by a 4-tuple, where coordinates are (left, upper, right,
|
||||||
lower). The Python Imaging Library uses a coordinate system with (0, 0) in the
|
lower). The Python Imaging Library uses a coordinate system with (0, 0) in the
|
||||||
upper left corner. Also note that coordinates refer to positions between the
|
upper left corner. Also note that coordinates refer to positions between the
|
||||||
pixels, so the region in the above example is exactly 300x300 pixels.
|
pixels, so the region in the above example is exactly 64x64 pixels.
|
||||||
|
|
||||||
The region could now be processed in a certain manner and pasted back.
|
The region could now be processed in a certain manner and pasted back.
|
||||||
|
|
||||||
|
.. image:: cropped_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Processing a subrectangle, and pasting it back
|
Processing a subrectangle, and pasting it back
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -164,6 +176,9 @@ modes of the original image and the region do not need to match. If they don’t
|
||||||
the region is automatically converted before being pasted (see the section on
|
the region is automatically converted before being pasted (see the section on
|
||||||
:ref:`color-transforms` below for details).
|
:ref:`color-transforms` below for details).
|
||||||
|
|
||||||
|
.. image:: pasted_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Here’s an additional example:
|
Here’s an additional example:
|
||||||
|
|
||||||
Rolling an image
|
Rolling an image
|
||||||
|
@ -186,6 +201,9 @@ Rolling an image
|
||||||
|
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
.. image:: rolled_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Or if you would like to merge two images into a wider image:
|
Or if you would like to merge two images into a wider image:
|
||||||
|
|
||||||
Merging images
|
Merging images
|
||||||
|
@ -203,6 +221,9 @@ Merging images
|
||||||
|
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
.. image:: merged_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
For more advanced tricks, the paste method can also take a transparency mask as
|
For more advanced tricks, the paste method can also take a transparency mask as
|
||||||
an optional argument. In this mask, the value 255 indicates that the pasted
|
an optional argument. In this mask, the value 255 indicates that the pasted
|
||||||
image is opaque in that position (that is, the pasted image should be used as
|
image is opaque in that position (that is, the pasted image should be used as
|
||||||
|
@ -229,6 +250,9 @@ Note that for a single-band image, :py:meth:`~PIL.Image.Image.split` returns
|
||||||
the image itself. To work with individual color bands, you may want to convert
|
the image itself. To work with individual color bands, you may want to convert
|
||||||
the image to “RGB” first.
|
the image to “RGB” first.
|
||||||
|
|
||||||
|
.. image:: rebanded_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Geometrical transforms
|
Geometrical transforms
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
@ -245,6 +269,9 @@ Simple geometry transforms
|
||||||
out = im.resize((128, 128))
|
out = im.resize((128, 128))
|
||||||
out = im.rotate(45) # degrees counter-clockwise
|
out = im.rotate(45) # degrees counter-clockwise
|
||||||
|
|
||||||
|
.. image:: rotated_hopper_90.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
To rotate the image in 90 degree steps, you can either use the
|
To rotate the image in 90 degree steps, you can either use the
|
||||||
:py:meth:`~PIL.Image.Image.rotate` method or the
|
:py:meth:`~PIL.Image.Image.rotate` method or the
|
||||||
:py:meth:`~PIL.Image.Image.transpose` method. The latter can also be used to
|
:py:meth:`~PIL.Image.Image.transpose` method. The latter can also be used to
|
||||||
|
@ -256,11 +283,38 @@ Transposing an image
|
||||||
::
|
::
|
||||||
|
|
||||||
out = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
out = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
||||||
|
|
||||||
|
.. image:: flip_left_right_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
|
out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
|
||||||
|
|
||||||
|
.. image:: flip_top_bottom_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
out = im.transpose(Image.Transpose.ROTATE_90)
|
out = im.transpose(Image.Transpose.ROTATE_90)
|
||||||
|
|
||||||
|
.. image:: rotated_hopper_90.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
out = im.transpose(Image.Transpose.ROTATE_180)
|
out = im.transpose(Image.Transpose.ROTATE_180)
|
||||||
|
|
||||||
|
.. image:: rotated_hopper_180.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
out = im.transpose(Image.Transpose.ROTATE_270)
|
out = im.transpose(Image.Transpose.ROTATE_270)
|
||||||
|
|
||||||
|
.. image:: rotated_hopper_270.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
``transpose(ROTATE)`` operations can also be performed identically with
|
``transpose(ROTATE)`` operations can also be performed identically with
|
||||||
:py:meth:`~PIL.Image.Image.rotate` operations, provided the ``expand`` flag is
|
:py:meth:`~PIL.Image.Image.rotate` operations, provided the ``expand`` flag is
|
||||||
true, to provide for the same changes to the image's size.
|
true, to provide for the same changes to the image's size.
|
||||||
|
@ -278,7 +332,7 @@ choose to resize relative to a given size.
|
||||||
|
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
size = (100, 150)
|
size = (100, 150)
|
||||||
with Image.open("Tests/images/hopper.webp") as im:
|
with Image.open("hopper.webp") as im:
|
||||||
ImageOps.contain(im, size).save("imageops_contain.webp")
|
ImageOps.contain(im, size).save("imageops_contain.webp")
|
||||||
ImageOps.cover(im, size).save("imageops_cover.webp")
|
ImageOps.cover(im, size).save("imageops_cover.webp")
|
||||||
ImageOps.fit(im, size).save("imageops_fit.webp")
|
ImageOps.fit(im, size).save("imageops_fit.webp")
|
||||||
|
@ -342,6 +396,9 @@ Applying filters
|
||||||
from PIL import ImageFilter
|
from PIL import ImageFilter
|
||||||
out = im.filter(ImageFilter.DETAIL)
|
out = im.filter(ImageFilter.DETAIL)
|
||||||
|
|
||||||
|
.. image:: enhanced_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Point Operations
|
Point Operations
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -355,8 +412,11 @@ Applying point transforms
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
# multiply each pixel by 1.2
|
# multiply each pixel by 20
|
||||||
out = im.point(lambda i: i * 1.2)
|
out = im.point(lambda i: i * 20)
|
||||||
|
|
||||||
|
.. image:: transformed_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Using the above technique, you can quickly apply any simple expression to an
|
Using the above technique, you can quickly apply any simple expression to an
|
||||||
image. You can also combine the :py:meth:`~PIL.Image.Image.point` and
|
image. You can also combine the :py:meth:`~PIL.Image.Image.point` and
|
||||||
|
@ -388,6 +448,9 @@ Note the syntax used to create the mask::
|
||||||
|
|
||||||
imout = im.point(lambda i: expression and 255)
|
imout = im.point(lambda i: expression and 255)
|
||||||
|
|
||||||
|
.. image:: masked_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Python only evaluates the portion of a logical expression as is necessary to
|
Python only evaluates the portion of a logical expression as is necessary to
|
||||||
determine the outcome, and returns the last value examined as the result of the
|
determine the outcome, and returns the last value examined as the result of the
|
||||||
expression. So if the expression above is false (0), Python does not look at
|
expression. So if the expression above is false (0), Python does not look at
|
||||||
|
@ -412,6 +475,10 @@ Enhancing images
|
||||||
enh = ImageEnhance.Contrast(im)
|
enh = ImageEnhance.Contrast(im)
|
||||||
enh.enhance(1.3).show("30% more contrast")
|
enh.enhance(1.3).show("30% more contrast")
|
||||||
|
|
||||||
|
|
||||||
|
.. image:: contrasted_hopper.jpg
|
||||||
|
:align: center
|
||||||
|
|
||||||
Image sequences
|
Image sequences
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -444,10 +511,43 @@ Reading sequences
|
||||||
As seen in this example, you’ll get an :py:exc:`EOFError` exception when the
|
As seen in this example, you’ll get an :py:exc:`EOFError` exception when the
|
||||||
sequence ends.
|
sequence ends.
|
||||||
|
|
||||||
|
Writing sequences
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
You can create animated GIFs with Pillow, e.g.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# List of image filenames
|
||||||
|
image_filenames = [
|
||||||
|
"hopper.jpg",
|
||||||
|
"rotated_hopper_270.jpg",
|
||||||
|
"rotated_hopper_180.jpg",
|
||||||
|
"rotated_hopper_90.jpg",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Open images and create a list
|
||||||
|
images = [Image.open(filename) for filename in image_filenames]
|
||||||
|
|
||||||
|
# Save the images as an animated GIF
|
||||||
|
images[0].save(
|
||||||
|
"animated_hopper.gif",
|
||||||
|
save_all=True,
|
||||||
|
append_images=images[1:],
|
||||||
|
duration=500, # duration of each frame in milliseconds
|
||||||
|
loop=0, # loop forever
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
.. image:: animated_hopper.gif
|
||||||
|
:align: center
|
||||||
|
|
||||||
The following class lets you use the for-statement to loop over the sequence:
|
The following class lets you use the for-statement to loop over the sequence:
|
||||||
|
|
||||||
Using the ImageSequence Iterator class
|
Using the :py:class:`~PIL.ImageSequence.Iterator` class
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
@ -467,25 +567,61 @@ Drawing PostScript
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image, PSDraw
|
||||||
from PIL import PSDraw
|
import os
|
||||||
|
|
||||||
with Image.open("hopper.ppm") as im:
|
# Define the PostScript file
|
||||||
title = "hopper"
|
ps_file = open("hopper.ps", "wb")
|
||||||
box = (1 * 72, 2 * 72, 7 * 72, 10 * 72) # in points
|
|
||||||
|
|
||||||
ps = PSDraw.PSDraw() # default is sys.stdout or sys.stdout.buffer
|
# Create a PSDraw object
|
||||||
ps.begin_document(title)
|
ps = PSDraw.PSDraw(ps_file)
|
||||||
|
|
||||||
# draw the image (75 dpi)
|
# Start the document
|
||||||
ps.image(box, im, 75)
|
ps.begin_document()
|
||||||
ps.rectangle(box)
|
|
||||||
|
|
||||||
# draw title
|
# Set the text to be drawn
|
||||||
ps.setfont("HelveticaNarrow-Bold", 36)
|
text = "Hopper"
|
||||||
ps.text((3 * 72, 4 * 72), title)
|
|
||||||
|
|
||||||
|
# Define the PostScript font
|
||||||
|
font_name = "Helvetica-Narrow-Bold"
|
||||||
|
font_size = 36
|
||||||
|
|
||||||
|
# Calculate text size (approximation as PSDraw doesn't provide direct method)
|
||||||
|
# Assuming average character width as 0.6 of the font size
|
||||||
|
text_width = len(text) * font_size * 0.6
|
||||||
|
text_height = font_size
|
||||||
|
|
||||||
|
# Set the position (top-center)
|
||||||
|
page_width, page_height = 595, 842 # A4 size in points
|
||||||
|
text_x = (page_width - text_width) // 2
|
||||||
|
text_y = page_height - text_height - 50 # Distance from the top of the page
|
||||||
|
|
||||||
|
# Load the image
|
||||||
|
image_path = "hopper.ppm" # Update this with your image path
|
||||||
|
with Image.open(image_path) as im:
|
||||||
|
# Resize the image if it's too large
|
||||||
|
im.thumbnail((page_width - 100, page_height // 2))
|
||||||
|
|
||||||
|
# Define the box where the image will be placed
|
||||||
|
img_x = (page_width - im.width) // 2
|
||||||
|
img_y = text_y + text_height - 200 # 200 points below the text
|
||||||
|
|
||||||
|
# Draw the image (75 dpi)
|
||||||
|
ps.image((img_x, img_y, img_x + im.width, img_y + im.height), im, 75)
|
||||||
|
|
||||||
|
# Draw the text
|
||||||
|
ps.setfont(font_name, font_size)
|
||||||
|
ps.text((text_x, text_y), text)
|
||||||
|
|
||||||
|
# End the document
|
||||||
ps.end_document()
|
ps.end_document()
|
||||||
|
ps_file.close()
|
||||||
|
|
||||||
|
.. image:: hopper_ps.webp
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
PostScript converted to PDF for display purposes
|
||||||
|
|
||||||
More on reading images
|
More on reading images
|
||||||
----------------------
|
----------------------
|
||||||
|
@ -553,7 +689,7 @@ Reading from a tar archive
|
||||||
|
|
||||||
from PIL import Image, TarIO
|
from PIL import Image, TarIO
|
||||||
|
|
||||||
fp = TarIO.TarIO("Tests/images/hopper.tar", "hopper.jpg")
|
fp = TarIO.TarIO("hopper.tar", "hopper.jpg")
|
||||||
im = Image.open(fp)
|
im = Image.open(fp)
|
||||||
|
|
||||||
|
|
||||||
|
@ -568,7 +704,6 @@ in the current directory can be saved as JPEGs at reduced quality.
|
||||||
import glob
|
import glob
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
def compress_image(source_path, dest_path):
|
def compress_image(source_path, dest_path):
|
||||||
with Image.open(source_path) as img:
|
with Image.open(source_path) as img:
|
||||||
if img.mode != "RGB":
|
if img.mode != "RGB":
|
||||||
|
|