diff --git a/image-cropper.html b/image-cropper.html index 5d3cc08..0cc8b02 100644 --- a/image-cropper.html +++ b/image-cropper.html @@ -177,50 +177,12 @@ padding: 10px; border-radius: 8px; } - - .template-section { - text-align: center; - margin-bottom: 30px; - padding: 20px; - background: white; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); - } - - .template-info { - font-size: 12px; - color: #666; - margin-top: 5px; - } - - .template-btn { - background-color: #ffc107; - color: #333; - padding: 8px 16px; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 14px; - margin-left: 10px; - } - - .template-btn:hover { - background-color: #e0a800; - } - - .match-result { - margin-top: 10px; - padding: 10px; - border: 1px solid #007bff; - background-color: #e6f2ff; - border-radius: 4px; - } - + .loading { color: #666; font-style: italic; } - + .error { color: #dc3545; font-size: 12px; @@ -254,86 +216,62 @@ // 存储每个code的裁剪计数 const cropCounters = {}; - // 模板匹配相关 - let templateData = null; // 存储模板的像素数据 - let templateCode = null; // 模板所属的code - let templateStreet = null; // 模板所属的street - // 初始化页面 function initializePage() { const grid = document.getElementById('imagesGrid'); grid.innerHTML = ''; - - // 先按street分组,再按code分组 - const groupedByStreet = {}; + + // 按code分组 + const groupedData = {}; imagesData.forEach((item, index) => { - const street = item.street || 'unknown'; const code = item.code || `img_${index}`; - if (!groupedByStreet[street]) { - groupedByStreet[street] = {}; - } - if (!groupedByStreet[street][code]) { - groupedByStreet[street][code] = []; + if (!groupedData[code]) { + groupedData[code] = []; } - groupedByStreet[street][code].push({ ...item, index }); + groupedData[code].push({ ...item, index }); }); - - // 为每个street添加分隔符和图片 - Object.keys(groupedByStreet).forEach((street, streetIndex) => { - // 添加street分隔符(如果不是第一个) - if (streetIndex > 0) { + + // 为每个code添加分隔符和图片 + Object.keys(groupedData).forEach((code, codeIndex) => { + // 添加分隔符(如果不是第一个) + if (codeIndex > 0) { const separator = document.createElement('div'); separator.className = 'code-separator'; - separator.textContent = `Street: ${street}`; - separator.style.borderColor = '#28a745'; - separator.style.color = '#28a745'; - separator.style.backgroundColor = '#f0fff0'; + separator.textContent = `Code: ${code}`; grid.appendChild(separator); } - - // 为该street下的每个code添加分隔符和图片 - const codes = Object.keys(groupedByStreet[street]); - codes.forEach((code, codeIndex) => { - // 添加code分隔符 - if (codeIndex > 0 || streetIndex > 0) { - const separator = document.createElement('div'); - separator.className = 'code-separator'; - separator.textContent = `Code: ${code}`; - grid.appendChild(separator); + + // 添加该code的所有图片 + groupedData[code].forEach((item) => { + const key = `${item.code}_1${item.index + 1}`; + const url = item.url; + const imageItem = createImageItem(key, url, code); + grid.appendChild(imageItem); + + // 初始化裁剪计数器 + if (!cropCounters[code]) { + cropCounters[code] = 0; + } + + // 在元素添加到DOM后,添加鼠标悬停事件 + const container = document.getElementById(`container-${key}`); + const img = document.getElementById(`img-${key}`); + + if (container) { + container.addEventListener('mouseenter', function() { + if (!cropStates[key].cropping) { + startCrop(key); + } + }); } - - // 添加该code的所有图片 - groupedByStreet[street][code].forEach((item) => { - const key = `${item.code}-1${item.index + 1}`; - const url = item.url; - const imageItem = createImageItem(key, url, code, item.street); - grid.appendChild(imageItem); - - // 初始化裁剪计数器 - if (!cropCounters[code]) { - cropCounters[code] = 0; - } - - // 在元素添加到DOM后,添加鼠标悬停事件 - const container = document.getElementById(`container-${key}`); - const img = document.getElementById(`img-${key}`); - - if (container) { - container.addEventListener('mouseenter', function() { - if (!cropStates[key].cropping) { - startCrop(key); - } - }); - } - }); }); }); } // 创建单个图片项目 - function createImageItem(key, url, code, street) { + function createImageItem(key, url, code) { const div = document.createElement('div'); div.className = 'image-item'; div.innerHTML = ` @@ -341,18 +279,16 @@
图片 ${key} -
+
鼠标悬停开始裁剪
-
-
`; - + // 初始化裁剪状态 cropStates[key] = { isSelecting: false, @@ -362,7 +298,7 @@ endY: 0, cropping: false }; - + return div; } @@ -475,33 +411,33 @@ function performCrop(key, x, y, width, height) { const img = document.getElementById(`img-${key}`); const previewContainer = document.getElementById(`cropped-preview-${key}`); - + // 提取code - const code = key.split('-')[0]; - + const code = key.split('_')[0]; + // 增加该code的裁剪计数 cropCounters[code] = (cropCounters[code] || 0) + 1; - + // 创建canvas进行裁剪 const cropCanvas = document.createElement('canvas'); const cropCtx = cropCanvas.getContext('2d'); - + // 计算实际图片坐标 const scaleX = img.naturalWidth / img.clientWidth; const scaleY = img.naturalHeight / img.clientHeight; - + const actualX = x * scaleX; const actualY = y * scaleY; const actualWidth = width * scaleX; const actualHeight = height * scaleY; - + cropCanvas.width = actualWidth; cropCanvas.height = actualHeight; - + // 裁剪图片 - cropCtx.drawImage(img, actualX, actualY, actualWidth, actualHeight, + cropCtx.drawImage(img, actualX, actualY, actualWidth, actualHeight, 0, 0, actualWidth, actualHeight); - + // 转换为blob并存储 cropCanvas.toBlob(function(blob) { if (!blob) { @@ -509,11 +445,11 @@ document.getElementById(`info-${key}`).textContent = '裁剪失败:无法生成图片数据'; return; } - - // 使用code-1 + 序号的格式,如130665-11, 130665-12 - const cropKey = `${code}-1${cropCounters[code]}`; + + // 使用code_1 + 序号的格式,如130665_11, 130665_12 + const cropKey = `${code}_1${cropCounters[code]}`; croppedImages[cropKey] = blob; - + // 显示预览 previewContainer.innerHTML = `
@@ -521,8 +457,8 @@ 裁剪结果
`; - - document.getElementById(`info-${key}`).textContent = + + document.getElementById(`info-${key}`).textContent = `裁剪完成: ${Math.round(actualWidth)}x${Math.round(actualHeight)} 像素 (${cropKey})`; }, 'image/png'); } @@ -557,311 +493,63 @@ info.textContent = '点击图片开始裁剪'; } - // 在裁剪完成后,使用当前裁剪作为模板,识别相同巷道、相同code的其他图片 - function matchTemplate(key, code, street) { - // 检查是否有裁剪的图片作为模板 - const croppedPreview = document.getElementById(`cropped-preview-${key}`); - const croppedImg = croppedPreview?.querySelector('.cropped-img'); - - if (!croppedImg) { - alert('请先在当前图片上裁剪出一个区域作为模板!'); - return; - } - - // 获取模板图片 - const templateImg = new Image(); - templateImg.onload = function() { - // 提取模板像素数据 - const templateCanvas = document.createElement('canvas'); - templateCanvas.width = templateImg.naturalWidth; - templateCanvas.height = templateImg.naturalHeight; - const templateCtx = templateCanvas.getContext('2d'); - templateCtx.drawImage(templateImg, 0, 0); - const templatePixels = templateCtx.getImageData(0, 0, templateCanvas.width, templateCanvas.height); - - // 保存模板数据 - templateData = templatePixels; - templateCode = code; - templateStreet = street; - - // 在相同巷道、相同code的其他图片上进行匹配 - autoMatchAllImages(code, street); - }; - templateImg.src = croppedImg.src; - } - - // 自动在所有匹配的图片上进行模板匹配和裁剪 - function autoMatchAllImages(targetCode, targetStreet) { - if (!templateData) { - return; - } - - const results = []; - const matchedKeys = []; - - // 遍历所有图片 - imagesData.forEach((item, index) => { - const currentCode = item.code; - const currentStreet = item.street; - const key = `${item.code}-1${item.index + 1}`; - - // 只处理相同巷道和相同code的图片 - if (currentCode === targetCode && currentStreet === targetStreet) { - const img = document.getElementById(`img-${key}`); - if (!img || !img.complete) return; - - // 执行模板匹配 - const matchResult = findBestMatch(img, templateData); - if (matchResult && matchResult.score > 0.7) { - results.push({ - key: key, - score: matchResult.score, - x: matchResult.x, - y: matchResult.y, - width: matchResult.width, - height: matchResult.height - }); - matchedKeys.push(key); - } - } - }); - - // 根据匹配结果自动裁剪 - results.forEach(result => { - if (result.key !== `${templateCode}-11`) { // 不裁剪模板图片本身 - autoCropByMatch(result); - } - }); - - // 提示结果 - if (results.length > 0) { - alert(`在 ${results.length} 张图片中找到匹配并自动裁剪!`); - } else { - alert('未找到匹配的图片!'); - } - } - - // 使用像素对比进行模板匹配(不使用OpenCV) - function findBestMatch(img, templateData) { - const targetCanvas = document.createElement('canvas'); - targetCanvas.width = img.naturalWidth; - targetCanvas.height = img.naturalHeight; - const targetCtx = targetCanvas.getContext('2d'); - targetCtx.drawImage(img, 0, 0); - const targetPixels = targetCtx.getImageData(0, 0, targetCanvas.width, targetCanvas.height); - - const templateWidth = templateData.width; - const templateHeight = templateData.height; - const targetWidth = targetPixels.width; - const targetHeight = targetPixels.height; - - // 模板不能比目标图片大 - if (templateWidth > targetWidth || templateHeight > targetHeight) { - return null; - } - - let bestMatch = { score: 0, x: 0, y: 0, width: templateWidth, height: templateHeight }; - const step = 5; // 步长,提高性能 - - // 滑动窗口匹配 - for (let y = 0; y <= targetHeight - templateHeight; y += step) { - for (let x = 0; x <= targetWidth - templateWidth; x += step) { - const score = comparePixelRegion(targetPixels, templateData, x, y, templateWidth, templateHeight); - if (score > bestMatch.score) { - bestMatch = { score, x, y, width: templateWidth, height: templateHeight }; - } - } - } - - return bestMatch.score > 0.7 ? bestMatch : null; - } - - // 比较两个像素区域的相似度 - function comparePixelRegion(targetPixels, templatePixels, startX, startY, width, height) { - let totalDiff = 0; - let totalPixels = 0; - - for (let y = 0; y < height; y += 2) { // 每2个像素采样一次,提高性能 - for (let x = 0; x < width; x += 2) { - const targetIndex = ((startY + y) * targetPixels.width + (startX + x)) * 4; - const templateIndex = (y * templatePixels.width + x) * 4; - - // 计算RGB差异 - const diffR = Math.abs(targetPixels.data[targetIndex] - templatePixels.data[templateIndex]); - const diffG = Math.abs(targetPixels.data[targetIndex + 1] - templatePixels.data[templateIndex + 1]); - const diffB = Math.abs(targetPixels.data[targetIndex + 2] - templatePixels.data[templateIndex + 2]); - - totalDiff += (diffR + diffG + diffB) / 3; - totalPixels++; - } - } - - const avgDiff = totalDiff / totalPixels; - // 转换为相似度(0-1) - const similarity = Math.max(0, 1 - (avgDiff / 255)); - return similarity; - } - - // 根据匹配结果自动裁剪 - function autoCropByMatch(result) { - const { key, x, y, width, height } = result; - const img = document.getElementById(`img-${key}`); - - if (!img) return; - - // 计算缩放比例 - const scaleX = img.clientWidth / img.naturalWidth; - const scaleY = img.clientHeight / img.naturalHeight; - - // 转换到显示坐标 - const displayX = x * scaleX; - const displayY = y * scaleY; - const displayWidth = width * scaleX; - const displayHeight = height * scaleY; - - // 执行裁剪 - performCrop(key, displayX, displayY, displayWidth, displayHeight); - } - // 删除图片 function deleteImage(key) { // 从imagesData中移除 const index = imagesData.findIndex(item => { - const itemKey = item.code ? `${item.code}-1${imagesData.indexOf(item) + 1}` : `img_${imagesData.indexOf(item) + 1}`; + const itemKey = item.code ? `${item.code}_1${imagesData.indexOf(item) + 1}` : `img_${imagesData.indexOf(item) + 1}`; return itemKey === key; }); - + if (index !== -1) { imagesData.splice(index, 1); } - + // 清除相关数据 delete croppedImages[key]; delete cropStates[key]; - + // 重新初始化页面 initializePage(); } - // 按巷道导出 - function exportByStreet(street) { - const croppedKeys = Object.keys(croppedImages); - const streetImages = []; - - // 筛选出该巷道的图片 - croppedKeys.forEach(key => { - const code = key.split('-')[0]; - const item = imagesData.find(img => img.code === code && img.street === street); - if (item) { - streetImages.push(key); - } - }); - - if (streetImages.length === 0) { - alert(`巷道 ${street} 没有裁剪的图片可以导出!`); - return; - } - - // 创建ZIP文件 - createZipFileByStreet(street, streetImages); - } - - // 为特定巷道创建ZIP文件 - async function createZipFileByStreet(street, imageKeys) { - try { - const files = []; - - // 收集该巷道的文件数据 - for (const key of imageKeys) { - const blob = croppedImages[key]; - const arrayBuffer = await blobToArrayBuffer(blob); - const fileName = `${key}.png`; - - files.push({ - name: fileName, - data: new Uint8Array(arrayBuffer) - }); - } - - // 生成ZIP文件 - const zipBlob = createSimpleZip(files); - - // 下载ZIP文件 - const fileName = `${street}.zip`; - downloadFile(zipBlob, fileName); - - alert(`已导出巷道 ${street} 的 ${imageKeys.length} 个裁剪图片`); - } catch (error) { - console.error('导出ZIP失败:', error); - alert('导出ZIP文件失败,请重试!'); - } - } - - // 为每个巷道添加导出按钮 - function addStreetExportButtons() { - const grid = document.getElementById('imagesGrid'); - const streets = new Set(imagesData.map(item => item.street)); - - streets.forEach(street => { - const exportBtn = document.createElement('button'); - exportBtn.className = 'export-btn'; - exportBtn.style.margin = '10px auto'; - exportBtn.style.display = 'block'; - exportBtn.textContent = `导出巷道 ${street}`; - exportBtn.onclick = () => exportByStreet(street); - grid.appendChild(exportBtn); - }); - } - - // 修改初始化页面函数,添加导出按钮 - const originalInitializePage = initializePage; - initializePage = function() { - originalInitializePage(); - addStreetExportButtons(); - }; - - // 创建并下载所有裁剪图片为ZIP + // 创建并下载所有裁剪图片 async function exportAllCroppedImages() { const croppedKeys = Object.keys(croppedImages); - + if (croppedKeys.length === 0) { alert('没有裁剪的图片可以导出!'); return; } - - try { - // 创建ZIP文件 - const zipBlob = await createZipFile(); - - // 下载ZIP文件 - const fileName = `cropped_images_${new Date().getTime()}.zip`; - downloadFile(zipBlob, fileName); - - alert(`已导出 ${croppedKeys.length} 个裁剪图片到ZIP文件\n文件格式:PNG\n文件名格式:130665-11.png, 130665-12.png`); - } catch (error) { - console.error('导出ZIP失败:', error); - alert('导出ZIP文件失败,请重试!'); + + // 逐个下载图片文件 + for (const key of croppedKeys) { + const blob = croppedImages[key]; + const fileName = `${key}.png`; + downloadFile(blob, fileName); } + + alert(`已导出 ${croppedKeys.length} 个裁剪图片\n文件格式:PNG\n文件名格式:130665_11.png, 130665_12.png\n请手动创建文件夹整理`); } // 创建ZIP文件 async function createZipFile() { const files = []; - + // 收集所有文件数据 for (const key of Object.keys(croppedImages)) { const blob = croppedImages[key]; const arrayBuffer = await blobToArrayBuffer(blob); - const fileName = `${key}.png`; - + const fileName = `${key}/${key}_11.png`; // 改为PNG格式 + files.push({ name: fileName, data: new Uint8Array(arrayBuffer) }); } - + // 生成ZIP文件 return createSimpleZip(files); } @@ -1253,88 +941,11 @@ initializePage(); } - // 按巷道导出 - function exportByStreet(street) { - const croppedKeys = Object.keys(croppedImages); - const streetImages = []; - - // 筛选出该巷道的图片 - croppedKeys.forEach(key => { - const code = key.split('-')[0]; - const item = imagesData.find(img => img.code === code && img.street === street); - if (item) { - streetImages.push(key); - } - }); - - if (streetImages.length === 0) { - alert(`巷道 ${street} 没有裁剪的图片可以导出!`); - return; - } - - // 创建ZIP文件 - createZipFileByStreet(street, streetImages); - } - - // 为特定巷道创建ZIP文件 - async function createZipFileByStreet(street, imageKeys) { - try { - const files = []; - - // 收集该巷道的文件数据 - for (const key of imageKeys) { - const blob = croppedImages[key]; - const arrayBuffer = await blobToArrayBuffer(blob); - const fileName = `${key}.png`; - - files.push({ - name: fileName, - data: new Uint8Array(arrayBuffer) - }); - } - - // 生成ZIP文件 - const zipBlob = createSimpleZip(files); - - // 下载ZIP文件 - const fileName = `${street}.zip`; - downloadFile(zipBlob, fileName); - - alert(`已导出巷道 ${street} 的 ${imageKeys.length} 个裁剪图片`); - } catch (error) { - console.error('导出ZIP失败:', error); - alert('导出ZIP文件失败,请重试!'); - } - } - - // 为每个巷道添加导出按钮 - function addStreetExportButtons() { - const grid = document.getElementById('imagesGrid'); - const streets = new Set(imagesData.map(item => item.street)); - - streets.forEach(street => { - const exportBtn = document.createElement('button'); - exportBtn.className = 'export-btn'; - exportBtn.style.margin = '10px auto'; - exportBtn.style.display = 'block'; - exportBtn.textContent = `导出巷道 ${street}`; - exportBtn.onclick = () => exportByStreet(street); - grid.appendChild(exportBtn); - }); - } - - // 修改初始化页面函数,添加导出按钮 - const originalInitializePage = initializePage; - initializePage = function() { - originalInitializePage(); - addStreetExportButtons(); - }; - // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', initializePage); // 图片数据对象 const imagesData = [ - {"code":"130665", "street":"20", "url":"http://10.17.152.53:8007/fileData/20251229206649/40858/130665_error.jpg"} + {"code": "130665", "url": "https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"} ];