Toolmingo
Guides7 min read

How to Convert an Image to PDF (JPG, PNG, WebP, and More)

Learn how to convert images to PDF — single or multiple images — using JavaScript, Python, Go, and PHP. Includes batch conversion, page sizing, DPI control, and common pitfalls.

How to Convert an Image to PDF

Converting images to PDF is one of the most common document tasks — scanning receipts, archiving photos, submitting assignments, or packaging deliverables for a client. This guide explains how the conversion works, what settings matter (page size, DPI, compression), and how to do it in JavaScript, Python, Go, and PHP.


Why Convert Images to PDF?

PDF is a universal, self-contained format that:

  • Looks identical on every device and operating system
  • Preserves exact dimensions and resolution without re-encoding
  • Supports multiple images in a single file with correct page order
  • Is accepted everywhere: email, courts, banks, universities, government portals
  • Can be password-protected and digitally signed

JPEG and PNG are great for display, but they are single-image formats. The moment you need to deliver a multi-page document or guarantee pixel-perfect rendering, PDF is the right container.


Key Concepts Before You Start

Page Size vs Image Size

A PDF page has a size (A4, Letter, custom). When embedding an image, you choose:

  • Fit to image: the page exactly matches the image dimensions — no borders, no scaling
  • Fit to page: the image is scaled to fill a standard page (A4/Letter) with optional margins
  • Scale to DPI: the image is sized on the page based on its resolution (pixels ÷ DPI = inches)

For documents and archives, "fit to page" with A4 is most common. For pixel-perfect archives, "fit to image" or "scale to DPI" is better.

DPI and Print Quality

DPI (dots per inch) controls how large the image appears on the page:

Image size DPI Page size
2480 × 3508 px 300 DPI A4 (8.27 × 11.69 in)
595 × 842 px 72 DPI A4 (screen resolution)
1240 × 1754 px 150 DPI A4 (medium quality)

300 DPI is the standard for print. 72–96 DPI is adequate for screen PDFs. If you just embed pixels without DPI information, most PDF viewers default to 72 DPI, making a large image look enormous.

PDF Units: Points

PDF coordinates are in points (1 point = 1/72 inch). A4 is 595 × 842 pt. Letter is 612 × 792 pt.

width_in_points = pixel_width / dpi * 72

Single Image to PDF

The simplest case: one image, one PDF page.

JavaScript (pdf-lib)

import { PDFDocument } from "pdf-lib";
import fs from "fs";

async function imageToPdf(imagePath, outputPath, dpi = 150) {
  const imageBytes = fs.readFileSync(imagePath);
  const pdfDoc = await PDFDocument.create();

  // Embed image based on type
  const isJpeg = imagePath.toLowerCase().match(/\.jpe?g$/);
  const image = isJpeg
    ? await pdfDoc.embedJpg(imageBytes)
    : await pdfDoc.embedPng(imageBytes);

  // Calculate page size in points at target DPI
  const widthPt = (image.width / dpi) * 72;
  const heightPt = (image.height / dpi) * 72;

  const page = pdfDoc.addPage([widthPt, heightPt]);
  page.drawImage(image, { x: 0, y: 0, width: widthPt, height: heightPt });

  const pdfBytes = await pdfDoc.save();
  fs.writeFileSync(outputPath, pdfBytes);
}

await imageToPdf("photo.jpg", "photo.pdf", 150);

For browser use, replace fs.readFileSync with fetch or a <input type="file"> FileReader.


Python (Pillow + reportlab, or img2pdf)

Option A — img2pdf (lossless, no re-encoding):

import img2pdf

def image_to_pdf(image_path: str, output_path: str) -> None:
    with open(image_path, "rb") as img_file, open(output_path, "wb") as pdf_file:
        pdf_file.write(img2pdf.convert(img_file))

image_to_pdf("photo.jpg", "photo.pdf")

img2pdf embeds JPEG without re-encoding (lossless quality), sets page size to match the image at its native DPI, and supports JPEG, PNG, TIFF, and WebP.

Option B — Pillow (more control over page size):

from PIL import Image

def image_to_pdf_pillow(image_path: str, output_path: str) -> None:
    with Image.open(image_path) as img:
        # Convert to RGB (PDF does not support RGBA/palette mode directly)
        if img.mode in ("RGBA", "P"):
            img = img.convert("RGB")
        img.save(output_path, "PDF", resolution=150)

image_to_pdf_pillow("photo.png", "photo.pdf")

Go (pdfcpu or go-pdf)

package main

import (
    "github.com/pdfcpu/pdfcpu/pkg/api"
    "os"
)

func imageToPDF(imagePath, outputPath string) error {
    // pdfcpu imports a single image as a one-page PDF
    // Page size defaults to A4; use ImportConfig to customise
    imp, _ := api.Import("form:A4, pos:full, sc:1.0", nil)
    return api.ImportImagesFile([]string{imagePath}, outputPath, imp, nil)
}

func main() {
    if err := imageToPDF("photo.jpg", "photo.pdf"); err != nil {
        panic(err)
    }
}

pdfcpu supports JPEG, PNG, TIFF, GIF, and WebP. The pos:full option scales the image to fill the page; pos:c centres it with its native size.


PHP (TCPDF or FPDF)

<?php
require 'vendor/autoload.php'; // composer require tecnickcom/tcpdf

function image_to_pdf(string $imagePath, string $outputPath, int $dpi = 150): void {
    [$pixelW, $pixelH] = getimagesize($imagePath);

    // Convert pixels to mm at target DPI (1 inch = 25.4 mm)
    $mmW = $pixelW / $dpi * 25.4;
    $mmH = $pixelH / $dpi * 25.4;

    $pdf = new TCPDF('P', 'mm', [$mmW, $mmH], true, 'UTF-8', false);
    $pdf->AddPage();
    $pdf->Image($imagePath, 0, 0, $mmW, $mmH);
    $pdf->Output($outputPath, 'F');
}

image_to_pdf('photo.jpg', 'photo.pdf', 150);

Multiple Images to One PDF

Batch conversion — each image becomes one page.

JavaScript (pdf-lib, multi-page)

import { PDFDocument } from "pdf-lib";
import fs from "fs";
import path from "path";

async function imagesToPdf(imagePaths, outputPath, dpi = 150) {
  const pdfDoc = await PDFDocument.create();

  for (const imgPath of imagePaths) {
    const bytes = fs.readFileSync(imgPath);
    const isJpeg = imgPath.toLowerCase().match(/\.jpe?g$/);
    const image = isJpeg
      ? await pdfDoc.embedJpg(bytes)
      : await pdfDoc.embedPng(bytes);

    const widthPt = (image.width / dpi) * 72;
    const heightPt = (image.height / dpi) * 72;
    const page = pdfDoc.addPage([widthPt, heightPt]);
    page.drawImage(image, { x: 0, y: 0, width: widthPt, height: heightPt });
  }

  fs.writeFileSync(outputPath, await pdfDoc.save());
}

await imagesToPdf(["page1.jpg", "page2.jpg", "page3.png"], "combined.pdf");

Python (img2pdf, multi-page)

import img2pdf
import glob

def images_to_pdf(pattern: str, output_path: str) -> None:
    image_paths = sorted(glob.glob(pattern))
    with open(output_path, "wb") as pdf_file:
        pdf_file.write(img2pdf.convert(image_paths))

images_to_pdf("scans/*.jpg", "document.pdf")

Page Size Presets

Preset Width × Height (mm) Width × Height (pt)
A4 210 × 297 595 × 842
A5 148 × 210 420 × 595
Letter (US) 215.9 × 279.4 612 × 792
Legal (US) 215.9 × 355.6 612 × 1008
Square (Instagram) 210 × 210 595 × 595

Common Pitfalls

PNG with transparency becomes black background in PDF
PDF does not support alpha channels in the same way. Composite the image onto a white background before embedding:

// pdf-lib handles PNG alpha automatically — no action needed
// Python Pillow: convert RGBA to RGB with white fill
from PIL import Image
img = Image.open("transparent.png").convert("RGBA")
white = Image.new("RGB", img.size, (255, 255, 255))
white.paste(img, mask=img.split()[3])
white.save("flat.png")

File size is huge
Embedding a 12 MP RAW JPEG at 100% quality produces a large PDF. Two options:

  1. Compress the image before embedding (compress-image tool, 80% quality is usually invisible)
  2. Use img2pdf in Python — it embeds JPEG without re-encoding, keeping the original compression

Page orientation is wrong
If a portrait photo lands as landscape, check whether the image has EXIF rotation data. Most PDF libraries ignore EXIF rotation. Strip or bake-in the rotation first:

from PIL import Image, ImageOps
img = ImageOps.exif_transpose(Image.open("rotated.jpg"))
img.save("corrected.jpg")

WebP is not supported by all PDF libraries
Convert WebP to PNG first if your library does not accept it:

# using ImageMagick
magick input.webp output.png

Quick Reference

Task Best library
JS — browser + Node pdf-lib
Python — lossless JPEG img2pdf
Python — format control Pillow
Go pdfcpu
PHP TCPDF or FPDF
CLI (any format) ImageMagick (magick *.jpg out.pdf)

Frequently Asked Questions

Does converting JPG to PDF lose quality?
Not if the library embeds the JPEG without re-encoding (like img2pdf). Libraries that draw the image onto a PDF canvas (Pillow, TCPDF) re-compress the JPEG internally, which adds one generation of lossy compression. The difference is usually invisible at 80%+ quality, but for archival use, prefer lossless embedding.

What is the maximum image size for a PDF?
The PDF specification allows pages up to 14,400 × 14,400 points (200 × 200 inches). In practice, use the native image dimensions mapped to a sensible DPI — a 6000 px wide scan at 300 DPI produces a 20-inch wide page, which is fine for a large-format document.

Can I convert WebP to PDF directly?
Some libraries (pdfcpu, newer img2pdf) support WebP. If yours does not, convert to PNG first with ImageMagick or the browser's canvas.toBlob("image/png"). The extra step adds no visible quality loss.

How do I add a margin around the image on the PDF page?
Use A4 or Letter page dimensions and draw the image inset from all four edges:

const margin = 40; // points (~14 mm)
page.drawImage(image, {
  x: margin,
  y: margin,
  width: pageW - margin * 2,
  height: pageH - margin * 2,
});

Will the PDF be searchable?
No — unless you add an OCR text layer. A plain image-to-PDF conversion produces an image-only PDF. For searchable PDFs, run Tesseract OCR and embed the text layer (Python: ocrmypdf library).

What about HEIC images (iPhone photos)?
Most PDF libraries do not support HEIC directly. Convert to JPEG first using ImageMagick (magick photo.heic photo.jpg) or the heic2any npm package in the browser.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools