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,539 @@
1
+ /**
2
+ * OpenCV 便捷适配层
3
+ * 将 react-native-fast-opencv 的 invoke API 封装为 MaskSegmentCanvas 所需的 async 风格接口
4
+ */
5
+ import RNFS from 'react-native-fs';
6
+ import {
7
+ OpenCV,
8
+ ObjectType,
9
+ DataTypes,
10
+ ColorConversionCodes,
11
+ ThresholdTypes,
12
+ MorphShapes,
13
+ MorphTypes,
14
+ RetrievalModes,
15
+ ContourApproximationModes,
16
+ InterpolationFlags,
17
+ type Mat,
18
+ type PointVector,
19
+ } from 'react-native-fast-opencv';
20
+ import {
21
+ PNG_COMPRESSION,
22
+ ensureMat8U,
23
+ ensurePngFile,
24
+ isPngPath,
25
+ normalizePath,
26
+ pngCacheName,
27
+ readPngBgrBuffer,
28
+ readPngHeaderFromBase64,
29
+ } from './pngImage';
30
+ import { rgbaBufferToSkiaImage } from './skiaImage';
31
+ import type { SkImage } from '@shopify/react-native-skia';
32
+
33
+ const IMREAD_GRAYSCALE = 0;
34
+
35
+ type Point = { x: number; y: number };
36
+ type BBox = { x: number; y: number; width: number; height: number };
37
+ type BgrColor = { b: number; g: number; r: number };
38
+
39
+ export class WrappedMat {
40
+ readonly mat: Mat;
41
+ cols: number;
42
+ rows: number;
43
+ readonly channels: number;
44
+
45
+ constructor(mat: Mat, cols: number, rows: number, channels = 3) {
46
+ this.mat = mat;
47
+ this.cols = cols;
48
+ this.rows = rows;
49
+ this.channels = channels;
50
+ }
51
+
52
+ release() {
53
+ OpenCV.releaseBuffers([this.mat.id]);
54
+ }
55
+
56
+ async clone(): Promise<WrappedMat> {
57
+ const cloned = OpenCV.invoke('clone', this.mat) as Mat;
58
+ return new WrappedMat(cloned, this.cols, this.rows, this.channels);
59
+ }
60
+ }
61
+
62
+ export class ContourWrapper {
63
+ readonly pointVector: PointVector;
64
+
65
+ constructor(pointVector: PointVector) {
66
+ this.pointVector = pointVector;
67
+ }
68
+
69
+ release() {
70
+ OpenCV.releaseBuffers([this.pointVector.id]);
71
+ }
72
+ }
73
+
74
+ function createScalar(a: number, b?: number, c?: number) {
75
+ if (b === undefined) {
76
+ return OpenCV.createObject(ObjectType.Scalar, a);
77
+ }
78
+ if (c === undefined) {
79
+ return OpenCV.createObject(ObjectType.Scalar, a, b, 0);
80
+ }
81
+ return OpenCV.createObject(ObjectType.Scalar, a, b, c);
82
+ }
83
+
84
+ async function readImageFromPath(path: string, grayscale = false): Promise<WrappedMat> {
85
+ const filePath = normalizePath(path);
86
+ const base64 = await RNFS.readFile(filePath, 'base64');
87
+ const srcMat = OpenCV.base64ToMat(base64);
88
+
89
+ // 16-bit PNG 降级: toJSValue 可能崩溃,先解析 PNG 头
90
+ let pngHeader;
91
+ try {
92
+ pngHeader = readPngHeaderFromBase64(base64);
93
+ } catch {
94
+ // 非 PNG 不传
95
+ }
96
+
97
+ const { mat, extraReleaseIds } = ensureMat8U(srcMat, pngHeader);
98
+ const info = OpenCV.toJSValue(mat);
99
+
100
+ if (grayscale) {
101
+ const gray = OpenCV.createObject(
102
+ ObjectType.Mat,
103
+ info.rows,
104
+ info.cols,
105
+ DataTypes.CV_8UC1,
106
+ );
107
+ OpenCV.invoke('cvtColor', mat, gray, ColorConversionCodes.COLOR_BGR2GRAY);
108
+ OpenCV.releaseBuffers([
109
+ ...new Set([srcMat.id, mat.id, ...extraReleaseIds]),
110
+ ]);
111
+ return new WrappedMat(gray, info.cols, info.rows, 1);
112
+ }
113
+
114
+ const channels =
115
+ info.type === DataTypes.CV_8UC1
116
+ ? 1
117
+ : info.type === DataTypes.CV_8UC4
118
+ ? 4
119
+ : 3;
120
+ if (mat.id !== srcMat.id) {
121
+ OpenCV.releaseBuffers([srcMat.id]);
122
+ }
123
+ return new WrappedMat(mat, info.cols, info.rows, channels);
124
+ }
125
+
126
+ function createSize(width: number, height: number) {
127
+ return OpenCV.createObject(ObjectType.Size, width, height);
128
+ }
129
+
130
+ /** OpenCV GaussianBlur 要求核宽高为正奇数 */
131
+ function normalizeGaussianKernel(size: number): number {
132
+ const n = Math.max(1, Math.round(size));
133
+ return n % 2 === 0 ? n + 1 : n;
134
+ }
135
+
136
+ const cv = {
137
+ IMREAD_GRAYSCALE,
138
+ THRESH_BINARY: ThresholdTypes.THRESH_BINARY,
139
+ MORPH_RECT: MorphShapes.MORPH_RECT,
140
+ MORPH_ELLIPSE: MorphShapes.MORPH_ELLIPSE,
141
+ MORPH_OPEN: MorphTypes.MORPH_OPEN,
142
+ MORPH_CLOSE: MorphTypes.MORPH_CLOSE,
143
+ RETR_EXTERNAL: RetrievalModes.RETR_EXTERNAL,
144
+ CHAIN_APPROX_SIMPLE: ContourApproximationModes.CHAIN_APPROX_SIMPLE,
145
+ CHAIN_APPROX_NONE: ContourApproximationModes.CHAIN_APPROX_NONE,
146
+ COLOR_BGR2GRAY: ColorConversionCodes.COLOR_BGR2GRAY,
147
+ COLOR_BGR2Lab: ColorConversionCodes.COLOR_BGR2Lab,
148
+ COLOR_Lab2BGR: ColorConversionCodes.COLOR_Lab2BGR,
149
+ COLOR_GRAY2BGR: ColorConversionCodes.COLOR_GRAY2BGR,
150
+ CV_8UC1: DataTypes.CV_8UC1,
151
+ CV_16SC1: DataTypes.CV_16SC1,
152
+ INTER_LINEAR: InterpolationFlags.INTER_LINEAR,
153
+ INTER_NEAREST: InterpolationFlags.INTER_NEAREST,
154
+
155
+ async ensurePngPath(path: string, cacheFileName?: string): Promise<string> {
156
+ const name = cacheFileName ?? pngCacheName(path, 'img');
157
+ return ensurePngFile(path, name);
158
+ },
159
+
160
+ async imread(path: string, flags?: number): Promise<WrappedMat> {
161
+ const filePath = normalizePath(path);
162
+ if (isPngPath(filePath) && (await RNFS.exists(filePath))) {
163
+ if (flags === IMREAD_GRAYSCALE) {
164
+ return readImageFromPath(filePath, true);
165
+ }
166
+ const { buffer, cols, rows } = await readPngBgrBuffer(filePath);
167
+ return cv.bgrBufferToMat(buffer, cols, rows);
168
+ }
169
+ const pngPath = await ensurePngFile(path, pngCacheName(path, 'imread'));
170
+ if (flags === IMREAD_GRAYSCALE) {
171
+ return readImageFromPath(pngPath, true);
172
+ }
173
+ const { buffer, cols, rows } = await readPngBgrBuffer(pngPath);
174
+ return cv.bgrBufferToMat(buffer, cols, rows);
175
+ },
176
+
177
+ createMat(cols: number, rows: number, channels: 1 | 3 | 4 = 1): WrappedMat {
178
+ const type =
179
+ channels === 1
180
+ ? DataTypes.CV_8UC1
181
+ : channels === 3
182
+ ? DataTypes.CV_8UC3
183
+ : DataTypes.CV_8UC4;
184
+ const mat = OpenCV.createObject(ObjectType.Mat, rows, cols, type);
185
+ return new WrappedMat(mat, cols, rows, channels);
186
+ },
187
+
188
+ async cvtColor(
189
+ src: WrappedMat,
190
+ code: ColorConversionCodes,
191
+ ): Promise<WrappedMat> {
192
+ const dst = cv.createMat(src.cols, src.rows, 1);
193
+ OpenCV.invoke('cvtColor', src.mat, dst.mat, code);
194
+ return dst;
195
+ },
196
+
197
+ /** 三通道色彩空间转换(BGR/Lab 等) */
198
+ cvtColorBgr(src: WrappedMat, code: ColorConversionCodes): WrappedMat {
199
+ const dst = cv.createMat(src.cols, src.rows, 3);
200
+ OpenCV.invoke('cvtColor', src.mat, dst.mat, code);
201
+ return dst;
202
+ },
203
+
204
+ /** 灰度 Mat → 三通道 BGR(供 Skia 显示) */
205
+ grayToBgr(src: WrappedMat): WrappedMat {
206
+ const dst = cv.createMat(src.cols, src.rows, 3);
207
+ OpenCV.invoke(
208
+ 'cvtColor',
209
+ src.mat,
210
+ dst.mat,
211
+ ColorConversionCodes.COLOR_GRAY2BGR,
212
+ );
213
+ return dst;
214
+ },
215
+
216
+ /** 掩码统一为三通道 BGR;已是 3 通道则原样返回,色序由分割侧 swapBr 检测 */
217
+ async ensureBgr3(src: WrappedMat): Promise<WrappedMat> {
218
+ if (src.channels === 3) {
219
+ return src;
220
+ }
221
+
222
+ const dst = cv.createMat(src.cols, src.rows, 3);
223
+ const code =
224
+ src.channels === 4
225
+ ? ColorConversionCodes.COLOR_BGRA2BGR
226
+ : ColorConversionCodes.COLOR_GRAY2BGR;
227
+ OpenCV.invoke('cvtColor', src.mat, dst.mat, code);
228
+ return dst;
229
+ },
230
+
231
+ /** JS 二值缓冲(0/255)→ 单通道 Mat */
232
+ binaryBufferToMat(
233
+ buffer: Uint8Array,
234
+ cols: number,
235
+ rows: number,
236
+ ): WrappedMat {
237
+ const mat = OpenCV.bufferToMat('uint8', rows, cols, 1, buffer);
238
+ return new WrappedMat(mat, cols, rows, 1);
239
+ },
240
+
241
+ /** 连续 BGR 缓冲 → 三通道 Mat */
242
+ bgrBufferToMat(
243
+ buffer: Uint8Array,
244
+ cols: number,
245
+ rows: number,
246
+ ): WrappedMat {
247
+ const mat = OpenCV.bufferToMat('uint8', rows, cols, 3, buffer);
248
+ return new WrappedMat(mat, cols, rows, 3);
249
+ },
250
+
251
+ /** 将 JS 侧生成的灰度二值图写入临时 PGM 并读回 Mat */
252
+ async grayBufferToMat(
253
+ gray: Uint8Array,
254
+ cols: number,
255
+ rows: number,
256
+ ): Promise<WrappedMat> {
257
+ const path = `${RNFS.CachesDirectoryPath}/seg_bin_${Date.now()}.pgm`;
258
+ const header = `P5\n${cols} ${rows}\n255\n`;
259
+ const headerBytes = new TextEncoder().encode(header);
260
+ const fileBytes = new Uint8Array(headerBytes.length + gray.length);
261
+ fileBytes.set(headerBytes, 0);
262
+ fileBytes.set(gray, headerBytes.length);
263
+
264
+ let binary = '';
265
+ const chunkSize = 8192;
266
+ for (let i = 0; i < fileBytes.length; i += chunkSize) {
267
+ const slice = fileBytes.subarray(i, i + chunkSize);
268
+ binary += String.fromCharCode(...slice);
269
+ }
270
+
271
+ await RNFS.writeFile(path, btoa(binary), 'base64');
272
+ try {
273
+ return readImageFromPath(path, true);
274
+ } finally {
275
+ if (await RNFS.exists(path)) {
276
+ await RNFS.unlink(path);
277
+ }
278
+ }
279
+ },
280
+
281
+ /**
282
+ * 导出 Mat 像素。先 clone 保证内存连续,避免原生 matToBuffer 忽略 step 导致行错位。
283
+ */
284
+ matToBuffer(src: WrappedMat): {
285
+ buffer: Uint8Array;
286
+ cols: number;
287
+ rows: number;
288
+ channels: number;
289
+ } {
290
+ const continuous = OpenCV.invoke('clone', src.mat) as Mat;
291
+ try {
292
+ const { buffer, cols, rows, channels } = OpenCV.matToBuffer(
293
+ continuous,
294
+ 'uint8',
295
+ );
296
+ return { buffer, cols, rows, channels };
297
+ } finally {
298
+ OpenCV.releaseBuffers([continuous.id]);
299
+ }
300
+ },
301
+
302
+ async inRangeBgr(
303
+ src: WrappedMat,
304
+ color: BgrColor,
305
+ tolerance: number,
306
+ dst: WrappedMat,
307
+ ): Promise<void> {
308
+ const lower = createScalar(
309
+ Math.max(0, color.b - tolerance),
310
+ Math.max(0, color.g - tolerance),
311
+ Math.max(0, color.r - tolerance),
312
+ );
313
+ const upper = createScalar(
314
+ Math.min(255, color.b + tolerance),
315
+ Math.min(255, color.g + tolerance),
316
+ Math.min(255, color.r + tolerance),
317
+ );
318
+ OpenCV.invoke('inRange', src.mat, lower, upper, dst.mat);
319
+ },
320
+
321
+ async resize(
322
+ src: WrappedMat,
323
+ dst: WrappedMat,
324
+ size: { width: number; height: number },
325
+ interpolation: InterpolationFlags = InterpolationFlags.INTER_LINEAR,
326
+ ): Promise<void> {
327
+ const dsize = createSize(size.width, size.height);
328
+ OpenCV.invoke('resize', src.mat, dst.mat, dsize, 0, 0, interpolation);
329
+ dst.cols = size.width;
330
+ dst.rows = size.height;
331
+ },
332
+
333
+ /** BGR 缓冲原生缩放(掩码用语义色,默认最近邻) */
334
+ async resizeBgrBuffer(
335
+ buffer: Uint8Array,
336
+ srcCols: number,
337
+ srcRows: number,
338
+ dstCols: number,
339
+ dstRows: number,
340
+ interpolation: InterpolationFlags = InterpolationFlags.INTER_NEAREST,
341
+ ): Promise<Uint8Array> {
342
+ if (srcCols === dstCols && srcRows === dstRows) {
343
+ return buffer;
344
+ }
345
+
346
+ const srcMat = cv.bgrBufferToMat(buffer, srcCols, srcRows);
347
+ const dstMat = cv.createMat(dstCols, dstRows, 3);
348
+ try {
349
+ await cv.resize(srcMat, dstMat, { width: dstCols, height: dstRows }, interpolation);
350
+ return cv.matToBuffer(dstMat).buffer;
351
+ } finally {
352
+ srcMat.release();
353
+ dstMat.release();
354
+ }
355
+ },
356
+
357
+ /** BGR Mat → RGBA 连续缓冲(供 Skia 直传) */
358
+ async matToRgbaBuffer(src: WrappedMat): Promise<{
359
+ buffer: Uint8Array;
360
+ cols: number;
361
+ rows: number;
362
+ }> {
363
+ const dst = cv.createMat(src.cols, src.rows, 4);
364
+ try {
365
+ OpenCV.invoke(
366
+ 'cvtColor',
367
+ src.mat,
368
+ dst.mat,
369
+ ColorConversionCodes.COLOR_BGR2RGBA,
370
+ );
371
+ const { buffer, cols, rows } = cv.matToBuffer(dst);
372
+ return { buffer, cols, rows };
373
+ } finally {
374
+ dst.release();
375
+ }
376
+ },
377
+
378
+ /** BGR Mat → Skia 图像(跳过低频/高频 PNG 编码) */
379
+ async matToSkiaImage(src: WrappedMat): Promise<SkImage | null> {
380
+ const { buffer, cols, rows } = await cv.matToRgbaBuffer(src);
381
+ return rgbaBufferToSkiaImage(buffer, cols, rows);
382
+ },
383
+
384
+ /** 单通道灰度 Mat → Skia RGBA(跳过 BGR 伪彩 + 四通道 matToBuffer) */
385
+ grayMatToSkiaImage(src: WrappedMat): SkImage | null {
386
+ const { buffer, cols, rows } = cv.matToBuffer(src);
387
+ const pixelCount = cols * rows;
388
+ const rgba = new Uint8Array(pixelCount * 4);
389
+ for (let i = 0; i < pixelCount; i++) {
390
+ const value = buffer[i];
391
+ const offset = i * 4;
392
+ rgba[offset] = value;
393
+ rgba[offset + 1] = value;
394
+ rgba[offset + 2] = value;
395
+ rgba[offset + 3] = 255;
396
+ }
397
+ return rgbaBufferToSkiaImage(rgba, cols, rows);
398
+ },
399
+
400
+ /** 连续 BGR 缓冲 → Skia 图像(工作分辨率原图 / 高低频,复用 OpenCV 解码结果) */
401
+ async bgrBufferToSkiaImage(
402
+ buffer: Uint8Array,
403
+ cols: number,
404
+ rows: number,
405
+ ): Promise<SkImage | null> {
406
+ const mat = cv.bgrBufferToMat(buffer, cols, rows);
407
+ try {
408
+ return await cv.matToSkiaImage(mat);
409
+ } finally {
410
+ mat.release();
411
+ }
412
+ },
413
+
414
+ async threshold(
415
+ src: WrappedMat,
416
+ dst: WrappedMat,
417
+ thresh: number,
418
+ maxval: number,
419
+ type: number,
420
+ ): Promise<void> {
421
+ OpenCV.invoke('threshold', src.mat, dst.mat, thresh, maxval, type);
422
+ },
423
+
424
+ async getStructuringElement(
425
+ shape: MorphShapes,
426
+ ksize: { width: number; height: number },
427
+ ): Promise<WrappedMat> {
428
+ const size = createSize(ksize.width, ksize.height);
429
+ const kernel = OpenCV.invoke('getStructuringElement', shape, size) as Mat;
430
+ return new WrappedMat(kernel, ksize.width, ksize.height, 1);
431
+ },
432
+
433
+ async morphologyEx(
434
+ src: WrappedMat,
435
+ dst: WrappedMat,
436
+ op: MorphTypes,
437
+ kernel: WrappedMat,
438
+ ): Promise<void> {
439
+ OpenCV.invoke('morphologyEx', src.mat, dst.mat, op, kernel.mat);
440
+ },
441
+
442
+ async findContours(
443
+ image: WrappedMat,
444
+ mode: RetrievalModes,
445
+ method: ContourApproximationModes,
446
+ ): Promise<ContourWrapper[]> {
447
+ const contours = OpenCV.createObject(ObjectType.PointVectorOfVectors);
448
+ OpenCV.invoke('findContours', image.mat, contours, mode, method);
449
+ const data = OpenCV.toJSValue(contours);
450
+ const wrappers = data.array.map((_, index) => {
451
+ const pv = OpenCV.copyObjectFromVector(contours, index) as PointVector;
452
+ return new ContourWrapper(pv);
453
+ });
454
+ OpenCV.releaseBuffers([contours.id]);
455
+ return wrappers;
456
+ },
457
+
458
+ async contourArea(contour: ContourWrapper): Promise<number> {
459
+ const result = OpenCV.invoke('contourArea', contour.pointVector, false);
460
+ return result.value;
461
+ },
462
+
463
+ async boundingRect(contour: ContourWrapper): Promise<BBox> {
464
+ const rect = OpenCV.invoke('boundingRect', contour.pointVector);
465
+ const js = OpenCV.toJSValue(rect);
466
+ return { x: js.x, y: js.y, width: js.width, height: js.height };
467
+ },
468
+
469
+ async arcLength(contour: ContourWrapper, closed: boolean): Promise<number> {
470
+ const result = OpenCV.invoke('arcLength', contour.pointVector, closed);
471
+ return result.value;
472
+ },
473
+
474
+ async approxPolyDP(
475
+ contour: ContourWrapper,
476
+ epsilon: number,
477
+ closed: boolean,
478
+ ): Promise<Point[]> {
479
+ const approx = OpenCV.createObject(ObjectType.PointVector);
480
+ try {
481
+ OpenCV.invoke('approxPolyDP', contour.pointVector, approx, epsilon, closed);
482
+ return OpenCV.toJSValue(approx).array;
483
+ } finally {
484
+ OpenCV.releaseBuffers([approx.id]);
485
+ }
486
+ },
487
+
488
+ async GaussianBlur(
489
+ src: WrappedMat,
490
+ dst: WrappedMat,
491
+ ksize: { width: number; height: number },
492
+ sigma: number,
493
+ ): Promise<void> {
494
+ const width = normalizeGaussianKernel(ksize.width);
495
+ const height = normalizeGaussianKernel(ksize.height);
496
+ const size = createSize(width, height);
497
+ OpenCV.invoke('GaussianBlur', src.mat, dst.mat, size, sigma);
498
+ },
499
+
500
+ extractChannel(src: WrappedMat, dst: WrappedMat, channel: number): void {
501
+ OpenCV.invoke('extractChannel', src.mat, dst.mat, channel);
502
+ },
503
+
504
+ convertTo(
505
+ src: WrappedMat,
506
+ dst: WrappedMat,
507
+ rtype: number,
508
+ alpha = 1,
509
+ beta = 0,
510
+ ): void {
511
+ OpenCV.invoke('convertTo', src.mat, dst.mat, rtype, alpha, beta);
512
+ },
513
+
514
+ async subtract(src1: WrappedMat, src2: WrappedMat, dst: WrappedMat): Promise<void> {
515
+ OpenCV.invoke('subtract', src1.mat, src2.mat, dst.mat);
516
+ },
517
+
518
+ async addWeighted(
519
+ src1: WrappedMat,
520
+ alpha: number,
521
+ src2: WrappedMat | null,
522
+ beta: number,
523
+ gamma: number,
524
+ dst: WrappedMat,
525
+ ): Promise<void> {
526
+ if (src2) {
527
+ OpenCV.invoke('addWeighted', src1.mat, alpha, src2.mat, beta, gamma, dst.mat);
528
+ } else {
529
+ OpenCV.invoke('addWeighted', src1.mat, alpha, src1.mat, 0, gamma, dst.mat);
530
+ }
531
+ },
532
+
533
+ async imwrite(path: string, mat: WrappedMat): Promise<void> {
534
+ const filePath = normalizePath(path);
535
+ OpenCV.saveMatToFile(mat.mat, filePath, 'png', PNG_COMPRESSION);
536
+ },
537
+ };
538
+
539
+ export default cv;