Skip to content

Commit b609eb7

Browse files
authored
Create AI-Coloring-Book-Maker.py
1 parent 04b12d7 commit b609eb7

1 file changed

Lines changed: 302 additions & 0 deletions

File tree

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
import tkinter as tk
2+
from tkinter import messagebox, filedialog
3+
import ttkbootstrap as tb
4+
from ttkbootstrap.widgets.scrolled import ScrolledFrame
5+
import threading
6+
import os
7+
import sys
8+
from PIL import Image, ImageDraw, ImageTk
9+
import random
10+
11+
# Optional for PDF export
12+
from reportlab.lib.pagesizes import A4
13+
from reportlab.pdfgen import canvas
14+
from reportlab.lib.utils import ImageReader
15+
16+
# Optional for Stable Diffusion API
17+
import requests
18+
import base64
19+
from io import BytesIO
20+
21+
# =================== APP INFO ===================
22+
APP_NAME = "AI Coloring Book Maker"
23+
VERSION = "1.0"
24+
25+
# =================== UTIL ===================
26+
def resource_path(file_name):
27+
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
28+
return os.path.join(base_path, file_name)
29+
30+
def show_error(title, msg):
31+
messagebox.showerror(title, msg)
32+
33+
def show_info(title, msg):
34+
messagebox.showinfo(title, msg)
35+
36+
# =================== APP ===================
37+
app = tb.Window(f"{APP_NAME} v{VERSION}", themename="superhero", size=(1300, 720))
38+
app.grid_columnconfigure(0, weight=1)
39+
app.grid_columnconfigure(1, weight=2)
40+
app.grid_rowconfigure(1, weight=1)
41+
42+
try:
43+
app.iconbitmap(resource_path("icon.ico"))
44+
except Exception:
45+
pass
46+
47+
generated_images = []
48+
selected_images = set()
49+
thumbnail_labels = []
50+
51+
# ================= LEFT PANEL =================
52+
left_panel = tb.Labelframe(app, text="AI Prompt Settings", padding=15)
53+
left_panel.grid(row=0, column=0, rowspan=2, sticky="nsew", padx=10, pady=10)
54+
55+
tb.Label(left_panel, text="Prompt / Theme").pack(anchor="w")
56+
prompt_entry = tb.Entry(left_panel)
57+
prompt_entry.pack(fill="x", pady=5)
58+
59+
tb.Label(left_panel, text="Art Style").pack(anchor="w")
60+
style_combo = tb.Combobox(
61+
left_panel,
62+
values=[
63+
"Cartoon Coloring Page",
64+
"Cute Kawaii Line Art",
65+
"Kids Coloring Book",
66+
"Mandala Line Art",
67+
"Simple Thick Outline"
68+
],
69+
state="readonly"
70+
)
71+
style_combo.set("Cartoon Coloring Page")
72+
style_combo.pack(fill="x", pady=5)
73+
74+
tb.Label(left_panel, text="Image Size").pack(anchor="w")
75+
size_combo = tb.Combobox(
76+
left_panel,
77+
values=["256x256", "512x512", "768x768", "1024x1024"],
78+
state="readonly"
79+
)
80+
size_combo.set("512x512")
81+
size_combo.pack(fill="x", pady=5)
82+
83+
tb.Label(left_panel, text="Batch Count (1–10)").pack(anchor="w")
84+
batch_spin = tb.Spinbox(left_panel, from_=1, to=10, width=5)
85+
batch_spin.set(4)
86+
batch_spin.pack(anchor="w", pady=5)
87+
88+
generate_btn = tb.Button(
89+
left_panel,
90+
text="Generate Images",
91+
bootstyle="success",
92+
width=25
93+
)
94+
generate_btn.pack(pady=15)
95+
96+
save_btn = tb.Button(
97+
left_panel,
98+
text="Save Selected Images",
99+
bootstyle="primary",
100+
width=25
101+
)
102+
save_btn.pack(pady=5)
103+
104+
export_pdf_btn = tb.Button(
105+
left_panel,
106+
text="Export Coloring Book (PDF)",
107+
bootstyle="warning",
108+
width=25
109+
)
110+
export_pdf_btn.pack(pady=5)
111+
112+
# ================= RIGHT PANEL =================
113+
preview_panel = tb.Labelframe(app, text="Thumbnail Preview", padding=10)
114+
preview_panel.grid(row=0, column=1, rowspan=2, sticky="nsew", padx=10, pady=10)
115+
preview_panel.grid_rowconfigure(0, weight=1)
116+
preview_panel.grid_columnconfigure(0, weight=1)
117+
118+
scroll = ScrolledFrame(preview_panel, autohide=True)
119+
scroll.pack(fill="both", expand=True)
120+
121+
# ================= AI PLACEHOLDER =================
122+
def fake_ai_generate(size):
123+
img = Image.new("RGB", size, "white")
124+
d = ImageDraw.Draw(img)
125+
for _ in range(15):
126+
x1 = random.randint(0, size[0])
127+
y1 = random.randint(0, size[1])
128+
x2 = random.randint(0, size[0])
129+
y2 = random.randint(0, size[1])
130+
d.line((x1, y1, x2, y2), fill="black", width=3)
131+
return img
132+
133+
# Optional: Stable Diffusion generator
134+
def stable_diffusion_generate(prompt, size):
135+
# Replace URL with your SD WebUI API endpoint
136+
try:
137+
payload = {
138+
"prompt": f"{prompt}, black and white coloring page, line art, no shading",
139+
"negative_prompt": "color, gray, shadows, text",
140+
"steps": 25,
141+
"width": size[0],
142+
"height": size[1],
143+
"cfg_scale": 7,
144+
"sampler_name": "Euler a"
145+
}
146+
r = requests.post("http://127.0.0.1:7860/sdapi/v1/txt2img", json=payload)
147+
r.raise_for_status()
148+
img_data = base64.b64decode(r.json()["images"][0])
149+
return Image.open(BytesIO(img_data)).convert("RGB")
150+
except Exception as e:
151+
show_error("Stable Diffusion Error", str(e))
152+
return fake_ai_generate(size)
153+
154+
# ================= GENERATION =================
155+
def get_column_count():
156+
width = scroll.winfo_width()
157+
return max(1, width // 210)
158+
159+
def refresh_thumbnail_grid(event=None):
160+
cols = get_column_count()
161+
for idx, lbl in enumerate(thumbnail_labels):
162+
lbl.grid(row=idx // cols, column=idx % cols, padx=5, pady=5)
163+
164+
def generate_images():
165+
prompt = prompt_entry.get().strip()
166+
if not prompt:
167+
show_error("Input Error", "Please enter a prompt or theme.")
168+
return
169+
170+
for widget in scroll.winfo_children():
171+
widget.destroy()
172+
173+
generated_images.clear()
174+
selected_images.clear()
175+
thumbnail_labels.clear()
176+
177+
size = tuple(map(int, size_combo.get().split("x")))
178+
batch = int(batch_spin.get())
179+
180+
for i in range(batch):
181+
# Replace fake_ai_generate with stable_diffusion_generate if SD is available
182+
img = fake_ai_generate(size)
183+
# img = stable_diffusion_generate(prompt, size)
184+
185+
generated_images.append(img)
186+
187+
thumb = img.copy()
188+
thumb.thumbnail((200, 200))
189+
tk_img = ImageTk.PhotoImage(thumb)
190+
191+
lbl = tk.Label(scroll, image=tk_img, borderwidth=2, relief="ridge")
192+
lbl.image = tk_img
193+
thumbnail_labels.append(lbl)
194+
195+
def toggle_select(event, index=i, label=lbl):
196+
if index in selected_images:
197+
selected_images.remove(index)
198+
label.config(relief="ridge", borderwidth=2)
199+
else:
200+
selected_images.add(index)
201+
label.config(relief="solid", borderwidth=4)
202+
203+
lbl.bind("<Button-1>", toggle_select)
204+
205+
refresh_thumbnail_grid()
206+
207+
scroll.bind("<Configure>", refresh_thumbnail_grid)
208+
209+
# ================= SAVE =================
210+
def save_selected():
211+
if not selected_images:
212+
show_error("Save Error", "No images selected.")
213+
return
214+
215+
folder = filedialog.askdirectory(title="Select Save Folder")
216+
if not folder:
217+
return
218+
219+
for i in selected_images:
220+
path = os.path.join(folder, f"coloring_page_{i+1}.png")
221+
img_300 = generated_images[i].resize((2480, 3508), Image.LANCZOS)
222+
img_300.save(path, dpi=(300, 300))
223+
224+
show_info("Saved", f"{len(selected_images)} images saved successfully.")
225+
226+
# ================= PDF EXPORT =================
227+
def export_pdf():
228+
if not selected_images:
229+
show_error("Export Error", "No images selected.")
230+
return
231+
232+
path = filedialog.asksaveasfilename(
233+
defaultextension=".pdf",
234+
filetypes=[("PDF Files", "*.pdf")]
235+
)
236+
if not path:
237+
return
238+
239+
c = canvas.Canvas(path, pagesize=A4)
240+
page_width, page_height = A4
241+
242+
for idx in selected_images:
243+
img = generated_images[idx]
244+
img_300 = img.resize((2480, 3508), Image.LANCZOS)
245+
img_reader = ImageReader(img_300)
246+
c.drawImage(
247+
img_reader,
248+
0,
249+
0,
250+
width=page_width,
251+
height=page_height,
252+
preserveAspectRatio=True,
253+
anchor='c'
254+
)
255+
c.showPage()
256+
257+
c.save()
258+
show_info("PDF Exported", "Coloring book PDF created successfully.")
259+
260+
generate_btn.config(command=lambda: threading.Thread(target=generate_images, daemon=True).start())
261+
save_btn.config(command=save_selected)
262+
export_pdf_btn.config(command=export_pdf)
263+
264+
# ================= ABOUT =================
265+
def show_about():
266+
win = tb.Toplevel(app)
267+
win.title("About")
268+
win.geometry("500x400")
269+
win.resizable(False, False)
270+
frame = tb.Frame(win, padding=15)
271+
frame.pack(fill="both", expand=True)
272+
273+
tb.Label(frame, text=APP_NAME, font=("Segoe UI", 14, "bold")).pack(pady=5)
274+
tb.Label(frame, text=f"Version {VERSION}").pack()
275+
276+
tb.Label(
277+
frame,
278+
text=(
279+
"AI Coloring Book Maker is a standalone desktop application\n"
280+
"that generates printable black-and-white coloring pages\n"
281+
"using AI-powered image generation.\n\n"
282+
"Key Features:\n"
283+
"- Custom prompts\n"
284+
"- Batch image generation\n"
285+
"- Thumbnail selection\n"
286+
"- Print-ready line art\n"
287+
"- 300 DPI PDF export\n"
288+
"- Fully offline executable\n"
289+
),
290+
justify="left",
291+
wraplength=460
292+
).pack(pady=10)
293+
294+
tb.Button(frame, text="Close", bootstyle="danger", command=win.destroy).pack(pady=10)
295+
296+
menu = tk.Menu(app)
297+
app.config(menu=menu)
298+
help_menu = tk.Menu(menu, tearoff=0)
299+
menu.add_cascade(label="Help", menu=help_menu)
300+
help_menu.add_command(label="About", command=show_about)
301+
302+
app.mainloop()

0 commit comments

Comments
 (0)