npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@homecheck/vue3-object-detection-marker

v1.0.2

Published

A Vue 3 component for object detection marking with multi-layer support

Readme

Vue 3 Object Detection Marker

Vue 3를 위한 강력한 객체 감지 마킹 컴포넌트입니다. 멀티 레이어 지원, 그리드 기반 선택, 해상도 독립적 데이터 처리 기능을 제공합니다.

🚀 데모

라이브 데모

✨ 주요 기능

🎨 멀티 레이어 시스템

  • 다중 색상 레이어: 동시에 여러 색상 레이어로 작업
  • 레이어 가시성 제어: 각 레이어의 표시/숨김 제어
  • 레이어별 투명도: 각 레이어의 투명도 개별 설정
  • 활성 레이어 전환: 실시간 레이어 간 전환

🖱️ 다양한 선택 모드

  • 포인트 모드: 개별 그리드 셀 선택
  • 사각형 모드: 드래그로 영역 선택
  • 지우개 모드: 선택된 영역 제거
  • 브러시 시스템: 크기 및 모양(원형/사각형) 조절 가능

📐 해상도 독립적 설계

  • 백분율 기반 좌표: 다양한 해상도에서 일관된 결과
  • 종횡비 자동 계산: 이미지 비율에 따른 자동 그리드 조정
  • 스케일링 지원: 캔버스 크기 변경에 따른 자동 스케일링

📱 터치 지원

  • 모바일 호환: 터치 이벤트 완전 지원
  • 태블릿 최적화: 터치 드래그 및 제스처 지원
  • 반응형 디자인: 다양한 화면 크기 대응

📊 다양한 데이터 형식

  • 그리드 내보내기: 원시 그리드 데이터 형식
  • 최적화된 내보내기: 사각형으로 병합된 최적화 형식
  • 백분율 내보내기: 해상도 독립적 백분율 형식
  • 렌더링 모드: 그리드/사각형 렌더링 모드 지원

📦 설치

npm install @homecheck/vue3-object-detection-marker

🛠️ 사용법

기본 사용법

<template>
  <div>
    <!-- 마킹 컴포넌트 -->
    <ObjectDetectionMarker
      ref="markerRef"
      :image="imageUrl"
      :canvas-width="800"
      :canvas-height="600"
      :resolution="10"
      selection-mode="point"
      @selection-change="handleSelectionChange"
      @layer-change="handleLayerChange"
    />
    
    <!-- 미리보기 컴포넌트 -->
    <ObjectDetectionPreview
      :image="imageUrl"
      :selection-data="previewData"
      object-fit-mode="contain"
      render-mode="grid"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { ObjectDetectionMarker, ObjectDetectionPreview } from '@homecheck/vue3-object-detection-marker'
import type { GridLayerExport, ExtendedSelectionData, LayerSelectionData } from '@homecheck/vue3-object-detection-marker'

const markerRef = ref()
const imageUrl = ref('your-image-url.jpg')
const previewData = ref<GridLayerExport | null>(null)

const handleSelectionChange = (data: ExtendedSelectionData) => {
  console.log('선택 변경:', data)
}

const handleLayerChange = (data: LayerSelectionData) => {
  console.log('레이어 변경:', data)
}

const exportData = () => {
  if (markerRef.value) {
    const data = markerRef.value.exportOptimizedLayers()
    previewData.value = data
    console.log('내보낸 데이터:', data)
  }
}
</script>

고급 사용법 - 외부 컨트롤

<template>
  <div class="marker-container">
    <!-- 외부 컨트롤 -->
    <div class="controls">
      <!-- 모드 전환 -->
      <div class="mode-controls">
        <button @click="switchMode('point')" :class="{ active: currentMode === 'point' }">
          포인트 모드
        </button>
        <button @click="switchMode('rectangle')" :class="{ active: currentMode === 'rectangle' }">
          사각형 모드
        </button>
        <button @click="switchMode('eraser')" :class="{ active: currentMode === 'eraser' }">
          지우개 모드
        </button>
      </div>
      
      <!-- 레이어 선택 -->
      <div class="layer-controls">
        <button 
          v-for="color in layerColors" 
          :key="color"
          @click="setActiveLayer(color)"
          :class="{ active: activeLayer === color }"
          :style="{ backgroundColor: color }"
        >
          {{ getColorName(color) }}
        </button>
      </div>
      
      <!-- 브러시 설정 -->
      <div class="brush-controls">
        <label>브러시 크기: {{ brushSize }}</label>
        <input 
          type="range" 
          min="1" 
          max="10" 
          v-model="brushSize" 
          @input="updateBrushSize"
        />
        
        <label>브러시 모양:</label>
        <select v-model="brushShape" @change="updateBrushShape">
          <option value="circle">원형</option>
          <option value="square">사각형</option>
        </select>
      </div>
    </div>
    
    <!-- 마커 컴포넌트 -->
    <ObjectDetectionMarker
      ref="markerRef"
      :image="imageUrl"
      :canvas-width="800"
      :canvas-height="600"
      :resolution="resolution"
      :layer-colors="layerColors"
      :default-brush-size="brushSize"
      :default-brush-shape="brushShape"
      @layer-change="handleLayerChange"
      @active-layer-change="handleActiveLayerChange"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { ObjectDetectionMarker } from '@homecheck/vue3-object-detection-marker'
import type { SelectionMode, BrushShape, LayerSelectionData } from '@homecheck/vue3-object-detection-marker'

const markerRef = ref()
const currentMode = ref<SelectionMode>('point')
const activeLayer = ref('#ff0000')
const brushSize = ref(3)
const brushShape = ref<BrushShape>('circle')
const layerColors = ref(['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff'])

const switchMode = (mode: SelectionMode) => {
  currentMode.value = mode
  markerRef.value?.switchMode(mode)
}

const setActiveLayer = (color: string) => {
  activeLayer.value = color
  markerRef.value?.setActiveColorLayer(color)
}

const updateBrushSize = () => {
  markerRef.value?.setBrushSize(brushSize.value)
}

const updateBrushShape = () => {
  markerRef.value?.setBrushShape(brushShape.value)
}

const getColorName = (color: string) => {
  const names: Record<string, string> = {
    '#ff0000': '빨강',
    '#00ff00': '초록',
    '#0000ff': '파랑',
    '#ffff00': '노랑',
    '#ff00ff': '마젠타'
  }
  return names[color.toLowerCase()] || color
}

const handleLayerChange = (data: LayerSelectionData) => {
  console.log('레이어 데이터:', data)
}

const handleActiveLayerChange = (color: string) => {
  activeLayer.value = color
}
</script>

📋 컴포넌트 API

ObjectDetectionMarker

객체 감지 마킹을 위한 메인 컴포넌트입니다.

Props

| 속성 | 타입 | 기본값 | 설명 | |------|------|---------|-------------| | image | string \| File \| Blob | '' | 로드할 이미지 소스 | | canvasWidth | number | 800 | 캔버스 너비 (픽셀) | | canvasHeight | number | 600 | 캔버스 높이 (픽셀) | | resolution | number | 10 | 그리드 해상도 (차원당 셀 수) | | selectionMode | 'point' \| 'rectangle' \| 'eraser' | 'point' | 초기 선택 모드 | | layerColors | string[] | ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff'] | 사용 가능한 레이어 색상 | | defaultBrushSize | number | 3 | 초기 브러시 크기 | | defaultBrushShape | 'circle' \| 'square' | 'circle' | 초기 브러시 모양 | | highlightColor | string | '#007bff' | 선택 영역 강조 색상 | | gridColor | string | '#cccccc' | 그리드 라인 색상 | | hoverColor | string | '#6c757d' | 호버 상태 색상 | | backgroundColor | string | '#ffffff' | 배경 색상 |

Events

| 이벤트 | 매개변수 | 설명 | |--------|----------|-------------| | selectionChange | ExtendedSelectionData | 선택 영역 변경 시 발생 | | modeChange | SelectionMode | 선택 모드 변경 시 발생 | | imageLoad | HTMLImageElement | 이미지 로드 완료 시 발생 | | imageError | Error | 이미지 로드 실패 시 발생 | | gridHover | GridCell \| null | 그리드 셀 호버 시 발생 | | layerChange | LayerSelectionData | 레이어 데이터 변경 시 발생 | | activeLayerChange | string | 활성 레이어 변경 시 발생 | | layerVisibilityChange | string, boolean | 레이어 가시성 변경 시 발생 |

Methods

| 메서드 | 매개변수 | 반환값 | 설명 | |--------|----------|---------|-------------| | exportGridLayers() | - | ColorLayerExport | 원시 그리드 데이터 내보내기 | | exportOptimizedLayers() | - | GridLayerExport | 최적화된 데이터 내보내기 | | importGridLayers(data) | ColorLayerExport | void | 그리드 데이터 가져오기 | | importOptimizedLayers(data) | GridLayerExport | void | 최적화된 데이터 가져오기 | | switchMode(mode) | SelectionMode | void | 선택 모드 변경 | | setActiveColorLayer(color) | string | void | 활성 레이어 색상 설정 | | toggleLayerVisibility(color) | string | void | 레이어 가시성 토글 | | setBrushSize(size) | number | void | 브러시 크기 설정 | | setBrushShape(shape) | BrushShape | void | 브러시 모양 설정 | | getTotalSelectedCount() | - | number | 전체 선택된 셀 수 반환 | | getActiveLayerCount() | - | number | 활성 레이어 선택된 셀 수 반환 |

ObjectDetectionPreview

선택된 데이터를 미리보기로 표시하는 읽기 전용 컴포넌트입니다.

Props

| 속성 | 타입 | 기본값 | 설명 | |------|------|---------|-------------| | image | string \| File \| Blob | '' | 표시할 이미지 | | selectionData | GridLayerExport \| null | null | 렌더링할 선택 데이터 | | objectFitMode | 'contain' \| 'cover' | 'contain' | 이미지 맞춤 모드 | | renderMode | 'grid' \| 'rect' | 'grid' | 렌더링 모드 | | canvasWidth | number | auto | 캔버스 너비 | | canvasHeight | number | auto | 캔버스 높이 | | backgroundColor | string | '#f8f9fa' | 배경 색상 | | layerColors | Record<string, {color: string, opacity: number}> | {} | 레이어별 색상 및 투명도 설정 |

렌더링 모드

  • grid: 개별 그리드 셀들을 모두 렌더링
  • rect: 각 레이어의 모든 셀을 포함하는 단일 경계 사각형으로 렌더링

🔧 헬퍼 함수

그리드 변환 함수

import { 
  convertGridLayerToRenderRects,
  getBoundingRectangle,
  convertGridRectsToPercentRects,
  convertPercentRectsToPixelRects
} from '@homecheck/vue3-object-detection-marker'

convertGridLayerToRenderRects(data, renderMode)

GridLayerExport 데이터를 렌더링 모드에 맞게 변환합니다.

const renderRects = convertGridLayerToRenderRects(gridData, 'rect')
// 'grid' 모드: 원본 사각형들 반환
// 'rect' 모드: 각 레이어를 단일 경계 사각형으로 병합

getBoundingRectangle(rects)

주어진 사각형들을 모두 포함하는 경계 사각형을 계산합니다.

const boundingRect = getBoundingRectangle([
  { x: 10, y: 10, width: 20, height: 20 },
  { x: 40, y: 40, width: 30, height: 30 }
])
// 결과: { x: 10, y: 10, width: 60, height: 60 }

convertGridRectsToPercentRects(rects, gridCols, gridRows)

그리드 기반 사각형을 백분율 기반으로 변환합니다.

const percentRects = convertGridRectsToPercentRects(
  [{ x: 5, y: 5, width: 10, height: 10 }],
  100, // 총 열 수
  100  // 총 행 수
)
// 결과: [{ x: 0.05, y: 0.05, width: 0.1, height: 0.1 }]

convertPercentRectsToPixelRects(percentRects, canvasWidth, canvasHeight)

백분율 기반 사각형을 픽셀 기반으로 변환합니다.

const pixelRects = convertPercentRectsToPixelRects(
  [{ x: 0.1, y: 0.1, width: 0.2, height: 0.2 }],
  800, // 캔버스 너비
  600  // 캔버스 높이
)
// 결과: [{ x: 80, y: 60, width: 160, height: 120 }]

그리드 계산 함수

import { 
  calculateGridFromResolution,
  screenToGridResolution,
  gridToScreenResolution,
  mergeGridsToRects
} from '@homecheck/vue3-object-detection-marker'

calculateGridFromResolution(imageWidth, imageHeight, canvasWidth, canvasHeight, resolution)

해상도 기반 그리드 시스템의 전체 계산을 수행합니다.

const gridCalc = calculateGridFromResolution(1920, 1080, 800, 600, 10)
// 결과: {
//   aspectRatio: { width: 16, height: 9 },
//   gridDimensions: { cols: 160, rows: 90 },
//   cellSize: { width: 5, height: 6.67 }
// }

screenToGridResolution(screenX, screenY, cellWidth, cellHeight, gridCols, gridRows)

화면 좌표를 그리드 좌표로 변환합니다.

const gridCell = screenToGridResolution(100, 150, 5, 6.67, 160, 90)
// 결과: { row: 22, col: 20 }

mergeGridsToRects(selectedGrids, totalCols, totalRows)

선택된 그리드 셀들을 최소한의 사각형으로 병합합니다.

const selectedGrids = new Set(['0,0', '0,1', '1,0', '1,1'])
const rects = mergeGridsToRects(selectedGrids, 100, 100)
// 결과: [{ x: 0, y: 0, width: 2, height: 2 }]

📊 데이터 형식

GridLayerExport

최적화된 내보내기 형식입니다.

interface GridLayerExport {
  metadata: {
    cols: number        // 그리드 열 수
    rows: number        // 그리드 행 수
  }
  layers: Record<string, Rectangle[]>  // 색상별 사각형 배열
}

interface Rectangle {
  x: number           // 시작 열 위치
  y: number           // 시작 행 위치
  width: number       // 너비 (셀 단위)
  height: number      // 높이 (셀 단위)
}

ColorLayerExport

원시 그리드 데이터 형식입니다.

interface ColorLayerExport {
  imageSize: { width: number; height: number }
  resolution: number
  layers: Record<string, string[]>  // 색상별 그리드 키 배열
}

PercentRect

해상도 독립적 백분율 사각형입니다.

interface PercentRect {
  x: number      // 0-1 (0% - 100%)
  y: number      // 0-1 (0% - 100%)
  width: number  // 0-1 (0% - 100%)
  height: number // 0-1 (0% - 100%)
}

ExtendedSelectionData

선택 변경 이벤트 데이터입니다.

interface ExtendedSelectionData {
  selectedCells: GridCell[]
  resolution: number
  imageAspectRatio: { width: number; height: number }
  gridDimensions: { cols: number; rows: number }
  cellSize: { width: number; height: number }
  imageWidth: number
  imageHeight: number
  canvasWidth: number
  canvasHeight: number
  layerData?: LayerSelectionData
}

LayerSelectionData

레이어 정보 데이터입니다.

interface LayerSelectionData {
  activeColor: string
  layers: Array<{
    color: string
    selectedCount: number
    visible: boolean
    name?: string
  }>
  totalSelected: number
}

🎨 스타일링

컴포넌트는 scoped CSS를 사용하며 CSS 변수 재정의나 deep 선택자를 통해 커스터마이징할 수 있습니다.

/* 캔버스 테두리 커스터마이징 */
:deep(.marker-canvas) {
  border: 2px solid #007bff;
  border-radius: 8px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

/* 로딩 인디케이터 커스터마이징 */
:deep(.loading-indicator) {
  background: rgba(0, 123, 255, 0.9);
  color: white;
  font-weight: bold;
}

/* 에러 메시지 커스터마이징 */
:deep(.error-message) {
  background: rgba(220, 53, 69, 0.9);
  color: white;
  border-radius: 4px;
}

/* 미리보기 컴포넌트 스타일링 */
:deep(.preview-canvas) {
  border: 1px solid #dee2e6;
  border-radius: 4px;
}

🔄 실시간 데이터 동기화

마킹 컴포넌트와 미리보기 컴포넌트 간 실시간 동기화 예제:

<template>
  <div class="sync-container">
    <div class="marker-section">
      <ObjectDetectionMarker
        ref="markerRef"
        :image="imageUrl"
        @selection-change="handleSelectionChange"
        @layer-change="handleLayerChange"
      />
    </div>
    
    <div class="preview-section">
      <ObjectDetectionPreview
        :image="imageUrl"
        :selection-data="previewData"
        :render-mode="renderMode"
      />
      
      <!-- 헬퍼 함수 활용 정보 표시 -->
      <div class="helper-info">
        <h4>변환된 렌더링 데이터:</h4>
        <pre>{{ JSON.stringify(convertedRects, null, 2) }}</pre>
        
        <h4>경계 사각형 정보:</h4>
        <div v-for="(info, color) in boundingInfo" :key="color">
          <span :style="{ color }">{{ color }}</span>: 
          {{ info.rectCount }}개 사각형 → 
          경계 크기: {{ info.boundingRect.width }}×{{ info.boundingRect.height }}
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import { 
  ObjectDetectionMarker, 
  ObjectDetectionPreview,
  convertGridLayerToRenderRects,
  getBoundingRectangle
} from '@homecheck/vue3-object-detection-marker'

const markerRef = ref()
const previewData = ref(null)
const renderMode = ref('grid')

// 실시간 변환된 데이터
const convertedRects = computed(() => {
  if (!previewData.value) return {}
  return convertGridLayerToRenderRects(previewData.value, renderMode.value)
})

// 경계 사각형 정보
const boundingInfo = computed(() => {
  if (!previewData.value) return {}
  const result = {}
  
  Object.entries(previewData.value.layers).forEach(([color, rects]) => {
    if (rects.length > 0) {
      result[color] = {
        rectCount: rects.length,
        boundingRect: getBoundingRectangle(rects)
      }
    }
  })
  
  return result
})

const handleSelectionChange = () => {
  // 실시간 업데이트
  if (markerRef.value) {
    previewData.value = markerRef.value.exportOptimizedLayers()
  }
}

const handleLayerChange = (layerData) => {
  console.log('레이어 변경:', layerData)
  // 필요시 추가 처리
}
</script>

🚀 성능 최적화

대용량 데이터 처리

// 대용량 선택 데이터 최적화
const optimizeSelection = () => {
  if (markerRef.value) {
    // 원시 데이터 대신 최적화된 데이터 사용
    const optimizedData = markerRef.value.exportOptimizedLayers()
    
    // 백분율 변환으로 메모리 사용량 감소
    const percentData = convertGridRectsToPercentRects(
      optimizedData.layers['#ff0000'] || [],
      optimizedData.metadata.cols,
      optimizedData.metadata.rows
    )
    
    console.log('최적화된 데이터:', percentData)
  }
}

이벤트 최적화

import { debounce } from '@homecheck/vue3-object-detection-marker'

// 선택 변경 이벤트 디바운싱
const debouncedSelectionChange = debounce((data) => {
  // 실제 처리 로직
  updatePreview(data)
}, 100)

🤝 기여하기

기여를 환영합니다! Pull Request를 자유롭게 제출해 주세요.

📄 라이선스

MIT License - 자세한 내용은 LICENSE 파일을 참조하세요.

🔗 링크

📝 변경 로그

v1.0.0-rc.1

  • 초기 릴리스
  • 멀티 레이어 시스템 구현
  • 브러시 시스템 추가
  • 헬퍼 함수 라이브러리 제공
  • TypeScript 완전 지원
  • 터치 이벤트 지원
  • 해상도 독립적 데이터 처리