docs: align Markdown table columns in README

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
a1518 2026-07-01 00:53:21 -07:00
parent db572ee51d
commit 1b3a6d5a4a

327
README.md
View File

@ -107,6 +107,7 @@ import '@shopify/react-native-skia';
**几乎总是**因为 Metro 同时解析到多份 reanimated / skia / gesture-handler / fast-opencv / safe-area 等包。
**最佳实践**
- 直接复制 `example/metro.config.js` 里的 `singletonPackages` + extraNodeModules + blockList 写法
- 在你的 `index.js` 最顶部加入 gesture-handler → reanimated → skia 三个 import
- 重启 Metro (`--reset-cache`) + 重装 app
@ -141,16 +142,18 @@ import MaskSegmentCanvas, {
主要导出一览:
| 类别 | 名称 |
| ---- | ---- |
| 组件 | `MaskSegmentCanvas`default |
| Ref / Props 类型 | `MaskSegmentCanvasRef`、`MaskSegmentCanvasProps` |
| 会话 / 回调类型 | `MaskSegmentSession`、`PaintCallbackPayload`、`PaintedRegionRecord`、`SavePaintResult` |
| Watch 类型 | `MaskSegmentWatchState`、`MaskSegmentWatchDetail` |
| 配置类型 | `PipelineConfig`、`MaskSegmentConfig`、`PaintConfig`、`InteractionConfig` |
| 语义色 | `MASK_SEMANTIC_COLORS`、`BASEBOARD_SEMANTIC_NAME` |
| 工具 | `prewarmPngBgrCacheAsync`、`prewarmPngBgrCache` |
| 运行时 | `DEFAULT_*_CONFIG`、`getMaskSegmentRuntimeConfig`、`setMaskSegmentRuntimeConfig` |
| 类别 | 名称 |
| -------------- | ----------------------------------------------------------------------------------- |
| 组件 | `MaskSegmentCanvas`default |
| Ref / Props 类型 | `MaskSegmentCanvasRef`、`MaskSegmentCanvasProps` |
| 会话 / 回调类型 | `MaskSegmentSession`、`PaintCallbackPayload`、`PaintedRegionRecord`、`SavePaintResult` |
| Watch 类型 | `MaskSegmentWatchState`、`MaskSegmentWatchDetail` |
| 配置类型 | `PipelineConfig`、`MaskSegmentConfig`、`PaintConfig`、`InteractionConfig` |
| 语义色 | `MASK_SEMANTIC_COLORS`、`BASEBOARD_SEMANTIC_NAME` |
| 工具 | `prewarmPngBgrCacheAsync`、`prewarmPngBgrCache` |
| 运行时 | `DEFAULT_*_CONFIG`、`getMaskSegmentRuntimeConfig`、`setMaskSegmentRuntimeConfig` |
---
@ -384,16 +387,16 @@ export function PaintScreen() {
#### 示例里涉及的 state 说明
| state | 类型 | 用途 |
| ------------------ | -------------------------- | ---- |
| `imagePaths` | `{ origin, mask } \| null` | 业务侧解析后的本地/远程图片路径 |
| `pathsError` | `string` | 路径解析或 PNG 预热失败文案 |
| `watchState` | `MaskSegmentWatchState \| ''` | `onWatch` 上报的初始化阶段 |
| `isInteractive` | 派生 | `interactive``mask_paths_ready` 时为 true可开放操作 |
| `isOutlineReady` | 派生 | `mask_paths_ready` 时为 true轮播虚线已就绪 |
| `isCanvasLoading` | 派生 | 画布初始化阻塞 Loading不含 PNG 路径等待) |
| `errorMessage` | `string` | 由 `onError` 写入的分割/加载失败文案 |
| `sessionDraft` | `MaskSegmentSession \| null` | MMKV 等恢复的草稿 |
| state | 类型 | 用途 |
| ----------------- | ---------------------------- | ------------------------------------------------ |
| `imagePaths` | `{ origin, mask } | null` | 业务侧解析后的本地/远程图片路径 |
| `pathsError` | `string` | 路径解析或 PNG 预热失败文案 |
| `watchState` | `MaskSegmentWatchState | ''` | `onWatch` 上报的初始化阶段 |
| `isInteractive` | 派生 | `interactive``mask_paths_ready` 时为 true可开放操作 |
| `isOutlineReady` | 派生 | `mask_paths_ready` 时为 true轮播虚线已就绪 |
| `isCanvasLoading` | 派生 | 画布初始化阻塞 Loading不含 PNG 路径等待) |
| `errorMessage` | `string` | 由 `onError` 写入的分割/加载失败文案 |
| `sessionDraft` | `MaskSegmentSession | null` | MMKV 等恢复的草稿 |
#### 配置项怎么选
@ -447,22 +450,26 @@ const hasError = watchState === 'error';
### 图片与初始化
| 属性 | 类型 | 必填 | 默认 | 说明 |
| ---- | ---- | ---- | ---- | ---- |
| `originUrl` | `string` | 是* | — | 原图地址(`file://`、绝对路径或 `http(s)://` |
| `maskUrl` | `string` | 是* | — | 掩码图地址(语义色块图,建议与原图同尺寸) |
| `originImgPath` | `string` | — | — | **deprecated**,请用 `originUrl` |
| `maskImgPath` | `string` | — | — | **deprecated**,请用 `maskUrl` |
| `initialSession` | `MaskSegmentSession` | 否 | — | 从 MMKV 等恢复的草稿;分区就绪后自动 `loadSession` |
| `initialPaintColor` | `BgrColor` | 否 | — | **可选**。初始自定义笔刷色 `{ b, g, r }`;不传则默认无笔刷,需用户选色或 `ref.setPaintColor` |
| `initialPaintConfigJson` | `Record<string, unknown>` | 否 | — | **可选**。与 `initialPaintColor` 配套的笔刷配置,上色成功时随 `onPaintCallback` 回传 |
| 属性 | 类型 | 必填 | 默认 | 说明 |
| ------------------------ | ------------------------- | --- | --- | ----------------------------------------------------------------- |
| `originUrl` | `string` | 是* | — | 原图地址(`file://`、绝对路径或 `http(s)://` |
| `maskUrl` | `string` | 是* | — | 掩码图地址(语义色块图,建议与原图同尺寸) |
| `originImgPath` | `string` | — | — | **deprecated**,请用 `originUrl` |
| `maskImgPath` | `string` | — | — | **deprecated**,请用 `maskUrl` |
| `initialSession` | `MaskSegmentSession` | 否 | — | 从 MMKV 等恢复的草稿;分区就绪后自动 `loadSession` |
| `initialPaintColor` | `BgrColor` | 否 | — | **可选**。初始自定义笔刷色 `{ b, g, r }`;不传则默认无笔刷,需用户选色或 `ref.setPaintColor` |
| `initialPaintConfigJson` | `Record<string, unknown>` | 否 | — | **可选**。与 `initialPaintColor` 配套的笔刷配置,上色成功时随 `onPaintCallback` 回传 |
### 识别色与虚线(顶层便捷配置)
| 属性 | 类型 | 默认 | 说明 |
| ---- | ---- | ---- | ---- |
| `semanticColors` | `MaskSemanticColor[]` | `MASK_SEMANTIC_COLORS` | 掩码语义识别色,等同 `maskConfig.semanticColors` |
| `regionOutlineColor` | `string` | `rgba(20, 120, 235, 0.58)` | 分区虚线高亮色,等同 `paintConfig.regionOverlayFill` |
| 属性 | 类型 | 默认 | 说明 |
| -------------------- | --------------------- | -------------------------- | ------------------------------------------ |
| `semanticColors` | `MaskSemanticColor[]` | `MASK_SEMANTIC_COLORS` | 掩码语义识别色,等同 `maskConfig.semanticColors` |
| `regionOutlineColor` | `string` | `rgba(20, 120, 235, 0.58)` | 分区虚线高亮色,等同 `paintConfig.regionOverlayFill` |
顶层属性优先级高于嵌套 `maskConfig` / `paintConfig`
@ -480,72 +487,80 @@ const hasError = watchState === 'error';
### maskConfig
| 字段 | 类型 | 默认 | 说明 |
| ---- | ---- | ---- | ---- |
| `semanticColors` | `MaskSemanticColor[]` | 内置色表 | 掩码语义色(可被顶层 `semanticColors` 覆盖) |
| `blackThreshold` | `number` | `30` | BGR 最大值低于此值的像素视为黑色背景 |
| `maxRegionColors` | `number` | `6` | 最终保留的最大语义分区数 |
| `quantStep` | `number` | `64` | 踢脚线量化步长 |
| `baseboardMaxColorDist` | `number` | `42` | 踢脚线色距阈值 |
| `baseboardStripQuantKeys` | `string[]` | 内置键集 | 踢脚线条带量化键,格式 `"b,g,r"` |
| `wallQuantKeys` | `string[]` | 内置键集 | 墙面量化键 |
| `cabinetQuantKeys` | `string[]` | 内置键集 | 柜体量化键 |
| `secondarySemanticNames` | `string[]` | `garageDoor, roof, eave` | 次要语义名 |
| `secondaryMinPixelRatio` | `number` | `0.002` | 次要语义最小像素占比 |
| `junctionHRadiusPx` | `number` | `24` | 踢脚线交界水平半径 |
| `junctionVRadiusPx` | `number` | `2` | 踢脚线交界垂直半径 |
| `kickBridgeHalfWPx` | `number` | `6` | 踢脚线横向补缝半宽 |
| `baseboardJunctionRowMarginPx` | `number` | `1` | 踢脚线交界行边距 |
| `baseboardJunctionVReachPx` | `number` | `2` | 踢脚线交界纵向延伸 |
| `baseboardMinRunPx` | `number` | `2` | 蒙版条带最小 run 长度 |
| `splitWalls` | `boolean` | `false` | 在 wall 掩码内按纹理边界细分为 `wall-1`、`wall-2`… |
| `splitWallsMaxCount` | `number` | `8` | 墙壁子区最大数量 |
| `splitWallsMinAreaRatio` | `number` | `0.002` | 碎块最小面积比(相对 seg 总像素) |
| `splitWallsColorDistSq` | `number` | `1400` | 连通域色度均值距离平方阈值(墙内光影容忍,材质间更严) |
| `splitWallsChromaBlurRadius` | `number` | `5` | 预留:色度平滑半径 |
| `splitWallsNeutralChromaMax` | `number` | `14` | 白/灰墙低饱和判定半径;与有色墙强制分界 |
| 字段 | 类型 | 默认 | 说明 |
| ------------------------------ | --------------------- | ------------------------ | ------------------------------------- |
| `semanticColors` | `MaskSemanticColor[]` | 内置色表 | 掩码语义色(可被顶层 `semanticColors` 覆盖) |
| `blackThreshold` | `number` | `30` | BGR 最大值低于此值的像素视为黑色背景 |
| `maxRegionColors` | `number` | `6` | 最终保留的最大语义分区数 |
| `quantStep` | `number` | `64` | 踢脚线量化步长 |
| `baseboardMaxColorDist` | `number` | `42` | 踢脚线色距阈值 |
| `baseboardStripQuantKeys` | `string[]` | 内置键集 | 踢脚线条带量化键,格式 `"b,g,r"` |
| `wallQuantKeys` | `string[]` | 内置键集 | 墙面量化键 |
| `cabinetQuantKeys` | `string[]` | 内置键集 | 柜体量化键 |
| `secondarySemanticNames` | `string[]` | `garageDoor, roof, eave` | 次要语义名 |
| `secondaryMinPixelRatio` | `number` | `0.002` | 次要语义最小像素占比 |
| `junctionHRadiusPx` | `number` | `24` | 踢脚线交界水平半径 |
| `junctionVRadiusPx` | `number` | `2` | 踢脚线交界垂直半径 |
| `kickBridgeHalfWPx` | `number` | `6` | 踢脚线横向补缝半宽 |
| `baseboardJunctionRowMarginPx` | `number` | `1` | 踢脚线交界行边距 |
| `baseboardJunctionVReachPx` | `number` | `2` | 踢脚线交界纵向延伸 |
| `baseboardMinRunPx` | `number` | `2` | 蒙版条带最小 run 长度 |
| `splitWalls` | `boolean` | `false` | 在 wall 掩码内按纹理边界细分为 `wall-1`、`wall-2`… |
| `splitWallsMaxCount` | `number` | `8` | 墙壁子区最大数量 |
| `splitWallsMinAreaRatio` | `number` | `0.002` | 碎块最小面积比(相对 seg 总像素) |
| `splitWallsColorDistSq` | `number` | `1400` | 连通域色度均值距离平方阈值(墙内光影容忍,材质间更严) |
| `splitWallsChromaBlurRadius` | `number` | `5` | 预留:色度平滑半径 |
| `splitWallsNeutralChromaMax` | `number` | `14` | 白/灰墙低饱和判定半径;与有色墙强制分界 |
开启 `splitWalls` 后,原有单一 `wall` 区域会被替换为多个 `wall-N` 子区,各自独立上色与撤销。旧 Session 中 `regionName: 'wall'` 无法映射到新子区名,需重新上色。
### pipelineConfig
| 字段 | 类型 | 默认 | 说明 |
| ---- | ---- | ---- | ---- |
| `maxImageLongSide` | `number` | `720` | 分割 / pickMap / 工作区缩放最长边 |
| `paintFreqMaxLongSide` | `number` | `480` | OpenCV LAB 高低频最长边 |
| `originPreviewMaxLongSide` | `number` | `360` | 预览最长边(主路径走工作区分辨率) |
| `maskPathMaxLongSide` | `number` | `480` | 虚线轮廓降采样最长边 |
| `minContourArea` | `number` | `100` | 最小轮廓面积(随缩放同比缩放) |
| `contourApproxEpsilon` | `number` | `0.003` | 轮廓多边形逼近系数 |
| `maxRegions` | `number` | `500` | 分割阶段最大区域数上限 |
| 字段 | 类型 | 默认 | 说明 |
| -------------------------- | -------- | ------- | ----------------------- |
| `maxImageLongSide` | `number` | `720` | 分割 / pickMap / 工作区缩放最长边 |
| `paintFreqMaxLongSide` | `number` | `480` | OpenCV LAB 高低频最长边 |
| `originPreviewMaxLongSide` | `number` | `360` | 预览最长边(主路径走工作区分辨率) |
| `maskPathMaxLongSide` | `number` | `480` | 虚线轮廓降采样最长边 |
| `minContourArea` | `number` | `100` | 最小轮廓面积(随缩放同比缩放) |
| `contourApproxEpsilon` | `number` | `0.003` | 轮廓多边形逼近系数 |
| `maxRegions` | `number` | `500` | 分割阶段最大区域数上限 |
### paintConfig
| 字段 | 类型 | 默认 | 说明 |
| ---- | ---- | ---- | ---- |
| `palette` | `BgrColor[]` | 6 色内置盘 | 底部笔刷色条 |
| `colorBaseOpacity` | `number` | `0.88` | 底色不透明度 |
| `lLightOpacity` | `number` | `0.50` | L 通道叠加强度 |
| `textureOpacity` | `number` | `0.85` | 高频纹理叠加强度(纹理保留更强) |
| `lLowBlurKernel` | `number` | `7` | 低频高斯核(奇数) |
| `lLowContrast` | `number` | `1.15` | 低频对比度 |
| `lLowBrightness` | `number` | `0.9` | 低频亮度 |
| `lHighGain` | `number` | `1.22` | 高频增益 |
| `maskFeatherColor` | `number` | `1.6` | 上色边缘羽化(颜色)——软边 alpha 半径,像素 |
| `maskFeatherTexture` | `number` | `0.9` | 上色边缘羽化(纹理)——预留/辅助 |
| `regionOverlayFill` | `string` | `rgba(20,120,235,0.58)` | 虚线/高亮填充色 |
| `regionOutlineStrokeWidth` | `number` | `4` | 虚线描边宽度 |
| 字段 | 类型 | 默认 | 说明 |
| -------------------------- | ------------ | ----------------------- | -------------------------- |
| `palette` | `BgrColor[]` | 6 色内置盘 | 底部笔刷色条 |
| `colorBaseOpacity` | `number` | `0.88` | 底色不透明度 |
| `lLightOpacity` | `number` | `0.50` | L 通道叠加强度 |
| `textureOpacity` | `number` | `0.85` | 高频纹理叠加强度(纹理保留更强) |
| `lLowBlurKernel` | `number` | `7` | 低频高斯核(奇数) |
| `lLowContrast` | `number` | `1.15` | 低频对比度 |
| `lLowBrightness` | `number` | `0.9` | 低频亮度 |
| `lHighGain` | `number` | `1.22` | 高频增益 |
| `maskFeatherColor` | `number` | `1.6` | 上色边缘羽化(颜色)——软边 alpha 半径,像素 |
| `maskFeatherTexture` | `number` | `0.9` | 上色边缘羽化(纹理)——预留/辅助 |
| `regionOverlayFill` | `string` | `rgba(20,120,235,0.58)` | 虚线/高亮填充色 |
| `regionOutlineStrokeWidth` | `number` | `4` | 虚线描边宽度 |
### interactionConfig
| 字段 | 类型 | 默认 | 说明 |
| ---- | ---- | ---- | ---- |
| `pickMapSearchRadiusPx` | `number` | `14` | 点击 pickMap 搜索半径(像素) |
| `kickMaskPickRadiusPx` | `number` | `36` | 踢脚线掩码拾取半径 |
| `thinStripPadding` | `number` | `0.008` | 细条带(踢脚线)点击扩展比例 |
| `regionPadding` | `number` | `0.003` | 普通分区点击扩展比例 |
| `initRegionFlashMs` | `number` | `1000` | 初始化轮播每条虚线停留毫秒 |
| `enableInitRegionFlash` | `boolean` | `true` | 是否启用初始化轮播 |
| 字段 | 类型 | 默认 | 说明 |
| ----------------------- | --------- | ------- | ------------------- |
| `pickMapSearchRadiusPx` | `number` | `14` | 点击 pickMap 搜索半径(像素) |
| `kickMaskPickRadiusPx` | `number` | `36` | 踢脚线掩码拾取半径 |
| `thinStripPadding` | `number` | `0.008` | 细条带(踢脚线)点击扩展比例 |
| `regionPadding` | `number` | `0.003` | 普通分区点击扩展比例 |
| `initRegionFlashMs` | `number` | `1000` | 初始化轮播每条虚线停留毫秒 |
| `enableInitRegionFlash` | `boolean` | `true` | 是否启用初始化轮播 |
> 完整默认值常量:`DEFAULT_MASK_CONFIG`、`DEFAULT_PIPELINE_CONFIG`、`DEFAULT_PAINT_CONFIG`、`DEFAULT_INTERACTION_CONFIG`(自包入口导出)。
@ -573,11 +588,13 @@ const hasError = watchState === 'error';
### 回调
| 属性 | 签名 | 说明 |
| ---- | ---- | ---- |
| `onWatch` | `(state, durationMs, detail?) => void` | 初始化阶段回调;`durationMs` 自本次 `init` 起算 |
| `onPaintCallback` | `(payload: PaintCallbackPayload) => void` | 上色成功或未选笔刷时点击分区 |
| `onError` | `(message, error?) => void` | 分割或加载失败 |
| 属性 | 签名 | 说明 |
| ----------------- | ----------------------------------------- | ---------------------------------- |
| `onWatch` | `(state, durationMs, detail?) => void` | 初始化阶段回调;`durationMs` 自本次 `init` 起算 |
| `onPaintCallback` | `(payload: PaintCallbackPayload) => void` | 上色成功或未选笔刷时点击分区 |
| `onError` | `(message, error?) => void` | 分割或加载失败 |
`PaintCallbackPayload`(判别联合,`payload.kind` 区分):
@ -614,12 +631,14 @@ onPaintCallback={payload => {
`onWatch``detail``MaskSegmentWatchDetail`
| 字段 | 类型 | 说明 |
| ---- | ---- | ---- |
| `regionCount` | `number` | 当前有效分区数 |
| `maskPathsReady` | `boolean` | 轮廓 Skia 路径是否就绪 |
| 字段 | 类型 | 说明 |
| ----------------- | --------- | ----------------- |
| `regionCount` | `number` | 当前有效分区数 |
| `maskPathsReady` | `boolean` | 轮廓 Skia 路径是否就绪 |
| `freqLayersReady` | `boolean` | 高低频 Shader 纹理是否就绪 |
| `errorMessage` | `string` | `error` 状态下的失败说明 |
| `errorMessage` | `string` | `error` 状态下的失败说明 |
#### onWatch 状态流转
@ -643,19 +662,21 @@ init
通过 `ref` 调用(类型 `MaskSegmentCanvasRef`
| 方法 | 签名 | 说明 |
| ---- | ---- | ---- |
| `reset` | `() => void` | 撤销上一步上色(按 `paintHistory` |
| `swap` | `(showOrigin?: boolean) => void` | 对比原图;不传参 toggle`true`/`false` 显式开关 |
| `save` | `(options?) => Promise<SavePaintResult>` | 合成并保存 PNG`options.destDir` 可选输出目录 |
| `session` | `() => MaskSegmentSession` | 导出可 JSON 序列化会话(存 MMKV |
| `loadSession` | `(session) => void` | 恢复上色状态(也可通过 `initialSession` |
| `setPaintColor` | `(color, configJson?) => void` | 设置当前笔刷色,清空底部色条选中 |
| `setMaskConfig` | `(config) => void` | 运行时更新掩码配置并**重新分割** |
| `clearAllPaint` | `() => void` | 清空全部上色记录 |
| `resegment` | `() => Promise<void>` | 清空 PNG 缓存并重新分割 |
| `getRegions` | `() => SegmentRegion[]` | 当前分区列表快照 |
| `getPaintedRegions` | `() => PaintedRegionRecord[]` | 当前上色记录快照 |
| 方法 | 签名 | 说明 |
| ------------------- | ---------------------------------------- | ------------------------------------- |
| `reset` | `() => void` | 撤销上一步上色(按 `paintHistory` |
| `swap` | `(showOrigin?: boolean) => void` | 对比原图;不传参 toggle`true`/`false` 显式开关 |
| `save` | `(options?) => Promise<SavePaintResult>` | 合成并保存 PNG`options.destDir` 可选输出目录 |
| `session` | `() => MaskSegmentSession` | 导出可 JSON 序列化会话(存 MMKV |
| `loadSession` | `(session) => void` | 恢复上色状态(也可通过 `initialSession` |
| `setPaintColor` | `(color, configJson?) => void` | 设置当前笔刷色,清空底部色条选中 |
| `setMaskConfig` | `(config) => void` | 运行时更新掩码配置并**重新分割** |
| `clearAllPaint` | `() => void` | 清空全部上色记录 |
| `resegment` | `() => Promise<void>` | 清空 PNG 缓存并重新分割 |
| `getRegions` | `() => SegmentRegion[]` | 当前分区列表快照 |
| `getPaintedRegions` | `() => PaintedRegionRecord[]` | 当前上色记录快照 |
`SavePaintResult``{ filePath, width, height, paintedCount, previewPath? }`
@ -832,44 +853,52 @@ MaskSegmentApp/ # 仓库根目录npm 包 react-n
Demo 在挂载画布前调用 `prewarmPngBgrCacheAsync([origin, mask])`PNG 解码命中内存缓存。典型日志:
| 阶段 | watchState | 约耗时 | 说明 |
| ---- | ---------- | ------ | ---- |
| 掩码对齐 | `mask_aligned` | ~160ms | 掩码缩放到分割工作分辨率 |
| 分区完成 | `regions_ready` / `mask_sampled` | ~320ms | 布局扫描 + 踢脚线 + pickMap |
| **可交互** | **`interactive`** | **~320450ms** | 可点击选区、选色、Shader 上色 |
| 轮廓就绪 | `mask_paths_ready` | ~430550ms | 比 `interactive`**~100ms**,轮播虚线可显示 |
| 阶段 | watchState | 约耗时 | 说明 |
| ------- | -------------------------------- | -------------- | ------------------------------------ |
| 掩码对齐 | `mask_aligned` | ~160ms | 掩码缩放到分割工作分辨率 |
| 分区完成 | `regions_ready` / `mask_sampled` | ~320ms | 布局扫描 + 踢脚线 + pickMap |
| **可交互** | `**interactive`** | **~320450ms** | 可点击选区、选色、Shader 上色 |
| 轮廓就绪 | `mask_paths_ready` | ~430550ms | 比 `interactive`**~100ms**,轮播虚线可显示 |
`interactive` **不等待**轮廓路径;`mask_paths_ready` 仅影响初始化轮播虚线与可选 UI 提示。
同图各子步骤(`__DEV__` 日志,默认 pipeline量级
| 子步骤 | 约耗时 | 工作分辨率 |
| ------ | ------ | ---------- |
| OpenCV LAB 高低频 | ~1040ms | 270×480 |
| 高低频 Skia 纹理 | ~2030ms | 同上 |
| 子步骤 | 约耗时 | 工作分辨率 |
| ----------------- | --------- | ------------------------------ |
| OpenCV LAB 高低频 | ~1040ms | 270×480 |
| 高低频 Skia 纹理 | ~2030ms | 同上 |
| 布局扫描 + 踢脚线 + 点击查表 | ~90120ms | 405×7201080p 缩至 longSide 720 |
| 全量轮廓路径(异步,不阻塞交互) | ~80150ms | 270×480 |
| 全量轮廓路径(异步,不阻塞交互) | ~80150ms | 270×480 |
### 分辨率与 `pipelineConfig` 的关系
计算密集型步骤被 **最长边上限** 截断,**不随 4K/8K 原图线性放大****PNG 全图解码**仍随像素量线性增长。
| 步骤 | 配置项 | 1080×1920 实际处理尺寸 | 随原图像素增长 |
| ---- | ------ | ---------------------- | -------------- |
| PNG 解码 | — | 1080×1920 × 2 张 | **是** |
| 掩码分割 / pickMap | `maxImageLongSide: 720` | ~405×720 | **否**(长边 >720 时固定) |
| Shader 高低频 | `paintFreqMaxLongSide: 480` | ~270×480 | **否** |
| 工作区 Skia 原图 | 同 `maxImageLongSide` | ~405×720 | **否** |
| 虚线轮廓 | `maskPathMaxLongSide: 480` | ~270×480 | **否**(不阻塞 `interactive` |
| 步骤 | 配置项 | 1080×1920 实际处理尺寸 | 随原图像素增长 |
| -------------- | --------------------------- | ---------------- | ------------------------ |
| PNG 解码 | — | 1080×1920 × 2 张 | **是** |
| 掩码分割 / pickMap | `maxImageLongSide: 720` | ~405×720 | **否**(长边 >720 时固定) |
| Shader 高低频 | `paintFreqMaxLongSide: 480` | ~270×480 | **否** |
| 工作区 Skia 原图 | 同 `maxImageLongSide` | ~405×720 | **否** |
| 虚线轮廓 | `maskPathMaxLongSide: 480` | ~270×480 | **否**(不阻塞 `interactive` |
### `interactive` 预估(默认 pipeline
| 原图规格 | 相对 1080p 像素 | 有 PNG 预热 | 冷启动(无预热) |
| -------- | --------------- | ----------- | ---------------- |
| 1080×1920 | 1× | **320450ms** | **450700ms** |
| 1440×2560 (2K) | ~1.8× | **400550ms** | **600900ms** |
| 3840×2160 (4K) | ~4× | **500750ms** | **8001200ms** |
| 7680×4320 (8K) | ~16× | **0.81.5s** | **1.53s+** |
| 原图规格 | 相对 1080p 像素 | 有 PNG 预热 | 冷启动(无预热) |
| -------------- | ----------- | ------------- | -------------- |
| 1080×1920 | 1× | **320450ms** | **450700ms** |
| 1440×2560 (2K) | ~1.8× | **400550ms** | **600900ms** |
| 3840×2160 (4K) | ~4× | **500750ms** | **8001200ms** |
| 7680×4320 (8K) | ~16× | **0.81.5s** | **1.53s+** |
> **300ms 内可交互**:在 1080p + 预热 + 默认 pipeline + 中高端机上**接近但偏乐观**;不宜作为全机型 SLA。
@ -877,11 +906,13 @@ Demo 在挂载画布前调用 `prewarmPngBgrCacheAsync([origin, mask])`PNG
相对上述开发环境 ~320ms 的量级:
| 档位 | 相对倍数 | 有预热 `interactive` | 冷启动 |
| ---- | -------- | -------------------- | ------ |
| 旗舰 iOS / 新旗舰 Android | 0.81.2× | 300450ms | 500800ms |
| 中端 Android | 1.52.5× | 500800ms | 700ms1.2s |
| 低端 Android4GB、老 U | 2.54× | 800ms1.3s | 12s+ |
| 档位 | 相对倍数 | 有预热 `interactive` | 冷启动 |
| -------------------- | -------- | ----------------- | ---------- |
| 旗舰 iOS / 新旗舰 Android | 0.81.2× | 300450ms | 500800ms |
| 中端 Android | 1.52.5× | 500800ms | 700ms1.2s |
| 低端 Android4GB、老 U | 2.54× | 800ms1.3s | 12s+ |
Android 额外开销主要来自JS ↔ OpenCV bridge、内存带宽/GC、Skia 纹理上传。
@ -889,10 +920,12 @@ Android 额外开销主要来自JS ↔ OpenCV bridge、内存带宽/GC、Skia
若将 `pipelineConfig.maxImageLongSide` 设为 **1280**(高于默认 720分割工作区约 **720×1280**,像素约为 720 档的 **3×**
| 场景 | 默认 720 | 改为 1280 |
| ---- | -------- | --------- |
| 场景 | 默认 720 | 改为 1280 |
| ------------------------ | ---------- | ------------- |
| 1080p `interactive`(中端机) | ~320800ms | **500ms1s+** |
| 分割 / pickMap 耗时 | ~90120ms | ~250350ms |
| 分割 / pickMap 耗时 | ~90120ms | ~250350ms |
更高精度换更长初始化;若目标仍是 **<500ms 可交互**建议维持默认 **720**必要时降至 **640**
@ -907,10 +940,10 @@ await prewarmPngBgrCacheAsync([originPath, maskPath]);
// 再挂载 MaskSegmentCanvas
```
2. **Loading 时机**:阻塞式 Loading 在 `interactive` 关闭;「轮廓准备中」可选监听 `mask_paths_ready`
3. **大图 / 低端机**:保持默认 `maxImageLongSide: 720`;可再将 `paintFreqMaxLongSide` 降至 **360**
4. **4K 素材**:业务侧先下采样再传入,或接受 **0.81.5s** 量级的 `interactive`(预热后)。
5. **观测**:开发环境关注 Metro 中 `[MaskSegment]`、`[⏱ ...]` 与 `onWatch``durationMs`
1. **Loading 时机**:阻塞式 Loading 在 `interactive` 关闭;「轮廓准备中」可选监听 `mask_paths_ready`
2. **大图 / 低端机**:保持默认 `maxImageLongSide: 720`;可再将 `paintFreqMaxLongSide` 降至 **360**
3. **4K 素材**:业务侧先下采样再传入,或接受 **0.81.5s** 量级的 `interactive`(预热后)。
4. **观测**:开发环境关注 Metro 中 `[MaskSegment]`、`[⏱ ...]` 与 `onWatch``durationMs`
---