facebetter 1.0.14 → 1.1.1

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.
@@ -47,7 +47,13 @@ class EngineConfig {
47
47
  this.appId = config.appId || null;
48
48
  this.appKey = config.appKey || null;
49
49
  this.licenseJson = config.licenseJson || null;
50
- this.resourcePath = '/facebetter/resource.bundle';
50
+ this.resourcePath = '/resource.fbd';
51
+ /**
52
+ * Whether to use an external GL context (native platforms only).
53
+ * In Web/WASM environments, this field is kept for configuration structure alignment,
54
+ * but it does not take effect in the current implementation.
55
+ */
56
+ this.externalContext = !!config.externalContext;
51
57
  }
52
58
 
53
59
  /**
@@ -55,11 +61,11 @@ class EngineConfig {
55
61
  * @returns {boolean} True if valid
56
62
  */
57
63
  isValid() {
58
- // 如果提供了 licenseJson,则使用授权数据验证
64
+ // If licenseJson is provided, use it for validation
59
65
  if (this.licenseJson) {
60
66
  return typeof this.licenseJson === 'string' && this.licenseJson.trim() !== '';
61
67
  }
62
- // 否则需要 appId appKey
68
+ // Otherwise appId and appKey are required
63
69
  return this.appId && typeof this.appId === 'string' && this.appId.trim() !== '' &&
64
70
  this.appKey && typeof this.appKey === 'string' && this.appKey.trim() !== '';
65
71
  }
@@ -82,61 +88,87 @@ class EngineConfig {
82
88
  * Beauty type enumeration
83
89
  */
84
90
  const BeautyType$1 = {
85
- Basic: 0, // 基础美颜
86
- Reshape: 1, // 面部重塑
87
- Makeup: 2, // 美妆效果
88
- VirtualBackground: 3 // 虚拟背景
91
+ Basic: 0, // Basic beauty (smoothing, whitening, etc.)
92
+ Reshape: 1, // Face reshaping (face thinning, big eyes, etc.)
93
+ Makeup: 2, // Makeup effects (lipstick, blush, etc.)
94
+ VirtualBackground: 3, // Virtual background (blur, image replacement)
95
+ ChromaKey: 4, // Chroma key (green screen removal)
96
+ Filter: 5, // LUT based filter
97
+ Sticker: 6 // 2D/3D sticker
89
98
  };
90
99
 
91
100
  /**
92
101
  * Basic beauty parameter enumeration
102
+ * All values should be in range [0.0, 1.0]
93
103
  */
94
104
  const BasicParam$1 = {
95
- Smoothing: 0, // 磨皮
96
- Sharpening: 1, // 锐化
97
- Whitening: 2, // 美白
98
- Rosiness: 3 // 红润
105
+ Smoothing: 0, // Skin smoothing (0.0: none, 1.0: maximum)
106
+ Sharpening: 1, // Image sharpening (0.0: none, 1.0: maximum)
107
+ Whitening: 2, // Skin whitening (0.0: none, 1.0: maximum)
108
+ Rosiness: 3 // Skin rosiness (0.0: none, 1.0: maximum)
99
109
  };
100
110
 
101
111
  /**
102
112
  * Face reshape parameter enumeration
113
+ * All values should be in range [0.0, 1.0]
103
114
  */
104
115
  const ReshapeParam$1 = {
105
- FaceThin: 0, // 瘦脸
106
- FaceVShape: 1, // V
107
- FaceNarrow: 2, // 窄脸
108
- FaceShort: 3, // 短脸
109
- Cheekbone: 4, // 颧骨
110
- Jawbone: 5, // 下颌骨
111
- Chin: 6, // 下巴
112
- NoseSlim: 7, // 瘦鼻梁
113
- EyeSize: 8, // 大眼
114
- EyeDistance: 9 // 眼距
116
+ FaceThinning: 0, // Face thinning (0.0: none, 1.0: maximum)
117
+ FaceVShape: 1, // V-shape face (0.0: none, 1.0: maximum)
118
+ FaceNarrowing: 2, // Face narrowing (0.0: none, 1.0: maximum)
119
+ FaceShortening: 3,// Face shortening (0.0: none, 1.0: maximum)
120
+ Cheekbone: 4, // Cheekbone slimming (0.0: none, 1.0: maximum)
121
+ Jawbone: 5, // Jawbone slimming (0.0: none, 1.0: maximum)
122
+ Chin: 6, // Chin length adjustment (0.0: none, 1.0: maximum)
123
+ NoseSlimming: 7, // Nose slimming (0.0: none, 1.0: maximum)
124
+ EyeSize: 8, // Eye size (0.0: normal, 1.0: maximum)
125
+ EyeDistance: 9 // Eye distance (0.0: normal, 1.0: maximum)
115
126
  };
116
127
 
117
128
  /**
118
129
  * Makeup parameter enumeration
130
+ * All values should be in range [0.0, 1.0]
119
131
  */
120
132
  const MakeupParam$1 = {
121
- Lipstick: 0, // 口红
122
- Blush: 1 // 腮红
133
+ Lipstick: 0, // Lipstick intensity (0.0: none, 1.0: maximum)
134
+ Blush: 1 // Blush intensity (0.0: none, 1.0: maximum)
135
+ };
136
+
137
+ /**
138
+ * Chroma Key parameter enumeration
139
+ */
140
+ const ChromaKeyParam$1 = {
141
+ KeyColor: 0, // Key color (0.0: Green, 1.0: Blue, 2.0: Red)
142
+ Similarity: 1, // Color similarity (0.0 - 1.0)
143
+ Smoothness: 2, // Edge smoothness (0.0 - 1.0)
144
+ Desaturation: 3 // Spill desaturation (0.0 - 1.0)
123
145
  };
124
146
 
125
147
  /**
126
148
  * Background mode enumeration
127
149
  */
128
150
  const BackgroundMode$1 = {
129
- None: 0, // 无背景处理
130
- Blur: 1, // 模糊背景
131
- Image: 2 // 背景图片替换
151
+ None: 0, // No background processing
152
+ Blur: 1, // Blurred background
153
+ Image: 2 // Background image replacement
132
154
  };
133
155
 
134
156
  /**
135
- * Process mode enumeration
157
+ * Frame type enumeration
136
158
  */
137
- const ProcessMode$1 = {
138
- Image: 0, // 图片模式
139
- Video: 1 // 视频模式
159
+ const FrameType$1 = {
160
+ Image: 0, // Image mode
161
+ Video: 1 // Video mode
162
+ };
163
+
164
+ /**
165
+ * Mirror mode enumeration (apply to input before processing)
166
+ */
167
+ const MirrorMode$1 = {
168
+ None: 0, // No mirror
169
+ Horizontal: 1, // Mirror horizontally (e.g. front camera selfie)
170
+ Vertical: 2, // Mirror vertically
171
+ Both: 3 // Mirror both axes
140
172
  };
141
173
 
142
174
  /**
@@ -172,8 +204,10 @@ var constants = /*#__PURE__*/Object.freeze({
172
204
  BackgroundMode: BackgroundMode$1,
173
205
  BasicParam: BasicParam$1,
174
206
  BeautyType: BeautyType$1,
207
+ ChromaKeyParam: ChromaKeyParam$1,
208
+ FrameType: FrameType$1,
175
209
  MakeupParam: MakeupParam$1,
176
- ProcessMode: ProcessMode$1,
210
+ MirrorMode: MirrorMode$1,
177
211
  ReshapeParam: ReshapeParam$1,
178
212
  VirtualBackgroundOptions: VirtualBackgroundOptions$1
179
213
  });
@@ -211,7 +245,7 @@ class BeautyEffectEngine {
211
245
  throw new FacebetterError('Platform API is required');
212
246
  }
213
247
 
214
- // 如果传入的是 EngineConfig 实例,直接使用;否则创建新的 EngineConfig
248
+ // If an EngineConfig instance is passed, use it directly; otherwise create a new EngineConfig
215
249
  let engineConfig;
216
250
  if (config instanceof EngineConfig) {
217
251
  engineConfig = config;
@@ -229,20 +263,23 @@ class BeautyEffectEngine {
229
263
  this.appId = engineConfig.appId;
230
264
  this.appKey = engineConfig.appKey;
231
265
  this.licenseJson = engineConfig.licenseJson;
232
- this.resourcePath = '/facebetter/resource.bundle';
266
+ this.resourcePath = '/resource.fbd';
233
267
  this.enginePtr = null;
234
268
  this.initialized = false;
235
269
  this.srcBufferPtr = null;
236
270
  this.dstBufferPtr = null;
237
271
  this.bufferSize = 0;
238
272
 
273
+ // Callback related state
274
+ this._callbackSharedBufferPtr = null;
275
+ this._callbackSharedBufferSize = 0;
276
+
239
277
  // Store platform API
240
278
  this._platformAPI = platformAPI;
241
279
  this._loadWasmModule = platformAPI.loadWasmModule;
242
280
  this._getWasmModule = platformAPI.getWasmModule;
243
281
  this._getWasmBuffer = platformAPI.getWasmBuffer;
244
282
  this._toImageData = platformAPI.toImageData;
245
- this._verifyAppKeyOnline = platformAPI.verifyAppKeyOnline;
246
283
  this._ensureGPUPixelCanvas = platformAPI.ensureGPUPixelCanvas;
247
284
  this._cleanupGPUPixelCanvas = platformAPI.cleanupGPUPixelCanvas;
248
285
 
@@ -320,23 +357,22 @@ class BeautyEffectEngine {
320
357
  * @returns {Promise<void>} Promise that resolves when initialization is complete
321
358
  */
322
359
  async init(options = {}) {
323
- // 并发控制:如果已经初始化,直接返回
360
+ // Concurrency control: if already initialized, return immediately
324
361
  if (this.initialized) {
325
362
  return;
326
363
  }
327
364
 
328
- // 并发控制:如果正在初始化,返回同一个 Promise
365
+ // Concurrency control: if initialization is in progress, return the same Promise
329
366
  if (this._initPromise) {
330
367
  return this._initPromise;
331
368
  }
332
369
 
333
- // 创建初始化 Promise
370
+ // Create initialization Promise
334
371
  this._initPromise = (async () => {
335
372
  try {
336
373
  const wasmTimeout = options.timeout || 30000;
337
- const authTimeout = options.authTimeout || 10000;
338
374
 
339
- // 等待 WASM 模块加载(带超时)
375
+ // Wait for WASM module loading (with timeout)
340
376
  try {
341
377
  await Promise.race([
342
378
  this._wasmLoadPromise,
@@ -348,48 +384,60 @@ class BeautyEffectEngine {
348
384
 
349
385
  const Module = this._getWasmModule();
350
386
 
351
- let licenseJsonToUse = this.licenseJson;
352
-
353
- // 如果用户提供了 appId appKey(但没有提供 licenseJson),则在 JS 层发送 HTTP 请求
354
- if (this.appId && this.appKey && !this.licenseJson && this._verifyAppKeyOnline) {
355
- try {
356
- // 在线认证(带超时)
357
- const authResponse = await Promise.race([
358
- this._verifyAppKeyOnline(this.appId, this.appKey),
359
- this._createTimeout(authTimeout, 'Online authentication')
360
- ]);
361
-
362
- if (!authResponse) {
363
- throw new FacebetterError(
364
- 'Failed to get server response from online_auth API. The server returned an empty response.',
365
- 'AUTH_EMPTY_RESPONSE'
366
- );
387
+ // Setup usage report proxy for WASM
388
+ if (!Module.onReportUsage) {
389
+ Module.onReportUsage = async (payloadJson) => {
390
+ try {
391
+ const url = 'https://facebetter.pixpark.net/facebetter/v1/report';
392
+ const response = await fetch(url, {
393
+ method: 'POST',
394
+ headers: {
395
+ 'Content-Type': 'application/json'
396
+ },
397
+ body: payloadJson
398
+ });
399
+ return response.ok;
400
+ } catch (error) {
401
+ return false;
367
402
  }
403
+ };
404
+ }
368
405
 
369
- // 将服务器响应转换为 JSON 字符串,作为 licenseJson 传递给 WASM
370
- licenseJsonToUse = JSON.stringify(authResponse);
371
- console.log('Online auth response received, using as licenseJson');
372
- } catch (error) {
373
- if (error instanceof FacebetterError && error.code === 'TIMEOUT') {
374
- throw new FacebetterError(
375
- `Online authentication timed out after ${authTimeout}ms. Please check your network connection or provide licenseJson directly.`,
376
- 'AUTH_TIMEOUT'
377
- );
406
+ // Setup online auth proxy for WASM
407
+ if (!Module.onOnlineAuth) {
408
+ Module.onOnlineAuth = async (payloadJson) => {
409
+ try {
410
+ const url = 'https://facebetter.pixpark.net/facebetter/v1/auth';
411
+ const response = await fetch(url, {
412
+ method: 'POST',
413
+ headers: {
414
+ 'Content-Type': 'application/json'
415
+ },
416
+ body: payloadJson
417
+ });
418
+ if (!response.ok) {
419
+ console.warn('[JS Engine] Online auth fetch failed with status:', response.status);
420
+ return "";
421
+ }
422
+ return await response.text();
423
+ } catch (error) {
424
+ console.error('[JS Engine] Online auth fetch exception:', error);
425
+ return "";
378
426
  }
379
- throw this._enhanceError(error, 'Failed to verify app key online');
380
- }
427
+ };
381
428
  }
382
429
 
383
- // 如果用户直接提供了 licenseJson,则直接使用
384
- // 如果通过 appId/appKey 获取到了响应,则使用响应作为 licenseJson
385
- // WASM 层只需要验证 licenseJson,不需要发送 HTTP 请求
430
+ // Create engine instance, auth (online/offline) is handled by WASM layer via onOnlineAuth
386
431
  const enginePtr = Module.ccall(
387
- 'CreateBeautyEffectEngine',
432
+ 'CreateBeautyEffectEngineEx',
388
433
  'number',
389
- ['string', 'string'],
434
+ ['string', 'string', 'string', 'string', 'number'],
390
435
  [
391
436
  this.resourcePath,
392
- licenseJsonToUse || '' // 使用 licenseJson 验证
437
+ this.licenseJson || '',
438
+ this.appId || '',
439
+ this.appKey || '',
440
+ this.config.externalContext ? 1 : 0
393
441
  ]
394
442
  );
395
443
 
@@ -402,9 +450,9 @@ class BeautyEffectEngine {
402
450
 
403
451
  this.enginePtr = enginePtr;
404
452
  this.initialized = true;
405
- this._initPromise = null; // 清除 Promise 缓存,允许重新初始化(如果需要)
453
+ this._initPromise = null; // Clear Promise cache, allow re-initialization (if needed)
406
454
  } catch (error) {
407
- this._initPromise = null; // 清除 Promise 缓存,允许重试
455
+ this._initPromise = null; // Clear Promise cache, allow retry
408
456
  throw error;
409
457
  }
410
458
  })();
@@ -432,11 +480,18 @@ class BeautyEffectEngine {
432
480
  this.dstBufferPtr = null;
433
481
  }
434
482
 
483
+ // Clean up shared memory
484
+ if (this._callbackSharedBufferPtr) {
485
+ Module._free(this._callbackSharedBufferPtr);
486
+ this._callbackSharedBufferPtr = null;
487
+ this._callbackSharedBufferSize = 0;
488
+ }
489
+
435
490
  Module.ccall('DestroyBeautyEffectEngine', null, ['number'], [this.enginePtr]);
436
491
  this.enginePtr = null;
437
492
  this.initialized = false;
438
493
  this.bufferSize = 0;
439
- this._initPromise = null; // 清除初始化 Promise
494
+ this._initPromise = null; // Clear initialization Promise
440
495
 
441
496
  // Clean up offscreen canvas
442
497
  if (this._offscreenCanvas) {
@@ -541,7 +596,7 @@ class BeautyEffectEngine {
541
596
 
542
597
  /**
543
598
  * Sets a basic beauty parameter
544
- * @param {number} param - Parameter (use BasicParam enum)
599
+ * @param {number} param - Parameter (use BasicParam enum, e.g., BasicParam.Smoothing)
545
600
  * @param {number} value - Parameter value (0.0 - 1.0)
546
601
  */
547
602
  setBasicParam(param, value) {
@@ -551,7 +606,7 @@ class BeautyEffectEngine {
551
606
 
552
607
  /**
553
608
  * Sets a reshape parameter
554
- * @param {number} param - Parameter (use ReshapeParam enum)
609
+ * @param {number} param - Parameter (use ReshapeParam enum, e.g., ReshapeParam.FaceThinning)
555
610
  * @param {number} value - Parameter value (0.0 - 1.0)
556
611
  */
557
612
  setReshapeParam(param, value) {
@@ -561,7 +616,7 @@ class BeautyEffectEngine {
561
616
 
562
617
  /**
563
618
  * Sets a makeup parameter
564
- * @param {number} param - Parameter (use MakeupParam enum)
619
+ * @param {number} param - Parameter (use MakeupParam enum, e.g., MakeupParam.Lipstick)
565
620
  * @param {number} value - Parameter value (0.0 - 1.0)
566
621
  */
567
622
  setMakeupParam(param, value) {
@@ -570,7 +625,253 @@ class BeautyEffectEngine {
570
625
  }
571
626
 
572
627
  /**
573
- * Internal method to set beauty parameters
628
+ * Sets chroma key parameter
629
+ * @param {number} param - Parameter (use ChromaKeyParam enum, e.g., ChromaKeyParam.Similarity)
630
+ * @param {number} value - Parameter value (KeyColor: 0.0=Green, 1.0=Blue, 2.0=Red; others 0.0-1.0)
631
+ */
632
+ setChromaKeyParam(param, value) {
633
+ this._ensureInitialized();
634
+ // For KeyColor, values are 0, 1, 2, not 0.0-1.0
635
+ if (param === 0) { // ChromaKeyParam.KeyColor
636
+ const Module = this._getWasmModule();
637
+ const result = Module.ccall(
638
+ 'SetBeautyParamChromaKey',
639
+ 'number',
640
+ ['number', 'number', 'number'],
641
+ [this.enginePtr, param, value]
642
+ );
643
+ checkResult(result, `Failed to set chroma key parameter`);
644
+ return;
645
+ }
646
+ this._setBeautyParam('SetBeautyParamChromaKey', param, value);
647
+ }
648
+
649
+ /**
650
+ * Sets a LUT-based filter
651
+ * @param {string} filterId - Unique identifier of the filter (e.g., "chuxin"). Pass "" to clear.
652
+ */
653
+ setFilter(filterId) {
654
+ this._ensureInitialized();
655
+ const Module = this._getWasmModule();
656
+ const result = Module.ccall(
657
+ 'SetFilter',
658
+ 'number',
659
+ ['number', 'string'],
660
+ [this.enginePtr, filterId || '']
661
+ );
662
+ checkResult(result, 'Failed to set filter');
663
+ }
664
+
665
+ /**
666
+ * Sets the intensity of the current filter
667
+ * @param {number} intensity - Filter intensity (0.0 - 1.0)
668
+ */
669
+ setFilterIntensity(intensity) {
670
+ this._ensureInitialized();
671
+ const Module = this._getWasmModule();
672
+ const result = Module.ccall(
673
+ 'SetFilterIntensity',
674
+ 'number',
675
+ ['number', 'number'],
676
+ [this.enginePtr, intensity]
677
+ );
678
+ checkResult(result, 'Failed to set filter intensity');
679
+ }
680
+
681
+ /**
682
+ * Sets a 2D sticker
683
+ * @param {string} stickerId - Unique identifier of the sticker (e.g., "樱花"). Pass "" to clear.
684
+ */
685
+ setSticker(stickerId) {
686
+ this._ensureInitialized();
687
+ const Module = this._getWasmModule();
688
+ const result = Module.ccall(
689
+ 'SetSticker',
690
+ 'number',
691
+ ['number', 'string'],
692
+ [this.enginePtr, stickerId || '']
693
+ );
694
+ checkResult(result, 'Failed to set sticker');
695
+ }
696
+
697
+ /**
698
+ * Registers a filter from a file path or data
699
+ * @param {string} filterId - Unique identifier for the filter
700
+ * @param {string|Uint8Array} resource - Path to .fbd file or Uint8Array data
701
+ */
702
+ registerFilter(filterId, resource) {
703
+ this._ensureInitialized();
704
+ const Module = this._getWasmModule();
705
+
706
+ if (typeof resource === 'string') {
707
+ const result = Module.ccall(
708
+ 'RegisterFilterPath',
709
+ 'number',
710
+ ['number', 'string', 'string'],
711
+ [this.enginePtr, filterId, resource]
712
+ );
713
+ checkResult(result, `Failed to register filter path: ${filterId}`);
714
+ } else if (resource instanceof Uint8Array) {
715
+ const dataPtr = Module._malloc(resource.length);
716
+ Module.HEAPU8.set(resource, dataPtr);
717
+ try {
718
+ const result = Module.ccall(
719
+ 'RegisterFilterData',
720
+ 'number',
721
+ ['number', 'string', 'number', 'number'],
722
+ [this.enginePtr, filterId, dataPtr, resource.length]
723
+ );
724
+ checkResult(result, `Failed to register filter data: ${filterId}`);
725
+ } finally {
726
+ Module._free(dataPtr);
727
+ }
728
+ } else {
729
+ throw new FacebetterError('Resource must be a string path or Uint8Array data');
730
+ }
731
+ }
732
+
733
+ /**
734
+ * Registers a sticker from a file path or data
735
+ * @param {string} stickerId - Unique identifier for the sticker
736
+ * @param {string|Uint8Array} resource - Path to .fbd file or Uint8Array data
737
+ */
738
+ registerSticker(stickerId, resource) {
739
+ this._ensureInitialized();
740
+ const Module = this._getWasmModule();
741
+
742
+ if (typeof resource === 'string') {
743
+ const result = Module.ccall(
744
+ 'RegisterStickerPath',
745
+ 'number',
746
+ ['number', 'string', 'string'],
747
+ [this.enginePtr, stickerId, resource]
748
+ );
749
+ checkResult(result, `Failed to register sticker path: ${stickerId}`);
750
+ } else if (resource instanceof Uint8Array) {
751
+ const dataPtr = Module._malloc(resource.length);
752
+ Module.HEAPU8.set(resource, dataPtr);
753
+ try {
754
+ const result = Module.ccall(
755
+ 'RegisterStickerData',
756
+ 'number',
757
+ ['number', 'string', 'number', 'number'],
758
+ [this.enginePtr, stickerId, dataPtr, resource.length]
759
+ );
760
+ checkResult(result, `Failed to register sticker data: ${stickerId}`);
761
+ } finally {
762
+ Module._free(dataPtr);
763
+ }
764
+ } else {
765
+ throw new FacebetterError('Resource must be a string path or Uint8Array data');
766
+ }
767
+ }
768
+
769
+ /**
770
+ * Unregisters a specific filter
771
+ * @param {string} filterId - Filter ID to unregister
772
+ */
773
+ unregisterFilter(filterId) {
774
+ this._ensureInitialized();
775
+ const Module = this._getWasmModule();
776
+ const result = Module.ccall(
777
+ 'UnregisterFilter',
778
+ 'number',
779
+ ['number', 'string'],
780
+ [this.enginePtr, filterId]
781
+ );
782
+ checkResult(result, `Failed to unregister filter: ${filterId}`);
783
+ }
784
+
785
+ /**
786
+ * Unregisters all filters
787
+ */
788
+ unregisterAllFilters() {
789
+ this._ensureInitialized();
790
+ const Module = this._getWasmModule();
791
+ const result = Module.ccall(
792
+ 'UnregisterAllFilters',
793
+ 'number',
794
+ ['number'],
795
+ [this.enginePtr]
796
+ );
797
+ checkResult(result, 'Failed to unregister all filters');
798
+ }
799
+
800
+ /**
801
+ * Unregisters a specific sticker
802
+ * @param {string} stickerId - Sticker ID to unregister
803
+ */
804
+ unregisterSticker(stickerId) {
805
+ this._ensureInitialized();
806
+ const Module = this._getWasmModule();
807
+ const result = Module.ccall(
808
+ 'UnregisterSticker',
809
+ 'number',
810
+ ['number', 'string'],
811
+ [this.enginePtr, stickerId]
812
+ );
813
+ checkResult(result, `Failed to unregister sticker: ${stickerId}`);
814
+ }
815
+
816
+ /**
817
+ * Unregisters all stickers
818
+ */
819
+ unregisterAllStickers() {
820
+ this._ensureInitialized();
821
+ const Module = this._getWasmModule();
822
+ const result = Module.ccall(
823
+ 'UnregisterAllStickers',
824
+ 'number',
825
+ ['number'],
826
+ [this.enginePtr]
827
+ );
828
+ checkResult(result, 'Failed to unregister all stickers');
829
+ }
830
+
831
+ /**
832
+ * Gets the list of registered filter IDs
833
+ * @returns {string[]} Array of registered filter IDs
834
+ */
835
+ getRegisteredFilters() {
836
+ this._ensureInitialized();
837
+ const Module = this._getWasmModule();
838
+ const jsonStr = Module.ccall(
839
+ 'GetRegisteredFilters',
840
+ 'string',
841
+ ['number'],
842
+ [this.enginePtr]
843
+ );
844
+ try {
845
+ return JSON.parse(jsonStr || '[]');
846
+ } catch (e) {
847
+ console.error('Failed to parse registered filters JSON:', e);
848
+ return [];
849
+ }
850
+ }
851
+
852
+ /**
853
+ * Gets the list of registered sticker IDs
854
+ * @returns {string[]} Array of registered sticker IDs
855
+ */
856
+ getRegisteredStickers() {
857
+ this._ensureInitialized();
858
+ const Module = this._getWasmModule();
859
+ const jsonStr = Module.ccall(
860
+ 'GetRegisteredStickers',
861
+ 'string',
862
+ ['number'],
863
+ [this.enginePtr]
864
+ );
865
+ try {
866
+ return JSON.parse(jsonStr || '[]');
867
+ } catch (e) {
868
+ console.error('Failed to parse registered stickers JSON:', e);
869
+ return [];
870
+ }
871
+ }
872
+
873
+ /**
874
+ * Internal method to set beauty parameters
574
875
  * @private
575
876
  */
576
877
  _setBeautyParam(functionName, param, value) {
@@ -589,6 +890,139 @@ class BeautyEffectEngine {
589
890
  checkResult(result, `Failed to set beauty parameter`);
590
891
  }
591
892
 
893
+ /**
894
+ * Sets engine callbacks (face landmarks detection)
895
+ * @param {Object} callbacks - Callback functions
896
+ * @param {Function} callbacks.onFaceLandmarks - Callback for face landmarks detection
897
+ * @param {number} [callbacks.maxFaces=10] - Maximum number of faces to support (affects shared buffer size)
898
+ */
899
+ setCallbacks(callbacks) {
900
+ this._ensureInitialized();
901
+ const Module = this._getWasmModule();
902
+
903
+ // Initialize callback storage (if it doesn't exist)
904
+ if (!Module._face_landmarks_callbacks) {
905
+ Module._face_landmarks_callbacks = [];
906
+ }
907
+
908
+ let callbackId = 0;
909
+ let sharedBufferPtr = null;
910
+ let sharedBufferSize = 0;
911
+
912
+ if (callbacks && callbacks.onFaceLandmarks) {
913
+ // Get max faces (default 10)
914
+ const maxFaces = callbacks.maxFaces || 10;
915
+
916
+ // Pre-allocate shared memory
917
+ // Metadata: 2 ints (frame_number, face_count) = 8 bytes
918
+ // Face data: maxFaces * 343 floats * 4 bytes = maxFaces * 1372 bytes
919
+ const metadataSize = 2 * 4; // 2 ints
920
+ const faceDataSize = maxFaces * 343 * 4; // 343 floats per face
921
+ sharedBufferSize = metadataSize + faceDataSize;
922
+ sharedBufferPtr = Module._malloc(sharedBufferSize);
923
+
924
+ if (!sharedBufferPtr) {
925
+ throw new FacebetterError('Failed to allocate shared buffer for callbacks');
926
+ }
927
+
928
+ // Register callback function (internal wrapper, reads data from shared memory)
929
+ // Push first, then get index as callbackId (allows callbackId to be 0)
930
+ Module._face_landmarks_callbacks.push(
931
+ (frameNumber, faceCount, dataPtr) => {
932
+ // Read data from shared memory (zero-copy)
933
+ const heap = Module.HEAPF32;
934
+ const dataOffset = dataPtr / 4; // float is 4 bytes
935
+
936
+ const results = [];
937
+ const FLOATS_PER_FACE = 343;
938
+
939
+ for (let i = 0; i < faceCount; i++) {
940
+ const faceOffset = dataOffset + i * FLOATS_PER_FACE;
941
+ let offset = faceOffset;
942
+
943
+ // Read rect
944
+ const rect = {
945
+ x: heap[offset++],
946
+ y: heap[offset++],
947
+ width: heap[offset++],
948
+ height: heap[offset++]
949
+ };
950
+
951
+ // Read basic fields
952
+ const faceId = Math.round(heap[offset++]);
953
+ const faceAction = Math.round(heap[offset++]);
954
+ const score = heap[offset++];
955
+ const pitch = heap[offset++];
956
+ const roll = heap[offset++];
957
+ const yaw = heap[offset++];
958
+
959
+ // Read key_points (111 points)
960
+ const keyPoints = [];
961
+ for (let j = 0; j < 111; j++) {
962
+ keyPoints.push({
963
+ x: heap[offset++],
964
+ y: heap[offset++]
965
+ });
966
+ }
967
+
968
+ // Read visibility (111 points)
969
+ const visibility = [];
970
+ for (let j = 0; j < 111; j++) {
971
+ visibility.push(heap[offset++]);
972
+ }
973
+
974
+ results.push({
975
+ rect,
976
+ key_points: keyPoints,
977
+ visibility,
978
+ face_id: faceId,
979
+ face_action: faceAction,
980
+ score,
981
+ pitch,
982
+ roll,
983
+ yaw
984
+ });
985
+ }
986
+
987
+ // Call user callback
988
+ callbacks.onFaceLandmarks(results);
989
+ }
990
+ );
991
+
992
+ // Get callback ID (index after push, can be 0)
993
+ callbackId = Module._face_landmarks_callbacks.length - 1;
994
+ }
995
+
996
+ // Call C API
997
+ const result = Module.ccall(
998
+ 'SetCallbacks',
999
+ 'number',
1000
+ ['number', 'number', 'number', 'number'],
1001
+ [
1002
+ this.enginePtr,
1003
+ callbackId,
1004
+ sharedBufferPtr || 0,
1005
+ sharedBufferSize
1006
+ ]
1007
+ );
1008
+
1009
+ checkResult(result, 'Failed to set callbacks');
1010
+
1011
+ // Clean up old shared memory (if it exists)
1012
+ if (this._callbackSharedBufferPtr) {
1013
+ Module._free(this._callbackSharedBufferPtr);
1014
+ }
1015
+
1016
+ // Store new shared memory pointer for cleanup
1017
+ if (sharedBufferPtr) {
1018
+ this._callbackSharedBufferPtr = sharedBufferPtr;
1019
+ this._callbackSharedBufferSize = sharedBufferSize;
1020
+ } else {
1021
+ this._callbackSharedBufferPtr = null;
1022
+ this._callbackSharedBufferSize = 0;
1023
+ }
1024
+ }
1025
+
592
1026
  /**
593
1027
  * Sets virtual background options (unified API, matches C++/Java/OC)
594
1028
  * @param {VirtualBackgroundOptions|Object} options - Virtual background options
@@ -606,7 +1040,7 @@ class BeautyEffectEngine {
606
1040
  let imageBufferPtr = null;
607
1041
 
608
1042
  try {
609
- // 如果提供了背景图片,转换为 ImageData 并准备缓冲区
1043
+ // If a background image is provided, convert to ImageData and prepare buffer
610
1044
  if (opts.backgroundImage) {
611
1045
  imageData = this._toImageData(opts.backgroundImage);
612
1046
  const bufferSize = imageData.width * imageData.height * 4;
@@ -617,7 +1051,7 @@ class BeautyEffectEngine {
617
1051
  view.set(imageData.data);
618
1052
  }
619
1053
 
620
- // 使用统一的 SetVirtualBackground C 接口(与其他平台一致)
1054
+ // Use unified SetVirtualBackground C interface (consistent with other platforms)
621
1055
  const result = Module.ccall(
622
1056
  'SetVirtualBackground',
623
1057
  'number',
@@ -625,7 +1059,7 @@ class BeautyEffectEngine {
625
1059
  [
626
1060
  this.enginePtr,
627
1061
  opts.mode,
628
- imageBufferPtr || 0, // 如果为 null,传递 0
1062
+ imageBufferPtr || 0, // If null, pass 0
629
1063
  imageData ? imageData.width : 0,
630
1064
  imageData ? imageData.height : 0,
631
1065
  imageData ? imageData.width * 4 : 0
@@ -634,7 +1068,7 @@ class BeautyEffectEngine {
634
1068
 
635
1069
  checkResult(result, 'Failed to set virtual background');
636
1070
  } finally {
637
- // 释放内存
1071
+ // Free memory
638
1072
  if (imageBufferPtr) {
639
1073
  Module._free(imageBufferPtr);
640
1074
  }
@@ -674,10 +1108,11 @@ class BeautyEffectEngine {
674
1108
  * @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement|Uint8ClampedArray} input - Input image
675
1109
  * @param {number} width - Image width (required if input is Uint8ClampedArray)
676
1110
  * @param {number} height - Image height (required if input is Uint8ClampedArray)
677
- * @param {number} processMode - Process mode (use ProcessMode enum, default: ProcessMode.Image)
1111
+ * @param {number} frameType - Frame type (use FrameType enum, default: FrameType.Video)
1112
+ * @param {number} mirrorMode - Mirror mode applied to input before processing (MirrorMode enum, default: MirrorMode.None)
678
1113
  * @returns {ImageData} Processed image data
679
1114
  */
680
- processImage(input, width, height, processMode = ProcessMode$1.Image) {
1115
+ processImage(input, width, height, frameType = FrameType$1.Video, mirrorMode = MirrorMode$1.None) {
681
1116
  this._ensureInitialized();
682
1117
  const imageData = this._platformAPI.toImageData(input, width, height);
683
1118
  const Module = this._getWasmModule();
@@ -695,7 +1130,7 @@ class BeautyEffectEngine {
695
1130
  const result = Module.ccall(
696
1131
  'ProcessImageRGBA',
697
1132
  'number',
698
- ['number', 'number', 'number', 'number', 'number', 'number', 'number'],
1133
+ ['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'],
699
1134
  [
700
1135
  this.enginePtr,
701
1136
  this.srcBufferPtr,
@@ -703,7 +1138,8 @@ class BeautyEffectEngine {
703
1138
  imageData.height,
704
1139
  imageData.width * 4,
705
1140
  this.dstBufferPtr,
706
- processMode
1141
+ frameType,
1142
+ mirrorMode
707
1143
  ]
708
1144
  );
709
1145
 
@@ -722,6 +1158,65 @@ class BeautyEffectEngine {
722
1158
  }
723
1159
  }
724
1160
 
1161
+ /**
1162
+ * Processes an external GPU texture.
1163
+ *
1164
+ * Note:
1165
+ * - This interface is mainly used for integration with internal WASM or advanced scenarios.
1166
+ * - textureHandle must be a texture handle (uint32) compatible with the OpenGL/WebGL context used by WASM.
1167
+ * - For common web scenarios, it is recommended to use processImage with ImageData/Canvas etc.
1168
+ *
1169
+ * @param {number} textureHandle - External texture handle (GL_TEXTURE_2D)
1170
+ * @param {number} width - Texture width
1171
+ * @param {number} height - Texture height
1172
+ * @param {number} stride - Row stride (usually width * 4)
1173
+ * @param {number} [frameType] - Frame type (FrameType.Image / FrameType.Video)
1174
+ * @param {number} [mirrorMode] - Mirror mode (MirrorMode enum; texture path does not support mirror, param reserved)
1175
+ * @returns {number} Processed texture handle (GL_TEXTURE_2D, uint32)
1176
+ */
1177
+ processTexture(textureHandle,
1178
+ width,
1179
+ height,
1180
+ stride,
1181
+ frameType = FrameType$1.Video,
1182
+ mirrorMode = MirrorMode$1.None) {
1183
+ this._ensureInitialized();
1184
+ if (!textureHandle || width <= 0 || height <= 0 || stride <= 0) {
1185
+ throw new FacebetterError('Invalid textureHandle or dimensions for processTexture');
1186
+ }
1187
+
1188
+ const Module = this._getWasmModule();
1189
+
1190
+ try {
1191
+ const result = Module.ccall(
1192
+ 'ProcessImageTexture',
1193
+ 'number',
1194
+ ['number', 'number', 'number', 'number', 'number', 'number', 'number'],
1195
+ [
1196
+ this.enginePtr,
1197
+ textureHandle,
1198
+ width,
1199
+ height,
1200
+ stride,
1201
+ frameType,
1202
+ mirrorMode
1203
+ ]
1204
+ );
1205
+
1206
+ if (!result || result === 0) {
1207
+ throw new FacebetterError('Failed to process external texture or got invalid output texture');
1208
+ }
1209
+
1210
+ // Return processed texture handle for caller to continue using in same GL context
1211
+ return result;
1212
+ } catch (error) {
1213
+ if (error instanceof FacebetterError) {
1214
+ throw error;
1215
+ }
1216
+ throw new FacebetterError(`Texture processing error: ${error.message}`);
1217
+ }
1218
+ }
1219
+
725
1220
  /**
726
1221
  * Ensures the engine is initialized
727
1222
  * @private
@@ -842,77 +1337,6 @@ function toImageData(source, width, height) {
842
1337
  throw new FacebetterError('Unsupported image source type');
843
1338
  }
844
1339
 
845
- /**
846
- * Gets User-Agent string for web platform
847
- * @returns {string} User-Agent string
848
- */
849
- function getUserAgentString() {
850
- return 'FB/1.0 (web; wasm32)';
851
- }
852
-
853
- /**
854
- * Generates HMAC-SHA256 signature (browser version)
855
- * @param {string} key - HMAC key (app_key)
856
- * @param {string} data - Data to sign (timestamp string)
857
- * @returns {Promise<string>} Promise that resolves with hex-encoded signature
858
- */
859
- async function generateHMACSignature(key, data) {
860
- const encoder = new TextEncoder();
861
- const keyData = encoder.encode(key);
862
- const messageData = encoder.encode(data);
863
-
864
- const cryptoKey = await crypto.subtle.importKey(
865
- 'raw',
866
- keyData,
867
- { name: 'HMAC', hash: 'SHA-256' },
868
- false,
869
- ['sign']
870
- );
871
-
872
- const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData);
873
-
874
- // Convert to hex string
875
- const hashArray = Array.from(new Uint8Array(signature));
876
- const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
877
- return hashHex;
878
- }
879
-
880
- /**
881
- * Calls online_auth API to verify app_key (browser version)
882
- * @param {string} appId - Application ID
883
- * @param {string} appKey - Application key
884
- * @returns {Promise<Object>} Promise that resolves with response data
885
- */
886
- async function verifyAppKeyOnline(appId, appKey) {
887
- try {
888
- const timestamp = Math.floor(Date.now() / 1000);
889
- const timestampStr = timestamp.toString();
890
- const hmacSignature = await generateHMACSignature(appKey, timestampStr);
891
-
892
- const requestBody = {
893
- p_app_id: appId,
894
- p_hmac_signature: hmacSignature,
895
- p_timestamp: timestamp,
896
- p_user_agent: getUserAgentString()
897
- };
898
-
899
- const response = await fetch('https://facebetter.pixpark.net/rest/v1/rpc/online_auth', {
900
- method: 'POST',
901
- headers: {
902
- 'Content-Type': 'application/json',
903
- 'User-Agent': getUserAgentString()
904
- },
905
- body: JSON.stringify(requestBody)
906
- });
907
-
908
- const responseData = await response.json();
909
- return responseData;
910
- } catch (error) {
911
- console.error('Online auth request failed:', error);
912
- return null;
913
- }
914
- }
915
-
916
1340
  /**
917
1341
  * ESM Entry Point
918
1342
  * For Vue/React/Vite/Webpack (ES Module)
@@ -1054,7 +1478,6 @@ const platformAPI = {
1054
1478
  getWasmModule,
1055
1479
  getWasmBuffer,
1056
1480
  toImageData: toImageData,
1057
- verifyAppKeyOnline: verifyAppKeyOnline,
1058
1481
  ensureGPUPixelCanvas,
1059
1482
  cleanupGPUPixelCanvas
1060
1483
  };
@@ -1078,8 +1501,10 @@ const BeautyType = BeautyType$1;
1078
1501
  const BasicParam = BasicParam$1;
1079
1502
  const ReshapeParam = ReshapeParam$1;
1080
1503
  const MakeupParam = MakeupParam$1;
1504
+ const ChromaKeyParam = ChromaKeyParam$1;
1081
1505
  const BackgroundMode = BackgroundMode$1;
1082
- const ProcessMode = ProcessMode$1;
1506
+ const FrameType = FrameType$1;
1507
+ const MirrorMode = MirrorMode$1;
1083
1508
  const VirtualBackgroundOptions = VirtualBackgroundOptions$1;
1084
1509
 
1085
1510
  // Default export
@@ -1091,5 +1516,5 @@ var index = {
1091
1516
  loadWasmModule
1092
1517
  };
1093
1518
 
1094
- export { BackgroundMode, BasicParam, ESMBeautyEffectEngine as BeautyEffectEngine, BeautyType, EngineConfig, FacebetterError, MakeupParam, ProcessMode, ReshapeParam, VirtualBackgroundOptions, createBeautyEffectEngine, index as default, getWasmBuffer, getWasmModule, loadWasmModule };
1519
+ export { BackgroundMode, BasicParam, ESMBeautyEffectEngine as BeautyEffectEngine, BeautyType, ChromaKeyParam, EngineConfig, FacebetterError, FrameType, MakeupParam, MirrorMode, ReshapeParam, VirtualBackgroundOptions, createBeautyEffectEngine, index as default, getWasmBuffer, getWasmModule, loadWasmModule };
1095
1520
  //# sourceMappingURL=facebetter.esm.js.map