2nzi's picture
first commit
b4f9490 verified
<template>
<div class="zoom-view">
<div v-if="hasAnnotations" class="zoom-image-container">
<canvas
ref="zoomCanvas"
class="zoom-canvas"
:width="zoomWidth"
:height="zoomHeight"
></canvas>
<div class="zoom-info">
<span>Frame {{ currentFrameNumber }}</span>
<span v-if="zoomRegion">{{ Math.round(zoomRegion.width) }}x{{ Math.round(zoomRegion.height) }}px</span>
<span v-if="zoomRegion?.type === 'points' && selectedAnnotations.length > 1">
{{ selectedAnnotations.length }} points
</span>
</div>
</div>
<div v-else class="no-annotations">
<p>Aucune annotation sur cette frame</p>
</div>
</div>
</template>
<script>
import { useAnnotationStore } from '@/stores/annotationStore'
import { useVideoStore } from '@/stores/videoStore'
import { computed, ref, watch, nextTick, onMounted } from 'vue'
export default {
name: 'ZoomView',
mounted() {
// Forcer le rafraîchissement quand le composant est monté
this.$nextTick(() => {
setTimeout(() => {
this.updateZoomImage()
}, 100) // Petit délai pour s'assurer que la vidéo est prête
})
},
setup() {
const annotationStore = useAnnotationStore()
const videoStore = useVideoStore()
const zoomCanvas = ref(null)
// Dimensions du canvas de zoom
const zoomWidth = 200
const zoomHeight = 300
const getCurrentFrameNumber = () => {
const frameRate = annotationStore.currentSession?.frameRate || 30
return Math.round(videoStore.currentTime * frameRate)
}
const currentFrameNumber = computed(() => getCurrentFrameNumber())
const selectedAnnotations = computed(() => {
const currentFrame = getCurrentFrameNumber()
const frameAnnotations = annotationStore.getAnnotationsForFrame(currentFrame) || []
return frameAnnotations.filter(
annotation => annotation && annotation.objectId === annotationStore.selectedObjectId
)
})
const hasAnnotations = computed(() => {
return selectedAnnotations.value.length > 0
})
const zoomRegion = computed(() => {
const annotations = selectedAnnotations.value
if (!annotations.length) return null
// Séparer rectangles et points
const rectangles = annotations.filter(a => a.type === 'rectangle')
const points = annotations.filter(a => a.type === 'point')
if (rectangles.length > 0) {
// Utiliser le premier rectangle trouvé
const rect = rectangles[0]
return {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
type: 'rectangle'
}
} else if (points.length > 0) {
// Calculer le centre moyen des points
const avgX = points.reduce((sum, p) => sum + p.x, 0) / points.length
const avgY = points.reduce((sum, p) => sum + p.y, 0) / points.length
if (points.length === 1) {
// Pour un seul point, utiliser une taille fixe raisonnable
const fixedSize = 120
return {
x: avgX - fixedSize,
y: avgY - fixedSize,
width: fixedSize * 2,
height: fixedSize * 2,
type: 'points',
centerX: avgX,
centerY: avgY
}
} else {
// Pour plusieurs points, calculer la bounding box englobante
const minX = Math.min(...points.map(p => p.x))
const maxX = Math.max(...points.map(p => p.x))
const minY = Math.min(...points.map(p => p.y))
const maxY = Math.max(...points.map(p => p.y))
// Calculer les dimensions nécessaires
const pointsWidth = maxX - minX
const pointsHeight = maxY - minY
// Ajouter une marge (minimum 60px de chaque côté)
const marginX = Math.max(60, pointsWidth * 0.3)
const marginY = Math.max(60, pointsHeight * 0.3)
// Calculer les dimensions finales
const finalWidth = pointsWidth + marginX * 2
const finalHeight = pointsHeight + marginY * 2
return {
x: minX - marginX,
y: minY - marginY,
width: finalWidth,
height: finalHeight,
type: 'points',
centerX: avgX,
centerY: avgY,
pointsBounds: {
minX, maxX, minY, maxY,
pointsWidth, pointsHeight
}
}
}
}
return null
})
const drawAnnotationsOnZoom = (ctx, sourceX, sourceY, sourceWidth, sourceHeight) => {
const annotations = selectedAnnotations.value
if (!annotations.length) return
// Calculer le facteur d'échelle entre la source et le canvas
const scaleX = zoomWidth / sourceWidth
const scaleY = zoomHeight / sourceHeight
annotations.forEach(annotation => {
if (annotation.type === 'rectangle') {
// Calculer la position du rectangle dans le canvas zoomé
const rectX = (annotation.x - sourceX) * scaleX
const rectY = (annotation.y - sourceY) * scaleY
const rectWidth = annotation.width * scaleX
const rectHeight = annotation.height * scaleY
// Ne dessiner que si le rectangle est visible dans la zone
if (rectX < zoomWidth && rectY < zoomHeight &&
rectX + rectWidth > 0 && rectY + rectHeight > 0) {
// Dessiner le rectangle
ctx.strokeStyle = '#00ff00' // Vert pour les rectangles
ctx.lineWidth = 2
ctx.setLineDash([5, 3]) // Trait pointillé
ctx.strokeRect(rectX, rectY, rectWidth, rectHeight)
ctx.setLineDash([]) // Remettre trait plein
}
} else if (annotation.type === 'point') {
// Calculer la position du point dans le canvas zoomé
const pointX = (annotation.x - sourceX) * scaleX
const pointY = (annotation.y - sourceY) * scaleY
// Ne dessiner que si le point est visible dans la zone
if (pointX >= 0 && pointX <= zoomWidth && pointY >= 0 && pointY <= zoomHeight) {
// Couleur selon le type de point
const pointColor = annotation.pointType === 'positive' ? '#00ff00' : '#ff0000'
// Dessiner le cercle du point
ctx.fillStyle = pointColor
ctx.strokeStyle = '#ffffff'
ctx.lineWidth = 2
ctx.beginPath()
ctx.arc(pointX, pointY, 6, 0, 2 * Math.PI)
ctx.fill()
ctx.stroke()
// Dessiner le symbole + ou -
ctx.strokeStyle = '#ffffff'
ctx.lineWidth = 2
ctx.beginPath()
if (annotation.pointType === 'positive') {
// Dessiner +
ctx.moveTo(pointX - 3, pointY)
ctx.lineTo(pointX + 3, pointY)
ctx.moveTo(pointX, pointY - 3)
ctx.lineTo(pointX, pointY + 3)
} else {
// Dessiner -
ctx.moveTo(pointX - 3, pointY)
ctx.lineTo(pointX + 3, pointY)
}
ctx.stroke()
}
}
})
// Si c'est une vue centrée sur des points, dessiner une croix de repère au centre
if (zoomRegion.value?.type === 'points') {
ctx.strokeStyle = '#ffff00' // Jaune pour le centre
ctx.lineWidth = 1
ctx.setLineDash([3, 3])
ctx.beginPath()
const centerX = zoomWidth / 2
const centerY = zoomHeight / 2
ctx.moveTo(centerX - 15, centerY)
ctx.lineTo(centerX + 15, centerY)
ctx.moveTo(centerX, centerY - 15)
ctx.lineTo(centerX, centerY + 15)
ctx.stroke()
ctx.setLineDash([])
}
}
const updateZoomImage = async () => {
if (!zoomCanvas.value || !zoomRegion.value) return
// Trouver l'élément vidéo
const videoElement = document.querySelector('video')
if (!videoElement) return
const canvas = zoomCanvas.value
const ctx = canvas.getContext('2d')
// Effacer le canvas
ctx.clearRect(0, 0, zoomWidth, zoomHeight)
try {
// Calculer les coordonnées source dans la vidéo
const sourceX = Math.max(0, zoomRegion.value.x)
const sourceY = Math.max(0, zoomRegion.value.y)
const sourceWidth = Math.min(zoomRegion.value.width, videoElement.videoWidth - sourceX)
const sourceHeight = Math.min(zoomRegion.value.height, videoElement.videoHeight - sourceY)
// S'assurer que les dimensions sont valides
if (sourceWidth <= 0 || sourceHeight <= 0) return
// Dessiner la région zoomée sur le canvas
ctx.drawImage(
videoElement,
sourceX, sourceY, sourceWidth, sourceHeight, // Source (région de la vidéo)
0, 0, zoomWidth, zoomHeight // Destination (canvas)
)
// Dessiner les annotations sur l'image zoomée
drawAnnotationsOnZoom(ctx, sourceX, sourceY, sourceWidth, sourceHeight)
} catch (error) {
console.error('Erreur lors de la capture du zoom:', error)
// Afficher un message d'erreur sur le canvas
ctx.fillStyle = '#666'
ctx.fillRect(0, 0, zoomWidth, zoomHeight)
ctx.fillStyle = '#fff'
ctx.font = '14px Arial'
ctx.textAlign = 'center'
ctx.fillText('Erreur de capture', zoomWidth / 2, zoomHeight / 2)
}
}
// Watcher pour mettre à jour l'image quand les annotations changent
watch([selectedAnnotations, currentFrameNumber], () => {
nextTick(() => {
updateZoomImage()
})
}, { deep: true })
// Watcher pour mettre à jour quand le temps de la vidéo change
watch(() => videoStore.currentTime, () => {
nextTick(() => {
updateZoomImage()
})
})
// Hook onMounted pour forcer le rafraîchissement au montage
onMounted(() => {
nextTick(() => {
setTimeout(() => {
updateZoomImage()
}, 200) // Délai plus long pour s'assurer que tout est prêt
})
})
return {
selectedAnnotations,
hasAnnotations,
zoomRegion,
currentFrameNumber,
zoomCanvas,
zoomWidth,
zoomHeight,
updateZoomImage
}
}
}
</script>
<style scoped>
.zoom-view {
height: 100%;
padding: 10px;
color: white;
overflow: auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.zoom-image-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.zoom-canvas {
border: 1px solid #555;
border-radius: 4px;
background: #000;
}
.zoom-info {
display: flex;
gap: 12px;
font-size: 0.8rem;
color: #ccc;
}
.no-annotations {
text-align: center;
color: #888;
font-style: italic;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.no-annotations p {
margin: 0;
}
</style>