DreamRenderer / bbox_component.html
Longxiang-ai's picture
Initial commit: DreamRenderer with Zero GPU support
0274afd
<!DOCTYPE html>
<html>
<head>
<style>
.canvas-container {
position: relative;
display: inline-block;
border: 2px solid #ddd;
border-radius: 8px;
overflow: hidden;
background-color: #f8f9fa;
}
.bbox-canvas {
cursor: crosshair;
display: block;
background-color: white;
}
.bbox-list {
margin-top: 10px;
padding: 10px;
background-color: #f8f9fa;
border-radius: 5px;
max-height: 200px;
overflow-y: auto;
}
.bbox-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px;
margin: 2px 0;
background-color: white;
border-radius: 3px;
border-left: 4px solid #007bff;
}
.bbox-input {
width: 150px;
padding: 2px 5px;
border: 1px solid #ddd;
border-radius: 3px;
}
.delete-btn {
background-color: #dc3545;
color: white;
border: none;
padding: 2px 8px;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.delete-btn:hover {
background-color: #c82333;
}
.clear-btn {
background-color: #6c757d;
color: white;
border: none;
padding: 5px 15px;
border-radius: 3px;
cursor: pointer;
margin-top: 10px;
}
.clear-btn:hover {
background-color: #5a6268;
}
.color-indicator {
width: 20px;
height: 20px;
border-radius: 3px;
border: 2px solid white;
box-shadow: 0 0 3px rgba(0,0,0,0.3);
}
.info-text {
margin: 10px 0;
padding: 8px;
background-color: #e3f2fd;
border-radius: 4px;
font-size: 14px;
color: #1976d2;
}
.debug-info {
margin: 10px 0;
padding: 8px;
background-color: #fff3cd;
border-radius: 4px;
font-size: 12px;
color: #856404;
font-family: monospace;
}
</style>
</head>
<body>
<div class="info-text">
💡 拖拽鼠标在画布上绘制边界框,然后为每个框添加描述
</div>
<div class="canvas-container">
<canvas id="bboxCanvas" class="bbox-canvas" width="512" height="512"></canvas>
</div>
<script>
console.log('边界框组件已加载');
const canvas = document.getElementById('bboxCanvas');
const ctx = canvas.getContext('2d');
const debugInfo = document.getElementById('debugInfo');
let isDrawing = false;
let startX, startY;
let boxes = [];
let currentBox = null;
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F'];
let colorIndex = 0;
// 添加调试日志函数
function log(message) {
console.log(message);
debugInfo.textContent = `调试: ${message}`;
}
// 初始化画布
function initCanvas() {
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#ddd';
ctx.lineWidth = 1;
ctx.strokeRect(0, 0, canvas.width, canvas.height);
log('画布已初始化');
}
// 事件监听器
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseleave', stopDrawing); // 添加鼠标离开事件
function startDrawing(e) {
isDrawing = true;
const rect = canvas.getBoundingClientRect();
startX = e.clientX - rect.left;
startY = e.clientY - rect.top;
log(`开始绘制: (${Math.round(startX)}, ${Math.round(startY)})`);
}
function draw(e) {
if (!isDrawing) return;
const rect = canvas.getBoundingClientRect();
const currentX = e.clientX - rect.left;
const currentY = e.clientY - rect.top;
redrawCanvas();
// 绘制当前正在绘制的框
ctx.strokeStyle = colors[colorIndex % colors.length];
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.strokeRect(startX, startY, currentX - startX, currentY - startY);
ctx.setLineDash([]);
}
function stopDrawing(e) {
if (!isDrawing) return;
isDrawing = false;
const rect = canvas.getBoundingClientRect();
const endX = e.clientX - rect.left;
const endY = e.clientY - rect.top;
const width = Math.abs(endX - startX);
const height = Math.abs(endY - startY);
if (width > 10 && height > 10) {
const box = {
x: Math.min(startX, endX),
y: Math.min(startY, endY),
width: width,
height: height,
color: colors[colorIndex % colors.length],
label: '',
id: Date.now()
};
boxes.push(box);
colorIndex++;
addBoxToList(box);
redrawCanvas();
updateOutput();
log(`添加边界框: ${boxes.length}个`);
} else {
redrawCanvas();
log('边界框太小,已忽略');
}
}
function redrawCanvas() {
// 清除画布并重新绘制背景
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 绘制所有边界框
boxes.forEach((box, index) => {
ctx.strokeStyle = box.color;
ctx.lineWidth = 2;
ctx.setLineDash([]);
ctx.strokeRect(box.x, box.y, box.width, box.height);
// 绘制标签
if (box.label) {
ctx.fillStyle = box.color;
ctx.font = '14px Arial';
ctx.fillText(box.label, box.x, box.y - 5);
}
// 绘制索引号
ctx.fillStyle = box.color;
ctx.font = 'bold 12px Arial';
ctx.fillText(`${index + 1}`, box.x + 3, box.y + 15);
});
}
function addBoxToList(box) {
const bboxItems = document.getElementById('bboxItems');
const item = document.createElement('div');
item.className = 'bbox-item';
item.id = `bbox-item-${box.id}`;
item.innerHTML = `
<div style="display: flex; align-items: center; gap: 10px;">
<div class="color-indicator" style="background-color: ${box.color}"></div>
<input type="text" class="bbox-input" placeholder="输入描述..."
onchange="updateBoxLabel(${box.id}, this.value)"
oninput="updateBoxLabel(${box.id}, this.value)">
<span style="font-size: 12px; color: #666;">
(${Math.round(box.x)}, ${Math.round(box.y)}, ${Math.round(box.width)}, ${Math.round(box.height)})
</span>
</div>
<button class="delete-btn" onclick="deleteBox(${box.id})">删除</button>
`;
bboxItems.appendChild(item);
}
function updateBoxLabel(boxId, label) {
const box = boxes.find(b => b.id === boxId);
if (box) {
box.label = label;
redrawCanvas();
updateOutput();
log(`更新标签: ${label}`);
}
}
function deleteBox(boxId) {
const oldLength = boxes.length;
boxes = boxes.filter(b => b.id !== boxId);
redrawBboxList();
redrawCanvas();
updateOutput();
log(`删除边界框: ${oldLength} -> ${boxes.length}`);
}
function clearAllBoxes() {
boxes = [];
redrawBboxList();
redrawCanvas();
updateOutput();
log('清除所有边界框');
}
function redrawBboxList() {
const bboxItems = document.getElementById('bboxItems');
bboxItems.innerHTML = '';
boxes.forEach(box => addBoxToList(box));
}
function updateOutput() {
try {
// 将边界框数据传递给Gradio
const boxData = boxes.map(box => ({
x: box.x / canvas.width, // 归一化坐标
y: box.y / canvas.height,
width: box.width / canvas.width,
height: box.height / canvas.height,
label: box.label || ''
}));
const dataString = JSON.stringify(boxData);
log(`发送数据: ${boxData.length}个边界框`);
// 直接查找Gradio输入框(因为组件直接嵌入在页面中)
const bboxInput = document.querySelector('#bbox_data textarea');
if (bboxInput) {
bboxInput.value = dataString;
// 触发多种事件确保Gradio能检测到变化
bboxInput.dispatchEvent(new Event('input', { bubbles: true }));
bboxInput.dispatchEvent(new Event('change', { bubbles: true }));
bboxInput.dispatchEvent(new Event('blur', { bubbles: true }));
log('直接更新Gradio输入框成功');
} else {
// 如果直接查找失败,尝试延迟查找
setTimeout(() => {
const delayedBboxInput = document.querySelector('#bbox_data textarea') ||
document.querySelector('[data-testid="textbox"] textarea');
if (delayedBboxInput) {
delayedBboxInput.value = dataString;
delayedBboxInput.dispatchEvent(new Event('input', { bubbles: true }));
delayedBboxInput.dispatchEvent(new Event('change', { bubbles: true }));
log('延迟更新Gradio输入框成功');
} else {
log('未找到Gradio输入框');
}
}, 500);
}
// 同时触发自定义事件作为备用
document.dispatchEvent(new CustomEvent('bbox_data_update', {
detail: { data: boxData, dataString: dataString }
}));
} catch (error) {
log(`更新输出时出错: ${error.message}`);
console.error('updateOutput error:', error);
}
}
// 接收来自Gradio的图片更新
window.addEventListener('message', function(event) {
if (event.data && event.data.type === 'update_image') {
const img = new Image();
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
redrawCanvas();
log('图片已更新');
};
img.src = event.data.imageUrl;
}
});
// 页面加载完成后初始化
window.addEventListener('load', function() {
initCanvas();
log('组件已就绪');
});
// 立即初始化
initCanvas();
</script>
</body>
</html>