In this tutorial, we’ll build ArtForge, a class-based, production-ready AI art generator GUI that uses OpenAI’s Images API. We’ll cover:
Creating the app structure
Building a modern GUI with Tkinter + ttkbootstrap
Connecting to OpenAI’s Images API
Thread-safe batch generation and logging
1️⃣ Project Setup
Before starting, make sure you have the following installed:
pip install openai pillow ttkbootstrap
Tip: ttkbootstrap is a modern styling toolkit for Tkinter. It makes GUIs look sleek without extra CSS.
Create a new Python file:
artforge.py
2️⃣ Import Required Libraries
We’ll import libraries for GUI, threading, file handling, images, and OpenAI API access.
import os
import sys
import threading
import platform
from datetime import datetime
import tkinter as tk
from tkinter import filedialog
import ttkbootstrap as tb
from ttkbootstrap.constants import *
from PIL import Image
import base64
import openai
Explanation:
tkinter → GUI elements
ttkbootstrap → modern styling
threading → run generation without freezing the GUI
PIL → image handling
openai → connect to OpenAI Images API
3️⃣ Set Up Your OpenAI API Key
Make sure your environment variable is set:
# Linux / macOS
export OPENAI_API_KEY="your_api_key_here"
# Windows
setx OPENAI_API_KEY "your_api_key_here"
Then, in your code:
openai.api_key = os.getenv("OPENAI_API_KEY")
Tip: Never hardcode your API key. Use environment variables for security.
4️⃣ Define the App Class
We’ll use a class-based structure for better organization:
class ArtForgeApp:
APP_NAME = "ArtForge"
APP_VERSION = "1.1.0"
def __init__(self):
# Root window
self.root = tk.Tk()
self.style = tb.Style(theme="darkly")
self.root.title(f"{self.APP_NAME} v{self.APP_VERSION}")
self.root.geometry("1100x720")
Explanation:
self.root → main window
tb.Style(theme="darkly") → applies a dark theme
geometry → sets window size
5️⃣ App Directories & State Variables
We’ll create a folder for saving images and initialize GUI state variables:
# App directories
self.home_dir = os.path.expanduser("~")
self.app_dir = (
os.path.join(self.home_dir, "AppData", "Local", self.APP_NAME)
if platform.system() == "Windows"
else os.path.join(self.home_dir, f".{self.APP_NAME.lower()}")
)
os.makedirs(self.app_dir, exist_ok=True)
# State variables
self.stop_event = threading.Event()
self.progress_var = tk.IntVar(value=0)
self.output_dir = tk.StringVar(value=self.app_dir)
Tip: Using stop_event allows us to stop image generation safely without freezing the GUI.
6️⃣ Build the User Interface (UI)
We’ll create sections: Prompt, Settings, Log, and Controls.
Prompt Input:
prompt_frame = tb.Labelframe(self.root, text="Prompt", padding=10)
prompt_frame.pack(fill="x", padx=12, pady=6)
self.prompt_box = tk.Text(prompt_frame, height=4)
self.prompt_box.pack(fill="x", expand=True)
Settings & Output Folder:
settings = tb.Labelframe(self.root, text="Generation Settings", padding=10)
settings.pack(fill="x", padx=12, pady=6)
self.images_var = tk.IntVar(value=1)
tb.Label(settings, text="Images").pack(side="left")
tb.Spinbox(settings, from_=1, to=10, textvariable=self.images_var, width=6).pack(side="left", padx=6)
tb.Button(
settings, text="📁 Output Folder",
bootstyle="secondary-outline",
command=self.choose_output
).pack(side="right")
Explanation:
Text → multi-line input for prompts
Spinbox → number of images to generate
Button → choose output folder
7️⃣ Logging & Progress
preview = tb.Labelframe(self.root, text="Generation Log", padding=10)
preview.pack(fill="both", expand=True, padx=12, pady=6)
self.log_view = tk.Text(preview, height=10)
self.log_view.pack(fill="both", expand=True)
self.progress = tb.Progressbar(
self.root,
variable=self.progress_var,
maximum=100,
bootstyle="success-striped"
)
self.progress.pack(fill="x", padx=12, pady=6)
Tip: Logging helps track generation progress and errors.
8️⃣ File Dialog for Output
def choose_output(self):
path = filedialog.askdirectory()
if path:
self.output_dir.set(path)
This allows the user to select where generated images are saved.
9️⃣ Generating Images via OpenAI
def generate_image_api(self, prompt):
response = openai.images.generate(
model="gpt-image-1",
prompt=prompt,
size="512x512"
)
image_base64 = response.data[0].b64_json
image_bytes = base64.b64decode(image_base64)
return image_bytes
Explanation:
Calls the OpenAI Images API
Returns PNG image bytes from base64
🔟 Run Batch Generation
def run_generation(self):
prompt = self.prompt_box.get("1.0", tk.END).strip()
if not prompt or not openai.api_key:
self.log_view.insert(tk.END, "⚠️ Prompt or API key missing!\n")
return
self.stop_event.clear()
self.progress_var.set(0)
for i in range(self.images_var.get()):
if self.stop_event.is_set():
break
try:
img_bytes = self.generate_image_api(prompt)
except Exception as e:
self.log_view.insert(tk.END, f"❌ API error: {e}\n")
break
filename = f"art_{datetime.now().strftime('%H%M%S')}_{i+1}.png"
filepath = os.path.join(self.output_dir.get(), filename)
with open(filepath, "wb") as f:
f.write(img_bytes)
self.log_view.insert(tk.END, f"✔ Generated {filename}\n")
self.progress_var.set(int(((i + 1) / self.images_var.get()) * 100))
self.log_view.insert(tk.END, "✅ Generation complete\n")
Thread-safe: Run run_generation in a separate thread to keep UI responsive.
1️⃣1️⃣ Stop Generation Button
def stop_generation(self):
self.stop_event.set()
self.log_view.insert(tk.END, "⛔ Generation stopped by user\n")
This allows safely stopping long-running image batches.
1️⃣2️⃣ About Window
def show_about(self):
win = tb.Toplevel(self.root)
win.title(f"{self.APP_NAME} v{self.APP_VERSION} – About")
win.geometry("500x380")
tb.Label(win, text=f"{self.APP_NAME} v{self.APP_VERSION}", font=("Segoe UI", 14, "bold")).pack()
tb.Label(win, text="AI art generator powered by OpenAI.", wraplength=460).pack()
Extra UI polish: users can view app info.
1️⃣3️⃣ Run the App
if __name__ == "__main__":
app = ArtForgeApp()
app.run()
✅ Congrats! You now have a modern AI art generator GUI with:
Multi-image batch generation
Thread-safe UI
Progress bar and logging
Custom output folder
Darkly themed Tkinter interface

Top comments (0)