PDF Pythonツール ドラッグアンドドロップ(単一ファイル) ファイル指定 画像(JPG,PNG,TIFFなど)

PythonでPDFビュアーを作成する! その3:画像コピー&保存機能

概要

このブログでは、Pythonといくつかのライブラリを使用して、機能豊富なPDFビュアーを作成する方法を詳細に解説します。このビュアーは、PDFファイルをドラッグ&ドロップで読み込み、ページをナビゲートし、選択範囲を画像としてコピーまたは保存する機能を備えています。

使用例

このPDFビュアーは、教育資料の作成、業務報告書の編集、研究論文のレビューなど、さまざまな場面で活用できます。たとえば、PDFファイルの特定の部分を画像としてクリップボードにコピーしたり、JPGファイルとして保存したりすることができます。

必要なPythonライブラリとインストール方法

このプロジェクトには以下のライブラリが必要です:

  • tkinter: Pythonの標準GUIツールキット
  • PyMuPDF (別名 fitz): PDFファイルを操作するためのライブラリ
  • Pillow: 画像処理ライブラリ
  • tkinterdnd2: ドラッグアンドドロップ機能を実装するためのライブラリ
  • pywin32: Windowsのクリップボード操作をサポートするライブラリ

これらのライブラリのインストール方法は以下の通りです:

pip install pymupdf Pillow tkinterdnd2 pywin32

使用手順

  1. 上記のライブラリをインストールします。
  2. 提供されたコードをメモ帳などに丸々コピーしてPythonファイル(例:pdf_viewer.py)に保存します。
  3. 保存したPythonファイルを実行します。
  4. PDFファイルをウィンドウにドラッグ&ドロップするか、「Open File」ボタンをクリックしてファイルを選択し、PDFを表示します。
  5. 画面下部に表示されるナビゲーションボタンを使用して、ページ間を移動します。
  6. 範囲選択ツールを使用してPDFの任意の部分を選択し、「Copy to Clipboard」ボタンをクリックして選択範囲をクリップボードにコピーします。
  7. または、「Save as Image」ボタンをクリックして選択範囲をJPG画像として保存します。

注意点

  • PDFファイルのパスに特殊文字が含まれている場合、エラーが発生する可能性があります。これを避けるために、ファイルパスを適切に処理する必要があります。
  • 一部のPDFファイルでは、保護されているために読み込みができないことがあります。これらのファイルは事前に保護解除が必要です。

プログラム

下記のコードをメモ帳などに丸々コピーしてPythonファイル(例:pdf_viewer.py)にしてください。

import tkinter as tk
from tkinter import filedialog
from tkinterdnd2 import DND_FILES, TkinterDnD
import fitz # PyMuPDF
from PIL import Image, ImageTk
import io
import win32clipboard

class PDFViewer:
def __init__(self, root):
self.root = root
self.canvas = tk.Canvas(root, cursor="cross")
self.canvas.pack(fill=tk.BOTH, expand=1)

self.doc = None
self.page = None
self.img = None
self.zoom = 1 # Zoom factor for 72 ppi (default PDF is 72 ppi)

self.canvas.bind("<ButtonPress-1>", self.start_select)
self.canvas.bind("<B1-Motion>", self.on_drag)
self.canvas.bind("<ButtonRelease-1>", self.end_select)

self.selection_rect = None
self.start_x = None
self.start_y = None
self.end_x = None
self.end_y = None

self.label = tk.Label(root)
self.label.pack(fill=tk.BOTH, expand=True)

btn_frame = tk.Frame(root)
btn_frame.pack(fill=tk.X)
self.prev_btn = tk.Button(btn_frame, text="<< Previous", command=self.prev_page)
self.prev_btn.pack(side=tk.LEFT)
self.next_btn = tk.Button(btn_frame, text="Next >>", command=self.next_page)
self.next_btn.pack(side=tk.RIGHT)
open_btn = tk.Button(btn_frame, text="Open File", command=self.open_file_dialog)
open_btn.pack(side=tk.LEFT)
copy_btn = tk.Button(btn_frame, text="Copy to Clipboard", command=self.copy_to_clipboard)
copy_btn.pack(side=tk.RIGHT)
save_img_btn = tk.Button(btn_frame, text="Save as Image", command=self.save_as_image)
save_img_btn.pack(side=tk.RIGHT)

# ファイルパス表示用ラベル
self.file_path_label = tk.Label(root, text="No file selected")
self.file_path_label.pack(side=tk.TOP)

# 座標表示用ラベル
self.coord_label = tk.Label(root, text="X: 0, Y: 0")
self.coord_label.pack(side=tk.BOTTOM)

self.label.bind('<Motion>', self.mouse_move) # マウス移動イベントをバインド

root.drop_target_register(DND_FILES)
root.dnd_bind('<<Drop>>', self.drop)

def open_file_dialog(self):
file_path = filedialog.askopenfilename(filetypes=[("PDF files", "*.pdf")])
if file_path:
self.file_path_label.config(text=f"Selected file: {file_path}")
self.open_pdf(file_path)

def open_pdf(self, path):
self.doc = fitz.open(path)
self.total_pages = len(self.doc)
self.current_page = 0
self.show_page(self.current_page)

def show_page(self, page_num):
self.page = self.doc.load_page(page_num)
pix = self.page.get_pixmap(matrix=fitz.Matrix(self.zoom, self.zoom))
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
self.img = ImageTk.PhotoImage(image=img)
self.canvas.create_image(0, 0, image=self.img, anchor="nw")
self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL))
# Adjust window size
self.root.geometry(f"{pix.width}x{pix.height + 100}")

def start_select(self, event):
self.canvas.delete(self.selection_rect)
self.start_x = self.canvas.canvasx(event.x)
self.start_y = self.canvas.canvasy(event.y)
self.selection_rect = self.canvas.create_rectangle(self.start_x, self.start_y, self.start_x, self.start_y, outline="red")

def on_drag(self, event):
curX = self.canvas.canvasx(event.x)
curY = self.canvas.canvasy(event.y)
self.canvas.coords(self.selection_rect, self.start_x, self.start_y, curX, curY)

def end_select(self, event):
self.end_x = self.canvas.canvasx(event.x)
self.end_y = self.canvas.canvasy(event.y)

def get_selected_image(self):
if not self.selection_rect or not self.doc:
return None
x1, y1, x2, y2 = map(int, self.canvas.coords(self.selection_rect))
# Ensure the selection is within the image bounds
x1, y1 = max(0, x1), max(0, y1)
x2, y2 = min(self.page.rect.width * self.zoom, x2), min(self.page.rect.height * self.zoom, y2)
clip_img = self.page.get_pixmap(matrix=fitz.Matrix(2, 2), clip=fitz.Rect(x1 / self.zoom, y1 / self.zoom, x2 / self.zoom, y2 / self.zoom))
img = Image.frombytes("RGB", [clip_img.width, clip_img.height], clip_img.samples)
return img

def copy_to_clipboard(self):
img = self.get_selected_image()
if img is None:
return

output = io.BytesIO()
img.save(output, format='BMP')
data = output.getvalue()[14:] # Remove BMP header
output.close()

# Use win32clipboard to put the image in the clipboard
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
win32clipboard.CloseClipboard()

def save_as_image(self):
img = self.get_selected_image()
if img is None:
return
file_path = filedialog.asksaveasfilename(defaultextension=".jpg", filetypes=[("JPEG files", "*.jpg")])
if file_path:
img.save(file_path, format='JPEG')

def prev_page(self):
if self.current_page > 0:
self.current_page -= 1
self.show_page(self.current_page)

def next_page(self):
if self.current_page < self.total_pages - 1:
self.current_page += 1
self.show_page(self.current_page)

def mouse_move(self, event):
if self.page:
# ラベルに座標を表示
self.coord_label.config(text=f"X: {event.x}, Y: {self.page.rect.height - event.y}")

def drop(self, event):
file_path = event.data
file_path = file_path.strip('\'"{}')
self.file_path_label.config(text=f"Selected file: {file_path}")
self.open_pdf(file_path)

def open_file():
file_path = filedialog.askopenfilename(filetypes=[("PDF files", "*.pdf")])
if file_path:
viewer.load_pdf(file_path)

root = TkinterDnD.Tk()
root.title('PDF Viewer')
root.geometry('800x600')
viewer = PDFViewer(root)
menu = tk.Menu(root)
root.config(menu=menu)
file_menu = tk.Menu(menu)
menu.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Open", command=open_file)
root.mainloop()

あるいは、下のテキストファイルをダウンロードし、「.txt」を「.py」に変えることでそのまま使えます。

まとめ

このブログでは、Pythonを使用して機能豊富なPDFビュアーを開発する方法を紹介しました。GUI操作により、ユーザーフレンドリーで直感的なPDF管理ツールを簡単に作成できます。このツールは、教育、ビジネス、研究など多岐にわたる分野での文書管理とレビューに役立つでしょう

-PDF, Pythonツール, ドラッグアンドドロップ(単一ファイル), ファイル指定, 画像(JPG,PNG,TIFFなど)