Untitled

 avatar
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