feat(autoScale): detectClothBounds 폴백 + usePinchScale hook (v0-plan Task 8 마무리)

src/features/autoScale/detectClothBounds.ts:
- v0 폴백: detectClothWidthPx → 항상 0 반환
  → calculateScale가 confidence=0으로 fallback, 사용자가 핀치로 직접 보정
- 정식 구현은 Task 12: iOS Vision Saliency (VNGenerateAttentionBasedSaliencyImageRequest)

src/features/autoScale/__tests__/detectClothBounds.test.ts:
- 회귀 테스트 2 케이스: 정상 입력 / 빈 문자열 → 둘 다 0 (Task 12 정식 구현 시 spec 변경 명시점)

src/features/autoScale/usePinchScale.ts:
- react-native-gesture-handler v2 Pinch + Pan Simultaneous
- .runOnJS(true) 명시 → worklet→JS thread 트램폴린 없이 setState 직접 (reanimated 의존 회피)
- [MIN_SCALE, MAX_SCALE] 재사용 (calculateScale 모듈에서 import)
- reset(): initialScale로 scale 복원 + tx/ty 0
- 검증은 Mac 실기기 manual test (Windows에서 hook 동작 검증 불가)

검증:
- npx tsc --noEmit: 무에러
- 전체 npm test: 6 suites / 17 tests passed
  (sanity 1 + pose 1 + photoValidation 3 + maskSplit 4 + calculateScale 6 + detectClothBounds 2)

남은 Task 8 부분: 없음 (Step 4-6 완료, Step 7 Mac 검증)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-25 16:18:25 +09:00
parent 1c29b4ee8a
commit 005d612ef3
3 changed files with 65 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
import { detectClothWidthPx } from '../detectClothBounds';
describe('detectClothWidthPx', () => {
it('v0 폴백: 자동 검출 미지원이므로 항상 0을 반환한다 (정식 구현은 Task 12)', async () => {
const result = await detectClothWidthPx('file://anyframe.jpg');
expect(result).toBe(0);
});
it('빈 문자열 입력도 동일하게 0 폴백', async () => {
const result = await detectClothWidthPx('');
expect(result).toBe(0);
});
});

View File

@@ -0,0 +1,12 @@
/**
* v0 폴백: 카메라 프레임에서 사용자가 든 옷의 가로 픽셀 검출.
*
* v0에서는 항상 0을 반환 → calculateScale가 confidence=0으로 fallback,
* 사용자는 핀치 줌(usePinchScale)으로 직접 보정.
*
* 정식 구현은 Task 12: iOS Vision Saliency(VNGenerateAttentionBasedSaliencyImageRequest
* 또는 VNGenerateForegroundInstanceMaskRequest)로 손에 든 옷 mask → bbox 가로 너비.
*/
export async function detectClothWidthPx(_cameraFrameUri: string): Promise<number> {
return 0;
}

View File

@@ -0,0 +1,40 @@
import { useCallback, useState } from 'react';
import { Gesture } from 'react-native-gesture-handler';
import { MIN_SCALE, MAX_SCALE } from './calculateScale';
/**
* 핀치 줌 + 팬 hook. 자동 스케일 추정값을 초기값으로 받고 사용자가 직접 보정.
*
* Mac 실기기 검증 항목:
* - .runOnJS(true)로 worklet→JS thread 트램폴린 없이 setState 직접 호출 (reanimated 의존 회피)
* - 핀치 줌 [MIN_SCALE, MAX_SCALE] 클램핑이 calculateScale과 동일 범위인지
* - 팬은 누적 translation, reset은 initialScale로 복원
*/
export function usePinchScale(initialScale: number = 1) {
const [scale, setScale] = useState(initialScale);
const [tx, setTx] = useState(0);
const [ty, setTy] = useState(0);
const pinch = Gesture.Pinch()
.runOnJS(true)
.onUpdate((e) => {
setScale((s) => Math.max(MIN_SCALE, Math.min(MAX_SCALE, s * e.scale)));
});
const pan = Gesture.Pan()
.runOnJS(true)
.onUpdate((e) => {
setTx((x) => x + e.translationX);
setTy((y) => y + e.translationY);
});
const gesture = Gesture.Simultaneous(pinch, pan);
const reset = useCallback(() => {
setScale(initialScale);
setTx(0);
setTy(0);
}, [initialScale]);
return { scale, tx, ty, gesture, reset };
}