Advertisement
Jabber666

Desktop Flux Img Gen With Upscaler

May 3rd, 2025 (edited)
252
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 95.71 KB | Help | 0 0
  1. // A 100% FREE DESKTOP HTML IMAGE GENERATOR USING POLLINATIONS.AI
  2. // BY UFO USING GPT4 ON PERCHANCE.ORG
  3. <style>
  4.   @keyframes gradientBG {
  5.     0% {
  6.       background-position: 0% 50%;
  7.     }
  8.     50% {
  9.       background-position: 100% 50%;
  10.     }
  11.     100% {
  12.       background-position: 0% 50%;
  13.     }
  14.   }
  15.  
  16.   @keyframes gradientBGReversed {
  17.     0% {
  18.       background-position: 100% 50%;
  19.     }
  20.     50% {
  21.       background-position: 0% 50%;
  22.     }
  23.     100% {
  24.       background-position: 100% 50%;
  25.     }
  26.   }
  27.  
  28.   body {
  29.     background: linear-gradient(-45deg, #111, #444, #888);
  30.     background-size: 600% 600%;
  31.     animation: gradientBG 25s ease infinite;
  32.     font-family: Arial, sans-serif;
  33.     margin: 0;
  34.     padding-top: 20px;
  35.     color: lightgray;
  36.     text-align: center;
  37.   }
  38.  
  39.   h1,
  40.   h3 {
  41.     text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
  42.     color: lightgray;
  43.   }
  44.  
  45.   button,
  46.   input {
  47.     background: linear-gradient(-45deg, #222, #444, #666);
  48.     background-size: 600% 600%;
  49.     animation: gradientBGReversed 25s ease infinite;
  50.     border: 1px solid #999;
  51.     color: lightgray;
  52.     padding: 10px;
  53.     margin: 5px;
  54.     border-radius: 5px;
  55.     box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
  56.     transition: all 0.3s ease;
  57.   }
  58.  
  59.   select {
  60.     background: #444444; /* Dark gray background for list menus */
  61.     border: 1px solid #999;
  62.     color: lightgray;
  63.     padding: 10px;
  64.     margin: 5px;
  65.     border-radius: 5px;
  66.     box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
  67.     transition: all 0.3s ease;
  68.     font-weight: bold;
  69.   }
  70.  
  71.   /* Set background for select dropdown options */
  72.   select option {
  73.     background-color: #444444; /* Dark gray for dropdown options */
  74.     color: lightgray;
  75.   }
  76.  
  77.   button {
  78.     border: 1px solid #aaa;
  79.   }
  80.  
  81.   button:hover,
  82.   input:hover {
  83.     background: linear-gradient(-45deg, #333, #555, #999);
  84.     background-size: 400% 400%;
  85.     animation: gradientBG 3s ease infinite;
  86.     box-shadow: 0 0 15px rgba(170, 170, 170, 0.5);
  87.   }
  88.  
  89.   select:hover {
  90.     background: #555555;
  91.     box-shadow: 0 0 15px rgba(170, 170, 170, 0.5);
  92.   }
  93.  
  94.   #framesCtn {
  95.     background: linear-gradient(-45deg, #111, #333, #555);
  96.     background-size: 600% 600%;
  97.     animation: gradientBGReversed 25s ease infinite;
  98.     border-radius: 10px;
  99.     padding: 20px;
  100.     box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
  101.     display: flex;
  102.     flex-wrap: wrap;
  103.     justify-content: center;
  104.     align-items: center;
  105.     border: 1px solid #999;
  106.   }
  107.  
  108.   textarea {
  109.     font-weight: bold;
  110.     color: lightgray;
  111.     background: linear-gradient(-45deg, #222, #333, #444);
  112.     background-size: 600% 600%;
  113.     animation: gradientBGReversed 25s ease infinite;
  114.     border: 1px solid #999;
  115.   }
  116.  
  117.   #fileListEl {
  118.     background: #444444; /* Dark gray background for list menus */
  119.     width: 400px;
  120.     margin: 0 auto;
  121.     text-align: left;
  122.     border: 1px solid #999;
  123.   }
  124.  
  125.   .fullscreen-image {
  126.     position: fixed;
  127.     top: 0;
  128.     left: 0;
  129.     width: 100%;
  130.     height: 100%;
  131.     background-color: rgba(0, 0, 0, 0.9);
  132.     display: flex;
  133.     justify-content: center;
  134.     align-items: center;
  135.     z-index: 1000;
  136.   }
  137.  
  138.   .fullscreen-image img {
  139.     max-width: 90%;
  140.     max-height: 90%;
  141.   }
  142.  
  143.   .container {
  144.     background: linear-gradient(-45deg, #111, #333, #555);
  145.     background-size: 600% 600%;
  146.     animation: gradientBGReversed 25s ease infinite;
  147.     border-radius: 8px;
  148.     box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
  149.     padding: 30px;
  150.     max-width: 600px;
  151.     width: 100%;
  152.     margin: 20px auto;
  153.     border: 1px solid #999;
  154.   }
  155.  
  156.   form {
  157.     display: flex;
  158.     flex-direction: column;
  159.     gap: 10px;
  160.     margin-bottom: 40px;
  161.     align-items: center;
  162.   }
  163.  
  164.   .button-row {
  165.     display: flex;
  166.     gap: 10px;
  167.   }
  168.  
  169.   #resultEl img {
  170.     max-width: 100%;
  171.     border-radius: 4px;
  172.     margin-top: 15px;
  173.     border: 1px solid #999;
  174.   }
  175.  
  176.   #resultEl p {
  177.     margin-top: 15px;
  178.     color: lightgray;
  179.   }
  180.  
  181.   .loading-screen {
  182.     display: flex;
  183.     justify-content: center;
  184.     align-items: center;
  185.     height: 200px;
  186.     border-radius: 4px;
  187.     margin-top: 15px;
  188.     border: 1px solid #999;
  189.   }
  190.  
  191.   .loading-spinner {
  192.     border: 4px solid #333;
  193.     border-top: 4px solid #aaa;
  194.     border-radius: 50%;
  195.     width: 40px;
  196.     height: 40px;
  197.     animation: spin 1s linear infinite;
  198.   }
  199.  
  200.   @keyframes spin {
  201.     0% {
  202.       transform: rotate(0deg);
  203.     }
  204.     100% {
  205.       transform: rotate(360deg);
  206.     }
  207.   }
  208.  
  209.   .error-message {
  210.     margin-top: 15px;
  211.     text-align: center;
  212.     color: lightgray;
  213.   }
  214.  
  215.   /* Set all labels and background elements to reversed gradient */
  216.   label, div, p, h1, h2, h3, h4, h5, h6, span, a, li, ul, table, tr, td, th {
  217.     background: linear-gradient(-45deg, #111, #333, #555);
  218.     background-size: 600% 600%;
  219.     animation: gradientBGReversed 25s ease infinite;
  220.     background-clip: padding-box;
  221.     color: lightgray;
  222.   }
  223.  
  224.   /* Override specific elements that should keep their existing styling */
  225.   body {
  226.     background: linear-gradient(-45deg, #111, #444, #888);
  227.     background-size: 600% 600%;
  228.     animation: gradientBG 25s ease infinite;
  229.   }
  230.  
  231.   .fullscreen-image {
  232.     background-color: rgba(0, 0, 0, 0.9);
  233.   }
  234.  
  235.   /* Ensure form elements maintain reversed gradient */
  236.   input[type="checkbox"] {
  237.     background: linear-gradient(-45deg, #111, #333, #555);
  238.     background-size: 600% 600%;
  239.     animation: gradientBGReversed 25s ease infinite;
  240.     border: 1px solid #999;
  241.   }
  242.  
  243.   /* Style any mainColumnEl85394739 elements if they exist */
  244.   #mainColumnEl85394739,
  245.   .mainColumnEl85394739,
  246.   [id*="mainColumnEl85394739"] {
  247.     background: linear-gradient(-45deg, #111, #333, #555);
  248.     background-size: 600% 600%;
  249.     animation: gradientBGReversed 25s ease infinite;
  250.     border: 1px solid #999;
  251.   }
  252. </style>
  253. <!-- Flux image generator -->
  254. <div class="container">
  255.   <form id="imageFormEl">
  256.     <textarea id="userPromptEl" placeholder="Enter your prompt" style="width: 100%; height: 200px; resize: both; overflow-y: auto" required></textarea>
  257.     <div style="display: flex; align-items: center; gap: 10px;">
  258.       <div>
  259.         <input type="checkbox" id="positivePromptCheckbox" />
  260.         <label for="positivePromptCheckbox">Positive</label>
  261.       </div>
  262.       <select id="modelSelectEl">
  263.         <option value="flux" selected>flux</option>
  264.         <option value="flux-realism">flux-realism</option>
  265.         <option value="any-dark">any-dark</option>
  266.         <option value="flux-anime">flux-anime</option>
  267.         <option value="flux-3d">flux-3d</option>
  268.         <option value="turbo">turbo</option>
  269.       </select>
  270.       <div>
  271.         <input type="checkbox" id="negativePromptCheckbox" />
  272.         <label for="negativePromptCheckbox">Negative</label>
  273.       </div>
  274.     </div>
  275.     <!-- New options for flux generator -->
  276.     <div>
  277.       <label for="guidanceScaleInput">Guidance Scale:</label>
  278.       <input type="number" id="guidanceScaleInputEl" min="1" max="30" value="30" step="1" /><label>GS 1 - 30</label>
  279.     </div>
  280.     <div>
  281.       <label for="numInferenceStepsInput">Num Inference Steps:</label>
  282.       <input type="number" id="numInferenceStepsInputEl" min="1" max="50" value="25" step="1" /><label> 1 - 50</label>
  283.     </div>
  284.     <div>
  285.       <label for="maxSequenceLengthInput">Max Sequence Length:</label>
  286.       <input type="number" id="maxSequenceLengthInputEl" min="1" max="256" value="128" step="1" /><label> 1 - 256</label>
  287.     </div>
  288.     <div class="button-row">
  289.       <button type="button" id="enhanceBtn">Enhance Prompt</button>
  290.       <button type="submit" id="generateBtn">Generate FLUX Image</button>
  291.     </div>
  292.   </form>
  293.   <div>
  294.     <input type="checkbox" id="autoUpscaleEl" />
  295.     <label for="autoUpscaleEl">Auto Upscale With Settings From Below</label>
  296.   </div>
  297.   <div>
  298.     <input type="text" id="seedInput" placeholder="Enter seed. This is FLUX." />
  299.     <button id="seedsBtn" onclick="generateRandomSeed()">SEEDS</button>
  300.   </div>
  301.   <div id="resultEl"></div>
  302. </div>
  303. <script>
  304.   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 🤯."];
  305.  
  306.   let currentPlaceholderIndex = 0;
  307.  
  308.   function rotatePlaceholder() {
  309.     document.getElementById("userPromptEl").placeholder = placeholders[currentPlaceholderIndex];
  310.     currentPlaceholderIndex = (currentPlaceholderIndex + 1) % placeholders.length;
  311.   }
  312.  
  313.   rotatePlaceholder();
  314.   setInterval(rotatePlaceholder, 3000);
  315.  
  316.   // Positive and Negative prompt text
  317.   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), ";
  318.   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)))";
  319.  
  320.   // Add event listeners for the checkboxes
  321.   document.getElementById("positivePromptCheckbox").addEventListener("change", updatePrompt);
  322.   document.getElementById("negativePromptCheckbox").addEventListener("change", updatePrompt);
  323.  
  324.   // Function to update the prompt based on checkbox states
  325.   function updatePrompt() {
  326.     const promptEl = document.getElementById("userPromptEl");
  327.     let currentPrompt = promptEl.value;
  328.    
  329.     // Remove previous positive and negative prefixes/suffixes if they exist
  330.     if (currentPrompt.startsWith(positivePromptText)) {
  331.       currentPrompt = currentPrompt.substring(positivePromptText.length).trim();
  332.     }
  333.    
  334.     if (currentPrompt.endsWith(negativePromptText)) {
  335.       currentPrompt = currentPrompt.substring(0, currentPrompt.length - negativePromptText.length).trim();
  336.     }
  337.    
  338.     // Add prefix if positive checkbox is checked
  339.     if (document.getElementById("positivePromptCheckbox").checked) {
  340.       currentPrompt = positivePromptText + " " + currentPrompt;
  341.     }
  342.    
  343.     // Add suffix if negative checkbox is checked
  344.     if (document.getElementById("negativePromptCheckbox").checked) {
  345.       currentPrompt = currentPrompt + " " + negativePromptText;
  346.     }
  347.    
  348.     promptEl.value = currentPrompt.trim();
  349.   }
  350.  
  351.   // Function to upscale an image using the settings from the upscaler
  352.   async function upscaleImage(imgUrl) {
  353.     return new Promise((resolve, reject) => {
  354.       const img = new Image();
  355.       img.onload = function() {
  356.         try {
  357.           // Get upscaler settings
  358.           const scaleValue = sizeSelectEl.value;
  359.           const method = methodSelectEl.value;
  360.           const effect = effectsSelectEl.value;
  361.           const speed = speedSelectEl.value;
  362.          
  363.           // Create canvas for initial image
  364.           const tempCanvas = document.createElement('canvas');
  365.           const tempCtx = tempCanvas.getContext('2d');
  366.           tempCanvas.width = img.width;
  367.           tempCanvas.height = img.height;
  368.          
  369.           // Draw the original image
  370.           tempCtx.drawImage(img, 0, 0);
  371.          
  372.           // Apply pre-processing effect if selected
  373.           if (effect !== 'none') {
  374.             applyEffect(tempCanvas, tempCtx, effect);
  375.           }
  376.          
  377.           // Determine scale factor and dimensions
  378.           let scale, destWidth, destHeight;
  379.          
  380.           if (scaleValue.includes('x')) {
  381.             // Fixed size format: widthxheight
  382.             const dimensions = scaleValue.split('x');
  383.             const targetWidth = parseInt(dimensions[0]);
  384.             const targetHeight = parseInt(dimensions[1]);
  385.            
  386.             // Calculate scale factors for width and height
  387.             const widthScale = targetWidth / img.width;
  388.             const heightScale = targetHeight / img.height;
  389.            
  390.             // Use the smaller scale to ensure the image fits within the target dimensions
  391.             // while maintaining aspect ratio
  392.             scale = Math.min(widthScale, heightScale);
  393.            
  394.             destWidth = Math.round(img.width * scale);
  395.             destHeight = Math.round(img.height * scale);
  396.           } else {
  397.             // Percentage scale
  398.             scale = parseFloat(scaleValue);
  399.             destWidth = Math.round(img.width * scale);
  400.             destHeight = Math.round(img.height * scale);
  401.           }
  402.          
  403.           // Create the destination canvas with calculated dimensions
  404.           const destCanvas = document.createElement('canvas');
  405.           const destCtx = destCanvas.getContext('2d');
  406.           destCanvas.width = destWidth;
  407.           destCanvas.height = destHeight;
  408.          
  409.           // Apply resize method
  410.           applyResizeMethod(tempCanvas, destCanvas, destCtx, method, scaleValue);
  411.          
  412.           // Convert to data URL
  413.           const upscaledUrl = destCanvas.toDataURL('image/jpeg', 0.9);
  414.           resolve(upscaledUrl);
  415.         } catch (error) {
  416.           console.error('Error upscaling:', error);
  417.           reject(error);
  418.         }
  419.       };
  420.       img.onerror = () => reject(new Error('Failed to load image'));
  421.       img.src = imgUrl;
  422.     });
  423.   }
  424.  
  425.   document.getElementById("imageFormEl").addEventListener("submit", async function (event) {
  426.     event.preventDefault(); // Prevent form submission
  427.     const prompt = document.getElementById("userPromptEl").value.trim();
  428.     const model = document.getElementById("modelSelectEl").value;
  429.     const guidanceScale = guidanceScaleInputEl.value;
  430.     const numInferenceSteps = numInferenceStepsInputEl.value;
  431.     const maxSequenceLength = maxSequenceLengthInputEl.value;
  432.     if (prompt) {
  433.       document.getElementById("resultEl").innerHTML = `
  434.           <p>Your generated image:</p>
  435.           <div class="loading-screen">
  436.             <div class="loading-spinner"></div>
  437.           </div>
  438.         `;
  439.  
  440.       try {
  441.         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`;
  442.  
  443.         const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Request timed out")), 60000));
  444.  
  445.         const response = await Promise.race([fetch(imageUrl), timeoutPromise]);
  446.  
  447.         if (!response.ok) {
  448.           throw new Error(`HTTP error! status: ${response.status}`);
  449.         }
  450.  
  451.         const blob = await response.blob();
  452.         const imgUrl = URL.createObjectURL(blob);
  453.  
  454.         // Check if auto upscale is enabled
  455.         if (autoUpscaleEl.checked) {
  456.           // Show upscaling status
  457.           document.getElementById("resultEl").innerHTML = `
  458.             <p>Your generated image (upscaling...):</p>
  459.             <div class="loading-screen">
  460.               <div class="loading-spinner"></div>
  461.             </div>
  462.           `;
  463.          
  464.           try {
  465.             // Upscale the image with settings from below
  466.             const upscaledUrl = await upscaleImage(imgUrl);
  467.            
  468.             // Display upscaled image
  469.             document.getElementById("resultEl").innerHTML = `
  470.               <p>Your generated image (auto upscaled):</p>
  471.               <div class="image-container">
  472.                 <img src="${upscaledUrl}" alt="Generated Image" onclick="openFullscreen(this.src)">
  473.               </div>
  474.               <button id="downloadBtn" onclick="downloadImage()">Download Image</button>
  475.             `;
  476.           } catch (error) {
  477.             console.error('Upscaling failed:', error);
  478.             // If upscaling fails, show the original
  479.             document.getElementById("resultEl").innerHTML = `
  480.               <p>Your generated image (upscaling failed):</p>
  481.               <div class="image-container">
  482.                 <img src="${imgUrl}" alt="Generated Image" onclick="openFullscreen(this.src)">
  483.               </div>
  484.               <button id="downloadBtn" onclick="downloadImage()">Download Image</button>
  485.             `;
  486.           }
  487.         } else {
  488.           // Display the original image
  489.           document.getElementById("resultEl").innerHTML = `
  490.             <p>Your generated image:</p>
  491.             <div class="image-container">
  492.               <img src="${imgUrl}" alt="Generated Image" onclick="openFullscreen(this.src)">
  493.             </div>
  494.             <button id="downloadBtn" onclick="downloadImage()">Download Image</button>
  495.           `;
  496.         }
  497.       } catch (error) {
  498.         console.error("Error generating image:", error);
  499.         document.getElementById("resultEl").innerHTML = `
  500.             <p class="error-message">Failed to generate image. Please try again. Error: ${error.message}</p>
  501.           `;
  502.       }
  503.     } else {
  504.       alert("Please enter a prompt!");
  505.     }
  506.   });
  507.  
  508.   document.getElementById("enhanceBtn").addEventListener("click", async function () {
  509.     const prompt = document.getElementById("userPromptEl").value.trim();
  510.     if (prompt) {
  511.       document.getElementById("enhanceBtn").disabled = true;
  512.       document.getElementById("enhanceBtn").textContent = "Enhancing...";
  513.       try {
  514.         console.log("Enhancing prompt:", prompt); // Debug log
  515.         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}`)}`);
  516.         if (!response.ok) {
  517.           throw new Error(`HTTP error! status: ${response.status}`);
  518.         }
  519.         const enhancedPrompt = await response.text();
  520.         console.log("Enhanced prompt:", enhancedPrompt); // Debug log
  521.         document.getElementById("userPromptEl").value = enhancedPrompt;
  522.       } catch (error) {
  523.         console.error("Error enhancing prompt:", error);
  524.         alert(`Failed to enhance prompt. Please try again. Error: ${error.message}`);
  525.       } finally {
  526.         document.getElementById("enhanceBtn").disabled = false;
  527.         document.getElementById("enhanceBtn").textContent = "Enhance Prompt";
  528.       }
  529.     } else {
  530.       alert("Please enter a prompt to enhance!");
  531.     }
  532.   });
  533.  
  534.   async function downloadImage() {
  535.     const image = document.querySelector("#resultEl img");
  536.     const response = await fetch(image.src);
  537.     const blob = await response.blob();
  538.     const url = window.URL.createObjectURL(blob);
  539.     const a = document.createElement("a");
  540.     a.style.display = "none";
  541.     a.href = url;
  542.     a.download = "generated_image.jpg";
  543.     document.body.appendChild(a);
  544.     a.click();
  545.     window.URL.revokeObjectURL(url);
  546.   }
  547.  
  548.   function generateRandomSeed() {
  549.     seedInput.value = Math.floor(Math.random() * 100000000000);
  550.   }
  551.  
  552.   function toggleComments() {
  553.     chatCtn.hidden = !chatCtn.hidden;
  554.     hideCommentsBtn.textContent = chatCtn.hidden ? "Show Comments" : "Hide Comments";
  555.   }
  556.  
  557.   function toggleGallery() {
  558.     galleryCtn.hidden = !galleryCtn.hidden;
  559.     hideGalleryBtn.textContent = galleryCtn.hidden ? "Show Gallery" : "Hide Gallery";
  560.   }
  561.  
  562.   function openFullscreen(src) {
  563.     const fullscreenDiv = document.createElement('div');
  564.     fullscreenDiv.className = 'fullscreen-image';
  565.     fullscreenDiv.innerHTML = `<img src="${src}" alt="Fullscreen Image">`;
  566.     fullscreenDiv.onclick = () => document.body.removeChild(fullscreenDiv);
  567.     document.body.appendChild(fullscreenDiv);
  568.   }
  569. </script>  <style>
  570.     /* Rainbow text animation */
  571.     @keyframes rainbowRotate {
  572.       0% {background-position: 0% 50%;}
  573.       100% {background-position: 100% 50%;}
  574.     }
  575.    
  576.     /* Reversed rainbow animation for hover */
  577.     @keyframes rainbowRotateReverse {
  578.       0% {background-position: 100% 50%;}
  579.       100% {background-position: 0% 50%;}
  580.     }
  581.    
  582.     /* Link styles */
  583.     .rainbow-link {
  584.       text-decoration: none;
  585.       display: inline-block;
  586.     }
  587.    
  588.     .rainbow-link span {
  589.       background: linear-gradient(to right, #111, #333, #555, #777, #999, #ddd);
  590.       background-size: 200% auto;
  591.       animation: rainbowRotate 5s linear infinite;
  592.       -webkit-background-clip: text;
  593.       background-clip: text;
  594.       color: transparent;
  595.       display: inline-block;
  596.     }
  597.    
  598.     .rainbow-link:hover span {
  599.       animation: rainbowRotateReverse 5s linear infinite;
  600.       text-shadow:
  601.         0px 0px 4px rgba(170,170,170,0.7),
  602.         0px 0px 8px rgba(150,150,150,0.7),
  603.         0px 0px 12px rgba(120,120,120,0.7),
  604.         0px 0px 16px rgba(100,100,100,0.7),
  605.         0px 0px 20px rgba(80,80,80,0.7),
  606.         0px 0px 24px rgba(60,60,60,0.7),
  607.         0px 0px 28px rgba(40,40,40,0.7);
  608.     }
  609.    
  610.     /* Table styling */
  611.     table {
  612.       width: 100%;
  613.       border-collapse: collapse;
  614.       border: 1px solid #999;
  615.     }
  616.    
  617.     td {
  618.       text-align: left;
  619.       padding: 4px 0;
  620.       border: 1px solid #999;
  621.     }
  622.    
  623.     /* 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. */
  624.     html { color-scheme: light dark; }
  625.   </style>
  626.  
  627.  
  628.   <!-- 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. -->
  629.   <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;">
  630.   <div class="upload-section" style="margin-bottom: 20px;">
  631.     <div id="dropAreaEl" style="border: 3px dashed #ccc; border-radius: 8px; padding: 30px; text-align: center; margin-bottom: 20px; transition: border-color 0.3s;">
  632.       <p>Drag & drop images here or...<br></p>
  633.      <input type="file" id="fileInputEl" multiple accept="image/*" style="display: none;">
  634.       <button id="browseBtn" style="background: #4CAF50; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Browse Files</button>
  635.     </div>
  636.     <div id="previewContainerEl" style="display: flex; flex-wrap: wrap; gap: 10px;"></div>
  637.   </div>
  638.  
  639.   <div class="controls" style="background: padding: 20px; border-radius: 8px; margin-bottom: 20px; display: flex; flex-wrap: wrap; gap: 20px; align-items: start;">
  640.     <div class="control-group" style="min-width: 200px;">
  641.       <label for="sizeSelect">Resize to:</label>
  642.       <select id="sizeSelectEl" style="padding: 10px; border-radius: 4px; border: 1px solid #ddd; width: 100%;">
  643.         <option value="0.1">10%</option>
  644.         <option value="0.2">20%</option>
  645.         <option value="0.3">30%</option>
  646.         <option value="0.4">40%</option>
  647.         <option value="0.5">50%</option>
  648.         <option value="0.6">60%</option>
  649.         <option value="0.7">70%</option>
  650.         <option value="0.8">80%</option>
  651.         <option value="0.9">90%</option>
  652.         <option value="1.0">100%</option>
  653.         <option value="1.1">110%</option>
  654.         <option value="1.2">120%</option>
  655.         <option value="1.3">130%</option>
  656.         <option value="1.4">140%</option>
  657.         <option value="1.5">150%</option>
  658.         <option value="1.6">160%</option>
  659.         <option value="1.7">170%</option>
  660.         <option value="1.8">180%</option>
  661.         <option value="1.9">190%</option>
  662.         <option value="2.0" selected>200%</option>
  663.         <option value="3.0">300%</option>
  664.         <option value="4.0">400%</option>
  665.         <option value="1920x1080">1920x1080 (HD)</option>
  666.         <option value="3840x2160">3840x2160 (4K)</option>
  667.         <option value="7680x4320">7680x4320 (8K)</option>
  668.       </select>
  669.     </div>
  670.    
  671.     <div class="control-group" style="min-width: 200px;">
  672.       <label for="methodSelect">Scaling Method:</label>
  673.       <select id="methodSelectEl" style="padding: 10px; border-radius: 4px; border: 1px solid #ddd; width: 100%;">
  674.         <option value="pixelated">Nearest Neighbor (Pixelated)</option>
  675.         <option value="bilinear">Bilinear (Smooth)</option>
  676.         <option value="bicubic" selected>Bicubic (Higher Quality)</option>
  677.         <option value="lanczos">Lanczos (Sharper Details)</option>
  678.         <option value="hermite">Hermite (Smooth Curves)</option>
  679.         <option value="mitchell">Mitchell-Netravali (Balanced)</option>
  680.         <option value="catmull-rom">Catmull-Rom (Preserve Edges)</option>
  681.         <option value="box">Box (Fast Scaling)</option>
  682.         <option value="hamming">Hamming (Reduce Aliasing)</option>
  683.         <option value="gaussian">Gaussian (Soft Edges)</option>
  684.         <option value="linear">Linear (Simple Interpolation)</option>
  685.         <option value="cubic">Cubic (Variant of Bicubic)</option>
  686.         <option value="spline">Spline (Smooth Curve Interpolation)</option>
  687.         <option value="lagrange">Lagrange (Mathematical Precision)</option>
  688.         <option value="sinc">Sinc (High Quality)</option>
  689.         <option value="hann">Hann-windowed Sinc (Reduced Ringing)</option>
  690.         <option value="bspline">B-Spline (Smooth with Less Overshoot)</option>
  691.         <option value="triangle">Triangle Filter (Weighted Average)</option>
  692.         <option value="quadratic">Quadratic Filter (Smoother than Triangle)</option>
  693.         <option value="kaiser">Kaiser Window (Adjustable Sharpness)</option>
  694.         <option value="blackman">Blackman Window (Low Ringing)</option>
  695.         <option value="adaptive">Adaptive (Content-Aware)</option>
  696.         <option value="super-res">Super Resolution (AI Enhanced)</option>
  697.         <option value="edt">Edge-Directed (Detail Preserving)</option>
  698.         <option value="fractal">Fractal (Pattern Enhancing)</option>
  699.       </select>
  700.     </div>
  701.    
  702.     <div class="control-group" style="min-width: 200px;">
  703.       <label for="effectsSelect">Pre-processing Effects:</label>
  704.       <select id="effectsSelectEl" style="padding: 10px; border-radius: 4px; border: 1px solid #ddd; width: 100%;">
  705.         <option value="none">None</option>
  706.         <option value="blur-light">Fine Blur</option>
  707.         <option value="blur-strong">Strong Blur</option>
  708.         <option value="sharpen-light" selected>Light Sharpen</option>
  709.         <option value="sharpen-strong">Strong Sharpen</option>
  710.         <option value="edge-enhance">Edge Enhance</option>
  711.         <option value="emboss">Emboss</option>
  712.         <option value="grayscale">Grayscale</option>
  713.         <option value="sepia">Sepia</option>
  714.         <option value="invert">Invert Colors</option>
  715.         <option value="brighten">Brighten</option>
  716.         <option value="darken">Darken</option>
  717.         <option value="increase-contrast">Increase Contrast</option>
  718.         <option value="decrease-contrast">Decrease Contrast</option>
  719.         <option value="noise-reduction">Noise Reduction</option>
  720.         <option value="posterize">Posterize</option>
  721.         <option value="saturation-increase">Increase Saturation</option>
  722.         <option value="saturation-decrease">Decrease Saturation</option>
  723.         <option value="oil-painting">Oil Painting Effect</option>
  724.         <option value="watercolor">Watercolor Effect</option>
  725.         <option value="sketch">Sketch Effect</option>
  726.         <option value="warm">Warm Color Filter</option>
  727.         <option value="cool">Cool Color Filter</option>
  728.         <option value="vintage">Vintage Filter</option>
  729.         <option value="vignette">Vignette Effect</option>
  730.         <option value="film-grain">Film Grain</option>
  731.         <option value="noise-add">Add Noise</option>
  732.         <option value="hdr">HDR Effect</option>
  733.         <option value="tint-red">Red Tint</option>
  734.         <option value="tint-green">Green Tint</option>
  735.         <option value="tint-blue">Blue Tint</option>
  736.         <option value="black-white">Black & White (High Contrast)</option>
  737.        <option value="solarize">Solarize Effect</option>
  738.        <option value="threshold">Threshold Effect</option>
  739.        <option value="dither">Dithering</option>
  740.        <option value="pixelate">Pixelate</option>
  741.        <option value="unsharp-mask">Unsharp Mask</option>
  742.        <option value="bloom">Bloom Effect</option>
  743.        <option value="glitch">Glitch Art</option>
  744.        <option value="duotone">Duotone</option>
  745.        <option value="halftone">Halftone</option>
  746.        <option value="color-balance">Color Balance</option>
  747.        <option value="clarity">Clarity Boost</option>
  748.        <option value="denoise">Advanced Denoise</option>
  749.        <option value="cartoon">Cartoon Effect</option>
  750.      </select>
  751.    </div>
  752.    
  753.    <div class="control-group" style="min-width: 200px;">
  754.       <label for="speedSelectEl">Rendering Speed:</label>
  755.       <select id="speedSelectEl" style="padding: 10px; border-radius: 4px; border: 1px solid #ddd; width: 100%;">
  756.         <option value="slow">Slow (25% resources)</option>
  757.         <option value="medium" selected>Medium (50% resources)</option>
  758.         <option value="fast">Fast (90% resources)</option>
  759.       </select>
  760.     </div>
  761.    
  762.     <div class="control-group" style="display: flex; align-items: flex-end; min-height: 70px;">
  763.       <button id="processBtn" disabled style="background: #4CAF50; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Process Images</button>
  764.     </div>
  765.   </div>
  766.  
  767.   <div id="resultsCtn" hidden style="margin-top: 30px;">
  768.     <h2>Processed Images</h2>
  769.     <div style="display: flex; align-items: center; margin-bottom: 15px; gap: 10px;">
  770.       <button id="downloadAllBtn" style="background: #2196F3; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Download All</button>
  771.      
  772.       <div style="display: flex; align-items: center;">
  773.         <label for="imageFormatSelectEl" style="margin-right: 5px;">Format:</label>
  774.         <select id="imageFormatSelectEl" style="padding: 8px; border-radius: 4px; border: 1px solid #ddd;">
  775.           <option value="png">PNG</option>
  776.           <option value="jpg">JPG</option>
  777.           <option value="jpeg">JPEG</option>
  778.           <option value="webp">WebP</option>
  779.         </select>
  780.       </div>
  781.     </div>
  782.     <div id="galleryEl" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px;"></div>
  783.   </div>
  784. </div>
  785.   <style>
  786.     body {
  787.       font-family: Arial, sans-serif;
  788.       max-width: 800px;
  789.       margin: 0 auto;
  790.       padding: 20px;
  791.       text-align: left;
  792.     }
  793.     .container {
  794.       margin-top: 20px;
  795.     }
  796.     .exif-data {
  797.       margin-top: 20px;
  798.       border: 1px solid #ddd;
  799.       padding: 15px;
  800.       border-radius: 5px;
  801.       max-height: 500px;
  802.       overflow-y: auto;
  803.     }
  804.     .exif-section {
  805.       margin-bottom: 15px;
  806.     }
  807.     .exif-section h3 {
  808.       margin-bottom: 5px;
  809.       border-bottom: 1px solid #eee;
  810.       padding-bottom: 5px;
  811.     }
  812.     .exif-item {
  813.       display: flex;
  814.       padding: 3px 0;
  815.     }
  816.     .exif-tag {
  817.       font-weight: bold;
  818.       min-width: 200px;
  819.     }
  820.     .image-preview {
  821.       max-width: 100%;
  822.       max-height: 300px;
  823.       margin-top: 10px;
  824.       display: block;
  825.     }
  826.     #loadingIndicatorEl {
  827.       display: none;
  828.       margin-top: 10px;
  829.       color: #666;
  830.     }
  831.     /* Drag and drop styles */
  832.     .upload-area {
  833.       border: 2px dashed #ccc;
  834.       border-radius: 5px;
  835.       padding: 20px;
  836.       text-align: center;
  837.       margin-bottom: 15px;
  838.       transition: all 0.3s ease;
  839.     }
  840.     .upload-area.dragover {
  841.       border-color: #007bff;
  842.       background-color: rgba(0, 123, 255, 0.1);
  843.     }
  844.     .upload-instructions {
  845.       margin: 10px 0;
  846.       color: #555;
  847.     }
  848.     .url-input-container {
  849.       display: flex;
  850.       margin-top: 15px;
  851.     }
  852.     .url-input {
  853.       flex: 1;
  854.       padding: 8px;
  855.       border: 1px solid #ccc;
  856.       border-radius: 4px 0 0 4px;
  857.     }
  858.     .load-url-btn {
  859.       padding: 8px 15px;
  860.       background-color: #4a90e2;
  861.       color: white;
  862.       border: none;
  863.       border-radius: 0 4px 4px 0;
  864.       cursor: pointer;
  865.     }
  866.     .load-url-btn:hover {
  867.       background-color: #357ae8;
  868.     }
  869.   </style>
  870. </head>
  871. <body>
  872.   <h1>IMAGE TO TEXT PROMPT</h1>
  873.   <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>
  874.  
  875.   <div id="uploadAreaEl" class="upload-area">
  876.     <input type="file" id="imageInputEl" accept="image/*">
  877.     <div class="upload-instructions">or drag & drop an image file here</div>
  878.    
  879.    <div class="url-input-container">
  880.      <input type="text" id="imageUrlInputEl" class="url-input" placeholder="Enter an image URL...">
  881.      <button id="loadUrlBtnEl" class="load-url-btn">Load URL</button>
  882.    </div>
  883.  </div>
  884.  
  885.  <div id="loadingIndicatorEl">Processing image...</div>
  886.  
  887.  <div class="container">
  888.    <img id="imagePreviewEl" class="image-preview" hidden>
  889.    <div id="exifDataContainerEl" class="exif-data" hidden></div>
  890.  </div>
  891.  
  892.  <script>
  893.    // EXIF tag names mapping
  894.    const exifTagNames = {
  895.      // TIFF tags
  896.      0x0100: "ImageWidth",
  897.      0x0101: "ImageHeight",
  898.      0x0102: "BitsPerSample",
  899.      0x0103: "Compression",
  900.      0x0106: "PhotometricInterpretation",
  901.      0x010E: "ImageDescription",
  902.      0x010F: "Make",
  903.      0x0110: "Model",
  904.      0x0111: "StripOffsets",
  905.      0x0112: "Orientation",
  906.      0x0115: "SamplesPerPixel",
  907.      0x0116: "RowsPerStrip",
  908.      0x0117: "StripByteCounts",
  909.      0x011A: "XResolution",
  910.      0x011B: "YResolution",
  911.      0x011C: "PlanarConfiguration",
  912.      0x0128: "ResolutionUnit",
  913.      0x0131: "Software",
  914.      0x0132: "DateTime",
  915.      0x013B: "Artist",
  916.      0x013E: "WhitePoint",
  917.      0x013F: "PrimaryChromaticities",
  918.      0x0201: "JPEGInterchangeFormat",
  919.      0x0202: "JPEGInterchangeFormatLength",
  920.      0x0211: "YCbCrCoefficients",
  921.      0x0212: "YCbCrSubSampling",
  922.      0x0213: "YCbCrPositioning",
  923.      0x0214: "ReferenceBlackWhite",
  924.      0x8298: "Copyright",
  925.      0x8769: "ExifIFDPointer",
  926.      0x8825: "GPSInfoIFDPointer",
  927.      
  928.      // EXIF tags
  929.      0x829A: "ExposureTime",
  930.      0x829D: "FNumber",
  931.      0x8822: "ExposureProgram",
  932.      0x8824: "SpectralSensitivity",
  933.      0x8827: "ISOSpeedRatings",
  934.      0x8828: "OECF",
  935.      0x8830: "SensitivityType",
  936.      0x8831: "StandardOutputSensitivity",
  937.      0x8832: "RecommendedExposureIndex",
  938.      0x8833: "ISOSpeed",
  939.      0x8834: "ISOSpeedLatitudeyyy",
  940.      0x8835: "ISOSpeedLatitudezzz",
  941.      0x9000: "ExifVersion",
  942.      0x9003: "DateTimeOriginal",
  943.      0x9004: "DateTimeDigitized",
  944.      0x9101: "ComponentsConfiguration",
  945.      0x9102: "CompressedBitsPerPixel",
  946.      0x9201: "ShutterSpeedValue",
  947.      0x9202: "ApertureValue",
  948.      0x9203: "BrightnessValue",
  949.      0x9204: "ExposureBiasValue",
  950.      0x9205: "MaxApertureValue",
  951.      0x9206: "SubjectDistance",
  952.      0x9207: "MeteringMode",
  953.      0x9208: "LightSource",
  954.      0x9209: "Flash",
  955.      0x920A: "FocalLength",
  956.      0x9214: "SubjectArea",
  957.      0x927C: "MakerNote",
  958.      0x9286: "UserComment",
  959.      0x9290: "SubSecTime",
  960.      0x9291: "SubSecTimeOriginal",
  961.      0x9292: "SubSecTimeDigitized",
  962.      0xA000: "FlashpixVersion",
  963.      0xA001: "ColorSpace",
  964.      0xA002: "PixelXDimension",
  965.      0xA003: "PixelYDimension",
  966.      0xA004: "RelatedSoundFile",
  967.      0xA005: "InteroperabilityIFDPointer",
  968.      0xA20B: "FlashEnergy",
  969.      0xA20C: "SpatialFrequencyResponse",
  970.      0xA20E: "FocalPlaneXResolution",
  971.      0xA20F: "FocalPlaneYResolution",
  972.      0xA210: "FocalPlaneResolutionUnit",
  973.      0xA214: "SubjectLocation",
  974.      0xA215: "ExposureIndex",
  975.      0xA217: "SensingMethod",
  976.      0xA300: "FileSource",
  977.      0xA301: "SceneType",
  978.      0xA302: "CFAPattern",
  979.      0xA401: "CustomRendered",
  980.      0xA402: "ExposureMode",
  981.      0xA403: "WhiteBalance",
  982.      0xA404: "DigitalZoomRatio",
  983.      0xA405: "FocalLengthIn35mmFilm",
  984.      0xA406: "SceneCaptureType",
  985.      0xA407: "GainControl",
  986.      0xA408: "Contrast",
  987.      0xA409: "Saturation",
  988.      0xA40A: "Sharpness",
  989.      0xA40B: "DeviceSettingDescription",
  990.      0xA40C: "SubjectDistanceRange",
  991.      0xA420: "ImageUniqueID",
  992.      0xA430: "CameraOwnerName",
  993.      0xA431: "BodySerialNumber",
  994.      0xA432: "LensSpecification",
  995.      0xA433: "LensMake",
  996.      0xA434: "LensModel",
  997.      0xA435: "LensSerialNumber",
  998.      
  999.      // GPS tags
  1000.      0x0000: "GPSVersionID",
  1001.      0x0001: "GPSLatitudeRef",
  1002.      0x0002: "GPSLatitude",
  1003.      0x0003: "GPSLongitudeRef",
  1004.      0x0004: "GPSLongitude",
  1005.      0x0005: "GPSAltitudeRef",
  1006.      0x0006: "GPSAltitude",
  1007.      0x0007: "GPSTimeStamp",
  1008.      0x0008: "GPSSatellites",
  1009.      0x0009: "GPSStatus",
  1010.      0x000A: "GPSMeasureMode",
  1011.      0x000B: "GPSDOP",
  1012.      0x000C: "GPSSpeedRef",
  1013.      0x000D: "GPSSpeed",
  1014.      0x000E: "GPSTrackRef",
  1015.      0x000F: "GPSTrack",
  1016.      0x0010: "GPSImgDirectionRef",
  1017.      0x0011: "GPSImgDirection",
  1018.      0x0012: "GPSMapDatum",
  1019.      0x0013: "GPSDestLatitudeRef",
  1020.      0x0014: "GPSDestLatitude",
  1021.      0x0015: "GPSDestLongitudeRef",
  1022.      0x0016: "GPSDestLongitude",
  1023.      0x0017: "GPSDestBearingRef",
  1024.      0x0018: "GPSDestBearing",
  1025.      0x0019: "GPSDestDistanceRef",
  1026.      0x001A: "GPSDestDistance",
  1027.      0x001B: "GPSProcessingMethod",
  1028.      0x001C: "GPSAreaInformation",
  1029.      0x001D: "GPSDateStamp",
  1030.      0x001E: "GPSDifferential"
  1031.    };
  1032.  
  1033.     // Format mappings for EXIF data types
  1034.     const formatMap = {
  1035.       1: { name: "BYTE", size: 1 },
  1036.       2: { name: "ASCII", size: 1 },
  1037.       3: { name: "SHORT", size: 2 },
  1038.       4: { name: "LONG", size: 4 },
  1039.       5: { name: "RATIONAL", size: 8 },
  1040.       7: { name: "UNDEFINED", size: 1 },
  1041.       9: { name: "SLONG", size: 4 },
  1042.       10: { name: "SRATIONAL", size: 8 }
  1043.     };
  1044.  
  1045.     // References to DOM elements
  1046.     const uploadArea = uploadAreaEl;
  1047.     const imageInput = imageInputEl;
  1048.     const imageUrlInput = imageUrlInputEl;
  1049.     const loadUrlBtn = loadUrlBtnEl;
  1050.     const imagePreview = imagePreviewEl;
  1051.     const exifDataContainer = exifDataContainerEl;
  1052.     const loadingIndicator = loadingIndicatorEl;
  1053.  
  1054.     // Listen for file selection
  1055.     imageInput.addEventListener('change', handleImageSelect);
  1056.    
  1057.     // Listen for URL load button click
  1058.     loadUrlBtn.addEventListener('click', handleImageUrl);
  1059.    
  1060.     // Listen for key press in URL input
  1061.     imageUrlInput.addEventListener('keypress', function(e) {
  1062.       if (e.key === 'Enter') {
  1063.         handleImageUrl();
  1064.       }
  1065.     });
  1066.    
  1067.     // Setup drag and drop event listeners
  1068.     uploadArea.addEventListener('dragover', function(e) {
  1069.       e.preventDefault();
  1070.       e.stopPropagation();
  1071.       uploadArea.classList.add('dragover');
  1072.     });
  1073.    
  1074.     uploadArea.addEventListener('dragleave', function(e) {
  1075.       e.preventDefault();
  1076.       e.stopPropagation();
  1077.       uploadArea.classList.remove('dragover');
  1078.     });
  1079.    
  1080.     uploadArea.addEventListener('drop', function(e) {
  1081.       e.preventDefault();
  1082.       e.stopPropagation();
  1083.       uploadArea.classList.remove('dragover');
  1084.      
  1085.       const files = e.dataTransfer.files;
  1086.       if (files.length > 0) {
  1087.         processImageFile(files[0]);
  1088.       }
  1089.     });
  1090.  
  1091.     async function handleImageSelect(event) {
  1092.       const file = event.target.files[0];
  1093.       if (file) {
  1094.         processImageFile(file);
  1095.       }
  1096.     }
  1097.    
  1098.     async function handleImageUrl() {
  1099.       const url = imageUrlInput.value.trim();
  1100.       if (!url) {
  1101.         alert('Please enter an image URL');
  1102.         return;
  1103.       }
  1104.      
  1105.       // Show loading indicator
  1106.       loadingIndicator.style.display = 'block';
  1107.       exifDataContainer.hidden = true;
  1108.      
  1109.       try {
  1110.         let blob;
  1111.        
  1112.         // Check if it's a blob URL
  1113.         if (url.startsWith('blob:')) {
  1114.           try {
  1115.             // For blob URLs, we'll try a direct fetch but this may fail
  1116.             // if the blob URL wasn't created in this context
  1117.             const response = await fetch(url);
  1118.             if (!response.ok) {
  1119.               throw new Error('Failed to fetch blob URL');
  1120.             }
  1121.             blob = await response.blob();
  1122.           } catch (error) {
  1123.             throw new Error('Could not access blob URL. Blob URLs can only be accessed in the same context they were created.');
  1124.           }
  1125.         } else {
  1126.           // Regular URL handling
  1127.           const response = await fetch(url);
  1128.           if (!response.ok) {
  1129.             throw new Error('Failed to fetch image from URL');
  1130.           }
  1131.           blob = await response.blob();
  1132.         }
  1133.        
  1134.         if (!blob.type.startsWith('image/')) {
  1135.           throw new Error('The URL does not point to a valid image');
  1136.         }
  1137.        
  1138.         // Create a File object from the blob
  1139.         const file = new File([blob], 'image-from-url.jpg', { type: blob.type });
  1140.        
  1141.         // Display image preview
  1142.         imagePreview.src = URL.createObjectURL(file);
  1143.         imagePreview.hidden = false;
  1144.        
  1145.         // Process the file for EXIF data
  1146.         const exifData = await extractExifData(file);
  1147.         displayExifData(exifData);
  1148.       } catch (error) {
  1149.         console.error('Error processing image URL:', error);
  1150.         exifDataContainer.innerHTML = `<p>Error: ${error.message}</p>`;
  1151.         exifDataContainer.hidden = false;
  1152.       }
  1153.      
  1154.       // Hide loading indicator
  1155.       loadingIndicator.style.display = 'none';
  1156.     }
  1157.  
  1158.     async function processImageFile(file) {
  1159.       if (!file || !file.type.startsWith('image/')) {
  1160.         alert('Please select a valid image file');
  1161.         return;
  1162.       }
  1163.  
  1164.       // Show loading indicator
  1165.       loadingIndicator.style.display = 'block';
  1166.       exifDataContainer.hidden = true;
  1167.      
  1168.       // Display image preview
  1169.       imagePreview.src = URL.createObjectURL(file);
  1170.       imagePreview.hidden = false;
  1171.      
  1172.       try {
  1173.         // Read the image file and extract EXIF data
  1174.         const exifData = await extractExifData(file);
  1175.         displayExifData(exifData);
  1176.       } catch (error) {
  1177.         console.error('Error extracting EXIF data:', error);
  1178.         exifDataContainer.innerHTML = `<p>Error extracting EXIF data: ${error.message}</p>`;
  1179.         exifDataContainer.hidden = false;
  1180.       }
  1181.      
  1182.       // Hide loading indicator
  1183.       loadingIndicator.style.display = 'none';
  1184.     }
  1185.  
  1186.     async function extractExifData(file) {
  1187.       return new Promise((resolve, reject) => {
  1188.         const reader = new FileReader();
  1189.        
  1190.         reader.onload = function(e) {
  1191.           try {
  1192.             const arrayBuffer = e.target.result;
  1193.             const dataView = new DataView(arrayBuffer);
  1194.            
  1195.             // Check for JPEG SOI marker (Start of Image)
  1196.             if (dataView.getUint16(0, false) !== 0xFFD8) {
  1197.               throw new Error("Not a valid JPEG file");
  1198.             }
  1199.            
  1200.             let offset = 2; // Skip SOI marker
  1201.             let markerFound = false;
  1202.             let exifData = { main: {}, exif: {}, gps: {} };
  1203.            
  1204.             // Scan through JPEG segments
  1205.             while (offset < dataView.byteLength) {
  1206.              const marker = dataView.getUint16(offset, false);
  1207.              offset += 2;
  1208.              
  1209.              // Check for APP1 marker (contains EXIF)
  1210.              if (marker === 0xFFE1) {
  1211.                const segmentLength = dataView.getUint16(offset, false);
  1212.                offset += 2;
  1213.                
  1214.                // Check for "Exif" identifier
  1215.                if (
  1216.                  dataView.getUint8(offset) === 0x45 && // 'E'
  1217.                  dataView.getUint8(offset + 1) === 0x78 && // 'x'
  1218.                  dataView.getUint8(offset + 2) === 0x69 && // 'i'
  1219.                  dataView.getUint8(offset + 3) === 0x66 && // 'f'
  1220.                  dataView.getUint8(offset + 4) === 0x00 && // null
  1221.                  dataView.getUint8(offset + 5) === 0x00 // null
  1222.                ) {
  1223.                  markerFound = true;
  1224.                  offset += 6; // Skip "Exif\0\0"
  1225.                  
  1226.                  // Parse TIFF header
  1227.                  const tiffOffset = offset;
  1228.                  const littleEndian = dataView.getUint16(offset, false) === 0x4949; // 'II' = little endian, 'MM' = big endian
  1229.                  
  1230.                  if (dataView.getUint16(offset + 2, littleEndian) !== 0x002A) {
  1231.                    throw new Error("Invalid TIFF data");
  1232.                  }
  1233.                  
  1234.                  // Get offset to first IFD (Image File Directory)
  1235.                  const ifdOffset = dataView.getUint32(offset + 4, littleEndian);
  1236.                  
  1237.                  // Parse the main IFD
  1238.                  const mainIfdData = parseIFD(dataView, tiffOffset + ifdOffset, littleEndian, tiffOffset);
  1239.                  exifData.main = mainIfdData.tags;
  1240.                  
  1241.                  // Check for ExifIFDPointer
  1242.                  if (mainIfdData.tags.ExifIFDPointer) {
  1243.                    const exifIfdOffset = mainIfdData.tags.ExifIFDPointer;
  1244.                    const exifIfdData = parseIFD(dataView, tiffOffset + exifIfdOffset, littleEndian, tiffOffset);
  1245.                    exifData.exif = exifIfdData.tags;
  1246.                    delete exifData.main.ExifIFDPointer; // Remove the pointer from main tags
  1247.                  }
  1248.                  
  1249.                  // Check for GPSInfoIFDPointer
  1250.                  if (mainIfdData.tags.GPSInfoIFDPointer) {
  1251.                    const gpsIfdOffset = mainIfdData.tags.GPSInfoIFDPointer;
  1252.                    const gpsIfdData = parseIFD(dataView, tiffOffset + gpsIfdOffset, littleEndian, tiffOffset);
  1253.                    exifData.gps = gpsIfdData.tags;
  1254.                    delete exifData.main.GPSInfoIFDPointer; // Remove the pointer from main tags
  1255.                  }
  1256.                  
  1257.                  break;
  1258.                } else {
  1259.                  offset += segmentLength - 2; // Skip to next segment
  1260.                }
  1261.              } else if ((marker & 0xFF00) !== 0xFF00) {
  1262.                break; // Not a valid marker, exit
  1263.              } else {
  1264.                // Skip other segments
  1265.                const segmentLength = dataView.getUint16(offset, false);
  1266.                offset += segmentLength;
  1267.              }
  1268.            }
  1269.            
  1270.            if (!markerFound) {
  1271.              throw new Error("No EXIF data found");
  1272.            }
  1273.            
  1274.            resolve(exifData);
  1275.          } catch (error) {
  1276.            reject(error);
  1277.          }
  1278.        };
  1279.        
  1280.        reader.onerror = function() {
  1281.          reject(new Error("Error reading file"));
  1282.        };
  1283.        
  1284.        reader.readAsArrayBuffer(file);
  1285.      });
  1286.    }
  1287.  
  1288.    function parseIFD(dataView, ifdOffset, littleEndian, tiffOffset) {
  1289.      const entriesCount = dataView.getUint16(ifdOffset, littleEndian);
  1290.      let tags = {};
  1291.      
  1292.      for (let i = 0; i < entriesCount; i++) {
  1293.        const entryOffset = ifdOffset + 2 + (i * 12);
  1294.        const tagId = dataView.getUint16(entryOffset, littleEndian);
  1295.        const dataFormat = dataView.getUint16(entryOffset + 2, littleEndian);
  1296.        const numValues = dataView.getUint32(entryOffset + 4, littleEndian);
  1297.        const valueOffset = numValues * (formatMap[dataFormat]?.size || 0) <= 4
  1298.          ? entryOffset + 8
  1299.          : tiffOffset + dataView.getUint32(entryOffset + 8, littleEndian);
  1300.        
  1301.        // Get tag name
  1302.        const tagName = exifTagNames[tagId] || `Unknown (0x${tagId.toString(16).padStart(4, '0')})`;
  1303.        
  1304.        let tagValue;
  1305.        
  1306.        // Parse the value based on data format
  1307.        if (dataFormat === 1 || dataFormat === 7) { // BYTE or UNDEFINED
  1308.          if (numValues === 1) {
  1309.            tagValue = dataView.getUint8(valueOffset);
  1310.          } else {
  1311.            const values = [];
  1312.            for (let j = 0; j < numValues; j++) {
  1313.              values.push(dataView.getUint8(valueOffset + j));
  1314.            }
  1315.            tagValue = values;
  1316.          }
  1317.        } else if (dataFormat === 2) { // ASCII
  1318.          let str = '';
  1319.          for (let j = 0; j < numValues; j++) {
  1320.            const char = dataView.getUint8(valueOffset + j);
  1321.            if (char === 0) break; // Null-terminated
  1322.            str += String.fromCharCode(char);
  1323.          }
  1324.          tagValue = str;
  1325.        } else if (dataFormat === 3) { // SHORT
  1326.          if (numValues === 1) {
  1327.            tagValue = dataView.getUint16(valueOffset, littleEndian);
  1328.          } else {
  1329.            const values = [];
  1330.            for (let j = 0; j < numValues; j++) {
  1331.              values.push(dataView.getUint16(valueOffset + (j * 2), littleEndian));
  1332.            }
  1333.            tagValue = values;
  1334.          }
  1335.        } else if (dataFormat === 4) { // LONG
  1336.          if (numValues === 1) {
  1337.            tagValue = dataView.getUint32(valueOffset, littleEndian);
  1338.          } else {
  1339.            const values = [];
  1340.            for (let j = 0; j < numValues; j++) {
  1341.              values.push(dataView.getUint32(valueOffset + (j * 4), littleEndian));
  1342.            }
  1343.            tagValue = values;
  1344.          }
  1345.        } else if (dataFormat === 5) { // RATIONAL
  1346.          if (numValues === 1) {
  1347.            const numerator = dataView.getUint32(valueOffset, littleEndian);
  1348.            const denominator = dataView.getUint32(valueOffset + 4, littleEndian);
  1349.            tagValue = denominator === 0 ? 0 : numerator / denominator;
  1350.          } else {
  1351.            const values = [];
  1352.            for (let j = 0; j < numValues; j++) {
  1353.              const numerator = dataView.getUint32(valueOffset + (j * 8), littleEndian);
  1354.              const denominator = dataView.getUint32(valueOffset + (j * 8) + 4, littleEndian);
  1355.              values.push(denominator === 0 ? 0 : numerator / denominator);
  1356.            }
  1357.            tagValue = values;
  1358.          }
  1359.        } else if (dataFormat === 9) { // SLONG
  1360.          if (numValues === 1) {
  1361.            tagValue = dataView.getInt32(valueOffset, littleEndian);
  1362.          } else {
  1363.            const values = [];
  1364.            for (let j = 0; j < numValues; j++) {
  1365.              values.push(dataView.getInt32(valueOffset + (j * 4), littleEndian));
  1366.            }
  1367.            tagValue = values;
  1368.          }
  1369.        } else if (dataFormat === 10) { // SRATIONAL
  1370.          if (numValues === 1) {
  1371.            const numerator = dataView.getInt32(valueOffset, littleEndian);
  1372.            const denominator = dataView.getInt32(valueOffset + 4, littleEndian);
  1373.            tagValue = denominator === 0 ? 0 : numerator / denominator;
  1374.          } else {
  1375.            const values = [];
  1376.            for (let j = 0; j < numValues; j++) {
  1377.              const numerator = dataView.getInt32(valueOffset + (j * 8), littleEndian);
  1378.              const denominator = dataView.getInt32(valueOffset + (j * 8) + 4, littleEndian);
  1379.              values.push(denominator === 0 ? 0 : numerator / denominator);
  1380.            }
  1381.            tagValue = values;
  1382.          }
  1383.        } else {
  1384.          tagValue = `Unsupported format: ${dataFormat}`;
  1385.        }
  1386.        
  1387.        // Store tags using their name
  1388.        tags[tagName] = tagValue;
  1389.      }
  1390.      
  1391.      // Calculate next IFD offset
  1392.      const nextIfdOffset = dataView.getUint32(ifdOffset + 2 + (entriesCount * 12), littleEndian);
  1393.      
  1394.      return {
  1395.        tags: tags,
  1396.        nextIfdOffset: nextIfdOffset ? tiffOffset + nextIfdOffset : 0
  1397.      };
  1398.    }
  1399.  
  1400.    function displayExifData(exifData) {
  1401.      let userCommentValue = null;
  1402.      
  1403.      // Look for UserComment in exif section (most likely location)
  1404.      if (exifData.exif && exifData.exif.UserComment) {
  1405.        userCommentValue = formatValue(exifData.exif.UserComment, "UserComment");
  1406.      }
  1407.      // Check main section if not found in exif section
  1408.      else if (exifData.main && exifData.main.UserComment) {
  1409.        userCommentValue = formatValue(exifData.main.UserComment, "UserComment");
  1410.      }
  1411.      // Check GPS section if not found elsewhere (unlikely but being thorough)
  1412.      else if (exifData.gps && exifData.gps.UserComment) {
  1413.        userCommentValue = formatValue(exifData.gps.UserComment, "UserComment");
  1414.      }
  1415.      
  1416.      if (userCommentValue) {
  1417.        exifDataContainer.innerHTML = `<div class="exif-value">${userCommentValue}</div>`;
  1418.        
  1419.         // Set the extracted value in the userPromptEl textarea
  1420.         const userPromptEl = document.getElementById("userPromptEl");
  1421.         if (userPromptEl) {
  1422.           userPromptEl.value = userCommentValue;
  1423.          
  1424.           // Trigger the form submission to generate the image
  1425.           setTimeout(() => {
  1426.             const generateBtn = document.getElementById("generateBtn");
  1427.             if (generateBtn) {
  1428.               generateBtn.click();
  1429.             }
  1430.           }, 500);
  1431.         }
  1432.       } else {
  1433.         exifDataContainer.innerHTML = '<p>No UserComment tag found in this image.</p>';
  1434.       }
  1435.      
  1436.       exifDataContainer.hidden = false;
  1437.     }
  1438.  
  1439.     function formatTagsToHtml(tags) {
  1440.       let html = '';
  1441.       for (const [tag, value] of Object.entries(tags)) {
  1442.         html += '<div class="exif-item">';
  1443.         html += `<div class="exif-tag">${tag}:</div>`;
  1444.         html += `<div class="exif-value">${formatValue(value, tag)}</div>`;
  1445.         html += '</div>';
  1446.       }
  1447.       return html;
  1448.     }
  1449.  
  1450.     function formatValue(value, tagName) {
  1451.       // Special handling for UserComment to convert decimal to ASCII
  1452.       if (tagName === "UserComment" && Array.isArray(value)) {
  1453.        // Convert byte array to ASCII string
  1454.        let commentText = "";
  1455.        
  1456.         // Check if there's a character code identifier (first 8 bytes)
  1457.         let charCodeIdentifier = "";
  1458.         if (value.length >= 8) {
  1459.           charCodeIdentifier = String.fromCharCode(...value.slice(0, 8));
  1460.         }
  1461.        
  1462.         // If we have a recognizable character code, skip the first 8 bytes
  1463.         if (charCodeIdentifier === "ASCII\0\0\0" ||
  1464.             charCodeIdentifier === "UNICODE\0" ||
  1465.             charCodeIdentifier === "JIS\0\0\0\0\0" ||
  1466.             value.slice(0, 8).every(byte => byte === 0)) {
  1467.           for (let i = 8; i < value.length; i++) {
  1468.            if (value[i] !== 0) { // Skip null bytes
  1469.              commentText += String.fromCharCode(value[i]);
  1470.            }
  1471.          }
  1472.        } else {
  1473.          // If no recognizable character code, convert all bytes
  1474.          for (let i = 0; i < value.length; i++) {
  1475.            if (value[i] !== 0) { // Skip null bytes
  1476.              commentText += String.fromCharCode(value[i]);
  1477.            }
  1478.          }
  1479.        }
  1480.        
  1481.        try {
  1482.          // Parse the comment text as JSON and extract the prompt field
  1483.          let jsonData = JSON.parse(commentText);
  1484.          return jsonData.prompt || "No prompt field found in JSON";
  1485.        } catch (error) {
  1486.          // If it's not valid JSON, return an error message
  1487.          return `Invalid JSON: ${commentText}`;
  1488.        }
  1489.      }
  1490.      
  1491.      if (Array.isArray(value)) {
  1492.        if (value.length > 10) {
  1493.           return `[${value.slice(0, 10).join(', ')}, ...]`;
  1494.         }
  1495.         return `[${value.join(', ')}]`;
  1496.       } else if (typeof value === 'number') {
  1497.         // Format numbers to be more readable
  1498.         if (Number.isInteger(value)) {
  1499.           return value.toString();
  1500.         } else {
  1501.           return value.toFixed(4);
  1502.         }
  1503.       }
  1504.       return value;
  1505.     }
  1506.   </script>
  1507. </body>
  1508. </html>
  1509. <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
  1510.  
  1511. <script>
  1512.   // Global variables to store images
  1513.   let uploadedImages = [];
  1514.   let processedImages = [];
  1515.  
  1516.   // DOM elements
  1517.   const dropArea = dropAreaEl;
  1518.   const previewContainer = previewContainerEl;
  1519.   const sizeSelect = sizeSelectEl;
  1520.   const methodSelect = methodSelectEl;
  1521.   const effectsSelect = effectsSelectEl;
  1522.   const speedSelect = speedSelectEl;
  1523.      
  1524.   // Set up event listeners
  1525.   browseBtn.addEventListener('click', () => fileInputEl.click());
  1526.   fileInputEl.addEventListener('change', handleFileSelect);
  1527.   processBtn.addEventListener('click', processImages);
  1528.   downloadAllBtn.addEventListener('click', downloadAllImages);
  1529.   sizeSelect.addEventListener('change', updateFormatOptions);
  1530.  
  1531.   // Set up drag and drop
  1532.   ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
  1533.     dropArea.addEventListener(eventName, preventDefaults, false);
  1534.   });
  1535.  
  1536.   function preventDefaults(e) {
  1537.     e.preventDefault();
  1538.     e.stopPropagation();
  1539.   }
  1540.  
  1541.   ['dragenter', 'dragover'].forEach(eventName => {
  1542.     dropArea.addEventListener(eventName, () => {
  1543.       dropArea.style.borderColor = '#4CAF50';
  1544.       dropArea.style.backgroundColor = 'rgba(76, 175, 80, 0.1)';
  1545.     }, false);
  1546.   });
  1547.  
  1548.   ['dragleave', 'drop'].forEach(eventName => {
  1549.     dropArea.addEventListener(eventName, () => {
  1550.       dropArea.style.borderColor = '#ccc';
  1551.       dropArea.style.backgroundColor = '';
  1552.     }, false);
  1553.   });
  1554.  
  1555.   dropArea.addEventListener('drop', handleDrop, false);
  1556.  
  1557.   function handleDrop(e) {
  1558.     const dt = e.dataTransfer;
  1559.     const files = dt.files;
  1560.     handleFiles(files);
  1561.   }
  1562.  
  1563.   // Handle the file selection
  1564.   function handleFileSelect(e) {
  1565.     const files = e.target.files;
  1566.     handleFiles(files);
  1567.   }
  1568.  
  1569.   function handleFiles(files) {
  1570.     if (files.length === 0) return;
  1571.    
  1572.     Array.from(files).forEach(file => {
  1573.       if (!file.type.match('image.*')) return;
  1574.      
  1575.       // Check if image already uploaded
  1576.       if (uploadedImages.some(img => img.name === file.name && img.size === file.size)) {
  1577.        return; // Skip duplicates
  1578.       }
  1579.      
  1580.       const reader = new FileReader();
  1581.       reader.onload = function(e) {
  1582.         const img = new Image();
  1583.         img.src = e.target.result;
  1584.         img.onload = function() {
  1585.           uploadedImages.push({
  1586.             name: file.name,
  1587.             size: file.size,
  1588.             type: file.type,
  1589.             dataUrl: e.target.result,
  1590.             width: img.width,
  1591.             height: img.height
  1592.           });
  1593.          
  1594.           updatePreview();
  1595.           updateProcessButton();
  1596.         };
  1597.       };
  1598.       reader.readAsDataURL(file);
  1599.     });
  1600.   }
  1601.  
  1602.   function updatePreview() {
  1603.     previewContainer.innerHTML = '';
  1604.    
  1605.     uploadedImages.forEach((img, index) => {
  1606.       const previewItem = document.createElement('div');
  1607.       previewItem.style.cssText = 'position: relative; width: 100px; height: 100px; border-radius: 4px; overflow: hidden;';
  1608.      
  1609.       const imgEl = document.createElement('img');
  1610.       imgEl.src = img.dataUrl;
  1611.       imgEl.alt = img.name;
  1612.       imgEl.style.cssText = 'width: 100%; height: 100%; object-fit: cover;';
  1613.      
  1614.       const removeBtn = document.createElement('button');
  1615.       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;';
  1616.       removeBtn.textContent = '×';
  1617.       removeBtn.onclick = () => removeImage(index);
  1618.      
  1619.       previewItem.appendChild(imgEl);
  1620.       previewItem.appendChild(removeBtn);
  1621.       previewContainer.appendChild(previewItem);
  1622.     });
  1623.   }
  1624.  
  1625.   function removeImage(index) {
  1626.     uploadedImages.splice(index, 1);
  1627.     updatePreview();
  1628.     updateProcessButton();
  1629.   }
  1630.  
  1631.   function updateProcessButton() {
  1632.     processBtn.disabled = uploadedImages.length === 0;
  1633.   }
  1634.  
  1635.   // Function to update format options based on selected size
  1636.   function updateFormatOptions() {
  1637.     const scaleValue = sizeSelect.value;
  1638.     const formatSelect = imageFormatSelectEl;
  1639.    
  1640.     // Get all format options
  1641.     const options = formatSelect.querySelectorAll('option');
  1642.    
  1643.     // Store current selected format
  1644.     const currentFormat = formatSelect.value;
  1645.    
  1646.     // Check if the selected scale is 300% or 400% or one of the large fixed sizes
  1647.     if (parseFloat(scaleValue) >= 3.0 || scaleValue === '3840x2160' || scaleValue === '7680x4320') {
  1648.       // Only allow jpg and webp for large scales
  1649.       for (const option of options) {
  1650.         if (option.value === 'jpg' || option.value === 'webp') {
  1651.           option.disabled = false;
  1652.         } else {
  1653.           option.disabled = true;
  1654.         }
  1655.       }
  1656.      
  1657.       // If current format is not allowed, change to jpg
  1658.       if (currentFormat !== 'jpg' && currentFormat !== 'webp') {
  1659.        formatSelect.value = 'jpg';
  1660.       }
  1661.     } else {
  1662.       // Enable all formats for other scales
  1663.       for (const option of options) {
  1664.         option.disabled = false;
  1665.       }
  1666.     }
  1667.   }
  1668.  
  1669.   // Process the images with the selected options
  1670.   function processImages() {
  1671.     if (uploadedImages.length === 0) return;
  1672.    
  1673.     const scaleValue = sizeSelect.value;
  1674.     const method = methodSelect.value;
  1675.     const effect = effectsSelect.value;
  1676.     const speed = speedSelect.value;
  1677.    
  1678.     processBtn.disabled = true;
  1679.     processBtn.textContent = '⏳ Processing...';
  1680.    
  1681.     // Clear previous results
  1682.     processedImages = [];
  1683.     galleryEl.innerHTML = '';
  1684.    
  1685.     // Process each image (with small delay to allow UI to update)
  1686.     setTimeout(() => {
  1687.       const promises = uploadedImages.map(img => processImage(img, scaleValue, method, effect, speed));
  1688.      
  1689.       Promise.all(promises).then(() => {
  1690.         processBtn.textContent = 'Process Images';
  1691.         processBtn.disabled = false;
  1692.         resultsCtn.hidden = false;
  1693.        
  1694.         // Display processed images in gallery
  1695.         displayGallery();
  1696.        
  1697.         // Update format options based on selected size
  1698.         updateFormatOptions();
  1699.       });
  1700.     }, 100);
  1701.   }
  1702.  
  1703.   function processImage(img, scaleValue, method, effect, speed) {
  1704.     return new Promise((resolve) => {
  1705.       const canvas = document.createElement('canvas');
  1706.       const ctx = canvas.getContext('2d');
  1707.      
  1708.       // Calculate new dimensions
  1709.       let newWidth, newHeight;
  1710.      
  1711.       if (scaleValue.includes('x')) {
  1712.         // Fixed size format: widthxheight
  1713.         const dimensions = scaleValue.split('x');
  1714.         const targetWidth = parseInt(dimensions[0]);
  1715.         const targetHeight = parseInt(dimensions[1]);
  1716.        
  1717.         // Calculate scale factors for width and height
  1718.         const widthScale = targetWidth / img.width;
  1719.         const heightScale = targetHeight / img.height;
  1720.        
  1721.         // Use the smaller scale to ensure the image fits within the target dimensions
  1722.         // while maintaining aspect ratio
  1723.         const scale = Math.min(widthScale, heightScale);
  1724.        
  1725.         newWidth = Math.round(img.width * scale);
  1726.         newHeight = Math.round(img.height * scale);
  1727.       } else {
  1728.         // Percentage scale
  1729.         const scale = parseFloat(scaleValue);
  1730.         newWidth = Math.round(img.width * scale);
  1731.         newHeight = Math.round(img.height * scale);
  1732.       }
  1733.      
  1734.       // Create a temporary canvas for pre-processing
  1735.       const tempCanvas = document.createElement('canvas');
  1736.       const tempCtx = tempCanvas.getContext('2d');
  1737.       tempCanvas.width = img.width;
  1738.       tempCanvas.height = img.height;
  1739.      
  1740.       // Load the image
  1741.       const imgEl = new Image();
  1742.       imgEl.onload = function() {
  1743.         // Apply rendering speed settings for resource usage control
  1744.         const speedSettings = {
  1745.           'slow': { chunkSize: 50, delay: 100 },    // 25% resources
  1746.           'medium': { chunkSize: 100, delay: 20 },  // 50% resources
  1747.           'fast': { chunkSize: 500, delay: 1 }      // 90% resources
  1748.         };
  1749.        
  1750.         const { chunkSize, delay } = speedSettings[speed] || speedSettings.medium;
  1751.        
  1752.         // First, draw the original image on the temp canvas
  1753.         tempCtx.drawImage(imgEl, 0, 0);
  1754.        
  1755.         // Apply pre-processing effect if selected (with throttling)
  1756.         if (effect !== 'none') {
  1757.           // Process the effect with throttling based on speed
  1758.           applyEffectWithThrottling(tempCanvas, tempCtx, effect, speed, () => {
  1759.             // After effect is applied, resize the image
  1760.             // Set final canvas size
  1761.             canvas.width = newWidth;
  1762.             canvas.height = newHeight;
  1763.            
  1764.             // Resize the pre-processed image using the selected method
  1765.             applyResizeMethodWithThrottling(tempCanvas, canvas, ctx, method, scaleValue, speed, () => {
  1766.               // Get the processed image data
  1767.               const processedDataUrl = canvas.toDataURL(img.type || 'image/jpeg');
  1768.              
  1769.               // Create processed image object
  1770.               const processedImage = {
  1771.                 name: img.name,
  1772.                 originalSize: `${img.width}x${img.height}`,
  1773.                 newSize: `${newWidth}x${newHeight}`,
  1774.                 scale: scaleValue.includes('x') ? scaleValue : Math.round(parseFloat(scaleValue) * 100) + '%',
  1775.                 method: getMethodName(method),
  1776.                 effect: getEffectName(effect),
  1777.                 dataUrl: processedDataUrl
  1778.               };
  1779.              
  1780.               // Store the processed image
  1781.               processedImages.push(processedImage);
  1782.              
  1783.               // Resolve with the processed image
  1784.               resolve(processedImage);
  1785.             });
  1786.           });
  1787.         } else {
  1788.           // No effect to apply, just resize
  1789.           // Set final canvas size
  1790.           canvas.width = newWidth;
  1791.           canvas.height = newHeight;
  1792.          
  1793.           // Resize the pre-processed image using the selected method
  1794.           applyResizeMethodWithThrottling(tempCanvas, canvas, ctx, method, scaleValue, speed, () => {
  1795.             // Get the processed image data
  1796.             const processedDataUrl = canvas.toDataURL(img.type || 'image/jpeg');
  1797.            
  1798.             // Create processed image object
  1799.             const processedImage = {
  1800.               name: img.name,
  1801.               originalSize: `${img.width}x${img.height}`,
  1802.               newSize: `${newWidth}x${newHeight}`,
  1803.               scale: scaleValue.includes('x') ? scaleValue : Math.round(parseFloat(scaleValue) * 100) + '%',
  1804.               method: getMethodName(method),
  1805.               effect: getEffectName(effect),
  1806.               dataUrl: processedDataUrl
  1807.             };
  1808.            
  1809.             // Store the processed image
  1810.             processedImages.push(processedImage);
  1811.            
  1812.             // Resolve with the processed image
  1813.             resolve(processedImage);
  1814.           });
  1815.         }
  1816.       };
  1817.       imgEl.src = img.dataUrl;
  1818.     });
  1819.   }
  1820.  
  1821.   // Apply effect with throttling based on selected speed
  1822.   function applyEffectWithThrottling(canvas, ctx, effect, speed, callback) {
  1823.     const width = canvas.width;
  1824.     const height = canvas.height;
  1825.     const imageData = ctx.getImageData(0, 0, width, height);
  1826.    
  1827.     // Determine chunk size and delay based on speed
  1828.     const speedSettings = {
  1829.       'slow': { chunkSize: Math.floor(height * 0.05), delay: 20 },     // Process 5% of image height at a time
  1830.       'medium': { chunkSize: Math.floor(height * 0.2), delay: 10 },   // Process 20% of image height at a time
  1831.       'fast': { chunkSize: height, delay: 0 }                        // Process entire image at once
  1832.     };
  1833.    
  1834.     const { chunkSize, delay } = speedSettings[speed] || speedSettings.medium;
  1835.    
  1836.     // For some effects that require the entire image data, we can't chunk
  1837.     // process them, so we need to apply them directly
  1838.     const globalEffects = [
  1839.       'blur-light', 'blur-strong', 'sharpen-light', 'sharpen-strong',
  1840.       'edge-enhance', 'emboss', 'noise-reduction'
  1841.     ];
  1842.    
  1843.     if (globalEffects.includes(effect)) {
  1844.       // For effects that require whole-image processing
  1845.       setTimeout(() => {
  1846.         applyEffect(canvas, ctx, effect);
  1847.         callback();
  1848.       }, delay);
  1849.     } else {
  1850.       // For effects that can be applied line by line
  1851.       let currentRow = 0;
  1852.      
  1853.       function processChunk() {
  1854.         if (currentRow >= height) {
  1855.           ctx.putImageData(imageData, 0, 0);
  1856.           callback();
  1857.           return;
  1858.         }
  1859.        
  1860.         const endRow = Math.min(currentRow + chunkSize, height);
  1861.        
  1862.         // Extract the chunk of data that needs processing
  1863.         const chunkImageData = ctx.getImageData(0, currentRow, width, endRow - currentRow);
  1864.        
  1865.         // Apply the effect to this chunk
  1866.         switch (effect) {
  1867.           case 'grayscale':
  1868.             for (let i = 0; i < chunkImageData.data.length; i += 4) {
  1869.              const avg = (chunkImageData.data[i] + chunkImageData.data[i + 1] + chunkImageData.data[i + 2]) / 3;
  1870.              chunkImageData.data[i] = avg;
  1871.              chunkImageData.data[i + 1] = avg;
  1872.              chunkImageData.data[i + 2] = avg;
  1873.            }
  1874.            break;
  1875.          
  1876.          case 'sepia':
  1877.            for (let i = 0; i < chunkImageData.data.length; i += 4) {
  1878.              const r = chunkImageData.data[i];
  1879.              const g = chunkImageData.data[i + 1];
  1880.              const b = chunkImageData.data[i + 2];
  1881.              
  1882.              chunkImageData.data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
  1883.              chunkImageData.data[i + 1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
  1884.              chunkImageData.data[i + 2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
  1885.            }
  1886.            break;
  1887.          
  1888.          case 'invert':
  1889.            for (let i = 0; i < chunkImageData.data.length; i += 4) {
  1890.              chunkImageData.data[i] = 255 - chunkImageData.data[i];
  1891.              chunkImageData.data[i + 1] = 255 - chunkImageData.data[i + 1];
  1892.              chunkImageData.data[i + 2] = 255 - chunkImageData.data[i + 2];
  1893.            }
  1894.            break;
  1895.          
  1896.          case 'brighten':
  1897.            for (let i = 0; i < chunkImageData.data.length; i += 4) {
  1898.              chunkImageData.data[i] = Math.min(255, chunkImageData.data[i] * 1.2);
  1899.              chunkImageData.data[i + 1] = Math.min(255, chunkImageData.data[i + 1] * 1.2);
  1900.              chunkImageData.data[i + 2] = Math.min(255, chunkImageData.data[i + 2] * 1.2);
  1901.            }
  1902.            break;
  1903.          
  1904.          case 'darken':
  1905.            for (let i = 0; i < chunkImageData.data.length; i += 4) {
  1906.              chunkImageData.data[i] = chunkImageData.data[i] * 0.8;
  1907.              chunkImageData.data[i + 1] = chunkImageData.data[i + 1] * 0.8;
  1908.              chunkImageData.data[i + 2] = chunkImageData.data[i + 2] * 0.8;
  1909.            }
  1910.            break;
  1911.          
  1912.          case 'increase-contrast':
  1913.            for (let i = 0; i < chunkImageData.data.length; i += 4) {
  1914.              for (let j = 0; j < 3; j++) {
  1915.                chunkImageData.data[i + j] = ((chunkImageData.data[i + j] / 255 - 0.5) * 1.4 + 0.5) * 255;
  1916.                if (chunkImageData.data[i + j] < 0) chunkImageData.data[i + j] = 0;
  1917.                if (chunkImageData.data[i + j] > 255) chunkImageData.data[i + j] = 255;
  1918.               }
  1919.             }
  1920.             break;
  1921.          
  1922.           case 'decrease-contrast':
  1923.             for (let i = 0; i < chunkImageData.data.length; i += 4) {
  1924.              for (let j = 0; j < 3; j++) {
  1925.                chunkImageData.data[i + j] = ((chunkImageData.data[i + j] / 255 - 0.5) * 0.7 + 0.5) * 255;
  1926.                if (chunkImageData.data[i + j] < 0) chunkImageData.data[i + j] = 0;
  1927.                if (chunkImageData.data[i + j] > 255) chunkImageData.data[i + j] = 255;
  1928.               }
  1929.             }
  1930.             break;
  1931.          
  1932.           case 'posterize':
  1933.             for (let i = 0; i < chunkImageData.data.length; i += 4) {
  1934.              const levels = 5;
  1935.              chunkImageData.data[i] = Math.floor(chunkImageData.data[i] / 255 * levels) / levels * 255;
  1936.              chunkImageData.data[i + 1] = Math.floor(chunkImageData.data[i + 1] / 255 * levels) / levels * 255;
  1937.              chunkImageData.data[i + 2] = Math.floor(chunkImageData.data[i + 2] / 255 * levels) / levels * 255;
  1938.            }
  1939.            break;
  1940.        }
  1941.        
  1942.        // Put the processed chunk back
  1943.        ctx.putImageData(chunkImageData, 0, currentRow);
  1944.        
  1945.        // Update row position
  1946.        currentRow = endRow;
  1947.        
  1948.        // Schedule the next chunk with delay
  1949.        setTimeout(processChunk, delay);
  1950.      }
  1951.      
  1952.      // Start processing chunks
  1953.      processChunk();
  1954.    }
  1955.  }
  1956.  
  1957.  // Apply resize method with throttling based on selected speed
  1958.  function applyResizeMethodWithThrottling(img, canvas, ctx, method, scaleValue, speed, callback) {
  1959.    // For simple methods that don't require special throttling, use the browser's built-in scaling
  1960.    if (method === 'pixelated' || method === 'bilinear' || method === 'bicubic') {
  1961.      ctx.imageSmoothingEnabled = method !== 'pixelated';
  1962.      if (method === 'bilinear') ctx.imageSmoothingQuality = 'low';
  1963.      if (method === 'bicubic') ctx.imageSmoothingQuality = 'medium';
  1964.      
  1965.      // Draw the image according to selected speed
  1966.      if (speed === 'fast') {
  1967.        // For fast mode, draw all at once
  1968.        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  1969.        callback();
  1970.      } else {
  1971.        // For slow/medium modes, break rendering into chunks
  1972.        const speedSettings = {
  1973.          'slow': { chunkSize: Math.floor(canvas.height * 0.1), delay: 50 },
  1974.          'medium': { chunkSize: Math.floor(canvas.height * 0.3), delay: 10 }
  1975.        };
  1976.        
  1977.        const { chunkSize, delay } = speedSettings[speed] || speedSettings.medium;
  1978.        
  1979.        let currentRow = 0;
  1980.        
  1981.        function drawChunk() {
  1982.          if (currentRow >= canvas.height) {
  1983.             callback();
  1984.             return;
  1985.           }
  1986.          
  1987.           const chunkHeight = Math.min(chunkSize, canvas.height - currentRow);
  1988.           const sourceYRatio = img.height / canvas.height;
  1989.          
  1990.           // Draw a slice of the image
  1991.           ctx.drawImage(
  1992.             img,
  1993.             0, Math.floor(currentRow * sourceYRatio),                          // Source x, y
  1994.             img.width, Math.ceil(chunkHeight * sourceYRatio),                  // Source width, height
  1995.             0, currentRow,                                                    // Destination x, y
  1996.             canvas.width, chunkHeight                                          // Destination width, height
  1997.           );
  1998.          
  1999.           // Move to next chunk
  2000.           currentRow += chunkHeight;
  2001.          
  2002.           // Schedule next chunk with delay
  2003.           setTimeout(drawChunk, delay);
  2004.         }
  2005.        
  2006.         // Start drawing chunks
  2007.         drawChunk();
  2008.       }
  2009.     } else {
  2010.       // For advanced methods, use existing implementation with throttling
  2011.       if (speed === 'fast') {
  2012.         // Fast mode - process all at once
  2013.         applyResizeMethod(img, canvas, ctx, method, scaleValue);
  2014.         callback();
  2015.       } else {
  2016.         // Add a small artificial delay to simulate resource throttling
  2017.         setTimeout(() => {
  2018.           applyResizeMethod(img, canvas, ctx, method, scaleValue);
  2019.           callback();
  2020.         }, speed === 'slow' ? 200 : 50);
  2021.       }
  2022.     }
  2023.   }
  2024.  
  2025.   // Apply the selected pre-processing effect to the canvas
  2026.   function applyEffect(canvas, ctx, effect) {
  2027.     const width = canvas.width;
  2028.     const height = canvas.height;
  2029.     const imageData = ctx.getImageData(0, 0, width, height);
  2030.     const data = imageData.data;
  2031.    
  2032.     switch (effect) {
  2033.       case 'blur-light':
  2034.         applyBoxBlur(imageData, width, height, 1);
  2035.         break;
  2036.      
  2037.       case 'blur-strong':
  2038.         applyBoxBlur(imageData, width, height, 3);
  2039.         break;
  2040.      
  2041.       case 'sharpen-light':
  2042.         applyConvolution(imageData, width, height, [
  2043.           0, -0.5, 0,
  2044.           -0.5, 3, -0.5,
  2045.           0, -0.5, 0
  2046.         ]);
  2047.         break;
  2048.      
  2049.       case 'sharpen-strong':
  2050.         applyConvolution(imageData, width, height, [
  2051.           -0.5, -1, -0.5,
  2052.           -1, 7, -1,
  2053.           -0.5, -1, -0.5
  2054.         ]);
  2055.         break;
  2056.      
  2057.       case 'edge-enhance':
  2058.         applyConvolution(imageData, width, height, [
  2059.           0, -1, 0,
  2060.           -1, 5, -1,
  2061.           0, -1, 0
  2062.         ]);
  2063.         break;
  2064.      
  2065.       case 'emboss':
  2066.         applyConvolution(imageData, width, height, [
  2067.           -2, -1, 0,
  2068.           -1, 1, 1,
  2069.           0, 1, 2
  2070.         ]);
  2071.         break;
  2072.      
  2073.       case 'grayscale':
  2074.         for (let i = 0; i < data.length; i += 4) {
  2075.          const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
  2076.          data[i] = avg;
  2077.          data[i + 1] = avg;
  2078.          data[i + 2] = avg;
  2079.        }
  2080.        break;
  2081.      
  2082.      case 'sepia':
  2083.        for (let i = 0; i < data.length; i += 4) {
  2084.          const r = data[i];
  2085.          const g = data[i + 1];
  2086.          const b = data[i + 2];
  2087.          
  2088.          data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
  2089.          data[i + 1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
  2090.          data[i + 2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
  2091.        }
  2092.        break;
  2093.      
  2094.      case 'invert':
  2095.        for (let i = 0; i < data.length; i += 4) {
  2096.          data[i] = 255 - data[i];
  2097.          data[i + 1] = 255 - data[i + 1];
  2098.          data[i + 2] = 255 - data[i + 2];
  2099.        }
  2100.        break;
  2101.      
  2102.      case 'brighten':
  2103.        for (let i = 0; i < data.length; i += 4) {
  2104.          data[i] = Math.min(255, data[i] * 1.2);
  2105.          data[i + 1] = Math.min(255, data[i + 1] * 1.2);
  2106.          data[i + 2] = Math.min(255, data[i + 2] * 1.2);
  2107.        }
  2108.        break;
  2109.      
  2110.      case 'darken':
  2111.        for (let i = 0; i < data.length; i += 4) {
  2112.          data[i] = data[i] * 0.8;
  2113.          data[i + 1] = data[i + 1] * 0.8;
  2114.          data[i + 2] = data[i + 2] * 0.8;
  2115.        }
  2116.        break;
  2117.      
  2118.      case 'increase-contrast':
  2119.        for (let i = 0; i < data.length; i += 4) {
  2120.          // Apply simple contrast adjustment
  2121.          for (let j = 0; j < 3; j++) {
  2122.            data[i + j] = ((data[i + j] / 255 - 0.5) * 1.4 + 0.5) * 255;
  2123.            if (data[i + j] < 0) data[i + j] = 0;
  2124.            if (data[i + j] > 255) data[i + j] = 255;
  2125.           }
  2126.         }
  2127.         break;
  2128.      
  2129.       case 'decrease-contrast':
  2130.         for (let i = 0; i < data.length; i += 4) {
  2131.          // Apply simple contrast reduction
  2132.          for (let j = 0; j < 3; j++) {
  2133.            data[i + j] = ((data[i + j] / 255 - 0.5) * 0.7 + 0.5) * 255;
  2134.            if (data[i + j] < 0) data[i + j] = 0;
  2135.            if (data[i + j] > 255) data[i + j] = 255;
  2136.           }
  2137.         }
  2138.         break;
  2139.      
  2140.       case 'noise-reduction':
  2141.         applyMedianFilter(imageData, width, height);
  2142.         break;
  2143.      
  2144.       case 'posterize':
  2145.         for (let i = 0; i < data.length; i += 4) {
  2146.          const levels = 5;
  2147.          data[i] = Math.floor(data[i] / 255 * levels) / levels * 255;
  2148.          data[i + 1] = Math.floor(data[i + 1] / 255 * levels) / levels * 255;
  2149.          data[i + 2] = Math.floor(data[i + 2] / 255 * levels) / levels * 255;
  2150.        }
  2151.        break;
  2152.    }
  2153.    
  2154.    ctx.putImageData(imageData, 0, 0);
  2155.  }
  2156.  
  2157.  // Apply convolution (used for many effects like blur, sharpen, etc.)
  2158.  function applyConvolution(imageData, width, height, kernel) {
  2159.    const data = imageData.data;
  2160.    const buff = new Uint8ClampedArray(data);
  2161.    
  2162.    const kSize = Math.sqrt(kernel.length);
  2163.    const kRadius = Math.floor(kSize / 2);
  2164.    
  2165.    for (let y = 0; y < height; y++) {
  2166.      for (let x = 0; x < width; x++) {
  2167.        const idx = (y * width + x) * 4;
  2168.        let r = 0, g = 0, b = 0;
  2169.        
  2170.        // Apply kernel
  2171.        for (let ky = 0; ky < kSize; ky++) {
  2172.          for (let kx = 0; kx < kSize; kx++) {
  2173.            const kIdx = ky * kSize + kx;
  2174.            const px = Math.min(width - 1, Math.max(0, x + kx - kRadius));
  2175.            const py = Math.min(height - 1, Math.max(0, y + ky - kRadius));
  2176.            const pIdx = (py * width + px) * 4;
  2177.            
  2178.            r += buff[pIdx] * kernel[kIdx];
  2179.            g += buff[pIdx + 1] * kernel[kIdx];
  2180.            b += buff[pIdx + 2] * kernel[kIdx];
  2181.          }
  2182.        }
  2183.        
  2184.        // Set new values
  2185.        data[idx] = Math.min(255, Math.max(0, r));
  2186.        data[idx + 1] = Math.min(255, Math.max(0, g));
  2187.        data[idx + 2] = Math.min(255, Math.max(0, b));
  2188.      }
  2189.    }
  2190.  }
  2191.  
  2192.  // Box blur implementation
  2193.  function applyBoxBlur(imageData, width, height, radius) {
  2194.    const data = imageData.data;
  2195.    const buff = new Uint8ClampedArray(data);
  2196.    
  2197.    // Horizontal pass
  2198.    for (let y = 0; y < height; y++) {
  2199.      for (let x = 0; x < width; x++) {
  2200.        let r = 0, g = 0, b = 0, count = 0;
  2201.        
  2202.        for (let i = Math.max(0, x - radius); i <= Math.min(width - 1, x + radius); i++) {
  2203.          const idx = (y * width + i) * 4;
  2204.          r += buff[idx];
  2205.          g += buff[idx + 1];
  2206.          b += buff[idx + 2];
  2207.          count++;
  2208.        }
  2209.        
  2210.        const idx = (y * width + x) * 4;
  2211.        data[idx] = r / count;
  2212.        data[idx + 1] = g / count;
  2213.        data[idx + 2] = b / count;
  2214.      }
  2215.    }
  2216.    
  2217.    // Copy for vertical pass
  2218.    for (let i = 0; i < data.length; i++) {
  2219.      buff[i] = data[i];
  2220.    }
  2221.    
  2222.    // Vertical pass
  2223.    for (let x = 0; x < width; x++) {
  2224.      for (let y = 0; y < height; y++) {
  2225.        let r = 0, g = 0, b = 0, count = 0;
  2226.        
  2227.        for (let j = Math.max(0, y - radius); j <= Math.min(height - 1, y + radius); j++) {
  2228.          const idx = (j * width + x) * 4;
  2229.          r += buff[idx];
  2230.          g += buff[idx + 1];
  2231.          b += buff[idx + 2];
  2232.          count++;
  2233.        }
  2234.        
  2235.        const idx = (y * width + x) * 4;
  2236.        data[idx] = r / count;
  2237.        data[idx + 1] = g / count;
  2238.        data[idx + 2] = b / count;
  2239.      }
  2240.    }
  2241.  }
  2242.  
  2243.  // Median filter (good for noise reduction)
  2244.  function applyMedianFilter(imageData, width, height) {
  2245.    const data = imageData.data;
  2246.    const buff = new Uint8ClampedArray(data);
  2247.    const radius = 1;
  2248.    
  2249.    for (let y = 0; y < height; y++) {
  2250.      for (let x = 0; x < width; x++) {
  2251.        const idx = (y * width + x) * 4;
  2252.        
  2253.        // For each channel (R,G,B)
  2254.        for (let c = 0; c < 3; c++) {
  2255.          let values = [];
  2256.          
  2257.          // Gather values in the neighborhood
  2258.          for (let dy = -radius; dy <= radius; dy++) {
  2259.            for (let dx = -radius; dx <= radius; dx++) {
  2260.              const nx = Math.min(width - 1, Math.max(0, x + dx));
  2261.              const ny = Math.min(height - 1, Math.max(0, y + dy));
  2262.              const nIdx = (ny * width + nx) * 4 + c;
  2263.              
  2264.              values.push(buff[nIdx]);
  2265.            }
  2266.          }
  2267.          
  2268.          // Sort and find median
  2269.          values.sort((a, b) => a - b);
  2270.           data[idx + c] = values[Math.floor(values.length / 2)];
  2271.         }
  2272.       }
  2273.     }
  2274.   }
  2275.  
  2276.   function getMethodName(method) {
  2277.     const methods = {
  2278.       'pixelated': 'Nearest Neighbor',
  2279.       'bilinear': 'Bilinear',
  2280.       'bicubic': 'Bicubic',
  2281.       'lanczos': 'Lanczos',
  2282.       'hermite': 'Hermite',
  2283.       'mitchell': 'Mitchell-Netravali',
  2284.       'catmull-rom': 'Catmull-Rom',
  2285.       'box': 'Box',
  2286.       'hamming': 'Hamming',
  2287.       'gaussian': 'Gaussian'
  2288.     };
  2289.     return methods[method] || method;
  2290.   }
  2291.  
  2292.   function getEffectName(effect) {
  2293.     const effects = {
  2294.       'none': 'None',
  2295.       'blur-light': 'Fine Blur',
  2296.       'blur-strong': 'Strong Blur',
  2297.       'sharpen-light': 'Light Sharpen',
  2298.       'sharpen-strong': 'Strong Sharpen',
  2299.       'edge-enhance': 'Edge Enhancement',
  2300.       'emboss': 'Emboss',
  2301.       'grayscale': 'Grayscale',
  2302.       'sepia': 'Sepia',
  2303.       'invert': 'Invert Colors',
  2304.       'brighten': 'Brighten',
  2305.       'darken': 'Darken',
  2306.       'increase-contrast': 'Increased Contrast',
  2307.       'decrease-contrast': 'Decreased Contrast',
  2308.       'noise-reduction': 'Noise Reduction',
  2309.       'posterize': 'Posterize'
  2310.     };
  2311.     return effects[effect] || effect;
  2312.   }
  2313.  
  2314.   function applyResizeMethod(img, canvas, ctx, method, scaleValue) {
  2315.     // Set default quality
  2316.     ctx.imageSmoothingEnabled = true;
  2317.    
  2318.     switch (method) {
  2319.       case 'pixelated':
  2320.         ctx.imageSmoothingEnabled = false;
  2321.         ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  2322.         break;
  2323.        
  2324.       case 'bilinear':
  2325.         ctx.imageSmoothingQuality = 'low';
  2326.         ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  2327.         break;
  2328.        
  2329.       case 'bicubic':
  2330.         ctx.imageSmoothingQuality = 'medium';
  2331.         ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  2332.         break;
  2333.        
  2334.       case 'lanczos':
  2335.         ctx.imageSmoothingQuality = 'high';
  2336.         ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  2337.         // Apply sharpening filter for lanczos-like effect
  2338.         applyLanczosSharpening(canvas, ctx);
  2339.         break;
  2340.        
  2341.       case 'hermite':
  2342.         // Custom hermite resampling implementation
  2343.         hermiteResize(canvas, ctx, img, canvas.width, canvas.height);
  2344.         break;
  2345.        
  2346.       case 'mitchell':
  2347.         // Mitchell-Netravali filter - similar to bicubic but with less ringing
  2348.         ctx.imageSmoothingQuality = 'high';
  2349.         ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  2350.         // Apply slight sharpening for Mitchell-like effect
  2351.         applyConvolution(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, [
  2352.           0, -0.15, 0,
  2353.           -0.15, 1.6, -0.15,
  2354.           0, -0.15, 0
  2355.         ]);
  2356.         ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
  2357.         break;
  2358.        
  2359.       case 'catmull-rom':
  2360.         // Catmull-Rom spline filter - preserves edges better than bicubic
  2361.         ctx.imageSmoothingQuality = 'high';
  2362.         ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  2363.         // Apply edge-preserving enhancement for Catmull-Rom-like effect
  2364.         applyConvolution(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, [
  2365.           0, -0.25, 0,
  2366.           -0.25, 2, -0.25,
  2367.           0, -0.25, 0
  2368.         ]);
  2369.         ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
  2370.         break;
  2371.        
  2372.       case 'box':
  2373.         // Box filter - simple averaging (faster but blurrier)
  2374.         ctx.imageSmoothingQuality = 'low';
  2375.         ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  2376.         applyBoxBlur(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, 0.5);
  2377.         ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
  2378.         break;
  2379.        
  2380.       case 'hamming':
  2381.         // Hamming filter - reduces aliasing
  2382.         ctx.imageSmoothingQuality = 'medium';
  2383.         ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  2384.         // Small blur + sharpen to simulate Hamming window
  2385.         applyBoxBlur(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, 0.7);
  2386.         ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
  2387.         applyConvolution(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, [
  2388.           0, -0.2, 0,
  2389.           -0.2, 1.8, -0.2,
  2390.           0, -0.2, 0
  2391.         ]);
  2392.         ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
  2393.         break;
  2394.        
  2395.       case 'gaussian':
  2396.         // Gaussian filter - smooth with soft edges
  2397.         ctx.imageSmoothingQuality = 'medium';
  2398.         ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  2399.         // Apply Gaussian-like blur
  2400.         applyConvolution(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, [
  2401.           1/16, 2/16, 1/16,
  2402.           2/16, 4/16, 2/16,
  2403.           1/16, 2/16, 1/16
  2404.         ]);
  2405.         ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
  2406.         break;
  2407.        
  2408.       default:
  2409.         ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  2410.     }
  2411.   }
  2412.  
  2413.   // Apply sharpening filter to simulate Lanczos
  2414.   function applyLanczosSharpening(canvas, ctx) {
  2415.     const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  2416.     const data = imageData.data;
  2417.     const width = canvas.width;
  2418.     const height = canvas.height;
  2419.    
  2420.     // Create a copy of the image data
  2421.     const original = new Uint8ClampedArray(data);
  2422.    
  2423.     // Apply convolution
  2424.     for (let y = 1; y < height-1; y++) {
  2425.      for (let x = 1; x < width-1; x++) {
  2426.        const idx = (y * width + x) * 4;
  2427.        
  2428.        // Apply sharpening for RGB channels
  2429.        for (let c = 0; c < 3; c++) {
  2430.          const val = 5 * original[idx + c]
  2431.                    - original[idx - 4 + c]
  2432.                    - original[idx + 4 + c]
  2433.                    - original[idx - width * 4 + c]
  2434.                    - original[idx + width * 4 + c];
  2435.          
  2436.          data[idx + c] = Math.min(255, Math.max(0, val));
  2437.        }
  2438.      }
  2439.    }
  2440.    
  2441.    ctx.putImageData(imageData, 0, 0);
  2442.  }
  2443.  
  2444.  // Hermite resampling method
  2445.  function hermiteResize(canvas, ctx, img, width, height) {
  2446.    const widthSource = img.width;
  2447.    const heightSource = img.height;
  2448.    
  2449.    // Create temporary canvas to hold the original image
  2450.    const tempCanvas = document.createElement('canvas');
  2451.    tempCanvas.width = widthSource;
  2452.    tempCanvas.height = heightSource;
  2453.    const tempCtx = tempCanvas.getContext('2d');
  2454.    tempCtx.drawImage(img, 0, 0);
  2455.    
  2456.    const imgData = tempCtx.getImageData(0, 0, widthSource, heightSource).data;
  2457.    
  2458.    // Set target canvas size
  2459.    canvas.width = width;
  2460.    canvas.height = height;
  2461.    
  2462.    // Create output data
  2463.    const outData = ctx.createImageData(width, height);
  2464.    const data = outData.data;
  2465.    
  2466.    // Ratio between original dimensions and new dimensions
  2467.    const ratioWidth = widthSource / width;
  2468.    const ratioHeight = heightSource / height;
  2469.    
  2470.    for (let y = 0; y < height; y++) {
  2471.      for (let x = 0; x < width; x++) {
  2472.        // Get calculated position in source
  2473.        let srcX = x * ratioWidth;
  2474.        let srcY = y * ratioHeight;
  2475.        
  2476.        // Source coordinates
  2477.        let srcX0 = Math.floor(srcX);
  2478.        let srcY0 = Math.floor(srcY);
  2479.        let srcX1 = Math.min(srcX0 + 1, widthSource - 1);
  2480.        let srcY1 = Math.min(srcY0 + 1, heightSource - 1);
  2481.        
  2482.        // Weights
  2483.        let xWeight = srcX - srcX0;
  2484.        let yWeight = srcY - srcY0;
  2485.        
  2486.        // Interpolate colors
  2487.        let i = (y * width + x) * 4;
  2488.        
  2489.        for (let c = 0; c < 4; c++) {
  2490.          let idx00 = (srcY0 * widthSource + srcX0) * 4 + c;
  2491.          let idx10 = (srcY0 * widthSource + srcX1) * 4 + c;
  2492.          let idx01 = (srcY1 * widthSource + srcX0) * 4 + c;
  2493.          let idx11 = (srcY1 * widthSource + srcX1) * 4 + c;
  2494.          
  2495.          // Hermite interpolation
  2496.          data[i + c] = Math.round(
  2497.            imgData[idx00] * (1 - xWeight) * (1 - yWeight) +
  2498.            imgData[idx10] * xWeight * (1 - yWeight) +
  2499.            imgData[idx01] * (1 - xWeight) * yWeight +
  2500.            imgData[idx11] * xWeight * yWeight
  2501.          );
  2502.        }
  2503.      }
  2504.    }
  2505.    
  2506.    ctx.putImageData(outData, 0, 0);
  2507.  }
  2508.  
  2509.  // Helper function to get format info
  2510.  function getFormatInfo(formatValue) {
  2511.    const formatMap = {
  2512.      'png': { mime: 'image/png', extension: 'png' },
  2513.      'jpg': { mime: 'image/jpeg', extension: 'jpg' },
  2514.      'jpeg': { mime: 'image/jpeg', extension: 'jpeg' },
  2515.      'webp': { mime: 'image/webp', extension: 'webp' }
  2516.    };
  2517.    
  2518.    return formatMap[formatValue] || formatMap['png']; // Default to PNG
  2519.  }
  2520.  
  2521.  // Convert image to selected format
  2522.  function convertImageFormat(dataUrl, formatValue) {
  2523.    return new Promise((resolve) => {
  2524.       const formatInfo = getFormatInfo(formatValue);
  2525.      
  2526.       const img = new Image();
  2527.       img.onload = function() {
  2528.         const canvas = document.createElement('canvas');
  2529.         canvas.width = img.width;
  2530.         canvas.height = img.height;
  2531.         const ctx = canvas.getContext('2d');
  2532.        
  2533.         // If it's a PNG, we need to draw on a white background for JPG/WEBP conversion
  2534.         if (formatInfo.mime !== 'image/png') {
  2535.           ctx.fillStyle = 'white';
  2536.           ctx.fillRect(0, 0, canvas.width, canvas.height);
  2537.         }
  2538.        
  2539.         ctx.drawImage(img, 0, 0);
  2540.        
  2541.         // Convert to the selected format (use 0.9 quality for lossy formats)
  2542.         const newDataUrl = canvas.toDataURL(formatInfo.mime, 0.9);
  2543.        
  2544.         resolve({ dataUrl: newDataUrl, formatInfo });
  2545.       };
  2546.       img.src = dataUrl;
  2547.     });
  2548.   }
  2549.  
  2550.   function displayGallery() {
  2551.     galleryEl.innerHTML = '';
  2552.    
  2553.     processedImages.forEach((img, index) => {
  2554.       const galleryItem = document.createElement('div');
  2555.       galleryItem.style.cssText = 'border: 1px solid #ddd; border-radius: 4px; overflow: hidden; display: flex; flex-direction: column;';
  2556.      
  2557.       const imgEl = document.createElement('img');
  2558.       imgEl.src = img.dataUrl;
  2559.       imgEl.alt = img.name;
  2560.       imgEl.style.cssText = 'width: 100%; max-height: 200px; object-fit: contain;';
  2561.      
  2562.       const infoEl = document.createElement('div');
  2563.       infoEl.style.cssText = 'padding: 10px; background: #f9f9f9; font-size: 12px;';
  2564.       infoEl.innerHTML = `
  2565.         <div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${img.name}</div>
  2566.         <div>Original: ${img.originalSize}</div>
  2567.         <div>New: ${img.newSize} (${img.scale})</div>
  2568.         <div>Method: ${img.method}</div>
  2569.         <div>Effect: ${img.effect}</div>
  2570.       `;
  2571.      
  2572.       const downloadBtn = document.createElement('div');
  2573.       downloadBtn.style.cssText = 'background: #2196F3; color: white; padding: 8px; text-align: center; cursor: pointer;';
  2574.       downloadBtn.textContent = 'Download';
  2575.       downloadBtn.onclick = () => downloadSingleImage(img);
  2576.      
  2577.       galleryItem.appendChild(imgEl);
  2578.       galleryItem.appendChild(infoEl);
  2579.       galleryItem.appendChild(downloadBtn);
  2580.       galleryEl.appendChild(galleryItem);
  2581.     });
  2582.   }
  2583.  
  2584.   async function downloadSingleImage(img) {
  2585.     const scaleValue = sizeSelect.value;
  2586.     let format = imageFormatSelectEl.value;
  2587.    
  2588.     // Ensure format is valid for large scales
  2589.     if (parseFloat(scaleValue) >= 3.0 || scaleValue === '3840x2160' || scaleValue === '7680x4320') {
  2590.       if (format !== 'jpg' && format !== 'webp') {
  2591.        format = 'jpg';
  2592.         imageFormatSelectEl.value = 'jpg';
  2593.       }
  2594.     }
  2595.    
  2596.     const { dataUrl, formatInfo } = await convertImageFormat(img.dataUrl, format);
  2597.    
  2598.     const a = document.createElement('a');
  2599.     a.href = dataUrl;
  2600.     a.download = `resized_${img.scale.toString().replace('x', '_')}_${img.name.split('.')[0]}.${formatInfo.extension}`;
  2601.     document.body.appendChild(a);
  2602.     a.click();
  2603.     document.body.removeChild(a);
  2604.   }
  2605.  
  2606.   async function downloadAllImages() {
  2607.     if (processedImages.length === 0) return;
  2608.    
  2609.     downloadAllBtn.textContent = '⏳ Downloading...';
  2610.     downloadAllBtn.disabled = true;
  2611.    
  2612.     const scaleValue = sizeSelect.value;
  2613.     let format = imageFormatSelectEl.value;
  2614.    
  2615.     // Ensure format is valid for large scales
  2616.     if (parseFloat(scaleValue) >= 3.0 || scaleValue === '3840x2160' || scaleValue === '7680x4320') {
  2617.       if (format !== 'jpg' && format !== 'webp') {
  2618.        format = 'jpg';
  2619.         imageFormatSelectEl.value = 'jpg';
  2620.       }
  2621.     }
  2622.    
  2623.     // Download each image individually
  2624.     for (const img of processedImages) {
  2625.       await downloadSingleImage(img);
  2626.     }
  2627.    
  2628.     downloadAllBtn.textContent = 'Download All';
  2629.     downloadAllBtn.disabled = false;
  2630.   }
  2631.  
  2632.   // Initialize format options based on selected size
  2633.   updateFormatOptions();
  2634. </script>
  2635. <br /><body>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement