run / index.html
hbheiner's picture
Add 3 files
b895a56 verified
Raw
History Blame Contribute Delete
17.8 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Animator</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.18.0/dist/tf.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/coco-ssd@2.2.2/dist/coco-ssd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.4/gsap.min.js"></script>
<style>
.canvas-container {
position: relative;
margin: 0 auto;
max-width: 100%;
}
#imageCanvas, #detectionCanvas {
position: absolute;
top: 0;
left: 0;
max-width: 100%;
height: auto;
}
#detectionCanvas {
pointer-events: none;
}
.object-box {
position: absolute;
border: 2px dashed rgba(255, 255, 255, 0.7);
background-color: rgba(0, 0, 0, 0.3);
display: flex;
align-items: flex-end;
justify-content: center;
color: white;
font-weight: bold;
padding-bottom: 5px;
cursor: pointer;
transition: all 0.3s;
}
.object-box:hover {
background-color: rgba(0, 100, 255, 0.5);
}
.object-box.selected {
border: 2px solid #3B82F6;
background-color: rgba(59, 130, 246, 0.5);
z-index: 10;
}
.animation-preview {
border: 2px solid #3B82F6;
box-shadow: 0 0 20px rgba(59, 130, 246, 0.5);
}
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 200px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.pulse {
animation: pulse 2s infinite;
}
</style>
</head>
<body class="bg-gray-900 text-white min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="text-center mb-8">
<h1 class="text-4xl font-bold text-blue-400 mb-2">Image Animator</h1>
<p class="text-gray-400">Upload an image, detect objects, and bring them to life with animations!</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Upload Section -->
<div class="bg-gray-800 rounded-lg p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4 text-blue-300">1. Upload Image</h2>
<div class="border-2 border-dashed border-gray-600 rounded-lg p-6 text-center mb-4 hover:border-blue-400 transition-colors">
<input type="file" id="imageUpload" accept="image/*" class="hidden">
<label for="imageUpload" class="cursor-pointer block">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto text-blue-400 mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<p class="text-gray-300">Click to upload an image</p>
<p class="text-sm text-gray-500 mt-1">Supports JPG, PNG, WEBP</p>
</label>
</div>
<div class="flex justify-between">
<button id="detectBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed" disabled>
Detect Objects
</button>
<button id="resetBtn" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed" disabled>
Reset
</button>
</div>
</div>
<!-- Detection Section -->
<div class="bg-gray-800 rounded-lg p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4 text-blue-300">2. Detected Objects</h2>
<div id="objectList" class="max-h-64 overflow-y-auto mb-4">
<p class="text-gray-500 text-center py-8">No objects detected yet</p>
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-gray-400">Selected: <span id="selectedObject">None</span></span>
<button id="animateBtn" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed" disabled>
Animate Selected
</button>
</div>
</div>
<!-- Animation Controls -->
<div class="bg-gray-800 rounded-lg p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4 text-blue-300">3. Animation Controls</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-400 mb-1">Animation Type</label>
<select id="animationType" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2 text-white">
<option value="bounce">Bounce</option>
<option value="shake">Shake</option>
<option value="float">Float</option>
<option value="spin">Spin</option>
<option value="pulse">Pulse</option>
<option value="slide">Slide</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-400 mb-1">Duration (seconds)</label>
<input type="range" id="duration" min="0.5" max="5" step="0.5" value="2" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer">
<span id="durationValue" class="text-sm text-gray-400">2.0s</span>
</div>
<div>
<label class="block text-sm font-medium text-gray-400 mb-1">Intensity</label>
<input type="range" id="intensity" min="1" max="10" step="1" value="5" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer">
<span id="intensityValue" class="text-sm text-gray-400">5</span>
</div>
<div class="flex space-x-2">
<button id="previewBtn" class="flex-1 bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed" disabled>
Preview
</button>
<button id="saveBtn" class="flex-1 bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed" disabled>
Save Animation
</button>
</div>
</div>
</div>
</div>
<!-- Image Display Area -->
<div class="mt-8 bg-gray-800 rounded-lg p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4 text-blue-300">Preview</h2>
<div class="canvas-container">
<canvas id="imageCanvas" class="rounded-lg shadow-lg"></canvas>
<canvas id="detectionCanvas" class="rounded-lg"></canvas>
<div id="objectBoxesContainer"></div>
</div>
</div>
<!-- Saved Animations -->
<div id="savedAnimations" class="mt-8 bg-gray-800 rounded-lg p-6 shadow-lg hidden">
<h2 class="text-xl font-semibold mb-4 text-blue-300">Saved Animations</h2>
<div id="savedAnimationsList" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Saved animations will appear here -->
</div>
</div>
</div>
<script>
// DOM Elements
const imageUpload = document.getElementById('imageUpload');
const detectBtn = document.getElementById('detectBtn');
const resetBtn = document.getElementById('resetBtn');
const animateBtn = document.getElementById('animateBtn');
const previewBtn = document.getElementById('previewBtn');
const saveBtn = document.getElementById('saveBtn');
const objectList = document.getElementById('objectList');
const selectedObjectDisplay = document.getElementById('selectedObject');
const animationType = document.getElementById('animationType');
const duration = document.getElementById('duration');
const durationValue = document.getElementById('durationValue');
const intensity = document.getElementById('intensity');
const intensityValue = document.getElementById('intensityValue');
const imageCanvas = document.getElementById('imageCanvas');
const detectionCanvas = document.getElementById('detectionCanvas');
const objectBoxesContainer = document.getElementById('objectBoxesContainer');
const savedAnimations = document.getElementById('savedAnimations');
const savedAnimationsList = document.getElementById('savedAnimationsList');
// Canvas contexts
const imageCtx = imageCanvas.getContext('2d');
const detectionCtx = detectionCanvas.getContext('2d');
// App state
let model = null;
let detectedObjects = [];
let selectedObject = null;
let originalImage = null;
let currentAnimations = [];
let savedAnimationItems = [];
// Initialize COCO-SSD model
async function initModel() {
try {
model = await cocoSsd.load();
console.log('Model loaded successfully');
} catch (error) {
console.error('Error loading model:', error);
alert('Failed to load the object detection model. Please try again later.');
}
}
// Handle image upload
imageUpload.addEventListener('change', function(e) {
if (e.target.files && e.target.files[0]) {
const reader = new FileReader();
reader.onload = function(event) {
originalImage = new Image();
originalImage.onload = function() {
// Set canvas dimensions
const maxWidth = 800;
const ratio = Math.min(maxWidth / originalImage.width, 1);
const width = originalImage.width * ratio;
const height = originalImage.height * ratio;
imageCanvas.width = width;
imageCanvas.height = height;
detectionCanvas.width = width;
detectionCanvas.height = height;
// Draw image on canvas
imageCtx.drawImage(originalImage, 0, 0, width, height);
// Enable detect button
detectBtn.disabled = false;
resetBtn.disabled = false;
// Reset other states
clearDetections();
selectedObject = null;
selectedObjectDisplay.textContent = 'None';
animateBtn.disabled = true;
previewBtn.disabled = true;
saveBtn.disabled = true;
};
originalImage.src = event.target.result;
};
reader.readAsDataURL(e.target.files[0]);
}
});
// Detect objects in the image
detectBtn.addEventListener('click', async function() {
if (!originalImage) return;
detectBtn.disabled = true;
detectBtn.textContent = 'Detecting...';
try {
// Perform object detection
const predictions = await model.detect(imageCanvas);
detectedObjects = predictions;
// Clear previous detections
detectionCtx.clearRect(0, 0, detectionCanvas.width, detectionCanvas.height);
objectBoxesContainer.innerHTML = '';
objectList.innerHTML = '';
if (predictions.length === 0) {
objectList.innerHTML = '<p class="text-gray-500 text-center py-8">No objects detected</p>';
return;
}
// Draw bounding boxes and create object list
predictions.forEach((obj, index) => {
// Draw on detection canvas
detectionCtx.strokeStyle = '#3B82F6';
detectionCtx.lineWidth = 2;
detectionCtx.strokeRect(obj.bbox[0], obj.bbox[1], obj.bbox[2], obj.bbox[3]);
detectionCtx.fillStyle = '#3B82F6';
detectionCtx.font = '12px Arial';
detectionCtx.fillText(`${obj.class} (${Math.round(obj.score * 100)}%)`, obj.bbox[0], obj.bbox[1] > 10 ? obj.bbox[1] - 5 : 10);
// Create object box div for interaction
const box = document.createElement('div');
box.className = 'object-box';
box.style.left = `${obj.bbox[0]}px`;
box.style.top = `${obj.bbox[1]}px`;
box.style.width = `${obj.bbox[2]}px`;
box.style.height = `${obj.bbox[3]}px`;
box.dataset.index = index;
box.innerHTML = `<span class="text-xs">${obj.class}</span>`;
box.addEventListener('click', function() {
selectObject(index);
});
objectBoxesContainer.appendChild(box);
// Add to object list
const listItem = document.createElement('div');
listItem.className = 'flex items-center justify-between p-2 hover:bg-gray-700 rounded-lg cursor-pointer';
listItem.dataset.index = index;
listItem.innerHTML = `
<div class="flex items-center">
<div class="w-3 h-3 rounded-full bg-blue-400 mr-2"></div>
<span>${obj.class}</span>
</div>
<span class="text-xs text-gray-400">${Math.round(obj.score * 100)}%</span>
`;
listItem.addEventListener('click', function() {
selectObject(index);
});
objectList.appendChild(listItem);
});
// Enable animation controls
animateBtn.disabled = false;
} catch (error) {
console.error('Detection error:', error);
alert('Error detecting objects. Please try again.');
} finally {
detectBtn.disabled = false;
detectBtn.textContent = 'Detect Objects';
}
});
// Select an object
function selectObject(index) {
// Deselect previous
if (selectedObject !== null) {
const prevBoxes = document.querySelectorAll('.object-box, #objectList div');
prevBoxes[selectedObject].classList.remove('selected');
}
// Select new
selectedObject = index;
const boxes = document.querySelectorAll('.object-box, #objectList div');
boxes[index].classList.add('selected');
// Update UI
selectedObjectDisplay.textContent = detectedObjects[index].class;
previewBtn.disabled = false;
saveBtn.disabled = false;
}
// Reset everything
resetBtn.addEventListener('click', function() {
if (!originalImage) return;
// Clear canvases
imageCtx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);
detectionCtx.clearRect(0, 0, detectionCanvas.width, detectionCanvas.height);
// Redraw original image
imageCtx.drawImage(originalImage, 0, 0, imageCanvas.width, image
</html>