@capacitor-mlkit/selfie-segmentation 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapacitorMlkitSelfieSegmentation.podspec +19 -0
- package/LICENSE +201 -0
- package/README.md +112 -0
- package/android/build.gradle +60 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/mlkit/selfiesegmentation/ProcessImageResultCallback.java +11 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/mlkit/selfiesegmentation/SelfieSegmentation.java +132 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/mlkit/selfiesegmentation/SelfieSegmentationPlugin.java +88 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/mlkit/selfiesegmentation/classes/ProcessImageOptions.java +47 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/mlkit/selfiesegmentation/classes/ProcessImageResult.java +29 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +156 -0
- package/dist/esm/definitions.d.ts +65 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +6 -0
- package/dist/esm/web.js +10 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +26 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +29 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/Classes/ProcessImageOptions.swift +64 -0
- package/ios/Plugin/Classes/ProcessImageResult.swift +30 -0
- package/ios/Plugin/Info.plist +24 -0
- package/ios/Plugin/SelfieSegmentation.swift +199 -0
- package/ios/Plugin/SelfieSegmentationPlugin.h +10 -0
- package/ios/Plugin/SelfieSegmentationPlugin.m +8 -0
- package/ios/Plugin/SelfieSegmentationPlugin.swift +61 -0
- package/package.json +91 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import MLKitVision
|
|
3
|
+
import MLKitSegmentationSelfie
|
|
4
|
+
|
|
5
|
+
@objc public class SelfieSegmentation: NSObject {
|
|
6
|
+
public let plugin: SelfieSegmentationPlugin
|
|
7
|
+
|
|
8
|
+
init(plugin: SelfieSegmentationPlugin) {
|
|
9
|
+
self.plugin = plugin
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@objc func createImageFromFilePath(_ path: String) -> UIImage? {
|
|
13
|
+
guard let url = URL.init(string: path) else {
|
|
14
|
+
return nil
|
|
15
|
+
}
|
|
16
|
+
if FileManager.default.fileExists(atPath: url.path) {
|
|
17
|
+
return UIImage.init(contentsOfFile: url.path)
|
|
18
|
+
} else {
|
|
19
|
+
return nil
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
enum ProcessError: Error {
|
|
24
|
+
case createImageBuffer
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private var segmenter: Segmenter?
|
|
28
|
+
|
|
29
|
+
@objc func processImage(_ options: ProcessImageOptions, completion: @escaping (ProcessImageResult?, Error?) -> Void) {
|
|
30
|
+
let image = options.getImage()
|
|
31
|
+
let threshold = options.getConfidence()
|
|
32
|
+
|
|
33
|
+
let visionImage = VisionImage.init(image: image)
|
|
34
|
+
visionImage.orientation = image.imageOrientation
|
|
35
|
+
|
|
36
|
+
let selfieSegmenterOptions: SelfieSegmenterOptions = SelfieSegmenterOptions()
|
|
37
|
+
selfieSegmenterOptions.segmenterMode = .singleImage
|
|
38
|
+
|
|
39
|
+
segmenter = Segmenter.segmenter(
|
|
40
|
+
options: selfieSegmenterOptions
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
segmenter?.process(visionImage) { mask, error in
|
|
44
|
+
self.segmenter = nil
|
|
45
|
+
|
|
46
|
+
guard error == nil, let mask = mask else {
|
|
47
|
+
return completion(nil, error)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
do {
|
|
51
|
+
guard let imageBuffer = self.createImageBuffer(from: image) else {
|
|
52
|
+
throw ProcessError.createImageBuffer
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
self.applySegmentationMask(
|
|
56
|
+
mask: mask, to: imageBuffer, threshold: threshold
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
let image = self.createImage(from: imageBuffer)
|
|
60
|
+
let result = ProcessImageResult(image: image)
|
|
61
|
+
|
|
62
|
+
completion(result, nil)
|
|
63
|
+
} catch {
|
|
64
|
+
completion(nil, error)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func createImageBuffer(from image: UIImage) -> CVImageBuffer? {
|
|
70
|
+
guard let cgImage = image.cgImage else { return nil }
|
|
71
|
+
let width = cgImage.width
|
|
72
|
+
let height = cgImage.height
|
|
73
|
+
|
|
74
|
+
var buffer: CVPixelBuffer?
|
|
75
|
+
CVPixelBufferCreate(
|
|
76
|
+
kCFAllocatorDefault,
|
|
77
|
+
width,
|
|
78
|
+
height,
|
|
79
|
+
kCVPixelFormatType_32BGRA,
|
|
80
|
+
nil,
|
|
81
|
+
&buffer)
|
|
82
|
+
guard let imageBuffer = buffer else { return nil }
|
|
83
|
+
|
|
84
|
+
let flags = CVPixelBufferLockFlags(rawValue: 0)
|
|
85
|
+
CVPixelBufferLockBaseAddress(imageBuffer, flags)
|
|
86
|
+
let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
|
|
87
|
+
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
88
|
+
let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
|
|
89
|
+
let context = CGContext(
|
|
90
|
+
data: baseAddress,
|
|
91
|
+
width: width,
|
|
92
|
+
height: height,
|
|
93
|
+
bitsPerComponent: 8,
|
|
94
|
+
bytesPerRow: bytesPerRow,
|
|
95
|
+
space: colorSpace,
|
|
96
|
+
bitmapInfo: (CGImageAlphaInfo.premultipliedFirst.rawValue
|
|
97
|
+
| CGBitmapInfo.byteOrder32Little.rawValue))
|
|
98
|
+
|
|
99
|
+
if let context = context {
|
|
100
|
+
let rect = CGRect.init(x: 0, y: 0, width: width, height: height)
|
|
101
|
+
context.draw(cgImage, in: rect)
|
|
102
|
+
CVPixelBufferUnlockBaseAddress(imageBuffer, flags)
|
|
103
|
+
return imageBuffer
|
|
104
|
+
} else {
|
|
105
|
+
CVPixelBufferUnlockBaseAddress(imageBuffer, flags)
|
|
106
|
+
return nil
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// func createSampleBuffer(with imageBuffer: CVImageBuffer) -> CMSampleBuffer? {
|
|
111
|
+
// var timingInfo = CMSampleTimingInfo()
|
|
112
|
+
//// guard CMSampleBufferGetSampleTimingInfo(sampleBuffer, at: 0, timingInfoOut: &timingInfo) == 0 else {
|
|
113
|
+
//// return nil
|
|
114
|
+
//// }
|
|
115
|
+
// var outputSampleBuffer: CMSampleBuffer?
|
|
116
|
+
// var newFormatDescription: CMFormatDescription?
|
|
117
|
+
// CMVideoFormatDescriptionCreateForImageBuffer(allocator: nil, imageBuffer: imageBuffer, formatDescriptionOut: &newFormatDescription)
|
|
118
|
+
// guard let formatDescription = newFormatDescription else {
|
|
119
|
+
// return nil
|
|
120
|
+
// }
|
|
121
|
+
// CMSampleBufferCreateReadyWithImageBuffer(allocator: nil, imageBuffer: imageBuffer, formatDescription: formatDescription, sampleTiming: &timingInfo, sampleBufferOut: &outputSampleBuffer)
|
|
122
|
+
// guard let buffer = outputSampleBuffer else {
|
|
123
|
+
// return nil
|
|
124
|
+
// }
|
|
125
|
+
// return buffer
|
|
126
|
+
// }
|
|
127
|
+
|
|
128
|
+
func createImage(
|
|
129
|
+
from imageBuffer: CVImageBuffer
|
|
130
|
+
) -> UIImage {
|
|
131
|
+
let ciImage = CIImage(cvPixelBuffer: imageBuffer)
|
|
132
|
+
let context = CIContext(options: nil)
|
|
133
|
+
let cgImage = context.createCGImage(ciImage, from: ciImage.extent)!
|
|
134
|
+
return UIImage(cgImage: cgImage)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
func applySegmentationMask(
|
|
138
|
+
mask: SegmentationMask, to imageBuffer: CVImageBuffer, threshold: CGFloat
|
|
139
|
+
) {
|
|
140
|
+
let bgraBytesPerPixel = 4
|
|
141
|
+
|
|
142
|
+
assert(
|
|
143
|
+
CVPixelBufferGetPixelFormatType(imageBuffer) == kCVPixelFormatType_32BGRA,
|
|
144
|
+
"Image buffer must have 32BGRA pixel format type")
|
|
145
|
+
|
|
146
|
+
let width = CVPixelBufferGetWidth(mask.buffer)
|
|
147
|
+
let height = CVPixelBufferGetHeight(mask.buffer)
|
|
148
|
+
assert(CVPixelBufferGetWidth(imageBuffer) == width, "Width must match")
|
|
149
|
+
assert(CVPixelBufferGetHeight(imageBuffer) == height, "Height must match")
|
|
150
|
+
|
|
151
|
+
let writeFlags = CVPixelBufferLockFlags(rawValue: 0)
|
|
152
|
+
CVPixelBufferLockBaseAddress(imageBuffer, writeFlags)
|
|
153
|
+
CVPixelBufferLockBaseAddress(mask.buffer, CVPixelBufferLockFlags.readOnly)
|
|
154
|
+
|
|
155
|
+
let maskBytesPerRow = CVPixelBufferGetBytesPerRow(mask.buffer)
|
|
156
|
+
var maskAddress =
|
|
157
|
+
CVPixelBufferGetBaseAddress(mask.buffer)!.bindMemory(
|
|
158
|
+
to: Float32.self, capacity: maskBytesPerRow * height)
|
|
159
|
+
|
|
160
|
+
let imageBytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
|
|
161
|
+
var imageAddress = CVPixelBufferGetBaseAddress(imageBuffer)!.bindMemory(
|
|
162
|
+
to: UInt8.self, capacity: imageBytesPerRow * height)
|
|
163
|
+
|
|
164
|
+
for _ in 0...(height - 1) {
|
|
165
|
+
for col in 0...(width - 1) {
|
|
166
|
+
let pixelOffset = col * bgraBytesPerPixel
|
|
167
|
+
let blueOffset = pixelOffset
|
|
168
|
+
let greenOffset = pixelOffset + 1
|
|
169
|
+
let redOffset = pixelOffset + 2
|
|
170
|
+
let alphaOffset = pixelOffset + 3
|
|
171
|
+
|
|
172
|
+
let confidence: CGFloat = CGFloat(maskAddress[col])
|
|
173
|
+
|
|
174
|
+
if confidence >= threshold {
|
|
175
|
+
let red = CGFloat(imageAddress[redOffset])
|
|
176
|
+
let green = CGFloat(imageAddress[greenOffset])
|
|
177
|
+
let blue = CGFloat(imageAddress[blueOffset])
|
|
178
|
+
// let alpha = CGFloat(imageAddress[alphaOffset])
|
|
179
|
+
|
|
180
|
+
imageAddress[redOffset] = UInt8(red * confidence)
|
|
181
|
+
imageAddress[greenOffset] = UInt8(green * confidence)
|
|
182
|
+
imageAddress[blueOffset] = UInt8(blue * confidence)
|
|
183
|
+
imageAddress[alphaOffset] = UInt8(0xff)
|
|
184
|
+
} else {
|
|
185
|
+
imageAddress[redOffset] = UInt8(0x00)
|
|
186
|
+
imageAddress[greenOffset] = UInt8(0x00)
|
|
187
|
+
imageAddress[blueOffset] = UInt8(0x00)
|
|
188
|
+
imageAddress[alphaOffset] = UInt8(0x00)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
imageAddress += imageBytesPerRow / MemoryLayout<UInt8>.size
|
|
193
|
+
maskAddress += maskBytesPerRow / MemoryLayout<Float32>.size
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
CVPixelBufferUnlockBaseAddress(imageBuffer, writeFlags)
|
|
197
|
+
CVPixelBufferUnlockBaseAddress(mask.buffer, CVPixelBufferLockFlags.readOnly)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
|
|
3
|
+
//! Project version number for Plugin.
|
|
4
|
+
FOUNDATION_EXPORT double PluginVersionNumber;
|
|
5
|
+
|
|
6
|
+
//! Project version string for Plugin.
|
|
7
|
+
FOUNDATION_EXPORT const unsigned char PluginVersionString[];
|
|
8
|
+
|
|
9
|
+
// In this header, you should import all the public headers of your framework using statements like #import <Plugin/PublicHeader.h>
|
|
10
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
#import <Capacitor/Capacitor.h>
|
|
3
|
+
|
|
4
|
+
// Define the plugin using the CAP_PLUGIN Macro, and
|
|
5
|
+
// each method the plugin supports using the CAP_PLUGIN_METHOD macro.
|
|
6
|
+
CAP_PLUGIN(SelfieSegmentationPlugin, "SelfieSegmentation",
|
|
7
|
+
CAP_PLUGIN_METHOD(processImage, CAPPluginReturnPromise);
|
|
8
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Please read the Capacitor iOS Plugin Development Guide
|
|
6
|
+
* here: https://capacitorjs.com/docs/plugins/ios
|
|
7
|
+
*/
|
|
8
|
+
@objc(SelfieSegmentationPlugin)
|
|
9
|
+
public class SelfieSegmentationPlugin: CAPPlugin {
|
|
10
|
+
public let tag = "SelfieSegmentation"
|
|
11
|
+
|
|
12
|
+
public let errorPathMissing = "path must be provided."
|
|
13
|
+
public let errorLoadImageFailed = "The image could not be loaded."
|
|
14
|
+
public let errorWriteFileFailed = "The result could not be created."
|
|
15
|
+
|
|
16
|
+
public let defaultConfidence: Float = 0.9
|
|
17
|
+
|
|
18
|
+
private var implementation: SelfieSegmentation?
|
|
19
|
+
|
|
20
|
+
override public func load() {
|
|
21
|
+
implementation = SelfieSegmentation(plugin: self)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@objc func processImage(_ call: CAPPluginCall) {
|
|
25
|
+
guard let path = call.getString("path") else {
|
|
26
|
+
call.reject(errorPathMissing)
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let width = call.getInt("width")
|
|
31
|
+
let height = call.getInt("height")
|
|
32
|
+
|
|
33
|
+
let confidence = call.getFloat("confidence", defaultConfidence)
|
|
34
|
+
|
|
35
|
+
guard let image = implementation?.createImageFromFilePath(path) else {
|
|
36
|
+
call.reject(errorLoadImageFailed)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let options = ProcessImageOptions(image: image,
|
|
41
|
+
width: width,
|
|
42
|
+
height: height,
|
|
43
|
+
confidence: CGFloat(confidence))
|
|
44
|
+
|
|
45
|
+
implementation?.processImage(options, completion: { result, error in
|
|
46
|
+
if let error = error {
|
|
47
|
+
CAPLog.print("[", self.tag, "] ", error)
|
|
48
|
+
call.reject(error.localizedDescription, nil, error)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if let result = result {
|
|
53
|
+
do {
|
|
54
|
+
call.resolve(try result.toJSObject())
|
|
55
|
+
} catch {
|
|
56
|
+
call.reject(self.errorWriteFileFailed)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capacitor-mlkit/selfie-segmentation",
|
|
3
|
+
"version": "5.2.0",
|
|
4
|
+
"description": "Capacitor plugin for ML Kit Selfie Segmentation.",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"dist/",
|
|
13
|
+
"ios/Plugin/",
|
|
14
|
+
"CapacitorMlkitSelfieSegmentation.podspec"
|
|
15
|
+
],
|
|
16
|
+
"author": "Robin Genz <mail@robingenz.dev>",
|
|
17
|
+
"license": "Apache-2.0",
|
|
18
|
+
"homepage": "https://capawesome.io/",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/capawesome-team/capacitor-mlkit.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/capawesome-team/capacitor-mlkit/issues"
|
|
25
|
+
},
|
|
26
|
+
"funding": [
|
|
27
|
+
{
|
|
28
|
+
"type": "github",
|
|
29
|
+
"url": "https://github.com/sponsors/capawesome-team/"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"type": "opencollective",
|
|
33
|
+
"url": "https://opencollective.com/capawesome"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"keywords": [
|
|
37
|
+
"capacitor",
|
|
38
|
+
"plugin",
|
|
39
|
+
"native"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
43
|
+
"verify:ios": "cd ios && pod install && xcodebuild -workspace Plugin.xcworkspace -scheme Plugin -destination generic/platform=iOS && cd ..",
|
|
44
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
45
|
+
"verify:web": "npm run build",
|
|
46
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
47
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
48
|
+
"eslint": "eslint . --ext ts",
|
|
49
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\"",
|
|
50
|
+
"swiftlint": "node-swiftlint",
|
|
51
|
+
"docgen": "docgen --api SelfieSegmentationPlugin --output-readme README.md --output-json dist/docs.json",
|
|
52
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.js",
|
|
53
|
+
"clean": "rimraf ./dist",
|
|
54
|
+
"watch": "tsc --watch",
|
|
55
|
+
"prepublishOnly": "npm run build"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@capacitor/android": "5.0.0",
|
|
59
|
+
"@capacitor/cli": "5.0.0",
|
|
60
|
+
"@capacitor/core": "5.0.0",
|
|
61
|
+
"@capacitor/docgen": "0.2.1",
|
|
62
|
+
"@capacitor/ios": "5.0.0",
|
|
63
|
+
"@ionic/eslint-config": "0.3.0",
|
|
64
|
+
"@ionic/swiftlint-config": "1.1.2",
|
|
65
|
+
"eslint": "7.32.0",
|
|
66
|
+
"prettier": "2.3.2",
|
|
67
|
+
"prettier-plugin-java": "1.0.2",
|
|
68
|
+
"rimraf": "3.0.2",
|
|
69
|
+
"rollup": "2.77.2",
|
|
70
|
+
"swiftlint": "1.0.1",
|
|
71
|
+
"typescript": "4.1.5"
|
|
72
|
+
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"@capacitor/core": "^5.0.0"
|
|
75
|
+
},
|
|
76
|
+
"swiftlint": "@ionic/swiftlint-config",
|
|
77
|
+
"eslintConfig": {
|
|
78
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
79
|
+
},
|
|
80
|
+
"capacitor": {
|
|
81
|
+
"ios": {
|
|
82
|
+
"src": "ios"
|
|
83
|
+
},
|
|
84
|
+
"android": {
|
|
85
|
+
"src": "android"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"publishConfig": {
|
|
89
|
+
"access": "public"
|
|
90
|
+
}
|
|
91
|
+
}
|