/** * Mask Segment Demo App * Mask segmentation canvas based on OpenCV + Skia * * Note: This is a demo for library development testing, directly referencing ./src. * For business project integration demonstration, please refer to the example/ directory (import using the public package name). */ import React, { useEffect, useRef, useState } from 'react'; import { ActivityIndicator, StatusBar, StyleSheet, Text, View, } from 'react-native'; import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; import MaskSegmentCanvas, { type MaskSegmentCanvasRef, type MaskSegmentSession, type MaskSegmentWatchState, MASK_SEMANTIC_COLORS, } from './src'; import { resolveAssetPath } from './src/utils/resolveAssetPath'; import { prewarmPngBgrCacheAsync } from './src/utils/pngImage'; const TEST_ORIGIN = require('./assets/test/origin.png'); const TEST_MASK = require('./assets/test/mask.png'); const INTERACTIVE_WATCH_STATES: MaskSegmentWatchState[] = [ 'interactive', 'mask_paths_ready', ]; function formatWatchStatus(state: MaskSegmentWatchState | ''): string { if (!state) { return ''; } if (state === 'interactive') { return 'Colorable (outline loading...)'; } if (state === 'mask_paths_ready') { return 'Ready'; } if (state === 'error') { return 'Failed'; } return `Loading: ${state}`; } function App(): React.JSX.Element { const canvasRef = useRef(null); const [testPaths, setTestPaths] = useState<{ origin: string; mask: string; } | null>(null); const [loadError, setLoadError] = useState(''); const [watchState, setWatchState] = useState(''); const [sessionDraft, setSessionDraft] = useState( null, ); const isFullyReady = watchState === 'mask_paths_ready'; const isInitLoading = testPaths != null && watchState !== '' && !INTERACTIVE_WATCH_STATES.includes(watchState as MaskSegmentWatchState) && watchState !== 'error'; useEffect(() => { let cancelled = false; (async () => { try { const [origin, mask] = await Promise.all([ resolveAssetPath(TEST_ORIGIN, 'gym_test_origin.png'), resolveAssetPath(TEST_MASK, 'gym_test_mask.png'), ]); await prewarmPngBgrCacheAsync([origin, mask]); if (!cancelled) { setTestPaths({ origin, mask }); } } catch (e) { if (!cancelled) { setLoadError(e instanceof Error ? e.message : String(e)); } } })(); return () => { cancelled = true; }; }, []); return ( {loadError ? ( {loadError} ) : testPaths ? ( <> {watchState ? ( Status: {formatWatchStatus(watchState)} {isFullyReady ? ' ยท Carousel dashed line ready' : null} ) : null} { setWatchState(state); if (__DEV__) { const extra = detail && Object.keys(detail).length > 0 ? ` ${JSON.stringify(detail)}` : ''; console.log( `[Demo onWatch] ${state} ${durationMs.toFixed(0)}ms${extra}`, ); } }} onPaintCallback={payload => { if (__DEV__) { if (payload.kind === 'brush_required') { console.log('[Demo onPaint]', payload.hint, payload.regionName); } else { console.log('[Demo onPaint]', payload.regionName, payload.color); } } }} onError={message => { setLoadError(message); setWatchState('error'); }} /> {isInitLoading ? ( {formatWatchStatus(watchState)} ) : null} {sessionDraft ? ( Restored MMKV draft ({sessionDraft.painted.length} regions) ) : null} ) : ( Loading gym test image... )} ); } const styles = StyleSheet.create({ root: { flex: 1, backgroundColor: '#fff', }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 24, }, loadingText: { marginTop: 12, color: '#666', fontSize: 14, }, errorText: { color: '#c33', fontSize: 14, textAlign: 'center', }, watchText: { paddingHorizontal: 12, paddingTop: 8, color: '#555', fontSize: 12, }, canvasHost: { flex: 1, position: 'relative', }, initOverlay: { ...StyleSheet.absoluteFillObject, alignItems: 'center', justifyContent: 'center', backgroundColor: 'rgba(255, 255, 255, 0.72)', gap: 8, }, initOverlayText: { color: '#666', fontSize: 13, }, sessionText: { paddingHorizontal: 12, paddingBottom: 8, color: '#2a7', fontSize: 12, }, }); export default App;