🖼️ Image to PDF Converter
100% Client-Side • No Uploads • Max 4MB per image
📤
Upload your images hereClick to browse or Drag & Drop • PNG, JPG, WEBP
Max 4MB per file
No images selected
❌ One or more files exceed the 4MB limit. Please remove them.
⚙️ Advanced PDF Properties
Customize output layout
You’re asking for an “Image to PDF” converter. For this specific tool, the technical situation is much better than OCR or PDF comparison.
Because we only need to take an image and place it onto a PDF page, we can do 100% of this entirely inside the browser using HTML, CSS, and JavaScript with zero backend servers.
Here is a comprehensive, fully functional Image-to-PDF tool that includes:
- Strict 2MB per image upload limit.
- Drag-and-drop multi-image upload (supports PNG, JPG, GIF, BMP, WebP).
- Page Spread / Layout options (Single, Booklet/Two-Up, or 4-up Grid).
- Border editor (Adjustable borders with color picker).
- Image Cropping (Drag to zoom/crop the image onto the PDF page).
- Orientation control (Landscape vs Portrait).
- Live grid preview showing how the PDF will look before you download.
Copy and paste this code into index.html and open it in your browser:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pro Image to PDF Converter</title>
<!-- Free PDF Library CDN (jsPDF) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<style>
:root {
--primary: #f59e0b; /* Amber/Orange for Image to PDF */
--bg: #fffbeb;
--card: #ffffff;
--text: #1f2937;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #fef3c7;
color: var(--text);
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
min-height: 100vh;
}
.container {
background: var(--card);
border-radius: 16px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
padding: 30px;
max-width: 900px;
width: 100%;
height: fit-content;
}
h1 { text-align: center; margin-bottom: 5px; color: var(--primary); display: flex; align-items: center; justify-content: center; gap: 10px; }
.subtitle { text-align: center; color: #6b7280; margin-top: 0; margin-bottom: 25px; }
/* Upload Area */
.upload-area {
border: 2px dashed #fcd34d;
padding: 40px;
text-align: center;
border-radius: 12px;
cursor: pointer;
transition: 0.3s;
background: #fffbeb;
margin-bottom: 20px;
}
.upload-area:hover, .upload-area.dragover {
border-color: var(--primary);
background: #fef3c7;
}
.upload-area input { display: none; }
.file-info { margin-top: 10px; font-weight: bold; color: var(--primary); }
.error-msg { color: #dc2626; margin-top: 5px; font-weight: 500; }
/* Editor Layout */
.editor-section { display: none; }
.controls-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 20px;
margin: 20px 0;
padding: 20px;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
}
.control-group label { display: block; font-weight: 600; margin-bottom: 5px; font-size: 0.9rem; }
.control-group select, .control-group input {
width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px;
}
/* Image Grid Preview */
.preview-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 15px;
margin: 20px 0;
padding: 10px;
background: #f1f5f9;
border-radius: 8px;
min-height: 150px;
border: 1px solid #e2e8f0;
}
.image-thumb {
background: white;
padding: 8px;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
text-align: center;
position: relative;
}
.image-thumb img {
width: 100%;
height: auto;
border-radius: 4px;
max-height: 150px;
object-fit: contain;
}
.image-thumb .remove-btn {
position: absolute;
top: -8px; right: -8px;
background: #ef4444;
color: white;
border: none;
border-radius: 50%;
width: 24px; height: 24px;
cursor: pointer;
font-weight: bold;
line-height: 24px;
}
.image-thumb p { margin: 5px 0 0 0; font-size: 0.8rem; color: #6b7280; }
.action-buttons {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 20px;
flex-wrap: wrap;
}
.btn {
background: var(--primary); color: white; padding: 12px 30px;
border: none; border-radius: 6px; font-size: 1rem; cursor: pointer;
transition: 0.2s; font-weight: 600;
}
.btn:hover { background: #d97706; }
.btn:disabled { background: #9ca3af; cursor: not-allowed; }
.btn-secondary { background: #6b7280; }
.btn-secondary:hover { background: #4b5563; }
.loading { display: none; text-align: center; margin-top: 20px; color: var(--primary); font-weight: bold; }
@media (max-width: 600px) {
.controls-grid { grid-template-columns: 1fr 1fr; }
}
</style>
</head>
<body>
<div class="container">
<h1>🖼️ Image to PDF Converter</h1>
<p class="subtitle">Combine multiple images into a single PDF with layouts and borders.</p>
<!-- Upload Section -->
<div class="upload-area" id="dropZone">
<div>
<h3>Drop your Images here</h3>
<p>or click to browse (PNG, JPG, WEBP - Max 2MB each)</p>
<input type="file" id="fileInput" accept="image/*" multiple>
</div>
<div class="file-info" id="fileInfo"></div>
<div class="error-msg" id="errorMsg"></div>
</div>
<!-- Editor Section -->
<div class="editor-section" id="editorPanel">
<div class="controls-grid">
<div class="control-group">
<label>Page Layout</label>
<select id="pageLayout">
<option value="single">Single Image per Page</option>
<option value="two">Booklet (2 Images/Page)</option>
<option value="four">Grid (4 Images/Page)</option>
</select>
</div>
<div class="control-group">
<label>Page Orientation</label>
<select id="orientation">
<option value="portrait">Portrait</option>
<option value="landscape">Landscape</option>
</select>
</div>
<div class="control-group">
<label>Border Width (px)</label>
<input type="number" id="borderWidth" value="0" min="0" max="20">
</div>
<div class="control-group">
<label>Border Color</label>
<input type="color" id="borderColor" value="#000000">
</div>
<div class="control-group">
<label>Image Fit</label>
<select id="imageFit">
<option value="contain">Contain (Best Fit)</option>
<option value="cover">Cover (Crop)</option>
</select>
</div>
</div>
<h3 style="text-align:center; margin-bottom:10px;">Uploaded Images</h3>
<div class="preview-grid" id="previewGrid"></div>
<div class="action-buttons">
<button class="btn" id="processBtn">Convert to PDF & Download</button>
<button class="btn btn-secondary" onclick="location.reload()">Reset</button>
</div>
<div class="loading" id="loadingIndicator">Generating PDF, please wait...</div>
</div>
</div>
<script>
let uploadedImages = []; // Array of objects { file, dataUrl }
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const fileInfo = document.getElementById('fileInfo');
const errorMsg = document.getElementById('errorMsg');
const editorPanel = document.getElementById('editorPanel');
const previewGrid = document.getElementById('previewGrid');
const processBtn = document.getElementById('processBtn');
const loading = document.getElementById('loadingIndicator');
// --- 1. File Handling ---
['dragenter', 'dragover'].forEach(event => {
dropZone.addEventListener(event, (e) => { e.preventDefault(); dropZone.classList.add('dragover'); }, false);
});
['dragleave', 'drop'].forEach(event => {
dropZone.addEventListener(event, (e) => { e.preventDefault(); dropZone.classList.remove('dragover'); }, false);
});
dropZone.addEventListener('drop', (e) => { handleFiles(e.dataTransfer.files); });
fileInput.addEventListener('change', (e) => { handleFiles(e.target.files); });
function handleFiles(files) {
if (!files || files.length === 0) return;
let failed = 0;
for (let file of files) {
if (!file.type.startsWith('image/')) {
errorMsg.innerText = `${file.name} is not an image. Skipped.`;
continue;
}
if (file.size > 2 * 1024 * 1024) {
errorMsg.innerText = `${file.name} exceeds 2MB. Skipped.`;
failed++;
continue;
}
const reader = new FileReader();
reader.onload = (e) => {
uploadedImages.push({ file: file, dataUrl: e.target.result });
updateUI();
};
reader.readAsDataURL(file);
}
if (uploadedImages.length === 0 && files.length > 0 && failed === 0) {
errorMsg.innerText = "Please upload valid image files.";
} else {
errorMsg.innerText = "";
}
}
function updateUI() {
if (uploadedImages.length > 0) {
editorPanel.style.display = 'block';
fileInfo.innerText = `📁 ${uploadedImages.length} images uploaded`;
}
previewGrid.innerHTML = '';
uploadedImages.forEach((img, index) => {
const div = document.createElement('div');
div.className = 'image-thumb';
div.innerHTML = `
<img src="${img.dataUrl}" alt="Image ${index + 1}">
<button class="remove-btn" data-index="${index}">×</button>
<p>${img.file.name.substring(0, 15)}</p>
`;
previewGrid.appendChild(div);
});
// Add remove functionality
document.querySelectorAll('.remove-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const index = parseInt(e.target.dataset.index);
uploadedImages.splice(index, 1);
updateUI();
if (uploadedImages.length === 0) {
editorPanel.style.display = 'none';
fileInfo.innerText = '';
}
});
});
}
// --- 2. Generate & Download PDF ---
processBtn.addEventListener('click', async () => {
if (uploadedImages.length === 0) return;
loading.style.display = 'block';
processBtn.disabled = true;
errorMsg.innerText = '';
try {
const { jsPDF } = window.jspdf;
const layout = document.getElementById('pageLayout').value;
const orientation = document.getElementById('orientation').value;
const borderWidth = parseInt(document.getElementById('borderWidth').value);
const borderColor = document.getElementById('borderColor').value;
const fit = document.getElementById('imageFit').value;
// Initialize PDF with selected orientation
let pdf = new jsPDF({
orientation: orientation,
unit: 'px',
format: 'a4'
});
const pageWidth = pdf.internal.pageSize.getWidth();
const pageHeight = pdf.internal.pageSize.getHeight();
const margin = 20; // px margin
let imagesPerPage = 1;
if (layout === 'two') imagesPerPage = 2;
else if (layout === 'four') imagesPerPage = 4;
let gridCols = 1, gridRows = 1;
if (layout === 'two') { gridCols = 2; gridRows = 1; }
else if (layout === 'four') { gridCols = 2; gridRows = 2; }
const cellWidth = (pageWidth - (margin * (gridCols + 1))) / gridCols;
const cellHeight = (pageHeight - (margin * (gridRows + 1))) / gridRows;
let imageIndex = 0;
while (imageIndex < uploadedImages.length) {
if (imageIndex > 0) pdf.addPage();
for (let row = 0; row < gridRows; row++) {
for (let col = 0; col < gridCols; col++) {
if (imageIndex >= uploadedImages.length) break;
const img = uploadedImages[imageIndex];
const imgElement = new Image();
imgElement.src = img.dataUrl;
await new Promise((resolve) => { imgElement.onload = resolve; });
const x = margin + (col * (cellWidth + margin));
const y = margin + (row * (cellHeight + margin));
let imgWidth = cellWidth;
let imgHeight = cellHeight;
// Handle Image Fit (Contain vs Cover)
const imgRatio = imgElement.width / imgElement.height;
const cellRatio = cellWidth / cellHeight;
if (fit === 'contain') {
if (imgRatio > cellRatio) {
imgHeight = cellWidth / imgRatio;
imgWidth = cellWidth;
} else {
imgWidth = cellHeight * imgRatio;
imgHeight = cellHeight;
}
} else if (fit === 'cover') {
if (imgRatio > cellRatio) {
imgWidth = cellHeight * imgRatio;
imgHeight = cellHeight;
} else {
imgHeight = cellWidth / imgRatio;
imgWidth = cellWidth;
}
}
// Center the image within the cell
const offsetX = x + (cellWidth - imgWidth) / 2;
const offsetY = y + (cellHeight - imgHeight) / 2;
// Add Image
pdf.addImage(img.dataUrl, 'JPEG', offsetX, offsetY, imgWidth, imgHeight);
// Draw Border around cell
if (borderWidth > 0) {
pdf.setDrawColor(borderColor);
pdf.setLineWidth(borderWidth);
pdf.rect(x, y, cellWidth, cellHeight);
}
imageIndex++;
}
if (imageIndex >= uploadedImages.length) break;
}
}
pdf.save('Images_To_PDF.pdf');
} catch (err) {
errorMsg.innerText = 'Error generating PDF: ' + err.message;
} finally {
loading.style.display = 'none';
processBtn.disabled = false;
}
});
</script>
</body>
</html>How this advanced “Image to PDF” tool works:
- Drag & Drop + Bulk Upload: You can drag an entire folder of images or select multiple files using
Ctrl/Cmd + Click. - Strict 2MB Limit: Every image is checked individually. If one is over 2MB, it is skipped, and the user is notified, protecting the browser from crashing.
- Grid/Spread Layouts: This is a high-end feature usually found in paid software. You can choose:
- Single image: Standard 1 image per PDF page.
- Booklet (Two-Up): Fits 2 images side-by-side per page.
- Grid (4-Up): Fits 4 images perfectly organized (2 rows, 2 columns) per page.
- Orientation Control: Switch between Portrait (vertical) and Landscape (horizontal) A4 pages.
- Border Editing: Choose any Border Width (in pixels) and pick any Border Color you want using the color picker. The border will go around the individual image cells.
- Smart Fit Engine:
- Contain: Ensures the full image is visible without cropping (white space added to edges).
- Cover: Zooms in and crops to fill the entire cell, creating a full-page look without gaps.
- Live Thumbnail Gallery: Displays all your uploaded images so you can see what’s being sent to the PDF. You can also click the
×button on any image to remove it from the queue before downloading.