上传文件,在前端用 pdf.js 提取 上传的pdf文件中的图片

上传文件,在前端用 pdf.js 提取 上传的pdf文件中的图片

在线访问:

https://chat.xutongbao.top/nextjs/light/pdf

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>PDF 图片提取工具</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; } .header { text-align: center; color: white; margin-bottom: 30px; } .header h1 { font-size: 36px; margin-bottom: 10px; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2); } .header p { font-size: 16px; opacity: 0.9; } .upload-card { background: white; border-radius: 16px; padding: 40px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); margin-bottom: 30px; } .upload-area { border: 3px dashed #667eea; border-radius: 12px; padding: 60px 20px; text-align: center; cursor: pointer; transition: all 0.3s ease; background: #f8f9ff; } .upload-area:hover { border-color: #764ba2; background: #f0f2ff; transform: translateY(-2px); } .upload-area.dragover { border-color: #764ba2; background: #e8ebff; transform: scale(1.02); } .upload-icon { font-size: 48px; margin-bottom: 20px; } .upload-text { font-size: 18px; color: #333; margin-bottom: 10px; font-weight: 600; } .upload-hint { font-size: 14px; color: #666; } #fileInput { display: none; } .btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 12px 30px; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); } .btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6); } .btn:active { transform: translateY(0); } .btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; } .progress-container { display: none; margin-top: 20px; } .progress-bar { width: 100%; height: 8px; background: #e0e0e0; border-radius: 4px; overflow: hidden; } .progress-fill { height: 100%; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); border-radius: 4px; transition: width 0.3s ease; width: 0%; } .progress-text { text-align: center; margin-top: 10px; color: #666; font-size: 14px; } .images-container { display: none; background: white; border-radius: 16px; padding: 40px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); } .images-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 2px solid #f0f0f0; } .images-title { font-size: 24px; font-weight: 700; color: #333; } .images-count { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 8px 20px; border-radius: 20px; font-size: 14px; font-weight: 600; } .images-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; } .image-card { border: 2px solid #e0e0e0; border-radius: 12px; overflow: hidden; transition: all 0.3s ease; background: white; } .image-card:hover { transform: translateY(-4px); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); border-color: #667eea; } .image-wrapper { width: 100%; height: 200px; display: flex; align-items: center; justify-content: center; background: #f8f9fa; overflow: hidden; } .image-wrapper img { max-width: 100%; max-height: 100%; object-fit: contain; } .image-info { padding: 15px; background: white; } .image-name { font-size: 14px; color: #333; margin-bottom: 8px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .image-meta { display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: #999; margin-bottom: 12px; } .image-actions { display: flex; gap: 8px; } .btn-small { flex: 1; padding: 8px 16px; font-size: 13px; border-radius: 6px; border: none; cursor: pointer; transition: all 0.2s ease; font-weight: 600; } .btn-download { background: #667eea; color: white; } .btn-download:hover { background: #5568d3; transform: translateY(-1px); } .btn-preview { background: #f0f0f0; color: #333; } .btn-preview:hover { background: #e0e0e0; } .empty-state { text-align: center; padding: 60px 20px; color: #999; } .empty-icon { font-size: 64px; margin-bottom: 20px; opacity: 0.5; } .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.9); z-index: 1000; align-items: center; justify-content: center; } .modal.active { display: flex; } .modal-content { max-width: 90%; max-height: 90%; position: relative; } .modal-image { max-width: 100%; max-height: 90vh; object-fit: contain; } .modal-close { position: absolute; top: -40px; right: 0; background: white; color: #333; border: none; width: 36px; height: 36px; border-radius: 50%; cursor: pointer; font-size: 20px; font-weight: bold; transition: all 0.2s ease; } .modal-close:hover { background: #f0f0f0; transform: rotate(90deg); } @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .image-card { animation: fadeIn 0.3s ease; } </style> </head> <body> <div> <div> <h1>📄 PDF 图片提取工具</h1> <p>上传 PDF 文件,自动提取其中的所有图片</p> </div> <div> <div> <div>📁</div> <div>点击或拖拽 PDF 文件到此处</div> <div>支持单个 PDF 文件上传</div> </div> <input type="file" accept=".pdf,application/pdf"> <div> <div> <div></div> </div> <div>处理中...</div> </div> </div> <div> <div> <div>提取的图片</div> <div>0 张图片</div> </div> <div></div> </div> </div> <div> <div> <button onclick="closeModal()">×</button> <img alt="预览"> </div> </div> <script> // 配置 PDF.js worker pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; const uploadArea = document.getElementById('uploadArea'); const fileInput = document.getElementById('fileInput'); const progressContainer = document.getElementById('progressContainer'); const progressFill = document.getElementById('progressFill'); const progressText = document.getElementById('progressText'); const imagesContainer = document.getElementById('imagesContainer'); const imagesGrid = document.getElementById('imagesGrid'); const imagesCount = document.getElementById('imagesCount'); let extractedImages = []; // 上传区域点击事件 uploadArea.addEventListener('click', () => { fileInput.click(); }); // 文件选择事件 fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (file && file.type === 'application/pdf') { handleFile(file); } }); // 拖拽事件 uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); }); uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('dragover'); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); const file = e.dataTransfer.files[0]; if (file && file.type === 'application/pdf') { handleFile(file); } }); // 处理 PDF 文件 async function handleFile(file) { extractedImages = []; imagesGrid.innerHTML = ''; imagesContainer.style.display = 'none'; progressContainer.style.display = 'block'; try { const arrayBuffer = await file.arrayBuffer(); await extractImagesFromPDF(arrayBuffer, file.name); progressContainer.style.display = 'none'; displayImages(); } catch (error) { console.error('处理 PDF 失败:', error); progressText.textContent = '处理失败: ' + error.message; progressText.style.color = '#e74c3c'; } } // 提取 PDF 中的图片 async function extractImagesFromPDF(arrayBuffer, fileName) { const pdfDocument = await pdfjsLib.getDocument({ data: arrayBuffer, useSystemFonts: true, disableFontFace: false, verbosity: 0, isEvalSupported: false, maxImageSize: 1024 * 1024 * 10 }).promise; const totalPages = pdfDocument.numPages; let imageIndex = 0; for (let pageNum = 1; pageNum <= totalPages; pageNum++) { updateProgress(pageNum, totalPages); const page = await pdfDocument.getPage(pageNum); const operatorList = await page.getOperatorList(); for (let i = 0; i < operatorList.fnArray.length; i++) { const fn = operatorList.fnArray[i]; if (fn === pdfjsLib.OPS.paintImageXObject || fn === pdfjsLib.OPS.paintInlineImageXObject) { const imageName = operatorList.argsArray[i][0]; await new Promise((resolve) => { page.objs.get(imageName, async (img) => { console.log('Image object:', img); // 调试输出 if (!img) { resolve(); return; } try { // 检查是否有 bitmap 属性(ImageBitmap) if (img.bitmap && img.bitmap instanceof ImageBitmap) { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); // 使用 ImageBitmap 绘制 ctx.drawImage(img.bitmap, 0, 0); await finishImageProcessing(canvas, img, fileName, pageNum, imageIndex); resolve(); return; } // 如果 img 本身是 ImageBitmap if (window.ImageBitmap && img instanceof ImageBitmap) { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); await finishImageProcessing(canvas, img, fileName, pageNum, imageIndex); resolve(); return; } // 如果有 data 属性(像素数据) if (img.data && img.width && img.height) { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); const imageData = ctx.createImageData(img.width, img.height); imageData.data.set(img.data); ctx.putImageData(imageData, 0, 0); await finishImageProcessing(canvas, img, fileName, pageNum, imageIndex); resolve(); return; } // 如果是 HTMLImageElement 或 HTMLCanvasElement if (img instanceof HTMLImageElement || img instanceof HTMLCanvasElement) { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); await finishImageProcessing(canvas, img, fileName, pageNum, imageIndex); resolve(); return; } // 如果有 src 属性 if (img.src) { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); const image = new Image(); image.onload = async () => { ctx.drawImage(image, 0, 0); await finishImageProcessing(canvas, img, fileName, pageNum, imageIndex); resolve(); }; image.onerror = () => { console.error('加载图片失败'); resolve(); }; image.src = img.src; return; } // 无法处理的情况 console.warn('无法处理的图片对象:', { hasData: !!img.data, hasBitmap: !!img.bitmap, width: img.width, height: img.height, keys: Object.keys(img) }); resolve(); } catch (error) { console.error('处理图片失败:', error, img); resolve(); } }); }); imageIndex++; } } } } // 完成图片处理 function finishImageProcessing(canvas, img, fileName, pageNum, imageIndex) { return new Promise((resolve) => { canvas.toBlob((blob) => { if (blob) { const url = URL.createObjectURL(blob); const name = `${fileName.replace('.pdf', '')}_page${pageNum}_img${imageIndex}.png`; extractedImages.push({ url: url, name: name, size: blob.size, width: canvas.width, height: canvas.height, blob: blob }); } resolve(); }, 'image/png'); }); } // 更新进度 function updateProgress(current, total) { const percent = (current / total) * 100; progressFill.style.width = percent + '%'; progressText.textContent = `正在处理第 ${current}/${total} 页...`; } // 显示图片 function displayImages() { if (extractedImages.length === 0) { imagesContainer.style.display = 'block'; imagesGrid.innerHTML = ` <div> <div>🖼️</div> <div>未在 PDF 中找到图片</div> </div> `; imagesCount.textContent = '0 张图片'; return; } imagesContainer.style.display = 'block'; imagesCount.textContent = `${extractedImages.length} 张图片`; extractedImages.forEach((image, index) => { const card = document.createElement('div'); card.className = 'image-card'; card.style.animationDelay = `${index * 0.05}s`; card.innerHTML = ` <div> <img src="${image.url}" alt="${image.name}"> </div> <div> <div title="${image.name}">${image.name}</div> <div> <span>${image.width} × ${image.height}</span> <span>${formatBytes(image.size)}</span> </div> <div> <button onclick="previewImage('${image.url}')">预览</button> <button onclick="downloadImage(${index})">下载</button> </div> </div> `; imagesGrid.appendChild(card); }); } // 格式化文件大小 function formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; } // 预览图片 function previewImage(url) { document.getElementById('modalImage').src = url; document.getElementById('modal').classList.add('active'); } // 关闭模态框 function closeModal() { document.getElementById('modal').classList.remove('active'); } // 下载图片 function downloadImage(index) { const image = extractedImages[index]; const link = document.createElement('a'); link.href = image.url; link.download = image.name; link.click(); } // 点击模态框背景关闭 document.getElementById('modal').addEventListener('click', (e) => { if (e.target.id === 'modal') { closeModal(); } }); </script> </body> </html> 

Read more

飞算 JavaAI 智能编程助手:颠覆编程旧模式,重构新生态

飞算 JavaAI 智能编程助手:颠覆编程旧模式,重构新生态

文章目录 * 飞算 JavaAI 智能编程助手:颠覆编程旧模式,重构新生态 * 一. 智能问答:让编程更轻松 * 1.1 🎙️ 智能问答の超萌知识充电站 ⚡ * 1.2 💬 聊聊天就有啦!图书借阅功能的 “嘴动” 魔法✨ * 二. Java Chat:新手的AI学伴 * 2.1 简单问题演示 * 问题一:如何输出“Hello World”? * 问题二:用JAVA编写一个程序,求两个整数的和。 * 问题三:JAVA中如何判断一个数是否为偶数? * 三. 智能向导:覆盖开发全流程 * 3.1 日常代码编写:缩短编码时间 * 3.1 🍬 智能引导功能体验指南 🍭 * 四. 飞算AI与平台其他的对比 * 🍬 4.1 核心优势🍭 * 🍬 4.

重磅!TRAE 中国版 SOLO 全量免费开放,AI 驱动开发迎来全民时代

2026 年开年伊始,AI 编程领域就迎来了一枚重磅炸弹——TRAE 中国版 SOLO 模式正式宣布全量免费开放!从此前的白名单排队、权限受限,到如今更新最新版 IDE 即可直接启用,这场免费策略的转变,不仅彻底降低了开发者接触顶尖 AI 编程工具的门槛,更标志着 AI 驱动开发正式迈入全民普及的新阶段。作为长期关注 AI 编程生态以及长期深度使用SOLO的技术博主,今天就带大家深度拆解这次开放的核心价值、SOLO 模式的独特优势,以及它将如何重塑我们的开发流程。 先搞懂:TRAE SOLO 到底是什么? 可能还有部分开发者对 SOLO 模式不太熟悉,简单来说,它绝非传统的代码补全工具,而是一个以 AI 为主导的全流程开发中枢。如果说普通的 AI 编程插件是“辅助打工者”,那 SOLO 模式就是“全能项目经理 + 执行团队”的结合体。 回顾

【AI大模型前沿】昆仑万维开源Skywork-R1V3:38B多模态推理模型,高考数学142分刷新开源SOTA

【AI大模型前沿】昆仑万维开源Skywork-R1V3:38B多模态推理模型,高考数学142分刷新开源SOTA

系列篇章💥 No.文章1【AI大模型前沿】深度剖析瑞智病理大模型 RuiPath:如何革新癌症病理诊断技术2【AI大模型前沿】清华大学 CLAMP-3:多模态技术引领音乐检索新潮流3【AI大模型前沿】浙大携手阿里推出HealthGPT:医学视觉语言大模型助力智能医疗新突破4【AI大模型前沿】阿里 QwQ-32B:320 亿参数推理大模型,性能比肩 DeepSeek-R1,免费开源5【AI大模型前沿】TRELLIS:微软、清华、中科大联合推出的高质量3D生成模型6【AI大模型前沿】Migician:清华、北大、华科联手打造的多图像定位大模型,一键解决安防监控与自动驾驶难题7【AI大模型前沿】DeepSeek-V3-0324:AI 模型的全面升级与技术突破8【AI大模型前沿】BioMedGPT-R1:清华联合水木分子打造的多模态生物医药大模型,开启智能研发新纪元9【AI大模型前沿】DiffRhythm:西北工业大学打造的10秒铸就完整歌曲的AI歌曲生成模型10【AI大模型前沿】R1-Omni:阿里开源全模态情感识别与强化学习的创新结合11【AI大模型前沿】Qwen2.5-Omni:

从MVP到千万级并发 AI在前后端开发中的差异化落地指南

从MVP到千万级并发 AI在前后端开发中的差异化落地指南

文章目录 * 前言 * 一、技术原理解析 * 1. 核心差异维度对比 * 2. AI 辅助开发的技术架构模型 * 二、按 DAU 规模分层的实战策略与代码实证 * 1. 低 DAU 项目(<1万):MVP 验证期 * 后端实战:从需求到接口的秒级响应 * 前端实战:快速但粗糙的 UI * 2. 中 DAU 项目(1万–100万):业务增长期 * 后端:复杂业务逻辑的精准生成 * 前端:C端体验的“陷阱” * 3. 高 DAU 项目(>100万):高并发架构期 * 后端进阶:AI 驱动的性能优化 * 高并发流程架构图 * 三、