相关链接
背景介绍
最近做标书,涉及到很多批量pdf文件要转图片,再将插入word的使用场景,大概几千份,本着能省就省的原则,下载了四五个app,基本都是要收费,永久会员50~600不等,不付费会受到各种功能限制(禁用批量功能、单个文件页数限制)。
自己尝试写个小工具,由于之前做的小工具都是打成jar包,只能在有jre环境的电脑上黑窗口操作。这次转为exe文件,用到的GUI和exe4j属于新的尝试,最终效果还比较满意。
开发流程
两处避坑:
1. macos M1芯片下载后使用不了,换了台windows才运行成功。
2. 下载时默认exe4j 9.0,应该是指对应的版本为java9及以上。我的项目使用jdk8,就要下载exe4j 8.0版本,否则会卡在其中一步,点下一步没有反应,进行不下去。
卡住的位置:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.examplegroupId>
<artifactId>pdf_2_imageartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.apache.pdfboxgroupId>
<artifactId>fontboxartifactId>
<version>2.0.9version>
dependency>
<dependency>
<groupId>org.apache.pdfboxgroupId>
<artifactId>pdfboxartifactId>
<version>2.0.9version>
dependency>
<dependency>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
<version>1.2version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-shade-pluginartifactId>
<version>1.4version>
<executions>
<execution>
<phase>packagephase>
<goals>
<goal>shadegoal>
goals>
<configuration>
<filters>
<filter>
<artifact>*:*artifact>
<excludes>
<exclude>META-INF/*.SFexclude>
<exclude>META-INF/*.DSAexclude>
<exclude>META-INF/*.RSAexclude>
excludes>
filter>
filters>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.groupies.Pdf2ImageIteratorWindowsAdaptivemainClass>
transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlersresource>
transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemasresource>
transformer>
transformers>
configuration>
execution>
executions>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>2.3.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>utf-8encoding>
configuration>
plugin>
plugins>
build>
project>
package com.groupies;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
/**
* @author GroupiesM
* @date 2023/11/30
* @version 1.2
* @introduction pdf转图片工具类,awt可视化界面,会自动遍历所有子目录,窗口大小自适应
*/
public class Pdf2ImageIteratorWindowsAdaptive {
//公共变量
static String filePath;//基础路径
static String fileName;//文件名称
//用户输入
static Integer dpiInput;//dpi清晰度
static String imageTypeInput;//图片类型
static File fileUrl;//文件目录
//循环相关
static Integer taskCount = 0;//任务数量
static Integer imageCount = 0;//图片数量
//主体
static Frame frame = new Frame("pdf转图片小工具 -- by GroupiesM");
//控制台
static TextArea textAreaConsole = new TextArea("");
static StringBuffer sb = new StringBuffer("");
//按比例获取尺寸
static Toolkit toolkit = Toolkit.getDefaultToolkit();
static int frameWidth = toolkit.getScreenSize().width * 2 / 5;//宽
static int frameHeight = toolkit.getScreenSize().height * 19 / 20;//高
static int leftEdge = frameWidth / 10;//左边距
static int elementWidth = frameWidth * 4 / 5;//组件宽度
static int elementHeight = frameWidth / 20;//组件宽度
//字体
static Font titleFontLabel = new Font("黑体", Font.BOLD, Math.max((frameWidth / 55), 12));//标签样式
static Font bodyFontLabel = new Font("宋体", Font.PLAIN, Math.max((frameWidth / 55), 12));//标签样式
/**
* @param args
* @introduction 程序入口
*/
public static void main(String[] args) {
textAreaConsole.setText("介绍:\nhttps://blog.csdn.net/qq_43529621/article/details/134758957");
initGUI();
}
/**
* @param folder
* @introduction 遍历目录中的所有pdf、子目录中的所有pdf,发现pdf文件则调用pdf2png方法转换pdf文件为png
*/
public static void traverseFolder(File folder) {
if (folder.isDirectory()) {
File[] files = folder.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
traverseFolder(file); // 递归遍历子文件夹
} else if (file.getAbsolutePath().endsWith(".pdf")) {//如果是pdf文件,转图片
filePath = folder.getAbsolutePath();//文件目录
fileName = file.getName();//文件名
sb.append("------------Task" + ++taskCount + "------------\r\n");
sb.append("目录:" + folder.getAbsolutePath() + "\r\n");
sb.append("文件:" + file.getAbsolutePath() + "\r\n");
textAreaConsole.setText(sb.toString());
System.out.println("------------Task" + taskCount + "------------");
System.out.println("目录:" + folder.getAbsolutePath());
System.out.println("文件:" + file.getAbsolutePath());
try {
pdf2png(filePath, filePath, fileName, imageTypeInput);
} catch (Exception e) {
//e.printStackTrace();
sb.append(e.getMessage() + "\r\n");
textAreaConsole.setText(sb.toString());
}
}
}
}
} else {
System.out.println("路径有误,请重新输入");
}
}
/**
* @introduction 使用pdfbox将整个pdf转换成图片
* @param sourceDirectory
* @param targetDirectory
* @param filename
* @param type 图片类型 png 和jpg
* @throws Exception
*/
public static void pdf2png(String sourceDirectory, String targetDirectory, String filename, String type) throws Exception {
long startTime = System.currentTimeMillis();
// 将文件地址和文件名拼接成路径 注意:线上环境不能使用\\拼接
File file = new File(sourceDirectory + "/" + filename);
try {
// 写入文件
PDDocument doc = PDDocument.load(file);
PDFRenderer renderer = new PDFRenderer(doc);
int pageCount = doc.getNumberOfPages();
for (int i = 0; i < pageCount; i++) {
// dpi为144,越高越清晰,转换越慢
BufferedImage image = renderer.renderImageWithDPI(i, dpiInput); // Windows native DPI
// 将图片写出到该路径下
sb.append("图片" + ++imageCount + ":" + targetDirectory + "/" + filename.replace(".pdf", "") + "_" + (i + 1) + "." + type + "\r\n");
textAreaConsole.setText(sb.toString());
System.out.println("图片" + imageCount + ":" + targetDirectory + "/" + filename.replace(".pdf", "") + "_" + (i + 1) + "." + type);
File imageTarget = new File(targetDirectory + "/" + filename.replace(".pdf", "") + "_" + (i + 1) + "." + type);
//覆盖写文件
ImageIO.write(image, type, imageTarget);
}
long endTime = System.currentTimeMillis();//结束时间
doc.close();//关流,否则会报异常 Warning: You did not close a PDF Document
sb.append("共耗时:" + ((endTime - startTime) / 1000.0) + "秒\r\n");
textAreaConsole.setText(sb.toString());
System.out.println("共耗时:" + ((endTime - startTime) / 1000.0) + "秒"); //转化用时
} catch (IOException e) {
e.printStackTrace();
sb.append(e.getMessage());
textAreaConsole.setText(sb.toString());
}
}
/**
* @introduction 初始化GUI页面
*/
public static void initGUI() {
//todo 0.创建窗口Frame
frame.setVisible(true);
frame.setSize(frameWidth, frameWidth * 203 / 200);//设置大小
frame.setBackground(new Color(88, 87, 86));//窗口背景色
frame.setLayout(null);
//todo 1.1 《dpi》标签
Label labelDpi = new Label(" 一、dpi分辨率(越高越清晰,转换越慢)");
labelDpi.setBounds(leftEdge, elementHeight * 3 / 2, elementWidth, elementHeight);//设置位置、大小
labelDpi.setBackground(Color.darkGray);//背景色
labelDpi.setForeground(Color.WHITE);//字体颜色
labelDpi.setFont(titleFontLabel);
frame.add(labelDpi);//给窗口添加组件
//todo 1.2 《dpi》滚动条
Scrollbar scrollbarDpi = new Scrollbar(Scrollbar.HORIZONTAL, 144, elementHeight, 100, 600 + elementHeight); //构造一个具有指定方向,初始值,粗细、最小值、最大值的新滚动条
scrollbarDpi.setBounds(leftEdge * 3 / 2, elementHeight * 27 / 10, elementWidth / 2, elementHeight * 2 / 3);//设置位置、大小
frame.add(scrollbarDpi);//给窗口添加组件
//todo 1.3《dpi回显》标签
Label labelDpiValue = new Label("\t\t144");
labelDpiValue.setBounds(leftEdge * 3 / 2 + elementWidth / 2, elementHeight * 27 / 10, leftEdge, elementHeight * 2 / 3);//设置位置、大小
labelDpiValue.setForeground(Color.WHITE);//字体颜色
labelDpiValue.setBackground(Color.darkGray);//字体颜色
labelDpiValue.setFont(bodyFontLabel);
frame.add(labelDpiValue);//给窗口添加组件
//todo 1.4《dpi》滚动条背景
Label labelDpiBackGround = new Label();
labelDpiBackGround.setBounds(leftEdge, elementHeight * 5 / 2, elementWidth, elementHeight * 6 / 5);//设置大小
labelDpiBackGround.setBackground(Color.darkGray);//背景色
frame.add(labelDpiBackGround);//给窗口添加组件
//todo 2.1《转换类型》标签
Label labelImageType = new Label(" 二、图片类型(jpg:有损压缩;png:无损压缩)");
labelImageType.setBounds(leftEdge, elementHeight * 21 / 5, elementWidth, elementHeight);//设置大小
labelImageType.setBackground(Color.darkGray);//设置标签背景色
labelImageType.setForeground(Color.white);//设置标签背景色
labelImageType.setFont(titleFontLabel);
frame.add(labelImageType);//add()给窗口添加标签
//todo 2.2《转换类型》单选框
CheckboxGroup boxImageType = new CheckboxGroup();
Checkbox ck1 = new Checkbox("jpg", boxImageType, true);
Checkbox ck2 = new Checkbox("png", boxImageType, false);
ck1.setBounds(leftEdge * 3 / 2, elementHeight * 26 / 5, leftEdge * 3 / 2, elementHeight);
ck2.setBounds(leftEdge * 6 / 2, elementHeight * 26 / 5, leftEdge * 3 / 2, elementHeight);
ck1.setForeground(Color.white);
ck2.setForeground(Color.white);
ck1.setBackground(Color.darkGray);
ck2.setBackground(Color.darkGray);
ck1.setFont(bodyFontLabel);
ck2.setFont(bodyFontLabel);
frame.add(ck1);//给窗口添加组件
frame.add(ck2);//给窗口添加组件
//todo 2.3《转换类型》背景
Label labelImageTypeBackGround = new Label();
labelImageTypeBackGround.setBounds(leftEdge, elementHeight * 26 / 5, elementWidth, elementHeight * 6 / 5);//设置大小
labelImageTypeBackGround.setBackground(Color.darkGray);//背景色
frame.add(labelImageTypeBackGround);//给窗口添加组件
//todo 3.1《目录》标签Label
Label labelUrl = new Label(" 三、文件路径,例如:C:\\Users\\GroupiesM\\Desktop");
labelUrl.setBounds(leftEdge, elementHeight * 7, elementWidth, elementHeight);//设置大小
labelUrl.setBackground(Color.darkGray);//背景色
labelUrl.setForeground(Color.WHITE);//字体颜色
labelUrl.setFont(titleFontLabel);
frame.add(labelUrl);//给窗口添加组件
//todo 3.2《目录》输入框
TextField textUrl = new TextField(elementHeight);
textUrl.setText("C:\\Users\\GroupiesM\\Desktop");
textUrl.setFont(bodyFontLabel);
textUrl.setBounds(leftEdge, elementHeight * 8, elementWidth, elementHeight);
frame.add(textUrl);//给窗口添加组件
//todo 4《清空》按钮
Button buttonClean = new Button("清空");//创建按钮
buttonClean.setBounds(leftEdge * 2, elementHeight * 19 / 2, leftEdge * 7 / 5, elementHeight * 7 / 5);//设置位置
buttonClean.setFont(titleFontLabel);
frame.add(buttonClean);//给窗口添加组件
//todo 5《转换》按钮
Button buttonTransform = new Button("转换");//创建按钮
buttonTransform.setBounds(leftEdge * 22 / 5, elementHeight * 19 / 2, leftEdge * 7 / 5, elementHeight * 7 / 5);//设置位置
buttonTransform.setFont(titleFontLabel);
frame.add(buttonTransform);//给窗口添加组件
//todo 6《退出》按钮
Button buttonExit = new Button("退出");//创建按钮
buttonExit.setBounds(leftEdge * 34 / 5, elementHeight * 19 / 2, leftEdge * 7 / 5, elementHeight * 7 / 5);//设置位置
buttonExit.setFont(titleFontLabel);
frame.add(buttonExit);//给窗口添加组件
//todo 7《控制台》
textAreaConsole.setBounds(leftEdge, elementHeight * 113 / 10, elementWidth, elementHeight * 9);//设置大小
textAreaConsole.setBackground(Color.white);//背景色
textAreaConsole.setForeground(Color.black);//背景色
textAreaConsole.setFont(bodyFontLabel);
frame.add(textAreaConsole);//给窗口添加组件
//todo 7.1《dpi滚动条》触发动作
scrollbarDpi.addAdjustmentListener(e -> labelDpiValue.setText(String.valueOf("\t\t" + scrollbarDpi.getValue())));//回显滚动条
//todo 7.2 《清空》触发动作
buttonClean.addActionListener(actionEvent -> textUrl.setText("\b"));//清空输入框
//todo 7.3 《转换》触发动作
buttonTransform.addActionListener(actionEvent -> startTransform(scrollbarDpi, boxImageType, textUrl));//开始转换,并校验
//todo 7.4 《退出》触发动作
buttonExit.addActionListener(Pdf2ImageIteratorWindowsAdaptive::actionPerformed);
//frame.pack();
}
/**
* @introduction 开始转换,初始化参数,校验文件目录是否存在
* @param scrollbarDpi dpi清晰度
* @param group 图片类型,单选框
* @param textUrl 文件目录
*/
public static void startTransform(Scrollbar scrollbarDpi, CheckboxGroup group, TextField textUrl) {
dpiInput = scrollbarDpi.getValue();//设置dpi
imageTypeInput = group.getSelectedCheckbox().getLabel();//设置图片类型
fileUrl = new File(textUrl.getText().trim());//创建文件
//如果目录不存在,不执行指令
if (!fileUrl.exists()) {
textUrl.setForeground(Color.red);
if (!textUrl.getText().trim().endsWith("(路径不存在)")) textUrl.setText(textUrl.getText().trim() + "(路径不存在)");
textUrl.selectAll();
return;
}
//校验通过
textUrl.setForeground(Color.black);
//初始化参数
sb = new StringBuffer();
taskCount = 0;//任务数量
imageCount = 0;//图片数量
//开始转换
traverseFolder(fileUrl);
}
/**
* @introduction 退出按钮, 监听
* @param e
*/
private static void actionPerformed(ActionEvent e) {
sb = initSB();
textAreaConsole.setText("....................正在退出.....................\n" +
"===============================================\n" +
sb.toString());
try {
Thread.sleep(1600);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
sb = initSB();
textAreaConsole.setText("....................正在退出.....................\n" +
"===============================================\n" +
sb.reverse().toString());
try {
Thread.sleep(1300);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.exit(0);
}
/**
* @introduction 退出程序-控制台
* @return StringBuffer
*/
public static StringBuffer initSB() {
sb = new StringBuffer();
sb.append(" _oo0oo_ \n");
sb.append(" o8888888o \n");
sb.append(" 88” . “88 \n");
sb.append(" ( |- _ - | ) \n");
sb.append(" O \\ = / O \n");
sb.append(" __/ ‘---’ \\__ \n");
sb.append(" '\\| |/' \n");
sb.append(" / \\\\||| : |||// \\ \n");
sb.append(" / _||||| -卍- |||||_ \\ \n");
sb.append(" | |\\\\\\ - /// |_ \\ \n");
sb.append(" | \\_| ''\\---/'' |_/ | \n");
sb.append(" \\ ,-\\__ '-' __/- , / \n");
sb.append(" ___'. .' /--.--\\ '. .'___ \n");
sb.append(" .”“'< ‘.___\\_<|>_/___.'> ' ”“. \n");
sb.append(" | | : '-\\'.:'\\ _ /' : .'/ - ': | | \n");
sb.append(" \\ \\ '_. \\_ __\\ /__ _/ , - ' / / \n");
sb.append(" ====='-.___'.___ \\____//___.-' ___.-' ===== \n");
sb.append(" '=----=' \n");
sb.append(" \n");
return sb;
}
}
23/12/3
M