Title: Fill a shape with an image with Python and PIL
This isn't particularly hard once you figure it out, but there are several steps. First, I'll explain the approach in English. Then you can read the code to see how it works.
Steps
Here are the steps for drawing an image inside a shape:
- Prepare the background image.
- Either load the image from a file or create it from scratch.
- (Optional) Draw stuff on the background image. (To do this, create an ImageDraw object for the image and use its methods.)
- Create a mask image.
- Create a mask image that uses 8-bit grayscale (mode L) or one bit per pixel (mode 1). Initially the image should be black.
- Create an ImageDraw object to draw on the mask image.
- Use the ImageDraw object to fill the area where you want the image in white. In other words, the result should be black where you do not want the image and white where you do want the image.
- Either load the fill image from a file or create it from scratch.
- Call the background image's paste method passing it the fill image, the location where it should be drawn, and the mask image. (If you use None for the location, it is placed at (0, 0). If you include the location, the image and mask are shifted.)
- Display the result or save it into a file as usual.
Python Code
Here's how the example program create its main window and displays its image.
def __init__(self):
self.window = tk.Tk()
self.window.title('pil_fill_star')
self.window.protocol('WM_DELETE_WINDOW', self.kill_callback)
self.window.geometry('300x300')
# Load the fill image.
fill_image = Image.open('robot.jpg')
wid = fill_image.width
hgt = fill_image.height
# Create the result image with a light yellow background.
result_image = Image.new('RGB', (wid, hgt), 'lightyellow')
# Create a Draw object to draw on the result image.
result_dr = ImageDraw.Draw(result_image)
# Draw something to be the background.
radius = math.sqrt(3) * max(wid, hgt) - 10
skip = 3
color = 'red'
for degrees in range(0, 90, skip):
radians = math.radians(degrees)
x = radius * math.cos(radians)
y = radius * math.sin(radians)
result_dr.line([(0, 0), (x, y)], fill=color)
dx = radius * math.cos(math.pi / 2 + radians)
dy = radius * math.sin(math.pi / 2 + radians)
result_dr.line([(wid, 0), (wid + dx, dy)], fill=color)
# Get the star's points.
bbox = (0, 0, wid - 1, hgt - 1)
points = get_star_points(-math.pi / 2, 7, 2, bbox)
# Create a mask from the star.
# L = 8-bit grayscale. 0 = black background.
mask_image = Image.new('L', (wid, hgt), 0)
# Make a Draw object to draw on the mask.
mask_dr = ImageDraw.Draw(mask_image)
# Fill the mask with 255 = white.
mask_dr.polygon(points, fill=255)
# Paste the fill image onto the base image using the mask
# None means upper left corner is (0, 0).
result_image.paste(fill_image, None, mask_image)
# Draw the an outline around the star.
result_dr.polygon(points, outline='blue', width=5)
# Save the result_image (just to show we can).
result_image.save('result.jpg')
# Convert the image into a PhotoImage for tkinter to display.
photo_image = ImageTk.PhotoImage(result_image)
# Create the canvas sized to fit the image.
self.canvas = tk.Canvas(self.window, bg='white', width=wid, height=hgt,
borderwidth=0, highlightthickness=0)
margin = 10
self.canvas.pack(side=tk.TOP, anchor=tk.NW, padx=margin, pady=margin)
# Display the image on the canvas.
self.canvas.create_image(0, 0, image=photo_image, anchor=tk.NW)
# Size the window to fit.
self.window.geometry(f'{wid + 2 * margin}x{hgt + 2 * margin}')
# Display the window.
self.window.focus_force()
self.window.mainloop()
In addition to the basic steps, this code draws some lines on the background image. Then, after it fills the star with the robot image, it outlines the star.
The code also saves the mask image and resulting image, shown in the picture below, just so you can take a look at them.
If you use a one-bit-per-pixel mask image (mode 1), then each pixel is either black or white. However, if you use an 8-bit mask image (mode L), the mask can contain shades of gray and the fill image will be more or less opaque depending on the pixels' values.
For example, if you initially fill an 8-bit mask image with the pixel value 100 (out of 255), the fill image shows through translucently outside of the star. The picture below shows the mask and its result. (I also removed the red lines and the star's outline to make the result easier to see.)
Conclusion
Filling a shape with an image requires a few extra steps to make a mask image, but the result can be pretty impressive. Download the example to experiment with the program and to see additional details.
|