Java中PDF轉(zhuǎn)高質(zhì)量圖片:使用Apache PDFBox庫(kù)

2024-11-29 12:02 更新

大家好,我是 V 哥。在Java中,將PDF文件轉(zhuǎn)換為高質(zhì)量的圖片可以使用不同的庫(kù),其中最常用的庫(kù)之一是 Apache PDFBox。通過(guò)該庫(kù),你可以讀取PDF文件,并將每一頁(yè)轉(zhuǎn)換為圖像文件。為了提高圖像的質(zhì)量,你可以指定分辨率等參數(shù)。此外,也可以結(jié)合 Java ImageIO 來(lái)保存生成的圖片文件。

如何實(shí)現(xiàn)

下面V哥通過(guò)一個(gè)詳細(xì)的案例,來(lái)展示如何使用 PDFBox 實(shí)現(xiàn) PDF 轉(zhuǎn)高質(zhì)量圖片:

所需依賴

首先,確保你已經(jīng)在項(xiàng)目中添加了 PDFBox 依賴。你可以通過(guò)Maven來(lái)添加:

  1. <dependency>
  2. <groupId>org.apache.pdfbox</groupId>
  3. <artifactId>pdfbox</artifactId>
  4. <version>2.0.29</version> <!-- 確保使用最新的版本 -->
  5. </dependency>

實(shí)現(xiàn)步驟

先來(lái)捋一下實(shí)現(xiàn)步驟哈。

  1. 加載 PDF 文件
  2. 設(shè)置渲染參數(shù)(如 DPI 來(lái)控制圖片分辨率)
  3. 將每頁(yè) PDF 渲染為圖片
  4. 保存圖片

通過(guò)以上1,2,3,4個(gè)步驟,咱們具體來(lái)實(shí)現(xiàn)一下代碼:

  1. import org.apache.pdfbox.pdmodel.PDDocument;
  2. import org.apache.pdfbox.rendering.PDFRenderer;
  3. import javax.imageio.ImageIO;
  4. import java.awt.image.BufferedImage;
  5. import java.io.File;
  6. import java.io.IOException;
  7. public class VGPdfToImage {
  8. public static void main(String[] args) {
  9. // PDF文件路徑
  10. String pdfFilePath = "path/to/your/pdf/vg_doc.pdf";
  11. // 輸出圖片文件夾路徑
  12. String outputDir = "path/to/output/images/";
  13. // 設(shè)置DPI(越高圖片越清晰,但文件也會(huì)更大)
  14. int dpi = 300;
  15. try (PDDocument document = PDDocument.load(new File(pdfFilePath))) {
  16. PDFRenderer pdfRenderer = new PDFRenderer(document);
  17. // 遍歷PDF每一頁(yè)并轉(zhuǎn)換為圖片
  18. for (int page = 0; page < document.getNumberOfPages(); ++page) {
  19. // 使用BufferedImage來(lái)表示圖像
  20. BufferedImage bim = pdfRenderer.renderImageWithDPI(page, dpi);
  21. // 生成文件名
  22. String fileName = outputDir + "pdf_page_" + (page + 1) + ".png";
  23. // 將圖片保存為PNG格式
  24. ImageIO.write(bim, "png", new File(fileName));
  25. System.out.println("Saved page " + (page + 1) + " as image.");
  26. }
  27. } catch (IOException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }

來(lái)解釋一下

  1. PDFRenderer: PDFBox 提供的 PDFRenderer 類(lèi)用于將 PDF 文檔頁(yè)渲染為圖像對(duì)象(BufferedImage)。
  2. renderImageWithDPI: 該方法可以指定DPI(每英寸點(diǎn)數(shù)),它直接影響圖片的分辨率。通常,72 DPI 是屏幕顯示的默認(rèn)分辨率,而300 DPI 被視為高質(zhì)量打印的分辨率。
  3. ImageIO: Java的 ImageIO 用于將 BufferedImage 保存為 PNG、JPEG 等常見(jiàn)圖片格式。

輸出效果

  • 每一頁(yè)的PDF將被單獨(dú)渲染為一張圖片,并且通過(guò)高DPI參數(shù)設(shè)置,圖片的質(zhì)量較高。
  • 輸出的文件路徑為 outputDir 指定的路徑,圖片將被保存為PNG格式。你也可以更改保存格式為JPEG等。

可調(diào)整的項(xiàng)有

  • DPI 設(shè)置: 如果你希望輸出更高質(zhì)量的圖片,可以將 DPI 設(shè)置為 300 或更高。如果需要快速渲染且質(zhì)量要求不高,可以設(shè)置為72 DPI。
  • 圖片格式: ImageIO.write() 可以使用不同的格式,如 "jpg"、"png",根據(jù)需求調(diào)整。

注意一下,確保你的PDFBox庫(kù)版本是較新的版本,如2.x系列,來(lái)保證支持更多的PDF功能和修復(fù)潛在問(wèn)題。

以上就是一個(gè)簡(jiǎn)單的實(shí)現(xiàn)過(guò)程DEMO,那在實(shí)際應(yīng)用中,一定會(huì)有特定問(wèn)題,問(wèn)題來(lái)了,如何你要處理的 PDF 文件比較大,或者頁(yè)數(shù)比較多,那必定是要考慮性能問(wèn)題滴。就這兩個(gè)問(wèn)題,V 哥來(lái)優(yōu)化一下。

兩個(gè)可能的性能優(yōu)化問(wèn)題

  1. 緩存策略:對(duì)于較大的 PDF 文件,你可以使用某些緩存策略來(lái)優(yōu)化性能。
  2. 并行處理:如果你需要處理很多頁(yè)的 PDF,可以通過(guò)多線程并行處理每一頁(yè)以提升速度。

緩存策略優(yōu)化

當(dāng)要處理較大的 PDF 文件時(shí),咱們使用緩存策略可以顯著優(yōu)化性能,特別是對(duì)于那些需要處理多個(gè)頁(yè)面或反復(fù)渲染的情況。對(duì)于 PDF 渲染操作,緩存策略主要是為了減少對(duì)磁盤(pán)或內(nèi)存的反復(fù)訪問(wèn),從而加快讀取、渲染速度并節(jié)省內(nèi)存。

在 Java 中,可以通過(guò)以下幾種方式實(shí)現(xiàn)緩存優(yōu)化:

  1. 內(nèi)存緩存:將已處理的頁(yè)面保存在內(nèi)存中,當(dāng)需要重復(fù)訪問(wèn)這些頁(yè)面時(shí)直接從緩存中獲取。
  2. 磁盤(pán)緩存:如果內(nèi)存不足以緩存所有頁(yè)面,可以將頁(yè)面渲染結(jié)果或部分中間數(shù)據(jù)緩存到磁盤(pán)上。
  3. 逐頁(yè)處理:只在需要時(shí)加載并處理某些頁(yè)面,而不是一次性加載整個(gè)PDF文件。

采用實(shí)現(xiàn)內(nèi)存緩存的案例

采用內(nèi)存緩存,咱們可以使用 ConcurrentHashMap 來(lái)實(shí)現(xiàn),將已經(jīng)渲染的 PDF 頁(yè)面存儲(chǔ)在內(nèi)存中,避免重復(fù)渲染。

來(lái)看一個(gè)使用內(nèi)存緩存的詳細(xì)實(shí)現(xiàn)案例:

  1. import org.apache.pdfbox.pdmodel.PDDocument;
  2. import org.apache.pdfbox.rendering.PDFRenderer;
  3. import javax.imageio.ImageIO;
  4. import java.awt.image.BufferedImage;
  5. import java.io.File;
  6. import java.io.IOException;
  7. import java.util.concurrent.ConcurrentHashMap;
  8. public class PdfToImageWithCache {
  9. // 用于緩存已渲染的PDF頁(yè)面(使用ConcurrentHashMap確保線程安全)
  10. private static final ConcurrentHashMap<Integer, BufferedImage> imageCache = new ConcurrentHashMap<>();
  11. private static final int dpi = 300; // 高質(zhì)量DPI設(shè)置
  12. public static void main(String[] args) {
  13. // PDF文件路徑
  14. String pdfFilePath = "path/to/your/large/pdf/ vg_doc.pdf";
  15. // 輸出圖片文件夾路徑
  16. String outputDir = "path/to/output/images/";
  17. try (PDDocument document = PDDocument.load(new File(pdfFilePath))) {
  18. PDFRenderer pdfRenderer = new PDFRenderer(document);
  19. // 獲取頁(yè)面總數(shù)
  20. int totalPages = document.getNumberOfPages();
  21. System.out.println("Total pages: " + totalPages);
  22. // 渲染并緩存每一頁(yè)
  23. for (int page = 0; page < totalPages; ++page) {
  24. BufferedImage image = renderPageWithCache(pdfRenderer, page);
  25. // 保存圖片
  26. String fileName = outputDir + "pdf_page_" + (page + 1) + ".png";
  27. ImageIO.write(image, "png", new File(fileName));
  28. System.out.println("Saved page " + (page + 1) + " as image.");
  29. }
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. /**
  35. * 使用緩存渲染PDF頁(yè)面
  36. * @param pdfRenderer PDFRenderer實(shí)例
  37. * @param page 頁(yè)碼(從0開(kāi)始)
  38. * @return 緩存或渲染后的BufferedImage
  39. */
  40. private static BufferedImage renderPageWithCache(PDFRenderer pdfRenderer, int page) throws IOException {
  41. // 檢查緩存是否已存在該頁(yè)面的圖像
  42. if (imageCache.containsKey(page)) {
  43. System.out.println("Page " + (page + 1) + " found in cache.");
  44. return imageCache.get(page);
  45. }
  46. // 如果緩存中不存在,則渲染并存入緩存
  47. System.out.println("Rendering page " + (page + 1) + "...");
  48. BufferedImage image = pdfRenderer.renderImageWithDPI(page, dpi);
  49. imageCache.put(page, image);
  50. return image;
  51. }
  52. }

解釋一下代碼

  1. 內(nèi)存緩存(ConcurrentHashMap:
    • 使用 ConcurrentHashMap<Integer, BufferedImage> 作為緩存結(jié)構(gòu),Integer 代表頁(yè)面的索引(從0開(kāi)始),BufferedImage 代表已渲染的圖像。
    • 每次渲染頁(yè)面前,先檢查緩存中是否存在該頁(yè)面的圖像,如果已存在,則直接返回緩存的圖像,否則渲染并保存到緩存中。

  1. renderPageWithCache 方法:
    • 該方法首先檢查頁(yè)面是否在緩存中,如果在,則直接從緩存中獲取。
    • 如果緩存中不存在該頁(yè)面的圖像,則渲染并將其保存到緩存中。

  1. DPI 設(shè)置:
    • dpi 參數(shù)設(shè)置為300以確保輸出的圖像質(zhì)量足夠高。

  1. 逐頁(yè)渲染:
    • 使用 for 循環(huán)逐頁(yè)處理,避免一次性加載所有頁(yè)面到內(nèi)存。對(duì)于每頁(yè)圖像的渲染,若該頁(yè)面已經(jīng)渲染過(guò),則直接從緩存中獲取。

這樣優(yōu)化的好處是啥

  1. 內(nèi)存緩存的好處:
    • 當(dāng)你需要多次訪問(wèn)或保存某些頁(yè)面時(shí),內(nèi)存緩存可以避免重復(fù)渲染,從而提升性能。
    • 對(duì)于較大的PDF文件,如果反復(fù)操作相同的頁(yè)面,緩存能顯著減少處理時(shí)間。

  1. 并發(fā)支持:
    • ConcurrentHashMap 保證了在多線程環(huán)境下緩存操作的安全性,可以安全地在多線程中使用。

  1. 控制內(nèi)存占用:
    • 如果內(nèi)存使用量過(guò)大,可以根據(jù)情況定期清理緩存,或者在緩存中限制最大保存數(shù)量,使用類(lèi)似LRU(最近最少使用)策略來(lái)清除舊緩存。

實(shí)現(xiàn)磁盤(pán)緩存的案例

接下來(lái),咱們看一個(gè)使用磁盤(pán)緩存要怎么實(shí)現(xiàn),如果 PDF 文件較大,內(nèi)存無(wú)法保存全部頁(yè)面的圖像,我的天啊,那要怎么辦?就是可以使用磁盤(pán)緩存,將渲染結(jié)果暫時(shí)保存到磁盤(pán)。

來(lái)看下面這個(gè)磁盤(pán)緩存策略實(shí)現(xiàn),將渲染的圖像保存為臨時(shí)文件,并在需要時(shí)從磁盤(pán)加載:

  1. import org.apache.pdfbox.pdmodel.PDDocument;
  2. import org.apache.pdfbox.rendering.PDFRenderer;
  3. import javax.imageio.ImageIO;
  4. import java.awt.image.BufferedImage;
  5. import java.io.File;
  6. import java.io.IOException;
  7. public class PdfToImageWithDiskCache {
  8. private static final int dpi = 300; // 高質(zhì)量DPI設(shè)置
  9. private static final String cacheDir = "path/to/cache/";
  10. public static void main(String[] args) {
  11. // PDF文件路徑
  12. String pdfFilePath = "path/to/your/large/pdf/vg_doc.pdf";
  13. // 輸出圖片文件夾路徑
  14. String outputDir = "path/to/output/images/";
  15. try (PDDocument document = PDDocument.load(new File(pdfFilePath))) {
  16. PDFRenderer pdfRenderer = new PDFRenderer(document);
  17. int totalPages = document.getNumberOfPages();
  18. for (int page = 0; page < totalPages; ++page) {
  19. BufferedImage image = renderPageWithDiskCache(pdfRenderer, page);
  20. // 保存圖片
  21. String fileName = outputDir + "pdf_page_" + (page + 1) + ".png";
  22. ImageIO.write(image, "png", new File(fileName));
  23. System.out.println("Saved page " + (page + 1) + " as image.");
  24. }
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. /**
  30. * 使用磁盤(pán)緩存渲染PDF頁(yè)面
  31. * @param pdfRenderer PDFRenderer實(shí)例
  32. * @param page 頁(yè)碼(從0開(kāi)始)
  33. * @return 緩存或渲染后的BufferedImage
  34. */
  35. private static BufferedImage renderPageWithDiskCache(PDFRenderer pdfRenderer, int page) throws IOException {
  36. // 磁盤(pán)緩存文件路徑
  37. File cachedFile = new File(cacheDir + "page_" + page + ".png");
  38. // 如果緩存文件已存在,則從磁盤(pán)加載
  39. if (cachedFile.exists()) {
  40. System.out.println("Loading page " + (page + 1) + " from disk cache.");
  41. return ImageIO.read(cachedFile);
  42. }
  43. // 如果緩存文件不存在,則渲染并保存到磁盤(pán)
  44. System.out.println("Rendering page " + (page + 1) + "...");
  45. BufferedImage image = pdfRenderer.renderImageWithDPI(page, dpi);
  46. ImageIO.write(image, "png", cachedFile);
  47. return image;
  48. }
  49. }

代碼解釋

  1. 緩存到磁盤(pán): 通過(guò) ImageIO.write() 將渲染的圖像保存到磁盤(pán)上,如果該頁(yè)面已經(jīng)有緩存文件,則直接從磁盤(pán)讀取。
  2. 緩存文件路徑: 每個(gè)頁(yè)面有對(duì)應(yīng)的緩存文件名,避免重復(fù)渲染和保存。
  3. 適用于內(nèi)存不足的情況: 當(dāng)內(nèi)存不足時(shí),可以通過(guò)磁盤(pán)緩存減輕內(nèi)存負(fù)擔(dān),同時(shí)仍然保留較好的訪問(wèn)速度。

通過(guò)這樣的優(yōu)化策略,咱們就可以在處理較大的 PDF 文件時(shí),顯著提升性能并減少資源消耗。

并行處理優(yōu)化

接下來(lái),看第二個(gè)問(wèn)題:在處理很多頁(yè)的 PDF 文件時(shí),通過(guò)多線程并行處理每一頁(yè)可以讓處理速度顯著提升,尤其是在每頁(yè)渲染操作耗時(shí)較長(zhǎng)的情況下。Java 提供了多線程的機(jī)制,咱們就用 ExecutorService 可以方便地管理和執(zhí)行多線程任務(wù)。

下面來(lái)看一下如何實(shí)現(xiàn)哈,使用多線程并行處理 PDF 文件的每一頁(yè),將其轉(zhuǎn)換為高質(zhì)量圖片。

主要步驟有三個(gè)

  1. 使用 ExecutorService 來(lái)創(chuàng)建線程池。
  2. 每個(gè)線程獨(dú)立處理一頁(yè) PDF,將其渲染為圖片。
  3. 線程任務(wù)執(zhí)行完畢后,統(tǒng)一關(guān)閉線程池

具體的代碼實(shí)現(xiàn)

  1. import org.apache.pdfbox.pdmodel.PDDocument;
  2. import org.apache.pdfbox.rendering.PDFRenderer;
  3. import javax.imageio.ImageIO;
  4. import java.awt.image.BufferedImage;
  5. import java.io.File;
  6. import java.io.IOException;
  7. import java.util.concurrent.ExecutorService;
  8. import java.util.concurrent.Executors;
  9. import java.util.concurrent.TimeUnit;
  10. public class PdfToImageWithMultithreading {
  11. // 設(shè)置DPI用于高質(zhì)量渲染
  12. private static final int dpi = 300;
  13. public static void main(String[] args) {
  14. // PDF文件路徑
  15. String pdfFilePath = "path/to/your/large/pdf/vg_doc.pdf";
  16. // 輸出圖片文件夾路徑
  17. String outputDir = "path/to/output/images/";
  18. // 線程池大?。梢愿鶕?jù)CPU核心數(shù)量或需要并行的任務(wù)數(shù)進(jìn)行調(diào)整)
  19. int numThreads = Runtime.getRuntime().availableProcessors();
  20. ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
  21. try (PDDocument document = PDDocument.load(new File(pdfFilePath))) {
  22. PDFRenderer pdfRenderer = new PDFRenderer(document);
  23. int totalPages = document.getNumberOfPages();
  24. System.out.println("Total pages: " + totalPages);
  25. // 為每一頁(yè)創(chuàng)建一個(gè)并行處理任務(wù)
  26. for (int page = 0; page < totalPages; page++) {
  27. final int currentPage = page; // 需要用final修飾以便在多線程中使用
  28. executorService.submit(() -> {
  29. try {
  30. renderAndSavePage(pdfRenderer, currentPage, outputDir);
  31. } catch (IOException e) {
  32. e.printStackTrace();
  33. }
  34. });
  35. }
  36. } catch (IOException e) {
  37. e.printStackTrace();
  38. } finally {
  39. // 關(guān)閉線程池
  40. executorService.shutdown();
  41. try {
  42. // 等待所有線程任務(wù)完成
  43. if (!executorService.awaitTermination(60, TimeUnit.MINUTES)) {
  44. System.err.println("Some tasks did not finish within the timeout.");
  45. }
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. }
  49. }
  50. }
  51. /**
  52. * 渲染PDF頁(yè)面并保存為圖片
  53. * @param pdfRenderer PDFRenderer實(shí)例
  54. * @param page 頁(yè)碼(從0開(kāi)始)
  55. * @param outputDir 輸出目錄
  56. * @throws IOException 如果發(fā)生IO錯(cuò)誤
  57. */
  58. private static void renderAndSavePage(PDFRenderer pdfRenderer, int page, String outputDir) throws IOException {
  59. // 渲染頁(yè)面為高質(zhì)量圖片
  60. BufferedImage image = pdfRenderer.renderImageWithDPI(page, dpi);
  61. // 保存圖片文件
  62. String fileName = outputDir + "pdf_page_" + (page + 1) + ".png";
  63. ImageIO.write(image, "png", new File(fileName));
  64. System.out.println("Saved page " + (page + 1) + " as image.");
  65. }
  66. }

來(lái)詳細(xì)解釋一下代碼和思路

1. 線程池的使用

  • ExecutorService :我們使用 Executors.newFixedThreadPool(numThreads) 來(lái)創(chuàng)建一個(gè)固定大小的線程池,其中 numThreads 是線程的數(shù)量。通過(guò) Runtime.getRuntime().availableProcessors() 獲取 CPU 核心數(shù)作為線程池大小的依據(jù),通常這個(gè)值是處理器核心數(shù)。
  • submit() :將任務(wù)提交給線程池,submit() 方法會(huì)立即返回,不會(huì)阻塞主線程,從而能夠讓多個(gè)頁(yè)面同時(shí)處理。

2. 任務(wù)分配

  • 每一頁(yè)的渲染任務(wù)被分配到一個(gè)線程中,通過(guò) executorService.submit() 提交渲染任務(wù)。每個(gè)任務(wù)都會(huì)調(diào)用 renderAndSavePage() 方法,處理特定頁(yè)面的渲染和保存。

3. 渲染與保存

  • 每個(gè)線程使用 renderAndSavePage() 方法渲染指定頁(yè)碼的 PDF,并將生成的圖像保存為 PNG 文件。這里使用 ImageIO.write() 來(lái)保存渲染結(jié)果。
  • 輸出的文件名根據(jù)頁(yè)面編號(hào)動(dòng)態(tài)生成。

4. 關(guān)閉線程池

  • shutdown() :主線程在提交所有任務(wù)后調(diào)用 shutdown() 方法,通知線程池停止接收新的任務(wù)。
  • awaitTermination():主線程等待所有線程任務(wù)完成,這里設(shè)置了一個(gè)較長(zhǎng)的超時(shí)時(shí)間(60分鐘),你要根據(jù)實(shí)際情況來(lái)調(diào)整一下,確保所有頁(yè)都能被處理完畢。

小結(jié)一下

通過(guò)多線程處理PDF的每一頁(yè),能顯著縮短處理時(shí)間,特別是在處理大文件或大量頁(yè)數(shù)的PDF時(shí)。線程池中的任務(wù)可以同時(shí)在多個(gè)CPU核心上運(yùn)行,最大化利用硬件資源。對(duì)于超級(jí)大PDF文件或需要處理大量PDF時(shí),可那就得上分布式處理了,每個(gè)節(jié)點(diǎn)處理一部分頁(yè)面來(lái)解決,這里就不多贅述了。

最后

Java 如何實(shí)現(xiàn)PDF轉(zhuǎn)高質(zhì)量圖片的案例就講完了,喜歡這篇文件的話,一定幫我點(diǎn)贊、評(píng)論支持哦,如果怕忘了,收藏起來(lái)備孕是不錯(cuò)的選擇。關(guān)注威哥愛(ài)編程,一群人的堅(jiān)持才更加快樂(lè)。么么噠~~~Apache PDFBox

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)