1

kkFileView优化PDF图片预览增加JPEG2000标准图片支持

 1 year ago
source link: https://blog.51cto.com/xuedingmaojun/5938506
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

kkFileView优化PDF图片预览增加JPEG2000标准图片支持

精选 原创

薛定喵君 2022-12-15 08:50:33 ©著作权

文章标签 图片转换 图片加载 PDF预览 文章分类 其它 系统/运维 阅读数167

本文作者:薛定喵君

项目在使用 kkFileView 时接到反馈说部分 PDF 在预览时没有内容,显示空白图片。
查看官方​​issue​​也发现很多类似问题,但是也没有详尽好用的解决办法。

仅仅修改依赖增加特殊标准图片的处理会降低页面打开速度,因为图片转换操作比较耗时,所以我们还需要把图片转换操作改成异步并使用多线程去增加转换效率。

下面介绍一下如何更好地处理这个 PDF 特殊图片解析问题。

  1. 依赖添加。
    PDF 以图片模式预览时无内容是因为包含 JPEG2000 标准的图片,而 kk 并没有添加此类图片的解析依赖,所以我们要在​​pom.xml​​添加相关依赖,这个与网上搜到的方法大致相同。
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-core</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>jbig2-imageio</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-jpeg2000</artifactId>
<version>1.3.0</version>
</dependency>
  1. 图片转换代码优化。
    接到转换请求之后根据线程数及 PDF 总页数分配图片转换任务给每个线程,已经转换完成的页码不会重复转换。
/**
* pdf文件转换成jpg图片集
* @param pdfFilePath pdf文件路径
* @param pdfName pdf文件名称
* @param baseUrl 基础访问地址
* @return 图片访问集合
*/
public List<String> pdf2jpg(String pdfFilePath, String pdfName, String baseUrl) {
List<String> imageUrls = new ArrayList<>();
Integer imageCount = this.getConvertedPdfImage(pdfFilePath);
String imageFileSuffix = ".jpg";
String pdfFolder = pdfName.substring(0, pdfName.length() - 4);
String urlPrefix;
try {
urlPrefix = baseUrl + URLEncoder.encode(pdfFolder, uriEncoding).replaceAll("\\+", "%20");
} catch (UnsupportedEncodingException e) {
logger.error("UnsupportedEncodingException", e);
urlPrefix = baseUrl + pdfFolder;
}

// 如果当前pdf已缓存,则直接返回
try {
PDDocument doc = PDDocument.load(new File(pdfFilePath));
PDFRenderer pdfRendererMulti = new PDFRenderer(doc);
int pageCount = doc.getNumberOfPages();
int index = pdfFilePath.lastIndexOf(".");
String folder = pdfFilePath.substring(0, index);
for (int i = 0; i < pageCount; i++) {
imageUrls.add(urlPrefix + "/" + i + imageFileSuffix);
}
Integer pdf2jpgLock = this.getConvertedPdfImage(pdfFilePath.concat("_LOCK"));
if (pdf2jpgLock != null && pdf2jpgLock > 0 || (imageCount != null && imageCount > 0)) {
return imageUrls;
}
File path = new File(folder);
if (!path.exists() && !path.mkdirs()) {
logger.error("创建转换文件【{}】目录失败,请检查目录权限!", folder);
}

CompletableFuture.runAsync(() -> {
List<CompletableFuture> futures = new ArrayList<>();
for (int i = 0; i < pageCount; i++) {
int finalI = i;
CompletableFuture<String> future = CompletableFuture.supplyAsync(() ->
this.pdf2jpg(pdfRendererMulti, pdfFilePath, pdfName, baseUrl, finalI), commonThreadPool);
futures.add(future);
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
this.addConvertedPdfImage(pdfFilePath, pageCount);
this.addConvertedPdfImage(pdfFilePath.concat("_LOCK"), 0);
try {
doc.close();
logger.info("doc close");
} catch (IOException e) {
logger.error("doc close error", e);
}
}, commonThreadPool);
} catch (Exception e) {
this.addConvertedPdfImage(pdfFilePath.concat("_LOCK"), 0);
logger.error("Convert pdf to jpg exception, pdfFilePath:{}", pdfFilePath, e);
}
return imageUrls;
}

/**
* pdf文件转换成jpg图片集
* @param pdfFilePath pdf文件路径
* @param pdfName pdf文件名称
* @param baseUrl 基础访问地址
* @param pageIndex 当前页
* @return 图片访问集合
*/
public String pdf2jpg(PDFRenderer pdfRendererMulti, String pdfFilePath, String pdfName, String baseUrl, int pageIndex) {
logger.info("current thread {}, currentIndex:{}", Thread.currentThread().getName(), pageIndex);
this.addConvertedPdfImage(pdfFilePath.concat("_LOCK"), pageIndex + 1);
String imageFileSuffixMulti = ".jpg";
String pdfFolder = pdfName.substring(0, pdfName.length() - 4);
String urlPrefix;
try {
urlPrefix = baseUrl + URLEncoder.encode(pdfFolder, uriEncoding).replaceAll("\\+", "%20");
} catch (UnsupportedEncodingException e) {
logger.error("UnsupportedEncodingException", e);
urlPrefix = baseUrl + pdfFolder;
}

// 判断文件是否已存在,已存在直接返回
String imageUrl = urlPrefix + "/" + pageIndex + imageFileSuffixMulti;
File path = new File(imageUrl);
if (path.exists()) {
logger.info("{} 文件已存在!", imageUrl);
return imageUrl;
}
// 图片不存在需要转换
try {
int index = pdfFilePath.lastIndexOf(".");
String folder = pdfFilePath.substring(0, index);

String imageFilePath = folder + File.separator + pageIndex + imageFileSuffixMulti;
BufferedImage imageResource = pdfRendererMulti.renderImageWithDPI(pageIndex, 105, ImageType.RGB);
ImageIOUtil.writeImage(imageResource, imageFilePath, 105);
// 释放对象
imageResource.getGraphics().dispose();
imageResource = null;
} catch (IOException e) {
this.addConvertedPdfImage(pdfFilePath.concat("_LOCK"), 0);
logger.error("Convert pdf to jpg exception, pdfFilePath:{}", pdfFilePath, e);
}
return imageUrl;
}
  1. 页面图片加载优化。
    正在转换的图片是无法正常显示的,所以在加载出错时隔一段时间再去请求图片,直到图片转换完成可以成功显示为止。
<!-- 在图片加载标签内添加加载出错事件处理 -->
<div class="img-area">
<img
class="my-photo"
alt="loading"
onerror="imgError(this)"
data-src="${img}"
src="images/loading.gif"
/>
</div>

图片加载报错处理:

// 图片加载出错时默认显示加载动画,6秒后显示原图
function imgError(img) {
img.setAttribute("src", "images/loading.gif") let t = setTimeout(function () {
img.setAttribute("src", $(img).data('src')) clearTimeout(t) }, 6000)
}

本文只是提供一个修改思路,在实际使用过程中会略微减慢 PDF 的预览速度(图片解析需要时间),原本正常的图片也会打开地慢一点,如果确实有相关特殊 PDF 的预览需求可以参考处理。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK