Building a Circle Crop Tool with Tkinter and PIL in Python

Javelin
4 min readSep 2, 2024

--

In this tutorial, we will create a simple graphical application using Python’s tkinter and Pillow libraries. This application will allow users to load an image, draw a circle on it, and then crop the image to the circle. The application will also enable users to save the cropped image.

Source Code

Download Release

Prerequisites

Before you begin, ensure you have the necessary libraries installed. You can install them using pip if they’re not already installed:

pip install pillow

Overview

Our application will have the following features:

  1. Load an Image: Allows users to select an image file.
  2. Draw and Move Circle: Users can draw and move a circle on the image.
  3. Save Cropped Image: Saves the image cropped to the circle’s area.

Step-by-Step Guide

1. Import Required Libraries

We need tkinter for the GUI, Pillow (PIL) for image manipulation, and some additional libraries for encoding and decoding image data.

import tkinter as tk
from tkinter import filedialog, Canvas
from PIL import Image, ImageDraw, ImageTk
import math
import base64
from io import BytesIO

2. Define the Main Application Class

Create a class CircleCropApp that encapsulates the functionality of the application.

class CircleCropApp:
def __init__(self, root):
self.root = root
self.root.title("Circle Crop Tool")
self.set_icon()

self.button_frame = tk.Frame(root)
self.button_frame.pack(side=tk.TOP, fill=tk.X)

self.load_button = tk.Button(self.button_frame, text="Load Image", command=self.load_image)
self.load_button.pack(side=tk.LEFT)

self.save_button = tk.Button(self.button_frame, text="Save Image", command=self.save_image)
self.save_button.pack(side=tk.LEFT)

self.canvas = Canvas(root, width=800, height=600)
self.canvas.pack(fill=tk.BOTH, expand=True)

self.image = None
self.image_id = None
self.circle_id = None
self.circle_center = (0, 0)
self.circle_radius = 0
self.drawing = False
self.moving = False

self.canvas.bind("<Button-1>", self.on_click)
self.canvas.bind("<B1-Motion>", self.move_circle)
self.canvas.bind("<ButtonRelease-1>", self.finish_moving)

3. Set the Application Icon

You can set a custom icon for the window using a base64-encoded image string.

    def set_icon(self):
base64_icon = """
AAABAAEAAAA==
"""
icon_data = base64.b64decode(base64_icon)
icon_image = Image.open(BytesIO(icon_data))
icon_image = icon_image.resize((16, 16), Image.LANCZOS)
self.icon_photo = ImageTk.PhotoImage(icon_image)
self.root.iconphoto(False, self.icon_photo)

4. Load and Display Image

Implement functionality to load and display an image on the canvas.

    def load_image(self):
file_path = filedialog.askopenfilename(filetypes=[("Image files", "*.jpg;*.png")])
if file_path:
self.image = Image.open(file_path).convert("RGBA")
self.display_image()

def display_image(self):
self.tk_image = ImageTk.PhotoImage(self.image)
self.canvas.config(width=self.image.width, height=self.image.height)
if self.image_id:
self.canvas.delete(self.image_id)
self.image_id = self.canvas.create_image(self.image.width // 2, self.image.height // 2, anchor=tk.CENTER, image=self.tk_image)

5. Handle Circle Drawing and Moving

Handle mouse events to draw a circle and move it.

    def on_click(self, event):
if self.is_within_circle(event.x, event.y):
self.moving = True
self.offset_x = event.x - self.circle_center[0]
self.offset_y = event.y - self.circle_center[1]
else:
self.drawing = True
self.circle_center = (event.x, event.y)
self.circle_radius = 0
self.draw_circle()

def move_circle(self, event):
if self.moving:
self.circle_center = (event.x - self.offset_x, event.y - self.offset_y)
self.draw_circle()
elif self.drawing:
self.circle_radius = int(math.sqrt((event.x - self.circle_center[0]) ** 2 + (event.y - self.circle_center[1]) ** 2))
self.draw_circle()

def finish_moving(self, event):
self.moving = False
self.drawing = False

6. Draw the Circle on the Canvas

Redraw the circle based on user actions.

    def draw_circle(self):
if self.circle_id:
self.canvas.delete(self.circle_id)

if self.circle_radius > 0:
x, y = self.circle_center
self.circle_id = self.canvas.create_oval(
x - self.circle_radius, y - self.circle_radius,
x + self.circle_radius, y + self.circle_radius,
outline='red', width=2
)

def is_within_circle(self, x, y):
cx, cy = self.circle_center
return (x - cx) ** 2 + (y - cy) ** 2 <= self.circle_radius ** 2

7. Save the Cropped Image

Implement the functionality to save the cropped image.

    def save_image(self):
if self.image:
result = Image.new("RGBA", self.image.size, (0, 0, 0, 0))
mask = Image.new("L", self.image.size, 0)
draw = ImageDraw.Draw(mask)
x, y = self.circle_center
draw.ellipse((x - self.circle_radius, y - self.circle_radius,
x + self.circle_radius, y + self.circle_radius), fill=255)

result.paste(self.image, (0, 0), mask)

left = x - self.circle_radius
upper = y - self.circle_radius
right = x + self.circle_radius
lower = y + self.circle_radius
result = result.crop((left, upper, right, lower))

save_path = filedialog.asksaveasfilename(defaultextension=".png",
filetypes=[("PNG files", "*.png")])
if save_path:
result.save(save_path)

8. Run the Application

Finally, set up the main function to run the application.

if __name__ == "__main__":
root = tk.Tk()
app = CircleCropApp(root)
root.mainloop()

Packaging Your Application

To distribute your application as a standalone executable, you can use PyInstaller. Here’s how you can package your script into a single executable file:

Install PyInstaller: If you haven’t already installed PyInstaller, you can do so with pip:

pip install pyinstaller

Create the Executable: Use the following command to package your Python script into a standalone executable. Replace icon.ico with the path to your icon file if you have one, and ImageCrop.py with the name of your Python script:

pyinstaller --onefile --icon=icon.ico ImageCrop.py --windowed
  • --onefile bundles everything into a single executable.
  • --icon=icon.ico sets the icon for your application.
  • --windowed prevents a terminal window from opening alongside your GUI application on Windows.

After running the command, PyInstaller will generate the executable in the dist directory. You can now distribute this standalone executable to users who don’t need to have Python installed.

Conclusion

In this tutorial, we’ve built a simple circle cropping tool using Python’s tkinter and Pillow libraries. This tool allows users to load an image, draw a circle on it, and save the cropped result. You can further enhance this tool by adding features such as resizing the circle, or by improving the user interface. Happy coding!

--

--

No responses yet