프론트엔드 개발자가 40페이지짜리 브랜드 가이드라인 PDF를 받아서 Figma 보드에 특정 3페이지를 넣어야 한다. 지원 엔지니어가 데이터시트의 다이어그램을 Slack 스레드에 붙여 넣고 싶어 한다. 변호사가 전체 파일을 보낼 필요 없이 서명된 계약서 한 페이지를 이메일에 첨부해야 한다.
PDF는 고정 레이아웃 문서를 위해 만들어졌다. 웹, 이미지 편집기, 채팅 앱은 픽셀을 기준으로 동작한다. PDF 페이지를 이미지로 변환하면 이 간극을 메울 수 있지만, 선택하는 방식에 따라 출력 품질, 파일 크기, 제어 가능 범위가 달라진다.
PDF가 이 작업에 적합하면서도 성가신 이유
PDF는 페이지를 그리기 명령의 흐름으로 저장한다: 이 글리프를 배치하고, 이 벡터를 그리고, 이 이미지를 이 크기로 렌더링한다. 덕분에 해상도에 독립적이고 시각적으로 일관성 있다. 동시에 PDF는 이미지가 아니다. 이미지로 만들려면 무언가가 이 명령들을 래스터 캔버스 위에 렌더링해야 한다.
장점:
- 텍스트와 벡터는 픽셀로 저장되는 것이 아니라 수학적으로 기술되므로 어떤 배율에서도 선명하다.
- 하나의 PDF에 수백 페이지를 담을 수 있다.
- 폰트, 색상 프로파일, 주석이 문서와 함께 이동한다.
단점:
- PDF 리더마다 렌더링 결과가 다르다. 같은 페이지라도 Adobe Acrobat, Preview, Chrome, 헤드리스 라이브러리에서 미세하게 다르게 보일 수 있다.
- 스캔 PDF는 PDF 컨테이너로 감싼 이미지에 불과하므로 "변환"은 재인코딩을 의미한다. 이 과정에서 아티팩트가 추가되거나 파일이 불어날 수 있다.
- 투명도, 레이어, 인터랙티브 양식이 포함된 복잡한 PDF는 예측하기 어렵게 평탄화될 수 있다.
- 페이지 크기가 제각각이다. US Letter 페이지가 72 DPI면 612×792 픽셀이고, 300 DPI면 2550×3300 픽셀이다. 해상도를 지정하지 않으면 쓸 수 없는 결과가 나올 수 있다.
사람들이 실제로 PDF를 변환하는 대상
대부분의 변환 작업은 세 가지 출력 형식 중 하나로 귀결된다. 각 형식은 용도가 다르다.
| Format | Best for | Trade-off |
|---|---|---|
| JPG | 사진, 미리보기, 이메일 첨부, 웹 갤러리 | 손실 압축이지만 파일 크기가 작다 |
| PNG | 스크린샷, 다이어그램, 투명도가 필요한 경우 | 무손실, 사진에는 JPG보다 크다 |
| WebP | 최신 웹, 앱, 대역폭이 중요한 모든 곳 | JPG/PNG보다 작지만 호환성이 약간 떨어진다 |
그 외 실용적인 차원도 있다:
- 단일 페이지 vs 일괄. 한 페이지는 쉽다. 송장 폴더 한 개는 자동화가 필요하다.
- DPI. 150 DPI면 썸네일에 충분하다. 300 DPI는 인쇄와 OCR의 표준이다. 600 DPI는 아주 작은 디테일을 확대하지 않는 한 과하다.
- 색 공간. RGB는 화면용으로 안전하다. CMYK PDF를 RGB로 단순하게 변환하면 색이 박탈되거나 과포화될 수 있다.
적절한 접근법 고르기
무엇을 최적화하느냐에 따라 도구가 달라진다.
빠른 브라우저 기반 변환
JPG, PNG, WebP 형식의 페이지가 필요하고 파일이 서버 처리까지 갈 만큼 민감하지 않다면, 브라우저 기반 변환이 가장 빠르다. 우리의 PDF를 JPG로, PDF를 PNG로, PDF를 WebP로 도구는 PDF를 로컬에서 렌더링한다. 파일이 디바이스를 벗어나지 않으므로 계약서, 신분증, 의료 기록 등에 유리하다.
명령줄 일괄 작업
PDF 폴더 여러 개나 CI 파이프라인이라면 명령줄 도구가 낫다. 반복 가능한 출력, DPI 제어, 스크립팅이 가능하다.
애플리케이션 내 변환
변환이 제품의 한 기능이라면 외부 CLI를 호출하기보다 라이브러리를 직접 쓰는 편이 깔끔하다. 외부 의존성을 없애고, 코드베이스의 나머지 부분과 동일한 오류 처리를 적용할 수 있다.
Windows에서 변환하기
Adobe Acrobat
- PDF를 열고 필요한 페이지로 이동한다.
- File > Export to > Image > JPEG/PNG/TIFF를 선택한다.
- 내보내기 대화 상자에서 출력 해상도를 설정한다.
- 저장한다.
Acrobat은 안정적인 출력을 제공하지만, 물론 유료이며 유료 SDK 없이는 스크립팅이 어렵다.
PDF-XChange Editor
가벼운 대안으로 무료 티어가 있다. File > Export > Export Pages as Images에서 형식, DPI, 페이지 범위를 선택할 수 있다.
PowerShell과 pdftoppm
Windows용 Poppler를 설치한 후 PowerShell에서 pdftoppm을 사용한다:
pdftoppm -jpeg -r 300 input.pdf output
이 명령은 300 DPI로 페이지별로 output-1.jpg, output-2.jpg 등을 생성한다.
투명 배경이 필요한 PNG의 경우:
pdftoppm -png -r 300 input.pdf output
단일 페이지만 변환할 때:
pdftoppm -jpeg -r 300 -f 1 -l 1 input.pdf output
-f와 -l 플래그는 각각 시작 페이지와 끝 페이지를 지정한다.
PowerShell과 ImageMagick
ImageMagick도 PDF를 렌더링할 수 있지만, Windows에서는 보통 내부적으로 Ghostscript를 호출한다:
magick -density 300 input.pdf[0] output.jpg
[0]은 첫 번째 페이지를 의미한다. 이를 빼면 ImageMagick이 멀티프레임 이미지를 만들려 할 수 있다.
macOS에서 변환하기
Preview
- Preview에서 PDF를 연다.
- 원하는 페이지 썸네일을 선택한다.
- File > Export를 선택하고 형식과 해상도를 설정한다.
Preview는 빠르고 사생활 보호 측면에서도 안전하지만, 한 번에 한 페이지씩 처리한다.
터미널과 sips
macOS에는 sips가 포함되어 있지만, PDF 텍스트 렌더링은 잘하지 못한다. 이미 비트맵으로 된 PDF에만 사용한다:
sips -s format jpeg input.pdf --out output.jpg
진짜 PDF 렌더링을 위해서는 Homebrew로 Poppler를 설치한다:
brew install poppler
pdftoppm -jpeg -r 300 input.pdf output
Automator 빠른 동작
Automator에서 선택한 PDF에 pdftoppm을 실행하는 우클릭 서비스를 만들 수 있다. 변환을 자주 해야 하지만 플래그를 외우고 싶지 않을 때 유용하다.
Linux에서 변환하기
Linux에서는 보통 Poppler가 최선의 선택이다.
sudo apt install poppler-utils
pdftoppm -jpeg -r 300 input.pdf output
pdftoppm -png -r 300 input.pdf output
WebP 출력이 필요하면 먼저 PNG로 변환한 뒤 cwebp를 사용한다:
pdftoppm -png -r 300 input.pdf temp
cwebp temp-1.png -o output.webp
일괄 변환
PDF가 든 폴더에서 각 파일의 첫 페이지를 하나의 이미지로 뽑고 싶다면:
for f in *.pdf; do
pdftoppm -jpeg -r 300 -f 1 -l 1 "$f" "${f%.pdf}"
done
ImageMagick
ImageMagick은 Linux에서도 동작하지만, Ghostscript를 통해 래스터화하므로 PDF 처리가 느린 편이다:
magick -density 300 input.pdf[0] -quality 90 output.jpg
-density는 PDF를 읽기 전에 지정해야 한다. 뒤에 넣으면 효과가 없다.
코드로 변환하기
TypeScript / Node.js
pdfjs-dist로 페이지를 캔버스에 렌더링한 뒤 이미지 데이터로 내보낸다.
import * as pdfjsLib from "pdfjs-dist"
import { createCanvas } from "canvas"
import fs from "fs"
async function pdfPageToPng(
pdfPath: string,
pageNumber: number,
outputPath: string,
scale: number = 2
) {
const data = new Uint8Array(fs.readFileSync(pdfPath))
const pdf = await pdfjsLib.getDocument({ data }).promise
const page = await pdf.getPage(pageNumber)
const viewport = page.getViewport({ scale })
const canvas = createCanvas(viewport.width, viewport.height)
const context = canvas.getContext("2d")
await page.render({ canvasContext: context, viewport }).promise
const buffer = canvas.toBuffer("image/png")
fs.writeFileSync(outputPath, buffer)
await pdf.destroy()
}
await pdfPageToPng("input.pdf", 1, "output.png", 2)
scale 값은 DPI와 대략적으로 매핑된다: 72 DPI에서 2이면 144 DPI 출력이다. 300 DPI를 원하면 4.17 정도를 사용한다.
브라우저 환경에서는 우리의 PDF를 PNG로 변환기가 파일을 외부로 전송하지 않고 동일한 캔버스 렌더링 단계를 수행한다.
PHP
PHP는 pdftoppm을 직접 호출하거나, ImageMagick을 래핑한 spatie/pdf-to-image 같은 라이브러리를 사용할 수 있다:
<?php
use Spatie\PdfToImage\Pdf;
$pdf = new Pdf('input.pdf');
$pdf->setPage(1)
->setOutputFormat('png')
->saveImage('output.png');
의존성을 추가하고 싶지 않다면 Poppler를 직접 호출한다:
<?php
function pdfPageToPng(string $input, int $page, string $output, int $dpi = 300): void {
$cmd = sprintf(
'pdftoppm -png -r %d -f %d -l %d %s %s',
$dpi,
$page,
$page,
escapeshellarg($input),
escapeshellarg($output)
);
exec($cmd);
}
pdfPageToPng('input.pdf', 1, 'output', 300);
이 코드는 output-1.png를 생성한다. 프로덕션에서는 직접 오류 처리를 추가해야 한다.
Go
Go에는 내장 PDF 렌더러가 없지만, github.com/gen2brain/go-fitz는 MuPDF를 래핑한다:
package main
import (
"image/jpeg"
"os"
"github.com/gen2brain/go-fitz"
)
func main() {
doc, err := fitz.New("input.pdf")
if err != nil {
panic(err)
}
defer doc.Close()
img, err := doc.Image(0)
if err != nil {
panic(err)
}
out, err := os.Create("output.jpg")
if err != nil {
panic(err)
}
defer out.Close()
if err := jpeg.Encode(out, img, &jpeg.Options{Quality: 90}); err != nil {
panic(err)
}
}
go-fitz는 PDF의 기본 렌더링 해상도로 이미지를 반환한다. DPI 제어가 필요하면 라이브러리 문서를 확인한다.
Java
Java의 PDF 작업 표준은 Apache PDFBox다.
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class PdfToImage {
public static void main(String[] args) throws IOException {
try (PDDocument document = PDDocument.load(new File("input.pdf"))) {
PDFRenderer renderer = new PDFRenderer(document);
BufferedImage image = renderer.renderImageWithDPI(0, 300);
ImageIO.write(image, "png", new File("output.png"));
}
}
}
Maven 의존성:
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>3.0.2</version>
</dependency>
renderImageWithDPI는 0 기반 페이지 인덱스와 DPI 값을 받는다.
Python
Python에서는 pymupdf나 pdf2image로 간단하게 처리할 수 있다.
pymupdf 사용:
import fitz
doc = fitz.open("input.pdf")
page = doc[0]
mat = fitz.Matrix(2, 2)
pix = page.get_pixmap(matrix=mat)
pix.save("output.png")
Matrix가 배율을 제어한다. 72 DPI PDF에서 대략 300 DPI를 원하면 fitz.Matrix(300/72, 300/72)를 사용한다.
pdf2image는 Poppler를 래핑한다:
from pdf2image import convert_from_path
images = convert_from_path("input.pdf", dpi=300, first_page=1, last_page=1)
images[0].save("output.jpg", "JPEG", quality=90)
pdf2image는 편리하지만 시스템에 Poppler가 설치되어 있어야 한다.
흔한 함정
- DPI를 잊는다. PDF 기본 렌더링은 종종 72 또는 96 DPI다. 이 해상도에서는 텍스트가 흐릿해 보인다. 품질이 중요하면 항상 출력 DPI를 지정하라.
- 색 공간을 무시한다. 프로파일 없이 CMYK PDF를 RGB로 변환하면 색이 탁하거나 과도하게 포화될 수 있다.
- 스캔 PDF를 재인코딩한다. PDF가 이미 JPEG 스캔이라면 PNG로 변환해도 잃어버린 디테일이 돌아오지 않는다. 파일만 커진다.
- 폰트 대체. 헤드리스 서버에는 PDF에 포함된 폰트가 없는 경우가 있다. 렌더러가 대체 폰트를 쓰면 레이아웃이 깨진다. PDF 생성 시 폰트를 내장하면 이를 방지할 수 있다.
- 페이지 번호의 0 기반/1 기반 혼동. 코드 API는 보통 0 기반 페이지 인덱스를, 명령줄 도구는 1 기반 페이지 번호를 사용한다.
상황별 도구 선택
- 일회성 개인용: macOS의 Preview, Windows의 PDF 리더, 또는 브라우저 변환기.
- 서버 일괄 처리:
pdftoppm또는pdf2image. - 제품 내부: Java는 Apache PDFBox, Python은
pymupdf, TypeScript는pdfjs-dist. - 프라이버시 민감 파일: 브라우저 기반 도구를 사용해 PDF가 디바이스를 벗어나지 않도록 한다.
아무것도 설치하지 않고 가장 빠르게 하고 싶다면, 우리의 PDF를 JPG로, PDF를 PNG로, PDF를 WebP로 변환기는 완전히 브라우저 안에서 동작한다.



