JavaScript提取PDF图片的方法详解

 更新时间:2025年03月13日 09:26:49   作者:NeoYu  
这篇文章主要为大家详细介绍了如何使用JavaScript提取 PDF 图片,即把每一页里的多张图片都提取出来,不是把一整页都转换为一张图片,感兴趣的可以了解下

提取 PDF 图片,是指把每一页里的多张图片都提取出来,不是把一整页都转换为一张图片 ^_^ 。

效果 

技术实现详解 

使用 PDF.js 解析和渲染 PDFs,在 github 上有 43.9kb Star,非常成熟。

一、 引入 pdf.js

需要同时引入 pdf.js 和 WebWorker,其中 WebWorker 在浏览器子线程解析图片等资源,不阻塞页面UI和交互。引入代码如下:

<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.10.111/pdf.js"></script>

<script>
// pdf.js 从性能考虑,使用了 WebWorker, 在浏览器子线程解析图片等资源,不阻塞页面UI和交互。
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.10.111/build/pdf.worker.js';
</script>

二、上传 PDF 文件并监听

上传文件时,使用 FileReader 异步文件读取对象,读取之后转换为 Uint8Array,传入 pdfjsLib,得到 PDF 加载任务。

代码如下:

<input type="file" id="fileInput" name="avatar" title ="选择 pdf 文件"/>

<script>
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener(
  "change",
  () => {
    const file = fileInput.files[0];
    console.log('fileInput file: ', file);
    // 上传文件时,使用 FileReader 对象读取
    var fileReader = new FileReader();
    fileReader.onload = function() {
          // 将文件对象转化为 Uint8Array 
          var typedarray = new Uint8Array(this.result);
          // 返回 PDF 加载任务 PDFDocumentLoadingTask
          const loadingTask = pdfjsLib.getDocument(typedarray);
          // 开始加载 PDF,这里封装了一个函数
          loadPDFFile(loadingTask);
    };

    fileReader.readAsArrayBuffer(file);
  },
  false
);
</script>

三、获取和遍历 PDF 页数

async function loadPDFFile(loadingTask) {
  // PDF 加载任务
  const pdf = await loadingTask.promise;
  // 获取 PDF 页数
  const numPages = pdf.numPages;
  for (let curPage = 1; curPage <= numPages; curPage++) {
    // 返回当前页
    console.log('loadingServerFile curPage: ', curPage);
    const page = await pdf.getPage(curPage);

    const scale = 1.5;
    // 获取渲染视角尺寸
    const viewport = page.getViewport({ scale });
    // Support HiDPI-screens.
    const outputScale = window.devicePixelRatio || 1;
    // 传入离屏 Canvas
    const canvas = new OffscreenCanvas(200, 200);
    // 获取 Canvas 上下文
    const context = canvas.getContext("2d");
    // 转换尺寸
    const transform = outputScale !== 1 
      ? [outputScale, 0, 0, outputScale, 0, 0] 
      : null;

    const renderContext = {
      canvasContext: context,
      transform,
      viewport,
    };
    // 调用 page.render 触发渲染
    const renderTask = page.render(renderContext);
    // 等待 renderTask 渲染任务
    // await delay(500); // 如果渲染过快,可能导致 CPU 飙升、电脑可用内存不够,可以加延迟减慢速度
    await renderTask.promise;
  }
}

代码里使用 OffscreenCanvas,提供了一个可以脱离屏幕渲染的 canvas 对象,在主线程和子线程 (web worker) 都可以使用,是为了性能优化,提取 PDF 图片不用渲染到页面,但也要渲染任务

如果渲染过快,可能导致 CPU 飙升、电脑可用内存不够,可以加延迟 setTimeout 减慢速度

四、提取每页 PDF 的图片

在这一步,我们获取了 pdfjs 提取的 PDF 图片,是 ImageBitmap 格式,它是对 Canvas 上位图数据的引用,存储在 GPU 中,可以在 web worker 进行生成,从而避免影响主线程绘制 UI,也避免阻塞响应用户交互。

代码如下:

async function loadPDFFile(loadingTask) {
    // ...接上一步
    const renderTask = page.render(renderContext);
    // 等待当前页渲染任务执行
    await renderTask.promise;
    // 获取操作列表
    const ops = await page.getOperatorList();

    // 提取图片
    const imageNames = ops.fnArray.reduce((acc, curr, i) => {
      if ([pdfjsLib.OPS.paintImageXObject, pdfjsLib.OPS.paintJpegXObject].includes(curr)) {
        acc.push(ops.argsArray[i][0]);
      }
      return acc;
    }, []);
    for (const imageName of imageNames) {
      console.log('imageName: ', imageName);
      page.objs.get(imageName, (image) => {
        // console.log('image: ', image);
        (async function() {          
          const bmp = image.bitmap;
          // create a canvas
          console.log('bmp: ', bmp);
        })();      
      });
    }
}

五、ImageBitmap 转化为 Img

再坚持一下,最后一步啦!─=≡Σ(((つ•̀ω•́)つ

在上一步中,我们获取了在 Canvas 绘图的引用 ImageBitmap,我们需要转换为浏览器下的 Img 图片。

具体过程:先将 ImageBitmap 渲染到 Canvas,调用 canvas.convertToBlob 就能得到 Blob 对象。

async function loadPDFFile(loadingTask) {
    // ...接上一步,拿到了每页 PDF 的 image.bitmap 对象
    for (const imageName of imageNames) {
      console.log('imageName: ', imageName);
      page.objs.get(imageName, (image) => {
        // console.log('image: ', image);
        (async function() {          
            const bmp = image.bitmap;
            console.log('bmp: ', bmp);
            // OffscreenCanvas
            const resizeScale = 1/4; // 这个可以控制转换后的图片大小
            const width = bmp.width * resizeScale;
            const height = bmp.height * resizeScale;
            const canvas = new OffscreenCanvas(width, height);
            // 获取 canvas bitmaprenderer 上下文
            const ctx = canvas.getContext('bitmaprenderer');
            // 把 ImageBitmap 渲染到 OffscreenCanvas
            ctx.transferFromImageBitmap(bmp);
            // 把 canvas 画布转化为 Blob 对象
            // https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas/convertToBlob
            const blob = await canvas.convertToBlob();
            console.log('blob: ', blob); // blob
            // 最后使用 Blob 作为 URL.createObjectURL 的参数,渲染出 img 图片
            // 如果不需要渲染,则可以讲 Blob 数据上传到云存储
            const img = document.body.appendChild(new Image());
            img.width = width;
            img.height = height;
            img.src = URL.createObjectURL(blob);
        })();      
      });
    }
}

OffscreenCanvas 可以放在 Web Worker 中创建,从而不阻塞主线程的UI绘制和交互响应。

bitmaprenderer.transferFromImageBitmap 可以实现不拷贝数据,只传递地址的方式,把 ImageBitmap 传递到 Canvas 上。

背景(缘起)

前段时间遇到 从 PDF 中提取图片用于 评论、审核 的功能。

由于项目时间赶、任务重,由后端实现了一页 PDF 转换为一张图片。

后端提取优劣:

  • 优: 用户体验好,不影响用户电脑性能
  • 劣:加大服务器压力
    • 占用大量CPU资源,在我Mac笔记本MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports) 解析时,能看到 CPU 每次都飙到 100% 以上
    • 占用大量内存,解析后的图片,在上传到 CDN 以前,放在服务器没有释放
    提取流程变长
    • 需要把 PDF 文件上传到服务器,由后端处理

前端提取优劣:

  • 优: 不占用服务器资源,在浏览器端就能完成提取。
  • 劣: 可能会引起用户电脑卡顿。

以上就是JavaScript提取PDF图片的方法详解的详细内容,更多关于JavaScript提取PDF图片的资料请关注脚本之家其它相关文章!

相关文章

  • JS隐藏参数post传值实例

    JS隐藏参数post传值实例

    JS隐藏参数post传值实例,需要的朋友可以参考一下
    2013-04-04
  • 详解Javascript中prototype属性(推荐)

    详解Javascript中prototype属性(推荐)

    这篇文章主要介绍了Javascript中prototype属性的相关资料,本文介绍的非常详细,具有参考借鉴价值,感兴趣的朋友一起看看吧
    2016-09-09
  • JavaScript通过HTML的class来获取HTML元素的方法总结

    JavaScript通过HTML的class来获取HTML元素的方法总结

    除了getElementsByClassName()函数,我们可以自己动手编写程式来通过class获取元素,接下来我们整理了一下JavaScript通过HTML的class来获取HTML元素的方法总结,需要的朋友可以参考下
    2016-05-05
  • 浅谈JavaScript中this的指向问题

    浅谈JavaScript中this的指向问题

    这篇文章主要介绍了浅谈JavaScript中this的指向问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • 发个自己写的表格操作类(添加,删除,排序,上移,下移)

    发个自己写的表格操作类(添加,删除,排序,上移,下移)

    发个自己写的表格操作类(添加,删除,排序,上移,下移)...
    2006-11-11
  • 详解使用JS如何制作简单的ASCII图与单极图

    详解使用JS如何制作简单的ASCII图与单极图

    这篇文章主要给大家介绍了使用JS如何制作简单的ASCII图与单极图的相关资料,文中介绍的非常详细,并在文末给出了详细的示例代码,相信对大家具有一定的参考价值,需要的朋友们下面来一起看看吧。
    2017-03-03
  • 微信小程序 this.triggerEvent()的具体使用

    微信小程序 this.triggerEvent()的具体使用

    这篇文章主要介绍了微信小程序 this.triggerEvent()的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • 微信小程序实现下拉框功能

    微信小程序实现下拉框功能

    这篇文章主要为大家详细介绍了微信小程序实现下拉框功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-07-07
  • JavaScript中关于Object.create()的用法

    JavaScript中关于Object.create()的用法

    这篇文章主要介绍了JavaScript中关于Object.create()的用法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • 学习JavaScript中的闭包closure应该注意什么

    学习JavaScript中的闭包closure应该注意什么

    这篇文章主要介绍了学习JavaScript中的闭包closure应该注意什么?在 JavaScript 中, 每当创建一个函数, 闭包就会在函数创建的同时被创建出来,但是学习的时候我们应该注意哪些问题呢,带着疑问一起进入下面文章学习具体内容吧
    2022-06-06

最新评论