Untitled
unknown
plain_text
9 days ago
9.4 kB
4
Indexable
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Image Banner Merger (Offline)</title>
<style>
body { font-family: Arial, sans-serif; max-width: 700px; margin: 40px auto; padding: 20px; background: #f9f9f9; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
.form-group { margin-bottom: 20px; }
label { display: block; font-weight: bold; margin-bottom: 5px; }
input[type="number"], input[type="file"] { width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; }
button { width: 100%; padding: 12px; background-color: #007bff; color: white; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; font-weight: bold; }
button:hover { background-color: #0056b3; }
button:disabled { background-color: #cccccc; cursor: not-allowed; }
#status { margin-top: 15px; font-weight: bold; text-align: center; }
.preview-heading { margin-top: 30px; border-top: 2px solid #eee; padding-top: 20px; display: none; color: #333; }
.preview-container { display: grid; grid-template-columns: 1fr; gap: 20px; margin-top: 15px; }
.preview-item { background: #fff; padding: 10px; border: 1px solid #ddd; border-radius: 4px; text-align: center; }
.preview-item p { margin: 0 0 10px 0; font-size: 14px; color: #555; font-weight: bold; }
.preview-item img { max-width: 100%; height: auto; border: 1px solid #eee; }
</style>
</head>
<body>
<h2>Image Banner Merger (Offline)</h2>
<div class="form-group">
<label for="bgInput">Select Background Image (bg01.png):</label>
<input type="file" id="bgInput" accept="image/*">
</div>
<div class="form-group">
<label for="logosInput">Select Logo Images (Select sm01, sm02, sm03 together):</label>
<input type="file" id="logosInput" accept="image/*" multiple>
</div>
<div class="form-group">
<label for="widthPercent">Logo Width (% of Background):</label>
<input type="number" id="widthPercent" value="30" min="1" max="100">
</div>
<div class="form-group">
<label for="topPadding">Top Padding (Pixels down from top):</label>
<input type="number" id="topPadding" value="20" min="0">
</div>
<button id="processBtn">Generate Previews & Download ZIP</button>
<div id="status" style="color: #333;"></div>
<h3 class="preview-heading" id="previewHeading">Generated Previews</h3>
<div class="preview-container" id="previewContainer"></div>
<script>
document.getElementById('processBtn').addEventListener('click', async () => {
const bgInput = document.getElementById('bgInput');
const logosInput = document.getElementById('logosInput');
const widthPercent = parseFloat(document.getElementById('widthPercent').value) / 100;
const topPadding = parseInt(document.getElementById('topPadding').value);
const statusDiv = document.getElementById('status');
const button = document.getElementById('processBtn');
const previewHeading = document.getElementById('previewHeading');
const previewContainer = document.getElementById('previewContainer');
if (bgInput.files.length === 0 || logosInput.files.length === 0) {
alert('Please select both background and logo images.');
return;
}
button.disabled = true;
statusDiv.style.color = '#333';
statusDiv.innerText = "Processing images locally...";
previewContainer.innerHTML = "";
previewHeading.style.display = "none";
try {
const bgFile = bgInput.files[0];
const logoFiles = logosInput.files;
const bgImage = await loadImage(bgFile);
const bgBaseName = bgFile.name.substring(0, bgFile.name.lastIndexOf('.'));
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = bgImage.width;
canvas.height = bgImage.height;
const zipFiles = [];
for (let i = 0; i < logoFiles.length; i++) {
const logoFile = logoFiles[i];
const logoImage = await loadImage(logoFile);
const logoBaseName = logoFile.name.substring(0, logoFile.name.lastIndexOf('.'));
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(bgImage, 0, 0);
const targetWidth = canvas.width * widthPercent;
const targetHeight = targetWidth * (logoImage.height / logoImage.width);
const xPosition = (canvas.width - targetWidth) / 2;
const yPosition = topPadding;
ctx.drawImage(logoImage, xPosition, yPosition, targetWidth, targetHeight);
const blob = await new Promise((resolve, reject) => {
canvas.toBlob(b => b ? resolve(b) : reject(new Error("Canvas export failed")), 'image/png');
});
const outputFileName = `${bgBaseName}_${logoBaseName}.png`;
const itemUrl = URL.createObjectURL(blob);
const arrayBuffer = await blob.arrayBuffer();
zipFiles.push({ name: outputFileName, buffer: new Uint8Array(arrayBuffer) });
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';
previewItem.innerHTML = `<p>${outputFileName}</p><img src="${itemUrl}">`;
previewContainer.appendChild(previewItem);
}
previewHeading.style.display = "block";
statusDiv.innerText = "Packaging files into ZIP...";
const zipBlob = generateSimpleZip(zipFiles);
const link = document.createElement('a');
link.href = URL.createObjectURL(zipBlob);
link.download = "combined_images.zip";
link.click();
statusDiv.style.color = '#28a745';
statusDiv.innerText = "Done! Archive downloaded completely offline.";
} catch (error) {
console.error(error);
statusDiv.style.color = '#d9534f';
statusDiv.innerText = "Error: " + error.message;
} finally {
button.disabled = false;
}
});
function loadImage(file) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`Failed to read file: ${file.name}`));
img.src = URL.createObjectURL(file);
});
}
function generateSimpleZip(files) {
const parts = [];
let centralDirOffset = 0;
const centralDirParts = [];
const w16 = (v) => new Uint8Array([v & 0xFF, (v >> 8) & 0xFF]);
const w32 = (v) => new Uint8Array([v & 0xFF, (v >> 8) & 0xFF, (v >> 16) & 0xFF, (v >> 24) & 0xFF]);
for (const file of files) {
const nameBytes = new TextEncoder().encode(file.name);
const localHeaderOffset = centralDirOffset;
const hash = crc32(file.buffer);
const localHeader = new Uint8Array([
0x50, 0x4B, 0x03, 0x04, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0,
...w32(hash), ...w32(file.buffer.length), ...w32(file.buffer.length),
...w16(nameBytes.length), 0, 0
]);
parts.push(localHeader, nameBytes, file.buffer);
centralDirOffset += localHeader.length + nameBytes.length + file.buffer.length;
const centralHeader = new Uint8Array([
0x50, 0x4B, 0x01, 0x02, 20, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0,
...w32(hash), ...w32(file.buffer.length), ...w32(file.buffer.length),
...w16(nameBytes.length), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
...w32(localHeaderOffset)
]);
centralDirParts.push(centralHeader, nameBytes);
}
const centralDirLen = centralDirParts.reduce((acc, p) => acc + p.length, 0);
const eocd = new Uint8Array([
0x50, 0x4B, 0x05, 0x06, 0, 0, 0, 0,
...w16(files.length), ...w16(files.length),
...w32(centralDirLen), ...w32(centralDirOffset), 0, 0
]);
return new Blob([...parts, ...centralDirParts, eocd], { type: "application/zip" });
}
function crc32(r){for(var a,o=[],c=0;c<256;c++){a=c;for(var f=0;f<8;f++)a=1&a?3988292384^a>>>1:a>>>1;o[c]=a}for(var n=-1,t=0;t<r.length;t++)n=n>>>8^o[255&(n^r[t])];return(-1^n)>>>0}
</script>
</body>
</html>
Editor is loading...
Leave a Comment