当前位置: 首页 > news >正文

PDF转图片工具

背景:

今天有个朋友找我:“我有个文件需要更改,但是文档是PDF的,需要你帮我改下内容,你是搞软件的,这个对你应该是轻车熟路了吧,帮我弄弄吧”,听到这话我本想反驳,我是开发不是美工,然后跟他科普科普两者的分工和区别。后来想想还是算了,隔行如隔山,讲了可能也是白讲。干脆给他干了得了。毕竟这种类似“程序员=修电脑的”印象在亲戚朋友中早已广为流传。

起因:

一开始觉得做这个工作很简单,打开WPS,直接按他的要求编辑下就算完成就可以的,可当我打开文档编辑的时候:

呵呵,这特么干个免费的活,感情还要自己掏腰包?

于是,一个想法冒出来了,把文档转成图片,再用PS改得了,于是我又尝试转换成图片

挣扎:

我了个擦,要点脸不,也不知道啥时候起金山也养成了企鹅家的作风。于是我想想既然是帮人干活,这个钱怎么也不至于我掏吧,对,让他掏!!可话又说回来,就这么点屁事,让人花几十上百也是有点坑。

既然WPS处处要花钱,那就不用了,自己写一个不就OK

import fitz
import os
from PIL import Image
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letterdef pdf_to_images(pdf_path, zoom_x=2.0, zoom_y=2.0):# 创建输出文件夹pdf_dir = os.path.dirname(pdf_path)sub_folder = os.path.basename(pdf_path).split(".")[0]output_folder = '{}/{}/imgs'.format(pdf_dir, sub_folder)if not os.path.exists(output_folder):os.makedirs(output_folder)# 打开PDF文件pdf_document = fitz.open(pdf_path)for page_num in range(len(pdf_document)):# 获取页面page = pdf_document.load_page(page_num)# 设置变换矩阵以增加图像分辨率mat = fitz.Matrix(zoom_x, zoom_y)# 转换页面为图像pix = page.get_pixmap(matrix=mat)# 保存图像output_image_path = os.path.join(output_folder, f'page_{page_num + 1}.png')pix.save(output_image_path)print(f"PDF {pdf_path} 已成功转换为图像,并保存到文件夹 {output_folder}")def images_to_pdf(images_folder, output_pdf_path):# 获取所有图片文件image_files = [f for f in os.listdir(images_folder) if f.endswith(('png', 'jpg', 'jpeg'))]image_files.sort()  # 按名称排序,确保顺序正确if not image_files:print("没有找到图片文件。")return# 创建一个空白的 PDF 文件c = canvas.Canvas(output_pdf_path, pagesize=letter)for image_file in image_files:image_path = os.path.join(images_folder, image_file)# 打开图片并获取其尺寸with Image.open(image_path) as img:img_width, img_height = img.size# 将图片按比例缩放以适应页面page_width, page_height = letterscale = min(page_width / img_width, page_height / img_height)img_width *= scaleimg_height *= scale# 将图片绘制到 PDF 页面上c.drawImage(image_path, 0, page_height - img_height, width=img_width, height=img_height)c.showPage()  # 开始一个新页面c.save()print(f"图片已成功合并为 PDF 文件:{output_pdf_path}")if __name__ == "__main__":# 输入 PDF 文档路径# pdf_path = input("请输入 PDF 文档的路径:")# pdf_to_images(pdf_path)images_folder = r'E:\PDF_PROJECT\马赛克\images_output'  # 图片文件夹路径output_pdf_path = r'E:\PDF_PROJECT\马赛克\马赛克.pdf'  # 输出PDF文件路径images_to_pdf(images_folder, output_pdf_path)

转成图片修改好以后,再给合回去,60+行代码换了100多的会员,头一次感受到了原来技术也不是一文不值,O(∩_∩)O哈哈~!

输出:

完事后,想想这个东西既然花了时间写出来,干脆加个界面,打包成程序提供给有需要的人用,岂不是更能发挥它的价值?

说干就干:

import os
import fitz
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from datetime import datetimeclass PDFImageConverterApp(tk.Tk):def __init__(self):super().__init__()self.title("PDF-图片 转换工具")self.geometry("650x500")self.create_widgets()def create_widgets(self):self.tabControl = ttk.Notebook(self)self.pdf_to_img_tab = ttk.Frame(self.tabControl)self.img_to_pdf_tab = ttk.Frame(self.tabControl)self.tabControl.add(self.pdf_to_img_tab, text="PDF转图片")self.tabControl.add(self.img_to_pdf_tab, text="图片转PDF")self.create_pdf_to_img_widgets()self.create_img_to_pdf_widgets()self.tabControl.pack(expand=1, fill="both")def create_pdf_to_img_widgets(self):ttk.Label(self.pdf_to_img_tab, text="请选择PDF文件路径:").grid(column=0, row=0, padx=10, pady=10)self.pdf_path = tk.StringVar()ttk.Entry(self.pdf_to_img_tab, width=50, textvariable=self.pdf_path).grid(column=1, row=0, padx=10, pady=10)ttk.Button(self.pdf_to_img_tab, text="Browse", command=self.browse_pdf).grid(column=2, row=0, padx=10, pady=10)ttk.Label(self.pdf_to_img_tab, text="请选择图片输出目录:").grid(column=0, row=1, padx=10, pady=10)self.img_output_folder = tk.StringVar()ttk.Entry(self.pdf_to_img_tab, width=50, textvariable=self.img_output_folder).grid(column=1, row=1, padx=10,pady=10)ttk.Button(self.pdf_to_img_tab, text="Browse", command=self.browse_img_output_folder).grid(column=2, row=1,padx=10, pady=10)ttk.Label(self.pdf_to_img_tab, text="图片质量:").grid(column=0, row=2, padx=10, pady=10)self.img_quality = tk.StringVar(value="标清")ttk.Combobox(self.pdf_to_img_tab, textvariable=self.img_quality, values=["标清", "高清", "超清"]).grid(column=1, row=2, padx=10, pady=10)self.pdf_to_img_progress = ttk.Progressbar(self.pdf_to_img_tab, orient="horizontal", length=400,mode="determinate")self.pdf_to_img_progress.grid(column=0, row=3, columnspan=3, padx=10, pady=10)self.pdf_to_img_log = tk.Text(self.pdf_to_img_tab, height=10, width=70)self.pdf_to_img_log.grid(column=0, row=4, columnspan=3, padx=10, pady=10)ttk.Button(self.pdf_to_img_tab, text="转换", command=self.convert_pdf_to_images).grid(column=0, row=5,columnspan=3, padx=10,pady=10)def create_img_to_pdf_widgets(self):ttk.Label(self.img_to_pdf_tab, text="请选择图片目录:").grid(column=0, row=0, padx=10, pady=10)self.images_folder = tk.StringVar()ttk.Entry(self.img_to_pdf_tab, width=50, textvariable=self.images_folder).grid(column=1, row=0, padx=10,pady=10)ttk.Button(self.img_to_pdf_tab, text="Browse", command=self.browse_images_folder).grid(column=2, row=0, padx=10,pady=10)ttk.Label(self.img_to_pdf_tab, text="请选择PDF输出目录:").grid(column=0, row=1, padx=10, pady=10)self.pdf_output_path = tk.StringVar()ttk.Entry(self.img_to_pdf_tab, width=50, textvariable=self.pdf_output_path).grid(column=1, row=1, padx=10,pady=10)ttk.Button(self.img_to_pdf_tab, text="Browse", command=self.browse_pdf_output_path).grid(column=2, row=1,padx=10, pady=10)self.img_to_pdf_progress = ttk.Progressbar(self.img_to_pdf_tab, orient="horizontal", length=400,mode="determinate")self.img_to_pdf_progress.grid(column=0, row=2, columnspan=3, padx=10, pady=10)self.img_to_pdf_log = tk.Text(self.img_to_pdf_tab, height=10, width=70)self.img_to_pdf_log.grid(column=0, row=3, columnspan=3, padx=10, pady=10)ttk.Button(self.img_to_pdf_tab, text="转换", command=self.convert_images_to_pdf).grid(column=0, row=4,columnspan=3, padx=10,pady=10)def browse_pdf(self):file_path = filedialog.askopenfilename(filetypes=[("PDF files", "*.pdf")])if file_path:self.pdf_path.set(file_path)def browse_img_output_folder(self):folder_path = filedialog.askdirectory()if folder_path:self.img_output_folder.set(folder_path)def browse_images_folder(self):folder_path = filedialog.askdirectory()if folder_path:self.images_folder.set(folder_path)def browse_pdf_output_path(self):file_folder = filedialog.askdirectory()if file_folder:timestamp = datetime.now().strftime("%y-%m-%d_%H%M%S")output_pdf_path = os.path.join(file_folder, f"output_{timestamp}.pdf")self.pdf_output_path.set(output_pdf_path)def log_message(self, log_widget, message):log_widget.insert(tk.END, message + "\n")log_widget.see(tk.END)def convert_pdf_to_images(self):pdf_path = self.pdf_path.get()output_folder = self.img_output_folder.get()quality = self.img_quality.get()if not pdf_path or not output_folder or not quality:messagebox.showwarning("Warning", "请选择所有输入项.")returnzoom_x, zoom_y = 1.0, 1.0if quality == "高清":zoom_x, zoom_y = 2.0, 2.0elif quality == "超清":zoom_x, zoom_y = 3.0, 3.0self.pdf_to_img_progress['value'] = 0self.update()pdf_document = fitz.open(pdf_path)total_pages = len(pdf_document)for page_num in range(total_pages):page = pdf_document.load_page(page_num)mat = fitz.Matrix(zoom_x, zoom_y)pix = page.get_pixmap(matrix=mat)output_image_path = os.path.join(output_folder, f'page_{page_num + 1}.png')pix.save(output_image_path)self.pdf_to_img_progress['value'] = (page_num + 1) / total_pages * 100self.log_message(self.pdf_to_img_log, f"Page {page_num + 1}/{total_pages} converted.")self.update()messagebox.showinfo("Info", "图片输出完成.")def convert_images_to_pdf(self):images_folder = self.images_folder.get()output_pdf_path = self.pdf_output_path.get()if not images_folder or not output_pdf_path:messagebox.showwarning("Warning", "请选择所有输入项.")returnself.img_to_pdf_progress['value'] = 0self.update()image_files = [f for f in os.listdir(images_folder) if f.endswith(('png', 'jpg', 'jpeg'))]image_files.sort()total_images = len(image_files)if not image_files:messagebox.showwarning("Warning", "该文件夹下没有图片,请重新选择!")returnc = canvas.Canvas(output_pdf_path, pagesize=letter)for idx, image_file in enumerate(image_files):image_path = os.path.join(images_folder, image_file)with Image.open(image_path) as img:img_width, img_height = img.sizepage_width, page_height = letterscale = min(page_width / img_width, page_height / img_height)img_width *= scaleimg_height *= scalec.drawImage(image_path, 0, page_height - img_height, width=img_width, height=img_height)c.showPage()self.img_to_pdf_progress['value'] = (idx + 1) / total_images * 100self.log_message(self.img_to_pdf_log, f"Image {idx + 1}/{total_images} added to PDF.")self.update()c.save()messagebox.showinfo("Info", "PDF转换完成!")if __name__ == "__main__":app = PDFImageConverterApp()app.mainloop()

打包exe传送门:

https://download.csdn.net/download/Hfengxiang/89409663

结语:

突然冒出个想法,朋友们,生活或工作中遇到类似这样的痛点,欢迎在评论区讨论,一起研究研究看看能否用代码解决^_^

相关文章:

PDF转图片工具

背景: 今天有个朋友找我:“我有个文件需要更改,但是文档是PDF的,需要你帮我改下内容,你是搞软件的,这个对你应该是轻车熟路了吧,帮我弄弄吧”,听到这话我本想反驳,我是开…...

Day 19:419. 甲板上的战舰

Leetcode 419. 甲板上的战舰 给你一个大小为 m x n 的矩阵 board 表示甲板,其中,每个单元格可以是一艘战舰 ‘X’ 或者是一个空位 ‘.’ ,返回在甲板 board 上放置的 战舰 的数量。 战舰 只能水平或者垂直放置在 board 上。换句话说&#xff…...

Web前端专科实习:技能提升、实践挑战与职业展望

Web前端专科实习:技能提升、实践挑战与职业展望 在数字化时代,Web前端技术作为连接用户与互联网世界的桥梁,其重要性日益凸显。作为一名Web前端专科实习生,我有幸在这个充满机遇和挑战的领域进行实践学习。接下来,我将…...

简单脉冲动画效果实现

简单脉冲动画效果实现 效果展示 CSS 知识点 CSS 变量的灵活使用CSS 动画使用 页面整体结构实现 <div class"pulse"><span style"--i: 1"></span><span style"--i: 2"></span><span style"--i: 3"…...

apache poi 插入“下一页分节符”并设置下一节纸张横向的一种方法

一、需求描述 我们知道&#xff0c;有时在word中需要同时存在不同的节&#xff0c;部分页面需要竖向、部分页面需要横向。本文就是用java调用apache poi来实现用代码生成上述效果。下图是本文实现的效果&#xff0c;供各位看官查阅&#xff0c;本文以一篇课文为例&#xff0c;…...

【React】useCallback和useMemo使用指南

useCallback和useMemo是React中两个用于优化性能的Hooks。以下是它们的使用指南,分点表示并归纳了关键信息: useCallback useCallback返回一个记忆化的回调函数,该回调函数只在它的依赖项发生改变时才会更新。这对于在组件渲染之间保持稳定的引用特别有用,可以防止不必要…...

XMind软件下载-详细安装教程视频

​简介 XMind是一款实用的思维导图软件&#xff0c;简单易用、美观、功能强大&#xff0c;拥有高效的可视化思维模式&#xff0c;具备可扩展、跨平台、稳定性和性能&#xff0c;真正帮助用户提高生产率&#xff0c;促进有效沟通及协作。中文官方网站&#xff1a;http://www.x…...

一个小的画布Canvas页面,记录点的轨迹

Hello大家好&#xff0c;好久没有更新了&#xff0c;最近在忙一些其他的事&#xff0c;今天说一下画布canvas&#xff0c;下面是我的代码&#xff0c;实现了一个点从画布的&#xff08;0,0&#xff09;到&#xff08;canvas.width&#xff0c;canvas.height&#xff09;的一个实…...

docker-compose教程

1. docker-compose是什么&#xff1f; 1. 1 简介 compose、machine 和 swarm 是docker 原生提供的三大编排工具。 简称docker三剑客。Compose 项目是 Docker 官方的开源项目&#xff0c;定义和运行多个 Docker 容器的应用&#xff08;Defining and running multi-container Do…...

结果出乎意料!MySQL和MariaDB谁快?MySQL 8.0比MySQL 5.6快吗?

MySQL和MariaDB哪个更快&#xff1f;MySQL 8.0的版本和早期MySQL 5.6的版本哪个更快&#xff1f;这儿有个第三方的测试报告回答了这两个大家关心的问题&#xff0c;姚远来和大家一起解读一下。https://smalldatum.blogspot.com/2024/04/sysbench-on-small-server-mariadb-and.h…...

Alienware外星人X17R2 原装Win11系统镜像下载 带SupportAssist OS Recovery一键恢复

装后恢复到您开箱的体验界面&#xff0c;包括所有原机所有驱动AWCC、Mydell、office、mcafee等所有预装软件。 最适合您电脑的系统&#xff0c;经厂家手调试最佳状态&#xff0c;性能与功耗直接拉满&#xff0c;体验最原汁原味的系统。 原厂系统下载网址&#xff1a;http://w…...

【NI国产替代】高速数据采集模块,最大采样率为 125 Msps,支持 FPGA 定制化

• 双通道高精度数据采集 • 支持 FPGA 定制化 • 双通道高精度采样率 最大采样率为 125 Msps12 位 ADC 分辨率 最大输入电压为 0.9 V -3 dB 带宽为 30 MHz 支持 FPGA 定制化 根据需求编程实现特定功能和性能通过定制 FPGA 实现硬件加速&#xff0c;提高系统的运算速度FPGA…...

【网络安全的神秘世界】2024.6.6 Docker镜像停服?解决最近Docker镜像无法拉取问题

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 解决Docker镜像无法拉取问题 &#x1f64b;‍♂️问题描述 常用镜像站&#xff1a;阿里云、科大、南大、上交等&#xff0c;全部挂掉 执行docker pull命…...

【Python入门与进阶】1基本输入和输出

基本输入输出 1.等号赋值 1.1 基本赋值 number_110number_1 1.2 多个赋值 number_2number_3number_420 number_2 number_3 number_4 1.3 多重赋值 number_5,number_6,number_730,35,40 number_5 number_6 number_7 1.4 下划线赋值 _50 _ 2.命名规则 注意&#xff1a…...

CTF Show MISC做题笔记

MISCX 30 题目压缩包为misc2.rar,其中包含三个文件:misc1.zip, flag.txt, hint.txt。其中后两个文件是加密的。 先解压出misc1.zip, 发现其中包含两个文件&#xff1a;misc.png和music.doc。其中后面文件是加密的。 解压出misc.png,发现图片尾部有消息&#xff1a;flag{flag…...

【QT5】<总览二> QT信号槽、对象树及常用函数

文章目录 前言 一、QT信号与槽 1. 信号槽连接模型 2. 信号槽介绍 3. 自定义信号槽 二、QT的对象树 三、添加资源文件 四、样式表的使用 五、QSS文件的使用 六、常用函数与宏 前言 承接【QT5】&#xff1c;总览一&#xff1e; QT环境搭建、快捷键及编程规范。若存在版…...

Button按钮类

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 按钮是GUI界面中应用最为广泛的控件&#xff0c;它常用于捕获用户生成的单击事件&#xff0c;其最明显的用途是触发绑定到一个处理函数。 wxPython类…...

代码随想录-二叉树 | 111 二叉树的最小深度

代码随想录-二叉树 | 111 二叉树的最小深度 LeetCode 111 二叉树的最小深度解题思路代码难点总结 LeetCode 111 二叉树的最小深度 题目链接 代码随想录 题目描述 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说…...

PCA降维算法

decomposition.h #pragma once #include <arrayfire.h>namespace decomposition {class PCA{public:af::array zero_centred(af::array...

Fast R-CNN 与 R-CNN的不同之处

目录 一、Fast R-CNN如何生成候选框特征矩阵 二、 关于正负样本的解释 三、训练样本的候选框 四、Fast R-CNN网络架构 4.1 分类器 4.2 边界框回归器 一、Fast R-CNN如何生成候选框特征矩阵 在R-CNN中&#xff0c;通过SS算法得到2000个候选框&#xff0c;则需要进行2000…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

2024年赣州旅游投资集团社会招聘笔试真

2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

均衡后的SNRSINR

本文主要摘自参考文献中的前两篇&#xff0c;相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程&#xff0c;其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt​ 根发送天线&#xff0c; n r n_r nr​ 根接收天线的 MIMO 系…...

鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南

1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发&#xff0c;使用DevEco Studio作为开发工具&#xff0c;采用Java语言实现&#xff0c;包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数&#xff08;函数作为参数、返回值&#xff09; 三、匿名函数与闭包1. 匿名函数&#xff08;Lambda函…...