From 9822cab93e67cc8e1513787b8df9f5af6a57cb02 Mon Sep 17 00:00:00 2001 From: Grzegorz Kucmierz Date: Sat, 28 Feb 2026 17:50:29 +0000 Subject: [PATCH] feat: restore QR code detection overlay in custom QrScanner --- src/components/tools/QrScanner.vue | 106 +++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/components/tools/QrScanner.vue b/src/components/tools/QrScanner.vue index 67e81e1..5d24e46 100644 --- a/src/components/tools/QrScanner.vue +++ b/src/components/tools/QrScanner.vue @@ -19,6 +19,99 @@ let stream = null let scanRafId = null let barcodeDetector = null +const overlayCanvas = ref(null) + +const paintDetections = (codes) => { + const canvas = overlayCanvas.value + const video = videoRef.value + + if (!canvas || !video) return + + const ctx = canvas.getContext('2d') + const { width, height } = canvas.getBoundingClientRect() + + // Update canvas size if needed (to match CSS size) + if (canvas.width !== width || canvas.height !== height) { + canvas.width = width + canvas.height = height + } + + ctx.clearRect(0, 0, width, height) + + if (!codes || codes.length === 0) return + + const vw = video.videoWidth + const vh = video.videoHeight + if (!vw || !vh) return + + // Calculate object-fit: cover scaling + const videoRatio = vw / vh + const canvasRatio = width / height + + let drawWidth, drawHeight, startX, startY + + if (canvasRatio > videoRatio) { + // Canvas is wider than video (video cropped top/bottom) + drawWidth = width + drawHeight = width / videoRatio + startX = 0 + startY = (height - drawHeight) / 2 + } else { + // Canvas is taller than video (video cropped left/right) + drawHeight = height + drawWidth = height * videoRatio + startY = 0 + startX = (width - drawWidth) / 2 + } + + const scale = drawWidth / vw + const isMirrored = isFront.value + + // Styles + const styles = getComputedStyle(document.documentElement) + const accent = styles.getPropertyValue('--primary-accent').trim() || '#00f2fe' + + ctx.lineWidth = 4 + ctx.strokeStyle = accent + ctx.fillStyle = accent + + codes.forEach(code => { + const points = code.cornerPoints + if (!points || points.length < 4) return + + ctx.beginPath() + + const transform = (p) => { + let x = p.x * scale + startX + let y = p.y * scale + startY + + if (isMirrored) { + x = width - x + } + return { x, y } + } + + const p0 = transform(points[0]) + ctx.moveTo(p0.x, p0.y) + + for (let i = 1; i < points.length; i++) { + const p = transform(points[i]) + ctx.lineTo(p.x, p.y) + } + + ctx.closePath() + ctx.stroke() + + // Draw corners + points.forEach(p => { + const tp = transform(p) + ctx.beginPath() + ctx.arc(tp.x, tp.y, 4, 0, Math.PI * 2) + ctx.fill() + }) + }) +} + const updateVideoAspect = () => { if (videoRef.value && videoRef.value.videoWidth && videoRef.value.videoHeight) { videoAspect.value = videoRef.value.videoWidth / videoRef.value.videoHeight @@ -142,6 +235,7 @@ const detectLoop = async () => { try { const codes = await barcodeDetector.detect(videoRef.value) + paintDetections(codes) if (codes.length > 0) { onDetect(codes) } @@ -384,6 +478,8 @@ const isUrl = (string) => { muted > + +

{{ error }}

@@ -540,6 +636,16 @@ const isUrl = (string) => { transform: scaleX(-1); } +.scan-overlay-canvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 5; +} + /* front mirror canvas removed */ .error-overlay {