facebetter 1.5.1 → 2.0.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.
- package/README.md +100 -30
- package/dist/facebetter.esm.js +970 -660
- package/dist/facebetter.esm.js.map +1 -1
- package/dist/facebetter.js +987 -654
- package/dist/facebetter.js.map +1 -1
- package/package.json +8 -3
package/dist/facebetter.js
CHANGED
|
@@ -4,6 +4,43 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Facebetter = {}));
|
|
5
5
|
})(this, (function (exports) { 'use strict';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Engine Configuration
|
|
9
|
+
* Platform-agnostic configuration class
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Engine configuration class
|
|
14
|
+
* Analogous to EngineConfig in C++ / Java / Objective-C. Web only accepts licenseToken.
|
|
15
|
+
*/
|
|
16
|
+
class EngineConfig {
|
|
17
|
+
/**
|
|
18
|
+
* @param {Object} config
|
|
19
|
+
* @param {string} config.licenseToken Compact JWS, or the raw `{success, token}`
|
|
20
|
+
* HTTP response body from your auth server.
|
|
21
|
+
* @param {boolean} [config.externalContext] Kept for cross-platform alignment;
|
|
22
|
+
* unused on Web today.
|
|
23
|
+
*/
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
this.licenseToken = config.licenseToken || null;
|
|
26
|
+
this.resourcePath = '/resource.fbd';
|
|
27
|
+
/**
|
|
28
|
+
* Whether to use an external GL context (native platforms only).
|
|
29
|
+
* On Web this field is kept for config-shape alignment and has no effect.
|
|
30
|
+
*/
|
|
31
|
+
this.externalContext = !!config.externalContext;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
isValid() {
|
|
35
|
+
return typeof this.licenseToken === 'string' &&
|
|
36
|
+
this.licenseToken.trim() !== '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
toString() {
|
|
40
|
+
return `EngineConfig{mode='${this.isValid() ? 'licenseToken' : 'invalid'}'}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
7
44
|
/**
|
|
8
45
|
* Facebetter Error Classes
|
|
9
46
|
* Platform-agnostic error handling
|
|
@@ -32,257 +69,246 @@
|
|
|
32
69
|
}
|
|
33
70
|
|
|
34
71
|
/**
|
|
35
|
-
*
|
|
36
|
-
* Platform-agnostic
|
|
72
|
+
* Facebetter Constants and Enums
|
|
73
|
+
* Platform-agnostic constants. Ordinals match facebetter::beauty_params.
|
|
37
74
|
*/
|
|
38
75
|
|
|
76
|
+
const WhiteningStyle$1 = {
|
|
77
|
+
ColdWhite: 0, // Cool white
|
|
78
|
+
PinkWhite: 1, // Pink white
|
|
79
|
+
WarmWhite: 2, // Warm white
|
|
80
|
+
Wheat: 3, // Light wheat / olive
|
|
81
|
+
Tan: 4 // Tan / bronzed
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const SmoothingStyle$1 = {
|
|
85
|
+
Natural: 0, // Natural: keeps pores
|
|
86
|
+
Texture: 1, // Cleaner skin while retaining texture
|
|
87
|
+
Smooth: 2 // Creamy / porcelain finish
|
|
88
|
+
};
|
|
39
89
|
|
|
40
90
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
91
|
+
* Face reshape parameters.
|
|
92
|
+
* Values match facebetter::beauty_params::Reshape.
|
|
93
|
+
* Intensity range is [-1.0, 1.0]; 0 is off. Comments list + / - directions.
|
|
43
94
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
95
|
+
const Reshape$1 = {
|
|
96
|
+
FaceThin: 0, // +slim face / -fuller cheeks
|
|
97
|
+
FaceVShape: 1, // +V-shape jaw / -square jaw
|
|
98
|
+
FaceNarrow: 2, // +narrow face / -wider face
|
|
99
|
+
FaceShort: 3, // +shorter face / -longer face
|
|
100
|
+
Cheekbone: 4, // +slim cheekbones / -wider cheekbones
|
|
101
|
+
Jawbone: 5, // +slim jaw / -wider jaw
|
|
102
|
+
Chin: 6, // +longer chin / -shorter chin
|
|
103
|
+
NoseSlim: 7, // +slimmer nose / -wider nose
|
|
104
|
+
EyeSize: 8, // +larger eyes / -smaller eyes
|
|
105
|
+
EyeDistance: 9, // +wider eye spacing / -closer eyes
|
|
106
|
+
FaceSmall: 10, // +smaller face / -larger face
|
|
107
|
+
Forehead: 11, // +fuller forehead / -lower forehead
|
|
108
|
+
NoseLong: 12, // +longer nose / -shorter nose
|
|
109
|
+
Philtrum: 13, // +shorter philtrum / -longer philtrum
|
|
110
|
+
MouthSize: 14, // +larger mouth / -smaller mouth
|
|
111
|
+
MouthPosition: 15, // +mouth lower / -mouth higher
|
|
112
|
+
MouthSmile: 16, // +smile lift / -droop corners
|
|
113
|
+
LipThickness: 17, // +thicker lips / -thinner lips
|
|
114
|
+
EyeRound: 18, // +rounder eyes / -narrower eyes
|
|
115
|
+
EyePosition: 19, // +eyes lower / -eyes higher
|
|
116
|
+
EyeAngle: 20, // +outer corner up / -outer corner down
|
|
117
|
+
EyeCornerOpen: 21, // +open eye corners / -close eye corners
|
|
118
|
+
LowerEyelid: 22, // +lower eyelid down / -lift lower eyelid
|
|
119
|
+
BrowPosition: 23, // +brows higher / -brows lower
|
|
120
|
+
BrowDistance: 24, // +wider brow spacing / -closer brows
|
|
121
|
+
BrowThickness: 25 // +thicker brows / -thinner brows
|
|
122
|
+
};
|
|
64
123
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (this.licenseJson) {
|
|
72
|
-
return typeof this.licenseJson === 'string' && this.licenseJson.trim() !== '';
|
|
73
|
-
}
|
|
74
|
-
// Otherwise appId and appKey are required
|
|
75
|
-
return this.appId && typeof this.appId === 'string' && this.appId.trim() !== '' &&
|
|
76
|
-
this.appKey && typeof this.appKey === 'string' && this.appKey.trim() !== '';
|
|
77
|
-
}
|
|
124
|
+
const EngineEventCode$1 = {
|
|
125
|
+
LicenseValidationSuccess: 0,
|
|
126
|
+
LicenseValidationFailed: 1,
|
|
127
|
+
InitializationComplete: 100,
|
|
128
|
+
InitializationFailed: 101
|
|
129
|
+
};
|
|
78
130
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
131
|
+
const LipstickColor$1 = {
|
|
132
|
+
Rouge: 0, // Classic rose
|
|
133
|
+
RetroRed: 1, // Retro red
|
|
134
|
+
Peach: 2, // Peach
|
|
135
|
+
CoralOrange: 3, // Coral orange
|
|
136
|
+
GentlePink: 4, // Soft pink
|
|
137
|
+
VitalityOrange: 5 // Bright orange
|
|
138
|
+
};
|
|
87
139
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
140
|
+
const BlushStyle$1 = {
|
|
141
|
+
SunKissed: 0, // Sun-kissed sweep across cheekbones and bridge
|
|
142
|
+
Igari: 1, // Flushed band across the nose bridge
|
|
143
|
+
Soft: 2, // Soft dual cheeks
|
|
144
|
+
Apple: 3, // Round apple cheeks
|
|
145
|
+
Classic: 4, // Classic two spots
|
|
146
|
+
Doll: 5, // Cheeks + nose tip
|
|
147
|
+
Rose: 6 // Rose dual cheeks
|
|
148
|
+
};
|
|
92
149
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
Makeup: 2, // Makeup effects (lipstick, blush, etc.)
|
|
100
|
-
VirtualBackground: 3, // Virtual background (blur, image replacement)
|
|
101
|
-
ChromaKey: 4, // Chroma key (green screen removal)
|
|
102
|
-
Filter: 5, // LUT based filter
|
|
103
|
-
Sticker: 6 // 2D/3D sticker
|
|
150
|
+
const BlushColor$1 = {
|
|
151
|
+
CoralPink: 0, // Coral pink
|
|
152
|
+
DustyRose: 1, // Dusty rose
|
|
153
|
+
VividRed: 2, // Vivid red
|
|
154
|
+
Berry: 3, // Berry
|
|
155
|
+
SunsetOrange: 4 // Sunset orange
|
|
104
156
|
};
|
|
105
157
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Whitening: 2, // Skin whitening (0.0: none, 1.0: maximum)
|
|
114
|
-
Rosiness: 3 // Skin rosiness (0.0: none, 1.0: maximum)
|
|
158
|
+
const ContourStyle$1 = {
|
|
159
|
+
Natural: 0, // Soft everyday contour
|
|
160
|
+
Sculpt: 1, // Deeper sculpted contour
|
|
161
|
+
Glow: 2, // Highlight-focused
|
|
162
|
+
Slim: 3, // Slim cheekbones
|
|
163
|
+
Nose: 4, // Nose bridge lift
|
|
164
|
+
Glam: 5 // Spot highlights
|
|
115
165
|
};
|
|
116
166
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
FaceShortening: 3,// Face shortening (0.0: none, 1.0: maximum)
|
|
126
|
-
Cheekbone: 4, // Cheekbone slimming (0.0: none, 1.0: maximum)
|
|
127
|
-
Jawbone: 5, // Jawbone slimming (0.0: none, 1.0: maximum)
|
|
128
|
-
Chin: 6, // Chin length adjustment (0.0: none, 1.0: maximum)
|
|
129
|
-
NoseSlimming: 7, // Nose slimming (0.0: none, 1.0: maximum)
|
|
130
|
-
EyeSize: 8, // Eye size (0.0: normal, 1.0: maximum)
|
|
131
|
-
EyeDistance: 9 // Eye distance (0.0: normal, 1.0: maximum)
|
|
167
|
+
const EyeShadowStyle$1 = {
|
|
168
|
+
Soft: 0, // Soft wash
|
|
169
|
+
Crease: 1, // Crease deepen
|
|
170
|
+
Smoky: 2, // Smoky surround
|
|
171
|
+
Halo: 3, // Halo around the eye
|
|
172
|
+
Glow: 4, // Outer-corner glow
|
|
173
|
+
Drama: 5, // Full dramatic cover
|
|
174
|
+
Warm: 6 // Warm-tone wash
|
|
132
175
|
};
|
|
133
176
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
Lipstick: 0, // Lipstick intensity (0.0: none, 1.0: maximum)
|
|
140
|
-
Blush: 1 // Blush intensity (0.0: none, 1.0: maximum)
|
|
177
|
+
const EyeShadowColor$1 = {
|
|
178
|
+
Plum: 0, // Plum / mauve (default)
|
|
179
|
+
Brown: 1, // Warm brown
|
|
180
|
+
Gold: 2, // Soft gold
|
|
181
|
+
Pink: 3 // Dusty pink
|
|
141
182
|
};
|
|
142
183
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
184
|
+
const EyeLinerStyle$1 = {
|
|
185
|
+
Classic: 0, // Full liner with winged tip
|
|
186
|
+
Flick: 1, // Extended outer flick
|
|
187
|
+
CatEye: 2, // Short cat-eye lift
|
|
188
|
+
Natural: 3, // Thin soft natural line
|
|
189
|
+
Bold: 4, // Bold cover
|
|
190
|
+
Soft: 5 // Soft feathered wing
|
|
150
191
|
};
|
|
151
192
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
Rose: 2 // Rose
|
|
193
|
+
const EyeLinerColor$1 = {
|
|
194
|
+
Burgundy: 0, // Burgundy brown
|
|
195
|
+
Plum: 1, // Deep plum
|
|
196
|
+
Chocolate: 2, // Chocolate brown
|
|
197
|
+
Coffee: 3, // Near-black coffee
|
|
198
|
+
Mauve: 4 // Mauve grey
|
|
159
199
|
};
|
|
160
200
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
201
|
+
const EyebrowStyle$1 = {
|
|
202
|
+
Natural: 0, // Natural arch with hair strokes
|
|
203
|
+
Soft: 1, // Soft powdery thick brow
|
|
204
|
+
Feathered: 2, // Feathered hair strokes
|
|
205
|
+
Mist: 3, // Light powder mist
|
|
206
|
+
Arched: 4, // Classic high arch
|
|
207
|
+
Powder: 5, // Dense powder fill
|
|
208
|
+
Wild: 6, // Wispy upper edge
|
|
209
|
+
Full: 7, // Full balanced brow
|
|
210
|
+
Straight: 8 // Straight low arch
|
|
169
211
|
};
|
|
170
212
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
None: 0, // No background processing
|
|
176
|
-
Blur: 1, // Blurred background
|
|
177
|
-
Image: 2 // Background image replacement
|
|
213
|
+
const EyebrowColor$1 = {
|
|
214
|
+
DarkBrown: 0, // Dark brown (default)
|
|
215
|
+
Black: 1, // Black
|
|
216
|
+
SoftBrown: 2 // Soft brown
|
|
178
217
|
};
|
|
179
218
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
219
|
+
const EyelashStyle$1 = {
|
|
220
|
+
Classic: 0, // Balanced clusters + lower lashes
|
|
221
|
+
Manga: 1, // Bold spiked clusters
|
|
222
|
+
Winged: 2, // Extended outer corner
|
|
223
|
+
Wispy: 3, // Fluffy lifted tips
|
|
224
|
+
Clustered: 4, // Distinct clusters
|
|
225
|
+
Doll: 5 // Short doll lashes
|
|
186
226
|
};
|
|
187
227
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
None: 0, // No mirror
|
|
193
|
-
Horizontal: 1, // Mirror horizontally (e.g. front camera selfie)
|
|
194
|
-
Vertical: 2, // Mirror vertically
|
|
195
|
-
Both: 3 // Mirror both axes
|
|
228
|
+
const EyelashColor$1 = {
|
|
229
|
+
Black: 0, // Near-black (default)
|
|
230
|
+
Brown: 1, // Soft brown
|
|
231
|
+
SoftBlack: 2 // Slightly softer black
|
|
196
232
|
};
|
|
197
233
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
constructor(options = {}) {
|
|
210
|
-
this.mode = options.mode !== undefined ? options.mode : BackgroundMode$1.None;
|
|
211
|
-
this.backgroundImage = options.backgroundImage || null;
|
|
212
|
-
}
|
|
234
|
+
const PupilColor$1 = {
|
|
235
|
+
Hazel: 0, // Amber / honey brown
|
|
236
|
+
Ice: 1, // Icy blue
|
|
237
|
+
Mocha: 2, // Dark brown with sparkle
|
|
238
|
+
Olive: 3, // Forest green
|
|
239
|
+
Gloss: 4, // Glossy dark brown
|
|
240
|
+
Moss: 5, // Muted olive
|
|
241
|
+
Sand: 6, // Sandy beige brown
|
|
242
|
+
Glow: 7, // Lower-iris crescent highlight
|
|
243
|
+
Slate: 8 // Cool blue-grey
|
|
244
|
+
};
|
|
213
245
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
246
|
+
const ChromaKeyColor$1 = {
|
|
247
|
+
Green: 0,
|
|
248
|
+
Blue: 1,
|
|
249
|
+
Red: 2
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/** Frame type for processing (affects temporal smoothing / tracking). */
|
|
253
|
+
const FrameType$1 = {
|
|
254
|
+
Image: 0, // Still image
|
|
255
|
+
Video: 1 // Continuous video stream
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/** Mirror mode applied to the input before processing. */
|
|
259
|
+
const MirrorMode$1 = {
|
|
260
|
+
None: 0, // No mirror
|
|
261
|
+
Horizontal: 1, // Horizontal (e.g. front-camera selfie)
|
|
262
|
+
Vertical: 2, // Vertical
|
|
263
|
+
Both: 3 // Both axes
|
|
224
264
|
};
|
|
225
265
|
|
|
226
266
|
var constants = /*#__PURE__*/Object.freeze({
|
|
227
267
|
__proto__: null,
|
|
228
|
-
|
|
229
|
-
BasicParam: BasicParam$1,
|
|
230
|
-
BeautyType: BeautyType$1,
|
|
268
|
+
BlushColor: BlushColor$1,
|
|
231
269
|
BlushStyle: BlushStyle$1,
|
|
232
|
-
|
|
270
|
+
ChromaKeyColor: ChromaKeyColor$1,
|
|
271
|
+
ContourStyle: ContourStyle$1,
|
|
272
|
+
EngineEventCode: EngineEventCode$1,
|
|
273
|
+
EyeLinerColor: EyeLinerColor$1,
|
|
274
|
+
EyeLinerStyle: EyeLinerStyle$1,
|
|
275
|
+
EyeShadowColor: EyeShadowColor$1,
|
|
276
|
+
EyeShadowStyle: EyeShadowStyle$1,
|
|
277
|
+
EyebrowColor: EyebrowColor$1,
|
|
278
|
+
EyebrowStyle: EyebrowStyle$1,
|
|
279
|
+
EyelashColor: EyelashColor$1,
|
|
280
|
+
EyelashStyle: EyelashStyle$1,
|
|
233
281
|
FrameType: FrameType$1,
|
|
234
|
-
|
|
235
|
-
MakeupParam: MakeupParam$1,
|
|
282
|
+
LipstickColor: LipstickColor$1,
|
|
236
283
|
MirrorMode: MirrorMode$1,
|
|
237
|
-
|
|
238
|
-
|
|
284
|
+
PupilColor: PupilColor$1,
|
|
285
|
+
Reshape: Reshape$1,
|
|
286
|
+
SmoothingStyle: SmoothingStyle$1,
|
|
287
|
+
WhiteningStyle: WhiteningStyle$1
|
|
239
288
|
});
|
|
240
289
|
|
|
241
290
|
/**
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
* spdlog level names: info, warning, error (see spdlog SPDLOG_LEVEL_NAMES).
|
|
291
|
+
* Beauty Effect Engine Core
|
|
292
|
+
* Platform-agnostic engine implementation with dependency injection
|
|
245
293
|
*/
|
|
246
294
|
|
|
247
|
-
function pad2(n) {
|
|
248
|
-
return String(n).padStart(2, '0');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* @param {'info'|'warning'|'error'} levelTag — must match spdlog long level strings
|
|
253
|
-
* @param {string} message
|
|
254
|
-
* @returns {string}
|
|
255
|
-
*/
|
|
256
|
-
function formatLogLine(levelTag, message) {
|
|
257
|
-
const d = new Date();
|
|
258
|
-
const ms = String(d.getMilliseconds()).padStart(3, '0');
|
|
259
|
-
const ts = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(
|
|
260
|
-
d.getHours()
|
|
261
|
-
)}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}.${ms}`;
|
|
262
|
-
return `[${ts}] [facebetter] [${levelTag}] [0] - ${message}`;
|
|
263
|
-
}
|
|
264
295
|
|
|
265
|
-
/**
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
296
|
+
/** Byte layouts of fb_engine_config_t / fb_log_config_t on wasm32. */
|
|
297
|
+
const FB_ENGINE_CONFIG_SIZE = 28;
|
|
298
|
+
const FB_LOG_CONFIG_SIZE = 16;
|
|
269
299
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
300
|
+
function allocUtf8(Module, text) {
|
|
301
|
+
const value = text || '';
|
|
302
|
+
const len = Module.lengthBytesUTF8(value) + 1;
|
|
303
|
+
const ptr = Module._malloc(len);
|
|
304
|
+
Module.stringToUTF8(value, ptr, len);
|
|
305
|
+
return ptr;
|
|
273
306
|
}
|
|
274
307
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
console.error(formatLogLine('error', message));
|
|
308
|
+
function writeI32(Module, ptr, index, value) {
|
|
309
|
+
Module.HEAP32[(ptr >> 2) + index] = value;
|
|
278
310
|
}
|
|
279
311
|
|
|
280
|
-
/**
|
|
281
|
-
* Beauty Effect Engine Core
|
|
282
|
-
* Platform-agnostic engine implementation with dependency injection
|
|
283
|
-
*/
|
|
284
|
-
|
|
285
|
-
|
|
286
312
|
/**
|
|
287
313
|
* Beauty effect engine class
|
|
288
314
|
* Provides high-level API for face beauty effects processing
|
|
@@ -297,9 +323,8 @@
|
|
|
297
323
|
* @param {Function} platformAPI.getWasmModule - Get WASM module instance
|
|
298
324
|
* @param {Function} platformAPI.getWasmBuffer - Get WASM memory buffer
|
|
299
325
|
* @param {Function} platformAPI.toImageData - Convert to ImageData
|
|
300
|
-
* @param {Function} platformAPI.
|
|
301
|
-
* @param {Function} [platformAPI.
|
|
302
|
-
* @param {Function} [platformAPI.cleanupGPUPixelCanvas] - Cleanup GPU canvas (browser only)
|
|
326
|
+
* @param {Function} [platformAPI.ensureFacebetterCanvas] - Ensure GPU canvas exists (browser only)
|
|
327
|
+
* @param {Function} [platformAPI.cleanupFacebetterCanvas] - Cleanup GPU canvas (browser only)
|
|
303
328
|
*/
|
|
304
329
|
constructor(config, platformAPI) {
|
|
305
330
|
if (!config) {
|
|
@@ -325,9 +350,7 @@
|
|
|
325
350
|
}
|
|
326
351
|
|
|
327
352
|
this.config = engineConfig;
|
|
328
|
-
this.
|
|
329
|
-
this.appKey = engineConfig.appKey;
|
|
330
|
-
this.licenseJson = engineConfig.licenseJson;
|
|
353
|
+
this.licenseToken = engineConfig.licenseToken;
|
|
331
354
|
this.resourcePath = '/resource.fbd';
|
|
332
355
|
this.enginePtr = null;
|
|
333
356
|
this.initialized = false;
|
|
@@ -345,22 +368,22 @@
|
|
|
345
368
|
this._getWasmModule = platformAPI.getWasmModule;
|
|
346
369
|
this._getWasmBuffer = platformAPI.getWasmBuffer;
|
|
347
370
|
this._toImageData = platformAPI.toImageData;
|
|
348
|
-
this.
|
|
349
|
-
this.
|
|
350
|
-
|
|
371
|
+
this._ensureFacebetterCanvas = platformAPI.ensureFacebetterCanvas;
|
|
372
|
+
this._cleanupFacebetterCanvas = platformAPI.cleanupFacebetterCanvas;
|
|
373
|
+
this._pendingLogConfig = null;
|
|
374
|
+
|
|
351
375
|
// Browser-specific state
|
|
352
|
-
this.
|
|
353
|
-
this.
|
|
376
|
+
this._facebetterCanvas = null;
|
|
377
|
+
this._createdFacebetterCanvas = false;
|
|
354
378
|
this._offscreenCanvas = null;
|
|
355
379
|
this._offscreenCtx = null;
|
|
356
380
|
|
|
357
|
-
// Automatically create
|
|
358
|
-
if (this.
|
|
359
|
-
this.
|
|
381
|
+
// Automatically create facebetter_canvas if it doesn't exist (browser only)
|
|
382
|
+
if (this._ensureFacebetterCanvas) {
|
|
383
|
+
this._ensureFacebetterCanvas();
|
|
360
384
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
this._wasmLoadPromise = this._loadWasmModule();
|
|
385
|
+
|
|
386
|
+
this._wasmLoadPromise = null;
|
|
364
387
|
this._initPromise = null;
|
|
365
388
|
}
|
|
366
389
|
|
|
@@ -417,8 +440,12 @@
|
|
|
417
440
|
/**
|
|
418
441
|
* Initializes the engine
|
|
419
442
|
* @param {Object} [options] - Initialization options
|
|
420
|
-
* @param {number} [options.timeout] - Timeout in milliseconds (default:
|
|
421
|
-
* @param {number} [options.authTimeout] -
|
|
443
|
+
* @param {number} [options.timeout] - Timeout in milliseconds (default: 120000)
|
|
444
|
+
* @param {number} [options.authTimeout] - Unused. Kept for compatibility.
|
|
445
|
+
* @param {function} [options.onProgress] - Download progress:
|
|
446
|
+
* `{ loaded, total, percent }` while fetching the runtime files.
|
|
447
|
+
* @param {string} [options.assetBaseUrl] - Directory that contains
|
|
448
|
+
* `facebetter-core.wasm` and `resource.fbd`. Optional when using npm.
|
|
422
449
|
* @returns {Promise<void>} Promise that resolves when initialization is complete
|
|
423
450
|
*/
|
|
424
451
|
async init(options = {}) {
|
|
@@ -435,9 +462,18 @@
|
|
|
435
462
|
// Create initialization Promise
|
|
436
463
|
this._initPromise = (async () => {
|
|
437
464
|
try {
|
|
438
|
-
const wasmTimeout = options.timeout ||
|
|
465
|
+
const wasmTimeout = options.timeout || 120000;
|
|
466
|
+
|
|
467
|
+
if (!this._wasmLoadPromise) {
|
|
468
|
+
this._wasmLoadPromise = this._loadWasmModule({
|
|
469
|
+
onProgress: options.onProgress,
|
|
470
|
+
assetBaseUrl: options.assetBaseUrl,
|
|
471
|
+
wasmUrl: options.wasmUrl,
|
|
472
|
+
wasmAssetUrl: options.wasmAssetUrl,
|
|
473
|
+
resourceAssetUrl: options.resourceAssetUrl,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
439
476
|
|
|
440
|
-
// Wait for WASM module loading (with timeout)
|
|
441
477
|
try {
|
|
442
478
|
await Promise.race([
|
|
443
479
|
this._wasmLoadPromise,
|
|
@@ -447,12 +483,21 @@
|
|
|
447
483
|
throw this._enhanceError(error, 'Failed to load WASM module');
|
|
448
484
|
}
|
|
449
485
|
|
|
486
|
+
if (this._pendingLogConfig) {
|
|
487
|
+
await this._applyLogConfig(this._pendingLogConfig);
|
|
488
|
+
this._pendingLogConfig = null;
|
|
489
|
+
}
|
|
490
|
+
|
|
450
491
|
const Module = this._getWasmModule();
|
|
451
492
|
|
|
452
493
|
// Setup usage report proxy for WASM
|
|
453
494
|
if (!Module.onReportUsage) {
|
|
454
495
|
Module.onReportUsage = async (payloadJson) => {
|
|
455
496
|
try {
|
|
497
|
+
const payload = JSON.parse(payloadJson);
|
|
498
|
+
if (!payload.app_id || !payload.hmac_signature) {
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
456
501
|
const url = 'https://facebetter.pixpark.net/facebetter/v1/report';
|
|
457
502
|
const response = await fetch(url, {
|
|
458
503
|
method: 'POST',
|
|
@@ -468,48 +513,43 @@
|
|
|
468
513
|
};
|
|
469
514
|
}
|
|
470
515
|
|
|
471
|
-
//
|
|
472
|
-
if (!
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const response = await fetch(authUrl, {
|
|
478
|
-
method: 'POST',
|
|
479
|
-
headers: {
|
|
480
|
-
'Content-Type': 'application/json',
|
|
481
|
-
},
|
|
482
|
-
body: payloadJson,
|
|
483
|
-
});
|
|
484
|
-
if (!response.ok) {
|
|
485
|
-
logWarn(`Online license: HTTP ${response.status}`);
|
|
486
|
-
return '';
|
|
487
|
-
}
|
|
488
|
-
const text = await response.text();
|
|
489
|
-
logInfo('Online license: server response ok');
|
|
490
|
-
return text;
|
|
491
|
-
} catch (err) {
|
|
492
|
-
logWarn(
|
|
493
|
-
`Online license: network error${err?.message ? `: ${err.message}` : ''}`
|
|
494
|
-
);
|
|
495
|
-
return '';
|
|
496
|
-
}
|
|
497
|
-
};
|
|
516
|
+
// Web: fetch the token outside the engine, then pass licenseToken.
|
|
517
|
+
if (!this.licenseToken) {
|
|
518
|
+
throw new FacebetterError(
|
|
519
|
+
'Web auth requires licenseToken. Fetch it from your server before init().',
|
|
520
|
+
'LICENSE_ERROR'
|
|
521
|
+
);
|
|
498
522
|
}
|
|
499
523
|
|
|
500
|
-
//
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
);
|
|
524
|
+
// C ABI still has app_id/app_key slots; leave them empty on Web.
|
|
525
|
+
const appIdPtr = allocUtf8(Module, '');
|
|
526
|
+
const appKeyPtr = allocUtf8(Module, '');
|
|
527
|
+
const licensePtr = allocUtf8(Module, this.licenseToken);
|
|
528
|
+
const resourcePtr = allocUtf8(Module, this.resourcePath);
|
|
529
|
+
const configPtr = Module._malloc(FB_ENGINE_CONFIG_SIZE);
|
|
530
|
+
writeI32(Module, configPtr, 0, appIdPtr);
|
|
531
|
+
writeI32(Module, configPtr, 1, appKeyPtr);
|
|
532
|
+
writeI32(Module, configPtr, 2, licensePtr);
|
|
533
|
+
writeI32(Module, configPtr, 3, resourcePtr);
|
|
534
|
+
writeI32(Module, configPtr, 4, 0);
|
|
535
|
+
writeI32(Module, configPtr, 5, this.config.externalContext ? 1 : 0);
|
|
536
|
+
writeI32(Module, configPtr, 6, 0);
|
|
537
|
+
|
|
538
|
+
let enginePtr = 0;
|
|
539
|
+
try {
|
|
540
|
+
enginePtr = Module.ccall(
|
|
541
|
+
'fb_engine_create',
|
|
542
|
+
'number',
|
|
543
|
+
['number'],
|
|
544
|
+
[configPtr]
|
|
545
|
+
);
|
|
546
|
+
} finally {
|
|
547
|
+
Module._free(configPtr);
|
|
548
|
+
Module._free(appIdPtr);
|
|
549
|
+
Module._free(appKeyPtr);
|
|
550
|
+
Module._free(licensePtr);
|
|
551
|
+
Module._free(resourcePtr);
|
|
552
|
+
}
|
|
513
553
|
|
|
514
554
|
if (!enginePtr) {
|
|
515
555
|
throw new FacebetterError(
|
|
@@ -522,7 +562,8 @@
|
|
|
522
562
|
this.initialized = true;
|
|
523
563
|
this._initPromise = null; // Clear Promise cache, allow re-initialization (if needed)
|
|
524
564
|
} catch (error) {
|
|
525
|
-
this._initPromise = null;
|
|
565
|
+
this._initPromise = null;
|
|
566
|
+
this._wasmLoadPromise = null;
|
|
526
567
|
throw error;
|
|
527
568
|
}
|
|
528
569
|
})();
|
|
@@ -557,7 +598,10 @@
|
|
|
557
598
|
this._callbackSharedBufferSize = 0;
|
|
558
599
|
}
|
|
559
600
|
|
|
560
|
-
Module.ccall('
|
|
601
|
+
Module.ccall('fb_engine_destroy', null, ['number'], [this.enginePtr]);
|
|
602
|
+
if (Module._engine_event_callbacks) {
|
|
603
|
+
delete Module._engine_event_callbacks[this.enginePtr];
|
|
604
|
+
}
|
|
561
605
|
this.enginePtr = null;
|
|
562
606
|
this.initialized = false;
|
|
563
607
|
this.bufferSize = 0;
|
|
@@ -569,15 +613,16 @@
|
|
|
569
613
|
this._offscreenCtx = null;
|
|
570
614
|
}
|
|
571
615
|
|
|
572
|
-
// Clean up
|
|
573
|
-
if (this.
|
|
574
|
-
this.
|
|
616
|
+
// Clean up facebetter_canvas if we created it
|
|
617
|
+
if (this._cleanupFacebetterCanvas) {
|
|
618
|
+
this._cleanupFacebetterCanvas();
|
|
575
619
|
}
|
|
576
620
|
}
|
|
577
621
|
|
|
578
622
|
/**
|
|
579
|
-
* Sets log configuration
|
|
580
|
-
*
|
|
623
|
+
* Sets log configuration.
|
|
624
|
+
* If called before `init()`, the values are applied after the runtime downloads
|
|
625
|
+
* and before the engine is created.
|
|
581
626
|
* @param {Object} config - Log configuration
|
|
582
627
|
* @param {boolean} config.consoleEnabled - Enable console logging
|
|
583
628
|
* @param {boolean} config.fileEnabled - Enable file logging
|
|
@@ -586,410 +631,426 @@
|
|
|
586
631
|
* @returns {Promise<void>} Promise that resolves when log config is set
|
|
587
632
|
*/
|
|
588
633
|
async setLogConfig(config) {
|
|
589
|
-
|
|
634
|
+
if (!this._wasmLoadPromise) {
|
|
635
|
+
this._pendingLogConfig = config;
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
590
638
|
await this._wasmLoadPromise;
|
|
639
|
+
await this._applyLogConfig(config);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async _applyLogConfig(config) {
|
|
591
643
|
const Module = this._getWasmModule();
|
|
592
644
|
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
645
|
+
const fileNamePtr = allocUtf8(Module, config.fileName);
|
|
646
|
+
const configPtr = Module._malloc(FB_LOG_CONFIG_SIZE);
|
|
647
|
+
writeI32(Module, configPtr, 0, config.consoleEnabled ? 1 : 0);
|
|
648
|
+
writeI32(Module, configPtr, 1, config.fileEnabled ? 1 : 0);
|
|
649
|
+
writeI32(Module, configPtr, 2, config.level || 0);
|
|
650
|
+
writeI32(Module, configPtr, 3, fileNamePtr);
|
|
651
|
+
|
|
652
|
+
let result;
|
|
653
|
+
try {
|
|
654
|
+
result = Module.ccall(
|
|
655
|
+
'fb_set_log_config',
|
|
656
|
+
'number',
|
|
657
|
+
['number'],
|
|
658
|
+
[configPtr]
|
|
659
|
+
);
|
|
660
|
+
} finally {
|
|
661
|
+
Module._free(configPtr);
|
|
662
|
+
Module._free(fileNamePtr);
|
|
663
|
+
}
|
|
604
664
|
|
|
605
665
|
checkResult(result, 'Failed to set log config');
|
|
606
666
|
}
|
|
607
667
|
|
|
608
668
|
/**
|
|
609
|
-
*
|
|
610
|
-
* -
|
|
611
|
-
* - setVirtualBackground
|
|
612
|
-
* - setFilter/setSticker
|
|
613
|
-
* Kept for compatibility and may be removed in a future release.
|
|
614
|
-
* @deprecated
|
|
615
|
-
* @param {number} beautyType - Beauty type (use BeautyType enum)
|
|
616
|
-
* @param {boolean} enabled - Enable or disable
|
|
669
|
+
* Sets skin smoothing intensity
|
|
670
|
+
* @param {number} value - Parameter value (0.0 - 1.0)
|
|
617
671
|
*/
|
|
618
|
-
|
|
672
|
+
setSmoothing(value) {
|
|
619
673
|
this._ensureInitialized();
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const result = Module.ccall(
|
|
623
|
-
'SetBeautyTypeEnabled',
|
|
624
|
-
'number',
|
|
625
|
-
['number', 'number', 'number'],
|
|
626
|
-
[this.enginePtr, beautyType, enabled ? 1 : 0]
|
|
627
|
-
);
|
|
674
|
+
this._setIntensity('fb_set_smoothing', value);
|
|
675
|
+
}
|
|
628
676
|
|
|
629
|
-
|
|
677
|
+
/**
|
|
678
|
+
* Sets skin smoothing style
|
|
679
|
+
* @param {number} style - Style (use SmoothingStyle enum, e.g., SmoothingStyle.Natural)
|
|
680
|
+
*/
|
|
681
|
+
setSmoothingStyle(style) {
|
|
682
|
+
this._setMakeupStyle('fb_set_smoothing_style', style, 'Failed to set smoothing style');
|
|
630
683
|
}
|
|
631
684
|
|
|
632
685
|
/**
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
* Kept for compatibility and may be removed in a future release.
|
|
636
|
-
* @deprecated
|
|
637
|
-
* @param {number} beautyType - Beauty type (use BeautyType enum)
|
|
638
|
-
* @returns {boolean} True if enabled
|
|
686
|
+
* Sets skin whitening intensity
|
|
687
|
+
* @param {number} value - Parameter value (0.0 - 1.0)
|
|
639
688
|
*/
|
|
640
|
-
|
|
689
|
+
setWhitening(value) {
|
|
641
690
|
this._ensureInitialized();
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const result = Module.ccall(
|
|
645
|
-
'IsBeautyTypeEnabled',
|
|
646
|
-
'number',
|
|
647
|
-
['number', 'number'],
|
|
648
|
-
[this.enginePtr, beautyType]
|
|
649
|
-
);
|
|
650
|
-
|
|
651
|
-
if (result === -1) {
|
|
652
|
-
throw new FacebetterError('Failed to check beauty type status');
|
|
653
|
-
}
|
|
691
|
+
this._setIntensity('fb_set_whitening', value);
|
|
692
|
+
}
|
|
654
693
|
|
|
655
|
-
|
|
694
|
+
/**
|
|
695
|
+
* Sets whitening style by swapping the custom LUT
|
|
696
|
+
* @param {number} style - Style (use WhiteningStyle enum, e.g., WhiteningStyle.ColdWhite)
|
|
697
|
+
*/
|
|
698
|
+
setWhiteningStyle(style) {
|
|
699
|
+
this._setMakeupStyle('fb_set_whitening_style', style, 'Failed to set whitening style');
|
|
656
700
|
}
|
|
657
701
|
|
|
658
702
|
/**
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
* be removed in a future release.
|
|
662
|
-
* @deprecated
|
|
703
|
+
* Sets image sharpening intensity
|
|
704
|
+
* @param {number} value - Parameter value (0.0 - 1.0)
|
|
663
705
|
*/
|
|
664
|
-
|
|
706
|
+
setSharpening(value) {
|
|
665
707
|
this._ensureInitialized();
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
const result = Module.ccall(
|
|
669
|
-
'DisableAllBeautyTypes',
|
|
670
|
-
'number',
|
|
671
|
-
['number'],
|
|
672
|
-
[this.enginePtr]
|
|
673
|
-
);
|
|
674
|
-
|
|
675
|
-
checkResult(result, 'Failed to disable all beauty types');
|
|
708
|
+
this._setIntensity('fb_set_sharpening', value);
|
|
676
709
|
}
|
|
677
710
|
|
|
678
711
|
/**
|
|
679
|
-
* Sets
|
|
680
|
-
* @param {number} param - Parameter (use BasicParam enum, e.g., BasicParam.Smoothing)
|
|
712
|
+
* Sets skin rosiness intensity
|
|
681
713
|
* @param {number} value - Parameter value (0.0 - 1.0)
|
|
682
714
|
*/
|
|
683
|
-
|
|
715
|
+
setRosiness(value) {
|
|
684
716
|
this._ensureInitialized();
|
|
685
|
-
this.
|
|
717
|
+
this._setIntensity('fb_set_rosiness', value);
|
|
686
718
|
}
|
|
687
719
|
|
|
688
720
|
/**
|
|
689
721
|
* Sets a reshape parameter
|
|
690
|
-
* @param {number} param - Parameter (use
|
|
691
|
-
* @param {number} value - Parameter value
|
|
722
|
+
* @param {number} param - Parameter (use Reshape enum, e.g., Reshape.FaceThin)
|
|
723
|
+
* @param {number} value - Parameter value in [-1.0, 1.0]. 0 is off.
|
|
692
724
|
*/
|
|
693
|
-
|
|
725
|
+
setReshape(param, value) {
|
|
694
726
|
this._ensureInitialized();
|
|
695
|
-
this._setBeautyParam('
|
|
727
|
+
this._setBeautyParam('fb_set_reshape', param, value);
|
|
696
728
|
}
|
|
697
729
|
|
|
698
730
|
/**
|
|
699
|
-
* Sets
|
|
700
|
-
* @param {number} param - Parameter (use MakeupParam enum, e.g., MakeupParam.Lipstick)
|
|
731
|
+
* Sets lipstick intensity
|
|
701
732
|
* @param {number} value - Parameter value (0.0 - 1.0)
|
|
702
733
|
*/
|
|
703
|
-
|
|
734
|
+
setLipstick(value) {
|
|
704
735
|
this._ensureInitialized();
|
|
705
|
-
this.
|
|
736
|
+
this._setIntensity('fb_set_lipstick', value);
|
|
706
737
|
}
|
|
707
738
|
|
|
708
739
|
/**
|
|
709
|
-
* Sets lipstick
|
|
710
|
-
* @param {number} style - Style (use
|
|
740
|
+
* Sets lipstick colour preset
|
|
741
|
+
* @param {number} style - Style (use LipstickColor enum, e.g., LipstickColor.Rouge)
|
|
711
742
|
*/
|
|
712
|
-
|
|
713
|
-
this.
|
|
714
|
-
const Module = this._getWasmModule();
|
|
715
|
-
const result = Module.ccall(
|
|
716
|
-
'SetLipstickStyle',
|
|
717
|
-
'number',
|
|
718
|
-
['number', 'number'],
|
|
719
|
-
[this.enginePtr, style]
|
|
720
|
-
);
|
|
721
|
-
checkResult(result, 'Failed to set lipstick style');
|
|
743
|
+
setLipstickColor(style) {
|
|
744
|
+
this._setMakeupStyle('fb_set_lipstick_color', style, 'Failed to set lipstick style');
|
|
722
745
|
}
|
|
723
746
|
|
|
724
747
|
/**
|
|
725
|
-
* Sets blush
|
|
726
|
-
* @param {number}
|
|
748
|
+
* Sets blush intensity
|
|
749
|
+
* @param {number} value - Parameter value (0.0 - 1.0)
|
|
727
750
|
*/
|
|
751
|
+
setBlush(value) {
|
|
752
|
+
this._ensureInitialized();
|
|
753
|
+
this._setIntensity('fb_set_blush', value);
|
|
754
|
+
}
|
|
755
|
+
|
|
728
756
|
setBlushStyle(style) {
|
|
757
|
+
this._setMakeupStyle('fb_set_blush_style', style, 'Failed to set blush style');
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
setBlushColor(color) {
|
|
761
|
+
this._setMakeupStyle('fb_set_blush_color', color, 'Failed to set blush color');
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
setContour(value) {
|
|
729
765
|
this._ensureInitialized();
|
|
730
|
-
|
|
731
|
-
const result = Module.ccall(
|
|
732
|
-
'SetBlushStyle',
|
|
733
|
-
'number',
|
|
734
|
-
['number', 'number'],
|
|
735
|
-
[this.enginePtr, style]
|
|
736
|
-
);
|
|
737
|
-
checkResult(result, 'Failed to set blush style');
|
|
766
|
+
this._setIntensity('fb_set_contour', value);
|
|
738
767
|
}
|
|
739
768
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
setChromaKeyParam(param, value) {
|
|
769
|
+
setContourStyle(style) {
|
|
770
|
+
this._setMakeupStyle('fb_set_contour_style', style, 'Failed to set contour style');
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
setEyeShadow(value) {
|
|
746
774
|
this._ensureInitialized();
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
this.
|
|
775
|
+
this._setIntensity('fb_set_eye_shadow', value);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
setEyeShadowStyle(style) {
|
|
779
|
+
this._setMakeupStyle('fb_set_eye_shadow_style', style, 'Failed to set eyeshadow style');
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
setEyeShadowColor(style) {
|
|
783
|
+
this._setMakeupStyle('fb_set_eye_shadow_color', style, 'Failed to set eyeshadow style');
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
setEyeLiner(value) {
|
|
787
|
+
this._ensureInitialized();
|
|
788
|
+
this._setIntensity('fb_set_eye_liner', value);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
setEyeLinerStyle(style) {
|
|
792
|
+
this._setMakeupStyle('fb_set_eye_liner_style', style, 'Failed to set eyeliner style');
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
setEyeLinerColor(style) {
|
|
796
|
+
this._setMakeupStyle('fb_set_eye_liner_color', style, 'Failed to set eyeliner style');
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
setEyebrow(value) {
|
|
800
|
+
this._ensureInitialized();
|
|
801
|
+
this._setIntensity('fb_set_eyebrow', value);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
setEyebrowStyle(style) {
|
|
805
|
+
this._setMakeupStyle('fb_set_eyebrow_style', style, 'Failed to set eyebrow style');
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
setEyebrowColor(style) {
|
|
809
|
+
this._setMakeupStyle('fb_set_eyebrow_color', style, 'Failed to set eyebrow style');
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
setEyelash(value) {
|
|
813
|
+
this._ensureInitialized();
|
|
814
|
+
this._setIntensity('fb_set_eyelash', value);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
setEyelashStyle(style) {
|
|
818
|
+
this._setMakeupStyle('fb_set_eyelash_style', style, 'Failed to set eyelash style');
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
setEyelashColor(style) {
|
|
822
|
+
this._setMakeupStyle('fb_set_eyelash_color', style, 'Failed to set eyelash style');
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
setPupil(value) {
|
|
826
|
+
this._ensureInitialized();
|
|
827
|
+
this._setIntensity('fb_set_pupil', value);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
setPupilColor(style) {
|
|
831
|
+
this._setMakeupStyle('fb_set_pupil_color', style, 'Failed to set pupil style');
|
|
760
832
|
}
|
|
761
833
|
|
|
762
834
|
/**
|
|
763
|
-
*
|
|
764
|
-
*
|
|
835
|
+
* Uses chroma keying as the virtual-background mask.
|
|
836
|
+
* Fill is still controlled by setVirtualBackgroundBlur / setVirtualBackground.
|
|
837
|
+
* @param {number} color - Key colour (use ChromaKeyColor enum)
|
|
765
838
|
*/
|
|
766
|
-
|
|
767
|
-
this.
|
|
768
|
-
const Module = this._getWasmModule();
|
|
769
|
-
const result = Module.ccall(
|
|
770
|
-
'SetFilter',
|
|
771
|
-
'number',
|
|
772
|
-
['number', 'string'],
|
|
773
|
-
[this.enginePtr, filterId || '']
|
|
774
|
-
);
|
|
775
|
-
checkResult(result, 'Failed to set filter');
|
|
839
|
+
setChromaKey(color) {
|
|
840
|
+
this._setMakeupStyle('fb_set_chroma_key', color, 'Failed to set chroma key');
|
|
776
841
|
}
|
|
777
842
|
|
|
778
843
|
/**
|
|
779
|
-
*
|
|
780
|
-
* @param {number} intensity - Filter intensity (0.0 - 1.0)
|
|
844
|
+
* Turns off chroma keying and restores portrait-segmentation as the mask.
|
|
781
845
|
*/
|
|
782
|
-
|
|
846
|
+
clearChromaKey() {
|
|
783
847
|
this._ensureInitialized();
|
|
784
848
|
const Module = this._getWasmModule();
|
|
785
849
|
const result = Module.ccall(
|
|
786
|
-
'
|
|
850
|
+
'fb_clear_chroma_key',
|
|
787
851
|
'number',
|
|
788
|
-
['number'
|
|
789
|
-
[this.enginePtr
|
|
852
|
+
['number'],
|
|
853
|
+
[this.enginePtr]
|
|
790
854
|
);
|
|
791
|
-
checkResult(result, 'Failed to
|
|
855
|
+
checkResult(result, 'Failed to clear chroma key');
|
|
792
856
|
}
|
|
793
857
|
|
|
794
|
-
/**
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
858
|
+
/** How close a pixel must be to the key colour to be keyed out. [0.0, 1.0]. */
|
|
859
|
+
setChromaKeySimilarity(value) {
|
|
860
|
+
this._setChromaKeyFloat('fb_set_chroma_key_similarity', value);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/** Edge feather around the key. [0.0, 1.0]. */
|
|
864
|
+
setChromaKeySmoothness(value) {
|
|
865
|
+
this._setChromaKeyFloat('fb_set_chroma_key_smoothness', value);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/** Spill suppression on semi-transparent edges. [0.0, 1.0]. */
|
|
869
|
+
setChromaKeyDesaturation(value) {
|
|
870
|
+
this._setChromaKeyFloat('fb_set_chroma_key_desaturation', value);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
_setChromaKeyFloat(cFuncName, value) {
|
|
799
874
|
this._ensureInitialized();
|
|
800
875
|
const Module = this._getWasmModule();
|
|
801
876
|
const result = Module.ccall(
|
|
802
|
-
|
|
877
|
+
cFuncName,
|
|
803
878
|
'number',
|
|
804
|
-
['number', '
|
|
805
|
-
[this.enginePtr,
|
|
879
|
+
['number', 'number'],
|
|
880
|
+
[this.enginePtr, value]
|
|
806
881
|
);
|
|
807
|
-
checkResult(result,
|
|
882
|
+
checkResult(result, `Failed to call ${cFuncName}`);
|
|
808
883
|
}
|
|
809
884
|
|
|
810
885
|
/**
|
|
811
|
-
*
|
|
812
|
-
* @param {string}
|
|
813
|
-
* @param {string|Uint8Array} resource - Path to .fbd file or Uint8Array data
|
|
886
|
+
* Applies a LUT filter from a file path or in-memory .fbd data.
|
|
887
|
+
* @param {string|Uint8Array} resource - Path to .fbd file or Uint8Array data.
|
|
814
888
|
*/
|
|
815
|
-
|
|
889
|
+
setFilter(resource) {
|
|
816
890
|
this._ensureInitialized();
|
|
817
891
|
const Module = this._getWasmModule();
|
|
818
892
|
|
|
819
893
|
if (typeof resource === 'string') {
|
|
894
|
+
if (!resource) {
|
|
895
|
+
throw new FacebetterError('Filter path must not be empty; use clearFilter()');
|
|
896
|
+
}
|
|
820
897
|
const result = Module.ccall(
|
|
821
|
-
'
|
|
898
|
+
'fb_set_filter',
|
|
822
899
|
'number',
|
|
823
|
-
['number', 'string'
|
|
824
|
-
[this.enginePtr,
|
|
900
|
+
['number', 'string'],
|
|
901
|
+
[this.enginePtr, resource]
|
|
825
902
|
);
|
|
826
|
-
checkResult(result,
|
|
827
|
-
|
|
903
|
+
checkResult(result, 'Failed to set filter from path');
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if (resource instanceof Uint8Array) {
|
|
828
908
|
const dataPtr = Module._malloc(resource.length);
|
|
829
909
|
Module.HEAPU8.set(resource, dataPtr);
|
|
830
910
|
try {
|
|
831
911
|
const result = Module.ccall(
|
|
832
|
-
'
|
|
912
|
+
'fb_set_filter_data',
|
|
833
913
|
'number',
|
|
834
|
-
['number', '
|
|
835
|
-
[this.enginePtr,
|
|
914
|
+
['number', 'number', 'number'],
|
|
915
|
+
[this.enginePtr, dataPtr, resource.length]
|
|
836
916
|
);
|
|
837
|
-
checkResult(result,
|
|
917
|
+
checkResult(result, 'Failed to set filter from data');
|
|
838
918
|
} finally {
|
|
839
919
|
Module._free(dataPtr);
|
|
840
920
|
}
|
|
841
|
-
|
|
842
|
-
throw new FacebetterError('Resource must be a string path or Uint8Array data');
|
|
921
|
+
return;
|
|
843
922
|
}
|
|
923
|
+
|
|
924
|
+
throw new FacebetterError('Filter resource must be a string path or Uint8Array');
|
|
844
925
|
}
|
|
845
926
|
|
|
846
927
|
/**
|
|
847
|
-
*
|
|
848
|
-
* @param {string} stickerId - Unique identifier for the sticker
|
|
849
|
-
* @param {string|Uint8Array} resource - Path to .fbd file or Uint8Array data
|
|
928
|
+
* Clears the current LUT filter.
|
|
850
929
|
*/
|
|
851
|
-
|
|
930
|
+
clearFilter() {
|
|
931
|
+
this._ensureInitialized();
|
|
932
|
+
const Module = this._getWasmModule();
|
|
933
|
+
const result = Module.ccall(
|
|
934
|
+
'fb_clear_filter',
|
|
935
|
+
'number',
|
|
936
|
+
['number'],
|
|
937
|
+
[this.enginePtr]
|
|
938
|
+
);
|
|
939
|
+
checkResult(result, 'Failed to clear filter');
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Sets the intensity of the current filter
|
|
944
|
+
* @param {number} intensity - Filter intensity (0.0 - 1.0)
|
|
945
|
+
*/
|
|
946
|
+
setFilterIntensity(intensity) {
|
|
947
|
+
this._ensureInitialized();
|
|
948
|
+
const Module = this._getWasmModule();
|
|
949
|
+
const result = Module.ccall(
|
|
950
|
+
'fb_set_filter_intensity',
|
|
951
|
+
'number',
|
|
952
|
+
['number', 'number'],
|
|
953
|
+
[this.enginePtr, intensity]
|
|
954
|
+
);
|
|
955
|
+
checkResult(result, 'Failed to set filter intensity');
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Applies a 2D sticker from a file path or in-memory .fbd data.
|
|
960
|
+
* @param {string|Uint8Array} resource - Path to .fbd file or Uint8Array data.
|
|
961
|
+
*/
|
|
962
|
+
setSticker(resource) {
|
|
852
963
|
this._ensureInitialized();
|
|
853
964
|
const Module = this._getWasmModule();
|
|
854
965
|
|
|
855
966
|
if (typeof resource === 'string') {
|
|
967
|
+
if (!resource) {
|
|
968
|
+
throw new FacebetterError('Sticker path must not be empty; use clearSticker()');
|
|
969
|
+
}
|
|
856
970
|
const result = Module.ccall(
|
|
857
|
-
'
|
|
971
|
+
'fb_set_sticker',
|
|
858
972
|
'number',
|
|
859
|
-
['number', 'string'
|
|
860
|
-
[this.enginePtr,
|
|
973
|
+
['number', 'string'],
|
|
974
|
+
[this.enginePtr, resource]
|
|
861
975
|
);
|
|
862
|
-
checkResult(result,
|
|
863
|
-
|
|
976
|
+
checkResult(result, 'Failed to set sticker from path');
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (resource instanceof Uint8Array) {
|
|
864
981
|
const dataPtr = Module._malloc(resource.length);
|
|
865
982
|
Module.HEAPU8.set(resource, dataPtr);
|
|
866
983
|
try {
|
|
867
984
|
const result = Module.ccall(
|
|
868
|
-
'
|
|
985
|
+
'fb_set_sticker_data',
|
|
869
986
|
'number',
|
|
870
|
-
['number', '
|
|
871
|
-
[this.enginePtr,
|
|
987
|
+
['number', 'number', 'number'],
|
|
988
|
+
[this.enginePtr, dataPtr, resource.length]
|
|
872
989
|
);
|
|
873
|
-
checkResult(result,
|
|
990
|
+
checkResult(result, 'Failed to set sticker from data');
|
|
874
991
|
} finally {
|
|
875
992
|
Module._free(dataPtr);
|
|
876
993
|
}
|
|
877
|
-
|
|
878
|
-
throw new FacebetterError('Resource must be a string path or Uint8Array data');
|
|
994
|
+
return;
|
|
879
995
|
}
|
|
996
|
+
|
|
997
|
+
throw new FacebetterError('Sticker resource must be a string path or Uint8Array');
|
|
880
998
|
}
|
|
881
999
|
|
|
882
1000
|
/**
|
|
883
|
-
*
|
|
884
|
-
* @param {string} filterId - Filter ID to unregister
|
|
1001
|
+
* Clears the current 2D sticker.
|
|
885
1002
|
*/
|
|
886
|
-
|
|
1003
|
+
clearSticker() {
|
|
887
1004
|
this._ensureInitialized();
|
|
888
1005
|
const Module = this._getWasmModule();
|
|
889
1006
|
const result = Module.ccall(
|
|
890
|
-
'
|
|
1007
|
+
'fb_clear_sticker',
|
|
891
1008
|
'number',
|
|
892
|
-
['number'
|
|
893
|
-
[this.enginePtr
|
|
1009
|
+
['number'],
|
|
1010
|
+
[this.enginePtr]
|
|
894
1011
|
);
|
|
895
|
-
checkResult(result,
|
|
1012
|
+
checkResult(result, 'Failed to clear sticker');
|
|
896
1013
|
}
|
|
897
1014
|
|
|
898
1015
|
/**
|
|
899
|
-
*
|
|
1016
|
+
* Internal method to set a single intensity parameter
|
|
1017
|
+
* @private
|
|
900
1018
|
*/
|
|
901
|
-
|
|
902
|
-
|
|
1019
|
+
_setIntensity(functionName, value) {
|
|
1020
|
+
if (value < 0 || value > 1) {
|
|
1021
|
+
throw new FacebetterError('Parameter value must be between 0.0 and 1.0');
|
|
1022
|
+
}
|
|
1023
|
+
|
|
903
1024
|
const Module = this._getWasmModule();
|
|
904
1025
|
const result = Module.ccall(
|
|
905
|
-
|
|
1026
|
+
functionName,
|
|
906
1027
|
'number',
|
|
907
|
-
['number'],
|
|
908
|
-
[this.enginePtr]
|
|
1028
|
+
['number', 'number'],
|
|
1029
|
+
[this.enginePtr, value]
|
|
909
1030
|
);
|
|
910
|
-
|
|
1031
|
+
|
|
1032
|
+
checkResult(result, `Failed to set beauty parameter`);
|
|
911
1033
|
}
|
|
912
1034
|
|
|
913
|
-
|
|
914
|
-
* Unregisters a specific sticker
|
|
915
|
-
* @param {string} stickerId - Sticker ID to unregister
|
|
916
|
-
*/
|
|
917
|
-
unregisterSticker(stickerId) {
|
|
1035
|
+
_setMakeupStyle(cFuncName, value, errorMessage) {
|
|
918
1036
|
this._ensureInitialized();
|
|
919
1037
|
const Module = this._getWasmModule();
|
|
920
1038
|
const result = Module.ccall(
|
|
921
|
-
|
|
1039
|
+
cFuncName,
|
|
922
1040
|
'number',
|
|
923
|
-
['number', '
|
|
924
|
-
[this.enginePtr,
|
|
1041
|
+
['number', 'number'],
|
|
1042
|
+
[this.enginePtr, value]
|
|
925
1043
|
);
|
|
926
|
-
checkResult(result,
|
|
1044
|
+
checkResult(result, errorMessage);
|
|
927
1045
|
}
|
|
928
1046
|
|
|
929
1047
|
/**
|
|
930
|
-
*
|
|
931
|
-
*/
|
|
932
|
-
unregisterAllStickers() {
|
|
933
|
-
this._ensureInitialized();
|
|
934
|
-
const Module = this._getWasmModule();
|
|
935
|
-
const result = Module.ccall(
|
|
936
|
-
'UnregisterAllStickers',
|
|
937
|
-
'number',
|
|
938
|
-
['number'],
|
|
939
|
-
[this.enginePtr]
|
|
940
|
-
);
|
|
941
|
-
checkResult(result, 'Failed to unregister all stickers');
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/**
|
|
945
|
-
* Gets the list of registered filter IDs
|
|
946
|
-
* @returns {string[]} Array of registered filter IDs
|
|
947
|
-
*/
|
|
948
|
-
getRegisteredFilters() {
|
|
949
|
-
this._ensureInitialized();
|
|
950
|
-
const Module = this._getWasmModule();
|
|
951
|
-
const jsonStr = Module.ccall(
|
|
952
|
-
'GetRegisteredFilters',
|
|
953
|
-
'string',
|
|
954
|
-
['number'],
|
|
955
|
-
[this.enginePtr]
|
|
956
|
-
);
|
|
957
|
-
try {
|
|
958
|
-
return JSON.parse(jsonStr || '[]');
|
|
959
|
-
} catch (e) {
|
|
960
|
-
logError(`Failed to parse registered filters JSON: ${e}`);
|
|
961
|
-
return [];
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
/**
|
|
966
|
-
* Gets the list of registered sticker IDs
|
|
967
|
-
* @returns {string[]} Array of registered sticker IDs
|
|
968
|
-
*/
|
|
969
|
-
getRegisteredStickers() {
|
|
970
|
-
this._ensureInitialized();
|
|
971
|
-
const Module = this._getWasmModule();
|
|
972
|
-
const jsonStr = Module.ccall(
|
|
973
|
-
'GetRegisteredStickers',
|
|
974
|
-
'string',
|
|
975
|
-
['number'],
|
|
976
|
-
[this.enginePtr]
|
|
977
|
-
);
|
|
978
|
-
try {
|
|
979
|
-
return JSON.parse(jsonStr || '[]');
|
|
980
|
-
} catch (e) {
|
|
981
|
-
logError(`Failed to parse registered stickers JSON: ${e}`);
|
|
982
|
-
return [];
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
/**
|
|
987
|
-
* Internal method to set beauty parameters
|
|
1048
|
+
* Internal method to set beauty parameters
|
|
988
1049
|
* @private
|
|
989
1050
|
*/
|
|
990
1051
|
_setBeautyParam(functionName, param, value) {
|
|
991
|
-
if (value <
|
|
992
|
-
throw new FacebetterError('Parameter value must be between
|
|
1052
|
+
if (value < -1 || value > 1) {
|
|
1053
|
+
throw new FacebetterError('Parameter value must be between -1.0 and 1.0');
|
|
993
1054
|
}
|
|
994
1055
|
|
|
995
1056
|
const Module = this._getWasmModule();
|
|
@@ -1004,15 +1065,25 @@
|
|
|
1004
1065
|
}
|
|
1005
1066
|
|
|
1006
1067
|
/**
|
|
1007
|
-
* Sets engine callbacks (face landmarks
|
|
1068
|
+
* Sets engine callbacks (face landmarks and engine events)
|
|
1008
1069
|
* @param {Object} callbacks - Callback functions
|
|
1009
|
-
* @param {Function} callbacks.onFaceLandmarks - Callback for face landmarks detection
|
|
1070
|
+
* @param {Function} [callbacks.onFaceLandmarks] - Callback for face landmarks detection
|
|
1071
|
+
* @param {Function} [callbacks.onEngineEvent] - Callback for license / init events (code, message)
|
|
1010
1072
|
* @param {number} [callbacks.maxFaces=10] - Maximum number of faces to support (affects shared buffer size)
|
|
1011
1073
|
*/
|
|
1012
1074
|
setCallbacks(callbacks) {
|
|
1013
1075
|
this._ensureInitialized();
|
|
1014
1076
|
const Module = this._getWasmModule();
|
|
1015
|
-
|
|
1077
|
+
|
|
1078
|
+
if (!Module._engine_event_callbacks) {
|
|
1079
|
+
Module._engine_event_callbacks = {};
|
|
1080
|
+
}
|
|
1081
|
+
if (callbacks && typeof callbacks.onEngineEvent === 'function') {
|
|
1082
|
+
Module._engine_event_callbacks[this.enginePtr] = callbacks.onEngineEvent;
|
|
1083
|
+
} else {
|
|
1084
|
+
delete Module._engine_event_callbacks[this.enginePtr];
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1016
1087
|
// Initialize callback storage (if it doesn't exist)
|
|
1017
1088
|
if (!Module._face_landmarks_callbacks) {
|
|
1018
1089
|
Module._face_landmarks_callbacks = [];
|
|
@@ -1028,9 +1099,9 @@
|
|
|
1028
1099
|
|
|
1029
1100
|
// Pre-allocate shared memory
|
|
1030
1101
|
// Metadata: 2 ints (frame_number, face_count) = 8 bytes
|
|
1031
|
-
// Face data: maxFaces *
|
|
1102
|
+
// Face data: maxFaces * 342 floats * 4 bytes = maxFaces * 1368 bytes
|
|
1032
1103
|
const metadataSize = 2 * 4; // 2 ints
|
|
1033
|
-
const faceDataSize = maxFaces *
|
|
1104
|
+
const faceDataSize = maxFaces * 342 * 4; // 342 floats per face
|
|
1034
1105
|
sharedBufferSize = metadataSize + faceDataSize;
|
|
1035
1106
|
sharedBufferPtr = Module._malloc(sharedBufferSize);
|
|
1036
1107
|
|
|
@@ -1047,7 +1118,7 @@
|
|
|
1047
1118
|
const dataOffset = dataPtr / 4; // float is 4 bytes
|
|
1048
1119
|
|
|
1049
1120
|
const results = [];
|
|
1050
|
-
const FLOATS_PER_FACE =
|
|
1121
|
+
const FLOATS_PER_FACE = 342;
|
|
1051
1122
|
|
|
1052
1123
|
for (let i = 0; i < faceCount; i++) {
|
|
1053
1124
|
const faceOffset = dataOffset + i * FLOATS_PER_FACE;
|
|
@@ -1063,7 +1134,6 @@
|
|
|
1063
1134
|
|
|
1064
1135
|
// Read basic fields
|
|
1065
1136
|
const faceId = Math.round(heap[offset++]);
|
|
1066
|
-
const faceAction = Math.round(heap[offset++]);
|
|
1067
1137
|
const score = heap[offset++];
|
|
1068
1138
|
const pitch = heap[offset++];
|
|
1069
1139
|
const roll = heap[offset++];
|
|
@@ -1089,7 +1159,6 @@
|
|
|
1089
1159
|
key_points: keyPoints,
|
|
1090
1160
|
visibility,
|
|
1091
1161
|
face_id: faceId,
|
|
1092
|
-
face_action: faceAction,
|
|
1093
1162
|
score,
|
|
1094
1163
|
pitch,
|
|
1095
1164
|
roll,
|
|
@@ -1108,7 +1177,7 @@
|
|
|
1108
1177
|
|
|
1109
1178
|
// Call C API
|
|
1110
1179
|
const result = Module.ccall(
|
|
1111
|
-
'
|
|
1180
|
+
'fb_engine_set_wasm_callbacks',
|
|
1112
1181
|
'number',
|
|
1113
1182
|
['number', 'number', 'number', 'number'],
|
|
1114
1183
|
[
|
|
@@ -1137,55 +1206,81 @@
|
|
|
1137
1206
|
}
|
|
1138
1207
|
|
|
1139
1208
|
/**
|
|
1140
|
-
*
|
|
1141
|
-
*
|
|
1142
|
-
* @param {number} [
|
|
1143
|
-
* @param {ImageData|HTMLImageElement|HTMLCanvasElement} [options.backgroundImage] - Background image (required when mode is Image)
|
|
1209
|
+
* Enables virtual background blur.
|
|
1210
|
+
* Level is continuous in [0.0, 1.0] (downsample + mix, no shader rebuild).
|
|
1211
|
+
* @param {number} level - Blur strength in [0.0, 1.0]. 0 clears the virtual background.
|
|
1144
1212
|
*/
|
|
1145
|
-
|
|
1213
|
+
setVirtualBackgroundBlur(level) {
|
|
1146
1214
|
this._ensureInitialized();
|
|
1147
1215
|
const Module = this._getWasmModule();
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1216
|
+
const result = Module.ccall(
|
|
1217
|
+
'fb_set_virtual_background_blur',
|
|
1218
|
+
'number',
|
|
1219
|
+
['number', 'number'],
|
|
1220
|
+
[this.enginePtr, level]
|
|
1221
|
+
);
|
|
1222
|
+
checkResult(result, 'Failed to set virtual background blur');
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/**
|
|
1226
|
+
* Replaces the background with an image file path or encoded png/jpg bytes.
|
|
1227
|
+
* @param {string|Uint8Array} resource - Image path or Uint8Array data.
|
|
1228
|
+
*/
|
|
1229
|
+
setVirtualBackground(resource) {
|
|
1230
|
+
this._ensureInitialized();
|
|
1231
|
+
const Module = this._getWasmModule();
|
|
1232
|
+
|
|
1233
|
+
if (typeof resource === 'string') {
|
|
1234
|
+
if (!resource) {
|
|
1235
|
+
throw new FacebetterError(
|
|
1236
|
+
'Virtual background path must not be empty; use clearVirtualBackground()'
|
|
1237
|
+
);
|
|
1165
1238
|
}
|
|
1166
|
-
|
|
1167
|
-
// Use unified SetVirtualBackground C interface (consistent with other platforms)
|
|
1168
1239
|
const result = Module.ccall(
|
|
1169
|
-
'
|
|
1240
|
+
'fb_set_virtual_background',
|
|
1170
1241
|
'number',
|
|
1171
|
-
['number', '
|
|
1172
|
-
[
|
|
1173
|
-
this.enginePtr,
|
|
1174
|
-
opts.mode,
|
|
1175
|
-
imageBufferPtr || 0, // If null, pass 0
|
|
1176
|
-
imageData ? imageData.width : 0,
|
|
1177
|
-
imageData ? imageData.height : 0,
|
|
1178
|
-
imageData ? imageData.width * 4 : 0
|
|
1179
|
-
]
|
|
1242
|
+
['number', 'string'],
|
|
1243
|
+
[this.enginePtr, resource]
|
|
1180
1244
|
);
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1245
|
+
checkResult(result, 'Failed to set virtual background from path');
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if (resource instanceof Uint8Array) {
|
|
1250
|
+
const dataPtr = Module._malloc(resource.length);
|
|
1251
|
+
Module.HEAPU8.set(resource, dataPtr);
|
|
1252
|
+
try {
|
|
1253
|
+
const result = Module.ccall(
|
|
1254
|
+
'fb_set_virtual_background_data',
|
|
1255
|
+
'number',
|
|
1256
|
+
['number', 'number', 'number'],
|
|
1257
|
+
[this.enginePtr, dataPtr, resource.length]
|
|
1258
|
+
);
|
|
1259
|
+
checkResult(result, 'Failed to set virtual background from data');
|
|
1260
|
+
} finally {
|
|
1261
|
+
Module._free(dataPtr);
|
|
1187
1262
|
}
|
|
1263
|
+
return;
|
|
1188
1264
|
}
|
|
1265
|
+
|
|
1266
|
+
throw new FacebetterError(
|
|
1267
|
+
'setVirtualBackground expects a file path string or Uint8Array'
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/**
|
|
1272
|
+
* Clears virtual background (blur or image replacement).
|
|
1273
|
+
*/
|
|
1274
|
+
clearVirtualBackground() {
|
|
1275
|
+
this._ensureInitialized();
|
|
1276
|
+
const Module = this._getWasmModule();
|
|
1277
|
+
const result = Module.ccall(
|
|
1278
|
+
'fb_clear_virtual_background',
|
|
1279
|
+
'number',
|
|
1280
|
+
['number'],
|
|
1281
|
+
[this.enginePtr]
|
|
1282
|
+
);
|
|
1283
|
+
checkResult(result, 'Failed to clear virtual background');
|
|
1189
1284
|
}
|
|
1190
1285
|
|
|
1191
1286
|
/**
|
|
@@ -1193,12 +1288,12 @@
|
|
|
1193
1288
|
* When enabled, beauty effects will only be applied to detected skin areas.
|
|
1194
1289
|
* @param {boolean} enabled - True to enable skin-only beauty, false to apply to entire image.
|
|
1195
1290
|
*/
|
|
1196
|
-
|
|
1291
|
+
setBeautySkinOnly(enabled) {
|
|
1197
1292
|
this._ensureInitialized();
|
|
1198
1293
|
const Module = this._getWasmModule();
|
|
1199
1294
|
|
|
1200
1295
|
const result = Module.ccall(
|
|
1201
|
-
'
|
|
1296
|
+
'fb_set_beauty_skin_only',
|
|
1202
1297
|
'number',
|
|
1203
1298
|
['number', 'number'],
|
|
1204
1299
|
[this.enginePtr, enabled ? 1 : 0]
|
|
@@ -1207,6 +1302,35 @@
|
|
|
1207
1302
|
checkResult(result, 'Failed to set skin only beauty');
|
|
1208
1303
|
}
|
|
1209
1304
|
|
|
1305
|
+
/**
|
|
1306
|
+
* Gets engine performance statistics.
|
|
1307
|
+
* @returns {{fps: number, avgProcessTimeMs: number, sessionTimeS: number}}
|
|
1308
|
+
*/
|
|
1309
|
+
getStats() {
|
|
1310
|
+
this._ensureInitialized();
|
|
1311
|
+
const Module = this._getWasmModule();
|
|
1312
|
+
const ptr = Module._malloc(24);
|
|
1313
|
+
try {
|
|
1314
|
+
const result = Module.ccall(
|
|
1315
|
+
'fb_engine_get_stats',
|
|
1316
|
+
'number',
|
|
1317
|
+
['number', 'number'],
|
|
1318
|
+
[this.enginePtr, ptr]
|
|
1319
|
+
);
|
|
1320
|
+
checkResult(result, 'Failed to get stats');
|
|
1321
|
+
if ((ptr & 7) !== 0) {
|
|
1322
|
+
throw new FacebetterError('Unaligned stats buffer');
|
|
1323
|
+
}
|
|
1324
|
+
const base = ptr >> 3;
|
|
1325
|
+
return {
|
|
1326
|
+
fps: Module.HEAPF64[base],
|
|
1327
|
+
avgProcessTimeMs: Module.HEAPF64[base + 1],
|
|
1328
|
+
sessionTimeS: Module.HEAPF64[base + 2]
|
|
1329
|
+
};
|
|
1330
|
+
} finally {
|
|
1331
|
+
Module._free(ptr);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1210
1334
|
|
|
1211
1335
|
/**
|
|
1212
1336
|
* Ensures buffers are allocated for the given dimensions
|
|
@@ -1260,7 +1384,7 @@
|
|
|
1260
1384
|
srcView.set(imageData.data);
|
|
1261
1385
|
|
|
1262
1386
|
const result = Module.ccall(
|
|
1263
|
-
'
|
|
1387
|
+
'fb_process_rgba',
|
|
1264
1388
|
'number',
|
|
1265
1389
|
['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'],
|
|
1266
1390
|
[
|
|
@@ -1321,7 +1445,7 @@
|
|
|
1321
1445
|
|
|
1322
1446
|
try {
|
|
1323
1447
|
const result = Module.ccall(
|
|
1324
|
-
'
|
|
1448
|
+
'fb_process_texture',
|
|
1325
1449
|
'number',
|
|
1326
1450
|
['number', 'number', 'number', 'number', 'number', 'number', 'number'],
|
|
1327
1451
|
[
|
|
@@ -1361,37 +1485,37 @@
|
|
|
1361
1485
|
}
|
|
1362
1486
|
|
|
1363
1487
|
/**
|
|
1364
|
-
* Ensures
|
|
1488
|
+
* Ensures facebetter_canvas exists in the DOM (browser only)
|
|
1365
1489
|
* This canvas is required by GPUPixel for WebGL context creation
|
|
1366
1490
|
* @private
|
|
1367
1491
|
*/
|
|
1368
|
-
|
|
1492
|
+
_ensureFacebetterCanvas() {
|
|
1369
1493
|
if (typeof document === 'undefined') {
|
|
1370
1494
|
return;
|
|
1371
1495
|
}
|
|
1372
1496
|
|
|
1373
|
-
let canvas = document.getElementById('
|
|
1497
|
+
let canvas = document.getElementById('facebetter_canvas');
|
|
1374
1498
|
if (!canvas) {
|
|
1375
1499
|
canvas = document.createElement('canvas');
|
|
1376
|
-
canvas.id = '
|
|
1500
|
+
canvas.id = 'facebetter_canvas';
|
|
1377
1501
|
canvas.style.display = 'none';
|
|
1378
1502
|
canvas.width = 1;
|
|
1379
1503
|
canvas.height = 1;
|
|
1380
1504
|
document.body.appendChild(canvas);
|
|
1381
|
-
this.
|
|
1505
|
+
this._createdFacebetterCanvas = true;
|
|
1382
1506
|
}
|
|
1383
|
-
this.
|
|
1507
|
+
this._facebetterCanvas = canvas;
|
|
1384
1508
|
}
|
|
1385
1509
|
|
|
1386
1510
|
/**
|
|
1387
|
-
* Cleans up
|
|
1511
|
+
* Cleans up facebetter_canvas if it was created by this engine instance (browser only)
|
|
1388
1512
|
* @private
|
|
1389
1513
|
*/
|
|
1390
|
-
|
|
1391
|
-
if (this.
|
|
1392
|
-
this.
|
|
1393
|
-
this.
|
|
1394
|
-
this.
|
|
1514
|
+
_cleanupFacebetterCanvas() {
|
|
1515
|
+
if (this._createdFacebetterCanvas && this._facebetterCanvas) {
|
|
1516
|
+
this._facebetterCanvas.remove();
|
|
1517
|
+
this._facebetterCanvas = null;
|
|
1518
|
+
this._createdFacebetterCanvas = false;
|
|
1395
1519
|
}
|
|
1396
1520
|
}
|
|
1397
1521
|
|
|
@@ -1420,12 +1544,213 @@
|
|
|
1420
1544
|
|
|
1421
1545
|
}
|
|
1422
1546
|
|
|
1547
|
+
const WASM_FILE = 'facebetter-core.wasm';
|
|
1548
|
+
const RESOURCE_FILE = 'resource.fbd';
|
|
1549
|
+
const MEMFS_RESOURCE_PATH = '/resource.fbd';
|
|
1550
|
+
|
|
1551
|
+
function emitProgress(onProgress, loaded, total) {
|
|
1552
|
+
if (typeof onProgress !== 'function') {
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
const safeTotal = total > 0 ? total : loaded;
|
|
1556
|
+
const percent =
|
|
1557
|
+
safeTotal > 0 ? Math.min(100, Math.round((loaded / safeTotal) * 100)) : 0;
|
|
1558
|
+
onProgress({ loaded, total: safeTotal, percent });
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
async function readFileUrl(url, onChunk) {
|
|
1562
|
+
const { readFile } = await import('node:fs/promises');
|
|
1563
|
+
const { fileURLToPath } = await import('node:url');
|
|
1564
|
+
const buf = await readFile(fileURLToPath(url));
|
|
1565
|
+
const bytes = new Uint8Array(buf);
|
|
1566
|
+
onChunk(bytes.byteLength, bytes.byteLength);
|
|
1567
|
+
return bytes;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
/**
|
|
1571
|
+
* Fetch a binary with byte-level download progress.
|
|
1572
|
+
* @param {string} url
|
|
1573
|
+
* @param {function(number, number): void} onChunk loaded, total (0 if unknown)
|
|
1574
|
+
* @returns {Promise<Uint8Array>}
|
|
1575
|
+
*/
|
|
1576
|
+
async function fetchBinary(url, onChunk) {
|
|
1577
|
+
if (typeof url === 'string' && url.startsWith('file:')) {
|
|
1578
|
+
return readFileUrl(url, onChunk);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
const response = await fetch(url);
|
|
1582
|
+
if (!response.ok) {
|
|
1583
|
+
throw new FacebetterError(
|
|
1584
|
+
`Failed to download ${url} (${response.status})`,
|
|
1585
|
+
'WASM_LOAD_ERROR'
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
const totalHeader = Number(response.headers.get('content-length'));
|
|
1590
|
+
const total = Number.isFinite(totalHeader) && totalHeader > 0 ? totalHeader : 0;
|
|
1591
|
+
|
|
1592
|
+
if (!response.body || typeof response.body.getReader !== 'function') {
|
|
1593
|
+
const buffer = await response.arrayBuffer();
|
|
1594
|
+
const bytes = new Uint8Array(buffer);
|
|
1595
|
+
onChunk(bytes.byteLength, total || bytes.byteLength);
|
|
1596
|
+
return bytes;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
const reader = response.body.getReader();
|
|
1600
|
+
const chunks = [];
|
|
1601
|
+
let loaded = 0;
|
|
1602
|
+
while (true) {
|
|
1603
|
+
const { done, value } = await reader.read();
|
|
1604
|
+
if (done) {
|
|
1605
|
+
break;
|
|
1606
|
+
}
|
|
1607
|
+
chunks.push(value);
|
|
1608
|
+
loaded += value.byteLength;
|
|
1609
|
+
onChunk(loaded, total);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
const bytes = new Uint8Array(loaded);
|
|
1613
|
+
let offset = 0;
|
|
1614
|
+
for (const chunk of chunks) {
|
|
1615
|
+
bytes.set(chunk, offset);
|
|
1616
|
+
offset += chunk.byteLength;
|
|
1617
|
+
}
|
|
1618
|
+
onChunk(loaded, total || loaded);
|
|
1619
|
+
return bytes;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
async function fetchRuntimeAssets({ wasmUrl, resourceUrl, onProgress }) {
|
|
1623
|
+
let wasmLoaded = 0;
|
|
1624
|
+
let wasmTotal = 0;
|
|
1625
|
+
let resourceLoaded = 0;
|
|
1626
|
+
let resourceTotal = 0;
|
|
1627
|
+
|
|
1628
|
+
const emit = () => {
|
|
1629
|
+
emitProgress(
|
|
1630
|
+
onProgress,
|
|
1631
|
+
wasmLoaded + resourceLoaded,
|
|
1632
|
+
wasmTotal + resourceTotal
|
|
1633
|
+
);
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
const [wasmBytes, resourceBytes] = await Promise.all([
|
|
1637
|
+
fetchBinary(wasmUrl, (loaded, total) => {
|
|
1638
|
+
wasmLoaded = loaded;
|
|
1639
|
+
wasmTotal = total || loaded;
|
|
1640
|
+
emit();
|
|
1641
|
+
}),
|
|
1642
|
+
fetchBinary(resourceUrl, (loaded, total) => {
|
|
1643
|
+
resourceLoaded = loaded;
|
|
1644
|
+
resourceTotal = total || loaded;
|
|
1645
|
+
emit();
|
|
1646
|
+
}),
|
|
1647
|
+
]);
|
|
1648
|
+
|
|
1649
|
+
emitProgress(
|
|
1650
|
+
onProgress,
|
|
1651
|
+
wasmBytes.byteLength + resourceBytes.byteLength,
|
|
1652
|
+
wasmBytes.byteLength + resourceBytes.byteLength
|
|
1653
|
+
);
|
|
1654
|
+
return { wasmBytes, resourceBytes };
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
function scriptDirectory() {
|
|
1658
|
+
if (typeof document === 'undefined') {
|
|
1659
|
+
return '';
|
|
1660
|
+
}
|
|
1661
|
+
if (document.currentScript?.src) {
|
|
1662
|
+
return document.currentScript.src.slice(
|
|
1663
|
+
0,
|
|
1664
|
+
document.currentScript.src.lastIndexOf('/') + 1
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
const scripts = document.getElementsByTagName('script');
|
|
1668
|
+
for (let i = scripts.length - 1; i >= 0; i--) {
|
|
1669
|
+
const src = scripts[i].src;
|
|
1670
|
+
if (src && (src.includes('facebetter.js') || src.includes('facebetter'))) {
|
|
1671
|
+
return src.slice(0, src.lastIndexOf('/') + 1);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
if (typeof location !== 'undefined') {
|
|
1675
|
+
return new URL('.', location.href).href;
|
|
1676
|
+
}
|
|
1677
|
+
return './';
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
/**
|
|
1681
|
+
* Resolve sidecar wasm / resource URLs.
|
|
1682
|
+
* Prefer `assetBaseUrl`, then `facebetter-core/assets`, then the script directory.
|
|
1683
|
+
*/
|
|
1684
|
+
async function resolveRuntimeAssetUrls(options = {}) {
|
|
1685
|
+
const joinBase = (base) => {
|
|
1686
|
+
const prefix = base.endsWith('/') ? base : `${base}/`;
|
|
1687
|
+
return {
|
|
1688
|
+
wasmUrl: options.wasmAssetUrl || `${prefix}${WASM_FILE}`,
|
|
1689
|
+
resourceUrl: options.resourceAssetUrl || `${prefix}${RESOURCE_FILE}`,
|
|
1690
|
+
};
|
|
1691
|
+
};
|
|
1692
|
+
|
|
1693
|
+
if (options.wasmAssetUrl && options.resourceAssetUrl) {
|
|
1694
|
+
return {
|
|
1695
|
+
wasmUrl: options.wasmAssetUrl,
|
|
1696
|
+
resourceUrl: options.resourceAssetUrl,
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
if (options.assetBaseUrl) {
|
|
1700
|
+
return joinBase(options.assetBaseUrl);
|
|
1701
|
+
}
|
|
1702
|
+
if (typeof options.wasmUrl === 'string' && /^(https?:|file:|\/)/.test(options.wasmUrl)) {
|
|
1703
|
+
return joinBase(options.wasmUrl.slice(0, options.wasmUrl.lastIndexOf('/') + 1));
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
try {
|
|
1707
|
+
const assets = await import('facebetter-core/assets');
|
|
1708
|
+
return {
|
|
1709
|
+
wasmUrl: options.wasmAssetUrl || assets.wasmUrl,
|
|
1710
|
+
resourceUrl: options.resourceAssetUrl || assets.resourceUrl,
|
|
1711
|
+
};
|
|
1712
|
+
} catch {
|
|
1713
|
+
return joinBase(scriptDirectory());
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
/**
|
|
1718
|
+
* Instantiate the Emscripten factory using already-downloaded bytes.
|
|
1719
|
+
*/
|
|
1720
|
+
async function instantiateRuntime(factory, wasmBytes, resourceBytes) {
|
|
1721
|
+
if (!factory) {
|
|
1722
|
+
throw new FacebetterError(
|
|
1723
|
+
'WASM module does not export createFaceBetterModule',
|
|
1724
|
+
'WASM_LOAD_ERROR'
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
const module = await factory({
|
|
1729
|
+
instantiateWasm(imports, receiveInstance) {
|
|
1730
|
+
return WebAssembly.instantiate(wasmBytes, imports).then((result) => {
|
|
1731
|
+
receiveInstance(result.instance, result.module);
|
|
1732
|
+
});
|
|
1733
|
+
},
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1736
|
+
if (!module) {
|
|
1737
|
+
throw new FacebetterError('Failed to initialize WASM module', 'WASM_LOAD_ERROR');
|
|
1738
|
+
}
|
|
1739
|
+
if (!module.FS) {
|
|
1740
|
+
throw new FacebetterError('Runtime filesystem is not available', 'WASM_LOAD_ERROR');
|
|
1741
|
+
}
|
|
1742
|
+
module.FS.writeFile(MEMFS_RESOURCE_PATH, resourceBytes);
|
|
1743
|
+
module.ready = true;
|
|
1744
|
+
return module;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1423
1747
|
/**
|
|
1424
1748
|
* Browser-specific WASM Module Loader
|
|
1425
1749
|
* Supports ESM format WASM modules (generated with MODULARIZE and EXPORT_ES6)
|
|
1426
1750
|
* Uses document.currentScript for automatic path detection in UMD builds
|
|
1427
1751
|
*/
|
|
1428
1752
|
|
|
1753
|
+
|
|
1429
1754
|
/**
|
|
1430
1755
|
* Gets the base path of the current script
|
|
1431
1756
|
* Similar to Node.js __dirname
|
|
@@ -1531,6 +1856,7 @@
|
|
|
1531
1856
|
*/
|
|
1532
1857
|
async function loadWasmModule(options = {}) {
|
|
1533
1858
|
if (wasmModuleInstance) {
|
|
1859
|
+
options.onProgress?.({ loaded: 1, total: 1, percent: 100 });
|
|
1534
1860
|
return wasmModuleInstance;
|
|
1535
1861
|
}
|
|
1536
1862
|
|
|
@@ -1540,57 +1866,44 @@
|
|
|
1540
1866
|
|
|
1541
1867
|
wasmModulePromise = (async () => {
|
|
1542
1868
|
try {
|
|
1543
|
-
let
|
|
1544
|
-
|
|
1869
|
+
let factory;
|
|
1870
|
+
|
|
1545
1871
|
if (options.wasmUrl) {
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
FaceBetterModuleFactory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
1872
|
+
const wasmModule = await import(/* @vite-ignore */ options.wasmUrl);
|
|
1873
|
+
factory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
1549
1874
|
} else {
|
|
1550
|
-
// Try to import from facebetter-core npm package
|
|
1551
|
-
// Note: In UMD builds, this may not work if facebetter-core is not available
|
|
1552
|
-
// In that case, fallback to relative path detection
|
|
1553
1875
|
try {
|
|
1554
1876
|
const wasmModule = await import('facebetter-core');
|
|
1555
|
-
|
|
1556
|
-
} catch
|
|
1557
|
-
// Fallback: try to find facebetter-core.js relative to current script
|
|
1558
|
-
// This handles UMD builds where npm packages may not be available
|
|
1877
|
+
factory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
1878
|
+
} catch {
|
|
1559
1879
|
const basePath = getScriptBasePath();
|
|
1560
|
-
const
|
|
1561
|
-
const wasmModule = await import(
|
|
1562
|
-
|
|
1880
|
+
const gluePath = resolvePath('facebetter-core.js', basePath);
|
|
1881
|
+
const wasmModule = await import(gluePath);
|
|
1882
|
+
factory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
1883
|
+
if (!options.assetBaseUrl) {
|
|
1884
|
+
options = { ...options, assetBaseUrl: basePath };
|
|
1885
|
+
}
|
|
1563
1886
|
}
|
|
1564
1887
|
}
|
|
1565
|
-
|
|
1566
|
-
if (!FaceBetterModuleFactory) {
|
|
1567
|
-
throw new Error('WASM module does not export createFaceBetterModule function');
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
// Configure module options
|
|
1571
|
-
// With SINGLE_FILE=1, WASM binary and data files are embedded in the JS file
|
|
1572
|
-
// No need to handle .wasm and .data file paths separately
|
|
1573
|
-
const moduleOptions = {
|
|
1574
|
-
locateFile: options.locateFile || function(path, prefix) {
|
|
1575
|
-
// With SINGLE_FILE=1, Emscripten handles embedded files automatically
|
|
1576
|
-
// Custom locateFile is only needed if user provides one
|
|
1577
|
-
return prefix + path;
|
|
1578
|
-
},
|
|
1579
|
-
...options
|
|
1580
|
-
};
|
|
1581
1888
|
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
if (!wasmModuleInstance) {
|
|
1586
|
-
throw new Error('Failed to initialize WASM module');
|
|
1889
|
+
if (!factory) {
|
|
1890
|
+
throw new Error('WASM module does not export createFaceBetterModule function');
|
|
1587
1891
|
}
|
|
1588
1892
|
|
|
1589
|
-
|
|
1893
|
+
const urls = await resolveRuntimeAssetUrls(options);
|
|
1894
|
+
const { wasmBytes, resourceBytes } = await fetchRuntimeAssets({
|
|
1895
|
+
...urls,
|
|
1896
|
+
onProgress: options.onProgress,
|
|
1897
|
+
});
|
|
1898
|
+
wasmModuleInstance = await instantiateRuntime(
|
|
1899
|
+
factory,
|
|
1900
|
+
wasmBytes,
|
|
1901
|
+
resourceBytes
|
|
1902
|
+
);
|
|
1590
1903
|
return wasmModuleInstance;
|
|
1591
1904
|
} catch (e) {
|
|
1592
1905
|
wasmModulePromise = null;
|
|
1593
|
-
throw new
|
|
1906
|
+
throw new FacebetterError(`Failed to load WASM module: ${e.message}`, 'WASM_LOAD_ERROR');
|
|
1594
1907
|
}
|
|
1595
1908
|
})();
|
|
1596
1909
|
|
|
@@ -1684,15 +1997,15 @@
|
|
|
1684
1997
|
|
|
1685
1998
|
|
|
1686
1999
|
// Browser-specific GPU canvas management
|
|
1687
|
-
function
|
|
2000
|
+
function ensureFacebetterCanvas() {
|
|
1688
2001
|
if (typeof document === 'undefined') {
|
|
1689
2002
|
return;
|
|
1690
2003
|
}
|
|
1691
2004
|
|
|
1692
|
-
let canvas = document.getElementById('
|
|
2005
|
+
let canvas = document.getElementById('facebetter_canvas');
|
|
1693
2006
|
if (!canvas) {
|
|
1694
2007
|
canvas = document.createElement('canvas');
|
|
1695
|
-
canvas.id = '
|
|
2008
|
+
canvas.id = 'facebetter_canvas';
|
|
1696
2009
|
canvas.style.display = 'none';
|
|
1697
2010
|
canvas.width = 1;
|
|
1698
2011
|
canvas.height = 1;
|
|
@@ -1702,18 +2015,18 @@
|
|
|
1702
2015
|
}
|
|
1703
2016
|
}
|
|
1704
2017
|
|
|
1705
|
-
function
|
|
2018
|
+
function cleanupFacebetterCanvas() {
|
|
1706
2019
|
// Cleanup is handled by engine instance
|
|
1707
2020
|
}
|
|
1708
2021
|
|
|
1709
2022
|
// Create platform API
|
|
1710
2023
|
const platformAPI = {
|
|
1711
|
-
loadWasmModule: () => loadWasmModule(),
|
|
2024
|
+
loadWasmModule: (options) => loadWasmModule(options),
|
|
1712
2025
|
getWasmModule: getWasmModule,
|
|
1713
2026
|
getWasmBuffer: getWasmBuffer,
|
|
1714
2027
|
toImageData: toImageData,
|
|
1715
|
-
|
|
1716
|
-
|
|
2028
|
+
ensureFacebetterCanvas,
|
|
2029
|
+
cleanupFacebetterCanvas
|
|
1717
2030
|
};
|
|
1718
2031
|
|
|
1719
2032
|
// Export factory function for UMD
|
|
@@ -1731,16 +2044,26 @@
|
|
|
1731
2044
|
}
|
|
1732
2045
|
|
|
1733
2046
|
// Export constants with proper names
|
|
1734
|
-
const
|
|
1735
|
-
const
|
|
1736
|
-
const
|
|
1737
|
-
const
|
|
1738
|
-
const LipstickStyle = LipstickStyle$1;
|
|
2047
|
+
const WhiteningStyle = WhiteningStyle$1;
|
|
2048
|
+
const SmoothingStyle = SmoothingStyle$1;
|
|
2049
|
+
const Reshape = Reshape$1;
|
|
2050
|
+
const LipstickColor = LipstickColor$1;
|
|
1739
2051
|
const BlushStyle = BlushStyle$1;
|
|
1740
|
-
const
|
|
2052
|
+
const BlushColor = BlushColor$1;
|
|
2053
|
+
const ContourStyle = ContourStyle$1;
|
|
2054
|
+
const EyeShadowStyle = EyeShadowStyle$1;
|
|
2055
|
+
const EyeShadowColor = EyeShadowColor$1;
|
|
2056
|
+
const EyeLinerStyle = EyeLinerStyle$1;
|
|
2057
|
+
const EyeLinerColor = EyeLinerColor$1;
|
|
2058
|
+
const EyebrowStyle = EyebrowStyle$1;
|
|
2059
|
+
const EyebrowColor = EyebrowColor$1;
|
|
2060
|
+
const EyelashStyle = EyelashStyle$1;
|
|
2061
|
+
const EyelashColor = EyelashColor$1;
|
|
2062
|
+
const PupilColor = PupilColor$1;
|
|
2063
|
+
const ChromaKeyColor = ChromaKeyColor$1;
|
|
1741
2064
|
const FrameType = FrameType$1;
|
|
1742
2065
|
const MirrorMode = MirrorMode$1;
|
|
1743
|
-
const
|
|
2066
|
+
const EngineEventCode = EngineEventCode$1;
|
|
1744
2067
|
|
|
1745
2068
|
// Default export
|
|
1746
2069
|
var index = {
|
|
@@ -1751,19 +2074,29 @@
|
|
|
1751
2074
|
loadWasmModule: loadWasmModule
|
|
1752
2075
|
};
|
|
1753
2076
|
|
|
1754
|
-
exports.BackgroundMode = BackgroundMode;
|
|
1755
|
-
exports.BasicParam = BasicParam;
|
|
1756
2077
|
exports.BeautyEffectEngine = BrowserBeautyEffectEngine;
|
|
1757
|
-
exports.
|
|
2078
|
+
exports.BlushColor = BlushColor;
|
|
1758
2079
|
exports.BlushStyle = BlushStyle;
|
|
2080
|
+
exports.ChromaKeyColor = ChromaKeyColor;
|
|
2081
|
+
exports.ContourStyle = ContourStyle;
|
|
1759
2082
|
exports.EngineConfig = EngineConfig;
|
|
2083
|
+
exports.EngineEventCode = EngineEventCode;
|
|
2084
|
+
exports.EyeLinerColor = EyeLinerColor;
|
|
2085
|
+
exports.EyeLinerStyle = EyeLinerStyle;
|
|
2086
|
+
exports.EyeShadowColor = EyeShadowColor;
|
|
2087
|
+
exports.EyeShadowStyle = EyeShadowStyle;
|
|
2088
|
+
exports.EyebrowColor = EyebrowColor;
|
|
2089
|
+
exports.EyebrowStyle = EyebrowStyle;
|
|
2090
|
+
exports.EyelashColor = EyelashColor;
|
|
2091
|
+
exports.EyelashStyle = EyelashStyle;
|
|
1760
2092
|
exports.FacebetterError = FacebetterError;
|
|
1761
2093
|
exports.FrameType = FrameType;
|
|
1762
|
-
exports.
|
|
1763
|
-
exports.MakeupParam = MakeupParam;
|
|
2094
|
+
exports.LipstickColor = LipstickColor;
|
|
1764
2095
|
exports.MirrorMode = MirrorMode;
|
|
1765
|
-
exports.
|
|
1766
|
-
exports.
|
|
2096
|
+
exports.PupilColor = PupilColor;
|
|
2097
|
+
exports.Reshape = Reshape;
|
|
2098
|
+
exports.SmoothingStyle = SmoothingStyle;
|
|
2099
|
+
exports.WhiteningStyle = WhiteningStyle;
|
|
1767
2100
|
exports.createBeautyEffectEngine = createBeautyEffectEngine;
|
|
1768
2101
|
exports.default = index;
|
|
1769
2102
|
exports.loadWasmModule = loadWasmModule;
|