DEV Community

Mate Technologies
Mate Technologies

Posted on

🖼️ Build a Batch Image Compression Tool in Python (Tkinter + ttkbootstrap)

In this tutorial, we’ll build PixelSqueeze, a modern desktop image compression tool using Python.

By the end, you’ll have:

A drag & drop GUI

Batch image compression (JPG / PNG / WEBP)

Live progress tracking

A portable Windows EXE

👉 Source code:
https://github.com/rogers-cyber/PixelSqueeze

👉 Windows EXE (Release):
https://github.com/rogers-cyber/PixelSqueeze/releases/tag/v1.0.0

🚀 What Is PixelSqueeze?

PixelSqueeze is a lightweight, open-source desktop app that compresses images without noticeable quality loss.

Features

Drag & drop files and folders

Batch image processing

JPG / PNG / WEBP output

Adjustable JPG quality

Live progress & speed display

Portable Windows EXE (no install)

🧰 Step 1: Install Dependencies

Create a virtual environment (optional but recommended), then install:

pip install pillow ttkbootstrap tkinterdnd2 pillow-heif
Enter fullscreen mode Exit fullscreen mode

Notes:

Tkinter comes bundled with Python

pillow-heif enables HEIC support (optional)

tkinterdnd2 enables drag & drop

🧱 Step 2: Project Structure

A simple structure is enough:

PixelSqueeze/
├── PixelSqueeze.py
├── requirements.txt
├── README.md
├── LICENSE
└── assets/
└── logo.ico

🖥️ Step 3: Create the Main Application Class

We start with a class-based design, which keeps the app clean and scalable.

class PixelSqueezeApp:
    APP_NAME = "PixelSqueeze Free Version"
    APP_VERSION = "1.0.0"
Enter fullscreen mode Exit fullscreen mode

Using constants makes it easy to update branding and versions later.

🎨 Step 4: Initialize the Tkinter Window

We use ttkbootstrap for a modern dark UI and enable drag & drop if available.

self.root = TkinterDnD.Tk() if DND_ENABLED else tk.Tk()
tb.Style(theme="darkly")
self.root.title(f"{self.APP_NAME} v{self.APP_VERSION}")
self.root.geometry("1020x630")
Enter fullscreen mode Exit fullscreen mode

Why ttkbootstrap?

Modern themes

Better widgets

Cleaner UI with minimal effort

📦 Step 5: Define Supported Formats

This allows safe filtering of files:

SUPPORTED_FORMATS = (
    ".png", ".jpg", ".jpeg", ".bmp", ".gif",
    ".tif", ".tiff", ".webp", ".heic"
)
Enter fullscreen mode Exit fullscreen mode

Output formats are mapped for flexibility:

OUTPUT_FORMATS = {
    "JPG (Best for Photos)": "JPEG",
    "PNG (Best for Graphics)": "PNG",
    "WEBP (Best for Web)": "WEBP"
}
Enter fullscreen mode Exit fullscreen mode

🧩 Step 6: Build the File List UI

A simple Listbox shows queued files:

self.listbox = tk.Listbox(box)
self.listbox.pack(side="left", fill="both", expand=True)
Enter fullscreen mode Exit fullscreen mode

Enable drag & drop if available:

self.listbox.drop_target_register(DND_FILES)
self.listbox.dnd_bind("<<Drop>>", self.on_drop)
Enter fullscreen mode Exit fullscreen mode

This makes the app feel native and modern.

🎛️ Step 7: Options Panel (Format & Quality)

Users can choose output format and quality:

self.format_var = tk.StringVar(value="JPG (Best for Photos)")
self.quality_var = tk.IntVar(value=90)
Enter fullscreen mode Exit fullscreen mode

Disable the quality slider for non-JPG formats:

def on_format_change(self, _=None):
    is_jpg = self.OUTPUT_FORMATS[self.format_var.get()] == "JPEG"
    self.quality_scale.config(state="normal" if is_jpg else "disabled")
Enter fullscreen mode Exit fullscreen mode

⚙️ Step 8: Background Image Conversion (Threaded)

To keep the UI responsive, compression runs in a background thread.

threading.Thread(
    target=self.convert_images,
    daemon=True
).start()


Inside convert_images():

with Image.open(src) as img:
    if fmt in ("JPEG", "WEBP"):
        img = img.convert("RGB")
Enter fullscreen mode Exit fullscreen mode

Saving with optimization:

img.save(
    dst,
    "JPEG",
    quality=self.quality_var.get(),
    optimize=True,
    progressive=True
)
Enter fullscreen mode Exit fullscreen mode

📊 Step 9: Live Progress & Speed Tracking

We use a queue to safely update the UI from the worker thread.

self.ui_queue.put(("progress", done))
self.ui_queue.put(("speed", f"{speed:.2f} files/sec"))
Enter fullscreen mode Exit fullscreen mode

Processed in the main loop:

def process_ui_queue(self):
    while not self.ui_queue.empty():
        key, value = self.ui_queue.get()
        if key == "progress":
            self.progress_var.set(value)
        elif key == "speed":
            self.speed_var.set(value)
Enter fullscreen mode Exit fullscreen mode

This avoids Tkinter threading crashes.

ℹ️ Step 10: About Dialog (No Minimize Button)

A clean modal About window:

win = tb.Toplevel(self.root)
win.resizable(False, False)
win.grab_set()
win.attributes("-toolwindow", True)
Enter fullscreen mode Exit fullscreen mode

Perfect for:

App description

Feature list

Developer info

📦 Step 11: Build a Portable Windows EXE

Use PyInstaller:

pyinstaller --onefile --windowed PixelSqueeze.py

Your EXE will appear in:

dist/PixelSqueeze.exe

👉 Download ready-made EXE here:
https://github.com/rogers-cyber/PixelSqueeze/releases/tag/v1.0.0

🏁 Final Thoughts

PixelSqueeze shows how powerful Python + Tkinter can be for real desktop apps.

You learned how to:

Build a modern GUI

Handle batch image processing

Use threading safely

Ship a real Windows application

⭐ If you found this useful, consider starring the repo:
https://github.com/rogers-cyber/PixelSqueeze

Happy coding 🚀

Top comments (0)