From 3312facd820e2f9c54800aa60749b70ba6ffabd1 Mon Sep 17 00:00:00 2001 From: 3dtours Date: Mon, 28 Sep 2026 17:36:30 +0700 Subject: [PATCH] docs: write down the camera port and the live-view plan CAMERA_PORT_ANDROID.md lists only what the web build has that Android does not, one row per gap, with the web file to read next to the Android one. The plan alongside it is the sketch for a live camera with a Skia filter on the frame thread. --- ...0972-vision-camera-skia-frame-processor.md | 106 +++++++ CAMERA_PORT_ANDROID.md | 258 ++++++++++++++++++ 2 files changed, 364 insertions(+) create mode 100644 .kilo/plans/1784212500972-vision-camera-skia-frame-processor.md create mode 100644 CAMERA_PORT_ANDROID.md diff --git a/.kilo/plans/1784212500972-vision-camera-skia-frame-processor.md b/.kilo/plans/1784212500972-vision-camera-skia-frame-processor.md new file mode 100644 index 0000000..6f9843f --- /dev/null +++ b/.kilo/plans/1784212500972-vision-camera-skia-frame-processor.md @@ -0,0 +1,106 @@ +# Kế hoạch: Real-time Skia filter trên camera live view dùng vision-camera v4 + +## Vấn đề +`expo-camera` render camera preview lên native surface (PreviewView/SurfaceView) không thể intercept bằng Skia. Periodic capture bị từ chối vì mất live view. + +## Giải pháp +Dùng `react-native-vision-camera@4.7.3` với `useSkiaFrameProcessor` — frame processor chạy native thread, mỗi frame camera được vẽ qua Skia với ColorMatrix trước khi hiển thị. + +## Các bước thực hiện + +### Bước 1: Cài react-native-vision-camera@4.7.3 +```bash +npm install react-native-vision-camera@4.7.3 +``` +- v4.7.3 là latest v4, có export `useSkiaFrameProcessor` và `frameProcessor` prop trên `Camera` +- v5+ đã xoá Skia integration, không dùng được + +### Bước 2: Thêm Expo Config Plugin +Sửa `app.json`: +- Thay `expo-camera` plugin bằng `react-native-vision-camera` plugin (v4 có plugin riêng) +- Thêm `withEnableFrameProcessorsAndroid` để bật frame processor trên Android +```json +"plugins": [ + "react-native-vision-camera", + [ + "expo-camera", + { + "cameraPermission": "..." + } + ] +] +``` +Cần kiểm tra: v4 có expo plugin riêng (`withVisionCamera`) hay dùng thẳng tên package. + +### Bước 3: Cập nhật App.tsx +- Import `useCameraPermission` từ `react-native-vision-camera` thay vì `useCameraPermissions` từ `expo-camera` +- Props: `hasPermission`, `requestPermission` + +### Bước 4: Viết lại Viewfinder.tsx +Component mới dùng: +- `Camera` từ vision-camera với prop `frameProcessor={frameProcessor}` +- `useSkiaFrameProcessor` hook — worklet chạy native, mỗi frame: + 1. Tạo `Skia.Paint()` với `Skia.ColorFilter.MakeMatrix(colorMatrix)` + 2. Gọi `frame.render(paint)` — vẽ frame qua color filter +- `colorMatrix` là `SharedValue` từ react-native-reanimated (để worklet đọc được) +- Grain noise, frame border, GPS watermark: giữ nguyên overlay React Native / Skia Canvas trên top + +```tsx +const App = () => { + const frameProcessor = useSkiaFrameProcessor((frame) => { + 'worklet'; + const paint = Skia.Paint(); + paint.setColorFilter(Skia.ColorFilter.MakeMatrix(colorMatrixSV.value)); + frame.render(paint); + }, [colorMatrixSV]); + + return ( + + ); +}; +``` + +### Bước 5: Loại bỏ expo-camera khỏi code +- `CameraView` → `Camera` (vision-camera) +- Xoá imports không dùng +- Giữ `expo-camera` trong package.json (không gỡ, tránh lỗi autolinking) + +### Bước 6: Re-prebuild & build APK +```bash +rm -rf android +npx expo prebuild --platform android +echo "sdk.dir=/home/locpham/android-sdk" > android/local.properties +cd android && ./gradlew assembleDebug +``` +(Không chạy `clean` — tránh lỗi CMake codegen paths) + +## API đã xác nhận (v4.7.3) +| Export | Path | +|---|---| +| `Camera` | `react-native-vision-camera` | +| `useCameraDevice` | `react-native-vision-camera` | +| `useCameraPermission` | `react-native-vision-camera` | +| `useSkiaFrameProcessor` | `react-native-vision-camera` | +| `SkiaCameraCanvas` | `react-native-vision-camera` | +| Expo plugin (`withVisionCamera`) | `lib/module/expo-plugin/withVisionCamera` | + +## Rủi ro & Xử lý +| Rủi ro | Xử lý | +|---|---| +| `react-native-worklets-core` conflict | Đã có sẵn qua `react-native-reanimated` dependency | +| CameraX dependency (`NoClassDefFoundError`) | v4 dùng `CameraX` không phải Nitro — build an toàn hơn v5 | +| CMake codegen paths lỗi sau `clean` | Không dùng `./gradlew clean`, dùng `--rerun-tasks` nếu cần | +| Frame processor không chạy trên Android | Bật flag qua Expo plugin: `withEnableFrameProcessorsAndroid` | + +## Kiểm thử +1. Build APK thành công +2. Camera live view hiển thị với filter: + - B&W/monochrome → true grayscale + - Saturation, temp, tint, exposure thay đổi real-time +3. Grain noise, frame border, GPS watermark vẫn hoạt động +4. Library mode không bị ảnh hưởng diff --git a/CAMERA_PORT_ANDROID.md b/CAMERA_PORT_ANDROID.md new file mode 100644 index 0000000..d3c717b --- /dev/null +++ b/CAMERA_PORT_ANDROID.md @@ -0,0 +1,258 @@ +# Camera trực tiếp: chỉ port những gì web có mà Android chưa có + +Phạm vi tài liệu: **chỉ liệt kê delta web-only**. Mọi thứ Android đã có thì ghi nhận một dòng rồi bỏ qua, +không mô tả cách làm lại. + +- Web (tham chiếu): `docker/frontend/` — Vite/React/TS, CanvasKit WASM. Camera thêm ở `cb1839e` + + `6814b05`. +- Android (đích): `App.tsx` + `src/` ở gốc repo — Expo/RN, `react-native-vision-camera-skia` (worklet + + GPU texture). + +Quy ước trích dẫn: cột **Web** trỏ vào file trong `docker/frontend/`; cột **Android** trỏ vào `App.tsx` / +`src/` ở gốc repo. + +**Tóm tắt:** web-only đáng port chỉ có **3** việc — P1 (quan trắc FPS/độ phân giải), P2 (gate PRO ở lối +vào camera, *cần quyết định*), P3 (luồng "chụp rồi chỉnh"). Web-only còn lại hoặc không port được, hoặc +Android đã có bản tương đương. §6 ghi thêm phương án thay thế: đóng gói chính web studio thành app mobile. + +--- + +## 1. Đối chiếu tính năng — cái nào thực sự web-only + +| | **Web** | **Android** | Delta | +|---|---|---|---| +| Lối vào | Nút `OPEN CAMERA` + badge `PRO` (`ui/ImageStage.tsx:1443-1444`, `:2002-2006`) | Chế độ `mode === 'camera'` (`App.tsx:1675`, `:1978`) | khác chỗ đặt, không phải thiếu | +| Gate | **Khoá cả camera** sau PRO: `cameraSupported() ? () => (pro ? setShooting(true) : promptPro()) : undefined` (`App.tsx:3237-3241`), `pro = !!user?.verified` (`:522`) | Gate ở **lúc render look**: `proLookInUse` (`App.tsx:1269`), `promptPro` (`:1275-1285`). `CameraControls.tsx` không có `pro` nào | **WEB-ONLY → P2** | +| Số đo trên viewfinder | `{w}×{h} · {fps} fps` (`ui/CameraView.tsx:214-218`, `i18n/en.ts:40`) | Không hiển thị gì (nhịp tim `frameBeatSync` chỉ phục vụ watchdog) | **WEB-ONLY → P1** | +| Ảnh chụp đi đâu | Still **full cảm biến → studio**; look do studio áp, file ghi sau bằng export chung (`docker/frontend/src/App.tsx:902-918`) | Render look **ngay** → file + gallery + `pushPhoto`; **không mở editor** (`App.tsx:1476-1489`) | **WEB-ONLY → P3** | +| Xem trước khi chỉnh | Cùng `renderPhoto`, khác đích ra (`drawTo` so với `encodeToBytes`) | Cùng SkSL, chạy trong worklet | tương đương | +| Lens | Chỉ `facingMode user/environment` (`engine/camera.ts:12-14`) | Flip, zoom 1–10x, ultra-wide 0.5x qua module Camera2 ẩn (`Viewfinder.tsx:599-713`), RAW DNG (`App.tsx:337-340`) | Android hơn | +| Panel khi đang ngắm | Rail + cột PRESETS vẫn cạnh viewfinder (`.stage-slot`, `styles/app.css:723-750`) | Overlay dp + panel ngay dưới khung (`Viewfinder.tsx:2774-2819`) | tương đương | +| Chuỗi UI | i18n EN/VI, 9 khoá `cam.*` (`i18n/en.ts:36-44`, `i18n/vi.ts:45-53`) | Hardcode tiếng Anh (app không có hạ tầng i18n) | khác phạm vi, xem P6 | +| Chống treo phiên | không cần (rAF tự dừng/tiếp) | Watchdog remount sau ~6 s không có khung (`Viewfinder.tsx:1846-1900`) | Android hơn | + +--- + +## 2. Delta kỹ thuật theo lớp + +| Lớp | Web | Android | Kết luận | +|---|---|---|---| +| Nhận biết khả năng | `navigator.mediaDevices?.getUserMedia` + yêu cầu **secure context** (`engine/camera.ts:27-32`) | `useCameraPermission()` (`App.tsx:322`), manifest `CAMERA` | web-only, **không port** (không có khái niệm tương ứng) | +| Phân loại lỗi | `err.name === 'NotAllowedError'` → `cam.denied`, còn lại `cam.fail` (`CameraView.tsx:176-181`) | 3 alert riêng: `Permission Needed` / `Camera Not Ready` / `Capture Failed` (`App.tsx:1338`, `:1348`, `:1499`) | ngữ nghĩa đã tương đương | +| Mở phiên | `getUserMedia` → `