Drop two images here or use file inputs
Strips per image:
Download Combined Image
Export to PDF
Zoom:
const img1Input = document.getElementById(‘img1’); const img2Input = document.getElementById(‘img2’); const stripCountInput = document.getElementById(‘stripCount’); const downloadBtn = document.getElementById(‘downloadBtn’); const pdfBtn = document.getElementById(‘pdfBtn’); const canvas = document.getElementById(‘canvas’); const ctx = canvas.getContext(‘2d’); const zoomSlider = document.getElementById(‘zoomSlider’); const dropZone = document.getElementById(‘dropZone’); let img1 = new Image(), img2 = new Image(); let loaded1 = false, loaded2 = false; function loadImageFromFile(file, img, done) { const reader = new FileReader(); reader.onload = () => { img.src = reader.result; }; img.onload = () => { done(); }; reader.readAsDataURL(file); } img1Input.onchange = e => loadImageFromFile(e.target.files[0], img1, () => { loaded1 = true; maybeProcess(); }); img2Input.onchange = e => loadImageFromFile(e.target.files[0], img2, () => { loaded2 = true; maybeProcess(); }); dropZone.ondragover = e => { e.preventDefault(); dropZone.style.borderColor = ‘blue’; }; dropZone.ondragleave = () => { dropZone.style.borderColor = ‘#ccc’; }; dropZone.ondrop = e => { e.preventDefault(); dropZone.style.borderColor = ‘#ccc’; const files = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith(‘image/’)).slice(0, 2); if (files.length > 0) loadImageFromFile(files[0], img1, () => { loaded1 = true; maybeProcess(); }); if (files.length > 1) loadImageFromFile(files[1], img2, () => { loaded2 = true; maybeProcess(); }); }; zoomSlider.oninput = () => { canvas.style.transform = `scale(${zoomSlider.value})`; canvas.style.transformOrigin = ‘top left’; }; stripCountInput.oninput = () => { if (loaded1 && loaded2) maybeProcess(); }; function drawDots(stripCount, sliceWidth, canvasWidth, padding, imageHeight) { const dotRadius = 1.5; const spacing = 4; ctx.fillStyle = ‘black’; for (let i = 1; i < stripCount * 2; i++) { const x = i * sliceWidth; // Top dots for (let j = -1; j <= 1; j++) { ctx.beginPath(); ctx.arc(x, padding / 2 + j * spacing, dotRadius, 0, 2 * Math.PI); ctx.fill(); } // Bottom dots for (let j = -1; j <= 1; j++) { ctx.beginPath(); ctx.arc(x, padding + imageHeight + padding / 2 + j * spacing, dotRadius, 0, 2 * Math.PI); ctx.fill(); } } } function maybeProcess() { const stripsPerImage = parseInt(stripCountInput.value, 10); if (!loaded1 || !loaded2 || isNaN(stripsPerImage) || stripsPerImage <= 0) return; const targetWidth = Math.min(img1.width, img2.width); const targetHeight = Math.min(img1.height, img2.height); const sliceWidth = Math.floor(targetWidth / stripsPerImage); const outputWidth = sliceWidth * stripsPerImage * 2; const padding = 20; canvas.width = outputWidth; canvas.height = targetHeight + 2 * padding; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw alternating strips for (let i = 0; i { const link = document.createElement(‘a’); link.download = ‘combined-image.png’; link.href = canvas.toDataURL(‘image/png’); link.click(); }; pdfBtn.onclick = async () => { const { jsPDF } = window.jspdf; const pdf = new jsPDF({ orientation: ‘portrait’, unit: ‘pt’, format: ‘a4’ }); const pageWidth = pdf.internal.pageSize.getWidth(); const pageHeight = pdf.internal.pageSize.getHeight(); const imgData = canvas.toDataURL(‘image/png’); const margin = 40; const maxImgWidth = pageWidth – 2 * margin; const scale = maxImgWidth / canvas.width; const imgWidth = canvas.width * scale; const imgHeight = canvas.height * scale; pdf.addImage(imgData, ‘PNG’, margin, margin, imgWidth, imgHeight); pdf.save(“foldable-image.pdf”); };