借助ChatGPT快速開發圖片轉PDF的Python工具

一、開發背景與適用場景

隨著數字文檔處理需求的激增,圖片轉PDF的需求日益廣泛。從學生提交圖像化作業,到教師整合掃描試卷等資料,再到行政人員歸檔證件照片,工作中的方方面面都離不開圖片的處理。如何高效、批量地將多個圖片文件整理為一個標準PDF文件,成為日常辦公中的痛點之一。

盡管市面上已有諸多工具支持圖像轉PDF,但大多數要么受限于平臺(如僅支持Windows或Web)、要么操作繁瑣、功能單一。而借助Python強大的圖像處理與GUI庫,我們可以快速構建一款輕量級、跨平臺、功能豐富的圖片轉PDF工具,適用于:

  • 教育場景下學生作業或試卷掃描;
  • 辦公場景下的證件照片批量歸檔;
  • 圖片編輯后統一格式輸出;
  • 快速制作圖文資料手冊等。

軟件的主界面

二、主要功能與技術特點

本項目以 Tkinter 為GUI主框架,集成 PIL (Pillow) 處理圖像、threading 異步轉PDF、shutil 文件管理等模塊,具備如下功能:

1. 多圖管理與預覽

  • 支持批量添加、刪除、清空圖片;
  • 支持上下移動圖片列表中的文件排序,決定PDF頁序;
  • 實時圖片預覽,并在右側展示縮略圖;

2. 圖片編輯操作

  • 支持圖片的 90° 左右旋轉;
  • 支持水平 / 垂直翻轉;
  • 支持單張圖片的“重置”功能,回到原始狀態;
  • 所有圖像操作均在不影響原圖基礎上完成,保留數據完整性;

3. 文件命名與批處理

  • 用戶可輸入模板(如“Page_##.jpg”)對所有圖片批量重命名; 比如你們把Page改為作業_##.jpg,生成的圖片就變成作業_01.jpg, 作業_02.jpg,作業_03.jpg
  • 自動創建名為?renamed_images/?的目錄并保存副本,確保原圖不被覆蓋;

圖片重命名

4. PDF轉換與保存

  • 一鍵將所有圖片按設定順序合成并轉化為PDF文件;
  • 異步轉換不阻塞界面響應,轉化速度高;
  • 支持軟件底部進度條反饋轉換進度;
  • 自動處理圖像色彩模式為PDF兼容的RGB格式;

5. 人性化交互設計

  • 鍵盤快捷操作支持:向上 / 向下鍵調整順序,Delete鍵刪除選中;
  • 彈窗提示操作狀態,如保存路徑、錯誤提示、任務完成反饋等。

三、使用方法與操作流程

1. 啟動程序

運行腳本后,程序將自動啟動一個800×600的窗口,主界面分為頂部按鈕欄、左側列表框和右側預覽區。

2. 添加與管理圖片

點擊“添加圖片”可選取多張圖片文件導入,隨后可在列表中拖動排序,或使用“上移 / 下移”按鈕調整順序。

3. 圖像編輯與重命名

選中某張圖片后,可以在右側操作區執行旋轉、翻轉等編輯操作。如需統一重命名圖片,點擊“批量重命名”,輸入模板后自動保存副本到 renamed_images/ 文件夾中。

4. 轉換與保存PDF

點擊“轉換為PDF”,選擇保存位置后,程序將自動執行圖像轉換、拼接與導出過程。用戶可實時查看進度條反饋。

四、代碼展示

本項目采用類的寫法,主要運用了Python的自帶的標準庫如os,shutil等,完整的代碼如下:

import os
import shutil
import threading
import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog, ttk
from PIL import Image, ImageTkclass ImageToPDFApp:def __init__(self, root):self.root = rootself.root.title("圖片轉PDF工具")self.root.geometry("800x600")self.image_paths = []self.modified_images = {}self.last_dir = os.path.expanduser("~")self.renamed_dir = os.path.join(os.getcwd(), "renamed_images")self.build_ui()def build_ui(self):top_frame = tk.Frame(self.root, bg="#f0f0f0")top_frame.pack(fill=tk.X, padx=5, pady=5)buttons = [("添加圖片", self.add_images),("刪除選中", self.remove_selected),("上移", self.move_up),("下移", self.move_down),("清空列表", self.clear_list),("轉換為PDF", self.convert_to_pdf)]for text, cmd in buttons:b = tk.Button(top_frame, text=text, command=cmd, width=12, font=("Times New Roman", 12))b.pack(side=tk.LEFT, padx=3, pady=2)middle_frame = tk.Frame(self.root)middle_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)left_frame = tk.Frame(middle_frame)left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)self.lst_images = tk.Listbox(left_frame)self.lst_images.pack(fill=tk.BOTH, expand=True)self.lst_images.bind("<<ListboxSelect>>", self.update_preview)#增加快捷鍵self.lst_images.bind("<Up>", self.on_key_up)self.lst_images.bind("<Down>", self.on_key_down)self.lst_images.bind("<Delete>", self.on_key_delete)preview_frame = tk.Frame(middle_frame)preview_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)self.preview_label = tk.Label(preview_frame, text="圖片預覽", relief=tk.SUNKEN, bg="white")self.preview_label.pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=5)ctrl_frame = tk.Frame(preview_frame)ctrl_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=5)ctrl_buttons = [("向左旋轉 (90°)", lambda: self.transform_image('rotate_left')),("向右旋轉 (90°)", lambda: self.transform_image('rotate_right')),("水平翻轉", lambda: self.transform_image('flip_h')),("垂直翻轉", lambda: self.transform_image('flip_v')),("重置", self.reset_image),("批量重命名", self.batch_rename_images)  # 添加按鈕]for text, cmd in ctrl_buttons:btn = tk.Button(ctrl_frame, text=text, command=cmd, width=14, font=("Times New Roman", 12))btn.pack(fill=tk.X, pady=3)self.img_count_label = tk.Label(ctrl_frame, text="已添加 0 張圖片", font=("Times New Roman", 12), anchor='w')self.img_count_label.pack(side=tk.BOTTOM, pady=5, fill=tk.X)self.progress = ttk.Progressbar(self.root, mode="determinate")self.progress.pack(fill=tk.X, padx=5, pady=5)def on_key_up(self, event):self.move_up()def on_key_down(self, event):self.move_down()def on_key_delete(self, event):self.remove_selected()def add_images(self):files = filedialog.askopenfilenames(filetypes=[("Image files", "*.png *.jpg *.jpeg *.bmp *.gif *.tiff")],initialdir=self.last_dir)if files:for f in files:if f not in self.image_paths:self.image_paths.append(f)self.lst_images.insert(tk.END, os.path.basename(f))self.img_count_label.config(text=f"已添加 {len(self.image_paths)} 張圖片")self.lst_images.select_set(0)self.update_preview()def remove_selected(self):selected = self.lst_images.curselection()for i in reversed(selected):del self.image_paths[i]self.lst_images.delete(i)self.img_count_label.config(text=f"已添加 {len(self.image_paths)} 張圖片")self.preview_label.config(image='', text='圖片預覽')def move_up(self):i = self.lst_images.curselection()if i and i[0] > 0:idx = i[0]self.image_paths[idx - 1], self.image_paths[idx] = self.image_paths[idx], self.image_paths[idx - 1]self.lst_images.delete(idx)self.lst_images.insert(idx - 1, os.path.basename(self.image_paths[idx - 1]))self.lst_images.select_set(idx - 1)self.update_preview()def move_down(self):i = self.lst_images.curselection()if i and i[0] < len(self.image_paths) - 1:idx = i[0]self.image_paths[idx + 1], self.image_paths[idx] = self.image_paths[idx], self.image_paths[idx + 1]self.lst_images.delete(idx)self.lst_images.insert(idx + 1, os.path.basename(self.image_paths[idx + 1]))self.lst_images.select_set(idx + 1)self.update_preview()def clear_list(self):self.image_paths.clear()self.lst_images.delete(0, tk.END)self.modified_images.clear()self.preview_label.config(image='', text='圖片預覽')self.img_count_label.config(text="已添加 0 張圖片")def update_preview(self, event=None):if not self.lst_images.curselection():returnidx = self.lst_images.curselection()[0]path = self.image_paths[idx]img = self.modified_images.get(path, Image.open(path))img.thumbnail((300, 300))self.tkimg = ImageTk.PhotoImage(img)self.preview_label.config(image=self.tkimg, text='')def transform_image(self, action):idxs = self.lst_images.curselection()if not idxs:returnidx = idxs[0]path = self.image_paths[idx]img = self.modified_images.get(path, Image.open(path))if action == 'rotate_left':img = img.rotate(90, expand=True)elif action == 'rotate_right':img = img.rotate(-90, expand=True)elif action == 'flip_h':img = img.transpose(Image.FLIP_LEFT_RIGHT)elif action == 'flip_v':img = img.transpose(Image.FLIP_TOP_BOTTOM)self.modified_images[path] = imgself.update_preview()def reset_image(self):idx = self.lst_images.curselection()if not idx:returnpath = self.image_paths[idx[0]]self.modified_images.pop(path, None)self.update_preview()def batch_rename_images(self):if not self.image_paths:messagebox.showwarning("警告", "沒有圖片可重命名")returntemplate = simpledialog.askstring("命名模板", "請輸入命名模板(如 Page_##.jpg):", initialvalue="Page_##.jpg")if not template or "##" not in template:messagebox.showwarning("格式錯誤", "命名模板必須包含 ## 作為編號占位符")returnif os.path.exists(self.renamed_dir):shutil.rmtree(self.renamed_dir)os.makedirs(self.renamed_dir, exist_ok=True)new_paths = []for i, path in enumerate(self.image_paths):new_name = template.replace("##", f"{i+1:02}")new_path = os.path.join(self.renamed_dir, new_name)shutil.copy2(path, new_path)new_paths.append(new_path)self.image_paths = new_pathsself.lst_images.delete(0, tk.END)for path in self.image_paths:self.lst_images.insert(tk.END, os.path.basename(path))self.lst_images.select_set(0)self.update_preview()messagebox.showinfo("完成", f"已重命名圖片,保存在: {self.renamed_dir}")def convert_to_pdf(self):if not self.image_paths:messagebox.showwarning("警告", "沒有圖片可轉換")returnoutput_path = filedialog.asksaveasfilename(defaultextension=".pdf", filetypes=[("PDF文件", "*.pdf")])if not output_path:returndef task():images = []for i, path in enumerate(self.image_paths):img = self.modified_images.get(path, Image.open(path))if img.mode != 'RGB':img = img.convert('RGB')images.append(img)self.progress['value'] = int((i + 1) / len(self.image_paths) * 100)self.root.update_idletasks()if images:images[0].save(output_path, save_all=True, append_images=images[1:])messagebox.showinfo("成功", f"PDF已保存至: {output_path}")self.progress['value'] = 0threading.Thread(target=task).start()
if __name__ == '__main__':root = tk.Tk()app = ImageToPDFApp(root)root.mainloop()

五、Python開發項目的優勢

采用Python開發出的軟件具有多重優勢如下:

跨平臺:Python支持Windows、macOS、Linux,代碼無需修改即可兼容多系統,同時免費;

豐富的生態:Pillow、Tkinter、shutil 等庫直接支持圖像處理、GUI與文件操作,無需自行封裝底層邏輯;

快速迭代:Python代碼結構清晰,便于快速開發與后期維護,可以進行個性化擴展,滿足多種多樣的需求;

大語言模型友好:大語言模型可以充分網上豐富的資源,對于軟件的設計、開發給出建議,輔助生成功能代碼。

六、學后總結

1. 在ChatGPT的輔助下編寫一個簡潔高效的個性化圖片轉PDF工具,不僅解決了實際辦公與學習場景中的具體問題,也展示了Python在圖形界面應用、批處理和跨平臺工具開發中的巨大潛力。

2. 此工具可能根據個人實際的需要,擴展添加如OCR識別、水印添加、圖片壓縮、圖片放大等模塊,使其逐步成長為一款專業的文檔圖像工具箱。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/910183.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/910183.shtml
英文地址,請注明出處:http://en.pswp.cn/news/910183.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SuGAR代碼精簡解讀

目錄 一、全流程訓練腳本 train_full_pipeline.py 二、核心訓練邏輯 train.py 粗優化 (coarse_density_and_dn_consistency.py) 網格提取 (extract_mesh_from_coarse_sugar) 精優化 (refined_training) 兩次優化&#xff08;粗優化和精優化&#xff09;中使用的損失函數及…

大模型安全關鍵技術研究

? 引言 隨著人工智能技術的迅猛發展&#xff0c;大模型已成為推動各行業變革的核心力量。從智能客服、醫療影像識別到金融風險預測&#xff0c;大模型的應用場景不斷拓展&#xff0c;深刻改變著人們的生產生活方式。大模型已經轉變為AI領域的基礎設施&#xff0c;為解決各種…

java面試題04成員變量和局部變量的區別

成員變量(Member Variable)和局部變量(Local Variable)是面向對象編程中兩種作用域和生命周期不同的變量,主要區別體現在以下幾個方面: 1. 聲明位置 成員變量: 聲明在類內部、方法/構造器/代碼塊外部。 例如: public class Person {// 成員變量(實例變量)private Str…

升級到 .NET 9 分步指南

隨著激動人心的 .Net 9 更新正式發布&#xff0c;漫長的等待終于結束了。它帶來了一些令人驚嘆的特性&#xff0c;例如改進的 LINQ 功能、HybridCache 等等。此外&#xff0c;憑借其卓越的性能提升、更佳的安全性、更完善的協議和更易維護的特性&#xff0c;它必將吸引開發者和…

day30打卡

# 導入模塊 import math print("方式1&#xff1a;使用 import math") print(f"圓周率π的值&#xff1a;{math.pi}") print(f"2的平方根&#xff1a;{math.sqrt(2)}\n") # 導入特定項 from math import pi, sqrt print("方式2&#…

優化數據庫查詢

優化數據庫查詢 在實際開發中,數據庫查詢的性能直接關系到系統響應速度和用戶體驗。尤其在高并發環境下,低效的SQL語句會成為瓶頸,導致系統負載升高,甚至引發宕機。因此,查詢優化是數據庫性能優化中最為關鍵的一環。 為了系統性地理解數據庫查詢優化策略,本節將從SQL語…

【LeetCode#第198題】打家劫舍(一維dp)

198. 打家劫舍 - 力扣&#xff08;LeetCode&#xff09; 你是一個專業的小偷&#xff0c;計劃偷竊沿街的房屋。每間房內都藏有一定的現金&#xff0c;影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統&#xff0c;如果兩間相鄰的房屋在同一晚上被小偷闖入&#…

微前端MFE:(React 與 Angular)框架之間的通信方式

在 微前端&#xff08;MFE, Micro Frontends&#xff09; 中使用 CustomEvent 是非常常見的&#xff0c;尤其是在不同子應用&#xff08;Micro Apps&#xff09;之間通信的時候。今天將以React 主應用 ? Angular 子應用 之間的通信進行示例 React 主應用 <-> Angular 子…

408考研逐題詳解:2010年第1題——理解棧的基本操作

2010年第1題 若元素 a&#xff0c;b&#xff0c;c&#xff0c;d&#xff0c;e&#xff0c;f 依次進棧&#xff0c;允許進棧、退棧操作交替進行&#xff0c;但不允許連續三次進行退棧操作&#xff0c;則不可能得到的出棧序列是&#xff08; &#xff09; A. dcebfa \qquad B.…

python追加合并excel效率記錄

第一種合并方法&#xff1a; 在sheet的第一行&#xff0c;追加新表concat舊表 read_excel讀取舊表全部 to_excel新表追加寫入舊表 需要的時間&#xff1a; 第二種合并方法&#xff1a; 在sheet的最后一行&#xff0c;直接追加新表 load_book只讀用來獲取舊表sheet行數 read_ex…

公鑰加密與簽名算法計算詳解(含計算題例子)

一、RSA 加密算法 密鑰生成&#xff1a; 選兩個大素數 p 和 q計算 n p q計算 φ(n) (p-1)(q-1)選整數 e 滿足 1 < e < φ(n) 且 gcd(e, φ(n)) 1計算 d 滿足 d e ≡ 1 mod φ(n) 公鑰&#xff1a;(e, n) 私鑰&#xff1a;(d, n) 加密&#xff1a; c ≡ m? mod…

63 網絡交互的過程中目標設備的選擇

前言 這里主要是 調研一下 發送網絡數據包的過程中 選擇網絡設備 比如 向本機發送信息, 走的是 lo 向局域網其他主機發送信息, 走無線網卡 或者 有線網卡 基于 linux 的調試 這里主要是基于 ping 192.168.1.2 的調試 skb->dev 的初始化是在 skb->_skb_refdst 初…

DE2-115板子上用 Verilog編程實現一個分秒計數器

一、實驗目的 掌握 Verilog 語言在硬件描述中的應用&#xff0c;通過編程實現分秒計數器的邏輯功能。 學習并實踐按鍵消抖的原理與實現方法&#xff0c;提升對硬件電路中信號處理的理解。 熟悉在 DE2-115 開發板上進行 Verilog 程序的開發、調試及下載驗證流程&#xff0c;將…

R4 LSTM-火災溫度預測

import tensorflow as tf import pandas as pd import numpy as npgpus tf.config.list_physical_devices("GPU") if gpus:tf.config.experimental.set_memory_growth(gpus[0], True) #設置GPU顯存用量按需使用tf.config.set_visible_devices([gpus[0]],&…

什么是跨域問題?后端如何解決跨域問題?

跨域問題是指瀏覽器為了安全&#xff0c;對不同域&#xff08;包含不同協議、不同端口或不同主機名&#xff09;的請求進行限制&#xff0c;從而導致請求無法正常訪問后端接口。 跨域問題的產生源于瀏覽器的同源策略&#xff08;Same-Origin Policy&#xff09;&#xff0c;這…

vue | rollup 打包 | 配置 rollup.config.js 文件,更改 rollup的行為

原因&#xff1a;將入口文件 轉為 esm 和 umd 兩種格式&#xff0c;要配置 rollup Rollup 已內置到 vite 工具中&#xff0c; 命令行打包&#xff0c;參數多&#xff0c;麻煩——》解決&#xff1a;創建配置文件&#xff0c;js 寫的&#xff0c;rollup.config.js 配置 rollup.…

服務器中物理處理器和邏輯處理器的區別?

在服務器或任何計算機系統中&#xff0c;**物理處理器&#xff08;Physical Processor&#xff09;和邏輯處理器&#xff08;Logical Processor&#xff09;**是兩個不同的概念&#xff0c;它們分別代表了硬件層面和操作系統層面的處理能力。 物理處理器&#xff08;Physical P…

【Gin框架】中間件

1. 什么是中間件 (Middleware)&#xff1f; 在 Web 框架的語境下&#xff0c;中間件 (Middleware) 是一種可重用的軟件組件或函數&#xff0c;它被設計用來在 HTTP 請求-響應生命周期中的特定點攔截和處理請求或響應。在 Gin 框架中&#xff0c;中間件特指符合 gin.HandlerFun…

STUN (Session Traversal Utilities for NAT) 服務器是一種網絡協議

STUN (Session Traversal Utilities for NAT) 服務器是一種網絡協議&#xff0c;主要用于幫助位于網絡地址轉換 (NAT) 設備&#xff08;如路由器&#xff09;后面的客戶端發現自己的公共 IP 地址和端口號。這對于建立點對點 (P2P) 通信至關重要&#xff0c;尤其是在 VoIP&#…

AQS詳解

概念 AQS&#xff08;AbstractQueuedSynchronizer&#xff09; 是并發包&#xff08;java.util.concurrent&#xff09;的核心組件&#xff0c;用于構建鎖和同步器&#xff08;如 ReentrantLock、Semaphore、CountDownLatch 等&#xff09;。它通過維護一個 CLH 隊列 和 同步狀…