From cd39c6d05ad6adae8b74c934baa4f0a55073bc64 Mon Sep 17 00:00:00 2001 From: dongjin kim Date: Fri, 14 Nov 2025 15:53:16 +0900 Subject: [PATCH] img --> canvas. --- public/app.js | 112 +++++++++++++++++++++++++++++++----------- public/dashboard.html | 2 +- public/style.css | 1 - 3 files changed, 84 insertions(+), 31 deletions(-) diff --git a/public/app.js b/public/app.js index c902466..273e8b0 100644 --- a/public/app.js +++ b/public/app.js @@ -270,7 +270,10 @@ document.addEventListener('DOMContentLoaded', () => { // ========== dashboard.html 스크립트 추가 시작 ========== const uri = "ws://10.10.11.246:8765"; // 필요하면 여기만 수정 - const imgEl = document.getElementById("frame"); + + // [수정] imgEl -> canvasEl로 변경 + const canvasEl = document.getElementById("frame"); + const statusEl = document.getElementById("status"); const frameInfoEl = document.getElementById("frame-info"); const detListEl = document.getElementById("det-list"); @@ -281,6 +284,12 @@ document.addEventListener('DOMContentLoaded', () => { const modelDisplayEl = document.getElementById("current-model-display"); const fpsDisplayEl = document.getElementById("fps-display"); + // [추가] 캔버스 2D 컨텍스트 + let ctx = null; + if (canvasEl) { + ctx = canvasEl.getContext('2d'); + } + // [추가] 클래스별 색상 팔레트 (원하는 색상으로 수정 가능) const CLASS_COLORS = [ '#FF3838', // 0: Red @@ -299,7 +308,8 @@ document.addEventListener('DOMContentLoaded', () => { let lastFrameMeta = null; - if (imgEl && statusEl && frameInfoEl && detListEl && bboxContainerEl && modelContainerEl) { + // [수정] canvasEl 및 ctx 존재 여부 확인 + if (canvasEl && ctx && statusEl && frameInfoEl && detListEl && bboxContainerEl && modelContainerEl) { // 모델 변경 이벤트 리스너 modelContainerEl.addEventListener('change', (event) => { @@ -335,11 +345,12 @@ document.addEventListener('DOMContentLoaded', () => { // 박스 컨테이너 위치/크기 조절 함수 function updateBboxContainerPosition() { - if (!imgEl || !bboxContainerEl) return; - const top = imgEl.offsetTop; - const left = imgEl.offsetLeft; - const width = imgEl.offsetWidth; - const height = imgEl.offsetHeight; + // [수정] imgEl -> canvasEl + if (!canvasEl || !bboxContainerEl) return; + const top = canvasEl.offsetTop; + const left = canvasEl.offsetLeft; + const width = canvasEl.offsetWidth; + const height = canvasEl.offsetHeight; bboxContainerEl.style.top = `${top}px`; bboxContainerEl.style.left = `${left}px`; bboxContainerEl.style.width = `${width}px`; @@ -382,7 +393,8 @@ document.addEventListener('DOMContentLoaded', () => { lines.push(`DET ch=${meta.ch} seq=${meta.seq} ts=${meta.ts_us} cnt=${items.length}`); bboxContainerEl.innerHTML = ""; - if (!imgEl || !lastFrameMeta) { + // [수정] imgEl -> canvasEl + if (!canvasEl || !lastFrameMeta) { // [수정됨] x1,y1,x2,y2 형식에 맞게 텍스트 로그 수정 items.forEach((it, i) => { // x1, y1, x2, y2 -> x, y, w, h @@ -402,8 +414,9 @@ document.addEventListener('DOMContentLoaded', () => { } updateBboxContainerPosition(); - const imgWidth = imgEl.clientWidth; - const imgHeight = imgEl.clientHeight; + // [수정] imgEl -> canvasEl + const imgWidth = canvasEl.clientWidth; + const imgHeight = canvasEl.clientHeight; const frameWidth = lastFrameMeta.w; const frameHeight = lastFrameMeta.h; @@ -511,26 +524,68 @@ document.addEventListener('DOMContentLoaded', () => { } return; } + + // [수정] ArrayBuffer 처리: img.src 대신 createImageBitmap 및 canvas.drawImage 사용 if (data instanceof ArrayBuffer && lastFrameMeta) { + + // ========== [수정됨] FPS 계산 로직 (이곳으로 이동) ========== + // WebSocket에서 ArrayBuffer(프레임)를 수신한 시점을 기준으로 FPS 계산 + frameCount++; + const now = performance.now(); + const delta = now - lastFpsCheckTime; + + if (delta >= 1000 && fpsDisplayEl) { // 1초마다 업데이트 + const fps = (frameCount * 1000) / delta; + fpsDisplayEl.textContent = `${Math.round(fps)} FPS`; + frameCount = 0; + lastFpsCheckTime = now; + } + // ========== FPS 계산 로직 끝 ========== + + const blob = new Blob([data], {type: "image/jpeg"}); - const url = URL.createObjectURL(blob); - imgEl.onload = () => { - URL.revokeObjectURL(url); - updateBboxContainerPosition(); - - // [추가] FPS 계산 - frameCount++; - const now = performance.now(); - const delta = now - lastFpsCheckTime; - - if (delta >= 1000 && fpsDisplayEl) { // 1초마다 업데이트 - const fps = (frameCount * 1000) / delta; - fpsDisplayEl.textContent = `${Math.round(fps)} FPS`; - frameCount = 0; - lastFpsCheckTime = now; - } - }; - imgEl.src = url; + + // createImageBitmap으로 비동기 디코딩 및 렌더링 + createImageBitmap(blob) + .then(imageBitmap => { + // 캔버스 크기를 CSS에 정의된 표시 크기로 맞춥니다. + // (object-fit: contain을 시뮬레이션하기 위해 매번 필요) + canvasEl.width = canvasEl.clientWidth; + canvasEl.height = canvasEl.clientHeight; + + const canvasWidth = canvasEl.width; + const canvasHeight = canvasEl.height; + const frameWidth = imageBitmap.width; // 실제 이미지 비트맵의 너비 + const frameHeight = imageBitmap.height; // 실제 이미지 비트맵의 높이 + + // 'object-fit: contain' 로직 + const widthRatio = canvasWidth / frameWidth; + const heightRatio = canvasHeight / frameHeight; + const ratio = Math.min(widthRatio, heightRatio); + + const videoDisplayWidth = frameWidth * ratio; + const videoDisplayHeight = frameHeight * ratio; + const offsetX = (canvasWidth - videoDisplayWidth) / 2; + const offsetY = (canvasHeight - videoDisplayHeight) / 2; + + // 캔버스를 지웁니다 (CSS의 background-color가 비친다) + ctx.clearRect(0, 0, canvasWidth, canvasHeight); + + // 계산된 위치에 이미지를 그립니다. + ctx.drawImage(imageBitmap, offsetX, offsetY, videoDisplayWidth, videoDisplayHeight); + + // 비트맵 메모리 해제 + imageBitmap.close(); + + // 프레임이 그려진 *이후*에 바운딩 박스 컨테이너 위치 업데이트 + updateBboxContainerPosition(); + + // [수정됨] FPS 계산 로직이 위로 이동했으므로 여기서는 제거됩니다. + + }) + .catch(e => { + console.error("createImageBitmap error:", e); + }); } }; } @@ -550,5 +605,4 @@ document.addEventListener('DOMContentLoaded', () => { // ========== 페이지 로드 시 모델 목록 즉시 로드 ========== loadModelList(); - }); \ No newline at end of file diff --git a/public/dashboard.html b/public/dashboard.html index bd139f4..4069564 100644 --- a/public/dashboard.html +++ b/public/dashboard.html @@ -34,7 +34,7 @@ - video frame +
diff --git a/public/style.css b/public/style.css index 10df12d..6b59498 100644 --- a/public/style.css +++ b/public/style.css @@ -276,7 +276,6 @@ main { min-height: 0; max-width: 100%; background: #000; - object-fit: contain; border: 1px solid #333; position: relative; z-index: 1;