capacitor-plugin-faceantispoofing 0.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.
Files changed (43) hide show
  1. package/CapacitorPluginFaceantispoofing.podspec +20 -0
  2. package/Package.swift +28 -0
  3. package/README.md +175 -0
  4. package/android/build.gradle +64 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/assets/FaceAntiSpoofing.tflite +0 -0
  7. package/android/src/main/assets/onet.tflite +0 -0
  8. package/android/src/main/assets/pnet.tflite +0 -0
  9. package/android/src/main/assets/rnet.tflite +0 -0
  10. package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/FaceAntiSpoofing.java +112 -0
  11. package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/FaceAntiSpoofingPlugin.java +178 -0
  12. package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/MyUtil.java +174 -0
  13. package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/mtcnn/Align.java +28 -0
  14. package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/mtcnn/Box.java +73 -0
  15. package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/mtcnn/MTCNN.java +268 -0
  16. package/android/src/main/java/io/github/asephermann/plugins/faceantispoofing/mtcnn/Utils.java +25 -0
  17. package/android/src/main/res/.gitkeep +0 -0
  18. package/dist/docs.json +104 -0
  19. package/dist/esm/definitions.d.ts +22 -0
  20. package/dist/esm/definitions.js +2 -0
  21. package/dist/esm/definitions.js.map +1 -0
  22. package/dist/esm/index.d.ts +4 -0
  23. package/dist/esm/index.js +7 -0
  24. package/dist/esm/index.js.map +1 -0
  25. package/dist/esm/web.d.ts +5 -0
  26. package/dist/esm/web.js +14 -0
  27. package/dist/esm/web.js.map +1 -0
  28. package/dist/plugin.cjs.js +28 -0
  29. package/dist/plugin.cjs.js.map +1 -0
  30. package/dist/plugin.js +31 -0
  31. package/dist/plugin.js.map +1 -0
  32. package/ios/Sources/FaceAntiSpoofingPlugin/Align.swift +41 -0
  33. package/ios/Sources/FaceAntiSpoofingPlugin/Box.swift +70 -0
  34. package/ios/Sources/FaceAntiSpoofingPlugin/FaceAntiSpoofing.swift +105 -0
  35. package/ios/Sources/FaceAntiSpoofingPlugin/FaceAntiSpoofing.tflite +0 -0
  36. package/ios/Sources/FaceAntiSpoofingPlugin/FaceAntiSpoofingPlugin.swift +166 -0
  37. package/ios/Sources/FaceAntiSpoofingPlugin/MTCNN.swift +407 -0
  38. package/ios/Sources/FaceAntiSpoofingPlugin/Tools.swift +103 -0
  39. package/ios/Sources/FaceAntiSpoofingPlugin/onet.tflite +0 -0
  40. package/ios/Sources/FaceAntiSpoofingPlugin/pnet.tflite +0 -0
  41. package/ios/Sources/FaceAntiSpoofingPlugin/rnet.tflite +0 -0
  42. package/ios/Tests/FaceAntiSpoofingPluginTests/FaceAntiSpoofingTests.swift +15 -0
  43. package/package.json +80 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=definitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface DetectionResult {\n error: boolean;\n liveness: boolean;\n score: string;\n threshold: string;\n message: string;\n}\n\nexport interface DetectOptions {\n /**\n * Base64 encoded image data (without data:image/...;base64, prefix)\n * or file URI to the image\n */\n image: string;\n}\n\nexport interface FaceAntiSpoofingPlugin {\n /**\n * Detect face liveness and anti-spoofing\n * @param options Image data as base64 string or file URI\n * @returns Promise with detection result containing liveness status\n */\n detect(options: DetectOptions): Promise<DetectionResult>;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { FaceAntiSpoofingPlugin } from './definitions';
2
+ declare const FaceAntiSpoofing: FaceAntiSpoofingPlugin;
3
+ export * from './definitions';
4
+ export { FaceAntiSpoofing };
@@ -0,0 +1,7 @@
1
+ import { registerPlugin } from '@capacitor/core';
2
+ const FaceAntiSpoofing = registerPlugin('FaceAntiSpoofing', {
3
+ web: () => import('./web').then((m) => new m.FaceAntiSpoofingWeb()),
4
+ });
5
+ export * from './definitions';
6
+ export { FaceAntiSpoofing };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,gBAAgB,GAAG,cAAc,CAAyB,kBAAkB,EAAE;IAClF,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;CACpE,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { FaceAntiSpoofingPlugin } from './definitions';\n\nconst FaceAntiSpoofing = registerPlugin<FaceAntiSpoofingPlugin>('FaceAntiSpoofing', {\n web: () => import('./web').then((m) => new m.FaceAntiSpoofingWeb()),\n});\n\nexport * from './definitions';\nexport { FaceAntiSpoofing };\n"]}
@@ -0,0 +1,5 @@
1
+ import { WebPlugin } from '@capacitor/core';
2
+ import type { DetectionResult, DetectOptions, FaceAntiSpoofingPlugin } from './definitions';
3
+ export declare class FaceAntiSpoofingWeb extends WebPlugin implements FaceAntiSpoofingPlugin {
4
+ detect(_options: DetectOptions): Promise<DetectionResult>;
5
+ }
@@ -0,0 +1,14 @@
1
+ import { WebPlugin } from '@capacitor/core';
2
+ export class FaceAntiSpoofingWeb extends WebPlugin {
3
+ async detect(_options) {
4
+ console.log('Face anti-spoofing is not supported on web platform');
5
+ return {
6
+ error: true,
7
+ liveness: false,
8
+ score: '0',
9
+ threshold: '0',
10
+ message: 'Face anti-spoofing is not supported on web platform. Please use a native platform (iOS or Android).',
11
+ };
12
+ }
13
+ }
14
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,MAAM,OAAO,mBAAoB,SAAQ,SAAS;IAChD,KAAK,CAAC,MAAM,CAAC,QAAuB;QAClC,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;QACnE,OAAO;YACL,KAAK,EAAE,IAAI;YACX,QAAQ,EAAE,KAAK;YACf,KAAK,EAAE,GAAG;YACV,SAAS,EAAE,GAAG;YACd,OAAO,EAAE,qGAAqG;SAC/G,CAAC;IACJ,CAAC;CACF","sourcesContent":["import { WebPlugin } from '@capacitor/core';\n\nimport type { DetectionResult, DetectOptions, FaceAntiSpoofingPlugin } from './definitions';\n\nexport class FaceAntiSpoofingWeb extends WebPlugin implements FaceAntiSpoofingPlugin {\n async detect(_options: DetectOptions): Promise<DetectionResult> {\n console.log('Face anti-spoofing is not supported on web platform');\n return {\n error: true,\n liveness: false,\n score: '0',\n threshold: '0',\n message: 'Face anti-spoofing is not supported on web platform. Please use a native platform (iOS or Android).',\n };\n }\n}\n"]}
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ var core = require('@capacitor/core');
4
+
5
+ const FaceAntiSpoofing = core.registerPlugin('FaceAntiSpoofing', {
6
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.FaceAntiSpoofingWeb()),
7
+ });
8
+
9
+ class FaceAntiSpoofingWeb extends core.WebPlugin {
10
+ async detect(_options) {
11
+ console.log('Face anti-spoofing is not supported on web platform');
12
+ return {
13
+ error: true,
14
+ liveness: false,
15
+ score: '0',
16
+ threshold: '0',
17
+ message: 'Face anti-spoofing is not supported on web platform. Please use a native platform (iOS or Android).',
18
+ };
19
+ }
20
+ }
21
+
22
+ var web = /*#__PURE__*/Object.freeze({
23
+ __proto__: null,
24
+ FaceAntiSpoofingWeb: FaceAntiSpoofingWeb
25
+ });
26
+
27
+ exports.FaceAntiSpoofing = FaceAntiSpoofing;
28
+ //# sourceMappingURL=plugin.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst FaceAntiSpoofing = registerPlugin('FaceAntiSpoofing', {\n web: () => import('./web').then((m) => new m.FaceAntiSpoofingWeb()),\n});\nexport * from './definitions';\nexport { FaceAntiSpoofing };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class FaceAntiSpoofingWeb extends WebPlugin {\n async detect(_options) {\n console.log('Face anti-spoofing is not supported on web platform');\n return {\n error: true,\n liveness: false,\n score: '0',\n threshold: '0',\n message: 'Face anti-spoofing is not supported on web platform. Please use a native platform (iOS or Android).',\n };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,gBAAgB,GAAGA,mBAAc,CAAC,kBAAkB,EAAE;AAC5D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;AACvE,CAAC;;ACFM,MAAM,mBAAmB,SAASC,cAAS,CAAC;AACnD,IAAI,MAAM,MAAM,CAAC,QAAQ,EAAE;AAC3B,QAAQ,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC;AAC1E,QAAQ,OAAO;AACf,YAAY,KAAK,EAAE,IAAI;AACvB,YAAY,QAAQ,EAAE,KAAK;AAC3B,YAAY,KAAK,EAAE,GAAG;AACtB,YAAY,SAAS,EAAE,GAAG;AAC1B,YAAY,OAAO,EAAE,qGAAqG;AAC1H,SAAS;AACT,IAAI;AACJ;;;;;;;;;"}
package/dist/plugin.js ADDED
@@ -0,0 +1,31 @@
1
+ var capacitorFaceAntiSpoofing = (function (exports, core) {
2
+ 'use strict';
3
+
4
+ const FaceAntiSpoofing = core.registerPlugin('FaceAntiSpoofing', {
5
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.FaceAntiSpoofingWeb()),
6
+ });
7
+
8
+ class FaceAntiSpoofingWeb extends core.WebPlugin {
9
+ async detect(_options) {
10
+ console.log('Face anti-spoofing is not supported on web platform');
11
+ return {
12
+ error: true,
13
+ liveness: false,
14
+ score: '0',
15
+ threshold: '0',
16
+ message: 'Face anti-spoofing is not supported on web platform. Please use a native platform (iOS or Android).',
17
+ };
18
+ }
19
+ }
20
+
21
+ var web = /*#__PURE__*/Object.freeze({
22
+ __proto__: null,
23
+ FaceAntiSpoofingWeb: FaceAntiSpoofingWeb
24
+ });
25
+
26
+ exports.FaceAntiSpoofing = FaceAntiSpoofing;
27
+
28
+ return exports;
29
+
30
+ })({}, capacitorExports);
31
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst FaceAntiSpoofing = registerPlugin('FaceAntiSpoofing', {\n web: () => import('./web').then((m) => new m.FaceAntiSpoofingWeb()),\n});\nexport * from './definitions';\nexport { FaceAntiSpoofing };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class FaceAntiSpoofingWeb extends WebPlugin {\n async detect(_options) {\n console.log('Face anti-spoofing is not supported on web platform');\n return {\n error: true,\n liveness: false,\n score: '0',\n threshold: '0',\n message: 'Face anti-spoofing is not supported on web platform. Please use a native platform (iOS or Android).',\n };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,gBAAgB,GAAGA,mBAAc,CAAC,kBAAkB,EAAE;IAC5D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;IACvE,CAAC;;ICFM,MAAM,mBAAmB,SAASC,cAAS,CAAC;IACnD,IAAI,MAAM,MAAM,CAAC,QAAQ,EAAE;IAC3B,QAAQ,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC;IAC1E,QAAQ,OAAO;IACf,YAAY,KAAK,EAAE,IAAI;IACvB,YAAY,QAAQ,EAAE,KAAK;IAC3B,YAAY,KAAK,EAAE,GAAG;IACtB,YAAY,SAAS,EAAE,GAAG;IAC1B,YAAY,OAAO,EAAE,qGAAqG;IAC1H,SAAS;IACT,IAAI;IACJ;;;;;;;;;;;;;;;"}
@@ -0,0 +1,41 @@
1
+ import Foundation
2
+ import UIKit
3
+
4
+ public class Align {
5
+ public static func faceAlign(image: UIImage, landmark: [CGPoint]) -> UIImage {
6
+ let xLeftEye = landmark[0].x
7
+ let yLeftEye = landmark[0].y
8
+ let xRightEye = landmark[1].x
9
+ let yRightEye = landmark[1].y
10
+
11
+ let dx = xRightEye - xLeftEye
12
+ let dy = yRightEye - yLeftEye
13
+ let angle = atan2(dy, dx) * 180.0 / .pi
14
+
15
+ let rotatedImage = image.rotated(by: -angle)
16
+
17
+ return rotatedImage
18
+ }
19
+ }
20
+
21
+ extension UIImage {
22
+ func rotated(by degrees: CGFloat) -> UIImage {
23
+ let radians = degrees * .pi / 180.0
24
+
25
+ let rotatedSize = CGRect(origin: .zero, size: size)
26
+ .applying(CGAffineTransform(rotationAngle: radians))
27
+ .integral.size
28
+
29
+ UIGraphicsBeginImageContext(rotatedSize)
30
+ defer { UIGraphicsEndImageContext() }
31
+
32
+ if let context = UIGraphicsGetCurrentContext() {
33
+ let origin = CGPoint(x: rotatedSize.width / 2.0, y: rotatedSize.height / 2.0)
34
+ context.translateBy(x: origin.x, y: origin.y)
35
+ context.rotate(by: radians)
36
+ draw(in: CGRect(x: -size.width / 2.0, y: -size.height / 2.0, width: size.width, height: size.height))
37
+ }
38
+
39
+ return UIGraphicsGetImageFromCurrentImageContext() ?? self
40
+ }
41
+ }
@@ -0,0 +1,70 @@
1
+ import Foundation
2
+ import UIKit
3
+
4
+ public class Box {
5
+ public var box = [Int](repeating: 0, count: 4)
6
+ public var score: Float = 0
7
+ public var bbr = [Float](repeating: 0, count: 4)
8
+ public var deleted = false
9
+ public var landmark = [CGPoint](repeating: CGPoint.zero, count: 5)
10
+
11
+ public func left() -> Int {
12
+ return box[0]
13
+ }
14
+
15
+ public func right() -> Int {
16
+ return box[2]
17
+ }
18
+
19
+ public func top() -> Int {
20
+ return box[1]
21
+ }
22
+
23
+ public func bottom() -> Int {
24
+ return box[3]
25
+ }
26
+
27
+ public func width() -> Int {
28
+ return box[2] - box[0] + 1
29
+ }
30
+
31
+ public func height() -> Int {
32
+ return box[3] - box[1] + 1
33
+ }
34
+
35
+ public func transform2Rect() -> CGRect {
36
+ return CGRect(x: box[0], y: box[1], width: width(), height: height())
37
+ }
38
+
39
+ public func area() -> Int {
40
+ return width() * height()
41
+ }
42
+
43
+ public func calibrate() {
44
+ let w = width()
45
+ let h = height()
46
+ box[0] = Int(round(Double(box[0] + Int(bbr[0] * Float(w)))))
47
+ box[1] = Int(round(Double(box[1] + Int(bbr[1] * Float(h)))))
48
+ box[2] = Int(round(Double(box[2] + Int(bbr[2] * Float(w)))))
49
+ box[3] = Int(round(Double(box[3] + Int(bbr[3] * Float(h)))))
50
+ }
51
+
52
+ public func toSquareShape() {
53
+ let w = width()
54
+ let h = height()
55
+ let maxSide = max(w, h)
56
+ box[2] = box[0] + maxSide - 1
57
+ box[3] = box[1] + maxSide - 1
58
+ }
59
+
60
+ public func limitSquare(w: Int, h: Int) {
61
+ if box[0] < 0 { box[0] = 0 }
62
+ if box[1] < 0 { box[1] = 0 }
63
+ if box[2] >= w { box[2] = w - 1 }
64
+ if box[3] >= h { box[3] = h - 1 }
65
+ }
66
+
67
+ public func transbound(w: Int, h: Int) -> Bool {
68
+ return box[0] < 0 || box[1] < 0 || box[2] >= w || box[3] >= h
69
+ }
70
+ }
@@ -0,0 +1,105 @@
1
+ import Foundation
2
+ import UIKit
3
+ import TensorFlowLite
4
+
5
+ public class FaceAntiSpoofing {
6
+ private static let modelFileName = "FaceAntiSpoofing"
7
+ private static let modelFileType = "tflite"
8
+ private static let imageWidth = 256
9
+ private static let imageHeight = 256
10
+ private static let laplaceThreshold = 50
11
+
12
+ public static let threshold: Float = 0.5
13
+ public static let laplacianThreshold = 0
14
+
15
+ private var interpreter: Interpreter?
16
+
17
+ public init() throws {
18
+ let modelPath = Tools.filePathForResourceName(name: modelFileName, extension: modelFileType)
19
+ var options = InterpreterOptions()
20
+ options.numberOfThreads = 4
21
+ interpreter = try Interpreter(modelPath: modelPath, options: options)
22
+ try interpreter?.allocateTensors()
23
+ }
24
+
25
+ public func antiSpoofing(image: UIImage) -> Float {
26
+ guard let interpreter = interpreter else {
27
+ return 0
28
+ }
29
+
30
+ let size = CGSize(width: CGFloat(Self.imageWidth), height: CGFloat(Self.imageHeight))
31
+ guard let imageScale = Tools.scaleImage(image: image, toSize: size),
32
+ let data = processData(image: imageScale) else {
33
+ return 0
34
+ }
35
+
36
+ do {
37
+ try interpreter.copy(data, toInputAt: 0)
38
+ try interpreter.invoke()
39
+
40
+ let clss_pred = try interpreter.output(at: 0) as [Float]
41
+ let leaf_node_mask = try interpreter.output(at: 1) as [Float]
42
+
43
+ var score: Float = 0
44
+ for i in 0..<8 {
45
+ score += abs(clss_pred[i]) * leaf_node_mask[i]
46
+ }
47
+
48
+ return score
49
+ } catch {
50
+ print("Error running inference: \(error)")
51
+ return 0
52
+ }
53
+ }
54
+
55
+ private func processData(image: UIImage) -> Data? {
56
+ guard let image_data = Tools.convertUIImageToBitmapRGBA8(image: image) else {
57
+ return nil
58
+ }
59
+
60
+ let input_std: Float = 255.0
61
+ var floats = [Float](repeating: 0, count: Self.imageWidth * Self.imageHeight * 3)
62
+
63
+ var k = 0
64
+ let size = Self.imageWidth * Self.imageHeight * 4
65
+ for j in 0..<size {
66
+ if j % 4 == 3 {
67
+ continue
68
+ }
69
+ floats[k] = Float(image_data[j]) / input_std
70
+ k += 1
71
+ }
72
+ image_data.deallocate()
73
+
74
+ return Data(bytes: floats, count: MemoryLayout<Float>.stride * floats.count)
75
+ }
76
+
77
+ public func laplacian(image: UIImage) -> Int {
78
+ let size = CGSize(width: CGFloat(Self.imageWidth), height: CGFloat(Self.imageHeight))
79
+ guard let imageScale = Tools.scaleImage(image: image, toSize: size),
80
+ let image_data = Tools.convertUIImageToBitmapGray(image: imageScale) else {
81
+ return 0
82
+ }
83
+
84
+ let laplace: [[Int]] = [[0, 1, 0], [1, -4, 1], [0, 1, 0]]
85
+ let laplaceSize = 3
86
+
87
+ var score = 0
88
+ for x in 0..<(Self.imageHeight - laplaceSize + 1) {
89
+ for y in 0..<(Self.imageWidth - laplaceSize + 1) {
90
+ var result = 0
91
+ for i in 0..<laplaceSize {
92
+ for j in 0..<laplaceSize {
93
+ result += (Int(image_data[(x + i) * Self.imageWidth + y + j]) & 0xFF) * laplace[i][j]
94
+ }
95
+ }
96
+ if result > Self.laplaceThreshold {
97
+ score += 1
98
+ }
99
+ }
100
+ }
101
+ image_data.deallocate()
102
+
103
+ return score
104
+ }
105
+ }
@@ -0,0 +1,166 @@
1
+ import Foundation
2
+ import Capacitor
3
+ import UIKit
4
+
5
+ /**
6
+ * Face Anti-Spoofing Plugin for Capacitor
7
+ * Provides passive liveness detection using TensorFlow Lite
8
+ */
9
+ @objc(FaceAntiSpoofingPlugin)
10
+ public class FaceAntiSpoofingPlugin: CAPPlugin, CAPBridgedPlugin {
11
+ public let identifier = "FaceAntiSpoofingPlugin"
12
+ public let jsName = "FaceAntiSpoofing"
13
+ public let pluginMethods: [CAPPluginMethod] = [
14
+ CAPPluginMethod(name: "detect", returnType: CAPPluginReturnPromise)
15
+ ]
16
+
17
+ private var faceAntiSpoofing: FaceAntiSpoofing?
18
+ private var mtcnn: MTCNN?
19
+
20
+ override public func load() {
21
+ do {
22
+ faceAntiSpoofing = try FaceAntiSpoofing()
23
+ mtcnn = try MTCNN()
24
+ print("[FaceAntiSpoofing] Models loaded successfully")
25
+ } catch {
26
+ print("[FaceAntiSpoofing] Failed to load models: \(error)")
27
+ }
28
+ }
29
+
30
+ @objc func detect(_ call: CAPPluginCall) {
31
+ guard let image = call.getString("image"), !image.isEmpty else {
32
+ call.reject("Image data is required")
33
+ return
34
+ }
35
+
36
+ guard let fas = faceAntiSpoofing, let mtcnn = mtcnn else {
37
+ call.reject("Models not loaded. Please ensure the plugin is properly initialized.")
38
+ return
39
+ }
40
+
41
+ do {
42
+ guard let uiImage = decodeImage(from: image) else {
43
+ call.reject("Failed to decode image")
44
+ return
45
+ }
46
+
47
+ let result = performAntiSpoofing(on: uiImage, fas: fas, mtcnn: mtcnn)
48
+ call.resolve(resultToJSObject(result))
49
+ } catch {
50
+ call.reject("Error during detection: \(error.localizedDescription)")
51
+ }
52
+ }
53
+
54
+ private func decodeImage(from imageData: String) -> UIImage? {
55
+ // Check if it's a base64 string with data URI prefix
56
+ if imageData.hasPrefix("data:image/") {
57
+ if let commaIndex = imageData.range(of: ",")?.upperBound {
58
+ let base64String = String(imageData[commaIndex...])
59
+ if let imageData = Data(base64Encoded: base64String) {
60
+ return UIImage(data: imageData)
61
+ }
62
+ }
63
+ } else if imageData.hasPrefix("file://") {
64
+ let filePath = String(imageData.dropFirst(7))
65
+ let url = URL(fileURLWithPath: filePath)
66
+ if let imageData = try? Data(contentsOf: url) {
67
+ return UIImage(data: imageData)
68
+ }
69
+ } else {
70
+ // Try as raw base64
71
+ if let imageData = Data(base64Encoded: imageData) {
72
+ return UIImage(data: imageData)
73
+ }
74
+ }
75
+ return nil
76
+ }
77
+
78
+ private func performAntiSpoofing(on image: UIImage, fas: FaceAntiSpoofing, mtcnn: MTCNN) -> DetectionResult {
79
+ var result = DetectionResult()
80
+
81
+ let boxes = mtcnn.detectFaces(image: image, minFaceSize: image.size.width / 8)
82
+
83
+ if boxes.isEmpty {
84
+ result.error = true
85
+ result.message = "No faces detected"
86
+ return result
87
+ }
88
+
89
+ guard let box1 = boxes.first else {
90
+ result.error = true
91
+ result.message = "No faces detected"
92
+ return result
93
+ }
94
+
95
+ let alignedImage = Align.faceAlign(image: image, landmark: box1.landmark)
96
+ let boxes2 = mtcnn.detectFaces(image: alignedImage, minFaceSize: alignedImage.size.width / 8)
97
+
98
+ if boxes2.isEmpty {
99
+ result.error = true
100
+ result.message = "No faces detected"
101
+ return result
102
+ }
103
+
104
+ guard let box2 = boxes2.first else {
105
+ result.error = true
106
+ result.message = "No faces detected"
107
+ return result
108
+ }
109
+
110
+ var box = box2
111
+ box.toSquareShape()
112
+ box.limitSquare(w: Int(alignedImage.size.width), h: Int(alignedImage.size.height))
113
+
114
+ let rect = box.transform2Rect()
115
+ guard let croppedImage = Tools.cropImage(image: alignedImage, toRect: rect) else {
116
+ result.error = true
117
+ result.message = "Failed to crop image"
118
+ return result
119
+ }
120
+
121
+ let laplace = fas.laplacian(image: croppedImage)
122
+
123
+ if laplace < FaceAntiSpoofing.laplacianThreshold {
124
+ result.error = false
125
+ result.score = "\(laplace)"
126
+ result.threshold = "\(FaceAntiSpoofing.laplacianThreshold)"
127
+ result.liveness = false
128
+ result.message = ""
129
+ } else {
130
+ let score = fas.antiSpoofing(image: croppedImage)
131
+ if score < FaceAntiSpoofing.threshold {
132
+ result.error = false
133
+ result.score = "\(score)"
134
+ result.threshold = "\(FaceAntiSpoofing.threshold)"
135
+ result.liveness = true
136
+ result.message = ""
137
+ } else {
138
+ result.error = false
139
+ result.score = "\(score)"
140
+ result.threshold = "\(FaceAntiSpoofing.threshold)"
141
+ result.liveness = false
142
+ result.message = "Sorry, we can't find a face or indicate that the photo is real."
143
+ }
144
+ }
145
+
146
+ return result
147
+ }
148
+
149
+ private func resultToJSObject(_ result: DetectionResult) -> [String: Any] {
150
+ return [
151
+ "error": result.error,
152
+ "liveness": result.liveness,
153
+ "score": result.score,
154
+ "threshold": result.threshold,
155
+ "message": result.message
156
+ ]
157
+ }
158
+
159
+ private struct DetectionResult {
160
+ var error = false
161
+ var liveness = false
162
+ var score = "0"
163
+ var threshold = "0"
164
+ var message = ""
165
+ }
166
+ }