Оффлайн конвертер изображений в PDF на Python

От бизнес-отчётов до фотопортфолио — часто нужно вставить изображения в PDF. Онлайн-сервисы удобны, но требуют загрузки файлов и могут вызывать вопросы безопасности и приватности. Оффлайн-конвертер позволяет оставаться в локальной сети и держать файлы под контролем.
В этом руководстве мы соберём простое приложение на Python, которое:
- позволяет выбрать несколько изображений (JPG / PNG),
- показывает превью выбранных файлов,
- создаёт PDF, где каждая страница соответствует исходному изображению и сохраняет его размер.
В гайде сохранены исходные блоки кода, а также добавлены рекомендации по надёжности, отладке и альтернативные способы решения задачи.
Краткое описание используемых модулей
- Tkinter — стандартная библиотека GUI в Python. Простая и достаточно универсальная для небольших утилит.
- Pillow — форк PIL, библиотека для работы с изображениями: открытие, преобразования, ресайз.
- ReportLab — библиотека для генерации PDF и графики в Python.
Установка (если требуются пакеты):
pip install tkinterpip install Pillowpip install reportlabВажно: на многих системах Tkinter уже поставляется вместе с Python. Если pip-пакета нет, используйте менеджер пакетов ОС (например, apt, dnf, brew) чтобы установить компонент GUI.
Полный пример класса конвертера (объяснённый)
Ниже — исходный основной код из примера. Он создаёт класс ImageToPDFConverter с кнопками, превью и функциями выбора изображений, ресайза и конвертации в PDF. Код сохранён в оригинальном виде для воспроизводимости:
import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageTk
from reportlab.lib.pagesizes import landscape
from reportlab.pdfgen import canvas
class ImageToPDFConverter:
def __init__(self, root):
self.root = root
self.image_paths = []
self.root.title("Image to PDF Converter")
self.root.geometry("750x600")
self.select_images_button = tk.Button(self.root, text="Select Images", command=self.select_images, font=("Helvetica", 12),)
self.select_images_button.pack(pady=10)
self.convert_to_pdf_button = tk.Button(self.root, text="Convert to PDF", command=self.convert_to_pdf,font=("Helvetica", 12),)
self.convert_to_pdf_button.pack(pady=10)Дальше создаётся метка и фрейм для превью:
self.select_images_label = tk.Label(self.root, text="Select Images", font=("Helvetica", 14))
self.select_images_label.pack(pady=10)
self.preview_frame = tk.Frame(self.root, width=380, height=200)
self.preview_frame.pack(pady=10)Метод выбора файлов и отображения превью (резайз для превью):
def select_images(self):
self.image_paths = filedialog.askopenfilenames(initialdir="/", title="Select Images", filetypes=(("Image Files", "*.jpg *.png"),))
for i, image_path in enumerate(self.image_paths):
image = Image.open(image_path)
image = self.resize_image(image, width=150, height=150)
photo = ImageTk.PhotoImage(image)
label = tk.Label(self.preview_frame, image=photo)
label.image = photo
label.grid(row=i // 3, column=i % 3, padx=10, pady=10)Функция ресайза с сохранением пропорций и би-линейной интерполяцией:
def resize_image(self, image, width, height):
aspect_ratio = min(width / float(image.size[0]), height / float(image.size[1]))
new_width = int(aspect_ratio * image.size[0])
new_height = int(aspect_ratio * image.size[1])
resized_image = image.resize((new_width, new_height), resample=Image.Resampling.BILINEAR)
return resized_imageФункция записи PDF с установкой размера страницы под размер изображения:
def convert_to_pdf(self):
pdf_path = filedialog.asksaveasfilename(defaultextension=".pdf", filetypes=(("PDF Files", "*.pdf"),))
c = canvas.Canvas(pdf_path, pagesize=landscape)
for image_path in self.image_paths:
image = Image.open(image_path)
width, height = image.size
c.setPageSize((width, height))
c.drawImage(image_path, 0, 0, width=width, height=height)
c.showPage()
c.save()
messagebox.showinfo("Conversion Successful", f"PDF saved at {pdf_path}")И стандартный блок запуска:
if __name__ == "__main__":
root = tk.Tk()
app = ImageToPDFConverter(root)
root.mainloop()Как это работает — шаг за шагом
- Вызывается filedialog.askopenfilenames — пользователь выбирает несколько файлов.
- Для каждого файла создаётся превью: изображение открывается через Pillow, уменьшается до миниатюры и показывается в сетке.
- При конвертации каждый файл снова открывается, берутся его пиксельные размеры и устанавливаются как размеры страницы PDF. Затем изображение рисуется в левом верхнем углу (координаты 0,0) с заданными размерами.
- Для каждого изображения вызывается showPage(), чтобы создать новую страницу.
- Файл сохраняется и пользователь видит сообщение об успешном завершении.
Полезные пояснения и советы
Important: Размер страницы PDF в ReportLab задаётся в пунктах (points). В коде мы передаём пиксели как числа. В большинстве практических случаев это работает, но при печати может потребоваться конвертация в точки/мм/дюймы в зависимости от DPI. Для экранных PDF это обычно не критично.
- DPI и единицы: PDF использует points (1 point = 1/72 inch). Если нужно точное соответствие размерам в миллиметрах или дюймах, пересчитайте пиксели через DPI: points = (pixels / DPI) * 72.
- Цветовые профили: ReportLab хитро работает с цветами. Если у вас CMYK-изображения, убедитесь, что Pillow возвращает ожидаемый режим (обычно RGB). Для профессиональной печати возможна необходимость в конвертации цветового профиля.
Ошибки и когда это не сработает
- Очень большие изображения (несколько десятков мегапикселей) потребляют много RAM. При выборе большого количества таких файлов программа может вылететь по памяти.
- Форматы с прозрачностью (PNG с альфа) при простом вставлении в PDF могут отрисовываться на чёрном фоне. Решение: перед вставкой преобразовать изображение в RGB и при необходимости добавить белый фон.
- Некорректные пути/права записи: если пользователь выбирает место, куда нет прав записи, сохранение упадёт.
Улучшения и альтернативные подходы
- Командная строка с img2pdf (быстро и без GUI):
# установить img2pdf
pip install img2pdf
# пример использования в Python
import img2pdf
with open("out.pdf","wb") as f:
f.write(img2pdf.convert(["a.jpg","b.png"]))img2pdf встраивает изображения без перекодирования и обычно экономнее по памяти.
- Использовать PIL.Image.save с опцией save_all для многостраничного PDF (работает для последовательности изображений):
from PIL import Image
images = [Image.open(p).convert('RGB') for p in paths]
images[0].save('out.pdf', save_all=True, append_images=images[1:])Этот метод проще и часто эффективнее для большинства задач, особенно когда не нужна точная верстка.
PyPDF2 / pikepdf для пост-обработки PDF: если нужно объединить, вставить страницы, добавить метаданные.
Для высоконагруженных задач: использовать очереди задач (Celery/RQ) и лимитировать количество одновременно обрабатываемых изображений.
Паттерны принятия решений (ментальные модели)
- “Keep it simple” — если вам нужно просто собрать фотоальбом или распечатать снимки, PIL.Image.save обычно достаточно.
- “Control vs Convenience” — ReportLab даёт контроль над страницами и вёрсткой. img2pdf — максимально прост и эффективен по памяти.
- “Оффлайн приватность” — если файлы нельзя отправлять на серверы третьих сторон, используйте локальный инструмент с минимальным набором зависимостей.
Чек-листы по ролям
Разработчик:
- Тестировать на изображениях разных размеров и режимов (RGBA, RGB, CMYK).
- Добавить обработку ошибок чтения/записи.
- Ограничить максимальный размер изображения и количество файлов.
Обычный пользователь:
- Выбирать файлы из локальных папок.
- Проверить место для сохранения и права записи.
- Если видите предупреждение про память — закрыть другие приложения.
Системный администратор:
- Развернуть приложение в виртуальной среде (venv).
- Установить мониторинг использования памяти и логирование ошибок.
Критерии приёмки
- Приложение открывается и показывает кнопки «Select Images» и «Convert to PDF».
- Можно выбрать несколько изображений, и их превью отображаются.
- PDF сохраняется по указанному пути, и у каждой страницы размер соответствует исходному изображению (проверка в свойствах PDF — размеры страниц совпадают по пикселам).
- Приложение не падает при выборе 10 изображений 1920×1080 (проверка в тестовой среде).
Производительность и надёжность
- Память: при чтении каждого изображения лучше закрывать объекты Image после использования. При массовой обработке читайте и конвертируйте по одному файлу, не храните все большие изображения в памяти.
- Параллельность: GUI-операции выполняйте в основном потоке, тяжёлые операции (конвертация) выносите в фон (threading / multiprocessing) и показывайте индикатор прогресса.
- Логи: записывайте ошибки с трассировкой в файл журнала для отладки.
Примеры улучшений интерфейса
- Добавить кнопки «Удалить выбранное», «Очистить превью», «Переместить вверх/вниз» для управления порядком изображений.
- Поддержка drag&drop для удобства.
- Предварительный выбор ориентации страницы (портрет/альбом) или автоориентация по размеру изображения.
Совместимость и переносимость
- Windows, macOS, Linux: Tkinter присутствует не везде по умолчанию. Проверьте системные зависимости. Для задач в Linux используйте apt/ yum/ dnf для установки пакета python3-tk.
- Версии Python: код совместим с Python 3.8+. В старых версиях Pillow/ReportLab могут отличаться API.
Пример альтернативной команды для пакетной обработки (CLI)
# Простой bash-скрипт для всех jpg в папке
python - <<'PY'
import img2pdf, glob
paths = sorted(glob.glob('*.jpg'))
with open('album.pdf','wb') as f:
f.write(img2pdf.convert(paths))
PYФакты и рекомендации
- Для экранного просмотра конвертация пикселей в точки обычно опускается. Для печати учитывайте DPI.
- Для больших объёмов предпочитайте img2pdf или потоковую обработку.
- Для прозрачности PNG перед вставкой в PDF конвертируйте в RGB и добавляйте фон при необходимости.
Шаблон поведенческого инцидента (если конвертация не удалась)
- Проверить лог-приложения на ошибки чтения/записи.
- Убедиться, что файлы доступны и не повреждены.
- Проверить свободное место на диске.
- Попробовать конвертировать один файл вручную через PIL или img2pdf, чтобы локализовать проблему.
- Если падает на больших файлах — уменьшить количество одновременно обрабатываемых изображений.
Часто задаваемые вопросы
Можно ли обрабатывать PNG с прозрачностью?
Да. Но перед записью в PDF часто полезно конвертировать изображения в RGB и заполнить прозрачные области белым или любым другим фоном:
img = Image.open(path)
if img.mode in ('RGBA', 'LA'):
background = Image.new('RGB', img.size, (255,255,255))
background.paste(img, mask=img.split()[-1])
img = backgroundКак уменьшить размер итогового PDF?
- Понизить качество/разрешение изображений перед вставкой.
- Использовать сжатие JPEG при преобразовании (сохранение в JPEG с нужным quality).
- Использовать инструменты оптимизации PDF после создания (например, ghostscript).
Что лучше для массовой обработки — ReportLab или img2pdf?
Для массовой пакетной обработки img2pdf обычно эффективнее и экономнее по памяти. ReportLab лучше, если требуется сложная верстка и добавление графики/текста.




Короткая инструкция по развёртыванию (SOP)
- Создайте виртуальное окружение: python -m venv venv && source venv/bin/activate
- Установите зависимости: pip install Pillow reportlab tkinter (если необходимо)
- Скопируйте скрипт и запустите: python app.py
- Тестируйте на контролируемом наборе изображений.
Краткое резюме
- Для простых задач используйте PIL.Image.save или img2pdf.
- Для тонкого контроля верстки и добавления меток/водяных знаков — ReportLab.
- Всегда учитывайте память и цветовые режимы.
- Оффлайн-приложение защищает приватность и подходит для конфиденциальных файлов.
FAQ (коротко)
- Поддерживаются ли TIFF и другие форматы? Зависит от Pillow. TIFF обычно поддерживается, но для многостраничных TIFF потребуется дополнительная логика.
- Можно ли менять порядок страниц? Да — реализуйте перемещение элементов в image_paths до конвертации.
- Как добавить водяной знак? Используйте ReportLab для рисования текста или изображения поверх каждой страницы.
Похожие материалы
Соберите Bluetooth‑динамик своими руками
Как запланировать встречу в Microsoft Teams
Как освободить место на Android с малой памятью
Forest — приложение для фокуса и борьбы с телефоном
Android для пожилых: простая настройка телефона