(新卷,100分)- 滑动窗口最大和(Java JS Python C)
2026/1/8 21:29:30
<!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 class="container"> <div class="header"> <h1>📄 PDF 图片提取工具</h1> <p>上传 PDF 文件,自动提取其中的所有图片</p> </div> <div class="upload-card"> <div class="upload-area" id="uploadArea"> <div class="upload-icon">📁</div> <div class="upload-text">点击或拖拽 PDF 文件到此处</div> <div class="upload-hint">支持单个 PDF 文件上传</div> </div> <input type="file" id="fileInput" accept=".pdf,application/pdf"> <div class="progress-container" id="progressContainer"> <div class="progress-bar"> <div class="progress-fill" id="progressFill"></div> </div> <div class="progress-text" id="progressText">处理中...</div> </div> </div> <div class="images-container" id="imagesContainer"> <div class="images-header"> <div class="images-title">提取的图片</div> <div class="images-count" id="imagesCount">0 张图片</div> </div> <div class="images-grid" id="imagesGrid"></div> </div> </div> <div class="modal" id="modal"> <div class="modal-content"> <button class="modal-close" onclick="closeModal()">×</button> <img class="modal-image" id="modalImage" src="" 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 class="empty-state" style="grid-column: 1 / -1;"> <div class="empty-icon">🖼️</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 class="image-wrapper"> <img src="${image.url}" alt="${image.name}"> </div> <div class="image-info"> <div class="image-name" title="${image.name}">${image.name}</div> <div class="image-meta"> <span>${image.width} × ${image.height}</span> <span>${formatBytes(image.size)}</span> </div> <div class="image-actions"> <button class="btn-small btn-preview" onclick="previewImage('${image.url}')">预览</button> <button class="btn-small btn-download" 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>