Client-side
IMAGE TO PDF
Convert JPG, PNG, WebP, BMP, and GIF images to a PDF document. Reorder images and configure output layout.
Drag & drop images here, or click to browse
Supports JPG, PNG, WebP, GIF, BMP
About Image to PDF Converter
Convert your images into clean, professional PDF documents instantly. This converter supports popular image formats including JPG, JPEG, PNG, WebP, GIF, and BMP. You can customize page sizes (A4, Letter, or Fit Image), adjust page layout orientation, and add custom margins.
The entire conversion is processed locally inside your web browser using HTML5 Canvas and pdf-lib. Your images are never uploaded to any remote servers, maintaining total privacy.
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
let imageFiles = [];
const uploadZone = document.getElementById('uploadZone');
const fileInput = document.getElementById('fileInput');
const thumbGrid = document.getElementById('thumbGrid');
const configPanel = document.getElementById('configPanel');
const pageSizeSelect = document.getElementById('pageSizeSelect');
const orientationSelect = document.getElementById('orientationSelect');
const marginSelect = document.getElementById('marginSelect');
const convertBtn = document.getElementById('convertBtn');
const loadingContainer = document.getElementById('loadingContainer');
const resultContainer = document.getElementById('resultContainer');
const resultInfo = document.getElementById('resultInfo');
const downloadBtn = document.getElementById('downloadBtn');
// Drag and drop event listeners
['dragenter', 'dragover'].forEach(eventName => {
uploadZone.addEventListener(eventName, e => {
e.preventDefault();
uploadZone.classList.add('dragover');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
uploadZone.addEventListener(eventName, e => {
e.preventDefault();
uploadZone.classList.remove('dragover');
}, false);
});
uploadZone.addEventListener('drop', e => {
const dt = e.dataTransfer;
handleFiles(dt.files);
});
fileInput.addEventListener('change', e => {
handleFiles(e.target.files);
});
function handleFiles(files) {
for (let i = 0; i < files.length; i++) {
if (files[i].type.startsWith('image/')) {
const reader = new FileReader();
const item = {
id: Date.now() + '-' + Math.random().toString(36).substr(2, 9),
file: files[i],
src: ''
};
reader.onload = (e) => {
item.src = e.target.result;
renderThumbnails();
};
reader.readAsDataURL(files[i]);
imageFiles.push(item);
}
}
}
function renderThumbnails() {
thumbGrid.innerHTML = '';
imageFiles.forEach((item, index) => {
if (!item.src) return;
const thumb = document.createElement('div');
thumb.className = 'thumb-item';
thumb.innerHTML = `
<div class="thumb-img-container">
<img src="${item.src}" class="thumb-img" />
</div>
<div class="thumb-title">${item.file.name}</div>
`;
const controls = document.createElement('div');
controls.className = 'thumb-controls';
if (index > 0) {
const up = document.createElement('button');
up.className = 'ctrl-btn';
up.textContent = '◀';
up.onclick = () => moveImage(index, -1);
controls.appendChild(up);
}
if (index < imageFiles.length - 1) {
const down = document.createElement('button');
down.className = 'ctrl-btn';
down.textContent = '▶';
down.onclick = () => moveImage(index, 1);
controls.appendChild(down);
}
const remove = document.createElement('button');
remove.className = 'ctrl-btn remove-btn';
remove.textContent = '✕';
remove.onclick = () => removeImage(item.id);
controls.appendChild(remove);
thumb.appendChild(controls);
thumbGrid.appendChild(thumb);
});
if (imageFiles.length > 0) {
configPanel.classList.remove('hidden');
} else {
configPanel.classList.add('hidden');
}
resultContainer.classList.add('hidden');
}
function moveImage(index, direction) {
const temp = imageFiles[index];
imageFiles[index] = imageFiles[index + direction];
imageFiles[index + direction] = temp;
renderThumbnails();
}
function removeImage(id) {
imageFiles = imageFiles.filter(item => item.id !== id);
renderThumbnails();
}
pageSizeSelect.addEventListener('change', () => {
const val = pageSizeSelect.value;
if (val === 'fit') {
orientationSelect.disabled = true;
} else {
orientationSelect.disabled = false;
}
});
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// Helper to load image dimensions
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
convertBtn.addEventListener('click', async () => {
if (imageFiles.length === 0) return;
configPanel.classList.add('hidden');
loadingContainer.classList.remove('hidden');
resultContainer.classList.add('hidden');
try {
const { PDFDocument } = PDFLib;
const pdfDoc = await PDFDocument.create();
const pageSizeType = pageSizeSelect.value;
const orientation = orientationSelect.value;
const marginType = marginSelect.value;
let margin = 0;
if (marginType === 'small') margin = 20;
else if (marginType === 'large') margin = 40;
for (const item of imageFiles) {
const img = await loadImage(item.src);
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
// Always export as high-quality JPEG to compress size
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
ctx.drawImage(img, 0, 0);
const jpgDataUrl = canvas.toDataURL('image/jpeg', 0.85);
const jpgBytes = await (await fetch(jpgDataUrl)).arrayBuffer();
const embeddedImage = await pdfDoc.embedJpg(jpgBytes);
let pageWidth, pageHeight;
if (pageSizeType === 'fit') {
pageWidth = embeddedImage.width + margin * 2;
pageHeight = embeddedImage.height + margin * 2;
} else {
// A4 dimensions in points: 595.27 x 841.89
// US Letter dimensions in points: 612 x 792
let baseWidth = pageSizeType === 'a4' ? 595.27 : 612;
let baseHeight = pageSizeType === 'a4' ? 841.89 : 792;
if (orientation === 'landscape') {
pageWidth = baseHeight;
pageHeight = baseWidth;
} else {
pageWidth = baseWidth;
pageHeight = baseHeight;
}
}
const page = pdfDoc.addPage([pageWidth, pageHeight]);
// Calculate scaling
const contentWidth = pageWidth - margin * 2;
const contentHeight = pageHeight - margin * 2;
const imgRatio = embeddedImage.width / embeddedImage.height;
const contentRatio = contentWidth / contentHeight;
let drawWidth, drawHeight;
if (imgRatio > contentRatio) {
drawWidth = contentWidth;
drawHeight = contentWidth / imgRatio;
} else {
drawHeight = contentHeight;
drawWidth = contentHeight * imgRatio;
}
// Center image
const x = margin + (contentWidth - drawWidth) / 2;
const y = margin + (contentHeight - drawHeight) / 2;
page.drawImage(embeddedImage, {
x: x,
y: y,
width: drawWidth,
height: drawHeight
});
}
const pdfBytes = await pdfDoc.save();
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const blobUrl = URL.createObjectURL(blob);
downloadBtn.href = blobUrl;
resultInfo.textContent = `Converted ${imageFiles.length} image(s) to a single PDF. Total size: ${formatBytes(blob.size)}.`;
loadingContainer.classList.add('hidden');
resultContainer.classList.remove('hidden');
} catch (err) {
console.error(err);
alert('An error occurred during PDF conversion.');
loadingContainer.classList.add('hidden');
configPanel.classList.remove('hidden');
}
});