pixelpaintpro / index.html
xjtudongyang's picture
点击上传按钮没有反应
b1aceeb verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AnnotateIt - Image Markup Tool</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<style>
#canvas-container {
position: relative;
display: inline-block;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
#image-canvas, #drawing-canvas {
position: absolute;
top: 0;
left: 0;
}
.annotation-item:hover {
background-color: rgba(59, 130, 246, 0.1);
}
.coordinates-display {
font-family: monospace;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="mb-8 text-center">
<h1 class="text-4xl font-bold text-indigo-600 mb-2">AnnotateIt 🎨</h1>
<p class="text-lg text-gray-600">Precision image annotation tool with coordinate export</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div class="lg:col-span-2">
<div class="bg-white rounded-xl shadow-md overflow-hidden mb-6">
<div class="p-4 bg-indigo-50 border-b border-indigo-100">
<h2 class="text-xl font-semibold text-indigo-700">Image Annotation Canvas</h2>
</div>
<div class="p-4">
<div id="canvas-container" class="mx-auto">
<canvas id="image-canvas"></canvas>
<canvas id="drawing-canvas"></canvas>
</div>
</div>
<div class="p-4 bg-gray-50 border-t border-gray-200 flex flex-wrap gap-4">
<button id="upload-btn" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition flex items-center gap-2">
<i data-feather="upload"></i> Upload Image
</button>
<input type="file" id="file-input" accept="image/*" class="hidden">
<button id="clear-btn" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition flex items-center gap-2">
<i data-feather="trash-2"></i> Clear Annotations
</button>
<button id="export-btn" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition flex items-center gap-2 ml-auto">
<i data-feather="download"></i> Export Coordinates
</button>
</div>
</div>
<div class="bg-white rounded-xl shadow-md overflow-hidden">
<div class="p-4 bg-indigo-50 border-b border-indigo-100">
<h2 class="text-xl font-semibold text-indigo-700">Instructions</h2>
</div>
<div class="p-4">
<ol class="list-decimal pl-5 space-y-2 text-gray-700">
<li>Upload an image using the upload button</li>
<li>Click and drag on the image to create rectangular annotations</li>
<li>View annotation coordinates in the panel on the right</li>
<li>Export coordinates when finished</li>
</ol>
<div class="mt-4 p-3 bg-yellow-50 border-l-4 border-yellow-400 text-yellow-700">
<p><strong>Note:</strong> Coordinates are normalized (0-1) relative to image dimensions with 6 decimal precision.</p>
</div>
</div>
</div>
</div>
<div>
<div class="bg-white rounded-xl shadow-md overflow-hidden sticky top-4">
<div class="p-4 bg-indigo-50 border-b border-indigo-100">
<h2 class="text-xl font-semibold text-indigo-700">Annotation Coordinates</h2>
</div>
<div class="p-4">
<div id="coordinates-list" class="space-y-3 max-h-96 overflow-y-auto">
<p class="text-gray-500 italic" id="empty-state">No annotations yet. Draw rectangles on the image to see coordinates here.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
feather.replace();
const imageCanvas = document.getElementById('image-canvas');
const drawingCanvas = document.getElementById('drawing-canvas');
const imageCtx = imageCanvas.getContext('2d');
const drawingCtx = drawingCanvas.getContext('2d');
const fileInput = document.getElementById('file-input');
const uploadBtn = document.getElementById('upload-btn');
const clearBtn = document.getElementById('clear-btn');
const exportBtn = document.getElementById('export-btn');
const coordinatesList = document.getElementById('coordinates-list');
const emptyState = document.getElementById('empty-state');
let image = null;
let isDrawing = false;
let startX = 0;
let startY = 0;
let annotations = [];
// Set canvas container size
const canvasContainer = document.getElementById('canvas-container');
canvasContainer.style.width = '100%';
canvasContainer.style.height = 'auto';
// Initialize canvases
function initCanvases() {
if (!image) return;
const containerWidth = canvasContainer.offsetWidth;
const scale = containerWidth / image.width;
const height = image.height * scale;
imageCanvas.width = containerWidth;
imageCanvas.height = height;
drawingCanvas.width = containerWidth;
drawingCanvas.height = height;
imageCtx.drawImage(image, 0, 0, containerWidth, height);
}
// Handle image upload
uploadBtn.addEventListener('click', function() {
fileInput.click();
});
fileInput.addEventListener('change', function(e) {
if (!e.target.files.length) return;
const file = e.target.files[0];
if (!file || !file.type.match('image.*')) {
alert('Please select a valid image file (JPEG, PNG, etc.)');
return;
}
// Show loading state
uploadBtn.disabled = true;
uploadBtn.innerHTML = '<i data-feather="loader" class="animate-spin"></i> Uploading...';
feather.replace();
const reader = new FileReader();
reader.onload = function(event) {
// Reset button state
uploadBtn.disabled = false;
uploadBtn.innerHTML = '<i data-feather="upload"></i> Upload Image';
feather.replace();
image = new Image();
image.onload = function() {
initCanvases();
annotations = [];
updateCoordinatesList();
// Show success toast
const toast = document.createElement('div');
toast.className = 'fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg';
toast.textContent = 'Image uploaded successfully!';
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
};
image.onerror = function() {
uploadBtn.disabled = false;
uploadBtn.innerHTML = '<i data-feather="upload"></i> Upload Image';
feather.replace();
alert('Error loading image. Please try another file.');
};
image.src = event.target.result;
};
reader.readAsDataURL(file);
});
// Drawing functionality
drawingCanvas.addEventListener('mousedown', (e) => {
if (!image) return;
isDrawing = true;
startX = e.offsetX;
startY = e.offsetY;
});
drawingCanvas.addEventListener('mousemove', (e) => {
if (!isDrawing || !image) return;
drawingCtx.clearRect(0, 0, drawingCanvas.width, drawingCanvas.height);
const currentX = e.offsetX;
const currentY = e.offsetY;
drawingCtx.strokeStyle = '#3B82F6';
drawingCtx.lineWidth = 2;
drawingCtx.setLineDash([5, 5]);
drawingCtx.strokeRect(
startX,
startY,
currentX - startX,
currentY - startY
);
drawingCtx.fillStyle = 'rgba(59, 130, 246, 0.1)';
drawingCtx.fillRect(
startX,
startY,
currentX - startX,
currentY - startY
);
});
drawingCanvas.addEventListener('mouseup', (e) => {
if (!isDrawing || !image) return;
isDrawing = false;
drawingCtx.clearRect(0, 0, drawingCanvas.width, drawingCanvas.height);
const endX = e.offsetX;
const endY = e.offsetY;
// Don't save zero-size rectangles
if (Math.abs(endX - startX) < 5 || Math.abs(endY - startY) < 5) return;
// Calculate normalized coordinates (0-1)
const width = imageCanvas.width;
const height = imageCanvas.height;
const x1 = (Math.min(startX, endX) / width).toFixed(6);
const y1 = (Math.min(startY, endY) / height).toFixed(6);
const x2 = (Math.max(startX, endX) / width).toFixed(6);
const y2 = (Math.max(startY, endY) / height).toFixed(6);
annotations.push({ x1, y1, x2, y2 });
updateCoordinatesList();
drawAllAnnotations();
});
// Draw all saved annotations
function drawAllAnnotations() {
drawingCtx.clearRect(0, 0, drawingCanvas.width, drawingCanvas.height);
annotations.forEach(rect => {
const width = imageCanvas.width;
const height = imageCanvas.height;
const x1 = parseFloat(rect.x1) * width;
const y1 = parseFloat(rect.y1) * height;
const x2 = parseFloat(rect.x2) * width;
const y2 = parseFloat(rect.y2) * height;
drawingCtx.strokeStyle = '#3B82F6';
drawingCtx.lineWidth = 2;
drawingCtx.setLineDash([]);
drawingCtx.strokeRect(x1, y1, x2 - x1, y2 - y1);
drawingCtx.fillStyle = 'rgba(59, 130, 246, 0.1)';
drawingCtx.fillRect(x1, y1, x2 - x1, y2 - y1);
});
}
// Update coordinates list in the UI
function updateCoordinatesList() {
if (annotations.length === 0) {
emptyState.style.display = 'block';
return;
}
emptyState.style.display = 'none';
coordinatesList.innerHTML = '';
annotations.forEach((rect, index) => {
const item = document.createElement('div');
item.className = 'annotation-item p-3 border border-gray-200 rounded-lg';
item.innerHTML = `
<div class="flex justify-between items-start mb-1">
<span class="font-medium text-indigo-600">Annotation #${index + 1}</span>
<button class="text-red-500 hover:text-red-700 delete-btn" data-index="${index}">
<i data-feather="x" class="w-4 h-4"></i>
</button>
</div>
<div class="coordinates-display text-sm text-gray-700">
<div>x1: ${rect.x1}</div>
<div>y1: ${rect.y1}</div>
<div>x2: ${rect.x2}</div>
<div>y2: ${rect.y2}</div>
</div>
`;
coordinatesList.appendChild(item);
// Add delete event to button
item.querySelector('.delete-btn').addEventListener('click', function() {
const indexToRemove = parseInt(this.getAttribute('data-index'));
annotations.splice(indexToRemove, 1);
updateCoordinatesList();
drawAllAnnotations();
});
});
feather.replace();
}
// Clear all annotations
clearBtn.addEventListener('click', function() {
if (!image || annotations.length === 0) return;
if (confirm('Are you sure you want to clear all annotations?')) {
annotations = [];
drawingCtx.clearRect(0, 0, drawingCanvas.width, drawingCanvas.height);
updateCoordinatesList();
}
});
// Export coordinates
exportBtn.addEventListener('click', function() {
if (!image || annotations.length === 0) {
alert('No annotations to export!');
return;
}
let exportData = annotations.map((rect, index) => ({
id: index + 1,
coordinates: {
x1: rect.x1,
y1: rect.y1,
x2: rect.x2,
y2: rect.y2
}
}));
const dataStr = JSON.stringify(exportData, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const exportName = 'annotations_' + new Date().toISOString().slice(0, 10) + '.json';
const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportName);
linkElement.click();
});
// Handle window resize
window.addEventListener('resize', function() {
if (image) {
initCanvases();
drawAllAnnotations();
}
});
});
</script>
</body>
</html>