Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // A 100% FREE DESKTOP HTML IMAGE GENERATOR USING POLLINATIONS.AI
- // BY UFO USING GPT4 ON PERCHANCE.ORG
- <style>
- @keyframes gradientBG {
- 0% {
- background-position: 0% 50%;
- }
- 50% {
- background-position: 100% 50%;
- }
- 100% {
- background-position: 0% 50%;
- }
- }
- @keyframes gradientBGReversed {
- 0% {
- background-position: 100% 50%;
- }
- 50% {
- background-position: 0% 50%;
- }
- 100% {
- background-position: 100% 50%;
- }
- }
- body {
- background: linear-gradient(-45deg, #111, #444, #888);
- background-size: 600% 600%;
- animation: gradientBG 25s ease infinite;
- font-family: Arial, sans-serif;
- margin: 0;
- padding-top: 20px;
- color: lightgray;
- text-align: center;
- }
- h1,
- h3 {
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
- color: lightgray;
- }
- button,
- input {
- background: linear-gradient(-45deg, #222, #444, #666);
- background-size: 600% 600%;
- animation: gradientBGReversed 25s ease infinite;
- border: 1px solid #999;
- color: lightgray;
- padding: 10px;
- margin: 5px;
- border-radius: 5px;
- box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
- transition: all 0.3s ease;
- }
- select {
- background: #444444; /* Dark gray background for list menus */
- border: 1px solid #999;
- color: lightgray;
- padding: 10px;
- margin: 5px;
- border-radius: 5px;
- box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
- transition: all 0.3s ease;
- font-weight: bold;
- }
- /* Set background for select dropdown options */
- select option {
- background-color: #444444; /* Dark gray for dropdown options */
- color: lightgray;
- }
- button {
- border: 1px solid #aaa;
- }
- button:hover,
- input:hover {
- background: linear-gradient(-45deg, #333, #555, #999);
- background-size: 400% 400%;
- animation: gradientBG 3s ease infinite;
- box-shadow: 0 0 15px rgba(170, 170, 170, 0.5);
- }
- select:hover {
- background: #555555;
- box-shadow: 0 0 15px rgba(170, 170, 170, 0.5);
- }
- #framesCtn {
- background: linear-gradient(-45deg, #111, #333, #555);
- background-size: 600% 600%;
- animation: gradientBGReversed 25s ease infinite;
- border-radius: 10px;
- padding: 20px;
- box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
- align-items: center;
- border: 1px solid #999;
- }
- textarea {
- font-weight: bold;
- color: lightgray;
- background: linear-gradient(-45deg, #222, #333, #444);
- background-size: 600% 600%;
- animation: gradientBGReversed 25s ease infinite;
- border: 1px solid #999;
- }
- #fileListEl {
- background: #444444; /* Dark gray background for list menus */
- width: 400px;
- margin: 0 auto;
- text-align: left;
- border: 1px solid #999;
- }
- .fullscreen-image {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.9);
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 1000;
- }
- .fullscreen-image img {
- max-width: 90%;
- max-height: 90%;
- }
- .container {
- background: linear-gradient(-45deg, #111, #333, #555);
- background-size: 600% 600%;
- animation: gradientBGReversed 25s ease infinite;
- border-radius: 8px;
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
- padding: 30px;
- max-width: 600px;
- width: 100%;
- margin: 20px auto;
- border: 1px solid #999;
- }
- form {
- display: flex;
- flex-direction: column;
- gap: 10px;
- margin-bottom: 40px;
- align-items: center;
- }
- .button-row {
- display: flex;
- gap: 10px;
- }
- #resultEl img {
- max-width: 100%;
- border-radius: 4px;
- margin-top: 15px;
- border: 1px solid #999;
- }
- #resultEl p {
- margin-top: 15px;
- color: lightgray;
- }
- .loading-screen {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 200px;
- border-radius: 4px;
- margin-top: 15px;
- border: 1px solid #999;
- }
- .loading-spinner {
- border: 4px solid #333;
- border-top: 4px solid #aaa;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
- }
- .error-message {
- margin-top: 15px;
- text-align: center;
- color: lightgray;
- }
- /* Set all labels and background elements to reversed gradient */
- label, div, p, h1, h2, h3, h4, h5, h6, span, a, li, ul, table, tr, td, th {
- background: linear-gradient(-45deg, #111, #333, #555);
- background-size: 600% 600%;
- animation: gradientBGReversed 25s ease infinite;
- background-clip: padding-box;
- color: lightgray;
- }
- /* Override specific elements that should keep their existing styling */
- body {
- background: linear-gradient(-45deg, #111, #444, #888);
- background-size: 600% 600%;
- animation: gradientBG 25s ease infinite;
- }
- .fullscreen-image {
- background-color: rgba(0, 0, 0, 0.9);
- }
- /* Ensure form elements maintain reversed gradient */
- input[type="checkbox"] {
- background: linear-gradient(-45deg, #111, #333, #555);
- background-size: 600% 600%;
- animation: gradientBGReversed 25s ease infinite;
- border: 1px solid #999;
- }
- /* Style any mainColumnEl85394739 elements if they exist */
- #mainColumnEl85394739,
- .mainColumnEl85394739,
- [id*="mainColumnEl85394739"] {
- background: linear-gradient(-45deg, #111, #333, #555);
- background-size: 600% 600%;
- animation: gradientBGReversed 25s ease infinite;
- border: 1px solid #999;
- }
- </style>
- <!-- Flux image generator -->
- <div class="container">
- <form id="imageFormEl">
- <textarea id="userPromptEl" placeholder="Enter your prompt" style="width: 100%; height: 200px; resize: both; overflow-y: auto" required></textarea>
- <div style="display: flex; align-items: center; gap: 10px;">
- <div>
- <input type="checkbox" id="positivePromptCheckbox" />
- <label for="positivePromptCheckbox">Positive</label>
- </div>
- <select id="modelSelectEl">
- <option value="flux" selected>flux</option>
- <option value="flux-realism">flux-realism</option>
- <option value="any-dark">any-dark</option>
- <option value="flux-anime">flux-anime</option>
- <option value="flux-3d">flux-3d</option>
- <option value="turbo">turbo</option>
- </select>
- <div>
- <input type="checkbox" id="negativePromptCheckbox" />
- <label for="negativePromptCheckbox">Negative</label>
- </div>
- </div>
- <!-- New options for flux generator -->
- <div>
- <label for="guidanceScaleInput">Guidance Scale:</label>
- <input type="number" id="guidanceScaleInputEl" min="1" max="30" value="30" step="1" /><label>GS 1 - 30</label>
- </div>
- <div>
- <label for="numInferenceStepsInput">Num Inference Steps:</label>
- <input type="number" id="numInferenceStepsInputEl" min="1" max="50" value="25" step="1" /><label> 1 - 50</label>
- </div>
- <div>
- <label for="maxSequenceLengthInput">Max Sequence Length:</label>
- <input type="number" id="maxSequenceLengthInputEl" min="1" max="256" value="128" step="1" /><label> 1 - 256</label>
- </div>
- <div class="button-row">
- <button type="button" id="enhanceBtn">Enhance Prompt</button>
- <button type="submit" id="generateBtn">Generate FLUX Image</button>
- </div>
- </form>
- <div>
- <input type="checkbox" id="autoUpscaleEl" />
- <label for="autoUpscaleEl">Auto Upscale With Settings From Below</label>
- </div>
- <div>
- <input type="text" id="seedInput" placeholder="Enter seed. This is FLUX." />
- <button id="seedsBtn" onclick="generateRandomSeed()">SEEDS</button>
- </div>
- <div id="resultEl"></div>
- </div>
- <script>
- const placeholders = ["Flux image gen with auto upscaler template 4U. Enjoy.", "(copy/paste all html/script)", "Pollinations only accepts sfw prompts rather strictly 95+% of time. 😇", "If you never used Flux before and never seen an auto img upscaler gen use at own risk, head may explode 🤯."];
- let currentPlaceholderIndex = 0;
- function rotatePlaceholder() {
- document.getElementById("userPromptEl").placeholder = placeholders[currentPlaceholderIndex];
- currentPlaceholderIndex = (currentPlaceholderIndex + 1) % placeholders.length;
- }
- rotatePlaceholder();
- setInterval(rotatePlaceholder, 3000);
- // Positive and Negative prompt text
- const positivePromptText = "(max_sequence_length=256), (guidance_scale=30.0), (num_inference_steps=50), (photorealistic), ((RAW photo, Film Still)), ((ultra-detailed photo, High resolution, Vibrant colours, vivid, dramatic shadows, Ultra contrast)), ((bright angle, octane, ((Ultra 8K HD)), cinematic color grading)), (best quality), ray_tracing:1, intgricate, ornate, (highly detailed skin, detailed face, detailed eyes, detailed hair), (detailed background, detailed foreground, detailed subject), (detailed clothing, detailed accessories), (detailed textures, detailed materials), (detailed lighting, detailed shadows), (detailed reflections, detailed refractions), (detailed environment, detailed atmosphere), ";
- const negativePromptText = ", (((negative_prompt=blurry, disfigured, missing_limbs, extra_limbs, low_res, low_poly, fat, distorted_limbs, disfigured_face, abnormal_limbs, low_res_face, poor_quality, low details, hideous, ugly, grotesque, fat_limbs, fat fingers, awkward, sketch, wrong, anime, manga, 3d, illustration, depth_of_field, dof, blur, bokeh, depth of field, out of focus, grain, grainy, noise, noisy, watermark, text, signature, logo, cropped, cut off)))";
- // Add event listeners for the checkboxes
- document.getElementById("positivePromptCheckbox").addEventListener("change", updatePrompt);
- document.getElementById("negativePromptCheckbox").addEventListener("change", updatePrompt);
- // Function to update the prompt based on checkbox states
- function updatePrompt() {
- const promptEl = document.getElementById("userPromptEl");
- let currentPrompt = promptEl.value;
- // Remove previous positive and negative prefixes/suffixes if they exist
- if (currentPrompt.startsWith(positivePromptText)) {
- currentPrompt = currentPrompt.substring(positivePromptText.length).trim();
- }
- if (currentPrompt.endsWith(negativePromptText)) {
- currentPrompt = currentPrompt.substring(0, currentPrompt.length - negativePromptText.length).trim();
- }
- // Add prefix if positive checkbox is checked
- if (document.getElementById("positivePromptCheckbox").checked) {
- currentPrompt = positivePromptText + " " + currentPrompt;
- }
- // Add suffix if negative checkbox is checked
- if (document.getElementById("negativePromptCheckbox").checked) {
- currentPrompt = currentPrompt + " " + negativePromptText;
- }
- promptEl.value = currentPrompt.trim();
- }
- // Function to upscale an image using the settings from the upscaler
- async function upscaleImage(imgUrl) {
- return new Promise((resolve, reject) => {
- const img = new Image();
- img.onload = function() {
- try {
- // Get upscaler settings
- const scaleValue = sizeSelectEl.value;
- const method = methodSelectEl.value;
- const effect = effectsSelectEl.value;
- const speed = speedSelectEl.value;
- // Create canvas for initial image
- const tempCanvas = document.createElement('canvas');
- const tempCtx = tempCanvas.getContext('2d');
- tempCanvas.width = img.width;
- tempCanvas.height = img.height;
- // Draw the original image
- tempCtx.drawImage(img, 0, 0);
- // Apply pre-processing effect if selected
- if (effect !== 'none') {
- applyEffect(tempCanvas, tempCtx, effect);
- }
- // Determine scale factor and dimensions
- let scale, destWidth, destHeight;
- if (scaleValue.includes('x')) {
- // Fixed size format: widthxheight
- const dimensions = scaleValue.split('x');
- const targetWidth = parseInt(dimensions[0]);
- const targetHeight = parseInt(dimensions[1]);
- // Calculate scale factors for width and height
- const widthScale = targetWidth / img.width;
- const heightScale = targetHeight / img.height;
- // Use the smaller scale to ensure the image fits within the target dimensions
- // while maintaining aspect ratio
- scale = Math.min(widthScale, heightScale);
- destWidth = Math.round(img.width * scale);
- destHeight = Math.round(img.height * scale);
- } else {
- // Percentage scale
- scale = parseFloat(scaleValue);
- destWidth = Math.round(img.width * scale);
- destHeight = Math.round(img.height * scale);
- }
- // Create the destination canvas with calculated dimensions
- const destCanvas = document.createElement('canvas');
- const destCtx = destCanvas.getContext('2d');
- destCanvas.width = destWidth;
- destCanvas.height = destHeight;
- // Apply resize method
- applyResizeMethod(tempCanvas, destCanvas, destCtx, method, scaleValue);
- // Convert to data URL
- const upscaledUrl = destCanvas.toDataURL('image/jpeg', 0.9);
- resolve(upscaledUrl);
- } catch (error) {
- console.error('Error upscaling:', error);
- reject(error);
- }
- };
- img.onerror = () => reject(new Error('Failed to load image'));
- img.src = imgUrl;
- });
- }
- document.getElementById("imageFormEl").addEventListener("submit", async function (event) {
- event.preventDefault(); // Prevent form submission
- const prompt = document.getElementById("userPromptEl").value.trim();
- const model = document.getElementById("modelSelectEl").value;
- const guidanceScale = guidanceScaleInputEl.value;
- const numInferenceSteps = numInferenceStepsInputEl.value;
- const maxSequenceLength = maxSequenceLengthInputEl.value;
- if (prompt) {
- document.getElementById("resultEl").innerHTML = `
- <p>Your generated image:</p>
- <div class="loading-screen">
- <div class="loading-spinner"></div>
- </div>
- `;
- try {
- const imageUrl = `https://image.pollinations.ai/prompt/${encodeURIComponent(prompt)}?width=1920&height=1080&seed=${seedInput.value || Math.random()}&model=${model}&guidance_scale=${guidanceScale}&num_inference_steps=${numInferenceSteps}&max_sequence_length=${maxSequenceLength}&nologo=true`;
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Request timed out")), 60000));
- const response = await Promise.race([fetch(imageUrl), timeoutPromise]);
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- const blob = await response.blob();
- const imgUrl = URL.createObjectURL(blob);
- // Check if auto upscale is enabled
- if (autoUpscaleEl.checked) {
- // Show upscaling status
- document.getElementById("resultEl").innerHTML = `
- <p>Your generated image (upscaling...):</p>
- <div class="loading-screen">
- <div class="loading-spinner"></div>
- </div>
- `;
- try {
- // Upscale the image with settings from below
- const upscaledUrl = await upscaleImage(imgUrl);
- // Display upscaled image
- document.getElementById("resultEl").innerHTML = `
- <p>Your generated image (auto upscaled):</p>
- <div class="image-container">
- <img src="${upscaledUrl}" alt="Generated Image" onclick="openFullscreen(this.src)">
- </div>
- <button id="downloadBtn" onclick="downloadImage()">Download Image</button>
- `;
- } catch (error) {
- console.error('Upscaling failed:', error);
- // If upscaling fails, show the original
- document.getElementById("resultEl").innerHTML = `
- <p>Your generated image (upscaling failed):</p>
- <div class="image-container">
- <img src="${imgUrl}" alt="Generated Image" onclick="openFullscreen(this.src)">
- </div>
- <button id="downloadBtn" onclick="downloadImage()">Download Image</button>
- `;
- }
- } else {
- // Display the original image
- document.getElementById("resultEl").innerHTML = `
- <p>Your generated image:</p>
- <div class="image-container">
- <img src="${imgUrl}" alt="Generated Image" onclick="openFullscreen(this.src)">
- </div>
- <button id="downloadBtn" onclick="downloadImage()">Download Image</button>
- `;
- }
- } catch (error) {
- console.error("Error generating image:", error);
- document.getElementById("resultEl").innerHTML = `
- <p class="error-message">Failed to generate image. Please try again. Error: ${error.message}</p>
- `;
- }
- } else {
- alert("Please enter a prompt!");
- }
- });
- document.getElementById("enhanceBtn").addEventListener("click", async function () {
- const prompt = document.getElementById("userPromptEl").value.trim();
- if (prompt) {
- document.getElementById("enhanceBtn").disabled = true;
- document.getElementById("enhanceBtn").textContent = "Enhancing...";
- try {
- console.log("Enhancing prompt:", prompt); // Debug log
- const response = await fetch(`https://text.pollinations.ai/prompt/${encodeURIComponent(`Please take this prompt and enhance it into a 900 to 2600+ letter prompt for images. Here is the prompt: ${prompt}`)}`);
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- const enhancedPrompt = await response.text();
- console.log("Enhanced prompt:", enhancedPrompt); // Debug log
- document.getElementById("userPromptEl").value = enhancedPrompt;
- } catch (error) {
- console.error("Error enhancing prompt:", error);
- alert(`Failed to enhance prompt. Please try again. Error: ${error.message}`);
- } finally {
- document.getElementById("enhanceBtn").disabled = false;
- document.getElementById("enhanceBtn").textContent = "Enhance Prompt";
- }
- } else {
- alert("Please enter a prompt to enhance!");
- }
- });
- async function downloadImage() {
- const image = document.querySelector("#resultEl img");
- const response = await fetch(image.src);
- const blob = await response.blob();
- const url = window.URL.createObjectURL(blob);
- const a = document.createElement("a");
- a.style.display = "none";
- a.href = url;
- a.download = "generated_image.jpg";
- document.body.appendChild(a);
- a.click();
- window.URL.revokeObjectURL(url);
- }
- function generateRandomSeed() {
- seedInput.value = Math.floor(Math.random() * 100000000000);
- }
- function toggleComments() {
- chatCtn.hidden = !chatCtn.hidden;
- hideCommentsBtn.textContent = chatCtn.hidden ? "Show Comments" : "Hide Comments";
- }
- function toggleGallery() {
- galleryCtn.hidden = !galleryCtn.hidden;
- hideGalleryBtn.textContent = galleryCtn.hidden ? "Show Gallery" : "Hide Gallery";
- }
- function openFullscreen(src) {
- const fullscreenDiv = document.createElement('div');
- fullscreenDiv.className = 'fullscreen-image';
- fullscreenDiv.innerHTML = `<img src="${src}" alt="Fullscreen Image">`;
- fullscreenDiv.onclick = () => document.body.removeChild(fullscreenDiv);
- document.body.appendChild(fullscreenDiv);
- }
- </script> <style>
- /* Rainbow text animation */
- @keyframes rainbowRotate {
- 0% {background-position: 0% 50%;}
- 100% {background-position: 100% 50%;}
- }
- /* Reversed rainbow animation for hover */
- @keyframes rainbowRotateReverse {
- 0% {background-position: 100% 50%;}
- 100% {background-position: 0% 50%;}
- }
- /* Link styles */
- .rainbow-link {
- text-decoration: none;
- display: inline-block;
- }
- .rainbow-link span {
- background: linear-gradient(to right, #111, #333, #555, #777, #999, #ddd);
- background-size: 200% auto;
- animation: rainbowRotate 5s linear infinite;
- -webkit-background-clip: text;
- background-clip: text;
- color: transparent;
- display: inline-block;
- }
- .rainbow-link:hover span {
- animation: rainbowRotateReverse 5s linear infinite;
- text-shadow:
- 0px 0px 4px rgba(170,170,170,0.7),
- 0px 0px 8px rgba(150,150,150,0.7),
- 0px 0px 12px rgba(120,120,120,0.7),
- 0px 0px 16px rgba(100,100,100,0.7),
- 0px 0px 20px rgba(80,80,80,0.7),
- 0px 0px 24px rgba(60,60,60,0.7),
- 0px 0px 28px rgba(40,40,40,0.7);
- }
- /* Table styling */
- table {
- width: 100%;
- border-collapse: collapse;
- border: 1px solid #999;
- }
- td {
- text-align: left;
- padding: 4px 0;
- border: 1px solid #999;
- }
- /* The line below makes it so if your device is in dark mode, then the default text color is switched to white, and the default background color is switch to black. */
- html { color-scheme: light dark; }
- </style>
- <!-- The line below makes it so if your device is in dark mode, then the default text color is switched to white, and the default background color is switch to black. -->
- <style> html { color-scheme: light dark; } </style><div class="container" style="max-width: 1000px; margin: 0 auto; font-family: Arial, sans-serif; text-align: left;">
- <div class="upload-section" style="margin-bottom: 20px;">
- <div id="dropAreaEl" style="border: 3px dashed #ccc; border-radius: 8px; padding: 30px; text-align: center; margin-bottom: 20px; transition: border-color 0.3s;">
- <p>Drag & drop images here or...<br></p>
- <input type="file" id="fileInputEl" multiple accept="image/*" style="display: none;">
- <button id="browseBtn" style="background: #4CAF50; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Browse Files</button>
- </div>
- <div id="previewContainerEl" style="display: flex; flex-wrap: wrap; gap: 10px;"></div>
- </div>
- <div class="controls" style="background: padding: 20px; border-radius: 8px; margin-bottom: 20px; display: flex; flex-wrap: wrap; gap: 20px; align-items: start;">
- <div class="control-group" style="min-width: 200px;">
- <label for="sizeSelect">Resize to:</label>
- <select id="sizeSelectEl" style="padding: 10px; border-radius: 4px; border: 1px solid #ddd; width: 100%;">
- <option value="0.1">10%</option>
- <option value="0.2">20%</option>
- <option value="0.3">30%</option>
- <option value="0.4">40%</option>
- <option value="0.5">50%</option>
- <option value="0.6">60%</option>
- <option value="0.7">70%</option>
- <option value="0.8">80%</option>
- <option value="0.9">90%</option>
- <option value="1.0">100%</option>
- <option value="1.1">110%</option>
- <option value="1.2">120%</option>
- <option value="1.3">130%</option>
- <option value="1.4">140%</option>
- <option value="1.5">150%</option>
- <option value="1.6">160%</option>
- <option value="1.7">170%</option>
- <option value="1.8">180%</option>
- <option value="1.9">190%</option>
- <option value="2.0" selected>200%</option>
- <option value="3.0">300%</option>
- <option value="4.0">400%</option>
- <option value="1920x1080">1920x1080 (HD)</option>
- <option value="3840x2160">3840x2160 (4K)</option>
- <option value="7680x4320">7680x4320 (8K)</option>
- </select>
- </div>
- <div class="control-group" style="min-width: 200px;">
- <label for="methodSelect">Scaling Method:</label>
- <select id="methodSelectEl" style="padding: 10px; border-radius: 4px; border: 1px solid #ddd; width: 100%;">
- <option value="pixelated">Nearest Neighbor (Pixelated)</option>
- <option value="bilinear">Bilinear (Smooth)</option>
- <option value="bicubic" selected>Bicubic (Higher Quality)</option>
- <option value="lanczos">Lanczos (Sharper Details)</option>
- <option value="hermite">Hermite (Smooth Curves)</option>
- <option value="mitchell">Mitchell-Netravali (Balanced)</option>
- <option value="catmull-rom">Catmull-Rom (Preserve Edges)</option>
- <option value="box">Box (Fast Scaling)</option>
- <option value="hamming">Hamming (Reduce Aliasing)</option>
- <option value="gaussian">Gaussian (Soft Edges)</option>
- <option value="linear">Linear (Simple Interpolation)</option>
- <option value="cubic">Cubic (Variant of Bicubic)</option>
- <option value="spline">Spline (Smooth Curve Interpolation)</option>
- <option value="lagrange">Lagrange (Mathematical Precision)</option>
- <option value="sinc">Sinc (High Quality)</option>
- <option value="hann">Hann-windowed Sinc (Reduced Ringing)</option>
- <option value="bspline">B-Spline (Smooth with Less Overshoot)</option>
- <option value="triangle">Triangle Filter (Weighted Average)</option>
- <option value="quadratic">Quadratic Filter (Smoother than Triangle)</option>
- <option value="kaiser">Kaiser Window (Adjustable Sharpness)</option>
- <option value="blackman">Blackman Window (Low Ringing)</option>
- <option value="adaptive">Adaptive (Content-Aware)</option>
- <option value="super-res">Super Resolution (AI Enhanced)</option>
- <option value="edt">Edge-Directed (Detail Preserving)</option>
- <option value="fractal">Fractal (Pattern Enhancing)</option>
- </select>
- </div>
- <div class="control-group" style="min-width: 200px;">
- <label for="effectsSelect">Pre-processing Effects:</label>
- <select id="effectsSelectEl" style="padding: 10px; border-radius: 4px; border: 1px solid #ddd; width: 100%;">
- <option value="none">None</option>
- <option value="blur-light">Fine Blur</option>
- <option value="blur-strong">Strong Blur</option>
- <option value="sharpen-light" selected>Light Sharpen</option>
- <option value="sharpen-strong">Strong Sharpen</option>
- <option value="edge-enhance">Edge Enhance</option>
- <option value="emboss">Emboss</option>
- <option value="grayscale">Grayscale</option>
- <option value="sepia">Sepia</option>
- <option value="invert">Invert Colors</option>
- <option value="brighten">Brighten</option>
- <option value="darken">Darken</option>
- <option value="increase-contrast">Increase Contrast</option>
- <option value="decrease-contrast">Decrease Contrast</option>
- <option value="noise-reduction">Noise Reduction</option>
- <option value="posterize">Posterize</option>
- <option value="saturation-increase">Increase Saturation</option>
- <option value="saturation-decrease">Decrease Saturation</option>
- <option value="oil-painting">Oil Painting Effect</option>
- <option value="watercolor">Watercolor Effect</option>
- <option value="sketch">Sketch Effect</option>
- <option value="warm">Warm Color Filter</option>
- <option value="cool">Cool Color Filter</option>
- <option value="vintage">Vintage Filter</option>
- <option value="vignette">Vignette Effect</option>
- <option value="film-grain">Film Grain</option>
- <option value="noise-add">Add Noise</option>
- <option value="hdr">HDR Effect</option>
- <option value="tint-red">Red Tint</option>
- <option value="tint-green">Green Tint</option>
- <option value="tint-blue">Blue Tint</option>
- <option value="black-white">Black & White (High Contrast)</option>
- <option value="solarize">Solarize Effect</option>
- <option value="threshold">Threshold Effect</option>
- <option value="dither">Dithering</option>
- <option value="pixelate">Pixelate</option>
- <option value="unsharp-mask">Unsharp Mask</option>
- <option value="bloom">Bloom Effect</option>
- <option value="glitch">Glitch Art</option>
- <option value="duotone">Duotone</option>
- <option value="halftone">Halftone</option>
- <option value="color-balance">Color Balance</option>
- <option value="clarity">Clarity Boost</option>
- <option value="denoise">Advanced Denoise</option>
- <option value="cartoon">Cartoon Effect</option>
- </select>
- </div>
- <div class="control-group" style="min-width: 200px;">
- <label for="speedSelectEl">Rendering Speed:</label>
- <select id="speedSelectEl" style="padding: 10px; border-radius: 4px; border: 1px solid #ddd; width: 100%;">
- <option value="slow">Slow (25% resources)</option>
- <option value="medium" selected>Medium (50% resources)</option>
- <option value="fast">Fast (90% resources)</option>
- </select>
- </div>
- <div class="control-group" style="display: flex; align-items: flex-end; min-height: 70px;">
- <button id="processBtn" disabled style="background: #4CAF50; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Process Images</button>
- </div>
- </div>
- <div id="resultsCtn" hidden style="margin-top: 30px;">
- <h2>Processed Images</h2>
- <div style="display: flex; align-items: center; margin-bottom: 15px; gap: 10px;">
- <button id="downloadAllBtn" style="background: #2196F3; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Download All</button>
- <div style="display: flex; align-items: center;">
- <label for="imageFormatSelectEl" style="margin-right: 5px;">Format:</label>
- <select id="imageFormatSelectEl" style="padding: 8px; border-radius: 4px; border: 1px solid #ddd;">
- <option value="png">PNG</option>
- <option value="jpg">JPG</option>
- <option value="jpeg">JPEG</option>
- <option value="webp">WebP</option>
- </select>
- </div>
- </div>
- <div id="galleryEl" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px;"></div>
- </div>
- </div>
- <style>
- body {
- font-family: Arial, sans-serif;
- max-width: 800px;
- margin: 0 auto;
- padding: 20px;
- text-align: left;
- }
- .container {
- margin-top: 20px;
- }
- .exif-data {
- margin-top: 20px;
- border: 1px solid #ddd;
- padding: 15px;
- border-radius: 5px;
- max-height: 500px;
- overflow-y: auto;
- }
- .exif-section {
- margin-bottom: 15px;
- }
- .exif-section h3 {
- margin-bottom: 5px;
- border-bottom: 1px solid #eee;
- padding-bottom: 5px;
- }
- .exif-item {
- display: flex;
- padding: 3px 0;
- }
- .exif-tag {
- font-weight: bold;
- min-width: 200px;
- }
- .image-preview {
- max-width: 100%;
- max-height: 300px;
- margin-top: 10px;
- display: block;
- }
- #loadingIndicatorEl {
- display: none;
- margin-top: 10px;
- color: #666;
- }
- /* Drag and drop styles */
- .upload-area {
- border: 2px dashed #ccc;
- border-radius: 5px;
- padding: 20px;
- text-align: center;
- margin-bottom: 15px;
- transition: all 0.3s ease;
- }
- .upload-area.dragover {
- border-color: #007bff;
- background-color: rgba(0, 123, 255, 0.1);
- }
- .upload-instructions {
- margin: 10px 0;
- color: #555;
- }
- .url-input-container {
- display: flex;
- margin-top: 15px;
- }
- .url-input {
- flex: 1;
- padding: 8px;
- border: 1px solid #ccc;
- border-radius: 4px 0 0 4px;
- }
- .load-url-btn {
- padding: 8px 15px;
- background-color: #4a90e2;
- color: white;
- border: none;
- border-radius: 0 4px 4px 0;
- cursor: pointer;
- }
- .load-url-btn:hover {
- background-color: #357ae8;
- }
- </style>
- </head>
- <body>
- <h1>IMAGE TO TEXT PROMPT</h1>
- <p>With big thanks! This image to prompt area was coded by <a href="https://perchance.org/bove-ai" target="_blank">Bove.AI</a> / <a href="https://perchance.org/bove-flux" target="_blank">Flux</a> gens owner.</p>
- <div id="uploadAreaEl" class="upload-area">
- <input type="file" id="imageInputEl" accept="image/*">
- <div class="upload-instructions">or drag & drop an image file here</div>
- <div class="url-input-container">
- <input type="text" id="imageUrlInputEl" class="url-input" placeholder="Enter an image URL...">
- <button id="loadUrlBtnEl" class="load-url-btn">Load URL</button>
- </div>
- </div>
- <div id="loadingIndicatorEl">Processing image...</div>
- <div class="container">
- <img id="imagePreviewEl" class="image-preview" hidden>
- <div id="exifDataContainerEl" class="exif-data" hidden></div>
- </div>
- <script>
- // EXIF tag names mapping
- const exifTagNames = {
- // TIFF tags
- 0x0100: "ImageWidth",
- 0x0101: "ImageHeight",
- 0x0102: "BitsPerSample",
- 0x0103: "Compression",
- 0x0106: "PhotometricInterpretation",
- 0x010E: "ImageDescription",
- 0x010F: "Make",
- 0x0110: "Model",
- 0x0111: "StripOffsets",
- 0x0112: "Orientation",
- 0x0115: "SamplesPerPixel",
- 0x0116: "RowsPerStrip",
- 0x0117: "StripByteCounts",
- 0x011A: "XResolution",
- 0x011B: "YResolution",
- 0x011C: "PlanarConfiguration",
- 0x0128: "ResolutionUnit",
- 0x0131: "Software",
- 0x0132: "DateTime",
- 0x013B: "Artist",
- 0x013E: "WhitePoint",
- 0x013F: "PrimaryChromaticities",
- 0x0201: "JPEGInterchangeFormat",
- 0x0202: "JPEGInterchangeFormatLength",
- 0x0211: "YCbCrCoefficients",
- 0x0212: "YCbCrSubSampling",
- 0x0213: "YCbCrPositioning",
- 0x0214: "ReferenceBlackWhite",
- 0x8298: "Copyright",
- 0x8769: "ExifIFDPointer",
- 0x8825: "GPSInfoIFDPointer",
- // EXIF tags
- 0x829A: "ExposureTime",
- 0x829D: "FNumber",
- 0x8822: "ExposureProgram",
- 0x8824: "SpectralSensitivity",
- 0x8827: "ISOSpeedRatings",
- 0x8828: "OECF",
- 0x8830: "SensitivityType",
- 0x8831: "StandardOutputSensitivity",
- 0x8832: "RecommendedExposureIndex",
- 0x8833: "ISOSpeed",
- 0x8834: "ISOSpeedLatitudeyyy",
- 0x8835: "ISOSpeedLatitudezzz",
- 0x9000: "ExifVersion",
- 0x9003: "DateTimeOriginal",
- 0x9004: "DateTimeDigitized",
- 0x9101: "ComponentsConfiguration",
- 0x9102: "CompressedBitsPerPixel",
- 0x9201: "ShutterSpeedValue",
- 0x9202: "ApertureValue",
- 0x9203: "BrightnessValue",
- 0x9204: "ExposureBiasValue",
- 0x9205: "MaxApertureValue",
- 0x9206: "SubjectDistance",
- 0x9207: "MeteringMode",
- 0x9208: "LightSource",
- 0x9209: "Flash",
- 0x920A: "FocalLength",
- 0x9214: "SubjectArea",
- 0x927C: "MakerNote",
- 0x9286: "UserComment",
- 0x9290: "SubSecTime",
- 0x9291: "SubSecTimeOriginal",
- 0x9292: "SubSecTimeDigitized",
- 0xA000: "FlashpixVersion",
- 0xA001: "ColorSpace",
- 0xA002: "PixelXDimension",
- 0xA003: "PixelYDimension",
- 0xA004: "RelatedSoundFile",
- 0xA005: "InteroperabilityIFDPointer",
- 0xA20B: "FlashEnergy",
- 0xA20C: "SpatialFrequencyResponse",
- 0xA20E: "FocalPlaneXResolution",
- 0xA20F: "FocalPlaneYResolution",
- 0xA210: "FocalPlaneResolutionUnit",
- 0xA214: "SubjectLocation",
- 0xA215: "ExposureIndex",
- 0xA217: "SensingMethod",
- 0xA300: "FileSource",
- 0xA301: "SceneType",
- 0xA302: "CFAPattern",
- 0xA401: "CustomRendered",
- 0xA402: "ExposureMode",
- 0xA403: "WhiteBalance",
- 0xA404: "DigitalZoomRatio",
- 0xA405: "FocalLengthIn35mmFilm",
- 0xA406: "SceneCaptureType",
- 0xA407: "GainControl",
- 0xA408: "Contrast",
- 0xA409: "Saturation",
- 0xA40A: "Sharpness",
- 0xA40B: "DeviceSettingDescription",
- 0xA40C: "SubjectDistanceRange",
- 0xA420: "ImageUniqueID",
- 0xA430: "CameraOwnerName",
- 0xA431: "BodySerialNumber",
- 0xA432: "LensSpecification",
- 0xA433: "LensMake",
- 0xA434: "LensModel",
- 0xA435: "LensSerialNumber",
- // GPS tags
- 0x0000: "GPSVersionID",
- 0x0001: "GPSLatitudeRef",
- 0x0002: "GPSLatitude",
- 0x0003: "GPSLongitudeRef",
- 0x0004: "GPSLongitude",
- 0x0005: "GPSAltitudeRef",
- 0x0006: "GPSAltitude",
- 0x0007: "GPSTimeStamp",
- 0x0008: "GPSSatellites",
- 0x0009: "GPSStatus",
- 0x000A: "GPSMeasureMode",
- 0x000B: "GPSDOP",
- 0x000C: "GPSSpeedRef",
- 0x000D: "GPSSpeed",
- 0x000E: "GPSTrackRef",
- 0x000F: "GPSTrack",
- 0x0010: "GPSImgDirectionRef",
- 0x0011: "GPSImgDirection",
- 0x0012: "GPSMapDatum",
- 0x0013: "GPSDestLatitudeRef",
- 0x0014: "GPSDestLatitude",
- 0x0015: "GPSDestLongitudeRef",
- 0x0016: "GPSDestLongitude",
- 0x0017: "GPSDestBearingRef",
- 0x0018: "GPSDestBearing",
- 0x0019: "GPSDestDistanceRef",
- 0x001A: "GPSDestDistance",
- 0x001B: "GPSProcessingMethod",
- 0x001C: "GPSAreaInformation",
- 0x001D: "GPSDateStamp",
- 0x001E: "GPSDifferential"
- };
- // Format mappings for EXIF data types
- const formatMap = {
- 1: { name: "BYTE", size: 1 },
- 2: { name: "ASCII", size: 1 },
- 3: { name: "SHORT", size: 2 },
- 4: { name: "LONG", size: 4 },
- 5: { name: "RATIONAL", size: 8 },
- 7: { name: "UNDEFINED", size: 1 },
- 9: { name: "SLONG", size: 4 },
- 10: { name: "SRATIONAL", size: 8 }
- };
- // References to DOM elements
- const uploadArea = uploadAreaEl;
- const imageInput = imageInputEl;
- const imageUrlInput = imageUrlInputEl;
- const loadUrlBtn = loadUrlBtnEl;
- const imagePreview = imagePreviewEl;
- const exifDataContainer = exifDataContainerEl;
- const loadingIndicator = loadingIndicatorEl;
- // Listen for file selection
- imageInput.addEventListener('change', handleImageSelect);
- // Listen for URL load button click
- loadUrlBtn.addEventListener('click', handleImageUrl);
- // Listen for key press in URL input
- imageUrlInput.addEventListener('keypress', function(e) {
- if (e.key === 'Enter') {
- handleImageUrl();
- }
- });
- // Setup drag and drop event listeners
- uploadArea.addEventListener('dragover', function(e) {
- e.preventDefault();
- e.stopPropagation();
- uploadArea.classList.add('dragover');
- });
- uploadArea.addEventListener('dragleave', function(e) {
- e.preventDefault();
- e.stopPropagation();
- uploadArea.classList.remove('dragover');
- });
- uploadArea.addEventListener('drop', function(e) {
- e.preventDefault();
- e.stopPropagation();
- uploadArea.classList.remove('dragover');
- const files = e.dataTransfer.files;
- if (files.length > 0) {
- processImageFile(files[0]);
- }
- });
- async function handleImageSelect(event) {
- const file = event.target.files[0];
- if (file) {
- processImageFile(file);
- }
- }
- async function handleImageUrl() {
- const url = imageUrlInput.value.trim();
- if (!url) {
- alert('Please enter an image URL');
- return;
- }
- // Show loading indicator
- loadingIndicator.style.display = 'block';
- exifDataContainer.hidden = true;
- try {
- let blob;
- // Check if it's a blob URL
- if (url.startsWith('blob:')) {
- try {
- // For blob URLs, we'll try a direct fetch but this may fail
- // if the blob URL wasn't created in this context
- const response = await fetch(url);
- if (!response.ok) {
- throw new Error('Failed to fetch blob URL');
- }
- blob = await response.blob();
- } catch (error) {
- throw new Error('Could not access blob URL. Blob URLs can only be accessed in the same context they were created.');
- }
- } else {
- // Regular URL handling
- const response = await fetch(url);
- if (!response.ok) {
- throw new Error('Failed to fetch image from URL');
- }
- blob = await response.blob();
- }
- if (!blob.type.startsWith('image/')) {
- throw new Error('The URL does not point to a valid image');
- }
- // Create a File object from the blob
- const file = new File([blob], 'image-from-url.jpg', { type: blob.type });
- // Display image preview
- imagePreview.src = URL.createObjectURL(file);
- imagePreview.hidden = false;
- // Process the file for EXIF data
- const exifData = await extractExifData(file);
- displayExifData(exifData);
- } catch (error) {
- console.error('Error processing image URL:', error);
- exifDataContainer.innerHTML = `<p>Error: ${error.message}</p>`;
- exifDataContainer.hidden = false;
- }
- // Hide loading indicator
- loadingIndicator.style.display = 'none';
- }
- async function processImageFile(file) {
- if (!file || !file.type.startsWith('image/')) {
- alert('Please select a valid image file');
- return;
- }
- // Show loading indicator
- loadingIndicator.style.display = 'block';
- exifDataContainer.hidden = true;
- // Display image preview
- imagePreview.src = URL.createObjectURL(file);
- imagePreview.hidden = false;
- try {
- // Read the image file and extract EXIF data
- const exifData = await extractExifData(file);
- displayExifData(exifData);
- } catch (error) {
- console.error('Error extracting EXIF data:', error);
- exifDataContainer.innerHTML = `<p>Error extracting EXIF data: ${error.message}</p>`;
- exifDataContainer.hidden = false;
- }
- // Hide loading indicator
- loadingIndicator.style.display = 'none';
- }
- async function extractExifData(file) {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = function(e) {
- try {
- const arrayBuffer = e.target.result;
- const dataView = new DataView(arrayBuffer);
- // Check for JPEG SOI marker (Start of Image)
- if (dataView.getUint16(0, false) !== 0xFFD8) {
- throw new Error("Not a valid JPEG file");
- }
- let offset = 2; // Skip SOI marker
- let markerFound = false;
- let exifData = { main: {}, exif: {}, gps: {} };
- // Scan through JPEG segments
- while (offset < dataView.byteLength) {
- const marker = dataView.getUint16(offset, false);
- offset += 2;
- // Check for APP1 marker (contains EXIF)
- if (marker === 0xFFE1) {
- const segmentLength = dataView.getUint16(offset, false);
- offset += 2;
- // Check for "Exif" identifier
- if (
- dataView.getUint8(offset) === 0x45 && // 'E'
- dataView.getUint8(offset + 1) === 0x78 && // 'x'
- dataView.getUint8(offset + 2) === 0x69 && // 'i'
- dataView.getUint8(offset + 3) === 0x66 && // 'f'
- dataView.getUint8(offset + 4) === 0x00 && // null
- dataView.getUint8(offset + 5) === 0x00 // null
- ) {
- markerFound = true;
- offset += 6; // Skip "Exif\0\0"
- // Parse TIFF header
- const tiffOffset = offset;
- const littleEndian = dataView.getUint16(offset, false) === 0x4949; // 'II' = little endian, 'MM' = big endian
- if (dataView.getUint16(offset + 2, littleEndian) !== 0x002A) {
- throw new Error("Invalid TIFF data");
- }
- // Get offset to first IFD (Image File Directory)
- const ifdOffset = dataView.getUint32(offset + 4, littleEndian);
- // Parse the main IFD
- const mainIfdData = parseIFD(dataView, tiffOffset + ifdOffset, littleEndian, tiffOffset);
- exifData.main = mainIfdData.tags;
- // Check for ExifIFDPointer
- if (mainIfdData.tags.ExifIFDPointer) {
- const exifIfdOffset = mainIfdData.tags.ExifIFDPointer;
- const exifIfdData = parseIFD(dataView, tiffOffset + exifIfdOffset, littleEndian, tiffOffset);
- exifData.exif = exifIfdData.tags;
- delete exifData.main.ExifIFDPointer; // Remove the pointer from main tags
- }
- // Check for GPSInfoIFDPointer
- if (mainIfdData.tags.GPSInfoIFDPointer) {
- const gpsIfdOffset = mainIfdData.tags.GPSInfoIFDPointer;
- const gpsIfdData = parseIFD(dataView, tiffOffset + gpsIfdOffset, littleEndian, tiffOffset);
- exifData.gps = gpsIfdData.tags;
- delete exifData.main.GPSInfoIFDPointer; // Remove the pointer from main tags
- }
- break;
- } else {
- offset += segmentLength - 2; // Skip to next segment
- }
- } else if ((marker & 0xFF00) !== 0xFF00) {
- break; // Not a valid marker, exit
- } else {
- // Skip other segments
- const segmentLength = dataView.getUint16(offset, false);
- offset += segmentLength;
- }
- }
- if (!markerFound) {
- throw new Error("No EXIF data found");
- }
- resolve(exifData);
- } catch (error) {
- reject(error);
- }
- };
- reader.onerror = function() {
- reject(new Error("Error reading file"));
- };
- reader.readAsArrayBuffer(file);
- });
- }
- function parseIFD(dataView, ifdOffset, littleEndian, tiffOffset) {
- const entriesCount = dataView.getUint16(ifdOffset, littleEndian);
- let tags = {};
- for (let i = 0; i < entriesCount; i++) {
- const entryOffset = ifdOffset + 2 + (i * 12);
- const tagId = dataView.getUint16(entryOffset, littleEndian);
- const dataFormat = dataView.getUint16(entryOffset + 2, littleEndian);
- const numValues = dataView.getUint32(entryOffset + 4, littleEndian);
- const valueOffset = numValues * (formatMap[dataFormat]?.size || 0) <= 4
- ? entryOffset + 8
- : tiffOffset + dataView.getUint32(entryOffset + 8, littleEndian);
- // Get tag name
- const tagName = exifTagNames[tagId] || `Unknown (0x${tagId.toString(16).padStart(4, '0')})`;
- let tagValue;
- // Parse the value based on data format
- if (dataFormat === 1 || dataFormat === 7) { // BYTE or UNDEFINED
- if (numValues === 1) {
- tagValue = dataView.getUint8(valueOffset);
- } else {
- const values = [];
- for (let j = 0; j < numValues; j++) {
- values.push(dataView.getUint8(valueOffset + j));
- }
- tagValue = values;
- }
- } else if (dataFormat === 2) { // ASCII
- let str = '';
- for (let j = 0; j < numValues; j++) {
- const char = dataView.getUint8(valueOffset + j);
- if (char === 0) break; // Null-terminated
- str += String.fromCharCode(char);
- }
- tagValue = str;
- } else if (dataFormat === 3) { // SHORT
- if (numValues === 1) {
- tagValue = dataView.getUint16(valueOffset, littleEndian);
- } else {
- const values = [];
- for (let j = 0; j < numValues; j++) {
- values.push(dataView.getUint16(valueOffset + (j * 2), littleEndian));
- }
- tagValue = values;
- }
- } else if (dataFormat === 4) { // LONG
- if (numValues === 1) {
- tagValue = dataView.getUint32(valueOffset, littleEndian);
- } else {
- const values = [];
- for (let j = 0; j < numValues; j++) {
- values.push(dataView.getUint32(valueOffset + (j * 4), littleEndian));
- }
- tagValue = values;
- }
- } else if (dataFormat === 5) { // RATIONAL
- if (numValues === 1) {
- const numerator = dataView.getUint32(valueOffset, littleEndian);
- const denominator = dataView.getUint32(valueOffset + 4, littleEndian);
- tagValue = denominator === 0 ? 0 : numerator / denominator;
- } else {
- const values = [];
- for (let j = 0; j < numValues; j++) {
- const numerator = dataView.getUint32(valueOffset + (j * 8), littleEndian);
- const denominator = dataView.getUint32(valueOffset + (j * 8) + 4, littleEndian);
- values.push(denominator === 0 ? 0 : numerator / denominator);
- }
- tagValue = values;
- }
- } else if (dataFormat === 9) { // SLONG
- if (numValues === 1) {
- tagValue = dataView.getInt32(valueOffset, littleEndian);
- } else {
- const values = [];
- for (let j = 0; j < numValues; j++) {
- values.push(dataView.getInt32(valueOffset + (j * 4), littleEndian));
- }
- tagValue = values;
- }
- } else if (dataFormat === 10) { // SRATIONAL
- if (numValues === 1) {
- const numerator = dataView.getInt32(valueOffset, littleEndian);
- const denominator = dataView.getInt32(valueOffset + 4, littleEndian);
- tagValue = denominator === 0 ? 0 : numerator / denominator;
- } else {
- const values = [];
- for (let j = 0; j < numValues; j++) {
- const numerator = dataView.getInt32(valueOffset + (j * 8), littleEndian);
- const denominator = dataView.getInt32(valueOffset + (j * 8) + 4, littleEndian);
- values.push(denominator === 0 ? 0 : numerator / denominator);
- }
- tagValue = values;
- }
- } else {
- tagValue = `Unsupported format: ${dataFormat}`;
- }
- // Store tags using their name
- tags[tagName] = tagValue;
- }
- // Calculate next IFD offset
- const nextIfdOffset = dataView.getUint32(ifdOffset + 2 + (entriesCount * 12), littleEndian);
- return {
- tags: tags,
- nextIfdOffset: nextIfdOffset ? tiffOffset + nextIfdOffset : 0
- };
- }
- function displayExifData(exifData) {
- let userCommentValue = null;
- // Look for UserComment in exif section (most likely location)
- if (exifData.exif && exifData.exif.UserComment) {
- userCommentValue = formatValue(exifData.exif.UserComment, "UserComment");
- }
- // Check main section if not found in exif section
- else if (exifData.main && exifData.main.UserComment) {
- userCommentValue = formatValue(exifData.main.UserComment, "UserComment");
- }
- // Check GPS section if not found elsewhere (unlikely but being thorough)
- else if (exifData.gps && exifData.gps.UserComment) {
- userCommentValue = formatValue(exifData.gps.UserComment, "UserComment");
- }
- if (userCommentValue) {
- exifDataContainer.innerHTML = `<div class="exif-value">${userCommentValue}</div>`;
- // Set the extracted value in the userPromptEl textarea
- const userPromptEl = document.getElementById("userPromptEl");
- if (userPromptEl) {
- userPromptEl.value = userCommentValue;
- // Trigger the form submission to generate the image
- setTimeout(() => {
- const generateBtn = document.getElementById("generateBtn");
- if (generateBtn) {
- generateBtn.click();
- }
- }, 500);
- }
- } else {
- exifDataContainer.innerHTML = '<p>No UserComment tag found in this image.</p>';
- }
- exifDataContainer.hidden = false;
- }
- function formatTagsToHtml(tags) {
- let html = '';
- for (const [tag, value] of Object.entries(tags)) {
- html += '<div class="exif-item">';
- html += `<div class="exif-tag">${tag}:</div>`;
- html += `<div class="exif-value">${formatValue(value, tag)}</div>`;
- html += '</div>';
- }
- return html;
- }
- function formatValue(value, tagName) {
- // Special handling for UserComment to convert decimal to ASCII
- if (tagName === "UserComment" && Array.isArray(value)) {
- // Convert byte array to ASCII string
- let commentText = "";
- // Check if there's a character code identifier (first 8 bytes)
- let charCodeIdentifier = "";
- if (value.length >= 8) {
- charCodeIdentifier = String.fromCharCode(...value.slice(0, 8));
- }
- // If we have a recognizable character code, skip the first 8 bytes
- if (charCodeIdentifier === "ASCII\0\0\0" ||
- charCodeIdentifier === "UNICODE\0" ||
- charCodeIdentifier === "JIS\0\0\0\0\0" ||
- value.slice(0, 8).every(byte => byte === 0)) {
- for (let i = 8; i < value.length; i++) {
- if (value[i] !== 0) { // Skip null bytes
- commentText += String.fromCharCode(value[i]);
- }
- }
- } else {
- // If no recognizable character code, convert all bytes
- for (let i = 0; i < value.length; i++) {
- if (value[i] !== 0) { // Skip null bytes
- commentText += String.fromCharCode(value[i]);
- }
- }
- }
- try {
- // Parse the comment text as JSON and extract the prompt field
- let jsonData = JSON.parse(commentText);
- return jsonData.prompt || "No prompt field found in JSON";
- } catch (error) {
- // If it's not valid JSON, return an error message
- return `Invalid JSON: ${commentText}`;
- }
- }
- if (Array.isArray(value)) {
- if (value.length > 10) {
- return `[${value.slice(0, 10).join(', ')}, ...]`;
- }
- return `[${value.join(', ')}]`;
- } else if (typeof value === 'number') {
- // Format numbers to be more readable
- if (Number.isInteger(value)) {
- return value.toString();
- } else {
- return value.toFixed(4);
- }
- }
- return value;
- }
- </script>
- </body>
- </html>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
- <script>
- // Global variables to store images
- let uploadedImages = [];
- let processedImages = [];
- // DOM elements
- const dropArea = dropAreaEl;
- const previewContainer = previewContainerEl;
- const sizeSelect = sizeSelectEl;
- const methodSelect = methodSelectEl;
- const effectsSelect = effectsSelectEl;
- const speedSelect = speedSelectEl;
- // Set up event listeners
- browseBtn.addEventListener('click', () => fileInputEl.click());
- fileInputEl.addEventListener('change', handleFileSelect);
- processBtn.addEventListener('click', processImages);
- downloadAllBtn.addEventListener('click', downloadAllImages);
- sizeSelect.addEventListener('change', updateFormatOptions);
- // Set up drag and drop
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
- dropArea.addEventListener(eventName, preventDefaults, false);
- });
- function preventDefaults(e) {
- e.preventDefault();
- e.stopPropagation();
- }
- ['dragenter', 'dragover'].forEach(eventName => {
- dropArea.addEventListener(eventName, () => {
- dropArea.style.borderColor = '#4CAF50';
- dropArea.style.backgroundColor = 'rgba(76, 175, 80, 0.1)';
- }, false);
- });
- ['dragleave', 'drop'].forEach(eventName => {
- dropArea.addEventListener(eventName, () => {
- dropArea.style.borderColor = '#ccc';
- dropArea.style.backgroundColor = '';
- }, false);
- });
- dropArea.addEventListener('drop', handleDrop, false);
- function handleDrop(e) {
- const dt = e.dataTransfer;
- const files = dt.files;
- handleFiles(files);
- }
- // Handle the file selection
- function handleFileSelect(e) {
- const files = e.target.files;
- handleFiles(files);
- }
- function handleFiles(files) {
- if (files.length === 0) return;
- Array.from(files).forEach(file => {
- if (!file.type.match('image.*')) return;
- // Check if image already uploaded
- if (uploadedImages.some(img => img.name === file.name && img.size === file.size)) {
- return; // Skip duplicates
- }
- const reader = new FileReader();
- reader.onload = function(e) {
- const img = new Image();
- img.src = e.target.result;
- img.onload = function() {
- uploadedImages.push({
- name: file.name,
- size: file.size,
- type: file.type,
- dataUrl: e.target.result,
- width: img.width,
- height: img.height
- });
- updatePreview();
- updateProcessButton();
- };
- };
- reader.readAsDataURL(file);
- });
- }
- function updatePreview() {
- previewContainer.innerHTML = '';
- uploadedImages.forEach((img, index) => {
- const previewItem = document.createElement('div');
- previewItem.style.cssText = 'position: relative; width: 100px; height: 100px; border-radius: 4px; overflow: hidden;';
- const imgEl = document.createElement('img');
- imgEl.src = img.dataUrl;
- imgEl.alt = img.name;
- imgEl.style.cssText = 'width: 100%; height: 100%; object-fit: cover;';
- const removeBtn = document.createElement('button');
- removeBtn.style.cssText = 'position: absolute; top: 5px; right: 5px; background: rgba(0, 0, 0, 0.5); color: white; border: none; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; cursor: pointer;';
- removeBtn.textContent = '×';
- removeBtn.onclick = () => removeImage(index);
- previewItem.appendChild(imgEl);
- previewItem.appendChild(removeBtn);
- previewContainer.appendChild(previewItem);
- });
- }
- function removeImage(index) {
- uploadedImages.splice(index, 1);
- updatePreview();
- updateProcessButton();
- }
- function updateProcessButton() {
- processBtn.disabled = uploadedImages.length === 0;
- }
- // Function to update format options based on selected size
- function updateFormatOptions() {
- const scaleValue = sizeSelect.value;
- const formatSelect = imageFormatSelectEl;
- // Get all format options
- const options = formatSelect.querySelectorAll('option');
- // Store current selected format
- const currentFormat = formatSelect.value;
- // Check if the selected scale is 300% or 400% or one of the large fixed sizes
- if (parseFloat(scaleValue) >= 3.0 || scaleValue === '3840x2160' || scaleValue === '7680x4320') {
- // Only allow jpg and webp for large scales
- for (const option of options) {
- if (option.value === 'jpg' || option.value === 'webp') {
- option.disabled = false;
- } else {
- option.disabled = true;
- }
- }
- // If current format is not allowed, change to jpg
- if (currentFormat !== 'jpg' && currentFormat !== 'webp') {
- formatSelect.value = 'jpg';
- }
- } else {
- // Enable all formats for other scales
- for (const option of options) {
- option.disabled = false;
- }
- }
- }
- // Process the images with the selected options
- function processImages() {
- if (uploadedImages.length === 0) return;
- const scaleValue = sizeSelect.value;
- const method = methodSelect.value;
- const effect = effectsSelect.value;
- const speed = speedSelect.value;
- processBtn.disabled = true;
- processBtn.textContent = '⏳ Processing...';
- // Clear previous results
- processedImages = [];
- galleryEl.innerHTML = '';
- // Process each image (with small delay to allow UI to update)
- setTimeout(() => {
- const promises = uploadedImages.map(img => processImage(img, scaleValue, method, effect, speed));
- Promise.all(promises).then(() => {
- processBtn.textContent = 'Process Images';
- processBtn.disabled = false;
- resultsCtn.hidden = false;
- // Display processed images in gallery
- displayGallery();
- // Update format options based on selected size
- updateFormatOptions();
- });
- }, 100);
- }
- function processImage(img, scaleValue, method, effect, speed) {
- return new Promise((resolve) => {
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
- // Calculate new dimensions
- let newWidth, newHeight;
- if (scaleValue.includes('x')) {
- // Fixed size format: widthxheight
- const dimensions = scaleValue.split('x');
- const targetWidth = parseInt(dimensions[0]);
- const targetHeight = parseInt(dimensions[1]);
- // Calculate scale factors for width and height
- const widthScale = targetWidth / img.width;
- const heightScale = targetHeight / img.height;
- // Use the smaller scale to ensure the image fits within the target dimensions
- // while maintaining aspect ratio
- const scale = Math.min(widthScale, heightScale);
- newWidth = Math.round(img.width * scale);
- newHeight = Math.round(img.height * scale);
- } else {
- // Percentage scale
- const scale = parseFloat(scaleValue);
- newWidth = Math.round(img.width * scale);
- newHeight = Math.round(img.height * scale);
- }
- // Create a temporary canvas for pre-processing
- const tempCanvas = document.createElement('canvas');
- const tempCtx = tempCanvas.getContext('2d');
- tempCanvas.width = img.width;
- tempCanvas.height = img.height;
- // Load the image
- const imgEl = new Image();
- imgEl.onload = function() {
- // Apply rendering speed settings for resource usage control
- const speedSettings = {
- 'slow': { chunkSize: 50, delay: 100 }, // 25% resources
- 'medium': { chunkSize: 100, delay: 20 }, // 50% resources
- 'fast': { chunkSize: 500, delay: 1 } // 90% resources
- };
- const { chunkSize, delay } = speedSettings[speed] || speedSettings.medium;
- // First, draw the original image on the temp canvas
- tempCtx.drawImage(imgEl, 0, 0);
- // Apply pre-processing effect if selected (with throttling)
- if (effect !== 'none') {
- // Process the effect with throttling based on speed
- applyEffectWithThrottling(tempCanvas, tempCtx, effect, speed, () => {
- // After effect is applied, resize the image
- // Set final canvas size
- canvas.width = newWidth;
- canvas.height = newHeight;
- // Resize the pre-processed image using the selected method
- applyResizeMethodWithThrottling(tempCanvas, canvas, ctx, method, scaleValue, speed, () => {
- // Get the processed image data
- const processedDataUrl = canvas.toDataURL(img.type || 'image/jpeg');
- // Create processed image object
- const processedImage = {
- name: img.name,
- originalSize: `${img.width}x${img.height}`,
- newSize: `${newWidth}x${newHeight}`,
- scale: scaleValue.includes('x') ? scaleValue : Math.round(parseFloat(scaleValue) * 100) + '%',
- method: getMethodName(method),
- effect: getEffectName(effect),
- dataUrl: processedDataUrl
- };
- // Store the processed image
- processedImages.push(processedImage);
- // Resolve with the processed image
- resolve(processedImage);
- });
- });
- } else {
- // No effect to apply, just resize
- // Set final canvas size
- canvas.width = newWidth;
- canvas.height = newHeight;
- // Resize the pre-processed image using the selected method
- applyResizeMethodWithThrottling(tempCanvas, canvas, ctx, method, scaleValue, speed, () => {
- // Get the processed image data
- const processedDataUrl = canvas.toDataURL(img.type || 'image/jpeg');
- // Create processed image object
- const processedImage = {
- name: img.name,
- originalSize: `${img.width}x${img.height}`,
- newSize: `${newWidth}x${newHeight}`,
- scale: scaleValue.includes('x') ? scaleValue : Math.round(parseFloat(scaleValue) * 100) + '%',
- method: getMethodName(method),
- effect: getEffectName(effect),
- dataUrl: processedDataUrl
- };
- // Store the processed image
- processedImages.push(processedImage);
- // Resolve with the processed image
- resolve(processedImage);
- });
- }
- };
- imgEl.src = img.dataUrl;
- });
- }
- // Apply effect with throttling based on selected speed
- function applyEffectWithThrottling(canvas, ctx, effect, speed, callback) {
- const width = canvas.width;
- const height = canvas.height;
- const imageData = ctx.getImageData(0, 0, width, height);
- // Determine chunk size and delay based on speed
- const speedSettings = {
- 'slow': { chunkSize: Math.floor(height * 0.05), delay: 20 }, // Process 5% of image height at a time
- 'medium': { chunkSize: Math.floor(height * 0.2), delay: 10 }, // Process 20% of image height at a time
- 'fast': { chunkSize: height, delay: 0 } // Process entire image at once
- };
- const { chunkSize, delay } = speedSettings[speed] || speedSettings.medium;
- // For some effects that require the entire image data, we can't chunk
- // process them, so we need to apply them directly
- const globalEffects = [
- 'blur-light', 'blur-strong', 'sharpen-light', 'sharpen-strong',
- 'edge-enhance', 'emboss', 'noise-reduction'
- ];
- if (globalEffects.includes(effect)) {
- // For effects that require whole-image processing
- setTimeout(() => {
- applyEffect(canvas, ctx, effect);
- callback();
- }, delay);
- } else {
- // For effects that can be applied line by line
- let currentRow = 0;
- function processChunk() {
- if (currentRow >= height) {
- ctx.putImageData(imageData, 0, 0);
- callback();
- return;
- }
- const endRow = Math.min(currentRow + chunkSize, height);
- // Extract the chunk of data that needs processing
- const chunkImageData = ctx.getImageData(0, currentRow, width, endRow - currentRow);
- // Apply the effect to this chunk
- switch (effect) {
- case 'grayscale':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- const avg = (chunkImageData.data[i] + chunkImageData.data[i + 1] + chunkImageData.data[i + 2]) / 3;
- chunkImageData.data[i] = avg;
- chunkImageData.data[i + 1] = avg;
- chunkImageData.data[i + 2] = avg;
- }
- break;
- case 'sepia':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- const r = chunkImageData.data[i];
- const g = chunkImageData.data[i + 1];
- const b = chunkImageData.data[i + 2];
- chunkImageData.data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
- chunkImageData.data[i + 1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
- chunkImageData.data[i + 2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
- }
- break;
- case 'invert':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- chunkImageData.data[i] = 255 - chunkImageData.data[i];
- chunkImageData.data[i + 1] = 255 - chunkImageData.data[i + 1];
- chunkImageData.data[i + 2] = 255 - chunkImageData.data[i + 2];
- }
- break;
- case 'brighten':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- chunkImageData.data[i] = Math.min(255, chunkImageData.data[i] * 1.2);
- chunkImageData.data[i + 1] = Math.min(255, chunkImageData.data[i + 1] * 1.2);
- chunkImageData.data[i + 2] = Math.min(255, chunkImageData.data[i + 2] * 1.2);
- }
- break;
- case 'darken':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- chunkImageData.data[i] = chunkImageData.data[i] * 0.8;
- chunkImageData.data[i + 1] = chunkImageData.data[i + 1] * 0.8;
- chunkImageData.data[i + 2] = chunkImageData.data[i + 2] * 0.8;
- }
- break;
- case 'increase-contrast':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- for (let j = 0; j < 3; j++) {
- chunkImageData.data[i + j] = ((chunkImageData.data[i + j] / 255 - 0.5) * 1.4 + 0.5) * 255;
- if (chunkImageData.data[i + j] < 0) chunkImageData.data[i + j] = 0;
- if (chunkImageData.data[i + j] > 255) chunkImageData.data[i + j] = 255;
- }
- }
- break;
- case 'decrease-contrast':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- for (let j = 0; j < 3; j++) {
- chunkImageData.data[i + j] = ((chunkImageData.data[i + j] / 255 - 0.5) * 0.7 + 0.5) * 255;
- if (chunkImageData.data[i + j] < 0) chunkImageData.data[i + j] = 0;
- if (chunkImageData.data[i + j] > 255) chunkImageData.data[i + j] = 255;
- }
- }
- break;
- case 'posterize':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- const levels = 5;
- chunkImageData.data[i] = Math.floor(chunkImageData.data[i] / 255 * levels) / levels * 255;
- chunkImageData.data[i + 1] = Math.floor(chunkImageData.data[i + 1] / 255 * levels) / levels * 255;
- chunkImageData.data[i + 2] = Math.floor(chunkImageData.data[i + 2] / 255 * levels) / levels * 255;
- }
- break;
- }
- // Put the processed chunk back
- ctx.putImageData(chunkImageData, 0, currentRow);
- // Update row position
- currentRow = endRow;
- // Schedule the next chunk with delay
- setTimeout(processChunk, delay);
- }
- // Start processing chunks
- processChunk();
- }
- }
- // Apply resize method with throttling based on selected speed
- function applyResizeMethodWithThrottling(img, canvas, ctx, method, scaleValue, speed, callback) {
- // For simple methods that don't require special throttling, use the browser's built-in scaling
- if (method === 'pixelated' || method === 'bilinear' || method === 'bicubic') {
- ctx.imageSmoothingEnabled = method !== 'pixelated';
- if (method === 'bilinear') ctx.imageSmoothingQuality = 'low';
- if (method === 'bicubic') ctx.imageSmoothingQuality = 'medium';
- // Draw the image according to selected speed
- if (speed === 'fast') {
- // For fast mode, draw all at once
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- callback();
- } else {
- // For slow/medium modes, break rendering into chunks
- const speedSettings = {
- 'slow': { chunkSize: Math.floor(canvas.height * 0.1), delay: 50 },
- 'medium': { chunkSize: Math.floor(canvas.height * 0.3), delay: 10 }
- };
- const { chunkSize, delay } = speedSettings[speed] || speedSettings.medium;
- let currentRow = 0;
- function drawChunk() {
- if (currentRow >= canvas.height) {
- callback();
- return;
- }
- const chunkHeight = Math.min(chunkSize, canvas.height - currentRow);
- const sourceYRatio = img.height / canvas.height;
- // Draw a slice of the image
- ctx.drawImage(
- img,
- 0, Math.floor(currentRow * sourceYRatio), // Source x, y
- img.width, Math.ceil(chunkHeight * sourceYRatio), // Source width, height
- 0, currentRow, // Destination x, y
- canvas.width, chunkHeight // Destination width, height
- );
- // Move to next chunk
- currentRow += chunkHeight;
- // Schedule next chunk with delay
- setTimeout(drawChunk, delay);
- }
- // Start drawing chunks
- drawChunk();
- }
- } else {
- // For advanced methods, use existing implementation with throttling
- if (speed === 'fast') {
- // Fast mode - process all at once
- applyResizeMethod(img, canvas, ctx, method, scaleValue);
- callback();
- } else {
- // Add a small artificial delay to simulate resource throttling
- setTimeout(() => {
- applyResizeMethod(img, canvas, ctx, method, scaleValue);
- callback();
- }, speed === 'slow' ? 200 : 50);
- }
- }
- }
- // Apply the selected pre-processing effect to the canvas
- function applyEffect(canvas, ctx, effect) {
- const width = canvas.width;
- const height = canvas.height;
- const imageData = ctx.getImageData(0, 0, width, height);
- const data = imageData.data;
- switch (effect) {
- case 'blur-light':
- applyBoxBlur(imageData, width, height, 1);
- break;
- case 'blur-strong':
- applyBoxBlur(imageData, width, height, 3);
- break;
- case 'sharpen-light':
- applyConvolution(imageData, width, height, [
- 0, -0.5, 0,
- -0.5, 3, -0.5,
- 0, -0.5, 0
- ]);
- break;
- case 'sharpen-strong':
- applyConvolution(imageData, width, height, [
- -0.5, -1, -0.5,
- -1, 7, -1,
- -0.5, -1, -0.5
- ]);
- break;
- case 'edge-enhance':
- applyConvolution(imageData, width, height, [
- 0, -1, 0,
- -1, 5, -1,
- 0, -1, 0
- ]);
- break;
- case 'emboss':
- applyConvolution(imageData, width, height, [
- -2, -1, 0,
- -1, 1, 1,
- 0, 1, 2
- ]);
- break;
- case 'grayscale':
- for (let i = 0; i < data.length; i += 4) {
- const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
- data[i] = avg;
- data[i + 1] = avg;
- data[i + 2] = avg;
- }
- break;
- case 'sepia':
- for (let i = 0; i < data.length; i += 4) {
- const r = data[i];
- const g = data[i + 1];
- const b = data[i + 2];
- data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
- data[i + 1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
- data[i + 2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
- }
- break;
- case 'invert':
- for (let i = 0; i < data.length; i += 4) {
- data[i] = 255 - data[i];
- data[i + 1] = 255 - data[i + 1];
- data[i + 2] = 255 - data[i + 2];
- }
- break;
- case 'brighten':
- for (let i = 0; i < data.length; i += 4) {
- data[i] = Math.min(255, data[i] * 1.2);
- data[i + 1] = Math.min(255, data[i + 1] * 1.2);
- data[i + 2] = Math.min(255, data[i + 2] * 1.2);
- }
- break;
- case 'darken':
- for (let i = 0; i < data.length; i += 4) {
- data[i] = data[i] * 0.8;
- data[i + 1] = data[i + 1] * 0.8;
- data[i + 2] = data[i + 2] * 0.8;
- }
- break;
- case 'increase-contrast':
- for (let i = 0; i < data.length; i += 4) {
- // Apply simple contrast adjustment
- for (let j = 0; j < 3; j++) {
- data[i + j] = ((data[i + j] / 255 - 0.5) * 1.4 + 0.5) * 255;
- if (data[i + j] < 0) data[i + j] = 0;
- if (data[i + j] > 255) data[i + j] = 255;
- }
- }
- break;
- case 'decrease-contrast':
- for (let i = 0; i < data.length; i += 4) {
- // Apply simple contrast reduction
- for (let j = 0; j < 3; j++) {
- data[i + j] = ((data[i + j] / 255 - 0.5) * 0.7 + 0.5) * 255;
- if (data[i + j] < 0) data[i + j] = 0;
- if (data[i + j] > 255) data[i + j] = 255;
- }
- }
- break;
- case 'noise-reduction':
- applyMedianFilter(imageData, width, height);
- break;
- case 'posterize':
- for (let i = 0; i < data.length; i += 4) {
- const levels = 5;
- data[i] = Math.floor(data[i] / 255 * levels) / levels * 255;
- data[i + 1] = Math.floor(data[i + 1] / 255 * levels) / levels * 255;
- data[i + 2] = Math.floor(data[i + 2] / 255 * levels) / levels * 255;
- }
- break;
- }
- ctx.putImageData(imageData, 0, 0);
- }
- // Apply convolution (used for many effects like blur, sharpen, etc.)
- function applyConvolution(imageData, width, height, kernel) {
- const data = imageData.data;
- const buff = new Uint8ClampedArray(data);
- const kSize = Math.sqrt(kernel.length);
- const kRadius = Math.floor(kSize / 2);
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- const idx = (y * width + x) * 4;
- let r = 0, g = 0, b = 0;
- // Apply kernel
- for (let ky = 0; ky < kSize; ky++) {
- for (let kx = 0; kx < kSize; kx++) {
- const kIdx = ky * kSize + kx;
- const px = Math.min(width - 1, Math.max(0, x + kx - kRadius));
- const py = Math.min(height - 1, Math.max(0, y + ky - kRadius));
- const pIdx = (py * width + px) * 4;
- r += buff[pIdx] * kernel[kIdx];
- g += buff[pIdx + 1] * kernel[kIdx];
- b += buff[pIdx + 2] * kernel[kIdx];
- }
- }
- // Set new values
- data[idx] = Math.min(255, Math.max(0, r));
- data[idx + 1] = Math.min(255, Math.max(0, g));
- data[idx + 2] = Math.min(255, Math.max(0, b));
- }
- }
- }
- // Box blur implementation
- function applyBoxBlur(imageData, width, height, radius) {
- const data = imageData.data;
- const buff = new Uint8ClampedArray(data);
- // Horizontal pass
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- let r = 0, g = 0, b = 0, count = 0;
- for (let i = Math.max(0, x - radius); i <= Math.min(width - 1, x + radius); i++) {
- const idx = (y * width + i) * 4;
- r += buff[idx];
- g += buff[idx + 1];
- b += buff[idx + 2];
- count++;
- }
- const idx = (y * width + x) * 4;
- data[idx] = r / count;
- data[idx + 1] = g / count;
- data[idx + 2] = b / count;
- }
- }
- // Copy for vertical pass
- for (let i = 0; i < data.length; i++) {
- buff[i] = data[i];
- }
- // Vertical pass
- for (let x = 0; x < width; x++) {
- for (let y = 0; y < height; y++) {
- let r = 0, g = 0, b = 0, count = 0;
- for (let j = Math.max(0, y - radius); j <= Math.min(height - 1, y + radius); j++) {
- const idx = (j * width + x) * 4;
- r += buff[idx];
- g += buff[idx + 1];
- b += buff[idx + 2];
- count++;
- }
- const idx = (y * width + x) * 4;
- data[idx] = r / count;
- data[idx + 1] = g / count;
- data[idx + 2] = b / count;
- }
- }
- }
- // Median filter (good for noise reduction)
- function applyMedianFilter(imageData, width, height) {
- const data = imageData.data;
- const buff = new Uint8ClampedArray(data);
- const radius = 1;
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- const idx = (y * width + x) * 4;
- // For each channel (R,G,B)
- for (let c = 0; c < 3; c++) {
- let values = [];
- // Gather values in the neighborhood
- for (let dy = -radius; dy <= radius; dy++) {
- for (let dx = -radius; dx <= radius; dx++) {
- const nx = Math.min(width - 1, Math.max(0, x + dx));
- const ny = Math.min(height - 1, Math.max(0, y + dy));
- const nIdx = (ny * width + nx) * 4 + c;
- values.push(buff[nIdx]);
- }
- }
- // Sort and find median
- values.sort((a, b) => a - b);
- data[idx + c] = values[Math.floor(values.length / 2)];
- }
- }
- }
- }
- function getMethodName(method) {
- const methods = {
- 'pixelated': 'Nearest Neighbor',
- 'bilinear': 'Bilinear',
- 'bicubic': 'Bicubic',
- 'lanczos': 'Lanczos',
- 'hermite': 'Hermite',
- 'mitchell': 'Mitchell-Netravali',
- 'catmull-rom': 'Catmull-Rom',
- 'box': 'Box',
- 'hamming': 'Hamming',
- 'gaussian': 'Gaussian'
- };
- return methods[method] || method;
- }
- function getEffectName(effect) {
- const effects = {
- 'none': 'None',
- 'blur-light': 'Fine Blur',
- 'blur-strong': 'Strong Blur',
- 'sharpen-light': 'Light Sharpen',
- 'sharpen-strong': 'Strong Sharpen',
- 'edge-enhance': 'Edge Enhancement',
- 'emboss': 'Emboss',
- 'grayscale': 'Grayscale',
- 'sepia': 'Sepia',
- 'invert': 'Invert Colors',
- 'brighten': 'Brighten',
- 'darken': 'Darken',
- 'increase-contrast': 'Increased Contrast',
- 'decrease-contrast': 'Decreased Contrast',
- 'noise-reduction': 'Noise Reduction',
- 'posterize': 'Posterize'
- };
- return effects[effect] || effect;
- }
- function applyResizeMethod(img, canvas, ctx, method, scaleValue) {
- // Set default quality
- ctx.imageSmoothingEnabled = true;
- switch (method) {
- case 'pixelated':
- ctx.imageSmoothingEnabled = false;
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- break;
- case 'bilinear':
- ctx.imageSmoothingQuality = 'low';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- break;
- case 'bicubic':
- ctx.imageSmoothingQuality = 'medium';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- break;
- case 'lanczos':
- ctx.imageSmoothingQuality = 'high';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- // Apply sharpening filter for lanczos-like effect
- applyLanczosSharpening(canvas, ctx);
- break;
- case 'hermite':
- // Custom hermite resampling implementation
- hermiteResize(canvas, ctx, img, canvas.width, canvas.height);
- break;
- case 'mitchell':
- // Mitchell-Netravali filter - similar to bicubic but with less ringing
- ctx.imageSmoothingQuality = 'high';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- // Apply slight sharpening for Mitchell-like effect
- applyConvolution(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, [
- 0, -0.15, 0,
- -0.15, 1.6, -0.15,
- 0, -0.15, 0
- ]);
- ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
- break;
- case 'catmull-rom':
- // Catmull-Rom spline filter - preserves edges better than bicubic
- ctx.imageSmoothingQuality = 'high';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- // Apply edge-preserving enhancement for Catmull-Rom-like effect
- applyConvolution(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, [
- 0, -0.25, 0,
- -0.25, 2, -0.25,
- 0, -0.25, 0
- ]);
- ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
- break;
- case 'box':
- // Box filter - simple averaging (faster but blurrier)
- ctx.imageSmoothingQuality = 'low';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- applyBoxBlur(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, 0.5);
- ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
- break;
- case 'hamming':
- // Hamming filter - reduces aliasing
- ctx.imageSmoothingQuality = 'medium';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- // Small blur + sharpen to simulate Hamming window
- applyBoxBlur(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, 0.7);
- ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
- applyConvolution(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, [
- 0, -0.2, 0,
- -0.2, 1.8, -0.2,
- 0, -0.2, 0
- ]);
- ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
- break;
- case 'gaussian':
- // Gaussian filter - smooth with soft edges
- ctx.imageSmoothingQuality = 'medium';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- // Apply Gaussian-like blur
- applyConvolution(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, [
- 1/16, 2/16, 1/16,
- 2/16, 4/16, 2/16,
- 1/16, 2/16, 1/16
- ]);
- ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
- break;
- default:
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- }
- }
- // Apply sharpening filter to simulate Lanczos
- function applyLanczosSharpening(canvas, ctx) {
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
- const data = imageData.data;
- const width = canvas.width;
- const height = canvas.height;
- // Create a copy of the image data
- const original = new Uint8ClampedArray(data);
- // Apply convolution
- for (let y = 1; y < height-1; y++) {
- for (let x = 1; x < width-1; x++) {
- const idx = (y * width + x) * 4;
- // Apply sharpening for RGB channels
- for (let c = 0; c < 3; c++) {
- const val = 5 * original[idx + c]
- - original[idx - 4 + c]
- - original[idx + 4 + c]
- - original[idx - width * 4 + c]
- - original[idx + width * 4 + c];
- data[idx + c] = Math.min(255, Math.max(0, val));
- }
- }
- }
- ctx.putImageData(imageData, 0, 0);
- }
- // Hermite resampling method
- function hermiteResize(canvas, ctx, img, width, height) {
- const widthSource = img.width;
- const heightSource = img.height;
- // Create temporary canvas to hold the original image
- const tempCanvas = document.createElement('canvas');
- tempCanvas.width = widthSource;
- tempCanvas.height = heightSource;
- const tempCtx = tempCanvas.getContext('2d');
- tempCtx.drawImage(img, 0, 0);
- const imgData = tempCtx.getImageData(0, 0, widthSource, heightSource).data;
- // Set target canvas size
- canvas.width = width;
- canvas.height = height;
- // Create output data
- const outData = ctx.createImageData(width, height);
- const data = outData.data;
- // Ratio between original dimensions and new dimensions
- const ratioWidth = widthSource / width;
- const ratioHeight = heightSource / height;
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- // Get calculated position in source
- let srcX = x * ratioWidth;
- let srcY = y * ratioHeight;
- // Source coordinates
- let srcX0 = Math.floor(srcX);
- let srcY0 = Math.floor(srcY);
- let srcX1 = Math.min(srcX0 + 1, widthSource - 1);
- let srcY1 = Math.min(srcY0 + 1, heightSource - 1);
- // Weights
- let xWeight = srcX - srcX0;
- let yWeight = srcY - srcY0;
- // Interpolate colors
- let i = (y * width + x) * 4;
- for (let c = 0; c < 4; c++) {
- let idx00 = (srcY0 * widthSource + srcX0) * 4 + c;
- let idx10 = (srcY0 * widthSource + srcX1) * 4 + c;
- let idx01 = (srcY1 * widthSource + srcX0) * 4 + c;
- let idx11 = (srcY1 * widthSource + srcX1) * 4 + c;
- // Hermite interpolation
- data[i + c] = Math.round(
- imgData[idx00] * (1 - xWeight) * (1 - yWeight) +
- imgData[idx10] * xWeight * (1 - yWeight) +
- imgData[idx01] * (1 - xWeight) * yWeight +
- imgData[idx11] * xWeight * yWeight
- );
- }
- }
- }
- ctx.putImageData(outData, 0, 0);
- }
- // Helper function to get format info
- function getFormatInfo(formatValue) {
- const formatMap = {
- 'png': { mime: 'image/png', extension: 'png' },
- 'jpg': { mime: 'image/jpeg', extension: 'jpg' },
- 'jpeg': { mime: 'image/jpeg', extension: 'jpeg' },
- 'webp': { mime: 'image/webp', extension: 'webp' }
- };
- return formatMap[formatValue] || formatMap['png']; // Default to PNG
- }
- // Convert image to selected format
- function convertImageFormat(dataUrl, formatValue) {
- return new Promise((resolve) => {
- const formatInfo = getFormatInfo(formatValue);
- const img = new Image();
- img.onload = function() {
- const canvas = document.createElement('canvas');
- canvas.width = img.width;
- canvas.height = img.height;
- const ctx = canvas.getContext('2d');
- // If it's a PNG, we need to draw on a white background for JPG/WEBP conversion
- if (formatInfo.mime !== 'image/png') {
- ctx.fillStyle = 'white';
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- }
- ctx.drawImage(img, 0, 0);
- // Convert to the selected format (use 0.9 quality for lossy formats)
- const newDataUrl = canvas.toDataURL(formatInfo.mime, 0.9);
- resolve({ dataUrl: newDataUrl, formatInfo });
- };
- img.src = dataUrl;
- });
- }
- function displayGallery() {
- galleryEl.innerHTML = '';
- processedImages.forEach((img, index) => {
- const galleryItem = document.createElement('div');
- galleryItem.style.cssText = 'border: 1px solid #ddd; border-radius: 4px; overflow: hidden; display: flex; flex-direction: column;';
- const imgEl = document.createElement('img');
- imgEl.src = img.dataUrl;
- imgEl.alt = img.name;
- imgEl.style.cssText = 'width: 100%; max-height: 200px; object-fit: contain;';
- const infoEl = document.createElement('div');
- infoEl.style.cssText = 'padding: 10px; background: #f9f9f9; font-size: 12px;';
- infoEl.innerHTML = `
- <div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${img.name}</div>
- <div>Original: ${img.originalSize}</div>
- <div>New: ${img.newSize} (${img.scale})</div>
- <div>Method: ${img.method}</div>
- <div>Effect: ${img.effect}</div>
- `;
- const downloadBtn = document.createElement('div');
- downloadBtn.style.cssText = 'background: #2196F3; color: white; padding: 8px; text-align: center; cursor: pointer;';
- downloadBtn.textContent = 'Download';
- downloadBtn.onclick = () => downloadSingleImage(img);
- galleryItem.appendChild(imgEl);
- galleryItem.appendChild(infoEl);
- galleryItem.appendChild(downloadBtn);
- galleryEl.appendChild(galleryItem);
- });
- }
- async function downloadSingleImage(img) {
- const scaleValue = sizeSelect.value;
- let format = imageFormatSelectEl.value;
- // Ensure format is valid for large scales
- if (parseFloat(scaleValue) >= 3.0 || scaleValue === '3840x2160' || scaleValue === '7680x4320') {
- if (format !== 'jpg' && format !== 'webp') {
- format = 'jpg';
- imageFormatSelectEl.value = 'jpg';
- }
- }
- const { dataUrl, formatInfo } = await convertImageFormat(img.dataUrl, format);
- const a = document.createElement('a');
- a.href = dataUrl;
- a.download = `resized_${img.scale.toString().replace('x', '_')}_${img.name.split('.')[0]}.${formatInfo.extension}`;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- }
- async function downloadAllImages() {
- if (processedImages.length === 0) return;
- downloadAllBtn.textContent = '⏳ Downloading...';
- downloadAllBtn.disabled = true;
- const scaleValue = sizeSelect.value;
- let format = imageFormatSelectEl.value;
- // Ensure format is valid for large scales
- if (parseFloat(scaleValue) >= 3.0 || scaleValue === '3840x2160' || scaleValue === '7680x4320') {
- if (format !== 'jpg' && format !== 'webp') {
- format = 'jpg';
- imageFormatSelectEl.value = 'jpg';
- }
- }
- // Download each image individually
- for (const img of processedImages) {
- await downloadSingleImage(img);
- }
- downloadAllBtn.textContent = 'Download All';
- downloadAllBtn.disabled = false;
- }
- // Initialize format options based on selected size
- updateFormatOptions();
- </script>
- <br /><body>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement