I have created an image tool which crops, scales, resizes. I now want to create a bulk image feature where it will take multiple folders with images inside, extract the images and then creates the a new folder with the original file name and put’s the processed images back into the folders created.
I’m 95% there with creating this tool but I’m stuck on trying to process multiple folders which is where it’s going wrong.
I’ve managed to get it so I can process one folder, extract the images, create a new folder with the original file name and put the processed image back into that folder.
I’m stuck on what to do next. I have tried searching the problem but I think my situation is a bit niche so I didn’t find much. So if anyone has a solution that would be much appreciated.
My specific question is:
How can I modify my current code to correctly process multiple folders, extract the images, and place the processed images back into the respective new folders?
document.addEventListener('DOMContentLoaded', function() {
const folderInput = document.getElementById('folderInput');
folderInput.addEventListener('change', function() {
updateSelectedFilesPreview('folderInput', 'bulk_selectedFilesPreview');
console.log('Folder input changed. Files selected:', folderInput.files.length);
});
});
function processFolderImages() {
const folderInput = document.getElementById('folderInput');
const output = document.getElementById('bulk_output');
const finalSize = 2000;
const maxScaleSize = 1900;
const compressionQuality = 0.5;
const zip = new JSZip();
const promises = [];
const oldButton = document.querySelector('.download-bulkimg-button');
if (oldButton) {
output.removeChild(oldButton);
}
const folderMap = {};
// Iterate through the selected files and categorize them by their folder paths
for (let i = 0; i < folderInput.files.length; i++) {
const file = folderInput.files[i];
const filePath = file.webkitRelativePath || file.relativePath || file.name;
const folderPath = filePath.substring(0, filePath.lastIndexOf('/'));
console.log(`Processing file: ${file.name}`);
console.log(`File path: ${filePath}`);
console.log(`Folder path: ${folderPath}`);
if (!folderMap[folderPath]) {
folderMap[folderPath] = [];
}
folderMap[folderPath].push(file);
}
console.log('Folder map:', folderMap);
for (const folderPath in folderMap) {
const files = folderMap[folderPath];
const folder = zip.folder(folderPath);
console.log(`Processing folder: ${folderPath}`);
files.forEach(file => {
const reader = new FileReader();
promises.push(
new Promise((resolve) => {
reader.onload = function(e) {
console.log(`Reading file: ${file.name}`);
const img = new Image();
img.src = e.target.result;
img.onload = function() {
console.log(`Image loaded: ${file.name}`);
const cropCanvas = document.createElement('canvas');
const cropCtx = cropCanvas.getContext('2d');
const {
left,
top,
width,
height
} = getBoundingRectangle(img);
cropCanvas.width = width;
cropCanvas.height = height;
cropCtx.drawImage(img, left, top, width, height, 0, 0, width, height);
const scaleCanvas = document.createElement('canvas');
const scaleCtx = scaleCanvas.getContext('2d');
let scaleWidth, scaleHeight;
if (width > height) {
scaleWidth = maxScaleSize;
scaleHeight = (height / width) * maxScaleSize;
} else {
scaleWidth = (width / height) * maxScaleSize;
scaleHeight = maxScaleSize;
}
scaleCanvas.width = scaleWidth;
scaleCanvas.height = scaleHeight;
scaleCtx.drawImage(cropCanvas, 0, 0, width, height, 0, 0, scaleWidth, scaleHeight);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const canvasSize = finalSize;
canvas.width = canvasSize;
canvas.height = canvasSize;
let sourceWidth, sourceHeight;
if (scaleWidth > scaleHeight) {
sourceWidth = canvasSize;
sourceHeight = (scaleHeight / scaleWidth) * canvasSize;
} else {
sourceWidth = (scaleWidth / scaleHeight) * canvasSize;
sourceHeight = canvasSize;
}
const xOffset = (canvasSize - sourceWidth) / 2;
const yOffset = (canvasSize - sourceHeight) / 2;
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvasSize, canvasSize);
ctx.drawImage(scaleCanvas, 0, 0, scaleWidth, scaleHeight, xOffset, yOffset, sourceWidth, sourceHeight);
canvas.toBlob(function(blob) {
console.log(`Image processing complete: ${file.name}`);
compressImage(blob, compressionQuality, function(compressedBlob) {
folder.file(file.name, compressedBlob);
resolve();
});
}, 'image/jpeg', 1);
};
};
reader.readAsDataURL(file);
})
);
});
}
Promise.all(promises).then(() => {
console.log('All images processed. Generating ZIP...');
if (!document.querySelector('.download-folder-img-button')) {
zip.generateAsync({
type: 'blob'
}).then(function(content) {
console.log('ZIP generated.');
const button = document.createElement('button');
button.innerText = 'Download';
button.classList.add('download-bulkimg-button');
button.addEventListener('click', function(event) {
event.preventDefault();
const a = document.createElement('a');
a.href = URL.createObjectURL(content);
a.download = 'processed-images.zip';
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
output.appendChild(button);
}).catch(error => {
console.error('Error generating ZIP:', error);
output.innerHTML = 'An error occurred while generating the ZIP.';
});
}
}).catch(error => {
console.error('Error processing images:', error);
output.innerHTML = 'An error occurred during processing.';
});
}
function getBoundingRectangle(img) {
const offscreenCanvas = document.createElement('canvas');
const offscreenCtx = offscreenCanvas.getContext('2d');
offscreenCanvas.width = img.width;
offscreenCanvas.height = img.height;
offscreenCtx.drawImage(img, 0, 0);
const imageData = offscreenCtx.getImageData(0, 0, offscreenCanvas.width, offscreenCanvas.height);
const data = imageData.data;
let left = offscreenCanvas.width;
let top = offscreenCanvas.height;
let right = 0;
let bottom = 0;
for (let y = 0; y < offscreenCanvas.height; y++) {
for (let x = 0; x < offscreenCanvas.width; x++) {
const index = (y * offscreenCanvas.width + x) * 4;
if (data[index] !== 255 || data[index + 1] !== 255 || data[index + 2] !== 255) {
left = Math.min(left, x);
right = Math.max(right, x);
top = Math.min(top, y);
bottom = Math.max(bottom, y);
}
}
}
const width = right - left;
const height = bottom - top;
console.log(`Bounding rectangle - left: ${left}, top: ${top}, width: ${width}, height: ${height}`);
return {
left,
top,
width,
height
};
}
function compressImage(blob, quality, callback) {
const img = new Image();
img.src = URL.createObjectURL(blob);
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
canvas.toBlob(function(compressedBlob) {
console.log('Image compressed.');
callback(compressedBlob);
}, 'image/jpeg', quality);
};
}
<div id="bulkContent" class="tab-content">
<div class="imageContent-centrecontainer" style="margin-left: 150px;">
<p class="header-card">Bulk Image Tool</p>
<div class="imagecontent-container">
<div class="imageform-container">
<form>
<div class="custom-input-container">
<!-- Updated input for folder selection -->
<label for="folderInput" class="custom-button">
<i class="fa-solid fa-cloud-arrow-down" style="display: grid; justify-content: center;"></i>
Drag & Drop Or Select
</label>
<div id="fileList" class="file-list"></div>
<!-- Folder input -->
<input type="file" id="folderInput" class="custom-input" webkitdirectory multiple>
</div>
<button class="custom-button-process" type="button" onclick="processFolderImages()">Process Images</button>
<div id="bulk_output"></div>
</form>
</div>
</div>
</div>
<div class="imgdisplay-container">
<div class="imgpreview-container" id="dragAndDropArea">
<div id="bulk_selectedFilesPreview">
</div>
</div>
</div>
</div>