diff --git a/docs/handbook/animated_hopper.gif b/docs/handbook/animated_hopper.gif new file mode 100644 index 00000000000..e6eeaffcc4e Binary files /dev/null and b/docs/handbook/animated_hopper.gif differ diff --git a/docs/handbook/contrasted_hopper.jpg b/docs/handbook/contrasted_hopper.jpg new file mode 100644 index 00000000000..b2d7cfacbb6 Binary files /dev/null and b/docs/handbook/contrasted_hopper.jpg differ diff --git a/docs/handbook/cropped_hopper.webp b/docs/handbook/cropped_hopper.webp new file mode 100644 index 00000000000..8d0ae4f9757 Binary files /dev/null and b/docs/handbook/cropped_hopper.webp differ diff --git a/docs/handbook/enhanced_hopper.webp b/docs/handbook/enhanced_hopper.webp new file mode 100644 index 00000000000..a582ac0c216 Binary files /dev/null and b/docs/handbook/enhanced_hopper.webp differ diff --git a/docs/handbook/flip_left_right_hopper.webp b/docs/handbook/flip_left_right_hopper.webp new file mode 100644 index 00000000000..59452268dfe Binary files /dev/null and b/docs/handbook/flip_left_right_hopper.webp differ diff --git a/docs/handbook/flip_top_bottom_hopper.webp b/docs/handbook/flip_top_bottom_hopper.webp new file mode 100644 index 00000000000..28af4bf3659 Binary files /dev/null and b/docs/handbook/flip_top_bottom_hopper.webp differ diff --git a/docs/handbook/hopper_ps.webp b/docs/handbook/hopper_ps.webp new file mode 100644 index 00000000000..3dd2943d682 Binary files /dev/null and b/docs/handbook/hopper_ps.webp differ diff --git a/docs/handbook/masked_hopper.webp b/docs/handbook/masked_hopper.webp new file mode 100644 index 00000000000..ef892cb94f7 Binary files /dev/null and b/docs/handbook/masked_hopper.webp differ diff --git a/docs/handbook/merged_hopper.webp b/docs/handbook/merged_hopper.webp new file mode 100644 index 00000000000..324628358e8 Binary files /dev/null and b/docs/handbook/merged_hopper.webp differ diff --git a/docs/handbook/pasted_hopper.webp b/docs/handbook/pasted_hopper.webp new file mode 100644 index 00000000000..90dc3199405 Binary files /dev/null and b/docs/handbook/pasted_hopper.webp differ diff --git a/docs/handbook/rebanded_hopper.webp b/docs/handbook/rebanded_hopper.webp new file mode 100644 index 00000000000..7a9069c9f3d Binary files /dev/null and b/docs/handbook/rebanded_hopper.webp differ diff --git a/docs/handbook/rolled_hopper.webp b/docs/handbook/rolled_hopper.webp new file mode 100644 index 00000000000..7fe80246363 Binary files /dev/null and b/docs/handbook/rolled_hopper.webp differ diff --git a/docs/handbook/rotated_hopper_180.webp b/docs/handbook/rotated_hopper_180.webp new file mode 100644 index 00000000000..08e14f0e3a1 Binary files /dev/null and b/docs/handbook/rotated_hopper_180.webp differ diff --git a/docs/handbook/rotated_hopper_270.webp b/docs/handbook/rotated_hopper_270.webp new file mode 100644 index 00000000000..ead2e102e23 Binary files /dev/null and b/docs/handbook/rotated_hopper_270.webp differ diff --git a/docs/handbook/rotated_hopper_90.webp b/docs/handbook/rotated_hopper_90.webp new file mode 100644 index 00000000000..9a5f70b2030 Binary files /dev/null and b/docs/handbook/rotated_hopper_90.webp differ diff --git a/docs/handbook/show_hopper.webp b/docs/handbook/show_hopper.webp new file mode 100644 index 00000000000..5cb73325b35 Binary files /dev/null and b/docs/handbook/show_hopper.webp differ diff --git a/docs/handbook/thumbnail_hopper.jpg b/docs/handbook/thumbnail_hopper.jpg new file mode 100644 index 00000000000..8c10589c2cf Binary files /dev/null and b/docs/handbook/thumbnail_hopper.jpg differ diff --git a/docs/handbook/transformed_hopper.webp b/docs/handbook/transformed_hopper.webp new file mode 100644 index 00000000000..be5e90f9677 Binary files /dev/null and b/docs/handbook/transformed_hopper.webp differ diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index 6cb1e26392c..c36011362c6 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -37,6 +37,9 @@ example, let’s display the image we just loaded:: >>> im.show() +.. image:: show_hopper.webp + :align: center + .. note:: The standard version of :py:meth:`~PIL.Image.Image.show` is not very @@ -79,6 +82,9 @@ Convert files to JPEG except OSError: 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` method which explicitly specifies a file format. If you use a non-standard extension, you must always specify the format this way: @@ -103,6 +109,9 @@ Create JPEG thumbnails except OSError: 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 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 @@ -140,16 +149,19 @@ Copying a subrectangle from an image :: - box = (100, 100, 400, 400) + box = (0, 0, 64, 64) region = im.crop(box) 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 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. +.. image:: cropped_hopper.webp + :align: center + 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 :ref:`color-transforms` below for details). +.. image:: pasted_hopper.webp + :align: center + Here’s an additional example: Rolling an image @@ -186,6 +201,9 @@ Rolling an image return im +.. image:: rolled_hopper.webp + :align: center + Or if you would like to merge two images into a wider image: Merging images @@ -203,6 +221,9 @@ Merging images return im +.. image:: merged_hopper.webp + :align: center + 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 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 to “RGB” first. +.. image:: rebanded_hopper.webp + :align: center + Geometrical transforms ---------------------- @@ -245,6 +269,9 @@ Simple geometry transforms out = im.resize((128, 128)) 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 :py:meth:`~PIL.Image.Image.rotate` method or the :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) + +.. image:: flip_left_right_hopper.webp + :align: center + +:: + out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM) + +.. image:: flip_top_bottom_hopper.webp + :align: center + +:: + out = im.transpose(Image.Transpose.ROTATE_90) + +.. image:: rotated_hopper_90.webp + :align: center + +:: + out = im.transpose(Image.Transpose.ROTATE_180) + +.. image:: rotated_hopper_180.webp + :align: center + +:: + out = im.transpose(Image.Transpose.ROTATE_270) +.. image:: rotated_hopper_270.webp + :align: center + ``transpose(ROTATE)`` operations can also be performed identically with :py:meth:`~PIL.Image.Image.rotate` operations, provided the ``expand`` flag is 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 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.cover(im, size).save("imageops_cover.webp") ImageOps.fit(im, size).save("imageops_fit.webp") @@ -342,6 +396,9 @@ Applying filters from PIL import ImageFilter out = im.filter(ImageFilter.DETAIL) +.. image:: enhanced_hopper.webp + :align: center + Point Operations ^^^^^^^^^^^^^^^^ @@ -355,8 +412,11 @@ Applying point transforms :: - # multiply each pixel by 1.2 - out = im.point(lambda i: i * 1.2) + # multiply each pixel by 20 + 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 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) +.. image:: masked_hopper.webp + :align: center + 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 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.enhance(1.3).show("30% more contrast") + +.. image:: contrasted_hopper.jpg + :align: center + Image sequences --------------- @@ -444,10 +511,43 @@ Reading sequences As seen in this example, you’ll get an :py:exc:`EOFError` exception when the 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: -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 PSDraw + from PIL import Image, PSDraw + import os - with Image.open("hopper.ppm") as im: - title = "hopper" - box = (1 * 72, 2 * 72, 7 * 72, 10 * 72) # in points + # Define the PostScript file + ps_file = open("hopper.ps", "wb") + + # Create a PSDraw object + ps = PSDraw.PSDraw(ps_file) + + # Start the document + ps.begin_document() + + # Set the text to be drawn + text = "Hopper" + + # 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 - ps = PSDraw.PSDraw() # default is sys.stdout or sys.stdout.buffer - ps.begin_document(title) + # 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 - # draw the image (75 dpi) - ps.image(box, im, 75) - ps.rectangle(box) + # 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)) - # draw title - ps.setfont("HelveticaNarrow-Bold", 36) - ps.text((3 * 72, 4 * 72), title) + # 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 - ps.end_document() + # 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_file.close() + +.. image:: hopper_ps.webp + +.. note:: + + PostScript converted to PDF for display purposes More on reading images ---------------------- @@ -553,7 +689,7 @@ Reading from a tar archive 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) @@ -568,7 +704,6 @@ in the current directory can be saved as JPEGs at reduced quality. import glob from PIL import Image - def compress_image(source_path, dest_path): with Image.open(source_path) as img: if img.mode != "RGB":