react-native-mask-segment-canvas 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +904 -0
  2. package/dist/components/MaskSegmentCanvas.d.ts +6 -0
  3. package/dist/components/MaskSegmentCanvas.d.ts.map +1 -0
  4. package/dist/components/MaskSegmentCanvas.js +2012 -0
  5. package/dist/components/MaskSegmentCanvas.js.map +1 -0
  6. package/dist/components/MaskSegmentCanvas.types.d.ts +189 -0
  7. package/dist/components/MaskSegmentCanvas.types.d.ts.map +1 -0
  8. package/dist/components/MaskSegmentCanvas.types.js +2 -0
  9. package/dist/components/MaskSegmentCanvas.types.js.map +1 -0
  10. package/dist/index.d.ts +6 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +5 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/shaders/regionPaint.sksl.d.ts +3 -0
  15. package/dist/shaders/regionPaint.sksl.d.ts.map +1 -0
  16. package/dist/shaders/regionPaint.sksl.js +72 -0
  17. package/dist/shaders/regionPaint.sksl.js.map +1 -0
  18. package/dist/utils/compositePaintedImage.d.ts +44 -0
  19. package/dist/utils/compositePaintedImage.d.ts.map +1 -0
  20. package/dist/utils/compositePaintedImage.js +146 -0
  21. package/dist/utils/compositePaintedImage.js.map +1 -0
  22. package/dist/utils/exportUtils.d.ts +20 -0
  23. package/dist/utils/exportUtils.d.ts.map +1 -0
  24. package/dist/utils/exportUtils.js +32 -0
  25. package/dist/utils/exportUtils.js.map +1 -0
  26. package/dist/utils/freqLayerPrep.d.ts +23 -0
  27. package/dist/utils/freqLayerPrep.d.ts.map +1 -0
  28. package/dist/utils/freqLayerPrep.js +168 -0
  29. package/dist/utils/freqLayerPrep.js.map +1 -0
  30. package/dist/utils/maskSegmentRuntime.d.ts +43 -0
  31. package/dist/utils/maskSegmentRuntime.d.ts.map +1 -0
  32. package/dist/utils/maskSegmentRuntime.js +181 -0
  33. package/dist/utils/maskSegmentRuntime.js.map +1 -0
  34. package/dist/utils/maskSegmentation.d.ts +133 -0
  35. package/dist/utils/maskSegmentation.d.ts.map +1 -0
  36. package/dist/utils/maskSegmentation.js +1600 -0
  37. package/dist/utils/maskSegmentation.js.map +1 -0
  38. package/dist/utils/maskSemanticPalette.d.ts +31 -0
  39. package/dist/utils/maskSemanticPalette.d.ts.map +1 -0
  40. package/dist/utils/maskSemanticPalette.js +125 -0
  41. package/dist/utils/maskSemanticPalette.js.map +1 -0
  42. package/dist/utils/opencvAdapter.d.ts +116 -0
  43. package/dist/utils/opencvAdapter.d.ts.map +1 -0
  44. package/dist/utils/opencvAdapter.js +353 -0
  45. package/dist/utils/opencvAdapter.js.map +1 -0
  46. package/dist/utils/paintColorMapTexture.d.ts +5 -0
  47. package/dist/utils/paintColorMapTexture.d.ts.map +1 -0
  48. package/dist/utils/paintColorMapTexture.js +203 -0
  49. package/dist/utils/paintColorMapTexture.js.map +1 -0
  50. package/dist/utils/paintShaderRuntime.d.ts +40 -0
  51. package/dist/utils/paintShaderRuntime.d.ts.map +1 -0
  52. package/dist/utils/paintShaderRuntime.js +76 -0
  53. package/dist/utils/paintShaderRuntime.js.map +1 -0
  54. package/dist/utils/pickMapTexture.d.ts +4 -0
  55. package/dist/utils/pickMapTexture.d.ts.map +1 -0
  56. package/dist/utils/pickMapTexture.js +24 -0
  57. package/dist/utils/pickMapTexture.js.map +1 -0
  58. package/dist/utils/pngImage.d.ts +49 -0
  59. package/dist/utils/pngImage.d.ts.map +1 -0
  60. package/dist/utils/pngImage.js +438 -0
  61. package/dist/utils/pngImage.js.map +1 -0
  62. package/dist/utils/resolveAssetPath.d.ts +3 -0
  63. package/dist/utils/resolveAssetPath.d.ts.map +1 -0
  64. package/dist/utils/resolveAssetPath.js +56 -0
  65. package/dist/utils/resolveAssetPath.js.map +1 -0
  66. package/dist/utils/resolveImageUrl.d.ts +3 -0
  67. package/dist/utils/resolveImageUrl.d.ts.map +1 -0
  68. package/dist/utils/resolveImageUrl.js +51 -0
  69. package/dist/utils/resolveImageUrl.js.map +1 -0
  70. package/dist/utils/skiaImage.d.ts +4 -0
  71. package/dist/utils/skiaImage.d.ts.map +1 -0
  72. package/dist/utils/skiaImage.js +12 -0
  73. package/dist/utils/skiaImage.js.map +1 -0
  74. package/package.json +100 -0
  75. package/patches/react-native-fast-opencv+0.4.8.patch +122 -0
  76. package/src/components/MaskSegmentCanvas.tsx +2832 -0
  77. package/src/components/MaskSegmentCanvas.types.ts +216 -0
  78. package/src/globals.d.ts +19 -0
  79. package/src/index.ts +45 -0
  80. package/src/shaders/regionPaint.sksl.ts +71 -0
  81. package/src/upng-js.d.ts +33 -0
  82. package/src/utils/compositePaintedImage.ts +201 -0
  83. package/src/utils/exportUtils.ts +40 -0
  84. package/src/utils/freqLayerPrep.ts +267 -0
  85. package/src/utils/maskSegmentRuntime.ts +257 -0
  86. package/src/utils/maskSegmentation.ts +2294 -0
  87. package/src/utils/maskSemanticPalette.ts +187 -0
  88. package/src/utils/opencvAdapter.ts +539 -0
  89. package/src/utils/paintColorMapTexture.ts +239 -0
  90. package/src/utils/paintShaderRuntime.tsx +150 -0
  91. package/src/utils/pickMapTexture.ts +37 -0
  92. package/src/utils/pngImage.ts +591 -0
  93. package/src/utils/resolveAssetPath.ts +64 -0
  94. package/src/utils/resolveImageUrl.ts +63 -0
  95. package/src/utils/skiaImage.ts +25 -0
@@ -0,0 +1,267 @@
1
+ import cv, { type WrappedMat } from './opencvAdapter';
2
+ import { rgbaBufferToSkiaImage } from './skiaImage';
3
+ import type { SkImage } from '@shopify/react-native-skia';
4
+ import { getMaskSegmentRuntimeConfig } from './maskSegmentRuntime';
5
+
6
+ export type FreqLayerImages = {
7
+ lowFreqImage: SkImage;
8
+ highFreqImage: SkImage;
9
+ };
10
+
11
+ export type PaintResourceBatch = {
12
+ originImage: SkImage;
13
+ layers: FreqLayerImages;
14
+ };
15
+
16
+ /** OpenCV 8-bit Lab L 通道(BGR 输入,供单测与近似对照) */
17
+ export function bgrToLabL(b: number, g: number, r: number): number {
18
+ let rf = r / 255;
19
+ let gf = g / 255;
20
+ let bf = b / 255;
21
+ rf = rf > 0.04045 ? Math.pow((rf + 0.055) / 1.055, 2.4) : rf / 12.92;
22
+ gf = gf > 0.04045 ? Math.pow((gf + 0.055) / 1.055, 2.4) : gf / 12.92;
23
+ bf = bf > 0.04045 ? Math.pow((bf + 0.055) / 1.055, 2.4) : bf / 12.92;
24
+
25
+ const x = rf * 0.412453 + gf * 0.35758 + bf * 0.180423;
26
+ const y = rf * 0.212671 + gf * 0.71516 + bf * 0.072169;
27
+ const z = rf * 0.019334 + gf * 0.119193 + bf * 0.950227;
28
+
29
+ const xn = 0.950456;
30
+ const yn = 1;
31
+ const zn = 1.088754;
32
+ const delta = 6 / 29;
33
+ const delta3 = delta * delta * delta;
34
+
35
+ let fx = x / xn;
36
+ let fy = y / yn;
37
+ let fz = z / zn;
38
+ fx = fx > delta3 ? Math.cbrt(fx) : fx / (3 * delta * delta) + 4 / 29;
39
+ fy = fy > delta3 ? Math.cbrt(fy) : fy / (3 * delta * delta) + 4 / 29;
40
+ fz = fz > delta3 ? Math.cbrt(fz) : fz / (3 * delta * delta) + 4 / 29;
41
+
42
+ const L = fy * 116 - 16;
43
+ return Math.max(0, Math.min(255, Math.round((L * 255) / 100)));
44
+ }
45
+
46
+ export function bgrBufferToRgbaBuffer(
47
+ bgr: Uint8Array,
48
+ cols: number,
49
+ rows: number,
50
+ ): Uint8Array {
51
+ const pixelCount = cols * rows;
52
+ const rgba = new Uint8Array(pixelCount * 4);
53
+ for (let i = 0; i < pixelCount; i++) {
54
+ const s = i * 3;
55
+ const d = i * 4;
56
+ rgba[d] = bgr[s + 2];
57
+ rgba[d + 1] = bgr[s + 1];
58
+ rgba[d + 2] = bgr[s];
59
+ rgba[d + 3] = 255;
60
+ }
61
+ return rgba;
62
+ }
63
+
64
+ export function releaseFreqLayerImages(layers: FreqLayerImages | null) {
65
+ layers?.lowFreqImage.dispose();
66
+ layers?.highFreqImage.dispose();
67
+ }
68
+
69
+ /** 16-bit 有符号差分 → 8-bit 高频层(detail * gain + 128) */
70
+ async function buildHighFreqMatNative(
71
+ lMat: WrappedMat,
72
+ lLowMat: WrappedMat,
73
+ cols: number,
74
+ rows: number,
75
+ gain: number,
76
+ ): Promise<WrappedMat> {
77
+ const l16 = cv.createMat(cols, rows, 1);
78
+ const lLow16 = cv.createMat(cols, rows, 1);
79
+ const diff16 = cv.createMat(cols, rows, 1);
80
+ const high8 = cv.createMat(cols, rows, 1);
81
+ try {
82
+ cv.convertTo(lMat, l16, cv.CV_16SC1);
83
+ cv.convertTo(lLowMat, lLow16, cv.CV_16SC1);
84
+ await cv.subtract(l16, lLow16, diff16);
85
+ await cv.addWeighted(diff16, gain, null, 0, 128, diff16);
86
+ cv.convertTo(diff16, high8, cv.CV_8UC1);
87
+ return high8;
88
+ } finally {
89
+ l16.release();
90
+ lLow16.release();
91
+ diff16.release();
92
+ }
93
+ }
94
+
95
+ async function downscaleMatForFreq(
96
+ workMat: WrappedMat,
97
+ cols: number,
98
+ rows: number,
99
+ ): Promise<{
100
+ mat: WrappedMat;
101
+ cols: number;
102
+ rows: number;
103
+ owned: boolean;
104
+ }> {
105
+ const maxLongSide = getMaskSegmentRuntimeConfig().pipeline.paintFreqMaxLongSide;
106
+ const longSide = Math.max(cols, rows);
107
+ if (longSide <= maxLongSide) {
108
+ return { mat: workMat, cols, rows, owned: false };
109
+ }
110
+
111
+ const scale = maxLongSide / longSide;
112
+ const freqCols = Math.floor(cols * scale);
113
+ const freqRows = Math.floor(rows * scale);
114
+ const resized = cv.createMat(freqCols, freqRows, 3);
115
+ await cv.resize(
116
+ workMat,
117
+ resized,
118
+ { width: freqCols, height: freqRows },
119
+ cv.INTER_LINEAR,
120
+ );
121
+ return { mat: resized, cols: freqCols, rows: freqRows, owned: true };
122
+ }
123
+
124
+ /** 复用已上传的 BGR Mat,避免重复 bufferToMat + JS↔原生往返 */
125
+ export async function prepareFreqLayersFromWorkMat(
126
+ workMat: WrappedMat,
127
+ cols: number,
128
+ rows: number,
129
+ ): Promise<FreqLayerImages | null> {
130
+ const paintCfg = getMaskSegmentRuntimeConfig().paint;
131
+ const blurStart = __DEV__ ? performance.now() : 0;
132
+ const scaled = await downscaleMatForFreq(workMat, cols, rows);
133
+ const freqMat = scaled.mat;
134
+ const freqCols = scaled.cols;
135
+ const freqRows = scaled.rows;
136
+ let labMat: WrappedMat | null = null;
137
+ let lMat: WrappedMat | null = null;
138
+ let lLowMat: WrappedMat | null = null;
139
+ let lHighMat: WrappedMat | null = null;
140
+
141
+ try {
142
+ labMat = cv.cvtColorBgr(freqMat, cv.COLOR_BGR2Lab);
143
+ lMat = cv.createMat(freqCols, freqRows, 1);
144
+ cv.extractChannel(labMat, lMat, 0);
145
+ labMat.release();
146
+ labMat = null;
147
+
148
+ lLowMat = cv.createMat(freqCols, freqRows, 1);
149
+ const kernel = paintCfg.lLowBlurKernel;
150
+ await cv.GaussianBlur(
151
+ lMat,
152
+ lLowMat,
153
+ { width: kernel, height: kernel },
154
+ 0,
155
+ );
156
+
157
+ lHighMat = await buildHighFreqMatNative(
158
+ lMat,
159
+ lLowMat,
160
+ freqCols,
161
+ freqRows,
162
+ paintCfg.lHighGain,
163
+ );
164
+
165
+ await cv.addWeighted(
166
+ lLowMat,
167
+ paintCfg.lLowContrast,
168
+ null,
169
+ 0,
170
+ 128 * (1 - paintCfg.lLowContrast),
171
+ lLowMat,
172
+ );
173
+ await cv.addWeighted(
174
+ lLowMat,
175
+ paintCfg.lLowBrightness,
176
+ null,
177
+ 0,
178
+ 128 * (1 - paintCfg.lLowBrightness),
179
+ lLowMat,
180
+ );
181
+
182
+
183
+ const lowFreqImage = cv.grayMatToSkiaImage(lLowMat);
184
+ const highFreqImage = cv.grayMatToSkiaImage(lHighMat);
185
+
186
+ if (!lowFreqImage || !highFreqImage) {
187
+ lowFreqImage?.dispose();
188
+ highFreqImage?.dispose();
189
+ return null;
190
+ }
191
+
192
+ return { lowFreqImage, highFreqImage };
193
+ } finally {
194
+ if (scaled.owned) {
195
+ freqMat.release();
196
+ }
197
+ labMat?.release();
198
+ lMat?.release();
199
+ lLowMat?.release();
200
+ lHighMat?.release();
201
+ }
202
+ }
203
+
204
+ /** 单次 Mat 上传 → 高低频 + 原图 Skia(并行,高低频先就绪时可回调) */
205
+ export async function preparePaintResourcesFromWorkBuffer(
206
+ bgrBuffer: Uint8Array,
207
+ cols: number,
208
+ rows: number,
209
+ onFreqLayersReady?: (layers: FreqLayerImages) => void,
210
+ ): Promise<PaintResourceBatch | null> {
211
+ const pixelCount = cols * rows;
212
+ if (bgrBuffer.length !== pixelCount * 3) {
213
+ return null;
214
+ }
215
+
216
+ const prepStart = __DEV__ ? performance.now() : 0;
217
+ const workMat = cv.bgrBufferToMat(bgrBuffer, cols, rows);
218
+ try {
219
+ const originPromise = cv.matToSkiaImage(workMat);
220
+ const freqPromise = prepareFreqLayersFromWorkMat(workMat, cols, rows);
221
+
222
+ const layers = await freqPromise;
223
+ if (!layers) {
224
+ const originImage = await originPromise;
225
+ originImage?.dispose();
226
+ return null;
227
+ }
228
+ onFreqLayersReady?.(layers);
229
+
230
+ const originImage = await originPromise;
231
+ if (!originImage) {
232
+ releaseFreqLayerImages(layers);
233
+ return null;
234
+ }
235
+
236
+ return { originImage, layers };
237
+ } finally {
238
+ workMat.release();
239
+ }
240
+ }
241
+
242
+ /** @deprecated 测试兼容;生产路径请用 preparePaintResourcesFromWorkBuffer */
243
+ export async function prepareFreqLayersFromBgrBuffer(
244
+ bgrBuffer: Uint8Array,
245
+ cols: number,
246
+ rows: number,
247
+ ): Promise<FreqLayerImages | null> {
248
+ const pixelCount = cols * rows;
249
+ if (bgrBuffer.length !== pixelCount * 3) {
250
+ return null;
251
+ }
252
+ const workMat = cv.bgrBufferToMat(bgrBuffer, cols, rows);
253
+ try {
254
+ return await prepareFreqLayersFromWorkMat(workMat, cols, rows);
255
+ } finally {
256
+ workMat.release();
257
+ }
258
+ }
259
+
260
+ /** 原图 BGR → Skia RGBA(OpenCV cvtColor,与 freq 并行) */
261
+ export async function originBgrBufferToSkiaImage(
262
+ bgrBuffer: Uint8Array,
263
+ cols: number,
264
+ rows: number,
265
+ ): Promise<SkImage | null> {
266
+ return cv.bgrBufferToSkiaImage(bgrBuffer, cols, rows);
267
+ }
@@ -0,0 +1,257 @@
1
+ import {
2
+ BASEBOARD_STRIP_QUANT_KEYS,
3
+ CABINET_QUANT_KEYS,
4
+ MASK_SEMANTIC_COLORS,
5
+ WALL_QUANT_KEYS,
6
+ type MaskSemanticColor,
7
+ } from './maskSemanticPalette';
8
+ import type {
9
+ InteractionConfig,
10
+ MaskSegmentConfig,
11
+ PaintConfig,
12
+ PipelineConfig,
13
+ PipelinePreset,
14
+ BgrColor,
15
+ } from '../components/MaskSegmentCanvas.types';
16
+ /** high */
17
+ export const PIPELINE_HIGH: Required<PipelineConfig> = {
18
+ maxImageLongSide: 1440,
19
+ paintFreqMaxLongSide: 960,
20
+ originPreviewMaxLongSide: 720,
21
+ maskPathMaxLongSide: 960,
22
+ minContourArea: 50,
23
+ contourApproxEpsilon: 0.002,
24
+ maxRegions: 800,
25
+ };
26
+
27
+ /** middle */
28
+ export const PIPELINE_MEDIUM: Required<PipelineConfig> = {
29
+ maxImageLongSide: 720,
30
+ paintFreqMaxLongSide: 480,
31
+ originPreviewMaxLongSide: 360,
32
+ maskPathMaxLongSide: 480,
33
+ minContourArea: 100,
34
+ contourApproxEpsilon: 0.003,
35
+ maxRegions: 500,
36
+ };
37
+
38
+ /** low */
39
+ export const PIPELINE_LOW: Required<PipelineConfig> = {
40
+ maxImageLongSide: 360,
41
+ paintFreqMaxLongSide: 240,
42
+ originPreviewMaxLongSide: 180,
43
+ maskPathMaxLongSide: 240,
44
+ minContourArea: 200,
45
+ contourApproxEpsilon: 0.005,
46
+ maxRegions: 300,
47
+ };
48
+ export const PIPELINE_PRESETS: Record<PipelinePreset, Required<PipelineConfig>> = {
49
+ high: PIPELINE_HIGH,
50
+ medium: PIPELINE_MEDIUM,
51
+ low: PIPELINE_LOW,
52
+ };
53
+
54
+ export const DEFAULT_PIPELINE_CONFIG: Required<PipelineConfig> = PIPELINE_HIGH;
55
+
56
+ export function resolvePipelineConfig(
57
+ preset?: PipelinePreset,
58
+ overrides?: PipelineConfig,
59
+ ): Required<PipelineConfig> {
60
+ const base =
61
+ preset != null ? PIPELINE_PRESETS[preset] : DEFAULT_PIPELINE_CONFIG;
62
+ return { ...base, ...overrides };
63
+ }
64
+
65
+ const DEFAULT_PAINT_PALETTE: BgrColor[] = [
66
+ { b: 138, g: 126, r: 110 },
67
+ { b: 92, g: 124, r: 86 },
68
+ { b: 70, g: 80, r: 158 },
69
+ { b: 54, g: 134, r: 182 },
70
+ { b: 128, g: 98, r: 142 },
71
+ { b: 76, g: 120, r: 138 },
72
+ ];
73
+
74
+ export const DEFAULT_PAINT_CONFIG: Required<PaintConfig> = {
75
+ palette: DEFAULT_PAINT_PALETTE,
76
+ // Optimized coloring: slightly stronger base color fidelity while preserving natural lighting.
77
+ colorBaseOpacity: 0.88,
78
+ lLightOpacity: 0.50,
79
+ // Strengthened texture retention (high-freq detail overlay) for richer surface appearance after recolor.
80
+ textureOpacity: 0.85,
81
+ // Slightly tighter low-freq lighting kernel for cleaner wall/ceiling shading without over-smoothing.
82
+ lLowBlurKernel: 7,
83
+ lLowContrast: 1.10,
84
+ lLowBrightness: 0.92,
85
+ lHighGain: 1.22,
86
+ // Edge handling: small positive feather produces soft transitions at painted region boundaries.
87
+ // color feather drives the paintColorMap alpha softness (used by shader for blend-to-origin).
88
+ maskFeatherColor: 1.6,
89
+ maskFeatherTexture: 0.9,
90
+ regionOverlayFill: '#FFC14D',
91
+ regionOutlineStrokeWidth: 4,
92
+ };
93
+
94
+ export const DEFAULT_INTERACTION_CONFIG: Required<InteractionConfig> = {
95
+ kickMaskPickRadiusPx: 36,
96
+ pickMapSearchRadiusPx: 14,
97
+ thinStripPadding: 0.008,
98
+ regionPadding: 0.003,
99
+ initRegionFlashMs: 1000,
100
+ enableInitRegionFlash: true,
101
+ };
102
+
103
+ export const DEFAULT_MASK_CONFIG: Required<
104
+ Omit<
105
+ MaskSegmentConfig,
106
+ | 'baseboardStripQuantKeys'
107
+ | 'wallQuantKeys'
108
+ | 'cabinetQuantKeys'
109
+ | 'secondarySemanticNames'
110
+ >
111
+ > & {
112
+ baseboardStripQuantKeys: Set<string>;
113
+ wallQuantKeys: Set<string>;
114
+ cabinetQuantKeys: Set<string>;
115
+ secondarySemanticNames: Set<string>;
116
+ semanticColors: MaskSemanticColor[];
117
+ } = {
118
+ semanticColors: MASK_SEMANTIC_COLORS,
119
+ baseboardMaxColorDist: 42,
120
+ blackThreshold: 30,
121
+ quantStep: 64,
122
+ baseboardStripQuantKeys: new Set(BASEBOARD_STRIP_QUANT_KEYS),
123
+ wallQuantKeys: new Set(WALL_QUANT_KEYS),
124
+ cabinetQuantKeys: new Set(CABINET_QUANT_KEYS),
125
+ maxRegionColors: 6,
126
+ secondarySemanticNames: new Set(['garageDoor', 'roof', 'eave']),
127
+ secondaryMinPixelRatio: 0.002,
128
+ junctionHRadiusPx: 24,
129
+ junctionVRadiusPx: 2,
130
+ kickBridgeHalfWPx: 6,
131
+ baseboardJunctionRowMarginPx: 1,
132
+ baseboardJunctionVReachPx: 2,
133
+ baseboardMinRunPx: 2,
134
+ };
135
+
136
+ export type ResolvedMaskSegmentRuntime = {
137
+ pipeline: Required<PipelineConfig>;
138
+ mask: typeof DEFAULT_MASK_CONFIG;
139
+ paint: Required<PaintConfig>;
140
+ interaction: Required<InteractionConfig>;
141
+ };
142
+
143
+ function toStringSet(values?: string[]): Set<string> {
144
+ return new Set(values ?? []);
145
+ }
146
+
147
+ export function mergeMaskConfig(
148
+ partial?: MaskSegmentConfig,
149
+ ): typeof DEFAULT_MASK_CONFIG {
150
+ if (!partial) {
151
+ return { ...DEFAULT_MASK_CONFIG };
152
+ }
153
+ return {
154
+ semanticColors: partial.semanticColors ?? DEFAULT_MASK_CONFIG.semanticColors,
155
+ baseboardMaxColorDist:
156
+ partial.baseboardMaxColorDist ?? DEFAULT_MASK_CONFIG.baseboardMaxColorDist,
157
+ blackThreshold:
158
+ partial.blackThreshold ?? DEFAULT_MASK_CONFIG.blackThreshold,
159
+ quantStep: partial.quantStep ?? DEFAULT_MASK_CONFIG.quantStep,
160
+ baseboardStripQuantKeys: partial.baseboardStripQuantKeys
161
+ ? toStringSet(partial.baseboardStripQuantKeys)
162
+ : new Set(DEFAULT_MASK_CONFIG.baseboardStripQuantKeys),
163
+ wallQuantKeys: partial.wallQuantKeys
164
+ ? toStringSet(partial.wallQuantKeys)
165
+ : new Set(DEFAULT_MASK_CONFIG.wallQuantKeys),
166
+ cabinetQuantKeys: partial.cabinetQuantKeys
167
+ ? toStringSet(partial.cabinetQuantKeys)
168
+ : new Set(DEFAULT_MASK_CONFIG.cabinetQuantKeys),
169
+ maxRegionColors:
170
+ partial.maxRegionColors ?? DEFAULT_MASK_CONFIG.maxRegionColors,
171
+ secondarySemanticNames: partial.secondarySemanticNames
172
+ ? toStringSet(partial.secondarySemanticNames)
173
+ : new Set(DEFAULT_MASK_CONFIG.secondarySemanticNames),
174
+ secondaryMinPixelRatio:
175
+ partial.secondaryMinPixelRatio ??
176
+ DEFAULT_MASK_CONFIG.secondaryMinPixelRatio,
177
+ junctionHRadiusPx:
178
+ partial.junctionHRadiusPx ?? DEFAULT_MASK_CONFIG.junctionHRadiusPx,
179
+ junctionVRadiusPx:
180
+ partial.junctionVRadiusPx ?? DEFAULT_MASK_CONFIG.junctionVRadiusPx,
181
+ kickBridgeHalfWPx:
182
+ partial.kickBridgeHalfWPx ?? DEFAULT_MASK_CONFIG.kickBridgeHalfWPx,
183
+ baseboardJunctionRowMarginPx:
184
+ partial.baseboardJunctionRowMarginPx ??
185
+ DEFAULT_MASK_CONFIG.baseboardJunctionRowMarginPx,
186
+ baseboardJunctionVReachPx:
187
+ partial.baseboardJunctionVReachPx ??
188
+ DEFAULT_MASK_CONFIG.baseboardJunctionVReachPx,
189
+ baseboardMinRunPx:
190
+ partial.baseboardMinRunPx ?? DEFAULT_MASK_CONFIG.baseboardMinRunPx,
191
+ };
192
+ }
193
+
194
+ export function createRuntimeConfig(input?: {
195
+ pipelineConfig?: PipelineConfig;
196
+ maskConfig?: MaskSegmentConfig;
197
+ paintConfig?: PaintConfig;
198
+ interactionConfig?: InteractionConfig;
199
+ }): ResolvedMaskSegmentRuntime {
200
+ return {
201
+ pipeline: {
202
+ ...DEFAULT_PIPELINE_CONFIG,
203
+ ...input?.pipelineConfig,
204
+ },
205
+ mask: mergeMaskConfig(input?.maskConfig),
206
+ paint: {
207
+ ...DEFAULT_PAINT_CONFIG,
208
+ ...input?.paintConfig,
209
+ palette: input?.paintConfig?.palette ?? DEFAULT_PAINT_CONFIG.palette,
210
+ },
211
+ interaction: {
212
+ ...DEFAULT_INTERACTION_CONFIG,
213
+ ...input?.interactionConfig,
214
+ },
215
+ };
216
+ }
217
+
218
+ let activeRuntime = createRuntimeConfig();
219
+ let runtimeRevision = 0;
220
+
221
+ export function getMaskRuntimeRevision(): number {
222
+ return runtimeRevision;
223
+ }
224
+
225
+ export function setMaskSegmentRuntimeConfig(input?: {
226
+ pipelineConfig?: PipelineConfig;
227
+ maskConfig?: MaskSegmentConfig;
228
+ paintConfig?: PaintConfig;
229
+ interactionConfig?: InteractionConfig;
230
+ }): ResolvedMaskSegmentRuntime {
231
+ activeRuntime = {
232
+ pipeline: input?.pipelineConfig
233
+ ? { ...activeRuntime.pipeline, ...input.pipelineConfig }
234
+ : activeRuntime.pipeline,
235
+ mask: input?.maskConfig
236
+ ? mergeMaskConfig(input.maskConfig)
237
+ : activeRuntime.mask,
238
+ paint: input?.paintConfig
239
+ ? { ...activeRuntime.paint, ...input.paintConfig }
240
+ : activeRuntime.paint,
241
+ interaction: input?.interactionConfig
242
+ ? { ...activeRuntime.interaction, ...input.interactionConfig }
243
+ : activeRuntime.interaction,
244
+ };
245
+ runtimeRevision += 1;
246
+ return activeRuntime;
247
+ }
248
+
249
+ export function getMaskSegmentRuntimeConfig(): ResolvedMaskSegmentRuntime {
250
+ return activeRuntime;
251
+ }
252
+
253
+ export function resetMaskSegmentRuntimeConfig(): ResolvedMaskSegmentRuntime {
254
+ activeRuntime = createRuntimeConfig();
255
+ runtimeRevision += 1;
256
+ return activeRuntime;
257
+ }