JavaScript提取PDF圖片的方法詳解
提取 PDF 圖片,是指把每一頁(yè)里的多張圖片都提取出來(lái),不是把一整頁(yè)都轉(zhuǎn)換為一張圖片 ^_^ 。
效果
技術(shù)實(shí)現(xiàn)詳解
使用 PDF.js 解析和渲染 PDFs,在 github 上有 43.9kb Star,非常成熟。
一、 引入 pdf.js
需要同時(shí)引入 pdf.js 和 WebWorker,其中 WebWorker 在瀏覽器子線程解析圖片等資源,不阻塞頁(yè)面UI和交互。引入代碼如下:
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.10.111/pdf.js"></script> <script> // pdf.js 從性能考慮,使用了 WebWorker, 在瀏覽器子線程解析圖片等資源,不阻塞頁(yè)面UI和交互。 pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.10.111/build/pdf.worker.js'; </script>
二、上傳 PDF 文件并監(jiān)聽(tīng)
上傳文件時(shí),使用 FileReader 異步文件讀取對(duì)象,讀取之后轉(zhuǎn)換為 Uint8Array,傳入 pdfjsLib,得到 PDF 加載任務(wù)。
代碼如下:
<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); // 上傳文件時(shí),使用 FileReader 對(duì)象讀取 var fileReader = new FileReader(); fileReader.onload = function() { // 將文件對(duì)象轉(zhuǎn)化為 Uint8Array var typedarray = new Uint8Array(this.result); // 返回 PDF 加載任務(wù) PDFDocumentLoadingTask const loadingTask = pdfjsLib.getDocument(typedarray); // 開(kāi)始加載 PDF,這里封裝了一個(gè)函數(shù) loadPDFFile(loadingTask); }; fileReader.readAsArrayBuffer(file); }, false ); </script>
三、獲取和遍歷 PDF 頁(yè)數(shù)
async function loadPDFFile(loadingTask) { // PDF 加載任務(wù) const pdf = await loadingTask.promise; // 獲取 PDF 頁(yè)數(shù) const numPages = pdf.numPages; for (let curPage = 1; curPage <= numPages; curPage++) { // 返回當(dāng)前頁(yè) 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"); // 轉(zhuǎn)換尺寸 const transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null; const renderContext = { canvasContext: context, transform, viewport, }; // 調(diào)用 page.render 觸發(fā)渲染 const renderTask = page.render(renderContext); // 等待 renderTask 渲染任務(wù) // await delay(500); // 如果渲染過(guò)快,可能導(dǎo)致 CPU 飆升、電腦可用內(nèi)存不夠,可以加延遲減慢速度 await renderTask.promise; } }
代碼里使用 OffscreenCanvas,提供了一個(gè)可以脫離屏幕渲染的 canvas 對(duì)象,在主線程和子線程 (web worker) 都可以使用,是為了性能優(yōu)化,提取 PDF 圖片不用渲染到頁(yè)面,但也要渲染任務(wù)
如果渲染過(guò)快,可能導(dǎo)致 CPU 飆升、電腦可用內(nèi)存不夠,可以加延遲 setTimeout 減慢速度
四、提取每頁(yè) PDF 的圖片
在這一步,我們獲取了 pdfjs 提取的 PDF 圖片,是 ImageBitmap 格式,它是對(duì) Canvas 上位圖數(shù)據(jù)的引用,存儲(chǔ)在 GPU 中,可以在 web worker 進(jìn)行生成,從而避免影響主線程繪制 UI,也避免阻塞響應(yīng)用戶(hù)交互。
代碼如下:
async function loadPDFFile(loadingTask) { // ...接上一步 const renderTask = page.render(renderContext); // 等待當(dāng)前頁(yè)渲染任務(wù)執(zhí)行 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 轉(zhuǎn)化為 Img
再堅(jiān)持一下,最后一步啦!─=≡Σ(((つ•?ω•?)つ
在上一步中,我們獲取了在 Canvas 繪圖的引用 ImageBitmap,我們需要轉(zhuǎn)換為瀏覽器下的 Img 圖片。
具體過(guò)程:先將 ImageBitmap 渲染到 Canvas,調(diào)用 canvas.convertToBlob 就能得到 Blob 對(duì)象。
async function loadPDFFile(loadingTask) { // ...接上一步,拿到了每頁(yè) PDF 的 image.bitmap 對(duì)象 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; // 這個(gè)可以控制轉(zhuǎn)換后的圖片大小 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 畫(huà)布轉(zhuǎn)化為 Blob 對(duì)象 // https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas/convertToBlob const blob = await canvas.convertToBlob(); console.log('blob: ', blob); // blob // 最后使用 Blob 作為 URL.createObjectURL 的參數(shù),渲染出 img 圖片 // 如果不需要渲染,則可以講 Blob 數(shù)據(jù)上傳到云存儲(chǔ) const img = document.body.appendChild(new Image()); img.width = width; img.height = height; img.src = URL.createObjectURL(blob); })(); }); } }
OffscreenCanvas 可以放在 Web Worker 中創(chuàng)建,從而不阻塞主線程的UI繪制和交互響應(yīng)。
bitmaprenderer.transferFromImageBitmap 可以實(shí)現(xiàn)不拷貝數(shù)據(jù),只傳遞地址的方式,把 ImageBitmap 傳遞到 Canvas 上。
背景(緣起)
前段時(shí)間遇到 從 PDF 中提取圖片用于 評(píng)論、審核 的功能。
由于項(xiàng)目時(shí)間趕、任務(wù)重,由后端實(shí)現(xiàn)了一頁(yè) PDF 轉(zhuǎn)換為一張圖片。
后端提取優(yōu)劣:
- 優(yōu): 用戶(hù)體驗(yàn)好,不影響用戶(hù)電腦性能
- 劣:加大服務(wù)器壓力
- 占用大量CPU資源,在我Mac筆記本
MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports)
解析時(shí),能看到 CPU 每次都飆到 100% 以上 - 占用大量?jī)?nèi)存,解析后的圖片,在上傳到 CDN 以前,放在服務(wù)器沒(méi)有釋放
- 需要把 PDF 文件上傳到服務(wù)器,由后端處理
- 占用大量CPU資源,在我Mac筆記本
前端提取優(yōu)劣:
- 優(yōu): 不占用服務(wù)器資源,在瀏覽器端就能完成提取。
- 劣: 可能會(huì)引起用戶(hù)電腦卡頓。
以上就是JavaScript提取PDF圖片的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于JavaScript提取PDF圖片的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript可否多線程? 深入理解JavaScript定時(shí)機(jī)制
JavaScript的setTimeout與setInterval是兩個(gè)很容易欺騙別人感情的方法,因?yàn)槲覀冮_(kāi)始常常以為調(diào)用了就會(huì)按既定的方式執(zhí)行, 我想不少人都深有同感2012-05-05javascript 獲取url參數(shù)和script標(biāo)簽中獲取url參數(shù)函數(shù)代碼
不要在方法中調(diào)用方法,否則可能始終獲取的是最后一個(gè)js的文件的參數(shù),要在方法中使用,請(qǐng)先用變量保存,在方法中直接獲取2010-01-01IE6 彈出Iframe層中的文本框“經(jīng)?!睙o(wú)法獲得輸入焦點(diǎn)
IE6間歇性精神障礙 彈出Iframe層中的文本框“經(jīng)常”無(wú)法獲得輸入焦點(diǎn)的解決方法。2009-12-12JS實(shí)現(xiàn)動(dòng)態(tài)增加和刪除li標(biāo)簽行的實(shí)例代碼
下面小編就為大家?guī)?lái)一篇JS實(shí)現(xiàn)動(dòng)態(tài)增加和刪除li標(biāo)簽行的實(shí)例代碼。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10js判斷瀏覽器類(lèi)型為ie6時(shí)不執(zhí)行
這篇文章主要介紹了js怎么判斷瀏覽器類(lèi)型,當(dāng)類(lèi)型為ie6時(shí)如何不執(zhí)行,需要的朋友可以參考下2014-06-06Javascript讀寫(xiě)cookie的實(shí)例源碼
今天小編就為大家分享一篇關(guān)于Javascript讀寫(xiě)cookie的實(shí)例源碼,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03關(guān)于微信公眾號(hào)開(kāi)發(fā)無(wú)法支付的問(wèn)題解決
這篇文章主要介紹了關(guān)于微信公眾號(hào)開(kāi)發(fā)無(wú)法支付的問(wèn)題解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12javascript實(shí)現(xiàn)的登陸遮罩效果匯總
小編給大家推薦幾款使用Javascript實(shí)現(xiàn)的遮罩效果登陸框,其實(shí)這種效果是很常見(jiàn)的,在許多互動(dòng)的社區(qū)及其它的一些地方,彈出框應(yīng)用想當(dāng)流行,在不妨礙網(wǎng)頁(yè)運(yùn)行的情況下,用戶(hù)可以輸入登錄信息,實(shí)現(xiàn)完美登錄。2015-11-11詳解JavaScript中的六種錯(cuò)誤類(lèi)型
本文給大家詳細(xì)介紹了JavaScript中的六種錯(cuò)誤類(lèi)型,需要的朋友可以參考下2017-09-09JS DOMReady事件的六種實(shí)現(xiàn)方法總結(jié)
下面小編就為大家?guī)?lái)一篇JS DOMReady事件的六種實(shí)現(xiàn)方法總結(jié)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-11-11