diff --git a/README.md b/README.md index f40438b..56f2631 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,179 @@ -# Legacy Stable Diffusion WebUI - +# Legacy Stable Diffusion WebUI β€” Summer Update β˜€οΈ ![phpsd-logo-256](https://github.com/user-attachments/assets/9ca5934f-97e2-4885-bf06-a93dcfc393a6) +A minimalistic, **JavaScript-free** web interface for Stable Diffusion, designed for compatibility with **legacy web browsers** like Internet Explorer 4, Netscape 4.x, and even Classilla on Mac OS 9. +Built for long-term access to AI image generation using the AUTOMATIC1111 API β€” from modern GPUs to vintage clients. -A minimalistic, JavaScript-free web interface, front-end and processing server for Stable Diffusion, designed specifically for being accessed by legacy systems and browsers as clients. This interface relies on the API from AUTOMATIC1111's Stable Diffusion WebUI. +--- -## Features -- No JavaScript dependencies -- Lightweight and fast loading -- Support for LORA models using square bracket syntax -- Image optimization with automatic PNG to JPG conversion -- Prompt saving and loading functionality -- Full configurability of key generation parameters: - - Image dimensions - - Sampling steps - - CFG Scale - - Choice of sampling methods - - Negative prompt support (1-19-2025 Update) - - Optimized default parameters for better quality images by default (1-19-2025 Update) +## πŸ†• Summer 2025 Update Highlights -## Browser Compatibility +- πŸ—‚ **File structure cleanup** – Generation, prompt saving, and UI now fully separated for better maintainability. +- ⏳ **True loading screen** – `loading.php` now displays a loading message before generation starts. +- πŸ’Ύ **"Save Prompt" moved to result page** – Save your prompt right after generation from `result.php`. +- πŸ“‰ **Improved compression** – Lowered ImageMagick quality to 75 (from 85) for **90–93% file size reduction**. +- βœ… **Default sampler set to `Euler a`**, which works especially well with **Illustrious XL** (the recommended model). +- 🧼 Overall code quality and parameter consistency improved. -- `index.php`: Tested working on IE4+, Netscape 4.x and above browsers (HTML 4 compliant) +--- -## Requirements +## πŸ”§ Features -- Linux server with a suitable GPU; running a model in AUTOMATIC1111's Stable Diffusion WebUI https://github.com/AUTOMATIC1111/stable-diffusion-webui with API access (image generation back-end) -- Externally accessible Linux server running PHP 8+ to run the PHP front-end (Tested on frankenphp running on Debian 12, should also run with traditional PHP setups with apache/nginx, can run on the same server as the back-end) -- Web server (Apache, nginx, etc.) -- ImageMagick for image optimization -- Curl -- Client machine to access the front-end via web browser +- No JavaScript β€” 100% HTML 4.01 and PHP +- Ultra-lightweight for slow or vintage clients +- LORA model support via `[name:weight]` syntax +- Prompt saving and loading via `saved-prompts.json` +- Server-side PNG ➜ JPG conversion with ImageMagick +- Compatible with AUTOMATIC1111 Stable Diffusion WebUI +- Configurable generation settings: + - Image size + - CFG scale + - Steps + - Sampler method + - Negative prompt support +--- -## Installation +## 🌐 Browser Compatibility -1. Ensure you have AUTOMATIC1111's Stable Diffusion WebUI running with the `--api` flag -2. Install PHP and ImageMagick on your system -3. Copy both `index.php` to your web server directory -4. Ensure the directory is writable by the web server for saved prompts and generated images -5. Access through your web browser: - - Use `index.php` for HTML 4.01+ compatible browsers +Tested on: -## Usage +- βœ… Internet Explorer 4–6 (Windows 95–2000) +- βœ… Netscape 4.x +- βœ… Classilla (Mac OS 9) +- βœ… Firefox ESR 102+ (modern systems) -1. Enter your prompt and negative prompt if applicable in the text area -2. Configure generation parameters as needed -3. Click "Generate Image" to create your image -4. Save frequently used prompts server-side using the "Save Prompt" feature -5. Load saved prompts from the dropdown menu +--- -## LORA Usage +## πŸ“¦ Requirements -To use LORA models, include them in your prompt using square brackets: -```[lora-name:weight]``` +- **Back-end:** + AUTOMATIC1111 Stable Diffusion WebUI running with `--api` + https://github.com/AUTOMATIC1111/stable-diffusion-webui + +- **Front-end Server:** + Linux server with: + - PHP 8+ + - ImageMagick + - cURL + - Any web server (Apache, nginx, or FrankenPHP) + +- **Client:** + Any browser capable of submitting forms and rendering HTML 4.01 + (Yes, it really works on Windows NT 4.0) + +--- + +## πŸ§ͺ Installation + +1. Start AUTOMATIC1111's WebUI with `--api` +2. Install PHP and ImageMagick on your web server +3. Place `index.php`, `loading.php`, `generate.php`, and `result.php` in your web directory +4. Ensure write permissions for: + - `saved-prompts.json` + - Your web server's root directory containing the phpsd files, this is where images will be stored +5. Access `index.php` from a browser and start generating! + +--- + +## 🧭 Usage Guide + +1. Open `index.php` +2. Type your prompt and negative prompt (optional) +3. Adjust generation parameters +4. Submit ➜ loading screen ➜ image result +5. Save your favorite prompts directly from `result.php` + +--- + +## 🧠 LORA Support + +Use the syntax: + +```text +[lora-name:weight] +```` Example: -```a beautiful landscape by [my-artist-lora:0.8]``` -## Notes +```text +a high-detail wizard portrait [wizard-style:0.8] +``` -- Images are automatically optimized and converted from PNG to JPG for faster loading -- The interface works without any client-side scripting -- All processing is done server-side -- Compatible with browsers from the late 1990s to now +Weight values typically range from `0.1` to `1.0` -## Screenshots +--- -### Running on Windows NT 4.0 With IE 4 -![Screenshot 2024-12-14 142743](https://github.com/user-attachments/assets/838095cb-0af7-4c6e-a140-78e8f7c65691) +## πŸ“Έ Image Processing Notes -### Running on Debian 12 With Firefox ESR 115 -![Screenshot 2024-12-14 143036](https://github.com/user-attachments/assets/9745d00f-57cc-4788-a17d-43782d7e6fa3) +* Generated PNGs are automatically converted to optimized JPGs +* ImageMagick runs with: + + ``` + -quality 75 -strip -interlace Plane -gaussian-blur 0.05 -sampling-factor 4:2:0 + ``` +* Resulting file sizes reduced by \~90–93%, ideal for slow connections and legacy hardware + +--- + + +## 🧭 System Architecture + +Below is a flowchart diagram representing the separation of concerns between the legacy client and the backend server during prompt submission and image generation. + +![Screenshot 2025-06-22 182155](https://github.com/user-attachments/assets/28e9aa66-42d8-4820-bfd9-6b4b23571188) + +--- + +## πŸ–₯️ Screenshots + +### Running on Windows 2000 SP2 with IE 5 β€” Illustrious XL Output (June 22, 2025 Summer Update) +![Gura sample](https://github.com/user-attachments/assets/afa2366b-1760-47da-a7d6-12465b1ba076) + +This image was generated using `Euler a` sampler and the Illustrious XL model, processed entirely through the `phpsd` legacy frontend. Displayed here in Internet Explorer 5 on Windows 2000 SP2. + + +### Running on Mac OS 9 + Classilla -### Running on Mac OS 9 With Classilla ![screenshot2](https://github.com/user-attachments/assets/5284613c-9060-49d4-aed6-5d7fa1d041d3) +### Firefox ESR 115 on Debian 12 -## Contributing +![Screenshot 2024-12-14 143036](https://github.com/user-attachments/assets/9745d00f-57cc-4788-a17d-43782d7e6fa3) -Contributions are welcome! Please feel free to submit a Pull Request or fork. +--- -## License +## πŸ–ΌοΈ Legacy Wallpaper Output -This project is MIT licensed. +Don’t just generate β€” **decorate**. + +All generated images are: +- Optimized for ultra-low size (JPG @ quality 75) +- CRT-safe resolution options +- Perfect for Retro PC wallpapers + +Whether you’re browsing from Classilla or setting your ThinkPad T21’s background, `phpsd` delivers. + + +## 🀝 Contributing + +Pull requests, forks, and retro tweaks welcome! +Feel free to adapt the UI for even older setups or customize layouts per browser profile. + +--- + +## πŸ“œ License + +MIT License + +--- + +## 🧠 Credits + +* Front-end & optimization by markmental +* Powered by [AUTOMATIC1111’s Stable Diffusion WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) + +--- -## Credits -Built to work with AUTOMATIC1111's Stable Diffusion WebUI API: -https://github.com/AUTOMATIC1111/stable-diffusion-webui diff --git a/generate.php b/generate.php new file mode 100644 index 0000000..7de268c --- /dev/null +++ b/generate.php @@ -0,0 +1,101 @@ +') !== false) { + die("Error: Angle brackets are not allowed in prompts."); +} + +// Convert LORA-style square bracket syntax +$processed_prompt = preg_replace('/\[([\w\s\-]+?):([\d\.]+)\]/', '', $prompt); + +// Build request +$data = array( + 'prompt' => $processed_prompt, + 'negative_prompt' => $negative_prompt, + 'steps' => $steps, + 'width' => $width, + 'height' => $height, + 'cfg_scale' => $cfg_scale, + 'sampler_name' => $sampler, + 'override_settings' => new stdClass(), + 'override_settings_restore_afterwards' => true +); + +// Call Stable Diffusion API +$ch = curl_init('http://127.0.0.1:7860/sdapi/v1/txt2img'); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_setopt($ch, CURLOPT_POST, true); +curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); +curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); +$result = curl_exec($ch); +curl_close($ch); + +if (!$result) { + die("Error: Failed to communicate with the image generation API."); +} + +$decoded = json_decode($result, true); +if (!isset($decoded['images'][0])) { + die("Error: No image returned."); +} + +// Decode base64 PNG +$image_data = base64_decode($decoded['images'][0]); + +// Save original PNG temporarily +$temp_png = 'temp_' . time() . '_' . bin2hex(random_bytes(8)) . '.png'; +file_put_contents($temp_png, $image_data); + +// Convert PNG to JPG with ImageMagick +$generated_image = 'generated_' . time() . '_' . bin2hex(random_bytes(8)) . '.jpg'; +$quality = 75; +$cmd = "convert \"$temp_png\" -strip -interlace Plane -gaussian-blur 0.05 -quality $quality -sampling-factor 4:2:0 \"$generated_image\" 2>&1"; +exec($cmd, $output, $returnVar); + +// Get image size info +if ($returnVar !== 0 || !file_exists($generated_image)) { + $generated_image = $temp_png; + $size_info = null; +} else { + $originalSize = filesize($temp_png); + $convertedSize = filesize($generated_image); + unlink($temp_png); + + $size_info = array( + 'original' => round($originalSize / 1024, 2), + 'converted' => round($convertedSize / 1024, 2), + 'reduction' => round((($originalSize - $convertedSize) / $originalSize) * 100, 2) + ); +} + +// Save result to session +$_SESSION['result'] = array( + 'generated_image' => $generated_image, + 'prompt' => $prompt, + 'negative_prompt' => $negative_prompt, + 'processed_prompt' => $processed_prompt, + 'width' => $width, + 'height' => $height, + 'steps' => $steps, + 'cfg_scale' => $cfg_scale, + 'sampler' => $sampler, + 'size_info' => $size_info +); + +// Redirect to result page +header("Location: result.php"); +exit; + diff --git a/index.php b/index.php index f8be232..af8857b 100644 --- a/index.php +++ b/index.php @@ -2,22 +2,8 @@ ini_set('max_execution_time', '300'); header('Content-Type: text/html; charset=iso-8859-1'); -$response = ""; -$generated_image = ""; -$prompt = ""; -$negative_prompt = ""; -$width = 768; -$height = 768; -$steps = 35; -$cfg_scale = 3; -$sampler = "DPM++ 3M SDE"; -$error_message = ""; -$success_message = ""; - -// File to store saved prompts $saved_prompts_file = 'saved-prompts.json'; -// Load saved prompts function loadSavedPrompts() { global $saved_prompts_file; if (file_exists($saved_prompts_file)) { @@ -27,366 +13,102 @@ function loadSavedPrompts() { return array(); } -// Save a new prompt -function savePrompt($prompt_text, $negative_prompt_text, $prompt_name) { - global $saved_prompts_file; - $prompts = loadSavedPrompts(); - $prompts[$prompt_name] = array( - 'prompt' => $prompt_text, - 'negative_prompt' => $negative_prompt_text - ); - file_put_contents($saved_prompts_file, json_encode($prompts, JSON_PRETTY_PRINT)); -} - $saved_prompts = loadSavedPrompts(); -// List of available samplers -$samplers = array( - "Euler", - "Euler a", - "Heun", - "DPM2", - "DPM2 a", - "DPM++ 2S a", - "DPM++ 2M", - "DPM++ SDE", - "DPM++ 2M SDE", - "DPM++ 3M SDE", - "DPM fast", - "DPM adaptive", - "LMS", - "DPM++ SDE Karras", - "DPM++ 2M SDE Karras", - "DPM++ 3M SDE Karras", - "DDIM", - "PLMS" -); +$prompt = ''; +$negative_prompt = ''; +$width = 768; +$height = 768; +$steps = 35; +$cfg_scale = 3; +$sampler = "Euler a"; -if ($_SERVER["REQUEST_METHOD"] == "POST") { - // All requests to the server to save or execute prompts will be POST requests to itself - // Handle prompt saving - if (isset($_POST['save_prompt']) && isset($_POST['prompt_name'])) { - $prompt_to_save = trim($_POST['prompt']); - $negative_prompt_to_save = trim($_POST['negative_prompt']); - $prompt_name = trim($_POST['prompt_name']); - - if (!empty($prompt_to_save) && !empty($prompt_name)) { - savePrompt($prompt_to_save, $negative_prompt_to_save, $prompt_name); - $success_message = "Prompt saved successfully!"; - $saved_prompts = loadSavedPrompts(); // Reload prompts - } else { - $error_message = "Both prompt and prompt name are required to save."; - } - } - // Handle image generation - else { - $prompt = isset($_POST['prompt']) ? $_POST['prompt'] : ''; - $negative_prompt = isset($_POST['negative_prompt']) ? $_POST['negative_prompt'] : ''; - $width = isset($_POST['width']) ? (int)$_POST['width'] : 512; - $height = isset($_POST['height']) ? (int)$_POST['height'] : 512; - $steps = isset($_POST['steps']) ? (int)$_POST['steps'] : 20; - $cfg_scale = isset($_POST['cfg_scale']) ? (float)$_POST['cfg_scale'] : 7.5; - $sampler = isset($_POST['sampler']) ? $_POST['sampler'] : 'Euler'; - - // Check for angle brackets in the prompt - if (strpos($prompt, '<') !== false || strpos($prompt, '>') !== false) { - $error_message = "Error: Angle brackets are not allowed in the prompt. Please use square brackets [lora-name:weight] for LORAs."; - } - // Only proceed if there's no error - elseif (!empty($prompt)) { - // Process the prompt to handle LORA syntax - $processed_prompt = preg_replace('/\[([\w\s\-]+?):([\d\.]+)\]/', '', $prompt); - - $data = array( - 'prompt' => $processed_prompt, - 'negative_prompt' => $negative_prompt, - 'steps' => $steps, - 'width' => $width, - 'height' => $height, - 'cfg_scale' => $cfg_scale, - 'sampler_name' => $sampler, - 'override_settings' => new stdClass(), - 'override_settings_restore_afterwards' => true - ); - - // Replace 127.0.0.1 with your Stable diffusion server's IP if running externally - $ch = curl_init('http://127.0.0.1:7860/sdapi/v1/txt2img'); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); - curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); - - $result = curl_exec($ch); - - if (curl_errno($ch)) { - $response = 'Error: ' . curl_error($ch); - } else { - $decoded = json_decode($result, true); - if (isset($decoded['images'][0])) { - $image_data = base64_decode($decoded['images'][0]); - - // Save original PNG temporarily - $temp_png = 'temp_' . time() . '_' . bin2hex(random_bytes(8)) . '.png'; - file_put_contents($temp_png, $image_data); - - // Create output JPG filename - $generated_image = 'generated_' . time() . '_' . bin2hex(random_bytes(8)) . '.jpg'; - - // Build ImageMagick convert command with optimization - $quality = 85; // Balanced quality setting - $cmd = "convert \"$temp_png\" -strip -interlace Plane -gaussian-blur 0.05 -quality $quality "; - $cmd .= "-sampling-factor 4:2:0 \"$generated_image\" 2>&1"; - - // Execute conversion - $output = []; - $returnVar = 0; - exec($cmd, $output, $returnVar); - - if ($returnVar === 0 && file_exists($generated_image)) { - // Get file sizes for comparison - $originalSize = filesize($temp_png); - $convertedSize = filesize($generated_image); - - // Clean up temporary PNG file - if (file_exists($temp_png)) { - unlink($temp_png); - } - - // Clean up old generated files - foreach (glob("generated_*.png") as $file) { - if ($file != $generated_image && (time() - filemtime($file)) > 3600) { - unlink($file); - } - } - - // Store size information for display - $size_info = array( - 'original' => round($originalSize / 1024, 2), - 'converted' => round($convertedSize / 1024, 2), - 'reduction' => round((($originalSize - $convertedSize) / $originalSize) * 100, 2) - ); - } else { - // If conversion fails, keep the original PNG - if (file_exists($temp_png)) { - $generated_image = $temp_png; - } - $error_message = "Image conversion failed. Using original PNG format."; - } - } - } - - curl_close($ch); - } - } -} else if ($_SERVER["REQUEST_METHOD"] == "GET" && isset($_GET['load_prompt'])) { - // For loading prompts we use GET method to send the client's choice of prompt to load - // Load saved prompt into textarea - $saved_prompts = loadSavedPrompts(); +if ($_SERVER["REQUEST_METHOD"] == "GET" && isset($_GET['load_prompt'])) { if (isset($saved_prompts[$_GET['load_prompt']])) { - // Check if the saved prompt is in the new format (array) or old format (string) if (is_array($saved_prompts[$_GET['load_prompt']])) { $prompt = $saved_prompts[$_GET['load_prompt']]['prompt']; $negative_prompt = $saved_prompts[$_GET['load_prompt']]['negative_prompt']; } else { - // Handle old format $prompt = $saved_prompts[$_GET['load_prompt']]; $negative_prompt = ''; } } } + +$samplers = array( + "Euler", "Euler a", "Heun", "DPM2", "DPM2 a", "DPM++ 2S a", "DPM++ 2M", + "DPM++ SDE", "DPM++ 2M SDE", "DPM++ 3M SDE", "DPM fast", "DPM adaptive", + "LMS", "DPM++ SDE Karras", "DPM++ 2M SDE Karras", "DPM++ 3M SDE Karras", + "DDIM", "PLMS" +); ?> - + Stable Diffusion Image Generator - + +
Prompt saved successfully.
+ +
Error: Prompt and name are required.
+ +

Stable Diffusion Image Generator

-
-

How to use LORAs:

-

Include LORAs in your prompt using this syntax: [lora-name:weight]

-

Example: a beautiful landscape by [my-artist-lora:0.8]

-

Weight values typically range from 0.1 to 1.0

-

Note: Angle brackets (< >) are not allowed in prompts. Use square brackets instead.

+
+ How to use LORAs: +

Use square bracket format: [lora-name:weight]

+

Example: a detailed castle, [my-style-lora:0.7]

+

Note: Angle brackets are not allowed.

- -
- -
- - - -
- -
- - -
-

Saved Prompts

-
"> - + + $data): ?> + - +
-
"> -
-
- -
+ +


+

-
-
- -
+


+

-
- - -
+

+ Width: + Height: +

-
- - -
+

+ Steps: + CFG Scale: +

-
- - -
- -
- - -
- -
- - -
+

-
- -
+

- -
-

Save Current Prompt

-
"> -
- - -
- - - -
-
- - -
-

Generated Image:

- Generated image -
- Download Generated Image - -
-

- Original size: KB
- Converted size: KB
- Size reduction: % -

-
- -
- Parameters used:
- Prompt:
- Negative Prompt:
- Processed Prompt:
- Size: x
- Steps:
- CFG Scale:
- Sampler: -
-
- - - -
-

Error:

-

-
- + diff --git a/loading.php b/loading.php new file mode 100644 index 0000000..ee6d83e --- /dev/null +++ b/loading.php @@ -0,0 +1,28 @@ + + + + + Generating Image... + + + +

Generating Your Image...

+

Please wait. This may take up to 30 seconds depending on prompt complexity.

+ +
+ $value): + $safeKey = htmlspecialchars($key, ENT_QUOTES); + $safeValue = htmlspecialchars($value, ENT_QUOTES); + ?> + + + +
+ + + diff --git a/result.php b/result.php new file mode 100644 index 0000000..ee9dfde --- /dev/null +++ b/result.php @@ -0,0 +1,50 @@ + + + + + Stable Diffusion Result + + +

Generated Image

+ Generated image
+ Download Generated Image +
+ Parameters used:
+ Prompt:
+ Negative Prompt:
+ Processed Prompt:

+ Size: x
+ Steps:
+ CFG Scale:
+ Sampler:
+ +

+ Original size: KB
+ Converted size: KB
+ Size reduction: % +

+ +
+
+

Save This Prompt

+
+
+ + + + +
+
+ Return to Home + + + + diff --git a/save-prompt.php b/save-prompt.php new file mode 100644 index 0000000..83823f0 --- /dev/null +++ b/save-prompt.php @@ -0,0 +1,41 @@ + $prompt_text, + 'negative_prompt' => $negative_prompt_text + ); + file_put_contents($saved_prompts_file, json_encode($prompts, JSON_PRETTY_PRINT)); +} + +if ($_SERVER["REQUEST_METHOD"] == "POST") { + $prompt = trim($_POST['prompt'] ?? ''); + $negative_prompt = trim($_POST['negative_prompt'] ?? ''); + $prompt_name = trim($_POST['prompt_name'] ?? ''); + + if (!empty($prompt) && !empty($prompt_name)) { + savePrompt($prompt, $negative_prompt, $prompt_name); + header("Location: index.php?save_success=1"); + } else { + header("Location: index.php?save_error=1"); + } + exit; +} else { + echo "Invalid request method."; +} +