Spaces:
Runtime error
Runtime error
Upload 10 files
Browse files- .env +19 -0
- SPACES_README.md +93 -0
- dev-windows.bat +4 -0
- dr_image_magic_FULL.py +728 -0
- gradio_app.py +265 -0
- package.json +115 -0
- simple_image_magic_gui.py +297 -0
- todo.md +44 -0
- vite.config.ts +193 -0
.env
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# DR-Image-Magic Environment Configuration
|
| 2 |
+
|
| 3 |
+
# Database Configuration
|
| 4 |
+
# The app is configured for MySQL
|
| 5 |
+
#
|
| 6 |
+
# OPTION 1: Install MySQL locally (recommended)
|
| 7 |
+
# Download from: https://dev.mysql.com/downloads/installer/
|
| 8 |
+
# Then use: mysql://root:yourpassword@localhost:3306/dr_image_magic
|
| 9 |
+
#
|
| 10 |
+
# OPTION 2: Skip database for now (Gradio app works without it)
|
| 11 |
+
# Just leave this commented out and use the Gradio interface
|
| 12 |
+
#
|
| 13 |
+
# DATABASE_URL=mysql://root:password@localhost:3306/dr_image_magic
|
| 14 |
+
|
| 15 |
+
# Analytics (optional - can leave blank for local dev)
|
| 16 |
+
VITE_ANALYTICS_ENDPOINT=
|
| 17 |
+
VITE_ANALYTICS_WEBSITE_ID=
|
| 18 |
+
|
| 19 |
+
# Add any other environment variables needed below
|
SPACES_README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# DR-Image-Magic on Hugging Face Spaces
|
| 2 |
+
|
| 3 |
+
This is a Gradio web interface for managing the Artistic Photo Transform project.
|
| 4 |
+
|
| 5 |
+
## Deployment Instructions
|
| 6 |
+
|
| 7 |
+
### Option 1: Deploy to Hugging Face Spaces
|
| 8 |
+
|
| 9 |
+
1. Go to [Hugging Face Spaces](https://huggingface.co/spaces)
|
| 10 |
+
2. Create a new Space:
|
| 11 |
+
- Select **Gradio** as the SDK
|
| 12 |
+
- Choose your username/organization
|
| 13 |
+
- Name it: `DR-Image-Magic` (or your preferred name)
|
| 14 |
+
3. In the Space settings:
|
| 15 |
+
- Clone this repository or upload these files
|
| 16 |
+
- The Space will automatically detect `gradio_app.py` and `requirements.txt`
|
| 17 |
+
4. Your Space will automatically start running!
|
| 18 |
+
|
| 19 |
+
### Option 2: Run Locally
|
| 20 |
+
|
| 21 |
+
```bash
|
| 22 |
+
# Install dependencies
|
| 23 |
+
pip install -r requirements.txt
|
| 24 |
+
|
| 25 |
+
# Run the Gradio app
|
| 26 |
+
python gradio_app.py
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
Then open your browser to `http://localhost:7860`
|
| 30 |
+
|
| 31 |
+
## Features
|
| 32 |
+
|
| 33 |
+
The Gradio interface provides:
|
| 34 |
+
|
| 35 |
+
### 📋 Project Info Tab
|
| 36 |
+
|
| 37 |
+
- View project details
|
| 38 |
+
- Tech stack information
|
| 39 |
+
- Setup instructions
|
| 40 |
+
|
| 41 |
+
### ⚙️ Setup Tab
|
| 42 |
+
|
| 43 |
+
- Install dependencies with one click
|
| 44 |
+
- Push database schema
|
| 45 |
+
|
| 46 |
+
### 🚀 Development Tab
|
| 47 |
+
|
| 48 |
+
- Start development server
|
| 49 |
+
- Run type checking
|
| 50 |
+
- Format code automatically
|
| 51 |
+
- Execute test suite
|
| 52 |
+
|
| 53 |
+
### 📦 Production Tab
|
| 54 |
+
|
| 55 |
+
- Build optimized production bundle
|
| 56 |
+
- Deployment guidance
|
| 57 |
+
|
| 58 |
+
## Environment Variables
|
| 59 |
+
|
| 60 |
+
To use this on Hugging Face Spaces with actual functionality, you'll need to:
|
| 61 |
+
|
| 62 |
+
1. Set up environment variables in your Space settings:
|
| 63 |
+
- `NODE_ENV`
|
| 64 |
+
- Database credentials
|
| 65 |
+
- AWS S3 credentials
|
| 66 |
+
- API keys
|
| 67 |
+
|
| 68 |
+
2. Navigate to Space Settings → Variables and secrets
|
| 69 |
+
|
| 70 |
+
## Requirements
|
| 71 |
+
|
| 72 |
+
- Python 3.8+
|
| 73 |
+
- Node.js 18+ (for running pnpm commands)
|
| 74 |
+
- pnpm package manager
|
| 75 |
+
|
| 76 |
+
## Source Code
|
| 77 |
+
|
| 78 |
+
Full project source: https://github.com/DR-Studios/DR-Image-Magic
|
| 79 |
+
|
| 80 |
+
## Note
|
| 81 |
+
|
| 82 |
+
This is a management interface for the DR-Image-Magic project. It assumes:
|
| 83 |
+
|
| 84 |
+
- The project files are cloned/deployed
|
| 85 |
+
- Node.js and pnpm are installed on the Spaces environment
|
| 86 |
+
- Environment variables are properly configured
|
| 87 |
+
|
| 88 |
+
For actual image transformation features, you'll need to:
|
| 89 |
+
|
| 90 |
+
1. Set up AWS S3 credentials
|
| 91 |
+
2. Configure AI model access (Claude, etc.)
|
| 92 |
+
3. Set up database connection
|
| 93 |
+
4. Configure authentication
|
dev-windows.bat
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
REM Windows-compatible dev server
|
| 3 |
+
set NODE_ENV=development
|
| 4 |
+
tsx watch server/_core/index.ts
|
dr_image_magic_FULL.py
ADDED
|
@@ -0,0 +1,728 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
DR-Image-Magic - FULL FEATURED GUI
|
| 4 |
+
All features, no database needed!
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import tkinter as tk
|
| 8 |
+
from tkinter import filedialog, messagebox, ttk, scrolledtext
|
| 9 |
+
from PIL import Image, ImageTk, ImageEnhance, ImageFilter, ImageOps, ImageDraw, ImageFont
|
| 10 |
+
import os
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
import numpy as np
|
| 13 |
+
from datetime import datetime
|
| 14 |
+
|
| 15 |
+
class DrImageMagicFull:
|
| 16 |
+
def __init__(self, root):
|
| 17 |
+
self.root = root
|
| 18 |
+
self.root.title("DR-Image-Magic - FULL EDITION")
|
| 19 |
+
self.root.geometry("1200x900")
|
| 20 |
+
self.root.configure(bg='#0a0a0a')
|
| 21 |
+
|
| 22 |
+
self.current_image = None
|
| 23 |
+
self.original_image = None
|
| 24 |
+
self.image_path = None
|
| 25 |
+
self.history = [] # Undo history
|
| 26 |
+
|
| 27 |
+
self.setup_ui()
|
| 28 |
+
|
| 29 |
+
def setup_ui(self):
|
| 30 |
+
# Main container with scrollbar
|
| 31 |
+
main_canvas = tk.Canvas(self.root, bg='#0a0a0a', highlightthickness=0)
|
| 32 |
+
scrollbar = ttk.Scrollbar(self.root, orient="vertical", command=main_canvas.yview)
|
| 33 |
+
scrollable_frame = tk.Frame(main_canvas, bg='#0a0a0a')
|
| 34 |
+
|
| 35 |
+
scrollable_frame.bind(
|
| 36 |
+
"<Configure>",
|
| 37 |
+
lambda e: main_canvas.configure(scrollregion=main_canvas.bbox("all"))
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
main_canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
|
| 41 |
+
main_canvas.configure(yscrollcommand=scrollbar.set)
|
| 42 |
+
|
| 43 |
+
main_canvas.pack(side="left", fill="both", expand=True)
|
| 44 |
+
scrollbar.pack(side="right", fill="y")
|
| 45 |
+
|
| 46 |
+
# Enable mousewheel scrolling
|
| 47 |
+
def _on_mousewheel(event):
|
| 48 |
+
main_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
|
| 49 |
+
main_canvas.bind_all("<MouseWheel>", _on_mousewheel)
|
| 50 |
+
|
| 51 |
+
# Header
|
| 52 |
+
header = tk.Label(
|
| 53 |
+
scrollable_frame,
|
| 54 |
+
text="🎨 DR-IMAGE-MAGIC",
|
| 55 |
+
font=("Arial", 32, "bold"),
|
| 56 |
+
bg='#0a0a0a',
|
| 57 |
+
fg='#ff6600'
|
| 58 |
+
)
|
| 59 |
+
header.pack(pady=15)
|
| 60 |
+
|
| 61 |
+
subtitle = tk.Label(
|
| 62 |
+
scrollable_frame,
|
| 63 |
+
text="FULL FEATURED EDITION - All Tools, No Limits",
|
| 64 |
+
font=("Arial", 12),
|
| 65 |
+
bg='#0a0a0a',
|
| 66 |
+
fg='#888'
|
| 67 |
+
)
|
| 68 |
+
subtitle.pack()
|
| 69 |
+
|
| 70 |
+
# Upload Section
|
| 71 |
+
upload_frame = tk.Frame(scrollable_frame, bg='#0a0a0a')
|
| 72 |
+
upload_frame.pack(pady=15)
|
| 73 |
+
|
| 74 |
+
upload_btn = tk.Button(
|
| 75 |
+
upload_frame,
|
| 76 |
+
text="📁 UPLOAD IMAGE",
|
| 77 |
+
command=self.upload_image,
|
| 78 |
+
font=("Arial", 16, "bold"),
|
| 79 |
+
bg='#ff6600',
|
| 80 |
+
fg='white',
|
| 81 |
+
padx=30,
|
| 82 |
+
pady=12,
|
| 83 |
+
cursor='hand2',
|
| 84 |
+
relief=tk.FLAT
|
| 85 |
+
)
|
| 86 |
+
upload_btn.pack()
|
| 87 |
+
|
| 88 |
+
# Image Preview
|
| 89 |
+
preview_frame = tk.Frame(scrollable_frame, bg='#1a1a1a', relief=tk.SUNKEN, bd=2)
|
| 90 |
+
preview_frame.pack(pady=10, padx=20, fill=tk.BOTH)
|
| 91 |
+
|
| 92 |
+
self.image_label = tk.Label(
|
| 93 |
+
preview_frame,
|
| 94 |
+
bg='#1a1a1a',
|
| 95 |
+
text="No Image Loaded\n\nDrag & Drop or Click Upload",
|
| 96 |
+
fg='#666',
|
| 97 |
+
font=("Arial", 14)
|
| 98 |
+
)
|
| 99 |
+
self.image_label.pack(pady=40, padx=20)
|
| 100 |
+
|
| 101 |
+
# Quick Actions
|
| 102 |
+
quick_frame = tk.Frame(scrollable_frame, bg='#0a0a0a')
|
| 103 |
+
quick_frame.pack(pady=10)
|
| 104 |
+
|
| 105 |
+
quick_btns = [
|
| 106 |
+
("🔄 UNDO", self.undo_last, '#ff9900'),
|
| 107 |
+
("↺ RESET", self.reset_image, '#cc0000'),
|
| 108 |
+
("💾 SAVE", self.save_image, '#00cc00'),
|
| 109 |
+
("📦 BATCH SAVE", self.batch_save, '#0066cc'),
|
| 110 |
+
]
|
| 111 |
+
|
| 112 |
+
for text, cmd, color in quick_btns:
|
| 113 |
+
btn = tk.Button(
|
| 114 |
+
quick_frame,
|
| 115 |
+
text=text,
|
| 116 |
+
command=cmd,
|
| 117 |
+
font=("Arial", 11, "bold"),
|
| 118 |
+
bg=color,
|
| 119 |
+
fg='white',
|
| 120 |
+
padx=15,
|
| 121 |
+
pady=8,
|
| 122 |
+
cursor='hand2',
|
| 123 |
+
relief=tk.FLAT
|
| 124 |
+
)
|
| 125 |
+
btn.pack(side=tk.LEFT, padx=5)
|
| 126 |
+
|
| 127 |
+
# EFFECTS SECTIONS
|
| 128 |
+
self.create_effects_section(scrollable_frame)
|
| 129 |
+
|
| 130 |
+
def create_effects_section(self, parent):
|
| 131 |
+
# Section 1: ESSENTIAL ENHANCEMENTS
|
| 132 |
+
self.create_section(parent, "⚡ ESSENTIAL ENHANCEMENTS", [
|
| 133 |
+
("✨ Auto Enhance", self.auto_enhance, "Smart enhancement"),
|
| 134 |
+
("💎 Super Sharpen", self.super_sharpen, "Crystal clear"),
|
| 135 |
+
("🌈 Color Pop", self.color_pop, "Vibrant colors"),
|
| 136 |
+
("🔥 HDR Effect", self.hdr_effect, "High dynamic range"),
|
| 137 |
+
])
|
| 138 |
+
|
| 139 |
+
# Section 2: ARTISTIC STYLES
|
| 140 |
+
self.create_section(parent, "🎨 ARTISTIC STYLES", [
|
| 141 |
+
("🖼️ Oil Painting", self.oil_painting, "Classic art"),
|
| 142 |
+
("📸 Film Noir", self.film_noir, "Black & white drama"),
|
| 143 |
+
("🌅 Golden Hour", self.golden_hour, "Warm sunset glow"),
|
| 144 |
+
("❄️ Ice Cold", self.ice_cold, "Cool blue tones"),
|
| 145 |
+
("🔴 Infrared", self.infrared, "IR photography"),
|
| 146 |
+
("⚫ High Key B&W", self.high_key_bw, "Bright grayscale"),
|
| 147 |
+
])
|
| 148 |
+
|
| 149 |
+
# Section 3: DRAMATIC EFFECTS
|
| 150 |
+
self.create_section(parent, "💥 DRAMATIC EFFECTS", [
|
| 151 |
+
("🌙 Dark Fantasy", self.dark_fantasy, "Gothic mood"),
|
| 152 |
+
("☀️ Sunburst", self.sunburst, "Intense brightness"),
|
| 153 |
+
("🎭 Vignette Drama", self.vignette_drama, "Dark edges"),
|
| 154 |
+
("✨ Glow", self.glow_effect, "Soft luminous"),
|
| 155 |
+
("🔆 Cross Process", self.cross_process, "Film effect"),
|
| 156 |
+
])
|
| 157 |
+
|
| 158 |
+
# Section 4: VINTAGE & RETRO
|
| 159 |
+
self.create_section(parent, "📷 VINTAGE & RETRO", [
|
| 160 |
+
("📼 VHS Glitch", self.vhs_glitch, "80s video"),
|
| 161 |
+
("📺 CRT Monitor", self.crt_effect, "Old screen"),
|
| 162 |
+
("🎞️ 70s Film", self.seventies_film, "Retro warm"),
|
| 163 |
+
("📟 Polaroid", self.polaroid, "Instant camera"),
|
| 164 |
+
("🌄 Faded Memory", self.faded_memory, "Old photo"),
|
| 165 |
+
])
|
| 166 |
+
|
| 167 |
+
# Section 5: MODERN & DIGITAL
|
| 168 |
+
self.create_section(parent, "🚀 MODERN & DIGITAL", [
|
| 169 |
+
("💻 Cyberpunk", self.cyberpunk, "Neon future"),
|
| 170 |
+
("🌐 Glitch Art", self.glitch_art, "Digital chaos"),
|
| 171 |
+
("🎮 Pixel Art", self.pixel_art, "8-bit style"),
|
| 172 |
+
("🌌 Vaporwave", self.vaporwave, "A E S T H E T I C"),
|
| 173 |
+
("⚡ Neon Lights", self.neon_lights, "Bright neon"),
|
| 174 |
+
])
|
| 175 |
+
|
| 176 |
+
# Section 6: PRO PRESETS
|
| 177 |
+
self.create_section(parent, "🏆 PRO PRESETS", [
|
| 178 |
+
("📱 Instagram Pro", self.instagram_pro, "Social ready"),
|
| 179 |
+
("🖼️ Gallery Print", self.gallery_print, "Museum quality"),
|
| 180 |
+
("💼 Professional", self.professional, "Business look"),
|
| 181 |
+
("🎪 Artistic Bold", self.artistic_bold, "Creative statement"),
|
| 182 |
+
("🕯️ Candlelight Sketch", self.candlelight_sketch, "Pencil + warm glow"),
|
| 183 |
+
])
|
| 184 |
+
|
| 185 |
+
# Section 7: IMAGE TOOLS
|
| 186 |
+
self.create_section(parent, "🔧 IMAGE TOOLS", [
|
| 187 |
+
("🔲 Expand Canvas", self.expand_canvas, "Extend image"),
|
| 188 |
+
("📐 Upscale 2x", self.upscale_image, "Make bigger"),
|
| 189 |
+
("🎨 Style Transfer", self.style_transfer, "Apply style"),
|
| 190 |
+
("🔄 Rotate 90°", self.rotate_90, "Quick rotate"),
|
| 191 |
+
("↔️ Flip Horizontal", self.flip_horizontal, "Mirror"),
|
| 192 |
+
("↕️ Flip Vertical", self.flip_vertical, "Flip"),
|
| 193 |
+
])
|
| 194 |
+
|
| 195 |
+
def create_section(self, parent, title, effects):
|
| 196 |
+
section = tk.LabelFrame(
|
| 197 |
+
parent,
|
| 198 |
+
text=title,
|
| 199 |
+
font=("Arial", 14, "bold"),
|
| 200 |
+
bg='#0a0a0a',
|
| 201 |
+
fg='#ff6600',
|
| 202 |
+
relief=tk.GROOVE,
|
| 203 |
+
bd=2
|
| 204 |
+
)
|
| 205 |
+
section.pack(pady=15, padx=20, fill=tk.X)
|
| 206 |
+
|
| 207 |
+
grid_frame = tk.Frame(section, bg='#0a0a0a')
|
| 208 |
+
grid_frame.pack(pady=10, padx=10)
|
| 209 |
+
|
| 210 |
+
row, col = 0, 0
|
| 211 |
+
for name, cmd, desc in effects:
|
| 212 |
+
btn_frame = tk.Frame(grid_frame, bg='#1a1a1a', relief=tk.RAISED, bd=1)
|
| 213 |
+
btn_frame.grid(row=row, column=col, padx=5, pady=5, sticky='ew')
|
| 214 |
+
|
| 215 |
+
btn = tk.Button(
|
| 216 |
+
btn_frame,
|
| 217 |
+
text=name,
|
| 218 |
+
command=cmd,
|
| 219 |
+
font=("Arial", 10, "bold"),
|
| 220 |
+
bg='#1a1a1a',
|
| 221 |
+
fg='#ff6600',
|
| 222 |
+
padx=12,
|
| 223 |
+
pady=8,
|
| 224 |
+
cursor='hand2',
|
| 225 |
+
relief=tk.FLAT
|
| 226 |
+
)
|
| 227 |
+
btn.pack(fill=tk.X)
|
| 228 |
+
|
| 229 |
+
desc_label = tk.Label(
|
| 230 |
+
btn_frame,
|
| 231 |
+
text=desc,
|
| 232 |
+
font=("Arial", 8),
|
| 233 |
+
bg='#1a1a1a',
|
| 234 |
+
fg='#666'
|
| 235 |
+
)
|
| 236 |
+
desc_label.pack()
|
| 237 |
+
|
| 238 |
+
col += 1
|
| 239 |
+
if col > 3: # 4 columns
|
| 240 |
+
col = 0
|
| 241 |
+
row += 1
|
| 242 |
+
|
| 243 |
+
def upload_image(self):
|
| 244 |
+
file_path = filedialog.askopenfilename(
|
| 245 |
+
title="Select Image",
|
| 246 |
+
filetypes=[("Images", "*.png *.jpg *.jpeg *.webp *.bmp"), ("All", "*.*")]
|
| 247 |
+
)
|
| 248 |
+
if file_path:
|
| 249 |
+
try:
|
| 250 |
+
self.image_path = file_path
|
| 251 |
+
self.original_image = Image.open(file_path).convert('RGB')
|
| 252 |
+
self.current_image = self.original_image.copy()
|
| 253 |
+
self.history = [self.current_image.copy()]
|
| 254 |
+
self.display_image(self.current_image)
|
| 255 |
+
except Exception as e:
|
| 256 |
+
messagebox.showerror("Error", f"Failed to load: {e}")
|
| 257 |
+
|
| 258 |
+
def display_image(self, image):
|
| 259 |
+
display_img = image.copy()
|
| 260 |
+
display_img.thumbnail((600, 400), Image.Resampling.LANCZOS)
|
| 261 |
+
photo = ImageTk.PhotoImage(display_img)
|
| 262 |
+
self.image_label.configure(image=photo, text="")
|
| 263 |
+
self.image_label.image = photo
|
| 264 |
+
|
| 265 |
+
def save_to_history(self):
|
| 266 |
+
if self.current_image:
|
| 267 |
+
self.history.append(self.current_image.copy())
|
| 268 |
+
if len(self.history) > 20: # Keep last 20
|
| 269 |
+
self.history.pop(0)
|
| 270 |
+
|
| 271 |
+
def check_image(self):
|
| 272 |
+
if not self.current_image:
|
| 273 |
+
messagebox.showwarning("No Image", "Upload an image first!")
|
| 274 |
+
return False
|
| 275 |
+
return True
|
| 276 |
+
|
| 277 |
+
def apply_effect(self, effect_func):
|
| 278 |
+
if not self.check_image(): return
|
| 279 |
+
self.save_to_history()
|
| 280 |
+
try:
|
| 281 |
+
self.current_image = effect_func(self.current_image)
|
| 282 |
+
self.display_image(self.current_image)
|
| 283 |
+
except Exception as e:
|
| 284 |
+
messagebox.showerror("Effect Error", str(e))
|
| 285 |
+
|
| 286 |
+
# === ESSENTIAL ENHANCEMENTS ===
|
| 287 |
+
def auto_enhance(self):
|
| 288 |
+
if not self.check_image(): return
|
| 289 |
+
self.save_to_history()
|
| 290 |
+
img = self.current_image
|
| 291 |
+
img = ImageEnhance.Contrast(img).enhance(1.2)
|
| 292 |
+
img = ImageEnhance.Sharpness(img).enhance(1.3)
|
| 293 |
+
img = ImageEnhance.Color(img).enhance(1.1)
|
| 294 |
+
self.current_image = img
|
| 295 |
+
self.display_image(img)
|
| 296 |
+
|
| 297 |
+
def super_sharpen(self):
|
| 298 |
+
self.apply_effect(lambda img: img.filter(ImageFilter.UnsharpMask(radius=2, percent=150, threshold=3)))
|
| 299 |
+
|
| 300 |
+
def color_pop(self):
|
| 301 |
+
self.apply_effect(lambda img: ImageEnhance.Color(img).enhance(1.8))
|
| 302 |
+
|
| 303 |
+
def hdr_effect(self):
|
| 304 |
+
if not self.check_image(): return
|
| 305 |
+
self.save_to_history()
|
| 306 |
+
img = self.current_image
|
| 307 |
+
img = ImageEnhance.Contrast(img).enhance(1.5)
|
| 308 |
+
img = ImageEnhance.Brightness(img).enhance(0.95)
|
| 309 |
+
img = ImageEnhance.Color(img).enhance(1.3)
|
| 310 |
+
self.current_image = img
|
| 311 |
+
self.display_image(img)
|
| 312 |
+
|
| 313 |
+
# === ARTISTIC STYLES ===
|
| 314 |
+
def oil_painting(self):
|
| 315 |
+
self.apply_effect(lambda img: img.filter(ImageFilter.SMOOTH_MORE).filter(ImageFilter.EDGE_ENHANCE))
|
| 316 |
+
|
| 317 |
+
def film_noir(self):
|
| 318 |
+
if not self.check_image(): return
|
| 319 |
+
self.save_to_history()
|
| 320 |
+
img = self.current_image.convert('L').convert('RGB')
|
| 321 |
+
img = ImageEnhance.Contrast(img).enhance(1.6)
|
| 322 |
+
self.current_image = img
|
| 323 |
+
self.display_image(img)
|
| 324 |
+
|
| 325 |
+
def golden_hour(self):
|
| 326 |
+
if not self.check_image(): return
|
| 327 |
+
self.save_to_history()
|
| 328 |
+
img = self.current_image
|
| 329 |
+
r, g, b = img.split()
|
| 330 |
+
r = r.point(lambda i: min(255, int(i * 1.15)))
|
| 331 |
+
g = g.point(lambda i: min(255, int(i * 1.08)))
|
| 332 |
+
b = b.point(lambda i: int(i * 0.88))
|
| 333 |
+
img = Image.merge('RGB', (r, g, b))
|
| 334 |
+
img = ImageEnhance.Brightness(img).enhance(1.05)
|
| 335 |
+
self.current_image = img
|
| 336 |
+
self.display_image(img)
|
| 337 |
+
|
| 338 |
+
def ice_cold(self):
|
| 339 |
+
if not self.check_image(): return
|
| 340 |
+
self.save_to_history()
|
| 341 |
+
img = self.current_image
|
| 342 |
+
r, g, b = img.split()
|
| 343 |
+
r = r.point(lambda i: int(i * 0.85))
|
| 344 |
+
g = g.point(lambda i: int(i * 0.92))
|
| 345 |
+
b = b.point(lambda i: min(255, int(i * 1.15)))
|
| 346 |
+
self.current_image = Image.merge('RGB', (r, g, b))
|
| 347 |
+
self.display_image(self.current_image)
|
| 348 |
+
|
| 349 |
+
def infrared(self):
|
| 350 |
+
if not self.check_image(): return
|
| 351 |
+
self.save_to_history()
|
| 352 |
+
img = self.current_image.convert('L')
|
| 353 |
+
img = ImageOps.invert(img)
|
| 354 |
+
img = ImageEnhance.Contrast(img).enhance(1.3)
|
| 355 |
+
r = img
|
| 356 |
+
g = img
|
| 357 |
+
b = img.point(lambda i: int(i * 0.7))
|
| 358 |
+
self.current_image = Image.merge('RGB', (r, g, b))
|
| 359 |
+
self.display_image(self.current_image)
|
| 360 |
+
|
| 361 |
+
def high_key_bw(self):
|
| 362 |
+
if not self.check_image(): return
|
| 363 |
+
self.save_to_history()
|
| 364 |
+
img = self.current_image.convert('L').convert('RGB')
|
| 365 |
+
img = ImageEnhance.Brightness(img).enhance(1.2)
|
| 366 |
+
img = ImageEnhance.Contrast(img).enhance(0.8)
|
| 367 |
+
self.current_image = img
|
| 368 |
+
self.display_image(img)
|
| 369 |
+
|
| 370 |
+
# === DRAMATIC EFFECTS ===
|
| 371 |
+
def dark_fantasy(self):
|
| 372 |
+
if not self.check_image(): return
|
| 373 |
+
self.save_to_history()
|
| 374 |
+
img = self.current_image
|
| 375 |
+
img = ImageEnhance.Brightness(img).enhance(0.6)
|
| 376 |
+
img = ImageEnhance.Contrast(img).enhance(1.6)
|
| 377 |
+
img = ImageEnhance.Color(img).enhance(0.8)
|
| 378 |
+
self.current_image = img
|
| 379 |
+
self.display_image(img)
|
| 380 |
+
|
| 381 |
+
def sunburst(self):
|
| 382 |
+
self.apply_effect(lambda img: ImageEnhance.Brightness(img).enhance(1.4))
|
| 383 |
+
|
| 384 |
+
def vignette_drama(self):
|
| 385 |
+
if not self.check_image(): return
|
| 386 |
+
self.save_to_history()
|
| 387 |
+
img = self.current_image
|
| 388 |
+
width, height = img.size
|
| 389 |
+
mask = Image.new('L', (width, height), 0)
|
| 390 |
+
draw = ImageDraw.Draw(mask)
|
| 391 |
+
|
| 392 |
+
for i in range(min(width, height) // 4):
|
| 393 |
+
alpha = int(255 * (i / (min(width, height) // 4)))
|
| 394 |
+
draw.ellipse(
|
| 395 |
+
[i, i, width-i, height-i],
|
| 396 |
+
fill=alpha
|
| 397 |
+
)
|
| 398 |
+
|
| 399 |
+
dark = Image.new('RGB', img.size, (0, 0, 0))
|
| 400 |
+
self.current_image = Image.composite(img, dark, mask)
|
| 401 |
+
self.display_image(self.current_image)
|
| 402 |
+
|
| 403 |
+
def glow_effect(self):
|
| 404 |
+
if not self.check_image(): return
|
| 405 |
+
self.save_to_history()
|
| 406 |
+
img = self.current_image
|
| 407 |
+
glow = img.filter(ImageFilter.GaussianBlur(15))
|
| 408 |
+
glow = ImageEnhance.Brightness(glow).enhance(1.5)
|
| 409 |
+
self.current_image = Image.blend(img, glow, 0.3)
|
| 410 |
+
self.display_image(self.current_image)
|
| 411 |
+
|
| 412 |
+
def cross_process(self):
|
| 413 |
+
if not self.check_image(): return
|
| 414 |
+
self.save_to_history()
|
| 415 |
+
img = self.current_image
|
| 416 |
+
r, g, b = img.split()
|
| 417 |
+
r = r.point(lambda i: min(255, int(i * 1.1)))
|
| 418 |
+
g = g.point(lambda i: int(i * 0.95))
|
| 419 |
+
b = b.point(lambda i: min(255, int(i * 1.15)))
|
| 420 |
+
img = Image.merge('RGB', (r, g, b))
|
| 421 |
+
img = ImageEnhance.Contrast(img).enhance(1.3)
|
| 422 |
+
self.current_image = img
|
| 423 |
+
self.display_image(img)
|
| 424 |
+
|
| 425 |
+
# === VINTAGE & RETRO ===
|
| 426 |
+
def vhs_glitch(self):
|
| 427 |
+
if not self.check_image(): return
|
| 428 |
+
self.save_to_history()
|
| 429 |
+
img = self.current_image
|
| 430 |
+
img = ImageEnhance.Contrast(img).enhance(1.2)
|
| 431 |
+
img = ImageEnhance.Color(img).enhance(0.8)
|
| 432 |
+
self.current_image = img
|
| 433 |
+
self.display_image(img)
|
| 434 |
+
|
| 435 |
+
def crt_effect(self):
|
| 436 |
+
self.apply_effect(lambda img: ImageEnhance.Brightness(img).enhance(1.1))
|
| 437 |
+
|
| 438 |
+
def seventies_film(self):
|
| 439 |
+
if not self.check_image(): return
|
| 440 |
+
self.save_to_history()
|
| 441 |
+
img = self.current_image
|
| 442 |
+
r, g, b = img.split()
|
| 443 |
+
r = r.point(lambda i: min(255, int(i * 1.12)))
|
| 444 |
+
g = g.point(lambda i: min(255, int(i * 1.05)))
|
| 445 |
+
b = b.point(lambda i: int(i * 0.9))
|
| 446 |
+
img = Image.merge('RGB', (r, g, b))
|
| 447 |
+
img = ImageEnhance.Contrast(img).enhance(0.9)
|
| 448 |
+
self.current_image = img
|
| 449 |
+
self.display_image(img)
|
| 450 |
+
|
| 451 |
+
def polaroid(self):
|
| 452 |
+
if not self.check_image(): return
|
| 453 |
+
self.save_to_history()
|
| 454 |
+
img = self.current_image
|
| 455 |
+
img = ImageEnhance.Brightness(img).enhance(1.1)
|
| 456 |
+
img = ImageEnhance.Color(img).enhance(0.85)
|
| 457 |
+
img = ImageEnhance.Contrast(img).enhance(0.95)
|
| 458 |
+
self.current_image = img
|
| 459 |
+
self.display_image(img)
|
| 460 |
+
|
| 461 |
+
def faded_memory(self):
|
| 462 |
+
if not self.check_image(): return
|
| 463 |
+
self.save_to_history()
|
| 464 |
+
img = self.current_image
|
| 465 |
+
img = ImageEnhance.Color(img).enhance(0.6)
|
| 466 |
+
img = ImageEnhance.Brightness(img).enhance(1.15)
|
| 467 |
+
img = ImageEnhance.Contrast(img).enhance(0.8)
|
| 468 |
+
self.current_image = img
|
| 469 |
+
self.display_image(img)
|
| 470 |
+
|
| 471 |
+
# === MODERN & DIGITAL ===
|
| 472 |
+
def cyberpunk(self):
|
| 473 |
+
if not self.check_image(): return
|
| 474 |
+
self.save_to_history()
|
| 475 |
+
img = self.current_image
|
| 476 |
+
r, g, b = img.split()
|
| 477 |
+
r = r.point(lambda i: min(255, int(i * 1.2)))
|
| 478 |
+
g = g.point(lambda i: int(i * 0.9))
|
| 479 |
+
b = b.point(lambda i: min(255, int(i * 1.3)))
|
| 480 |
+
img = Image.merge('RGB', (r, g, b))
|
| 481 |
+
img = ImageEnhance.Contrast(img).enhance(1.4)
|
| 482 |
+
self.current_image = img
|
| 483 |
+
self.display_image(img)
|
| 484 |
+
|
| 485 |
+
def glitch_art(self):
|
| 486 |
+
self.apply_effect(lambda img: img.filter(ImageFilter.EDGE_ENHANCE_MORE))
|
| 487 |
+
|
| 488 |
+
def pixel_art(self):
|
| 489 |
+
if not self.check_image(): return
|
| 490 |
+
self.save_to_history()
|
| 491 |
+
img = self.current_image
|
| 492 |
+
small = img.resize((img.width // 16, img.height // 16), Image.Resampling.NEAREST)
|
| 493 |
+
self.current_image = small.resize(img.size, Image.Resampling.NEAREST)
|
| 494 |
+
self.display_image(self.current_image)
|
| 495 |
+
|
| 496 |
+
def vaporwave(self):
|
| 497 |
+
if not self.check_image(): return
|
| 498 |
+
self.save_to_history()
|
| 499 |
+
img = self.current_image
|
| 500 |
+
r, g, b = img.split()
|
| 501 |
+
r = r.point(lambda i: min(255, int(i * 1.2)))
|
| 502 |
+
g = g.point(lambda i: min(255, int(i * 0.95)))
|
| 503 |
+
b = b.point(lambda i: min(255, int(i * 1.25)))
|
| 504 |
+
img = Image.merge('RGB', (r, g, b))
|
| 505 |
+
img = ImageEnhance.Color(img).enhance(1.5)
|
| 506 |
+
self.current_image = img
|
| 507 |
+
self.display_image(img)
|
| 508 |
+
|
| 509 |
+
def neon_lights(self):
|
| 510 |
+
if not self.check_image(): return
|
| 511 |
+
self.save_to_history()
|
| 512 |
+
img = self.current_image
|
| 513 |
+
img = ImageEnhance.Color(img).enhance(2.0)
|
| 514 |
+
img = ImageEnhance.Contrast(img).enhance(1.3)
|
| 515 |
+
img = ImageEnhance.Brightness(img).enhance(1.2)
|
| 516 |
+
self.current_image = img
|
| 517 |
+
self.display_image(img)
|
| 518 |
+
|
| 519 |
+
# === PRO PRESETS ===
|
| 520 |
+
def instagram_pro(self):
|
| 521 |
+
if not self.check_image(): return
|
| 522 |
+
self.save_to_history()
|
| 523 |
+
img = self.current_image
|
| 524 |
+
img = ImageEnhance.Contrast(img).enhance(1.15)
|
| 525 |
+
img = ImageEnhance.Color(img).enhance(1.2)
|
| 526 |
+
img = ImageEnhance.Sharpness(img).enhance(1.1)
|
| 527 |
+
self.current_image = img
|
| 528 |
+
self.display_image(img)
|
| 529 |
+
|
| 530 |
+
def gallery_print(self):
|
| 531 |
+
if not self.check_image(): return
|
| 532 |
+
self.save_to_history()
|
| 533 |
+
img = self.current_image
|
| 534 |
+
img = ImageEnhance.Contrast(img).enhance(1.1)
|
| 535 |
+
img = ImageEnhance.Sharpness(img).enhance(1.3)
|
| 536 |
+
img = ImageEnhance.Color(img).enhance(1.05)
|
| 537 |
+
self.current_image = img
|
| 538 |
+
self.display_image(img)
|
| 539 |
+
|
| 540 |
+
def professional(self):
|
| 541 |
+
self.apply_effect(lambda img: ImageEnhance.Contrast(img).enhance(1.1))
|
| 542 |
+
|
| 543 |
+
def artistic_bold(self):
|
| 544 |
+
if not self.check_image(): return
|
| 545 |
+
self.save_to_history()
|
| 546 |
+
img = self.current_image
|
| 547 |
+
img = ImageEnhance.Color(img).enhance(1.6)
|
| 548 |
+
img = ImageEnhance.Contrast(img).enhance(1.4)
|
| 549 |
+
self.current_image = img
|
| 550 |
+
self.display_image(img)
|
| 551 |
+
|
| 552 |
+
def candlelight_sketch(self):
|
| 553 |
+
"""Pencil sketch with warm candlelight - mimics the style Dave showed"""
|
| 554 |
+
if not self.check_image(): return
|
| 555 |
+
self.save_to_history()
|
| 556 |
+
|
| 557 |
+
img = self.current_image
|
| 558 |
+
|
| 559 |
+
# Step 1: Reduce color saturation (pencil sketch base)
|
| 560 |
+
img = ImageEnhance.Color(img).enhance(0.3)
|
| 561 |
+
|
| 562 |
+
# Step 2: Add warm candlelight glow (orange/golden tones)
|
| 563 |
+
r, g, b = img.split()
|
| 564 |
+
# Boost reds/oranges (candlelight)
|
| 565 |
+
r = r.point(lambda i: min(255, int(i * 1.3)))
|
| 566 |
+
g = g.point(lambda i: min(255, int(i * 1.15)))
|
| 567 |
+
b = b.point(lambda i: int(i * 0.75)) # Reduce blue for warmth
|
| 568 |
+
img = Image.merge('RGB', (r, g, b))
|
| 569 |
+
|
| 570 |
+
# Step 3: High contrast (dramatic lighting)
|
| 571 |
+
img = ImageEnhance.Contrast(img).enhance(1.8)
|
| 572 |
+
|
| 573 |
+
# Step 4: Darken overall (dark shadows)
|
| 574 |
+
img = ImageEnhance.Brightness(img).enhance(0.7)
|
| 575 |
+
|
| 576 |
+
# Step 5: Add slight blur to mimic soft pencil texture
|
| 577 |
+
img = img.filter(ImageFilter.GaussianBlur(radius=0.5))
|
| 578 |
+
|
| 579 |
+
# Step 6: Edge enhance for sketch lines
|
| 580 |
+
img = img.filter(ImageFilter.EDGE_ENHANCE)
|
| 581 |
+
|
| 582 |
+
self.current_image = img
|
| 583 |
+
self.display_image(img)
|
| 584 |
+
|
| 585 |
+
# === ACTIONS ===
|
| 586 |
+
def undo_last(self):
|
| 587 |
+
if len(self.history) > 1:
|
| 588 |
+
self.history.pop()
|
| 589 |
+
self.current_image = self.history[-1].copy()
|
| 590 |
+
self.display_image(self.current_image)
|
| 591 |
+
else:
|
| 592 |
+
messagebox.showinfo("Undo", "No more undo history!")
|
| 593 |
+
|
| 594 |
+
def reset_image(self):
|
| 595 |
+
if self.original_image:
|
| 596 |
+
self.current_image = self.original_image.copy()
|
| 597 |
+
self.history = [self.current_image.copy()]
|
| 598 |
+
self.display_image(self.current_image)
|
| 599 |
+
|
| 600 |
+
def save_image(self):
|
| 601 |
+
if not self.check_image(): return
|
| 602 |
+
file_path = filedialog.asksaveasfilename(
|
| 603 |
+
defaultextension=".png",
|
| 604 |
+
filetypes=[("PNG", "*.png"), ("JPEG", "*.jpg"), ("WebP", "*.webp")]
|
| 605 |
+
)
|
| 606 |
+
if file_path:
|
| 607 |
+
try:
|
| 608 |
+
self.current_image.save(file_path, quality=95)
|
| 609 |
+
messagebox.showinfo("Saved!", f"Saved to:\n{file_path}")
|
| 610 |
+
except Exception as e:
|
| 611 |
+
messagebox.showerror("Error", f"Save failed: {e}")
|
| 612 |
+
|
| 613 |
+
def batch_save(self):
|
| 614 |
+
if not self.check_image(): return
|
| 615 |
+
folder = filedialog.askdirectory(title="Select Output Folder")
|
| 616 |
+
if folder:
|
| 617 |
+
try:
|
| 618 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 619 |
+
filename = f"dr_magic_{timestamp}.png"
|
| 620 |
+
path = os.path.join(folder, filename)
|
| 621 |
+
self.current_image.save(path, quality=95)
|
| 622 |
+
messagebox.showinfo("Batch Saved!", f"Saved to:\n{path}")
|
| 623 |
+
except Exception as e:
|
| 624 |
+
messagebox.showerror("Error", str(e))
|
| 625 |
+
|
| 626 |
+
# === IMAGE TOOLS ===
|
| 627 |
+
def expand_canvas(self):
|
| 628 |
+
if not self.check_image(): return
|
| 629 |
+
self.save_to_history()
|
| 630 |
+
|
| 631 |
+
# Ask for expansion amount
|
| 632 |
+
expansion = 100 # pixels on each side
|
| 633 |
+
|
| 634 |
+
img = self.current_image
|
| 635 |
+
width, height = img.size
|
| 636 |
+
new_width = width + (expansion * 2)
|
| 637 |
+
new_height = height + (expansion * 2)
|
| 638 |
+
|
| 639 |
+
# Create new canvas
|
| 640 |
+
expanded = Image.new('RGB', (new_width, new_height), (20, 20, 20))
|
| 641 |
+
|
| 642 |
+
# Paste original in center
|
| 643 |
+
expanded.paste(img, (expansion, expansion))
|
| 644 |
+
|
| 645 |
+
self.current_image = expanded
|
| 646 |
+
self.display_image(self.current_image)
|
| 647 |
+
messagebox.showinfo("Expanded!", f"Canvas expanded by {expansion}px on each side")
|
| 648 |
+
|
| 649 |
+
def upscale_image(self):
|
| 650 |
+
if not self.check_image(): return
|
| 651 |
+
self.save_to_history()
|
| 652 |
+
|
| 653 |
+
img = self.current_image
|
| 654 |
+
new_size = (img.width * 2, img.height * 2)
|
| 655 |
+
|
| 656 |
+
# Use LANCZOS for quality upscaling
|
| 657 |
+
upscaled = img.resize(new_size, Image.Resampling.LANCZOS)
|
| 658 |
+
|
| 659 |
+
self.current_image = upscaled
|
| 660 |
+
self.display_image(self.current_image)
|
| 661 |
+
messagebox.showinfo("Upscaled!", f"Image doubled to {new_size[0]}x{new_size[1]}")
|
| 662 |
+
|
| 663 |
+
def style_transfer(self):
|
| 664 |
+
if not self.check_image(): return
|
| 665 |
+
|
| 666 |
+
style_path = filedialog.askopenfilename(
|
| 667 |
+
title="Select Style Reference Image",
|
| 668 |
+
filetypes=[("Images", "*.png *.jpg *.jpeg"), ("All", "*.*")]
|
| 669 |
+
)
|
| 670 |
+
|
| 671 |
+
if not style_path:
|
| 672 |
+
return
|
| 673 |
+
|
| 674 |
+
self.save_to_history()
|
| 675 |
+
|
| 676 |
+
try:
|
| 677 |
+
style_img = Image.open(style_path).convert('RGB')
|
| 678 |
+
|
| 679 |
+
# Simple style transfer using color statistics
|
| 680 |
+
content = self.current_image
|
| 681 |
+
|
| 682 |
+
# Get color statistics from style image
|
| 683 |
+
style_array = np.array(style_img)
|
| 684 |
+
content_array = np.array(content)
|
| 685 |
+
|
| 686 |
+
# Match mean and std of each channel
|
| 687 |
+
for i in range(3): # RGB channels
|
| 688 |
+
content_mean = content_array[:,:,i].mean()
|
| 689 |
+
content_std = content_array[:,:,i].std()
|
| 690 |
+
style_mean = style_array[:,:,i].mean()
|
| 691 |
+
style_std = style_array[:,:,i].std()
|
| 692 |
+
|
| 693 |
+
# Transfer statistics
|
| 694 |
+
content_array[:,:,i] = (content_array[:,:,i] - content_mean) / (content_std + 1e-5)
|
| 695 |
+
content_array[:,:,i] = content_array[:,:,i] * style_std + style_mean
|
| 696 |
+
|
| 697 |
+
# Clip to valid range
|
| 698 |
+
content_array = np.clip(content_array, 0, 255).astype(np.uint8)
|
| 699 |
+
|
| 700 |
+
self.current_image = Image.fromarray(content_array)
|
| 701 |
+
self.display_image(self.current_image)
|
| 702 |
+
messagebox.showinfo("Style Applied!", "Style transfer complete!")
|
| 703 |
+
|
| 704 |
+
except Exception as e:
|
| 705 |
+
messagebox.showerror("Error", f"Style transfer failed: {e}")
|
| 706 |
+
|
| 707 |
+
def rotate_90(self):
|
| 708 |
+
if not self.check_image(): return
|
| 709 |
+
self.save_to_history()
|
| 710 |
+
self.current_image = self.current_image.rotate(-90, expand=True)
|
| 711 |
+
self.display_image(self.current_image)
|
| 712 |
+
|
| 713 |
+
def flip_horizontal(self):
|
| 714 |
+
if not self.check_image(): return
|
| 715 |
+
self.save_to_history()
|
| 716 |
+
self.current_image = self.current_image.transpose(Image.FLIP_LEFT_RIGHT)
|
| 717 |
+
self.display_image(self.current_image)
|
| 718 |
+
|
| 719 |
+
def flip_vertical(self):
|
| 720 |
+
if not self.check_image(): return
|
| 721 |
+
self.save_to_history()
|
| 722 |
+
self.current_image = self.current_image.transpose(Image.FLIP_TOP_BOTTOM)
|
| 723 |
+
self.display_image(self.current_image)
|
| 724 |
+
|
| 725 |
+
if __name__ == "__main__":
|
| 726 |
+
root = tk.Tk()
|
| 727 |
+
app = DrImageMagicFull(root)
|
| 728 |
+
root.mainloop()
|
gradio_app.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
DR-Image-Magic Gradio Interface
|
| 4 |
+
A web UI for the Artistic Photo Transform project on Hugging Face Spaces
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import gradio as gr
|
| 8 |
+
import subprocess
|
| 9 |
+
import os
|
| 10 |
+
import sys
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
# Get the project root directory
|
| 14 |
+
PROJECT_ROOT = Path(__file__).parent
|
| 15 |
+
|
| 16 |
+
def run_command(cmd, shell=True):
|
| 17 |
+
"""Execute a command and return output"""
|
| 18 |
+
try:
|
| 19 |
+
result = subprocess.run(
|
| 20 |
+
cmd,
|
| 21 |
+
shell=shell,
|
| 22 |
+
capture_output=True,
|
| 23 |
+
text=True,
|
| 24 |
+
cwd=PROJECT_ROOT,
|
| 25 |
+
timeout=300
|
| 26 |
+
)
|
| 27 |
+
return result.stdout + result.stderr
|
| 28 |
+
except subprocess.TimeoutExpired:
|
| 29 |
+
return "⚠️ Command timed out (5 minutes)"
|
| 30 |
+
except Exception as e:
|
| 31 |
+
return f"❌ Error: {str(e)}"
|
| 32 |
+
|
| 33 |
+
def install_dependencies():
|
| 34 |
+
"""Install pnpm dependencies"""
|
| 35 |
+
return run_command("pnpm install")
|
| 36 |
+
|
| 37 |
+
def start_dev_server():
|
| 38 |
+
"""Start development server"""
|
| 39 |
+
return run_command("pnpm run dev", shell=True)
|
| 40 |
+
|
| 41 |
+
def type_check():
|
| 42 |
+
"""Run TypeScript type checking"""
|
| 43 |
+
return run_command("pnpm run check")
|
| 44 |
+
|
| 45 |
+
def format_code():
|
| 46 |
+
"""Format code with prettier"""
|
| 47 |
+
return run_command("pnpm run format")
|
| 48 |
+
|
| 49 |
+
def run_tests():
|
| 50 |
+
"""Run test suite"""
|
| 51 |
+
return run_command("pnpm test")
|
| 52 |
+
|
| 53 |
+
def push_db_schema():
|
| 54 |
+
"""Push database schema"""
|
| 55 |
+
return run_command("pnpm run db:push")
|
| 56 |
+
|
| 57 |
+
def build_production():
|
| 58 |
+
"""Build for production"""
|
| 59 |
+
return run_command("pnpm run build")
|
| 60 |
+
|
| 61 |
+
def get_project_info():
|
| 62 |
+
"""Get project information"""
|
| 63 |
+
info = """
|
| 64 |
+
# 🎨 Artistic Photo Transform - DR-Image-Magic
|
| 65 |
+
|
| 66 |
+
## Project Overview
|
| 67 |
+
A full-stack application that uses AI to transform photos into artistic variations.
|
| 68 |
+
|
| 69 |
+
## Tech Stack
|
| 70 |
+
- **Frontend**: React 19 + TypeScript + Tailwind CSS
|
| 71 |
+
- **Backend**: Express + tRPC + Node.js
|
| 72 |
+
- **Database**: MySQL + Drizzle ORM
|
| 73 |
+
- **Storage**: AWS S3
|
| 74 |
+
- **AI**: Image generation with custom prompts
|
| 75 |
+
- **UI Components**: Radix UI + shadcn/ui
|
| 76 |
+
- **Testing**: Vitest
|
| 77 |
+
|
| 78 |
+
## Project Structure
|
| 79 |
+
```
|
| 80 |
+
├── client/ # React frontend application
|
| 81 |
+
├── server/ # Express backend server
|
| 82 |
+
├── shared/ # Shared types and utilities
|
| 83 |
+
├── drizzle/ # Database migrations
|
| 84 |
+
└── patches/ # Patch files for dependencies
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
## Key Features
|
| 88 |
+
✅ Photo upload with drag-and-drop
|
| 89 |
+
✅ AI-powered image transformations (3 variations)
|
| 90 |
+
✅ S3 storage for images
|
| 91 |
+
✅ User authentication & history
|
| 92 |
+
✅ Transformation gallery view
|
| 93 |
+
✅ Image download functionality
|
| 94 |
+
✅ Real-time status updates
|
| 95 |
+
✅ Responsive design
|
| 96 |
+
|
| 97 |
+
## Scripts
|
| 98 |
+
- `pnpm run dev` - Start development server
|
| 99 |
+
- `pnpm run build` - Build for production
|
| 100 |
+
- `pnpm start` - Run production server
|
| 101 |
+
- `pnpm test` - Run tests
|
| 102 |
+
- `pnpm run format` - Format code
|
| 103 |
+
- `pnpm run check` - Type checking
|
| 104 |
+
- `pnpm run db:push` - Push database schema
|
| 105 |
+
"""
|
| 106 |
+
return info
|
| 107 |
+
|
| 108 |
+
def get_setup_guide():
|
| 109 |
+
"""Get setup instructions"""
|
| 110 |
+
guide = """
|
| 111 |
+
# Setup Guide
|
| 112 |
+
|
| 113 |
+
## Prerequisites
|
| 114 |
+
- Node.js 18+
|
| 115 |
+
- pnpm package manager
|
| 116 |
+
- MySQL database
|
| 117 |
+
- AWS S3 account
|
| 118 |
+
|
| 119 |
+
## Installation Steps
|
| 120 |
+
|
| 121 |
+
### 1. Install Dependencies
|
| 122 |
+
Click the button below to install all required packages:
|
| 123 |
+
(Note: This requires pnpm to be installed)
|
| 124 |
+
|
| 125 |
+
### 2. Environment Variables
|
| 126 |
+
Create a `.env` file with:
|
| 127 |
+
```
|
| 128 |
+
DATABASE_URL=mysql://user:password@localhost:3306/dr_image_magic
|
| 129 |
+
AWS_ACCESS_KEY_ID=your_key
|
| 130 |
+
AWS_SECRET_ACCESS_KEY=your_secret
|
| 131 |
+
AWS_S3_BUCKET=your_bucket
|
| 132 |
+
OPENAI_API_KEY=your_openai_key
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
### 3. Database Setup
|
| 136 |
+
Run migrations to set up your database:
|
| 137 |
+
```bash
|
| 138 |
+
pnpm run db:push
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
### 4. Development
|
| 142 |
+
Start the development server:
|
| 143 |
+
```bash
|
| 144 |
+
pnpm run dev
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
The app will be available at `http://localhost:5173`
|
| 148 |
+
|
| 149 |
+
### 5. Production Deployment
|
| 150 |
+
Build the app:
|
| 151 |
+
```bash
|
| 152 |
+
pnpm run build
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
Start the production server:
|
| 156 |
+
```bash
|
| 157 |
+
pnpm start
|
| 158 |
+
```
|
| 159 |
+
"""
|
| 160 |
+
return guide
|
| 161 |
+
|
| 162 |
+
# Create the Gradio interface
|
| 163 |
+
with gr.Blocks(title="DR-Image-Magic") as demo:
|
| 164 |
+
gr.Markdown("""
|
| 165 |
+
# 🎨 DR-Image-Magic Dashboard
|
| 166 |
+
## Artistic Photo Transform Project Manager
|
| 167 |
+
""")
|
| 168 |
+
|
| 169 |
+
with gr.Tabs():
|
| 170 |
+
# Tab 1: Project Info
|
| 171 |
+
with gr.Tab("📋 Project Info"):
|
| 172 |
+
project_info = gr.Markdown(get_project_info())
|
| 173 |
+
|
| 174 |
+
# Tab 2: Setup
|
| 175 |
+
with gr.Tab("⚙️ Setup"):
|
| 176 |
+
gr.Markdown(get_setup_guide())
|
| 177 |
+
with gr.Row():
|
| 178 |
+
install_btn = gr.Button("📥 Install Dependencies", size="lg", variant="primary")
|
| 179 |
+
db_btn = gr.Button("🗄️ Push Database Schema", size="lg")
|
| 180 |
+
|
| 181 |
+
install_output = gr.Textbox(
|
| 182 |
+
label="Installation Output",
|
| 183 |
+
lines=10,
|
| 184 |
+
interactive=False,
|
| 185 |
+
placeholder="Click 'Install Dependencies' to start..."
|
| 186 |
+
)
|
| 187 |
+
db_output = gr.Textbox(
|
| 188 |
+
label="Database Push Output",
|
| 189 |
+
lines=10,
|
| 190 |
+
interactive=False,
|
| 191 |
+
placeholder="Click 'Push Database Schema' to start..."
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
install_btn.click(install_dependencies, outputs=install_output)
|
| 195 |
+
db_btn.click(push_db_schema, outputs=db_output)
|
| 196 |
+
|
| 197 |
+
# Tab 3: Development
|
| 198 |
+
with gr.Tab("🚀 Development"):
|
| 199 |
+
gr.Markdown("""
|
| 200 |
+
## Development Tools
|
| 201 |
+
|
| 202 |
+
Use these tools during development:
|
| 203 |
+
- **Type Check**: Verify TypeScript types
|
| 204 |
+
- **Format Code**: Auto-format with Prettier
|
| 205 |
+
- **Run Tests**: Execute test suite
|
| 206 |
+
""")
|
| 207 |
+
|
| 208 |
+
with gr.Row():
|
| 209 |
+
type_check_btn = gr.Button("✓ Type Check", size="lg", variant="secondary")
|
| 210 |
+
format_btn = gr.Button("📝 Format Code", size="lg", variant="secondary")
|
| 211 |
+
test_btn = gr.Button("🧪 Run Tests", size="lg", variant="secondary")
|
| 212 |
+
|
| 213 |
+
with gr.Row():
|
| 214 |
+
type_check_output = gr.Textbox(
|
| 215 |
+
label="Type Check Results",
|
| 216 |
+
lines=8,
|
| 217 |
+
interactive=False
|
| 218 |
+
)
|
| 219 |
+
format_output = gr.Textbox(
|
| 220 |
+
label="Format Results",
|
| 221 |
+
lines=8,
|
| 222 |
+
interactive=False
|
| 223 |
+
)
|
| 224 |
+
test_output = gr.Textbox(
|
| 225 |
+
label="Test Results",
|
| 226 |
+
lines=8,
|
| 227 |
+
interactive=False
|
| 228 |
+
)
|
| 229 |
+
|
| 230 |
+
type_check_btn.click(type_check, outputs=type_check_output)
|
| 231 |
+
format_btn.click(format_code, outputs=format_output)
|
| 232 |
+
test_btn.click(run_tests, outputs=test_output)
|
| 233 |
+
|
| 234 |
+
# Tab 4: Production
|
| 235 |
+
with gr.Tab("📦 Production"):
|
| 236 |
+
gr.Markdown("""
|
| 237 |
+
## Production Deployment
|
| 238 |
+
|
| 239 |
+
Build your application for production:
|
| 240 |
+
1. Click "Build for Production" to optimize the code
|
| 241 |
+
2. Once complete, run `pnpm start` to launch the production server
|
| 242 |
+
3. Your app will be available at the server URL
|
| 243 |
+
""")
|
| 244 |
+
|
| 245 |
+
build_btn = gr.Button("🏗️ Build for Production", size="lg", variant="stop")
|
| 246 |
+
build_output = gr.Textbox(
|
| 247 |
+
label="Build Output",
|
| 248 |
+
lines=15,
|
| 249 |
+
interactive=False,
|
| 250 |
+
placeholder="Click 'Build for Production' to start..."
|
| 251 |
+
)
|
| 252 |
+
|
| 253 |
+
build_btn.click(build_production, outputs=build_output)
|
| 254 |
+
|
| 255 |
+
gr.Markdown("""
|
| 256 |
+
---
|
| 257 |
+
## 💡 Tips
|
| 258 |
+
- This interface provides a web UI for managing the DR-Image-Magic project
|
| 259 |
+
- Most operations require pnpm and Node.js to be installed
|
| 260 |
+
- For full development, clone the repository locally
|
| 261 |
+
- Check the GitHub repo: [DR-Studios/DR-Image-Magic](https://github.com/DR-Studios/DR-Image-Magic)
|
| 262 |
+
""")
|
| 263 |
+
|
| 264 |
+
if __name__ == "__main__":
|
| 265 |
+
demo.launch(share=False, theme=gr.themes.Soft(primary_hue="orange"))
|
package.json
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "artistic-photo-transform",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"type": "module",
|
| 5 |
+
"license": "MIT",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "NODE_ENV=development tsx watch server/_core/index.ts",
|
| 8 |
+
"build": "vite build && esbuild server/_core/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist",
|
| 9 |
+
"start": "NODE_ENV=production node dist/index.js",
|
| 10 |
+
"check": "tsc --noEmit",
|
| 11 |
+
"format": "prettier --write .",
|
| 12 |
+
"test": "vitest run",
|
| 13 |
+
"db:push": "drizzle-kit generate && drizzle-kit migrate"
|
| 14 |
+
},
|
| 15 |
+
"dependencies": {
|
| 16 |
+
"@aws-sdk/client-s3": "^3.693.0",
|
| 17 |
+
"@aws-sdk/s3-request-presigner": "^3.693.0",
|
| 18 |
+
"@hookform/resolvers": "^5.2.2",
|
| 19 |
+
"@radix-ui/react-accordion": "^1.2.12",
|
| 20 |
+
"@radix-ui/react-alert-dialog": "^1.1.15",
|
| 21 |
+
"@radix-ui/react-aspect-ratio": "^1.1.7",
|
| 22 |
+
"@radix-ui/react-avatar": "^1.1.10",
|
| 23 |
+
"@radix-ui/react-checkbox": "^1.3.3",
|
| 24 |
+
"@radix-ui/react-collapsible": "^1.1.12",
|
| 25 |
+
"@radix-ui/react-context-menu": "^2.2.16",
|
| 26 |
+
"@radix-ui/react-dialog": "^1.1.15",
|
| 27 |
+
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
| 28 |
+
"@radix-ui/react-hover-card": "^1.1.15",
|
| 29 |
+
"@radix-ui/react-label": "^2.1.7",
|
| 30 |
+
"@radix-ui/react-menubar": "^1.1.16",
|
| 31 |
+
"@radix-ui/react-navigation-menu": "^1.2.14",
|
| 32 |
+
"@radix-ui/react-popover": "^1.1.15",
|
| 33 |
+
"@radix-ui/react-progress": "^1.1.7",
|
| 34 |
+
"@radix-ui/react-radio-group": "^1.3.8",
|
| 35 |
+
"@radix-ui/react-scroll-area": "^1.2.10",
|
| 36 |
+
"@radix-ui/react-select": "^2.2.6",
|
| 37 |
+
"@radix-ui/react-separator": "^1.1.7",
|
| 38 |
+
"@radix-ui/react-slider": "^1.3.6",
|
| 39 |
+
"@radix-ui/react-slot": "^1.2.3",
|
| 40 |
+
"@radix-ui/react-switch": "^1.2.6",
|
| 41 |
+
"@radix-ui/react-tabs": "^1.1.13",
|
| 42 |
+
"@radix-ui/react-toggle": "^1.1.10",
|
| 43 |
+
"@radix-ui/react-toggle-group": "^1.1.11",
|
| 44 |
+
"@radix-ui/react-tooltip": "^1.2.8",
|
| 45 |
+
"@tanstack/react-query": "^5.90.2",
|
| 46 |
+
"@trpc/client": "^11.6.0",
|
| 47 |
+
"@trpc/react-query": "^11.6.0",
|
| 48 |
+
"@trpc/server": "^11.6.0",
|
| 49 |
+
"axios": "^1.12.0",
|
| 50 |
+
"class-variance-authority": "^0.7.1",
|
| 51 |
+
"clsx": "^2.1.1",
|
| 52 |
+
"cmdk": "^1.1.1",
|
| 53 |
+
"cookie": "^1.0.2",
|
| 54 |
+
"date-fns": "^4.1.0",
|
| 55 |
+
"dotenv": "^17.2.2",
|
| 56 |
+
"drizzle-orm": "^0.44.5",
|
| 57 |
+
"embla-carousel-react": "^8.6.0",
|
| 58 |
+
"express": "^4.21.2",
|
| 59 |
+
"framer-motion": "^12.23.22",
|
| 60 |
+
"input-otp": "^1.4.2",
|
| 61 |
+
"jose": "6.1.0",
|
| 62 |
+
"lucide-react": "^0.453.0",
|
| 63 |
+
"mysql2": "^3.15.0",
|
| 64 |
+
"nanoid": "^5.1.5",
|
| 65 |
+
"next-themes": "^0.4.6",
|
| 66 |
+
"react": "^19.2.1",
|
| 67 |
+
"react-day-picker": "^9.11.1",
|
| 68 |
+
"react-dom": "^19.2.1",
|
| 69 |
+
"react-hook-form": "^7.64.0",
|
| 70 |
+
"react-resizable-panels": "^3.0.6",
|
| 71 |
+
"recharts": "^2.15.2",
|
| 72 |
+
"sonner": "^2.0.7",
|
| 73 |
+
"streamdown": "^1.4.0",
|
| 74 |
+
"superjson": "^1.13.3",
|
| 75 |
+
"tailwind-merge": "^3.3.1",
|
| 76 |
+
"tailwindcss-animate": "^1.0.7",
|
| 77 |
+
"vaul": "^1.1.2",
|
| 78 |
+
"wouter": "^3.3.5",
|
| 79 |
+
"zod": "^4.1.12"
|
| 80 |
+
},
|
| 81 |
+
"devDependencies": {
|
| 82 |
+
"@builder.io/vite-plugin-jsx-loc": "^0.1.1",
|
| 83 |
+
"@tailwindcss/typography": "^0.5.15",
|
| 84 |
+
"@tailwindcss/vite": "^4.1.3",
|
| 85 |
+
"@types/express": "4.17.21",
|
| 86 |
+
"@types/google.maps": "^3.58.1",
|
| 87 |
+
"@types/node": "^24.7.0",
|
| 88 |
+
"@types/react": "^19.2.1",
|
| 89 |
+
"@types/react-dom": "^19.2.1",
|
| 90 |
+
"@vitejs/plugin-react": "^5.0.4",
|
| 91 |
+
"add": "^2.0.6",
|
| 92 |
+
"autoprefixer": "^10.4.20",
|
| 93 |
+
"drizzle-kit": "^0.31.4",
|
| 94 |
+
"esbuild": "^0.25.0",
|
| 95 |
+
"pnpm": "^10.15.1",
|
| 96 |
+
"postcss": "^8.4.47",
|
| 97 |
+
"prettier": "^3.6.2",
|
| 98 |
+
"tailwindcss": "^4.1.14",
|
| 99 |
+
"tsx": "^4.19.1",
|
| 100 |
+
"tw-animate-css": "^1.4.0",
|
| 101 |
+
"typescript": "5.9.3",
|
| 102 |
+
"vite": "^7.1.7",
|
| 103 |
+
"vite-plugin-manus-runtime": "^0.0.57",
|
| 104 |
+
"vitest": "^2.1.4"
|
| 105 |
+
},
|
| 106 |
+
"packageManager": "pnpm@10.4.1+sha512.c753b6c3ad7afa13af388fa6d808035a008e30ea9993f58c6663e2bc5ff21679aa834db094987129aa4d488b86df57f7b634981b2f827cdcacc698cc0cfb88af",
|
| 107 |
+
"pnpm": {
|
| 108 |
+
"patchedDependencies": {
|
| 109 |
+
"wouter@3.7.1": "patches/wouter@3.7.1.patch"
|
| 110 |
+
},
|
| 111 |
+
"overrides": {
|
| 112 |
+
"tailwindcss>nanoid": "3.3.7"
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
}
|
simple_image_magic_gui.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
DR-Image-Magic - Simple GUI Version
|
| 4 |
+
Quick image transformations without database setup
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import tkinter as tk
|
| 8 |
+
from tkinter import filedialog, messagebox, ttk
|
| 9 |
+
from PIL import Image, ImageTk, ImageEnhance, ImageFilter
|
| 10 |
+
import os
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
class ImageMagicGUI:
|
| 14 |
+
def __init__(self, root):
|
| 15 |
+
self.root = root
|
| 16 |
+
self.root.title("DR-Image-Magic - Simple GUI")
|
| 17 |
+
self.root.geometry("1000x850") # Bigger window
|
| 18 |
+
self.root.configure(bg='#1a1a2e')
|
| 19 |
+
|
| 20 |
+
self.current_image = None
|
| 21 |
+
self.original_image = None
|
| 22 |
+
self.image_path = None
|
| 23 |
+
|
| 24 |
+
self.setup_ui()
|
| 25 |
+
|
| 26 |
+
def setup_ui(self):
|
| 27 |
+
# Title
|
| 28 |
+
title = tk.Label(
|
| 29 |
+
self.root,
|
| 30 |
+
text="🎨 DR-Image-Magic",
|
| 31 |
+
font=("Arial", 24, "bold"),
|
| 32 |
+
bg='#1a1a2e',
|
| 33 |
+
fg='#ff6b35'
|
| 34 |
+
)
|
| 35 |
+
title.pack(pady=20)
|
| 36 |
+
|
| 37 |
+
# Upload Button
|
| 38 |
+
upload_btn = tk.Button(
|
| 39 |
+
self.root,
|
| 40 |
+
text="📁 Upload Image",
|
| 41 |
+
command=self.upload_image,
|
| 42 |
+
font=("Arial", 14),
|
| 43 |
+
bg='#ff6b35',
|
| 44 |
+
fg='white',
|
| 45 |
+
padx=20,
|
| 46 |
+
pady=10,
|
| 47 |
+
cursor='hand2'
|
| 48 |
+
)
|
| 49 |
+
upload_btn.pack(pady=10)
|
| 50 |
+
|
| 51 |
+
# Image Preview
|
| 52 |
+
self.image_label = tk.Label(
|
| 53 |
+
self.root,
|
| 54 |
+
bg='#16213e',
|
| 55 |
+
text="No image loaded",
|
| 56 |
+
fg='#aaa',
|
| 57 |
+
font=("Arial", 12)
|
| 58 |
+
)
|
| 59 |
+
self.image_label.pack(pady=20, padx=20, fill=tk.BOTH, expand=True)
|
| 60 |
+
|
| 61 |
+
# Effects Frame
|
| 62 |
+
effects_frame = tk.Frame(self.root, bg='#1a1a2e')
|
| 63 |
+
effects_frame.pack(pady=10)
|
| 64 |
+
|
| 65 |
+
tk.Label(
|
| 66 |
+
effects_frame,
|
| 67 |
+
text="🎨 Effects:",
|
| 68 |
+
font=("Arial", 14, "bold"),
|
| 69 |
+
bg='#1a1a2e',
|
| 70 |
+
fg='#ff6b35'
|
| 71 |
+
).grid(row=0, column=0, columnspan=4, pady=10)
|
| 72 |
+
|
| 73 |
+
# Effect Buttons
|
| 74 |
+
effects = [
|
| 75 |
+
("✨ Enhance", self.enhance_image),
|
| 76 |
+
("🌈 Vibrant", self.vibrant_image),
|
| 77 |
+
("🌙 Dark & Moody", self.dark_moody),
|
| 78 |
+
("☀️ Bright & Warm", self.bright_warm),
|
| 79 |
+
("🎭 High Contrast", self.high_contrast),
|
| 80 |
+
("🌊 Cool Tones", self.cool_tones),
|
| 81 |
+
("🔥 Warm Tones", self.warm_tones),
|
| 82 |
+
("📸 Vintage", self.vintage_effect),
|
| 83 |
+
("💎 Sharpen", self.sharpen_image),
|
| 84 |
+
("🌫️ Blur", self.blur_image),
|
| 85 |
+
("🎨 Abstract", self.abstract_effect),
|
| 86 |
+
("⚫ Grayscale", self.grayscale_image),
|
| 87 |
+
]
|
| 88 |
+
|
| 89 |
+
row = 1
|
| 90 |
+
col = 0
|
| 91 |
+
for text, command in effects:
|
| 92 |
+
btn = tk.Button(
|
| 93 |
+
effects_frame,
|
| 94 |
+
text=text,
|
| 95 |
+
command=command,
|
| 96 |
+
font=("Arial", 10),
|
| 97 |
+
bg='#0f3460',
|
| 98 |
+
fg='white',
|
| 99 |
+
padx=10,
|
| 100 |
+
pady=5,
|
| 101 |
+
cursor='hand2'
|
| 102 |
+
)
|
| 103 |
+
btn.grid(row=row, column=col, padx=5, pady=5, sticky='ew')
|
| 104 |
+
col += 1
|
| 105 |
+
if col > 3:
|
| 106 |
+
col = 0
|
| 107 |
+
row += 1
|
| 108 |
+
|
| 109 |
+
# Action Buttons
|
| 110 |
+
action_frame = tk.Frame(self.root, bg='#1a1a2e')
|
| 111 |
+
action_frame.pack(pady=20)
|
| 112 |
+
|
| 113 |
+
reset_btn = tk.Button(
|
| 114 |
+
action_frame,
|
| 115 |
+
text="🔄 Reset",
|
| 116 |
+
command=self.reset_image,
|
| 117 |
+
font=("Arial", 12),
|
| 118 |
+
bg='#e94560',
|
| 119 |
+
fg='white',
|
| 120 |
+
padx=15,
|
| 121 |
+
pady=8
|
| 122 |
+
)
|
| 123 |
+
reset_btn.pack(side=tk.LEFT, padx=10)
|
| 124 |
+
|
| 125 |
+
save_btn = tk.Button(
|
| 126 |
+
action_frame,
|
| 127 |
+
text="💾 Save As...",
|
| 128 |
+
command=self.save_image,
|
| 129 |
+
font=("Arial", 12),
|
| 130 |
+
bg='#16a34a',
|
| 131 |
+
fg='white',
|
| 132 |
+
padx=15,
|
| 133 |
+
pady=8
|
| 134 |
+
)
|
| 135 |
+
save_btn.pack(side=tk.LEFT, padx=10)
|
| 136 |
+
|
| 137 |
+
def upload_image(self):
|
| 138 |
+
file_path = filedialog.askopenfilename(
|
| 139 |
+
title="Select an Image",
|
| 140 |
+
filetypes=[
|
| 141 |
+
("Image Files", "*.png *.jpg *.jpeg *.webp *.bmp"),
|
| 142 |
+
("All Files", "*.*")
|
| 143 |
+
]
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
if file_path:
|
| 147 |
+
try:
|
| 148 |
+
self.image_path = file_path
|
| 149 |
+
self.original_image = Image.open(file_path)
|
| 150 |
+
self.current_image = self.original_image.copy()
|
| 151 |
+
self.display_image(self.current_image)
|
| 152 |
+
except Exception as e:
|
| 153 |
+
messagebox.showerror("Error", f"Failed to load image: {e}")
|
| 154 |
+
|
| 155 |
+
def display_image(self, image):
|
| 156 |
+
# Resize for display (max 500x300 to save space for buttons)
|
| 157 |
+
display_img = image.copy()
|
| 158 |
+
display_img.thumbnail((500, 300), Image.Resampling.LANCZOS)
|
| 159 |
+
|
| 160 |
+
photo = ImageTk.PhotoImage(display_img)
|
| 161 |
+
self.image_label.configure(image=photo, text="")
|
| 162 |
+
self.image_label.image = photo
|
| 163 |
+
|
| 164 |
+
def check_image_loaded(self):
|
| 165 |
+
if self.current_image is None:
|
| 166 |
+
messagebox.showwarning("No Image", "Please upload an image first!")
|
| 167 |
+
return False
|
| 168 |
+
return True
|
| 169 |
+
|
| 170 |
+
# Effect Methods
|
| 171 |
+
def enhance_image(self):
|
| 172 |
+
if not self.check_image_loaded(): return
|
| 173 |
+
enhancer = ImageEnhance.Contrast(self.current_image)
|
| 174 |
+
self.current_image = enhancer.enhance(1.3)
|
| 175 |
+
enhancer = ImageEnhance.Sharpness(self.current_image)
|
| 176 |
+
self.current_image = enhancer.enhance(1.2)
|
| 177 |
+
self.display_image(self.current_image)
|
| 178 |
+
|
| 179 |
+
def vibrant_image(self):
|
| 180 |
+
if not self.check_image_loaded(): return
|
| 181 |
+
enhancer = ImageEnhance.Color(self.current_image)
|
| 182 |
+
self.current_image = enhancer.enhance(1.5)
|
| 183 |
+
self.display_image(self.current_image)
|
| 184 |
+
|
| 185 |
+
def dark_moody(self):
|
| 186 |
+
if not self.check_image_loaded(): return
|
| 187 |
+
enhancer = ImageEnhance.Brightness(self.current_image)
|
| 188 |
+
self.current_image = enhancer.enhance(0.7)
|
| 189 |
+
enhancer = ImageEnhance.Contrast(self.current_image)
|
| 190 |
+
self.current_image = enhancer.enhance(1.4)
|
| 191 |
+
self.display_image(self.current_image)
|
| 192 |
+
|
| 193 |
+
def bright_warm(self):
|
| 194 |
+
if not self.check_image_loaded(): return
|
| 195 |
+
enhancer = ImageEnhance.Brightness(self.current_image)
|
| 196 |
+
self.current_image = enhancer.enhance(1.2)
|
| 197 |
+
enhancer = ImageEnhance.Color(self.current_image)
|
| 198 |
+
self.current_image = enhancer.enhance(1.3)
|
| 199 |
+
self.display_image(self.current_image)
|
| 200 |
+
|
| 201 |
+
def high_contrast(self):
|
| 202 |
+
if not self.check_image_loaded(): return
|
| 203 |
+
enhancer = ImageEnhance.Contrast(self.current_image)
|
| 204 |
+
self.current_image = enhancer.enhance(2.0)
|
| 205 |
+
self.display_image(self.current_image)
|
| 206 |
+
|
| 207 |
+
def cool_tones(self):
|
| 208 |
+
if not self.check_image_loaded(): return
|
| 209 |
+
# Convert to RGB if needed
|
| 210 |
+
if self.current_image.mode != 'RGB':
|
| 211 |
+
self.current_image = self.current_image.convert('RGB')
|
| 212 |
+
# Apply blue tint
|
| 213 |
+
r, g, b = self.current_image.split()
|
| 214 |
+
r = r.point(lambda i: i * 0.9)
|
| 215 |
+
g = g.point(lambda i: i * 0.95)
|
| 216 |
+
b = b.point(lambda i: i * 1.1)
|
| 217 |
+
self.current_image = Image.merge('RGB', (r, g, b))
|
| 218 |
+
self.display_image(self.current_image)
|
| 219 |
+
|
| 220 |
+
def warm_tones(self):
|
| 221 |
+
if not self.check_image_loaded(): return
|
| 222 |
+
# Convert to RGB if needed
|
| 223 |
+
if self.current_image.mode != 'RGB':
|
| 224 |
+
self.current_image = self.current_image.convert('RGB')
|
| 225 |
+
# Apply orange tint
|
| 226 |
+
r, g, b = self.current_image.split()
|
| 227 |
+
r = r.point(lambda i: min(255, i * 1.1))
|
| 228 |
+
g = g.point(lambda i: i * 1.05)
|
| 229 |
+
b = b.point(lambda i: i * 0.9)
|
| 230 |
+
self.current_image = Image.merge('RGB', (r, g, b))
|
| 231 |
+
self.display_image(self.current_image)
|
| 232 |
+
|
| 233 |
+
def vintage_effect(self):
|
| 234 |
+
if not self.check_image_loaded(): return
|
| 235 |
+
# Sepia effect
|
| 236 |
+
self.current_image = self.current_image.convert('L')
|
| 237 |
+
self.current_image = self.current_image.convert('RGB')
|
| 238 |
+
r, g, b = self.current_image.split()
|
| 239 |
+
r = r.point(lambda i: min(255, i * 1.2))
|
| 240 |
+
g = g.point(lambda i: i * 1.0)
|
| 241 |
+
b = b.point(lambda i: i * 0.8)
|
| 242 |
+
self.current_image = Image.merge('RGB', (r, g, b))
|
| 243 |
+
enhancer = ImageEnhance.Contrast(self.current_image)
|
| 244 |
+
self.current_image = enhancer.enhance(0.9)
|
| 245 |
+
self.display_image(self.current_image)
|
| 246 |
+
|
| 247 |
+
def sharpen_image(self):
|
| 248 |
+
if not self.check_image_loaded(): return
|
| 249 |
+
self.current_image = self.current_image.filter(ImageFilter.SHARPEN)
|
| 250 |
+
self.display_image(self.current_image)
|
| 251 |
+
|
| 252 |
+
def blur_image(self):
|
| 253 |
+
if not self.check_image_loaded(): return
|
| 254 |
+
self.current_image = self.current_image.filter(ImageFilter.GaussianBlur(radius=2))
|
| 255 |
+
self.display_image(self.current_image)
|
| 256 |
+
|
| 257 |
+
def abstract_effect(self):
|
| 258 |
+
if not self.check_image_loaded(): return
|
| 259 |
+
self.current_image = self.current_image.filter(ImageFilter.EDGE_ENHANCE_MORE)
|
| 260 |
+
enhancer = ImageEnhance.Color(self.current_image)
|
| 261 |
+
self.current_image = enhancer.enhance(1.8)
|
| 262 |
+
self.display_image(self.current_image)
|
| 263 |
+
|
| 264 |
+
def grayscale_image(self):
|
| 265 |
+
if not self.check_image_loaded(): return
|
| 266 |
+
self.current_image = self.current_image.convert('L').convert('RGB')
|
| 267 |
+
self.display_image(self.current_image)
|
| 268 |
+
|
| 269 |
+
def reset_image(self):
|
| 270 |
+
if not self.check_image_loaded(): return
|
| 271 |
+
self.current_image = self.original_image.copy()
|
| 272 |
+
self.display_image(self.current_image)
|
| 273 |
+
|
| 274 |
+
def save_image(self):
|
| 275 |
+
if not self.check_image_loaded(): return
|
| 276 |
+
|
| 277 |
+
file_path = filedialog.asksaveasfilename(
|
| 278 |
+
defaultextension=".png",
|
| 279 |
+
filetypes=[
|
| 280 |
+
("PNG", "*.png"),
|
| 281 |
+
("JPEG", "*.jpg"),
|
| 282 |
+
("WebP", "*.webp"),
|
| 283 |
+
("All Files", "*.*")
|
| 284 |
+
]
|
| 285 |
+
)
|
| 286 |
+
|
| 287 |
+
if file_path:
|
| 288 |
+
try:
|
| 289 |
+
self.current_image.save(file_path)
|
| 290 |
+
messagebox.showinfo("Success", f"Image saved to:\n{file_path}")
|
| 291 |
+
except Exception as e:
|
| 292 |
+
messagebox.showerror("Error", f"Failed to save image: {e}")
|
| 293 |
+
|
| 294 |
+
if __name__ == "__main__":
|
| 295 |
+
root = tk.Tk()
|
| 296 |
+
app = ImageMagicGUI(root)
|
| 297 |
+
root.mainloop()
|
todo.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Artistic Photo Transform - Project TODO
|
| 2 |
+
|
| 3 |
+
## Core Features
|
| 4 |
+
|
| 5 |
+
- [x] Database schema for images and transformations
|
| 6 |
+
- [x] Photo upload API with file validation (max 16MB, jpg/png/webp)
|
| 7 |
+
- [x] Drag-and-drop upload interface
|
| 8 |
+
- [x] AI-powered image transformation endpoint (3 variations)
|
| 9 |
+
- [x] S3 file storage integration for original and transformed images
|
| 10 |
+
- [x] Transformation history database queries
|
| 11 |
+
- [x] Custom text prompt support for guided transformations
|
| 12 |
+
- [x] Gallery view with original + 3 transformed variations
|
| 13 |
+
- [x] Image download functionality
|
| 14 |
+
- [x] Loading states with progress indication (5-20 seconds)
|
| 15 |
+
- [x] Error handling with retry option
|
| 16 |
+
- [x] User authentication and history tracking
|
| 17 |
+
|
| 18 |
+
## UI/Design
|
| 19 |
+
|
| 20 |
+
- [x] Implement chiaroscuro aesthetic (deep black background, golden light)
|
| 21 |
+
- [x] Bold uppercase sans-serif typography with gradient
|
| 22 |
+
- [x] Atmospheric elements (light rays, lens flares)
|
| 23 |
+
- [x] Responsive layout for desktop and mobile
|
| 24 |
+
- [x] Loading skeleton states
|
| 25 |
+
- [x] Empty state messaging
|
| 26 |
+
|
| 27 |
+
## Testing & Deployment
|
| 28 |
+
|
| 29 |
+
- [x] Unit tests for transformation logic
|
| 30 |
+
- [x] Integration tests for file upload
|
| 31 |
+
- [x] All tests passing (7/7)
|
| 32 |
+
- [ ] Generate 3 example transformations
|
| 33 |
+
- [ ] Final checkpoint and deployment
|
| 34 |
+
|
| 35 |
+
## Completed Features
|
| 36 |
+
|
| 37 |
+
- Backend image transformation service with AI integration
|
| 38 |
+
- Database schema with images and transformations tables
|
| 39 |
+
- Upload handler with file validation and S3 storage
|
| 40 |
+
- Transformation queue for async processing
|
| 41 |
+
- Frontend Transform page with drag-and-drop upload
|
| 42 |
+
- Status polling for real-time transformation updates
|
| 43 |
+
- Gallery display with download functionality
|
| 44 |
+
- Comprehensive test suite
|
vite.config.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { jsxLocPlugin } from "@builder.io/vite-plugin-jsx-loc";
|
| 2 |
+
import tailwindcss from "@tailwindcss/vite";
|
| 3 |
+
import react from "@vitejs/plugin-react";
|
| 4 |
+
import fs from "node:fs";
|
| 5 |
+
import path from "node:path";
|
| 6 |
+
import { defineConfig, type Plugin, type ViteDevServer } from "vite";
|
| 7 |
+
import { vitePluginManusRuntime } from "vite-plugin-manus-runtime";
|
| 8 |
+
|
| 9 |
+
// =============================================================================
|
| 10 |
+
// Manus Debug Collector - Vite Plugin
|
| 11 |
+
// Writes browser logs directly to files, trimmed when exceeding size limit
|
| 12 |
+
// =============================================================================
|
| 13 |
+
|
| 14 |
+
const PROJECT_ROOT = import.meta.dirname;
|
| 15 |
+
const LOG_DIR = path.join(PROJECT_ROOT, ".manus-logs");
|
| 16 |
+
const MAX_LOG_SIZE_BYTES = 1 * 1024 * 1024; // 1MB per log file
|
| 17 |
+
const TRIM_TARGET_BYTES = Math.floor(MAX_LOG_SIZE_BYTES * 0.6); // Trim to 60% to avoid constant re-trimming
|
| 18 |
+
|
| 19 |
+
type LogSource = "browserConsole" | "networkRequests" | "sessionReplay";
|
| 20 |
+
|
| 21 |
+
function ensureLogDir() {
|
| 22 |
+
if (!fs.existsSync(LOG_DIR)) {
|
| 23 |
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
function trimLogFile(logPath: string, maxSize: number) {
|
| 28 |
+
try {
|
| 29 |
+
if (!fs.existsSync(logPath) || fs.statSync(logPath).size <= maxSize) {
|
| 30 |
+
return;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
const lines = fs.readFileSync(logPath, "utf-8").split("\n");
|
| 34 |
+
const keptLines: string[] = [];
|
| 35 |
+
let keptBytes = 0;
|
| 36 |
+
|
| 37 |
+
// Keep newest lines (from end) that fit within 60% of maxSize
|
| 38 |
+
const targetSize = TRIM_TARGET_BYTES;
|
| 39 |
+
for (let i = lines.length - 1; i >= 0; i--) {
|
| 40 |
+
const lineBytes = Buffer.byteLength(`${lines[i]}\n`, "utf-8");
|
| 41 |
+
if (keptBytes + lineBytes > targetSize) break;
|
| 42 |
+
keptLines.unshift(lines[i]);
|
| 43 |
+
keptBytes += lineBytes;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
fs.writeFileSync(logPath, keptLines.join("\n"), "utf-8");
|
| 47 |
+
} catch {
|
| 48 |
+
/* ignore trim errors */
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
function writeToLogFile(source: LogSource, entries: unknown[]) {
|
| 53 |
+
if (entries.length === 0) return;
|
| 54 |
+
|
| 55 |
+
ensureLogDir();
|
| 56 |
+
const logPath = path.join(LOG_DIR, `${source}.log`);
|
| 57 |
+
|
| 58 |
+
// Format entries with timestamps
|
| 59 |
+
const lines = entries.map(entry => {
|
| 60 |
+
const ts = new Date().toISOString();
|
| 61 |
+
return `[${ts}] ${JSON.stringify(entry)}`;
|
| 62 |
+
});
|
| 63 |
+
|
| 64 |
+
// Append to log file
|
| 65 |
+
fs.appendFileSync(logPath, `${lines.join("\n")}\n`, "utf-8");
|
| 66 |
+
|
| 67 |
+
// Trim if exceeds max size
|
| 68 |
+
trimLogFile(logPath, MAX_LOG_SIZE_BYTES);
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
/**
|
| 72 |
+
* Vite plugin to collect browser debug logs
|
| 73 |
+
* - POST /__manus__/logs: Browser sends logs, written directly to files
|
| 74 |
+
* - Files: browserConsole.log, networkRequests.log, sessionReplay.log
|
| 75 |
+
* - Auto-trimmed when exceeding 1MB (keeps newest entries)
|
| 76 |
+
*/
|
| 77 |
+
function vitePluginManusDebugCollector(): Plugin {
|
| 78 |
+
return {
|
| 79 |
+
name: "manus-debug-collector",
|
| 80 |
+
|
| 81 |
+
transformIndexHtml(html) {
|
| 82 |
+
if (process.env.NODE_ENV === "production") {
|
| 83 |
+
return html;
|
| 84 |
+
}
|
| 85 |
+
return {
|
| 86 |
+
html,
|
| 87 |
+
tags: [
|
| 88 |
+
{
|
| 89 |
+
tag: "script",
|
| 90 |
+
attrs: {
|
| 91 |
+
src: "/__manus__/debug-collector.js",
|
| 92 |
+
defer: true,
|
| 93 |
+
},
|
| 94 |
+
injectTo: "head",
|
| 95 |
+
},
|
| 96 |
+
],
|
| 97 |
+
};
|
| 98 |
+
},
|
| 99 |
+
|
| 100 |
+
configureServer(server: ViteDevServer) {
|
| 101 |
+
// POST /__manus__/logs: Browser sends logs (written directly to files)
|
| 102 |
+
server.middlewares.use("/__manus__/logs", (req, res, next) => {
|
| 103 |
+
if (req.method !== "POST") {
|
| 104 |
+
return next();
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
const handlePayload = (payload: any) => {
|
| 108 |
+
// Write logs directly to files
|
| 109 |
+
if (payload.consoleLogs?.length > 0) {
|
| 110 |
+
writeToLogFile("browserConsole", payload.consoleLogs);
|
| 111 |
+
}
|
| 112 |
+
if (payload.networkRequests?.length > 0) {
|
| 113 |
+
writeToLogFile("networkRequests", payload.networkRequests);
|
| 114 |
+
}
|
| 115 |
+
if (payload.sessionEvents?.length > 0) {
|
| 116 |
+
writeToLogFile("sessionReplay", payload.sessionEvents);
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
res.writeHead(200, { "Content-Type": "application/json" });
|
| 120 |
+
res.end(JSON.stringify({ success: true }));
|
| 121 |
+
};
|
| 122 |
+
|
| 123 |
+
const reqBody = (req as { body?: unknown }).body;
|
| 124 |
+
if (reqBody && typeof reqBody === "object") {
|
| 125 |
+
try {
|
| 126 |
+
handlePayload(reqBody);
|
| 127 |
+
} catch (e) {
|
| 128 |
+
res.writeHead(400, { "Content-Type": "application/json" });
|
| 129 |
+
res.end(JSON.stringify({ success: false, error: String(e) }));
|
| 130 |
+
}
|
| 131 |
+
return;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
let body = "";
|
| 135 |
+
req.on("data", chunk => {
|
| 136 |
+
body += chunk.toString();
|
| 137 |
+
});
|
| 138 |
+
|
| 139 |
+
req.on("end", () => {
|
| 140 |
+
try {
|
| 141 |
+
const payload = JSON.parse(body);
|
| 142 |
+
handlePayload(payload);
|
| 143 |
+
} catch (e) {
|
| 144 |
+
res.writeHead(400, { "Content-Type": "application/json" });
|
| 145 |
+
res.end(JSON.stringify({ success: false, error: String(e) }));
|
| 146 |
+
}
|
| 147 |
+
});
|
| 148 |
+
});
|
| 149 |
+
},
|
| 150 |
+
};
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
const plugins = [
|
| 154 |
+
react(),
|
| 155 |
+
tailwindcss(),
|
| 156 |
+
jsxLocPlugin(),
|
| 157 |
+
vitePluginManusRuntime(),
|
| 158 |
+
vitePluginManusDebugCollector(),
|
| 159 |
+
];
|
| 160 |
+
|
| 161 |
+
export default defineConfig({
|
| 162 |
+
plugins,
|
| 163 |
+
resolve: {
|
| 164 |
+
alias: {
|
| 165 |
+
"@": path.resolve(import.meta.dirname, "client", "src"),
|
| 166 |
+
"@shared": path.resolve(import.meta.dirname, "shared"),
|
| 167 |
+
"@assets": path.resolve(import.meta.dirname, "attached_assets"),
|
| 168 |
+
},
|
| 169 |
+
},
|
| 170 |
+
envDir: path.resolve(import.meta.dirname),
|
| 171 |
+
root: path.resolve(import.meta.dirname, "client"),
|
| 172 |
+
publicDir: path.resolve(import.meta.dirname, "client", "public"),
|
| 173 |
+
build: {
|
| 174 |
+
outDir: path.resolve(import.meta.dirname, "dist/public"),
|
| 175 |
+
emptyOutDir: true,
|
| 176 |
+
},
|
| 177 |
+
server: {
|
| 178 |
+
host: true,
|
| 179 |
+
allowedHosts: [
|
| 180 |
+
".manuspre.computer",
|
| 181 |
+
".manus.computer",
|
| 182 |
+
".manus-asia.computer",
|
| 183 |
+
".manuscomputer.ai",
|
| 184 |
+
".manusvm.computer",
|
| 185 |
+
"localhost",
|
| 186 |
+
"127.0.0.1",
|
| 187 |
+
],
|
| 188 |
+
fs: {
|
| 189 |
+
strict: true,
|
| 190 |
+
deny: ["**/.*"],
|
| 191 |
+
},
|
| 192 |
+
},
|
| 193 |
+
});
|