import os
import zipfile
import shutil
import streamlit as st
import subprocess
import sys
# Ensure gdown is installed
try:
import gdown
except ImportError:
subprocess.check_call([sys.executable, "-m", "pip", "install", "gdown"])
import gdown
ASSETS_DIR = "assets"
ZIP_FILE = "assets.zip"
FILE_ID = "18qGAPUO3aCFKx7tfDxD2kOPzFXLUo66U"
ZIP_URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}"
# Download if not exists
if not os.path.exists(ASSETS_DIR):
st.info("Downloading assets from Google Drive... ⏳")
gdown.download(ZIP_URL, ZIP_FILE, quiet=False)
# Extract zip to a temp folder
temp_extract = "temp_assets_extract"
with zipfile.ZipFile(ZIP_FILE, 'r') as zip_ref:
zip_ref.extractall(temp_extract)
# Check if single top-level folder exists inside temp_extract
top_level = os.listdir(temp_extract)
if len(top_level) == 1 and os.path.isdir(os.path.join(temp_extract, top_level[0])):
inner_folder = os.path.join(temp_extract, top_level[0])
# Move content to ASSETS_DIR
shutil.move(inner_folder, ASSETS_DIR)
else:
# Move everything to ASSETS_DIR
os.makedirs(ASSETS_DIR, exist_ok=True)
for item in os.listdir(temp_extract):
shutil.move(os.path.join(temp_extract, item), ASSETS_DIR)
import streamlit as st
from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageFilter, ImageOps, ImageChops
import os
import io
import random
from datetime import datetime, timedelta
import zipfile
import numpy as np
import textwrap
from typing import Tuple, List, Optional
import math
import colorsys
import traceback
from collections import Counter
from streamlit.runtime.scriptrunner import get_script_run_ctx
import json, uuid, hashlib
from huggingface_hub import snapshot_download
# Download assets from Hugging Face dataset if not present
ASSETS_DIR = "assets"
if not os.path.exists(ASSETS_DIR):
os.makedirs(ASSETS_DIR, exist_ok=True)
try:
snapshot_download(repo_id="tmshivam/tool", repo_type="dataset", local_dir=ASSETS_DIR)
except Exception as e:
st.error(f"Failed to download assets: {str(e)}")
# =================== CONFIG ===================
# ========== BEGIN AUTH / ADMIN BLOCK (PASTE ABOVE "MAIN APP" MARK) ==========
DATA_DIR = "data"
USERS_FILE = os.path.join(DATA_DIR, "users.json")
SETTINGS_FILE = os.path.join(DATA_DIR, "settings.json")
def get_ip():
"""
Get the client's IP address from request headers.
Handles proxies by taking the first IP in X-Forwarded-For.
"""
try:
ctx = get_script_run_ctx()
if ctx:
headers = ctx._request_headers if hasattr(ctx, '_request_headers') else {}
ip = headers.get("X-Forwarded-For", "Unknown")
if ip != "Unknown":
ip = ip.split(',')[0].strip()
return ip
except Exception as e:
return "Unknown"
def _auth_hash(pw: str) -> str:
"""
Hash the password using SHA256 for secure storage.
"""
return hashlib.sha256(pw.encode()).hexdigest()
def _auth_load_users():
"""
Load users data from JSON file.
"""
try:
with open(USERS_FILE, "r") as f:
return json.load(f)
except:
return {"users": {}}
def _auth_save_users(data):
"""
Save users data to JSON file.
"""
with open(USERS_FILE, "w") as f:
json.dump(data, f, indent=2)
def _auth_load_settings():
"""
Load settings from JSON file.
"""
try:
with open(SETTINGS_FILE, "r") as f:
return json.load(f)
except:
return {"notice":"", "active_tool":"V1.0", "visible_tools":["V1.0"], "primary_color":"#ffcc00",
"login_required": True, "hue_enabled_pngs": {}, "tool_visibility": {}}
def _auth_save_settings(s):
"""
Save settings to JSON file.
"""
with open(SETTINGS_FILE, "w") as f:
json.dump(s, f, indent=2)
def _auth_ensure_files():
"""
Ensure data directories and default files exist, create admin if not present.
"""
os.makedirs(DATA_DIR, exist_ok=True)
users = _auth_load_users()
if "admin" not in users.get("users", {}):
default_pw = "admin123"
users.setdefault("users", {})["admin"] = {
"password_hash": _auth_hash(default_pw),
"is_admin": True,
"device_token": None,
"last_login": None,
"expires_at": None,
"last_ip": None
}
_auth_save_users(users)
settings = _auth_load_settings()
_auth_save_settings(settings)
_auth_ensure_files()
_users_db = _auth_load_users()
_settings = _auth_load_settings()
def _auth_logout_and_rerun():
"""
Logout the user by clearing session state and rerunning the app.
"""
for k in ("_auth_user","_auth_device","_auth_login_time","_auth_show_admin"):
if k in st.session_state:
del st.session_state[k]
st.rerun()
def _auth_check_session():
"""
Check if the current session is valid, including expiry and IP match.
"""
username = st.session_state.get("_auth_user")
if not username:
return
users = _auth_load_users()
u = users.get("users", {}).get(username)
if not u:
st.warning("Your account was removed. Logging out.")
_auth_logout_and_rerun()
if u.get("expires_at"):
try:
exp = datetime.fromisoformat(u["expires_at"])
if datetime.utcnow() > exp:
st.warning("Session expired — please login again.")
_auth_logout_and_rerun()
except:
pass
token = st.session_state.get("_auth_device")
current_ip = get_ip()
if u.get("last_ip") and current_ip != u.get("last_ip"):
st.warning("IP address mismatch. Logging out for security.")
_auth_logout_and_rerun()
if u.get("device_token") and token and u.get("device_token") != token:
st.warning("You were logged in from another device. This session is logged out.")
_auth_logout_and_rerun()
# Check if login is required
settings = _auth_load_settings()
login_required = settings.get("login_required", True)
if login_required and "_auth_user" not in st.session_state:
st.markdown("
🔐 Login First
", unsafe_allow_html=True)
st.info("For ID and Password, contact WhatsApp: 9140588751")
left, right = st.columns([2,1])
with left:
login_id = st.text_input("Enter ID")
login_pw = st.text_input("Password", type="password")
with right:
if st.button("Login"):
db = _auth_load_users()
user = db.get("users", {}).get(login_id)
if not user or user.get("password_hash") != _auth_hash(login_pw or ""):
st.error("Invalid ID or Password. Contact dev: +91 9140588751")
else:
token = str(uuid.uuid4())
now = datetime.utcnow()
current_ip = get_ip()
user["device_token"] = token
user["last_login"] = now.isoformat()
user["last_ip"] = current_ip
user["expires_at"] = (now + timedelta(days=7)).isoformat()
_auth_save_users(db)
st.session_state["_auth_user"] = login_id
st.session_state["_auth_device"] = token
st.session_state["_auth_login_time"] = now.isoformat()
st.success(f"Welcome {login_id} — logged in from IP {current_ip}!")
st.rerun()
st.stop()
if "_auth_user" in st.session_state:
_auth_check_session()
CURRENT_USER = st.session_state.get("_auth_user")
USERS_DB = _auth_load_users()
CURRENT_RECORD = USERS_DB.get("users", {}).get(CURRENT_USER, {})
IS_ADMIN = CURRENT_RECORD.get("is_admin", False)
if IS_ADMIN:
if "_auth_show_admin" not in st.session_state:
st.session_state["_auth_show_admin"] = False
if st.sidebar.button("🔧 Open Admin Panel"):
st.session_state["_auth_show_admin"] = not st.session_state["_auth_show_admin"]
if st.session_state.get("_auth_show_admin"):
st.markdown("## ⚙️ ADMIN PANEL")
# Enhanced admin features
st.markdown("### User Management")
tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs(["User Accounts", "Access Control", "System Settings", "IP Management", "Tools & Features", "PNG Hue Settings", "Tool Visibility"])
with tab1:
st.markdown("#### Create / Manage Users")
c1,c2 = st.columns(2)
with c1:
new_id = st.text_input("New ID", key="__new_id")
new_pw = st.text_input("New Password", type="password", key="__new_pw")
with c2:
new_admin = st.checkbox("Is Admin?", key="__new_admin")
user_type = st.selectbox("User Type", ["Member", "Pro Member", "Admin"], key="__user_type")
if st.button("Create User"):
db = _auth_load_users()
if new_id in db.get("users", {}):
st.error("User already exists.")
else:
db.setdefault("users", {})[new_id] = {
"password_hash": _auth_hash(new_pw or "12345"),
"is_admin": bool(new_admin),
"user_type": user_type,
"device_token": None,
"last_login": None,
"last_ip": None,
"expires_at": None
}
_auth_save_users(db)
st.success(f"User {new_id} created.")
st.markdown("#### Existing users")
db = _auth_load_users()
for uname, udata in list(db.get("users", {}).items()):
cols = st.columns([3,1,1,1,1,1,1])
exp = udata.get('expires_at', 'None')
last_login = udata.get('last_login', 'None')
last_ip = udata.get('last_ip', 'None')
user_type = udata.get('user_type', 'Member')
cols[0].write(f"**{uname}** ({user_type}) | Exp: {exp} | Last Login: {last_login} | IP: {last_ip}")
if cols[1].button("Reset PW", key=f"reset_{uname}"):
db["users"][uname]["password_hash"] = _auth_hash("admin123")
_auth_save_users(db)
st.success(f"{uname} password reset to admin123")
if cols[2].button("Expire Now", key=f"expire_{uname}"):
db["users"][uname]["expires_at"] = datetime.utcnow().isoformat()
_auth_save_users(db)
st.success(f"{uname} expired")
if cols[3].button("Delete", key=f"del_{uname}"):
del db["users"][uname]
_auth_save_users(db)
st.rerun()
exp_days = cols[4].number_input("Expiry Days", min_value=0, value=7, key=f"exp_{uname}")
if cols[5].button("Set Expiry", key=f"setexp_{uname}"):
if exp_days > 0:
exp = (datetime.utcnow() + timedelta(days=exp_days)).isoformat()
db["users"][uname]["expires_at"] = exp
else:
db["users"][uname]["expires_at"] = None
_auth_save_users(db)
st.success(f"Expiry set for {uname}")
# User type change
new_type = cols[6].selectbox("Type", ["Member", "Pro Member", "Admin"],
index=["Member", "Pro Member", "Admin"].index(user_type),
key=f"type_{uname}")
if new_type != user_type:
db["users"][uname]["user_type"] = new_type
_auth_save_users(db)
st.success(f"{uname} type changed to {new_type}")
with tab2:
st.markdown("### Access Control")
st.markdown("#### Tool Visibility Settings")
all_tools = ["V1.0", "V2.0", "V3.0", "V4.0", "V5.0", "Premium Tools", "Hue Color Tool"]
visible_tools = _settings.get("visible_tools", ["V1.0"])
for tool in all_tools:
is_visible = st.checkbox(f"Show {tool}", value=tool in visible_tools, key=f"tool_{tool}")
if is_visible and tool not in visible_tools:
visible_tools.append(tool)
elif not is_visible and tool in visible_tools:
visible_tools.remove(tool)
if st.button("Save Tool Visibility"):
_settings["visible_tools"] = visible_tools
_auth_save_settings(_settings)
st.success("Tool visibility settings saved!")
st.markdown("#### User Type Restrictions")
st.info("Pro Members can access all tools except Admin Panel. Members can only access basic tools.")
with tab3:
st.markdown("### System Settings")
st.markdown("#### Noticeboard")
new_notice = st.text_area("Global notice (shows on main page)", value=_settings.get("notice",""))
primary_color = st.color_picker("Primary Color", value=_settings.get("primary_color", "#ffcc00"))
# Login page toggle
login_required = st.checkbox("Require Login", value=_settings.get("login_required", True))
if st.button("Save Settings"):
_settings["notice"] = new_notice
_settings["primary_color"] = primary_color
_settings["login_required"] = login_required
_auth_save_settings(_settings)
st.success("Settings saved.")
st.markdown("#### App Configuration")
auto_logout = st.slider("Auto Logout (minutes of inactivity)", 5, 20, 10080)
max_upload_size = st.slider("Max Upload Size (MB)", 5, 500, 200)
if st.button("Save Configuration"):
_settings["auto_logout"] = auto_logout
_settings["max_upload_size"] = max_upload_size
_auth_save_settings(_settings)
st.success("Configuration saved!")
with tab4:
st.markdown("### IP Management")
st.markdown("#### IP Whitelist/Blacklist")
ip_list = _settings.get("ip_list", {"whitelist": [], "blacklist": []})
col1, col2 = st.columns(2)
with col1:
st.markdown("##### Whitelist")
for ip in ip_list["whitelist"]:
st.write(f"{ip} ❌", key=f"wl_{ip}")
new_whitelist = st.text_input("Add to Whitelist")
if st.button("Add IP to Whitelist"):
if new_whitelist and new_whitelist not in ip_list["whitelist"]:
ip_list["whitelist"].append(new_whitelist)
_settings["ip_list"] = ip_list
_auth_save_settings(_settings)
st.success(f"Added {new_whitelist} to whitelist")
with col2:
st.markdown("##### Blacklist")
for ip in ip_list["blacklist"]:
st.write(f"{ip} ❌", key=f"bl_{ip}")
new_blacklist = st.text_input("Add to Blacklist")
if st.button("Add IP to Blacklist"):
if new_blacklist and new_blacklist not in ip_list["blacklist"]:
ip_list["blacklist"].append(new_blacklist)
_settings["ip_list"] = ip_list
_auth_save_settings(_settings)
st.success(f"Added {new_blacklist} to blacklist")
st.markdown("#### Current IP Sessions")
db = _auth_load_users()
for uname, udata in db.get("users", {}).items():
if udata.get("last_ip"):
st.write(f"{uname}: {udata.get('last_ip')} - {udata.get('last_login', 'Never')}")
with tab5:
st.markdown("### Tools & Features")
st.markdown("#### Feature Toggles")
features = _settings.get("features", {
"watermark": True,
"premium_filters": True,
"batch_processing": True,
"high_res_output": True
})
for feature, enabled in features.items():
features[feature] = st.checkbox(f"Enable {feature.replace('_', ' ').title()}",
value=enabled, key=f"feature_{feature}")
if st.button("Save Feature Settings"):
_settings["features"] = features
_auth_save_settings(_settings)
st.success("Feature settings saved!")
st.markdown("#### System Tools")
if st.button("Clear All Cache"):
st.session_state.clear()
st.success("Cache cleared!")
if st.button("Export User Data"):
# Create export functionality
st.success("User data exported!")
if st.button("Import User Data"):
# Create import functionality
st.success("User data imported!")
with tab6:
st.markdown("### PNG Hue Color Settings")
st.info("Select which PNGs should have hue color change enabled")
# Get all available PNG folders
hue_enabled_pngs = _settings.get("hue_enabled_pngs", {})
# Find all overlay folders
overlay_base = "assets/overlays"
if os.path.exists(overlay_base):
years = os.listdir(overlay_base)
for year in years:
year_path = os.path.join(overlay_base, year)
if os.path.isdir(year_path):
themes = os.listdir(year_path)
for theme in themes:
theme_path = os.path.join(year_path, theme)
if os.path.isdir(theme_path):
png_key = f"{year}/{theme}"
current_value = hue_enabled_pngs.get(png_key, False)
# Check if this theme has PNG files
png_files = [f for f in os.listdir(theme_path) if f.lower().endswith('.png')]
if png_files:
enabled = st.checkbox(f"{png_key}", value=current_value, key=f"hue_{png_key}")
hue_enabled_pngs[png_key] = enabled
if st.button("Save Hue Settings"):
_settings["hue_enabled_pngs"] = hue_enabled_pngs
_auth_save_settings(_settings)
st.success("Hue settings saved!")
with tab7:
st.markdown("### Tool Visibility Settings")
st.info("Control which tools are visible to users")
# Get current tool visibility settings
tool_visibility = _settings.get("tool_visibility", {})
# Define all available tools
all_tools = {
"upload_images": "Image Upload",
"greeting_type": "Greeting Type Selection",
"generate_variants": "Generate Multiple Variants",
"style_mode": "Style Mode Selection",
"text_effect": "Text Style Selection",
"text_position": "Text Position Selection",
"custom_position": "Manual Positioning",
"show_text": "Show Greeting Text",
"show_wish": "Show Wish Text",
"overlap_percent": "Overlap Settings",
"show_date": "Show Date",
"show_quote": "Add Quote",
"use_watermark": "Add Watermark",
"use_coffee_pet": "Coffee & Pet PNG",
"apply_emoji": "Emoji Stickers",
"bulk_quality": "Bulk Processing",
"advanced_features": "Advanced Features",
"hue_tool": "Hue Color Tool"
}
for tool_key, tool_name in all_tools.items():
current_value = tool_visibility.get(tool_key, True)
tool_visibility[tool_key] = st.checkbox(f"Show {tool_name}", value=current_value, key=f"vis_{tool_key}")
if st.button("Save Tool Visibility Settings"):
_settings["tool_visibility"] = tool_visibility
_auth_save_settings(_settings)
st.success("Tool visibility settings saved!")
st.markdown("---")
st.write("Contact developer: +91 9140588751")
st.stop()
if _settings.get("notice"):
st.info(_settings.get("notice"))
# ========== END AUTH / ADMIN BLOCK ==========
# Custom CSS with dynamic primary color
primary_color = _settings.get("primary_color", "#ffcc00")
st.markdown(f"""
""", unsafe_allow_html=True)
# =================== UTILS ===================
def list_files(folder: str, exts: List[str]) -> List[str]:
"""
List files in a folder with specific extensions.
"""
if not os.path.exists(folder):
os.makedirs(folder, exist_ok=True)
return []
files = os.listdir(folder)
return [f for f in files
if any(f.lower().endswith(ext.lower()) for ext in exts)]
def list_subfolders(folder: str) -> List[str]:
"""
List subfolders in a folder.
"""
if not os.path.exists(folder):
os.makedirs(folder, exist_ok=True)
return []
return [d for d in os.listdir(folder) if os.path.isdir(os.path.join(folder, d))]
def smart_crop(img: Image.Image, target_ratio: float = 3/4) -> Image.Image:
"""
Smart crop image to target aspect ratio, centering the crop.
"""
w, h = img.size
if w/h > target_ratio:
new_w = int(h * target_ratio)
left = (w - new_w) // 2
return img.crop((left, 0, left + new_w, h))
else:
new_h = int(w / target_ratio)
top = (h - new_h) // 2
return img.crop((0, top, w, top + new_h))
def get_text_size(draw: ImageDraw.Draw, text: str, font: ImageFont.FreeTypeFont) -> Tuple[int, int]:
"""
Get the size of text with given font.
"""
if text is None:
return 0, 0
bbox = draw.textbbox((0, 0), text, font=font)
return bbox[2] - bbox[0], bbox[3] - bbox[1]
def get_random_font(font_folder="assets/fonts") -> ImageFont.FreeTypeFont:
"""
Get a random font from the fonts folder, fallback to default.
"""
try:
fonts = list_files(font_folder, [".ttf", ".otf"])
if not fonts:
return ImageFont.truetype("arial.ttf", 80)
for _ in range(3):
try:
font_path = os.path.join(font_folder, random.choice(fonts))
return ImageFont.truetype(font_path, 80)
except:
continue
return ImageFont.truetype("arial.ttf", 80)
except:
return ImageFont.load_default()
def get_random_wish(greeting_type: str) -> str:
"""
Get a random wish message based on greeting type.
Expanded list for more variety.
"""
wishes = {
"Good Morning": [
"Rise and shine! A new day is a new opportunity!",
"Good morning! Make today amazing!",
# ... (rest of the wishes remain the same)
],
# ... (other greeting types remain the same)
}
return random.choice(wishes.get(greeting_type, ["Have a nice day!"]))
def get_random_quote() -> str:
"""
Get a random inspirational quote from an expanded list.
"""
quotes = [
"The only way to do great work is to love what you do. - Steve Jobs",
"Innovation distinguishes between a leader and a follower. - Steve Jobs",
# ... (rest of the quotes remain the same)
]
return random.choice(quotes)
def get_random_color() -> Tuple[int, int, int]:
"""
Generate a random RGB color with medium brightness.
"""
return (random.randint(50, 255), random.randint(50, 255), random.randint(50, 255))
def get_vibrant_color() -> Tuple[int, int, int]:
"""
Generate a vibrant RGB color using HSV.
"""
hue = random.random()
r, g, b = [int(255 * c) for c in colorsys.hsv_to_rgb(hue, 0.9, 0.9)]
return (r, g, b)
PURE_COLORS = [
(255, 0, 0), # Red
(255, 255, 0), # Yellow
(0, 255, 0), # Green
(0, 0, 255), # Blue
(255, 0, 255), # Magenta
(0, 255, 255), # Cyan
]
def get_gradient_colors(dominant_color: Tuple[int, int, int]) -> List[Tuple[int, int, int]]:
"""
Get gradient colors based on dominant color or pure colors.
"""
if random.random() < 0.8:
return [(255, 255, 255), dominant_color]
else:
return [(255, 255, 255), random.choice(PURE_COLORS)]
def get_multi_gradient_colors() -> List[Tuple[int, int, int]]:
"""
Get multiple vibrant colors for rainbow gradient.
"""
num_colors = random.randint(2, 7)
colors = []
for _ in range(num_colors):
colors.append(get_vibrant_color())
return colors
def create_gradient_mask(width: int, height: int, colors: List[Tuple[int, int, int]], direction: str = 'horizontal') -> Image.Image:
"""
Create a gradient mask image with given colors.
"""
gradient = Image.new('RGB', (width, height))
draw = ImageDraw.Draw(gradient)
if len(colors) == 2:
start_color, end_color = colors
for x in range(width):
ratio = x / width
r = int(start_color[0] * (1 - ratio) + end_color[0] * ratio)
g = int(start_color[1] * (1 - ratio) + end_color[1] * ratio)
b = int(start_color[2] * (1 - ratio) + end_color[2] * ratio)
color = (r, g, b)
draw.line([(x, 0), (x, height)], fill=color)
if random.choice([True, False]):
gradient = gradient.transpose(Image.FLIP_LEFT_RIGHT)
else:
num_segments = len(colors) - 1
segment_width = width // num_segments
for seg in range(num_segments):
start_color = colors[seg]
end_color = colors[seg + 1]
start_x = seg * segment_width
end_x = start_x + segment_width
for x in range(start_x, min(end_x, width)):
ratio = (x - start_x) / segment_width
r = int(start_color[0] * (1 - ratio) + end_color[0] * ratio)
g = int(start_color[1] * (1 - ratio) + end_color[1] * ratio)
b = int(start_color[2] * (1 - ratio) + end_color[2] * ratio)
draw.line([(x, 0), (x, height)], fill=(r, g, b))
return gradient
def format_date(date_format: str = "%d %B %Y", show_day: bool = False) -> str:
"""
Format current date with optional day name.
"""
today = datetime.now()
formatted_date = today.strftime(date_format)
if show_day:
if today.hour >= 19:
next_day = today + timedelta(days=1)
day_name = next_day.strftime("%A")
formatted_date += f" (Advance {day_name})"
else:
day_name = today.strftime("%A")
formatted_date += f" ({day_name})"
return formatted_date
def apply_overlay(image: Image.Image, overlay_path: str, size: float = 0.5, position: Tuple[int, int] = None) -> Image.Image:
"""
Apply an overlay image with resizing and random or specified position.
"""
try:
overlay = Image.open(overlay_path).convert("RGBA")
new_size = (int(image.width * size), int(image.height * size))
overlay = overlay.resize(new_size, Image.LANCZOS)
if position is None:
max_x = max(20, image.width - overlay.width - 20)
max_y = max(20, image.height - overlay.height - 20)
x = random.randint(20, max_x) if max_x > 20 else 20
y = random.randint(20, max_y) if max_y > 20 else 20
else:
x, y = position
image.paste(overlay, (x, y), overlay)
except Exception as e:
st.error(f"Error applying overlay: {str(e)}")
return image
def generate_filename(base_name="Picsart") -> str:
"""
Generate a filename with future timestamp for uniqueness.
"""
future_minutes = random.randint(1, 10)
now = datetime.now()
future_time = now + timedelta(minutes=future_minutes)
return f"{base_name}_{future_time.strftime('%y-%m-%d_%H-%M-%S')}.jpg"
def get_watermark_position(img: Image.Image, watermark: Image.Image, avoid_positions: List[Tuple[int, int, int, int]] = None) -> Tuple[int, int]:
"""
Get a position for watermark, avoiding overlaps if provided.
"""
possible_positions = [20, img.width - watermark.width - 20]
x = random.choice(possible_positions)
y = img.height - watermark.height - 20
if avoid_positions:
for ax, ay, aw, ah in avoid_positions:
if abs(x - ax) < aw or abs(y - ay) < ah:
x = possible_positions[1 - possible_positions.index(x)] # switch side
return (x, y)
def enhance_image_quality(img: Image.Image, brightness=1.0, contrast=1.0, sharpness=1.0, saturation=1.0) -> Image.Image:
"""
Enhance image quality with adjustable parameters.
"""
if img.mode != 'RGB':
img = img.convert('RGB')
enhancer = ImageEnhance.Brightness(img)
img = enhancer.enhance(brightness)
enhancer = ImageEnhance.Contrast(img)
img = enhancer.enhance(contrast)
enhancer = ImageEnhance.Sharpness(img)
img = enhancer.enhance(sharpness)
enhancer = ImageEnhance.Color(img)
img = enhancer.enhance(saturation)
return img
def upscale_text_elements(img: Image.Image, scale_factor: int = 4) -> Image.Image:
"""
Upscale image for better text rendering.
"""
if scale_factor > 1:
new_size = (img.width * scale_factor, img.height * scale_factor)
img = img.resize(new_size, Image.LANCZOS)
return img
def apply_vignette(img: Image.Image, intensity: float = 0.8) -> Image.Image:
"""
Apply vignette effect to image.
"""
width, height = img.size
x = np.linspace(-1, 1, width)
y = np.linspace(-1, 1, height)
X, Y = np.meshgrid(x, y)
R = np.sqrt(X**2 + Y**2)
mask = 1 - np.clip(R * intensity, 0, 1)
mask = (mask * 255).astype(np.uint8)
mask_img = Image.fromarray(mask).convert('L')
vignette = Image.new('RGB', (width, height), (0, 0, 0))
img.paste(vignette, (0, 0), mask_img)
return img
def apply_sepia(img: Image.Image) -> Image.Image:
"""
Apply sepia filter to image.
"""
arr = np.array(img)
sepia_filter = np.array([
[.393, .769, .189],
[.349, .686, .168],
[.272, .534, .131]
])
arr = arr @ sepia_filter.T
arr = np.clip(arr, 0, 255)
return Image.fromarray(arr.astype('uint8'))
def apply_black_white(img: Image.Image) -> Image.Image:
"""
Convert image to black and white.
"""
return img.convert('L').convert('RGB')
def apply_vintage(img: Image.Image) -> Image.Image:
"""
Apply vintage effect: sepia + noise + vignette.
"""
img = apply_sepia(img)
noise = np.random.normal(0, 25, img.size[::-1] + (3,)).astype(np.uint8)
noise_img = Image.fromarray(noise)
img = ImageChops.add(img, noise_img, scale=2.0)
img = apply_vignette(img, 0.5)
return img
def apply_sketch_effect(img: Image.Image) -> Image.Image:
"""
Apply sketch effect to image.
"""
img_gray = img.convert('L')
img_invert = ImageOps.invert(img_gray)
img_blur = img_invert.filter(ImageFilter.GaussianBlur(radius=3))
return ImageOps.invert(img_blur)
def apply_cartoon_effect(img: Image.Image) -> Image.Image:
"""
Apply cartoon effect to image.
"""
reduced = img.quantize(colors=8, method=1)
gray = img.convert('L')
edges = gray.filter(ImageFilter.FIND_EDGES)
edges = edges.convert('L')
edges = edges.point(lambda x: 0 if x < 100 else 255)
cartoon = reduced.convert('RGB')
cartoon.paste((0, 0, 0), (0, 0), edges)
return cartoon
def apply_anime_effect(img: Image.Image) -> Image.Image:
"""
Apply anime-style effect to image.
"""
enhancer = ImageEnhance.Color(img)
img = enhancer.enhance(1.5)
edges = img.filter(ImageFilter.FIND_EDGES)
edges = edges.convert('L')
edges = edges.point(lambda x: 0 if x < 100 else 255)
result = img.copy()
result.paste((0, 0, 0), (0, 0), edges)
return result
def apply_emoji_stickers(img: Image.Image, emojis: List[str], num_stickers=5) -> Image.Image:
"""
Add random emoji stickers to image.
"""
if not emojis:
return img
draw = ImageDraw.Draw(img)
for _ in range(num_stickers):
x = random.randint(20, img.width-40)
y = random.randint(20, img.height-40)
emoji = random.choice(emojis)
font = ImageFont.truetype("arial.ttf", 40)
draw.text((x, y), emoji, font=font, fill=(255, 255, 0))
return img
def get_dominant_color(img: Image.Image) -> Tuple[int, int, int]:
"""
Get dominant color from resized image, adjust lightness.
FIXED: Handle RGBA images by converting to RGB first
"""
# Convert to RGB if image has alpha channel
if img.mode == 'RGBA':
img = img.convert('RGB')
img_small = img.resize((100, 100))
colors = Counter(img_small.getdata())
dominant = colors.most_common(1)[0][0]
# Ensure we only have 3 values (RGB)
if len(dominant) > 3:
dominant = dominant[:3]
h, l, s = colorsys.rgb_to_hls(dominant[0] / 255, dominant[1] / 255, dominant[2] / 255)
if l < 0.5:
l = 0.7
r, g, b = colorsys.hls_to_rgb(h, l, s)
return (int(r * 255), int(g * 255), int(b * 255))
def find_text_position(img: Image.Image, required_width: int, required_height: int, prefer_top: bool = True) -> Tuple[int, int]:
"""
Find optimal position for text based on image variance (low variance areas).
"""
arr = np.array(img.convert('L'))
step = 20
min_var = float('inf')
best_pos = (20, 20 if prefer_top else img.height - required_height - 20)
start_y = 0 if prefer_top else img.height // 2
for y in range(start_y, img.height - required_height, step):
for x in range(0, img.width - required_width, step):
region = arr[y:y + required_height, x:x + required_width]
var = np.var(region)
if var < min_var:
min_var = var
best_pos = (x, y)
return best_pos
def get_random_horizontal_position(img_width: int, text_width: int) -> int:
"""
Get random horizontal position: left, mid, right.
"""
positions = [
20, # left
(img_width - text_width) // 2, # mid
img_width - text_width - 20 # right
]
return random.choice(positions)
def apply_text_effect(draw: ImageDraw.Draw, position: Tuple[int, int], text: str, font: ImageFont.FreeTypeFont,
effect_settings: dict, base_img: Image.Image) -> dict:
"""
Apply advanced text effects using separate layers for better control.
"""
x, y = position
effect_type = effect_settings['type']
if text is None or text.strip() == "":
return effect_settings
text_width, text_height = get_text_size(draw, text, font)
# Handle RANDOM effect type
if effect_type == 'random':
available_effects = [
'white_only', 'white_black_outline_shadow', 'gradient',
'neon', 'rainbow', 'country_flag', '3d'
]
effect_type = random.choice(available_effects)
effect_settings['type'] = effect_type
# Create separate transparent layers for shadow, outline, and fill
shadow_layer = Image.new('RGBA', base_img.size, (0, 0, 0, 0))
outline_layer = Image.new('RGBA', base_img.size, (0, 0, 0, 0))
fill_layer = Image.new('RGBA', base_img.size, (0, 0, 0, 0))
shadow_draw = ImageDraw.Draw(shadow_layer)
outline_draw = ImageDraw.Draw(outline_layer)
fill_draw = ImageDraw.Draw(fill_layer)
# Draw shadow
shadow_offset = (2, 2)
shadow_draw.text((x + shadow_offset[0], y + shadow_offset[1]), text, font=font, fill=(0, 0, 0, 40))
# Draw outline
outline_range = 1 if effect_type == 'neon' else 2
for ox in range(-outline_range, outline_range + 1):
for oy in range(-outline_range, outline_range + 1):
if ox != 0 or oy != 0:
outline_draw.text((x + ox, y + oy), text, font=font, fill=(0, 0, 0, 255))
# Create mask for fill
mask = Image.new("L", (text_width, text_height), 0)
mask_draw = ImageDraw.Draw(mask)
mask_draw.text((0, 0), text, font=font, fill=255)
# Apply fill
if effect_type in ['gradient', 'rainbow']:
colors = effect_settings['colors']
gradient = create_gradient_mask(text_width, text_height, colors)
gradient_text = Image.new("RGBA", (text_width, text_height), (0, 0, 0, 0))
gradient_text.paste(gradient, (0, 0), mask)
fill_layer.paste(gradient_text, (x, y), gradient_text)
elif effect_type == 'neon':
glow_color = get_vibrant_color()
glow_size = 10
for i in range(glow_size, 0, -1):
alpha = int(80 * (i / glow_size))
for ox in range(-i, i + 1):
for oy in range(-i, i + 1):
if ox != 0 or oy != 0:
fill_draw.text((x + ox, y + oy), text, font=font, fill=(*glow_color, alpha))
fill_draw.text((x, y), text, font=font, fill=(255, 255, 255, 255))
elif effect_type == 'country_flag':
flags = list_files("assets/flags", [".png", ".jpg"])
if flags:
flag_path = os.path.join("assets/flags", random.choice(flags))
flag_img = Image.open(flag_path).convert("RGB").resize((text_width, text_height), Image.LANCZOS)
flag_text = Image.new("RGBA", (text_width, text_height), (0, 0, 0, 0))
flag_text.paste(flag_img, (0, 0), mask)
fill_layer.paste(flag_text, (x, y), flag_text)
else:
fill_draw.text((x, y), text, font=font, fill=(255, 255, 255, 255))
elif effect_type == '3d':
depth = 5
shadow_color = (100, 100, 100, 255)
for i in range(depth):
fill_draw.text((x + i, y + i), text, font=font, fill=shadow_color)
fill_draw.text((x, y), text, font=font, fill=(255, 255, 255, 255))
else:
fill_draw.text((x, y), text, font=font, fill=(255, 255, 255, 255))
# Composite layers
base_img_rgba = base_img.convert('RGBA') if base_img.mode != 'RGBA' else base_img
base_img_rgba = Image.alpha_composite(base_img_rgba, shadow_layer)
base_img_rgba = Image.alpha_composite(base_img_rgba, outline_layer)
base_img_rgba = Image.alpha_composite(base_img_rgba, fill_layer)
base_img.paste(base_img_rgba, (0, 0))
return effect_settings
def get_pet_position(img: Image.Image, pet_img: Image.Image) -> Tuple[int, int]:
"""
Get random position for pet PNG at bottom: 40% left, 40% right, 20% mid.
"""
prob = random.random()
if prob < 0.4:
x = 20 # left
elif prob < 0.8:
x = img.width - pet_img.width - 20 # right
else:
x = (img.width - pet_img.width) // 2 # mid
y = img.height - pet_img.height - 20
return x, y
def change_png_hue(png_image: Image.Image, hue_shift: float) -> Image.Image:
"""
Change the hue of a PNG image while preserving transparency.
"""
if png_image.mode != 'RGBA':
png_image = png_image.convert('RGBA')
# Convert to HSV color space
data = np.array(png_image)
r, g, b, a = data[:,:,0], data[:,:,1], data[:,:,2], data[:,:,3]
# Convert RGB to HSV
hsv = np.zeros_like(data[:,:,:3])
for i in range(data.shape[0]):
for j in range(data.shape[1]):
if a[i, j] > 0: # Only process non-transparent pixels
r_norm = r[i, j] / 255.0
g_norm = g[i, j] / 255.0
b_norm = b[i, j] / 255.0
h, s, v = colorsys.rgb_to_hsv(r_norm, g_norm, b_norm)
h = (h + hue_shift) % 1.0 # Apply hue shift
r_new, g_new, b_new = colorsys.hsv_to_rgb(h, s, v)
hsv[i, j] = [int(r_new * 255), int(g_new * 255), int(b_new * 255)]
else:
hsv[i, j] = [0, 0, 0] # Keep transparent pixels as is
# Combine the new RGB with original alpha channel
result = np.dstack((hsv, a))
return Image.fromarray(result, 'RGBA')
def create_variant(original_img: Image.Image, settings: dict) -> Optional[Image.Image]:
"""
Create a variant of the image with all applied settings, ensuring no overlaps except main.
"""
try:
img = original_img.copy()
draw = ImageDraw.Draw(img)
font = get_random_font(settings.get('font_folder', "assets/fonts"))
if font is None:
return None
dominant_color = get_dominant_color(img)
effect_settings = {
'type': settings.get('text_effect', 'gradient'),
'outline_size': 2,
'colors': get_gradient_colors(dominant_color) if settings.get('text_effect', 'gradient') == 'gradient' else get_multi_gradient_colors()
}
style_mode = settings.get('style_mode', 'Text')
overlap_percent = settings.get('overlap_percent', 30)
# Track positions to avoid overlaps
occupied_boxes = [] # list of (x, y, w, h)
if style_mode == 'PNG Overlay' and settings['greeting_type'] in ["Good Morning", "Good Night"]:
years = list_subfolders("assets/overlays")
if not years:
st.warning("No overlay years found.")
return img
if settings['overlay_year'] == "ALL":
selected_years = years
else:
selected_years = [settings['overlay_year']]
if settings['overlay_year'] not in years:
st.warning("Selected year not found.")
return img
theme_paths = []
for y in selected_years:
year_path = os.path.join("assets/overlays", y)
sub_themes = list_subfolders(year_path)
if not sub_themes:
if list_files(year_path, [".png"]):
theme_paths.append(year_path)
else:
for t in sub_themes:
theme_paths.append(os.path.join(year_path, t))
if not theme_paths:
st.warning("No overlay themes found.")
return img
base_path = random.choice(theme_paths)
png_files = []
if settings['greeting_type'] == "Good Morning":
png_files = ["1.png", "2.png"]
if settings['show_wish']:
png_files.append("4.png")
elif settings['greeting_type'] == "Good Night":
png_files = ["1.png", "3.png"]
if settings['show_wish']:
png_files.append("5.png")
pngs = []
for f in png_files:
path = os.path.join(base_path, f)
if os.path.exists(path):
png_img = Image.open(path).convert("RGBA")
# Apply hue change if enabled for this PNG
theme_key = f"{os.path.basename(os.path.dirname(base_path))}/{os.path.basename(base_path)}"
hue_enabled_pngs = _settings.get("hue_enabled_pngs", {})
if theme_key in hue_enabled_pngs and hue_enabled_pngs[theme_key]:
if 'hue_shift' in settings and settings['hue_shift'] != 0:
# Apply random hue shift if set to random
if settings['hue_shift'] == 'random':
hue_shift = random.random()
else:
hue_shift = settings['hue_shift']
png_img = change_png_hue(png_img, hue_shift)
pngs.append(png_img)
if pngs:
main_gap = -int(min(pngs[0].height, pngs[1].height) * overlap_percent / 100) if len(pngs) >= 2 else 0
wish_gap = 10
gaps = [main_gap] * (len(pngs) - 1)
if settings['show_wish']:
gaps[-1] = wish_gap
total_h = sum(p.height for p in pngs)
for g in gaps:
total_h += g
max_w = max(p.width for p in pngs)
png_size = settings.get('png_size', 0.5)
scale = min(png_size, min((img.width * 0.9) / max_w, (img.height * 0.9) / total_h))
pngs = [p.resize((int(p.width * scale), int(p.height * scale)), Image.LANCZOS) for p in pngs]
total_h = sum(p.height for p in pngs)
for g in gaps:
total_h += g
max_w = max(p.width for p in pngs)
main_position = random.choice(["top", "bottom"])
if main_position == "top":
start_y = random.randint(20, img.height // 4)
else:
start_y = img.height - total_h - random.randint(20, img.height // 4)
if settings.get('custom_position', False):
start_x = settings.get('text_x', 100)
start_y = settings.get('text_y', 100)
else:
start_x = get_random_horizontal_position(img.width, max_w)
current_y = start_y
for i, p in enumerate(pngs):
offset = random.randint(-20, 20)
x = start_x + (max_w - p.width) // 2 + offset
img.paste(p, (x, current_y), p)
occupied_boxes.append((x, current_y, p.width, p.height))
if i < len(pngs) - 1:
current_y += p.height + gaps[i]
main_end_y = current_y
else:
st.warning("Missing PNG files for selected theme.")
else:
if settings['show_text']:
font_size = settings['main_size']
font_main = font.font_variant(size=font_size)
main_texts = settings['greeting_type'].split()
if not main_texts:
main_texts = ["ULTRA", "PRO"]
line_heights = []
line_widths = []
for t in main_texts:
w, h = get_text_size(draw, t, font_main)
line_widths.append(w)
line_heights.append(h)
main_gap = -int(min(line_heights) * overlap_percent / 100) if len(line_heights) > 1 else 0
total_h = sum(line_heights) + (len(main_texts) - 1) * main_gap
max_w = max(line_widths)
while max(line_widths) > img.width * 0.8 and font_size > 10:
font_size -= 5
font_main = font.font_variant(size=font_size)
line_widths = []
line_heights = []
for t in main_texts:
w, h = get_text_size(draw, t, font_main)
line_widths.append(w)
line_heights.append(h)
total_h = sum(line_heights) + (len(main_texts) - 1) * main_gap
max_w = max(line_widths)
main_position = random.choice(["top", "bottom"])
if main_position == "top":
text_y = random.randint(20, img.height // 4)
else:
text_y = img.height - total_h - random.randint(20, img.height // 4)
if settings.get('custom_position', False):
text_x = settings.get('text_x', 100)
text_y = settings.get('text_y', 100)
else:
text_x = get_random_horizontal_position(img.width, max_w)
current_y = text_y
for i, t in enumerate(main_texts):
offset = random.randint(-30, 50)
line_x = text_x + (max_w - line_widths[i]) // 2 + offset
apply_text_effect(draw, (line_x, current_y), t, font_main, effect_settings, img)
occupied_boxes.append((line_x, current_y, line_widths[i], line_heights[i]))
if i < len(main_texts) - 1:
current_y += line_heights[i] + main_gap
main_end_y = current_y
if settings['show_wish']:
font_size = settings['wish_size']
font_wish = font.font_variant(size=font_size)
wish_text = settings.get('custom_wish', None)
if wish_text is None or wish_text.strip() == "":
wish_text = get_random_wish(settings['greeting_type'])
avg_char_width = get_text_size(draw, "A", font_wish)[0]
wrap_width = int((img.width * 0.8) / avg_char_width)
lines = textwrap.wrap(wish_text, width=wrap_width)
while len(lines) > 3 and font_size > 10:
font_size -= 5
font_wish = font.font_variant(size=font_size)
avg_char_width = get_text_size(draw, "A", font_wish)[0]
wrap_width = int((img.width * 0.8) / avg_char_width)
lines = textwrap.wrap(wish_text, width=wrap_width)
line_heights = []
line_widths = []
for line in lines:
w, h = get_text_size(draw, line, font_wish)
line_widths.append(w)
line_heights.append(h)
wish_gap = 5
total_h = sum(line_heights) + (len(lines) - 1) * wish_gap
max_w = max(line_widths)
wish_position = random.choice(["mid", "bottom"])
if wish_position == "mid":
wish_y = img.height // 2 - total_h // 2 + random.randint(-50, 50)
else:
wish_y = img.height - total_h - random.randint(20, 100)
if settings.get('custom_position', False):
wish_x = settings.get('text_x', 100)
else:
wish_x = get_random_horizontal_position(img.width, max_w)
# Avoid overlap with main
if settings['show_text']:
if wish_y < main_end_y + 20:
wish_y = main_end_y + 20
if wish_y + total_h > img.height - 20:
wish_y = img.height - total_h - 20
current_y = wish_y
for i, line in enumerate(lines):
offset = random.randint(-20, 20)
line_x = wish_x + (max_w - line_widths[i]) // 2 + offset
apply_text_effect(draw, (line_x, current_y), line, font_wish, effect_settings, img)
occupied_boxes.append((line_x, current_y, line_widths[i], line_heights[i]))
if i < len(lines) - 1:
current_y += line_heights[i] + wish_gap
if settings['show_date']:
font_date = font.font_variant(size=settings['date_size'])
if settings['date_format'] == "8 July 2025":
date_text = format_date("%d %B %Y", settings['show_day'])
elif settings['date_format'] == "28 January 2025":
date_text = format_date("%d %B %Y", settings['show_day'])
elif settings['date_format'] == "07/08/2025":
date_text = format_date("%m/%d/%Y", settings['show_day'])
else:
date_text = format_date("%Y-%m-%d", settings['show_day'])
date_width, date_height = get_text_size(draw, date_text, font_date)
date_x = get_random_horizontal_position(img.width, date_width)
date_y = img.height - date_height - 20
# Avoid overlap
for ox, oy, ow, oh in occupied_boxes:
if abs(date_y - oy) < oh + date_height:
date_x = (date_x + img.width // 2) % img.width # shift
if settings['show_day'] and "(" in date_text:
day_part = date_text[date_text.index("("):]
day_width, _ = get_text_size(draw, day_part, font_date)
if date_x + day_width > img.width - 20:
date_x = img.width - day_width - 25
apply_text_effect(draw, (date_x, date_y), date_text, font_date, effect_settings, img)
occupied_boxes.append((date_x, date_y, date_width, date_height))
if settings['show_quote']:
font_quote = font.font_variant(size=settings['quote_size'])
quote_text = settings['quote_text']
lines = [line.strip() for line in quote_text.split('\n') if line.strip()]
total_height = 0
line_heights = []
line_widths = []
for line in lines:
w, h = get_text_size(draw, line, font_quote)
line_heights.append(h)
line_widths.append(w)
total_height += h + 10
quote_y = (img.height - total_height) // 2
# Avoid overlap
for ox, oy, ow, oh in occupied_boxes:
if abs(quote_y - oy) < total_height + oh:
quote_y += oh + 20
for i, line in enumerate(lines):
line_x = (img.width - line_widths[i]) // 2
apply_text_effect(draw, (line_x, quote_y), line, font_quote, effect_settings, img)
occupied_boxes.append((line_x, quote_y, line_widths[i], line_heights[i]))
quote_y += line_heights[i] + 10
if settings['use_watermark'] and settings['watermark_image']:
watermark = settings['watermark_image'].copy()
if settings['watermark_opacity'] < 1.0:
alpha = watermark.split()[3]
alpha = ImageEnhance.Brightness(alpha).enhance(settings['watermark_opacity'])
watermark.putalpha(alpha)
watermark.thumbnail((img.width//4, img.height//4))
pos = get_watermark_position(img, watermark, occupied_boxes)
img.paste(watermark, pos, watermark)
occupied_boxes.append((pos[0], pos[1], watermark.width, watermark.height))
if settings['use_coffee_pet'] and settings['pet_choice']:
pet_files = list_files("assets/pets", [".png", ".jpg", ".jpeg"])
if settings['pet_choice'] == "Random":
selected_pet = random.choice(pet_files)
else:
selected_pet = settings['pet_choice']
pet_path = os.path.join("assets/pets", selected_pet)
if os.path.exists(pet_path):
pet_img = Image.open(pet_path).convert("RGBA")
pet_img = pet_img.resize(
(int(img.width * settings['pet_size']),
int((img.width * settings['pet_size']) * (pet_img.height / pet_img.width))),
Image.LANCZOS
)
pet_pos = get_pet_position(img, pet_img)
# Avoid overlap with watermark if present
for ox, oy, ow, oh in occupied_boxes:
if abs(pet_pos[0] - ox) < ow + pet_img.width and abs(pet_pos[1] - oy) < oh + pet_img.height:
pet_pos = ((img.width - pet_pos[0] - pet_img.width, pet_pos[1]) if pet_pos[0] == 20 else (20, pet_pos[1]))
img.paste(pet_img, pet_pos, pet_img)
occupied_boxes.append((pet_pos[0], pet_pos[1], pet_img.width, pet_img.height))
if settings.get('apply_emoji', False) and settings.get('emojis'):
img = apply_emoji_stickers(img, settings['emojis'], settings.get('num_emojis', 5))
# Apply advanced image enhancements
img = enhance_image_quality(
img,
settings.get('brightness', 1.0),
settings.get('contrast', 1.0),
settings.get('sharpness', 1.2),
settings.get('saturation', 1.1)
)
if settings.get('apply_sepia', False):
img = apply_sepia(img)
if settings.get('apply_bw', False):
img = apply_black_white(img)
if settings.get('apply_vintage', False):
img = apply_vintage(img)
if settings.get('apply_vignette', False):
img = apply_vignette(img, settings.get('vignette_intensity', 0.8))
if settings.get('apply_sketch', False):
img = apply_sketch_effect(img)
if settings.get('apply_cartoon', False):
img = apply_cartoon_effect(img)
if settings.get('apply_anime', False):
img = apply_anime_effect(img)
img = upscale_text_elements(img, scale_factor=settings.get('upscale_factor', 4))
return img.convert("RGB")
except Exception as e:
st.error(f"Error creating variant: {str(e)}")
st.error(traceback.format_exc())
return None
# =================== MAIN APP ===================
if 'generated_images' not in st.session_state:
st.session_state.generated_images = []
if 'watermark_groups' not in st.session_state:
st.session_state.watermark_groups = {}
# Check user access level
user_type = CURRENT_RECORD.get("user_type", "Member") if "_auth_user" in st.session_state else "Guest"
visible_tools = _settings.get("visible_tools", ["V1.0"])
if user_type == "Member":
# Only show basic tools to members
available_tools = ["V1.0"]
elif user_type == "Pro Member":
# Show all tools except admin features
available_tools = [t for t in visible_tools if t != "Admin Panel"]
elif user_type == "Admin":
# Show all tools
available_tools = visible_tools
else: # Guest
# Show only basic tools if login is not required
available_tools = ["V1.0"] if not login_required else []
# Show tool access message based on user type
if user_type == "Member":
st.warning("🔒 You are Member. Upgrade to Pro for more features!")
elif user_type == "Pro Member":
st.success("⭐ You have Pro Member access with premium features!")
elif user_type == "Admin":
st.success("👑 You have Admin access with all feature!")
elif user_type == "Guest":
st.info("👋 You are using the tool as a guest. Some features may be limited.")
# Check if Hue Color Tool is available
hue_tool_available = "Hue Color Tool" in available_tools
# Get tool visibility settings
tool_visibility = _settings.get("tool_visibility", {})
# Show image uploader if enabled
if tool_visibility.get("upload_images", True):
uploaded_images = st.file_uploader("📁 Upload Images", type=["jpg", "jpeg", "png"], accept_multiple_files=True)
else:
uploaded_images = []
with st.sidebar:
st.markdown("### ⚙️ SETTINGS")
if tool_visibility.get("greeting_type", True):
greeting_type = st.selectbox("Greeting Type",
["Good Morning", "Good Afternoon", "Good Evening", "Good Night",
"Happy Birthday", "Merry Christmas", "Custom Greeting"])
if greeting_type == "Custom Greeting":
custom_greeting = st.text_input("Enter Custom Greeting", "Awesome Day!")
else:
custom_greeting = None
if tool_visibility.get("generate_variants", True):
generate_variants = st.checkbox("Generate Multiple Variants", value=False)
if generate_variants:
num_variants = st.slider("Variants per Image", 1, 5, 3)
if tool_visibility.get("style_mode", True):
style_mode = st.selectbox("Style Mode", ["Text", "PNG Overlay"], index=0)
overlay_year = "2025"
if style_mode == 'PNG Overlay' and tool_visibility.get("style_mode", True):
overlay_year = st.selectbox("Overlay Year", ["2024", "2025", "ALL"], index=1)
png_size = st.slider("PNG Overlay Size", 0.1, 1.0, 0.5)
# Hue color tool (only show if available)
if hue_tool_available and tool_visibility.get("hue_tool", True):
st.markdown("### 🎨 HUE COLOR TOOL")
hue_options = ["Original", "Random", "Custom"]
hue_option = st.selectbox("Hue Option", hue_options)
if hue_option == "Custom":
hue_shift = st.slider("Hue Shift", 0.0, 1.0, 0.0, 0.01,
help="Change the color of PNG overlays. 0 = original, 1.0 = full color cycle")
elif hue_option == "Random":
hue_shift = "random"
else:
hue_shift = 0.0
else:
hue_shift = 0.0
if tool_visibility.get("text_effect", True):
text_effect = st.selectbox(
"Text Style",
["White Only", "White + Black Outline + Shadow", "Gradient", "NEON", "Rainbow", "RANDOM", "Country Flag", "3D"],
index=2
)
if tool_visibility.get("text_position", True):
text_position = st.radio("Main Text Position", ["Top Center", "Bottom Center", "Random"], index=1)
text_position = text_position.lower().replace(" ", "_")
if tool_visibility.get("custom_position", True):
st.markdown("### 🎨 MANUAL TEXT POSITIONING")
custom_position = st.checkbox("Enable Manual Positioning", value=False)
if custom_position:
text_x = st.slider("Text X Position", 0, 1000, 100)
text_y = st.slider("Text Y Position", 0, 1000, 100)
if tool_visibility.get("show_text", True):
show_text = st.checkbox("Show Greeting", value=True)
if show_text:
main_size = st.slider("Main Text Size", 10, 200, 90)
if tool_visibility.get("show_wish", True):
show_wish = st.checkbox("Show Wish", value=True)
if show_wish:
wish_size = st.slider("Wish Text Size", 10, 200, 60)
custom_wish = st.checkbox("Custom Wish", value=False)
if custom_wish:
wish_text = st.text_area("Enter Custom Wish", "Have a wonderful day!")
else:
wish_text = None
if tool_visibility.get("overlap_percent", True):
st.markdown("### Overlap Settings")
overlap_percent = st.slider("Main Text Overlap (%)", 0, 50, 14)
if tool_visibility.get("show_date", True):
show_date = st.checkbox("Show Date", value=False)
if show_date:
date_size = st.slider("Date Text Size", 10, 200, 30)
date_format = st.selectbox("Date Format",
["8 July 2025", "28 January 2025", "07/08/2025", "2025-07-08"],
index=0)
show_day = st.checkbox("Show Day", value=False)
if tool_visibility.get("show_quote", True):
show_quote = st.checkbox("Add Quote", value=False)
if show_quote:
quote_size = st.slider("Quote Text Size", 10, 100, 40)
st.markdown("### ✨ QUOTE DATABASE")
st.markdown("" + get_random_quote() + "
", unsafe_allow_html=True)
if st.button("Refresh Quote"):
st.rerun()
if tool_visibility.get("use_watermark", True):
use_watermark = st.checkbox("Add Watermark", value=True)
watermark_images = []
if use_watermark:
watermark_option = st.radio("Watermark Source", ["Pre-made", "Upload Your Own"])
if watermark_option == "Pre-made":
watermark_files = list_files("assets/logos", [".png", ".jpg", ".jpeg"])
if watermark_files:
default_wm = ["Creative Canvas.png", "Nature Vibes.png", "TM SHIVAM.png"]
default = [f for f in default_wm if f in watermark_files]
if not default and len(watermark_files) >= 3:
default = watermark_files[:3]
selected_watermarks = st.multiselect("Select Watermark(s)", watermark_files, default=default)
for watermark_file in selected_watermarks:
watermark_path = os.path.join("assets/logos", watermark_file)
if os.path.exists(watermark_path):
watermark_images.append(Image.open(watermark_path).convert("RGBA"))
else:
uploaded_watermark = st.file_uploader("Upload Watermark", type=["png"], accept_multiple_files=True)
if uploaded_watermark:
for watermark in uploaded_watermark:
watermark_images.append(Image.open(watermark).convert("RGBA"))
watermark_opacity = st.slider("Watermark Opacity", 0.1, 1.0, 1.0)
st.markdown("---")
# Show premium features only to Pro Members and Admins
if user_type in ["Pro Member", "Admin"] and tool_visibility.get("use_coffee_pet", True):
st.markdown("###☕🐾 PRO OVERLAYS")
use_coffee_pet = st.checkbox("Enable Coffee & Pet PNG", value=False)
if use_coffee_pet:
pet_size = st.slider("PNG Size", 0.1, 1.0, 0.3)
pet_files = list_files("assets/pets", [".png", ".jpg", ".jpeg"])
if pet_files:
pet_choice = st.selectbox("Select Pet PNG", ["Random"] + pet_files)
else:
pet_choice = None
st.warning("No pet PNGs found in assets/pets")
else:
pet_choice = None
if user_type in ["Pro Member", "Admin"] and tool_visibility.get("apply_emoji", True):
st.markdown("### 😊 EMOJI STICKERS")
apply_emoji = st.checkbox("Add Emoji Stickers", value=False)
if apply_emoji:
emojis = st.multiselect("Select Emojis", ["😊", "👍", "❤️", "🌟", "🎉", "🔥", "🌈", "✨", "💯"], default=["😊", "❤️", "🌟"])
num_emojis = st.slider("Number of Emojis", 1, 10, 5)
else:
emojis = []
num_emojis = 5
else:
if user_type not in ["Pro Member", "Admin"]:
st.markdown("### 🔒 PRO FEATURES")
st.info("Upgrade to Pro Member to access Coffee & Pet PNG overlays and Emoji Stickers!")
use_coffee_pet = False
pet_choice = None
apply_emoji = False
emojis = []
num_emojis = 5
if tool_visibility.get("bulk_quality", True):
st.markdown("### ⚡ BULK PROCESSING")
bulk_quality = st.selectbox("Output Quality", ["High (90%)", "Medium (80%)", "Low (70%)"], index=0)
# Show advanced features only to Pro Members and Admins
if user_type in ["Pro Member", "Admin"] and tool_visibility.get("advanced_features", True):
with st.expander("🔥 Advanced Features (Compact Form)"):
# Add 20+ new features here
st.markdown("### Image Adjustments")
brightness = st.slider("Brightness", 0.5, 1.5, 1.0)
contrast = st.slider("Contrast", 0.5, 1.5, 1.0)
sharpness = st.slider("Sharpness", 0.5, 2.0, 1.2)
saturation = st.slider("Saturation", 0.5, 2.0, 1.1)
st.markdown("### Filters")
apply_sepia = st.checkbox("Apply Sepia Filter", value=False)
apply_bw = st.checkbox("Apply Black & White Filter", value=False)
apply_vintage = st.checkbox("Apply Vintage Filter", value=False)
apply_vignette = st.checkbox("Apply Vignette Effect", value=False)
if apply_vignette:
vignette_intensity = st.slider("Vignette Intensity", 0.1, 1.0, 0.8)
apply_sketch = st.checkbox("Apply Sketch Effect", value=False)
apply_cartoon = st.checkbox("Apply Cartoon Effect", value=False)
apply_anime = st.checkbox("Apply Anime Effect", value=False)
st.markdown("### Text Customizations")
font_folder = st.text_input("Font Folder Path", "assets/fonts")
upscale_factor = st.slider("Text Upscale Factor", 1, 8, 4)
st.markdown("### Additional Overlays")
use_frame = st.checkbox("Add Frame Overlay", value=False)
if use_frame:
frame_files = list_files("assets/frames", [".png", ".jpg"])
if frame_files:
frame_choice = st.selectbox("Select Frame", frame_files)
frame_path = os.path.join("assets/frames", frame_choice)
frame_size = st.slider("Frame Size", 0.1, 1.0, 1.0)
st.markdown("### Export Options")
export_format = st.selectbox("Export Format", ["JPEG", "PNG"], index=0)
compression_level = st.slider("Compression Level (for JPEG)", 50, 100, 95)
else:
if user_type not in ["Pro Member", "Admin"]:
st.markdown("### 🔒 ADVANCED FEATURES")
st.info("Upgrade to Pro Member to access advanced image editing features!")
brightness = 1.0
contrast = 1.0
sharpness = 1.2
saturation = 1.1
apply_sepia = False
apply_bw = False
apply_vintage = False
apply_vignette = False
vignette_intensity = 0.8
apply_sketch = False
apply_cartoon = False
apply_anime = False
font_folder = "assets/fonts"
upscale_factor = 4
use_frame = False
export_format = "JPEG"
compression_level = 95
if st.button("✨ GENERATE", key="generate", use_container_width=True):
if uploaded_images:
with st.spinner("Processing images with ULTRA PRO quality..."):
processed_images = []
variant_images = []
progress_bar = st.progress(0)
total_images = len(uploaded_images)
effect_mapping = {
"White Only": "white_only",
"White + Black Outline + Shadow": "white_black_outline_shadow",
"Gradient": "gradient",
"NEON": "neon",
"Rainbow": "rainbow",
"RANDOM": "random",
"Country Flag": "country_flag",
"3D": "3d"
}
selected_effect = effect_mapping[text_effect]
watermark_groups = {}
if watermark_images:
if len(watermark_images) > 1:
group_size = len(uploaded_images) // len(watermark_images)
for i, watermark in enumerate(watermark_images):
start_idx = i * group_size
end_idx = (i + 1) * group_size if i < len(watermark_images) - 1 else len(uploaded_images)
watermark_groups[f"Group {i+1}"] = {
'watermark': watermark,
'images': uploaded_images[start_idx:end_idx]
}
else:
watermark_groups["All Images"] = {
'watermark': watermark_images[0],
'images': uploaded_images
}
else:
watermark_groups["All Images"] = {
'watermark': None,
'images': uploaded_images
}
st.session_state.watermark_groups = watermark_groups
for idx, (group_name, group_data) in enumerate(watermark_groups.items()):
watermark = group_data['watermark']
group_images = group_data['images']
for img_idx, uploaded_file in enumerate(group_images):
try:
if uploaded_file is None:
continue
img = Image.open(uploaded_file)
if img is None:
raise ValueError("Could not open image")
img = img.convert("RGBA")
img = smart_crop(img)
if generate_variants:
variants = []
for i in range(num_variants):
settings = {
'greeting_type': custom_greeting if greeting_type == "Custom Greeting" else greeting_type,
'show_text': show_text,
'main_size': main_size if show_text else 90,
'text_position': text_position,
'show_wish': show_wish,
'wish_size': wish_size if show_wish else 60,
'custom_wish': wish_text,
'show_date': show_date,
'show_day': show_day if show_date else False,
'date_size': date_size if show_date else 30,
'date_format': date_format if show_date else "8 July 2025",
'show_quote': show_quote,
'quote_text': get_random_quote() if show_quote else "",
'quote_size': quote_size if show_quote else 40,
'use_watermark': use_watermark,
'watermark_image': watermark,
'watermark_opacity': watermark_opacity if use_watermark else 1.0,
'use_coffee_pet': use_coffee_pet,
'pet_size': pet_size if use_coffee_pet else 0.3,
'pet_choice': pet_choice,
'text_effect': selected_effect,
'custom_position': custom_position,
'text_x': text_x if custom_position else 100,
'text_y': text_y if custom_position else 100,
'apply_emoji': apply_emoji,
'emojis': emojis,
'num_emojis': num_emojis,
'style_mode': style_mode,
'overlap_percent': overlap_percent,
'overlay_year': overlay_year,
'png_size': png_size if style_mode == 'PNG Overlay' else 0.5,
'brightness': brightness,
'contrast': contrast,
'sharpness': sharpness,
'saturation': saturation,
'apply_sepia': apply_sepia,
'apply_bw': apply_bw,
'apply_vintage': apply_vintage,
'apply_vignette': apply_vignette,
'vignette_intensity': vignette_intensity if 'vignette_intensity' in locals() else 0.8,
'apply_sketch': apply_sketch,
'apply_cartoon': apply_cartoon,
'apply_anime': apply_anime,
'font_folder': font_folder,
'upscale_factor': upscale_factor,
'hue_shift': hue_shift if hue_tool_available and style_mode == 'PNG Overlay' else 0.0
}
variant = create_variant(img, settings)
if variant is not None:
variants.append((generate_filename(), variant))
variant_images.extend(variants)
else:
settings = {
'greeting_type': custom_greeting if greeting_type == "Custom Greeting" else greeting_type,
'show_text': show_text,
'main_size': main_size if show_text else 90,
'text_position': text_position,
'show_wish': show_wish,
'wish_size': wish_size if show_wish else 60,
'custom_wish': wish_text,
'show_date': show_date,
'show_day': show_day if show_date else False,
'date_size': date_size if show_date else 30,
'date_format': date_format if show_date else "8 July 2025",
'show_quote': show_quote,
'quote_text': get_random_quote() if show_quote else "",
'quote_size': quote_size if show_quote else 40,
'use_watermark': use_watermark,
'watermark_image': watermark,
'watermark_opacity': watermark_opacity if use_watermark else 1.0,
'use_coffee_pet': use_coffee_pet,
'pet_size': pet_size if use_coffee_pet else 0.3,
'pet_choice': pet_choice,
'text_effect': selected_effect,
'custom_position': custom_position,
'text_x': text_x if custom_position else 100,
'text_y': text_y if custom_position else 100,
'apply_emoji': apply_emoji,
'emojis': emojis,
'num_emojis': num_emojis,
'style_mode': style_mode,
'overlap_percent': overlap_percent,
'overlay_year': overlay_year,
'png_size': png_size if style_mode == 'PNG Overlay' else 0.5,
'brightness': brightness,
'contrast': contrast,
'sharpness': sharpness,
'saturation': saturation,
'apply_sepia': apply_sepia,
'apply_bw': apply_bw,
'apply_vintage': apply_vintage,
'apply_vignette': apply_vignette,
'vignette_intensity': vignette_intensity if 'vignette_intensity' in locals() else 0.8,
'apply_sketch': apply_sketch,
'apply_cartoon': apply_cartoon,
'apply_anime': apply_anime,
'font_folder': font_folder,
'upscale_factor': upscale_factor,
'hue_shift': hue_shift if hue_tool_available and style_mode == 'PNG Overlay' else 0.0
}
processed_img = create_variant(img, settings)
if processed_img is not None:
processed_images.append((generate_filename(), processed_img))
progress = (idx * len(group_images) + img_idx + 1) / total_images
progress_bar.progress(min(progress, 1.0))
except Exception as e:
st.error(f"Error processing {uploaded_file.name}: {str(e)}")
st.error(traceback.format_exc())
continue
st.session_state.generated_images = processed_images + variant_images
if st.session_state.generated_images:
st.success(f"✅ Successfully processed {len(st.session_state.generated_images)} images with ULTRA PRO quality!")
else:
st.warning("No images were processed.")
else:
st.warning("Please upload at least one image")
if st.session_state.generated_images:
if len(st.session_state.watermark_groups) > 1:
for group_name, group_data in st.session_state.watermark_groups.items():
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED, False) as zip_file:
for filename, img in st.session_state.generated_images:
try:
if img.mode != 'RGB':
img = img.convert('RGB')
img_bytes = io.BytesIO()
quality = 90 if bulk_quality == "High (90%)" else 80 if bulk_quality == "Medium (80%)" else 70
img.save(img_bytes, format='JPEG', quality=quality)
zip_file.writestr(filename, img_bytes.getvalue())
except Exception as e:
st.error(f"Error adding {filename} to zip: {str(e)}")
continue
st.download_button(
label=f"⬇️ Download {group_name} Photos",
data=zip_buffer.getvalue(),
file_name=f"{group_name.replace(' ', '_').lower()}_photos.zip",
mime="application/zip",
use_container_width=True
)
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED, False) as zip_file:
for filename, img in st.session_state.generated_images:
try:
if img.mode != 'RGB':
img = img.convert('RGB')
img_bytes = io.BytesIO()
quality = 90 if bulk_quality == "High (90%)" else 80 if bulk_quality == "Medium (80%)" else 70
img.save(img_bytes, format='JPEG', quality=quality)
zip_file.writestr(filename, img_bytes.getvalue())
except Exception as e:
st.error(f"Error adding {filename} to zip: {str(e)}")
continue
st.download_button(
label="⬇️ Download All Photos ",
data=zip_buffer.getvalue(),
file_name="ultra_pro_photos.zip",
mime="application/zip",
use_container_width=True
)
st.markdown("""
😇 RESULTS
""", unsafe_allow_html=True)
cols_per_row = 3
rows = math.ceil(len(st.session_state.generated_images) / cols_per_row)
for row in range(rows):
cols = st.columns(cols_per_row)
for col in range(cols_per_row):
idx = row * cols_per_row + col
if idx < len(st.session_state.generated_images):
filename, img = st.session_state.generated_images[idx]
with cols[col]:
try:
if img.mode != 'RGB':
img = img.convert('RGB')
img_bytes = io.BytesIO()
img.save(img_bytes, format='JPEG', quality=95)
img_bytes.seek(0)
st.image(img_bytes, use_column_width=True)
st.caption(filename)
st.download_button(
label="⬇️ Download",
data=img_bytes.getvalue(),
file_name=filename,
mime="image/jpeg",
key=f"download_{idx}",
use_container_width=True
)
except Exception as e:
st.error(f"Error displaying {filename}: {str(e)}")