Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Made by (perchance.org) User UFO (me) Using GPT-4. You're welcome.
- Working example. https://perchance.org/ufo-image-resizer-utility
- Automatic image upscaler for text to image generators. 💜
- <h5>How to instal into your image generator?</h>
- First xD Make "your" image generator.
- In edit mode with new generator window open.
- Look bottom right corner of screen for a ✨
- Click the ✨ and select write new code.
- Tell it...
- "Create image editing site, add the ai image generator and
- configure it with the best possible settings, all render models
- known and apply the correct functions for the menu objects."
- You may have to use another prompt after to add more elements to the
- menus but you might get a generous set of menu objects first time.
- Now that your image generator is working (should be), check menu items
- etc. Go back to the ✨ and tell it this (after pasting the below code).
- "Place a tick box next to the stable diffusion generator's (insert name
- of menu item in your generator you want this box next to).
- When the image generator outputs it's image, if this box is ticked, off by default,
- instead use the settings from the upscale controls area effectsSelectEl, speedSelectEl,
- methodSelectEl and sizeSelectEl settings to automatically upscale the image from the
- image generator and put it back in the window it was supposed to load in when sent
- by the image generator."
- If you/AI did it right you will see the generator actually appears to send the image to the upscaler
- where it is upscaled according to the scaling model/filter settings and outputs the upscaled image.
- You may ned to try a few times to get this first time right.
- (OFC This assuming you know nothing about code and cannot just wisely do it yourself).
- Enables automatic upscaling of your Stable Diffusion/Flux image generator. (400% for example).
- Paste the below code into your html side in edit mode then follow above advice.
- <div class="container" style="max-width: 1000px; margin: 0 auto; font-family: Arial, sans-serif; text-align: left;">
- <div class="upload-section" style="margin-bottom: 20px;">
- <div id="dropAreaEl" style="border: 3px dashed #ccc; border-radius: 8px; padding: 30px; text-align: center; margin-bottom: 20px; transition: border-color 0.3s;">
- <p>Drag & drop (not too many big ones maybe) images here or...<br>
- I am thinking it may be possible for Stable Diffusion to run<br>
- image generator through this upscale process automatically🤯</p>
- <input type="file" id="fileInputEl" multiple accept="image/*" style="display: none;">
- <button id="browseBtn" style="background: #4CAF50; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Browse Files</button>
- </div>
- <div id="previewContainerEl" style="display: flex; flex-wrap: wrap; gap: 10px;"></div>
- </div>
- <div class="controls" style="background: #f5f5f5; padding: 20px; border-radius: 8px; margin-bottom: 20px; display: flex; flex-wrap: wrap; gap: 20px; align-items: start;">
- <div class="control-group" style="min-width: 200px;">
- <label for="sizeSelect">Resize to:</label>
- <select id="sizeSelectEl" style="padding: 10px; border-radius: 4px; border: 1px solid #ddd; width: 100%;">
- <option value="0.1">10%</option>
- <option value="0.2">20%</option>
- <option value="0.3">30%</option>
- <option value="0.4">40%</option>
- <option value="0.5">50%</option>
- <option value="0.6">60%</option>
- <option value="0.7">70%</option>
- <option value="0.8">80%</option>
- <option value="0.9">90%</option>
- <option value="1.0">100%</option>
- <option value="1.1">110%</option>
- <option value="1.2">120%</option>
- <option value="1.3">130%</option>
- <option value="1.4">140%</option>
- <option value="1.5">150%</option>
- <option value="1.6">160%</option>
- <option value="1.7">170%</option>
- <option value="1.8">180%</option>
- <option value="1.9">190%</option>
- <option value="2.0" selected>200%</option>
- <option value="3.0">300%</option>
- <option value="4.0">400%</option>
- </select>
- </div>
- <div class="control-group" style="min-width: 200px;">
- <label for="methodSelect">Scaling Method:</label>
- <select id="methodSelectEl" style="padding: 10px; border-radius: 4px; border: 1px solid #ddd; width: 100%;">
- <option value="pixelated">Nearest Neighbor (Pixelated)</option>
- <option value="bilinear">Bilinear (Smooth)</option>
- <option value="bicubic" selected>Bicubic (Higher Quality)</option>
- <option value="lanczos">Lanczos (Sharper Details)</option>
- <option value="hermite">Hermite (Smooth Curves)</option>
- <option value="mitchell">Mitchell-Netravali (Balanced)</option>
- <option value="catmull-rom">Catmull-Rom (Preserve Edges)</option>
- <option value="box">Box (Fast Scaling)</option>
- <option value="hamming">Hamming (Reduce Aliasing)</option>
- <option value="gaussian">Gaussian (Soft Edges)</option>
- </select>
- </div>
- <div class="control-group" style="min-width: 200px;">
- <label for="effectsSelect">Pre-processing Effects:</label>
- <select id="effectsSelectEl" style="padding: 10px; border-radius: 4px; border: 1px solid #ddd; width: 100%;">
- <option value="none">None</option>
- <option value="blur-light">Fine Blur</option>
- <option value="blur-strong">Strong Blur</option>
- <option value="sharpen-light" selected>Light Sharpen</option>
- <option value="sharpen-strong">Strong Sharpen</option>
- <option value="edge-enhance">Edge Enhance</option>
- <option value="emboss">Emboss</option>
- <option value="grayscale">Grayscale</option>
- <option value="sepia">Sepia</option>
- <option value="invert">Invert Colors</option>
- <option value="brighten">Brighten</option>
- <option value="darken">Darken</option>
- <option value="increase-contrast">Increase Contrast</option>
- <option value="decrease-contrast">Decrease Contrast</option>
- <option value="noise-reduction">Noise Reduction</option>
- <option value="posterize">Posterize</option>
- </select>
- </div>
- <div class="control-group" style="min-width: 200px;">
- <label for="speedSelectEl">Rendering Speed:</label>
- <select id="speedSelectEl" style="padding: 10px; border-radius: 4px; border: 1px solid #ddd; width: 100%;">
- <option value="slow">Slow (25% resources)</option>
- <option value="medium" selected>Medium (50% resources)</option>
- <option value="fast">Fast (90% resources)</option>
- </select>
- </div>
- <div class="control-group" style="display: flex; align-items: flex-end; min-height: 70px;">
- <button id="processBtn" disabled style="background: #4CAF50; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Process Images</button>
- </div>
- </div>
- <div id="resultsCtn" hidden style="margin-top: 30px;">
- <h2>Processed Images</h2>
- <div style="display: flex; align-items: center; margin-bottom: 15px; gap: 10px;">
- <button id="downloadAllBtn" style="background: #2196F3; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Download All</button>
- <div style="display: flex; align-items: center;">
- <label for="imageFormatSelectEl" style="margin-right: 5px;">Format:</label>
- <select id="imageFormatSelectEl" style="padding: 8px; border-radius: 4px; border: 1px solid #ddd;">
- <option value="png">PNG</option>
- <option value="jpg">JPG</option>
- <option value="jpeg">JPEG</option>
- <option value="webp">WebP</option>
- </select>
- </div>
- </div>
- <div id="galleryEl" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px;"></div>
- </div>
- </div>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
- <script>
- // Global variables to store images
- let uploadedImages = [];
- let processedImages = [];
- // DOM elements
- const dropArea = dropAreaEl;
- const previewContainer = previewContainerEl;
- const sizeSelect = sizeSelectEl;
- const methodSelect = methodSelectEl;
- const effectsSelect = effectsSelectEl;
- const speedSelect = speedSelectEl;
- // Set up event listeners
- browseBtn.addEventListener('click', () => fileInputEl.click());
- fileInputEl.addEventListener('change', handleFileSelect);
- processBtn.addEventListener('click', processImages);
- downloadAllBtn.addEventListener('click', downloadAllImages);
- sizeSelect.addEventListener('change', updateFormatOptions);
- // Set up drag and drop
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
- dropArea.addEventListener(eventName, preventDefaults, false);
- });
- function preventDefaults(e) {
- e.preventDefault();
- e.stopPropagation();
- }
- ['dragenter', 'dragover'].forEach(eventName => {
- dropArea.addEventListener(eventName, () => {
- dropArea.style.borderColor = '#4CAF50';
- dropArea.style.backgroundColor = 'rgba(76, 175, 80, 0.1)';
- }, false);
- });
- ['dragleave', 'drop'].forEach(eventName => {
- dropArea.addEventListener(eventName, () => {
- dropArea.style.borderColor = '#ccc';
- dropArea.style.backgroundColor = '';
- }, false);
- });
- dropArea.addEventListener('drop', handleDrop, false);
- function handleDrop(e) {
- const dt = e.dataTransfer;
- const files = dt.files;
- handleFiles(files);
- }
- // Handle the file selection
- function handleFileSelect(e) {
- const files = e.target.files;
- handleFiles(files);
- }
- function handleFiles(files) {
- if (files.length === 0) return;
- Array.from(files).forEach(file => {
- if (!file.type.match('image.*')) return;
- // Check if image already uploaded
- if (uploadedImages.some(img => img.name === file.name && img.size === file.size)) {
- return; // Skip duplicates
- }
- const reader = new FileReader();
- reader.onload = function(e) {
- const img = new Image();
- img.src = e.target.result;
- img.onload = function() {
- uploadedImages.push({
- name: file.name,
- size: file.size,
- type: file.type,
- dataUrl: e.target.result,
- width: img.width,
- height: img.height
- });
- updatePreview();
- updateProcessButton();
- };
- };
- reader.readAsDataURL(file);
- });
- }
- function updatePreview() {
- previewContainer.innerHTML = '';
- uploadedImages.forEach((img, index) => {
- const previewItem = document.createElement('div');
- previewItem.style.cssText = 'position: relative; width: 100px; height: 100px; border-radius: 4px; overflow: hidden;';
- const imgEl = document.createElement('img');
- imgEl.src = img.dataUrl;
- imgEl.alt = img.name;
- imgEl.style.cssText = 'width: 100%; height: 100%; object-fit: cover;';
- const removeBtn = document.createElement('button');
- removeBtn.style.cssText = 'position: absolute; top: 5px; right: 5px; background: rgba(0, 0, 0, 0.5); color: white; border: none; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; cursor: pointer;';
- removeBtn.textContent = '×';
- removeBtn.onclick = () => removeImage(index);
- previewItem.appendChild(imgEl);
- previewItem.appendChild(removeBtn);
- previewContainer.appendChild(previewItem);
- });
- }
- function removeImage(index) {
- uploadedImages.splice(index, 1);
- updatePreview();
- updateProcessButton();
- }
- function updateProcessButton() {
- processBtn.disabled = uploadedImages.length === 0;
- }
- // Function to update format options based on selected size
- function updateFormatOptions() {
- const scale = parseFloat(sizeSelect.value);
- const formatSelect = imageFormatSelectEl;
- // Get all format options
- const options = formatSelect.querySelectorAll('option');
- // Store current selected format
- const currentFormat = formatSelect.value;
- // Check if the selected scale is 300% or 400%
- if (scale >= 3.0) {
- // Only allow jpg and webp for large scales
- for (const option of options) {
- if (option.value === 'jpg' || option.value === 'webp') {
- option.disabled = false;
- } else {
- option.disabled = true;
- }
- }
- // If current format is not allowed, change to jpg
- if (currentFormat !== 'jpg' && currentFormat !== 'webp') {
- formatSelect.value = 'jpg';
- }
- } else {
- // Enable all formats for other scales
- for (const option of options) {
- option.disabled = false;
- }
- }
- }
- // Process the images with the selected options
- function processImages() {
- if (uploadedImages.length === 0) return;
- const scale = parseFloat(sizeSelect.value);
- const method = methodSelect.value;
- const effect = effectsSelect.value;
- const speed = speedSelect.value;
- processBtn.disabled = true;
- processBtn.textContent = '⏳ Processing...';
- // Clear previous results
- processedImages = [];
- galleryEl.innerHTML = '';
- // Process each image (with small delay to allow UI to update)
- setTimeout(() => {
- const promises = uploadedImages.map(img => processImage(img, scale, method, effect, speed));
- Promise.all(promises).then(() => {
- processBtn.textContent = 'Process Images';
- processBtn.disabled = false;
- resultsCtn.hidden = false;
- // Display processed images in gallery
- displayGallery();
- // Update format options based on selected size
- updateFormatOptions();
- });
- }, 100);
- }
- function processImage(img, scale, method, effect, speed) {
- return new Promise((resolve) => {
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
- // Calculate new dimensions
- const newWidth = Math.round(img.width * scale);
- const newHeight = Math.round(img.height * scale);
- // Create a temporary canvas for pre-processing
- const tempCanvas = document.createElement('canvas');
- const tempCtx = tempCanvas.getContext('2d');
- tempCanvas.width = img.width;
- tempCanvas.height = img.height;
- // Load the image
- const imgEl = new Image();
- imgEl.onload = function() {
- // Apply rendering speed settings for resource usage control
- const speedSettings = {
- 'slow': { chunkSize: 50, delay: 100 }, // 25% resources
- 'medium': { chunkSize: 100, delay: 20 }, // 50% resources
- 'fast': { chunkSize: 500, delay: 1 } // 90% resources
- };
- const { chunkSize, delay } = speedSettings[speed] || speedSettings.medium;
- // First, draw the original image on the temp canvas
- tempCtx.drawImage(imgEl, 0, 0);
- // Apply pre-processing effect if selected (with throttling)
- if (effect !== 'none') {
- // Process the effect with throttling based on speed
- applyEffectWithThrottling(tempCanvas, tempCtx, effect, speed, () => {
- // After effect is applied, resize the image
- // Set final canvas size
- canvas.width = newWidth;
- canvas.height = newHeight;
- // Resize the pre-processed image using the selected method
- applyResizeMethodWithThrottling(tempCanvas, canvas, ctx, method, scale, speed, () => {
- // Get the processed image data
- const processedDataUrl = canvas.toDataURL(img.type || 'image/jpeg');
- // Create processed image object
- const processedImage = {
- name: img.name,
- originalSize: `${img.width}x${img.height}`,
- newSize: `${newWidth}x${newHeight}`,
- scale: Math.round(scale * 100),
- method: getMethodName(method),
- effect: getEffectName(effect),
- dataUrl: processedDataUrl
- };
- // Store the processed image
- processedImages.push(processedImage);
- // Resolve with the processed image
- resolve(processedImage);
- });
- });
- } else {
- // No effect to apply, just resize
- // Set final canvas size
- canvas.width = newWidth;
- canvas.height = newHeight;
- // Resize the pre-processed image using the selected method
- applyResizeMethodWithThrottling(tempCanvas, canvas, ctx, method, scale, speed, () => {
- // Get the processed image data
- const processedDataUrl = canvas.toDataURL(img.type || 'image/jpeg');
- // Create processed image object
- const processedImage = {
- name: img.name,
- originalSize: `${img.width}x${img.height}`,
- newSize: `${newWidth}x${newHeight}`,
- scale: Math.round(scale * 100),
- method: getMethodName(method),
- effect: getEffectName(effect),
- dataUrl: processedDataUrl
- };
- // Store the processed image
- processedImages.push(processedImage);
- // Resolve with the processed image
- resolve(processedImage);
- });
- }
- };
- imgEl.src = img.dataUrl;
- });
- }
- // Apply effect with throttling based on selected speed
- function applyEffectWithThrottling(canvas, ctx, effect, speed, callback) {
- const width = canvas.width;
- const height = canvas.height;
- const imageData = ctx.getImageData(0, 0, width, height);
- // Determine chunk size and delay based on speed
- const speedSettings = {
- 'slow': { chunkSize: Math.floor(height * 0.05), delay: 20 }, // Process 5% of image height at a time
- 'medium': { chunkSize: Math.floor(height * 0.2), delay: 10 }, // Process 20% of image height at a time
- 'fast': { chunkSize: height, delay: 0 } // Process entire image at once
- };
- const { chunkSize, delay } = speedSettings[speed] || speedSettings.medium;
- // For some effects that require the entire image data, we can't chunk
- // process them, so we need to apply them directly
- const globalEffects = [
- 'blur-light', 'blur-strong', 'sharpen-light', 'sharpen-strong',
- 'edge-enhance', 'emboss', 'noise-reduction'
- ];
- if (globalEffects.includes(effect)) {
- // For effects that require whole-image processing
- setTimeout(() => {
- applyEffect(canvas, ctx, effect);
- callback();
- }, delay);
- } else {
- // For effects that can be applied line by line
- let currentRow = 0;
- function processChunk() {
- if (currentRow >= height) {
- ctx.putImageData(imageData, 0, 0);
- callback();
- return;
- }
- const endRow = Math.min(currentRow + chunkSize, height);
- // Extract the chunk of data that needs processing
- const chunkImageData = ctx.getImageData(0, currentRow, width, endRow - currentRow);
- // Apply the effect to this chunk
- switch (effect) {
- case 'grayscale':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- const avg = (chunkImageData.data[i] + chunkImageData.data[i + 1] + chunkImageData.data[i + 2]) / 3;
- chunkImageData.data[i] = avg;
- chunkImageData.data[i + 1] = avg;
- chunkImageData.data[i + 2] = avg;
- }
- break;
- case 'sepia':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- const r = chunkImageData.data[i];
- const g = chunkImageData.data[i + 1];
- const b = chunkImageData.data[i + 2];
- chunkImageData.data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
- chunkImageData.data[i + 1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
- chunkImageData.data[i + 2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
- }
- break;
- case 'invert':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- chunkImageData.data[i] = 255 - chunkImageData.data[i];
- chunkImageData.data[i + 1] = 255 - chunkImageData.data[i + 1];
- chunkImageData.data[i + 2] = 255 - chunkImageData.data[i + 2];
- }
- break;
- case 'brighten':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- chunkImageData.data[i] = Math.min(255, chunkImageData.data[i] * 1.2);
- chunkImageData.data[i + 1] = Math.min(255, chunkImageData.data[i + 1] * 1.2);
- chunkImageData.data[i + 2] = Math.min(255, chunkImageData.data[i + 2] * 1.2);
- }
- break;
- case 'darken':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- chunkImageData.data[i] = chunkImageData.data[i] * 0.8;
- chunkImageData.data[i + 1] = chunkImageData.data[i + 1] * 0.8;
- chunkImageData.data[i + 2] = chunkImageData.data[i + 2] * 0.8;
- }
- break;
- case 'increase-contrast':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- for (let j = 0; j < 3; j++) {
- chunkImageData.data[i + j] = ((chunkImageData.data[i + j] / 255 - 0.5) * 1.4 + 0.5) * 255;
- if (chunkImageData.data[i + j] < 0) chunkImageData.data[i + j] = 0;
- if (chunkImageData.data[i + j] > 255) chunkImageData.data[i + j] = 255;
- }
- }
- break;
- case 'decrease-contrast':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- for (let j = 0; j < 3; j++) {
- chunkImageData.data[i + j] = ((chunkImageData.data[i + j] / 255 - 0.5) * 0.7 + 0.5) * 255;
- if (chunkImageData.data[i + j] < 0) chunkImageData.data[i + j] = 0;
- if (chunkImageData.data[i + j] > 255) chunkImageData.data[i + j] = 255;
- }
- }
- break;
- case 'posterize':
- for (let i = 0; i < chunkImageData.data.length; i += 4) {
- const levels = 5;
- chunkImageData.data[i] = Math.floor(chunkImageData.data[i] / 255 * levels) / levels * 255;
- chunkImageData.data[i + 1] = Math.floor(chunkImageData.data[i + 1] / 255 * levels) / levels * 255;
- chunkImageData.data[i + 2] = Math.floor(chunkImageData.data[i + 2] / 255 * levels) / levels * 255;
- }
- break;
- }
- // Put the processed chunk back
- ctx.putImageData(chunkImageData, 0, currentRow);
- // Update row position
- currentRow = endRow;
- // Schedule the next chunk with delay
- setTimeout(processChunk, delay);
- }
- // Start processing chunks
- processChunk();
- }
- }
- // Apply resize method with throttling based on selected speed
- function applyResizeMethodWithThrottling(img, canvas, ctx, method, scale, speed, callback) {
- // For simple methods that don't require special throttling, use the browser's built-in scaling
- if (method === 'pixelated' || method === 'bilinear' || method === 'bicubic') {
- ctx.imageSmoothingEnabled = method !== 'pixelated';
- if (method === 'bilinear') ctx.imageSmoothingQuality = 'low';
- if (method === 'bicubic') ctx.imageSmoothingQuality = 'medium';
- // Draw the image according to selected speed
- if (speed === 'fast') {
- // For fast mode, draw all at once
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- callback();
- } else {
- // For slow/medium modes, break rendering into chunks
- const speedSettings = {
- 'slow': { chunkSize: Math.floor(canvas.height * 0.1), delay: 50 },
- 'medium': { chunkSize: Math.floor(canvas.height * 0.3), delay: 10 }
- };
- const { chunkSize, delay } = speedSettings[speed] || speedSettings.medium;
- let currentRow = 0;
- function drawChunk() {
- if (currentRow >= canvas.height) {
- callback();
- return;
- }
- const chunkHeight = Math.min(chunkSize, canvas.height - currentRow);
- const sourceYRatio = img.height / canvas.height;
- // Draw a slice of the image
- ctx.drawImage(
- img,
- 0, Math.floor(currentRow * sourceYRatio), // Source x, y
- img.width, Math.ceil(chunkHeight * sourceYRatio), // Source width, height
- 0, currentRow, // Destination x, y
- canvas.width, chunkHeight // Destination width, height
- );
- // Move to next chunk
- currentRow += chunkHeight;
- // Schedule next chunk with delay
- setTimeout(drawChunk, delay);
- }
- // Start drawing chunks
- drawChunk();
- }
- } else {
- // For advanced methods, use existing implementation with throttling
- if (speed === 'fast') {
- // Fast mode - process all at once
- applyResizeMethod(img, canvas, ctx, method, scale);
- callback();
- } else {
- // Add a small artificial delay to simulate resource throttling
- setTimeout(() => {
- applyResizeMethod(img, canvas, ctx, method, scale);
- callback();
- }, speed === 'slow' ? 200 : 50);
- }
- }
- }
- // Apply the selected pre-processing effect to the canvas
- function applyEffect(canvas, ctx, effect) {
- const width = canvas.width;
- const height = canvas.height;
- const imageData = ctx.getImageData(0, 0, width, height);
- const data = imageData.data;
- switch (effect) {
- case 'blur-light':
- applyBoxBlur(imageData, width, height, 1);
- break;
- case 'blur-strong':
- applyBoxBlur(imageData, width, height, 3);
- break;
- case 'sharpen-light':
- applyConvolution(imageData, width, height, [
- 0, -0.5, 0,
- -0.5, 3, -0.5,
- 0, -0.5, 0
- ]);
- break;
- case 'sharpen-strong':
- applyConvolution(imageData, width, height, [
- -0.5, -1, -0.5,
- -1, 7, -1,
- -0.5, -1, -0.5
- ]);
- break;
- case 'edge-enhance':
- applyConvolution(imageData, width, height, [
- 0, -1, 0,
- -1, 5, -1,
- 0, -1, 0
- ]);
- break;
- case 'emboss':
- applyConvolution(imageData, width, height, [
- -2, -1, 0,
- -1, 1, 1,
- 0, 1, 2
- ]);
- break;
- case 'grayscale':
- for (let i = 0; i < data.length; i += 4) {
- const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
- data[i] = avg;
- data[i + 1] = avg;
- data[i + 2] = avg;
- }
- break;
- case 'sepia':
- for (let i = 0; i < data.length; i += 4) {
- const r = data[i];
- const g = data[i + 1];
- const b = data[i + 2];
- data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
- data[i + 1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
- data[i + 2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
- }
- break;
- case 'invert':
- for (let i = 0; i < data.length; i += 4) {
- data[i] = 255 - data[i];
- data[i + 1] = 255 - data[i + 1];
- data[i + 2] = 255 - data[i + 2];
- }
- break;
- case 'brighten':
- for (let i = 0; i < data.length; i += 4) {
- data[i] = Math.min(255, data[i] * 1.2);
- data[i + 1] = Math.min(255, data[i + 1] * 1.2);
- data[i + 2] = Math.min(255, data[i + 2] * 1.2);
- }
- break;
- case 'darken':
- for (let i = 0; i < data.length; i += 4) {
- data[i] = data[i] * 0.8;
- data[i + 1] = data[i + 1] * 0.8;
- data[i + 2] = data[i + 2] * 0.8;
- }
- break;
- case 'increase-contrast':
- for (let i = 0; i < data.length; i += 4) {
- // Apply simple contrast adjustment
- for (let j = 0; j < 3; j++) {
- data[i + j] = ((data[i + j] / 255 - 0.5) * 1.4 + 0.5) * 255;
- if (data[i + j] < 0) data[i + j] = 0;
- if (data[i + j] > 255) data[i + j] = 255;
- }
- }
- break;
- case 'decrease-contrast':
- for (let i = 0; i < data.length; i += 4) {
- // Apply simple contrast reduction
- for (let j = 0; j < 3; j++) {
- data[i + j] = ((data[i + j] / 255 - 0.5) * 0.7 + 0.5) * 255;
- if (data[i + j] < 0) data[i + j] = 0;
- if (data[i + j] > 255) data[i + j] = 255;
- }
- }
- break;
- case 'noise-reduction':
- applyMedianFilter(imageData, width, height);
- break;
- case 'posterize':
- for (let i = 0; i < data.length; i += 4) {
- const levels = 5;
- data[i] = Math.floor(data[i] / 255 * levels) / levels * 255;
- data[i + 1] = Math.floor(data[i + 1] / 255 * levels) / levels * 255;
- data[i + 2] = Math.floor(data[i + 2] / 255 * levels) / levels * 255;
- }
- break;
- }
- ctx.putImageData(imageData, 0, 0);
- }
- // Apply convolution (used for many effects like blur, sharpen, etc.)
- function applyConvolution(imageData, width, height, kernel) {
- const data = imageData.data;
- const buff = new Uint8ClampedArray(data);
- const kSize = Math.sqrt(kernel.length);
- const kRadius = Math.floor(kSize / 2);
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- const idx = (y * width + x) * 4;
- let r = 0, g = 0, b = 0;
- // Apply kernel
- for (let ky = 0; ky < kSize; ky++) {
- for (let kx = 0; kx < kSize; kx++) {
- const kIdx = ky * kSize + kx;
- const px = Math.min(width - 1, Math.max(0, x + kx - kRadius));
- const py = Math.min(height - 1, Math.max(0, y + ky - kRadius));
- const pIdx = (py * width + px) * 4;
- r += buff[pIdx] * kernel[kIdx];
- g += buff[pIdx + 1] * kernel[kIdx];
- b += buff[pIdx + 2] * kernel[kIdx];
- }
- }
- // Set new values
- data[idx] = Math.min(255, Math.max(0, r));
- data[idx + 1] = Math.min(255, Math.max(0, g));
- data[idx + 2] = Math.min(255, Math.max(0, b));
- }
- }
- }
- // Box blur implementation
- function applyBoxBlur(imageData, width, height, radius) {
- const data = imageData.data;
- const buff = new Uint8ClampedArray(data);
- // Horizontal pass
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- let r = 0, g = 0, b = 0, count = 0;
- for (let i = Math.max(0, x - radius); i <= Math.min(width - 1, x + radius); i++) {
- const idx = (y * width + i) * 4;
- r += buff[idx];
- g += buff[idx + 1];
- b += buff[idx + 2];
- count++;
- }
- const idx = (y * width + x) * 4;
- data[idx] = r / count;
- data[idx + 1] = g / count;
- data[idx + 2] = b / count;
- }
- }
- // Copy for vertical pass
- for (let i = 0; i < data.length; i++) {
- buff[i] = data[i];
- }
- // Vertical pass
- for (let x = 0; x < width; x++) {
- for (let y = 0; y < height; y++) {
- let r = 0, g = 0, b = 0, count = 0;
- for (let j = Math.max(0, y - radius); j <= Math.min(height - 1, y + radius); j++) {
- const idx = (j * width + x) * 4;
- r += buff[idx];
- g += buff[idx + 1];
- b += buff[idx + 2];
- count++;
- }
- const idx = (y * width + x) * 4;
- data[idx] = r / count;
- data[idx + 1] = g / count;
- data[idx + 2] = b / count;
- }
- }
- }
- // Median filter (good for noise reduction)
- function applyMedianFilter(imageData, width, height) {
- const data = imageData.data;
- const buff = new Uint8ClampedArray(data);
- const radius = 1;
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- const idx = (y * width + x) * 4;
- // For each channel (R,G,B)
- for (let c = 0; c < 3; c++) {
- let values = [];
- // Gather values in the neighborhood
- for (let dy = -radius; dy <= radius; dy++) {
- for (let dx = -radius; dx <= radius; dx++) {
- const nx = Math.min(width - 1, Math.max(0, x + dx));
- const ny = Math.min(height - 1, Math.max(0, y + dy));
- const nIdx = (ny * width + nx) * 4 + c;
- values.push(buff[nIdx]);
- }
- }
- // Sort and find median
- values.sort((a, b) => a - b);
- data[idx + c] = values[Math.floor(values.length / 2)];
- }
- }
- }
- }
- function getMethodName(method) {
- const methods = {
- 'pixelated': 'Nearest Neighbor',
- 'bilinear': 'Bilinear',
- 'bicubic': 'Bicubic',
- 'lanczos': 'Lanczos',
- 'hermite': 'Hermite',
- 'mitchell': 'Mitchell-Netravali',
- 'catmull-rom': 'Catmull-Rom',
- 'box': 'Box',
- 'hamming': 'Hamming',
- 'gaussian': 'Gaussian'
- };
- return methods[method] || method;
- }
- function getEffectName(effect) {
- const effects = {
- 'none': 'None',
- 'blur-light': 'Fine Blur',
- 'blur-strong': 'Strong Blur',
- 'sharpen-light': 'Light Sharpen',
- 'sharpen-strong': 'Strong Sharpen',
- 'edge-enhance': 'Edge Enhancement',
- 'emboss': 'Emboss',
- 'grayscale': 'Grayscale',
- 'sepia': 'Sepia',
- 'invert': 'Invert Colors',
- 'brighten': 'Brighten',
- 'darken': 'Darken',
- 'increase-contrast': 'Increased Contrast',
- 'decrease-contrast': 'Decreased Contrast',
- 'noise-reduction': 'Noise Reduction',
- 'posterize': 'Posterize'
- };
- return effects[effect] || effect;
- }
- function applyResizeMethod(img, canvas, ctx, method, scale) {
- // Set default quality
- ctx.imageSmoothingEnabled = true;
- switch (method) {
- case 'pixelated':
- ctx.imageSmoothingEnabled = false;
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- break;
- case 'bilinear':
- ctx.imageSmoothingQuality = 'low';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- break;
- case 'bicubic':
- ctx.imageSmoothingQuality = 'medium';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- break;
- case 'lanczos':
- ctx.imageSmoothingQuality = 'high';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- // Apply sharpening filter for lanczos-like effect
- applyLanczosSharpening(canvas, ctx);
- break;
- case 'hermite':
- // Custom hermite resampling implementation
- hermiteResize(canvas, ctx, img, canvas.width, canvas.height);
- break;
- case 'mitchell':
- // Mitchell-Netravali filter - similar to bicubic but with less ringing
- ctx.imageSmoothingQuality = 'high';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- // Apply slight sharpening for Mitchell-like effect
- applyConvolution(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, [
- 0, -0.15, 0,
- -0.15, 1.6, -0.15,
- 0, -0.15, 0
- ]);
- ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
- break;
- case 'catmull-rom':
- // Catmull-Rom spline filter - preserves edges better than bicubic
- ctx.imageSmoothingQuality = 'high';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- // Apply edge-preserving enhancement for Catmull-Rom-like effect
- applyConvolution(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, [
- 0, -0.25, 0,
- -0.25, 2, -0.25,
- 0, -0.25, 0
- ]);
- ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
- break;
- case 'box':
- // Box filter - simple averaging (faster but blurrier)
- ctx.imageSmoothingQuality = 'low';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- applyBoxBlur(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, 0.5);
- ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
- break;
- case 'hamming':
- // Hamming filter - reduces aliasing
- ctx.imageSmoothingQuality = 'medium';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- // Small blur + sharpen to simulate Hamming window
- applyBoxBlur(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, 0.7);
- ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
- applyConvolution(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, [
- 0, -0.2, 0,
- -0.2, 1.8, -0.2,
- 0, -0.2, 0
- ]);
- ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
- break;
- case 'gaussian':
- // Gaussian filter - smooth with soft edges
- ctx.imageSmoothingQuality = 'medium';
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- // Apply Gaussian-like blur
- applyConvolution(ctx.getImageData(0, 0, canvas.width, canvas.height), canvas.width, canvas.height, [
- 1/16, 2/16, 1/16,
- 2/16, 4/16, 2/16,
- 1/16, 2/16, 1/16
- ]);
- ctx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
- break;
- default:
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- }
- }
- // Apply sharpening filter to simulate Lanczos
- function applyLanczosSharpening(canvas, ctx) {
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
- const data = imageData.data;
- const width = canvas.width;
- const height = canvas.height;
- // Create a copy of the image data
- const original = new Uint8ClampedArray(data);
- // Apply convolution
- for (let y = 1; y < height-1; y++) {
- for (let x = 1; x < width-1; x++) {
- const idx = (y * width + x) * 4;
- // Apply sharpening for RGB channels
- for (let c = 0; c < 3; c++) {
- const val = 5 * original[idx + c]
- - original[idx - 4 + c]
- - original[idx + 4 + c]
- - original[idx - width * 4 + c]
- - original[idx + width * 4 + c];
- data[idx + c] = Math.min(255, Math.max(0, val));
- }
- }
- }
- ctx.putImageData(imageData, 0, 0);
- }
- // Hermite resampling method
- function hermiteResize(canvas, ctx, img, width, height) {
- const widthSource = img.width;
- const heightSource = img.height;
- // Create temporary canvas to hold the original image
- const tempCanvas = document.createElement('canvas');
- tempCanvas.width = widthSource;
- tempCanvas.height = heightSource;
- const tempCtx = tempCanvas.getContext('2d');
- tempCtx.drawImage(img, 0, 0);
- const imgData = tempCtx.getImageData(0, 0, widthSource, heightSource).data;
- // Set target canvas size
- canvas.width = width;
- canvas.height = height;
- // Create output data
- const outData = ctx.createImageData(width, height);
- const data = outData.data;
- // Ratio between original dimensions and new dimensions
- const ratioWidth = widthSource / width;
- const ratioHeight = heightSource / height;
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- // Get calculated position in source
- let srcX = x * ratioWidth;
- let srcY = y * ratioHeight;
- // Source coordinates
- let srcX0 = Math.floor(srcX);
- let srcY0 = Math.floor(srcY);
- let srcX1 = Math.min(srcX0 + 1, widthSource - 1);
- let srcY1 = Math.min(srcY0 + 1, heightSource - 1);
- // Weights
- let xWeight = srcX - srcX0;
- let yWeight = srcY - srcY0;
- // Interpolate colors
- let i = (y * width + x) * 4;
- for (let c = 0; c < 4; c++) {
- let idx00 = (srcY0 * widthSource + srcX0) * 4 + c;
- let idx10 = (srcY0 * widthSource + srcX1) * 4 + c;
- let idx01 = (srcY1 * widthSource + srcX0) * 4 + c;
- let idx11 = (srcY1 * widthSource + srcX1) * 4 + c;
- // Hermite interpolation
- data[i + c] = Math.round(
- imgData[idx00] * (1 - xWeight) * (1 - yWeight) +
- imgData[idx10] * xWeight * (1 - yWeight) +
- imgData[idx01] * (1 - xWeight) * yWeight +
- imgData[idx11] * xWeight * yWeight
- );
- }
- }
- }
- ctx.putImageData(outData, 0, 0);
- }
- // Helper function to get format info
- function getFormatInfo(formatValue) {
- const formatMap = {
- 'png': { mime: 'image/png', extension: 'png' },
- 'jpg': { mime: 'image/jpeg', extension: 'jpg' },
- 'jpeg': { mime: 'image/jpeg', extension: 'jpeg' },
- 'webp': { mime: 'image/webp', extension: 'webp' }
- };
- return formatMap[formatValue] || formatMap['png']; // Default to PNG
- }
- // Convert image to selected format
- function convertImageFormat(dataUrl, formatValue) {
- return new Promise((resolve) => {
- const formatInfo = getFormatInfo(formatValue);
- const img = new Image();
- img.onload = function() {
- const canvas = document.createElement('canvas');
- canvas.width = img.width;
- canvas.height = img.height;
- const ctx = canvas.getContext('2d');
- // If it's a PNG, we need to draw on a white background for JPG/WEBP conversion
- if (formatInfo.mime !== 'image/png') {
- ctx.fillStyle = 'white';
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- }
- ctx.drawImage(img, 0, 0);
- // Convert to the selected format (use 0.9 quality for lossy formats)
- const newDataUrl = canvas.toDataURL(formatInfo.mime, 0.9);
- resolve({ dataUrl: newDataUrl, formatInfo });
- };
- img.src = dataUrl;
- });
- }
- function displayGallery() {
- galleryEl.innerHTML = '';
- processedImages.forEach((img, index) => {
- const galleryItem = document.createElement('div');
- galleryItem.style.cssText = 'border: 1px solid #ddd; border-radius: 4px; overflow: hidden; display: flex; flex-direction: column;';
- const imgEl = document.createElement('img');
- imgEl.src = img.dataUrl;
- imgEl.alt = img.name;
- imgEl.style.cssText = 'width: 100%; max-height: 200px; object-fit: contain;';
- const infoEl = document.createElement('div');
- infoEl.style.cssText = 'padding: 10px; background: #f9f9f9; font-size: 12px;';
- infoEl.innerHTML = `
- <div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${img.name}</div>
- <div>Original: ${img.originalSize}</div>
- <div>New: ${img.newSize} (${img.scale}%)</div>
- <div>Method: ${img.method}</div>
- <div>Effect: ${img.effect}</div>
- `;
- const downloadBtn = document.createElement('div');
- downloadBtn.style.cssText = 'background: #2196F3; color: white; padding: 8px; text-align: center; cursor: pointer;';
- downloadBtn.textContent = 'Download';
- downloadBtn.onclick = () => downloadSingleImage(img);
- galleryItem.appendChild(imgEl);
- galleryItem.appendChild(infoEl);
- galleryItem.appendChild(downloadBtn);
- galleryEl.appendChild(galleryItem);
- });
- }
- async function downloadSingleImage(img) {
- const scale = parseFloat(sizeSelect.value);
- let format = imageFormatSelectEl.value;
- // Ensure format is valid for large scales
- if (scale >= 3.0 && format !== 'jpg' && format !== 'webp') {
- format = 'jpg';
- imageFormatSelectEl.value = 'jpg';
- }
- const { dataUrl, formatInfo } = await convertImageFormat(img.dataUrl, format);
- const a = document.createElement('a');
- a.href = dataUrl;
- a.download = `resized_${img.scale}_${img.name.split('.')[0]}.${formatInfo.extension}`;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- }
- async function downloadAllImages() {
- if (processedImages.length === 0) return;
- downloadAllBtn.textContent = '⏳ Downloading...';
- downloadAllBtn.disabled = true;
- const scale = parseFloat(sizeSelect.value);
- let format = imageFormatSelectEl.value;
- // Ensure format is valid for large scales
- if (scale >= 3.0 && format !== 'jpg' && format !== 'webp') {
- format = 'jpg';
- imageFormatSelectEl.value = 'jpg';
- }
- // Download each image individually
- for (const img of processedImages) {
- await downloadSingleImage(img);
- }
- downloadAllBtn.textContent = 'Download All';
- downloadAllBtn.disabled = false;
- }
- // Initialize format options based on selected size
- updateFormatOptions();
- </script>
- <br />
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement